├── .gitignore ├── LICENSE ├── README.md ├── app ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── ic_launcher-web.png │ ├── java │ └── com │ │ └── github │ │ └── ykc3 │ │ └── android │ │ └── usbi2c │ │ └── app │ │ ├── I2cAdapterListActivity.java │ │ ├── I2cDeviceListActivity.java │ │ ├── I2cDeviceListFragment.java │ │ ├── device │ │ ├── handler │ │ │ ├── AbstractI2cDeviceHandler.java │ │ │ ├── Bme280Handler.java │ │ │ ├── I2cDeviceHandler.java │ │ │ └── I2cDeviceHandlerRegistry.java │ │ └── info │ │ │ ├── I2cDeviceInfo.java │ │ │ └── I2cDeviceInfoRegistry.java │ │ └── view │ │ └── CustomRecyclerView.java │ └── res │ ├── drawable │ └── ic_outline_info_24.xml │ ├── layout-w768dp │ └── adapter_list.xml │ ├── layout │ ├── about.xml │ ├── activity_adapter_list.xml │ ├── activity_device_list.xml │ ├── adapter_list.xml │ ├── adapter_list_content.xml │ ├── device_list.xml │ └── device_list_content.xml │ ├── menu │ └── toolbar.xml │ ├── mipmap-anydpi-v26 │ ├── ic_launcher.xml │ └── ic_launcher_round.xml │ ├── mipmap-hdpi │ ├── ic_launcher.png │ ├── ic_launcher_foreground.png │ └── ic_launcher_round.png │ ├── mipmap-mdpi │ ├── ic_launcher.png │ ├── ic_launcher_foreground.png │ └── ic_launcher_round.png │ ├── mipmap-xhdpi │ ├── ic_launcher.png │ ├── ic_launcher_foreground.png │ └── ic_launcher_round.png │ ├── mipmap-xxhdpi │ ├── ic_launcher.png │ ├── ic_launcher_foreground.png │ └── ic_launcher_round.png │ ├── mipmap-xxxhdpi │ ├── ic_launcher.png │ ├── ic_launcher_foreground.png │ └── ic_launcher_round.png │ ├── raw │ └── devices.json │ └── values │ ├── colors.xml │ ├── dimens.xml │ ├── ic_launcher_background.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── jitpack.yml ├── lib ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── github │ │ └── ykc3 │ │ └── android │ │ └── usbi2c │ │ ├── UsbI2cAdapter.java │ │ ├── UsbI2cDevice.java │ │ ├── UsbI2cManager.java │ │ └── adapter │ │ ├── BaseUsbI2cAdapter.java │ │ ├── Ch341UsbI2cAdapter.java │ │ ├── Cp2112UsbI2cAdapter.java │ │ ├── Ft232hUsbI2cAdapter.java │ │ ├── Ft260UsbI2cAdapter.java │ │ ├── HidUsbI2cAdapter.java │ │ └── TinyUsbI2cAdapter.java │ └── res │ ├── values │ └── strings.xml │ └── xml │ └── usb_device_filter.xml └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .idea 3 | .gradle 4 | build/ 5 | /local.properties 6 | .DS_Store 7 | /captures 8 | .externalNativeBuild 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # usb-i2c-android 2 | 3 | [![](https://jitpack.io/v/3cky/usb-i2c-android.svg)](https://jitpack.io/#3cky/usb-i2c-android) 4 | 5 | This is a library for communication with [I²C](https://en.wikipedia.org/wiki/I%C2%B2C) devices on Android using USB I²C adapters connected to the 6 | [Android USB Host (OTG)](http://developer.android.com/guide/topics/connectivity/usb/host.html). 7 | No root access or special kernel drivers are required. 8 | 9 | ## Supported adapters 10 | 11 | * [I2C-Tiny-USB](https://github.com/harbaum/I2C-Tiny-USB) 12 | * [Silicon Labs CP2112](https://www.silabs.com/documents/public/data-sheets/cp2112-datasheet.pdf) 13 | * [Qinheng Microelectronics CH341](https://www.wch-ic.com/products/CH341.html) 14 | * [Future Technology Devices International FT232H](https://ftdichip.com/wp-content/uploads/2024/09/DS_FT2232H.pdf) 15 | * [Future Technology Devices International FT260](https://www.ftdichip.com/Support/Documents/DataSheets/ICs/DS_FT260.pdf) 16 | 17 | ## Usage 18 | 19 | Add jitpack.io repository to your root build.gradle: 20 | ```gradle 21 | allprojects { 22 | repositories { 23 | ... 24 | maven { url 'https://jitpack.io' } 25 | } 26 | } 27 | ``` 28 | Add library to dependencies: 29 | ```gradle 30 | dependencies { 31 | implementation 'com.github.3cky:usb-i2c-android:1.4.0' 32 | } 33 | ``` 34 | 35 | Add USB host feature usage to your app manifest: 36 | 37 | ```xml 38 | 39 | 40 | 41 | ... 42 | 43 | ``` 44 | 45 | Example code: 46 | 47 | ```java 48 | import com.github.ykc3.android.usbi2c.UsbI2cAdapter; 49 | import com.github.ykc3.android.usbi2c.UsbI2cDevice; 50 | import com.github.ykc3.android.usbi2c.UsbI2cManager; 51 | ... 52 | 53 | // Get Android UsbManager 54 | UsbManager usbManager = (UsbManager) getSystemService(Context.USB_SERVICE); 55 | 56 | // Find all connected I²C adapters 57 | UsbI2cManager usbI2cManager = UsbI2cManager.create(usbManager).build(); 58 | List i2cAdapters = usbI2cManager.getAdapters(); 59 | if (i2cAdapters.isEmpty()) { 60 | return; 61 | } 62 | 63 | // Get first adapter 64 | UsbI2cAdapter i2cAdapter = i2cAdapters.get(0); 65 | 66 | // Request USB access permission 67 | usbManager.requestPermission(i2cAdapter.getUsbDevice(), usbPermissionIntent); 68 | ... 69 | // USB permission intent handler called with success result 70 | 71 | // Set bus clock speed to 400 kbit/s, if supported by adapter 72 | if (i2cAdapter.isClockSpeedSupported(UsbI2cAdapter.CLOCK_SPEED_FAST)) { 73 | i2cAdapter.setClockSpeed(UsbI2cAdapter.CLOCK_SPEED_FAST); 74 | } 75 | 76 | // Open adapter 77 | i2cAdapter.open(); 78 | 79 | // Get device with I²C address 0x42 80 | UsbI2cDevice i2cDevice = i2cAdapter.getDevice(0x42); 81 | 82 | // Read device register 0x01. 83 | // Throws java.lang.IOException if device is not connected or I/O error caused 84 | byte value = i2cDevice.readRegByte(0x01); 85 | 86 | // Close adapter 87 | i2cAdapter.close(); 88 | ``` 89 | 90 | ## Sample app 91 | 92 | For simple I²C device scanner sample app, check an `app` module. 93 | 94 | ## License 95 | 96 | This library is licensed under *LGPL Version 2.1*. Please see LICENSE.txt for the 97 | complete license. 98 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'maven-publish' 3 | 4 | android { 5 | namespace 'com.github.ykc3.android.usbi2c.app' 6 | 7 | defaultConfig { 8 | applicationId "com.github.ykc3.android.usbi2c.app" 9 | 10 | minSdkVersion versions.sdk.min 11 | targetSdkVersion versions.sdk.target 12 | compileSdk versions.sdk.compile 13 | 14 | versionCode versions.project.major * 1000000 + versions.project.minor * 1000 + versions.project.patch 15 | versionName versions.project.number 16 | } 17 | 18 | // Release management 19 | signingConfigs { 20 | release { 21 | File propsFile = project.rootProject.file('local.properties') 22 | if (propsFile.exists()) { 23 | Properties properties = new Properties() 24 | properties.load(propsFile.newDataInputStream()) 25 | keyAlias properties.getProperty('key.alias') 26 | keyPassword properties.getProperty('key.password') 27 | storeFile file(properties.getOrDefault('key.store.file', 'release.keystore')) 28 | storePassword properties.getProperty('key.store.password') 29 | } 30 | } 31 | } 32 | 33 | buildTypes { 34 | debug { 35 | versionNameSuffix "-debug" 36 | } 37 | release { 38 | minifyEnabled false 39 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 40 | 'proguard-rules.pro' 41 | signingConfig signingConfigs.release 42 | } 43 | } 44 | 45 | publishing { 46 | singleVariant("release") 47 | } 48 | } 49 | 50 | afterEvaluate { 51 | publishing { 52 | publications { 53 | release(MavenPublication) { 54 | from components.release 55 | 56 | groupId 'com.github.3cky' 57 | artifactId 'usb-i2c-android-app' 58 | version versions.project.number 59 | 60 | artifact source: file('build/outputs/apk/release/app-release.apk'), 61 | classifier: 'signed', extension: 'apk' 62 | } 63 | } 64 | } 65 | publishReleasePublicationToMavenLocal.configure { 66 | mustRunAfter packageRelease 67 | } 68 | } 69 | 70 | dependencies { 71 | implementation fileTree(dir: 'libs', include: ['*.jar']) 72 | implementation 'androidx.appcompat:appcompat:1.7.0' 73 | implementation 'androidx.legacy:legacy-support-v4:1.0.0' 74 | implementation 'androidx.recyclerview:recyclerview:1.4.0' 75 | implementation 'com.google.android.material:material:1.12.0' 76 | implementation 'com.google.code.gson:gson:2.10.1' 77 | 78 | implementation project(':lib') 79 | } 80 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 14 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 30 | 31 | 36 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /app/src/main/ic_launcher-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/3cky/usb-i2c-android/956f40840044566fca4ed53c39cc04a4b3474454/app/src/main/ic_launcher-web.png -------------------------------------------------------------------------------- /app/src/main/java/com/github/ykc3/android/usbi2c/app/I2cAdapterListActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Victor Antonovich 3 | * 4 | * This work is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Lesser General Public License 6 | * as published by the Free Software Foundation; either version 2.1 7 | * of the License, or (at your option) any later version. 8 | * 9 | * This work is distributed in the hope that it will be useful, but 10 | * without any warranty; without even the implied warranty of merchantability 11 | * or fitness for a particular purpose. See the GNU Lesser General Public 12 | * License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with this library; if not, write to the Free Software Foundation, Inc., 16 | * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. 17 | */ 18 | 19 | package com.github.ykc3.android.usbi2c.app; 20 | 21 | import android.annotation.SuppressLint; 22 | import android.app.Dialog; 23 | import android.app.PendingIntent; 24 | import android.content.BroadcastReceiver; 25 | import android.content.Context; 26 | import android.content.Intent; 27 | import android.content.IntentFilter; 28 | import android.content.pm.PackageManager.NameNotFoundException; 29 | import android.hardware.usb.UsbDevice; 30 | import android.hardware.usb.UsbManager; 31 | import android.os.Build; 32 | import android.os.Bundle; 33 | import android.os.Handler; 34 | import android.util.Log; 35 | import android.view.LayoutInflater; 36 | import android.view.View; 37 | import android.view.ViewGroup; 38 | import android.view.Window; 39 | import android.widget.TextView; 40 | import android.widget.Toast; 41 | 42 | import androidx.annotation.NonNull; 43 | import androidx.appcompat.app.AppCompatActivity; 44 | import androidx.appcompat.widget.Toolbar; 45 | import androidx.recyclerview.widget.RecyclerView; 46 | import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; 47 | 48 | import com.github.ykc3.android.usbi2c.UsbI2cAdapter; 49 | import com.github.ykc3.android.usbi2c.UsbI2cManager; 50 | import com.github.ykc3.android.usbi2c.app.view.CustomRecyclerView; 51 | import com.google.android.material.snackbar.Snackbar; 52 | 53 | import java.util.ArrayList; 54 | import java.util.List; 55 | 56 | /** 57 | * An activity representing a list of UsbI2cAdapters. This activity 58 | * has different presentations for handset and tablet-size devices. On 59 | * handsets, the activity presents a list of items, which when touched, 60 | * lead to a {@link I2cDeviceListActivity} representing 61 | * item details. On tablets, the activity presents the list of items and 62 | * item details side-by-side using two vertical panes. 63 | */ 64 | public class I2cAdapterListActivity extends AppCompatActivity { 65 | private final String TAG = I2cAdapterListActivity.class.getSimpleName(); 66 | 67 | /** 68 | * Whether or not the activity is in two-pane mode, i.e. running on a tablet 69 | * device. 70 | */ 71 | private boolean isTwoPane; 72 | 73 | private UsbManager usbManager; 74 | private UsbI2cManager usbI2cManager; 75 | 76 | private CustomRecyclerView recyclerView; 77 | private RecyclerViewAdapter recyclerViewAdapter; 78 | 79 | private SwipeRefreshLayout adapterListRefreshLayout; 80 | 81 | private PendingIntent usbPermissionIntent; 82 | 83 | public static final String ACTION_USB_PERMISSION = 84 | "com.github.ykc3.android.usbi2c.app.USB_PERMISSION"; 85 | 86 | private I2cDeviceListFragment i2cDeviceListFragment; 87 | 88 | private Dialog aboutDialog; 89 | 90 | private final BroadcastReceiver usbReceiver = new BroadcastReceiver() { 91 | public void onReceive(Context context, Intent intent) { 92 | String action = intent.getAction(); 93 | if (ACTION_USB_PERMISSION.equals(action)) { 94 | UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); 95 | boolean isGranted = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false); 96 | onUsbDevicePermission(usbDevice, isGranted); 97 | } else if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(action) || 98 | UsbManager.ACTION_USB_DEVICE_DETACHED.equals(action)) { 99 | onUsbDeviceChanged(); 100 | } 101 | } 102 | }; 103 | 104 | public class RecyclerViewAdapter extends RecyclerView.Adapter { 105 | private final List items = new ArrayList<>(); 106 | 107 | private final View.OnClickListener onClickListener = new View.OnClickListener() { 108 | @Override 109 | public void onClick(View view) { 110 | UsbI2cAdapter item = (UsbI2cAdapter) view.getTag(); 111 | usbManager.requestPermission(item.getUsbDevice(), usbPermissionIntent); 112 | } 113 | }; 114 | 115 | void updateItems(List items) { 116 | this.items.clear(); 117 | this.items.addAll(items); 118 | notifyDataSetChanged(); 119 | } 120 | 121 | void clearItems() { 122 | items.clear(); 123 | notifyDataSetChanged(); 124 | } 125 | 126 | @Override 127 | @NonNull 128 | public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { 129 | View view = LayoutInflater.from(parent.getContext()) 130 | .inflate(R.layout.adapter_list_content, parent, false); 131 | return new ViewHolder(view); 132 | } 133 | 134 | @Override 135 | public void onBindViewHolder(@NonNull final ViewHolder holder, int position) { 136 | UsbI2cAdapter usbI2cAdapter = items.get(position); 137 | UsbDevice usbDevice = usbI2cAdapter.getUsbDevice(); 138 | holder.adapterIdView.setText(String.format("%04x:%04x", usbDevice.getVendorId(), 139 | usbDevice.getProductId())); 140 | String adapterDeviceName = null; 141 | if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) { 142 | adapterDeviceName = usbDevice.getProductName(); 143 | } 144 | if (adapterDeviceName == null || adapterDeviceName.isEmpty()) { 145 | adapterDeviceName = usbDevice.getDeviceName(); 146 | } 147 | holder.adapterNameView.setText(String.format("%s (%s)", usbI2cAdapter.getName(), 148 | adapterDeviceName)); 149 | 150 | holder.itemView.setTag(items.get(position)); 151 | holder.itemView.setOnClickListener(onClickListener); 152 | } 153 | 154 | @Override 155 | public int getItemCount() { 156 | return items.size(); 157 | } 158 | 159 | class ViewHolder extends RecyclerView.ViewHolder { 160 | final TextView adapterIdView; 161 | final TextView adapterNameView; 162 | 163 | ViewHolder(View view) { 164 | super(view); 165 | adapterIdView = view.findViewById(R.id.adapter_id); 166 | adapterNameView = view.findViewById(R.id.adapter_name); 167 | } 168 | } 169 | } 170 | 171 | SwipeRefreshLayout.OnRefreshListener onRefreshListener = new SwipeRefreshLayout.OnRefreshListener() { 172 | @Override 173 | public void onRefresh() { 174 | hideI2cDeviceListPane(); 175 | recyclerViewAdapter.clearItems(); 176 | recyclerView.showEmptyView(false); 177 | final Snackbar snackbar = Snackbar.make(adapterListRefreshLayout, 178 | R.string.adapter_scan_info, Snackbar.LENGTH_INDEFINITE); 179 | snackbar.show(); 180 | new Handler().postDelayed(() -> { 181 | scanI2cAdapters(); 182 | recyclerView.showEmptyView(recyclerView.isEmpty()); 183 | adapterListRefreshLayout.setRefreshing(false); 184 | snackbar.dismiss(); 185 | }, 1000); 186 | } 187 | }; 188 | 189 | private void showI2cDeviceListPane(UsbDevice usbDevice) { 190 | if (isTwoPane) { 191 | hideI2cDeviceListPane(); 192 | Bundle arguments = new Bundle(); 193 | arguments.putParcelable(I2cDeviceListFragment.KEY_USB_DEVICE, usbDevice); 194 | i2cDeviceListFragment = new I2cDeviceListFragment(); 195 | i2cDeviceListFragment.setArguments(arguments); 196 | this.getSupportFragmentManager().beginTransaction() 197 | .replace(R.id.adapter_device_list_container, i2cDeviceListFragment) 198 | .commit(); 199 | } else { 200 | Intent intent = new Intent(this, I2cDeviceListActivity.class); 201 | intent.putExtra(I2cDeviceListFragment.KEY_USB_DEVICE, usbDevice); 202 | this.startActivity(intent); 203 | } 204 | } 205 | 206 | private void hideI2cDeviceListPane() { 207 | if (!isTwoPane || i2cDeviceListFragment == null) { 208 | return; 209 | } 210 | this.getSupportFragmentManager().beginTransaction() 211 | .remove(i2cDeviceListFragment) 212 | .commit(); 213 | i2cDeviceListFragment = null; 214 | this.getSupportFragmentManager().popBackStack(); 215 | } 216 | 217 | @SuppressLint("UnspecifiedRegisterReceiverFlag") 218 | @Override 219 | protected void onCreate(Bundle savedInstanceState) { 220 | super.onCreate(savedInstanceState); 221 | setContentView(R.layout.activity_adapter_list); 222 | 223 | Toolbar toolbar = findViewById(R.id.toolbar); 224 | toolbar.setOnMenuItemClickListener(item -> { 225 | showAboutDialog(); 226 | return true; 227 | }); 228 | 229 | if (findViewById(R.id.adapter_device_list_container) != null) { 230 | // The detail container view will be present only in the 231 | // large-screen layouts (res/values-w900dp). 232 | // If this view is present, then the 233 | // activity should be in two-pane mode. 234 | isTwoPane = true; 235 | } 236 | 237 | recyclerView = findViewById(R.id.adapter_list); 238 | recyclerViewAdapter = new RecyclerViewAdapter(); 239 | recyclerView.setAdapter(recyclerViewAdapter); 240 | recyclerView.setEmptyView(findViewById(R.id.empty_adapter_list)); 241 | recyclerView.showEmptyView(false); 242 | 243 | usbManager = (UsbManager) getSystemService(Context.USB_SERVICE); 244 | assert usbManager != null; 245 | 246 | usbI2cManager = UsbI2cManager.create(usbManager).build(); 247 | 248 | int flags = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? PendingIntent.FLAG_MUTABLE : 0; 249 | Intent intent = new Intent(ACTION_USB_PERMISSION); 250 | intent.setPackage(getPackageName()); 251 | usbPermissionIntent = PendingIntent.getBroadcast(this, 0, intent, flags); 252 | IntentFilter usbReceiverFilter = new IntentFilter(ACTION_USB_PERMISSION); 253 | usbReceiverFilter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED); 254 | usbReceiverFilter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED); 255 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { 256 | registerReceiver(usbReceiver, usbReceiverFilter, RECEIVER_EXPORTED); 257 | } else { 258 | registerReceiver(usbReceiver, usbReceiverFilter); 259 | } 260 | 261 | adapterListRefreshLayout = findViewById(R.id.adapter_list_refresh); 262 | adapterListRefreshLayout.setOnRefreshListener(onRefreshListener); 263 | 264 | refreshAdapterList(); 265 | } 266 | 267 | private void showAboutDialog() { 268 | aboutDialog = new Dialog(this); 269 | aboutDialog.setTitle(R.string.about); 270 | aboutDialog.requestWindowFeature(Window.FEATURE_LEFT_ICON); 271 | aboutDialog.setContentView(R.layout.about); 272 | aboutDialog.getWindow().setFeatureDrawableResource(Window.FEATURE_LEFT_ICON, 273 | android.R.drawable.ic_dialog_info); 274 | String versionString; 275 | try { 276 | versionString = getResources().getString(R.string.about_version, 277 | getPackageManager().getPackageInfo(getPackageName(), 0).versionName); 278 | } catch (NameNotFoundException e) { 279 | versionString = getResources().getString(R.string.about_version_unknown); 280 | } 281 | TextView versionTextView = aboutDialog.findViewById(R.id.about_version); 282 | versionTextView.setText(versionString); 283 | aboutDialog.show(); 284 | } 285 | 286 | private void onUsbDeviceChanged() { 287 | refreshAdapterList(); 288 | } 289 | 290 | private void onUsbDevicePermission(UsbDevice usbDevice, boolean isGranted) { 291 | synchronized (this) { 292 | if (isGranted) { 293 | showI2cDeviceListPane(usbDevice); 294 | } else { 295 | Log.d(TAG, "permission denied for device " + usbDevice); 296 | Toast.makeText(I2cAdapterListActivity.this, 297 | R.string.permission_denied, Toast.LENGTH_SHORT).show(); 298 | } 299 | } 300 | } 301 | 302 | void refreshAdapterList() { 303 | if (adapterListRefreshLayout != null) { 304 | adapterListRefreshLayout.post(() -> { 305 | adapterListRefreshLayout.setRefreshing(true); 306 | onRefreshListener.onRefresh(); 307 | }); 308 | } 309 | } 310 | 311 | void scanI2cAdapters() { 312 | List items = usbI2cManager.getAdapters(); 313 | recyclerViewAdapter.updateItems(items); 314 | } 315 | 316 | @Override 317 | protected void onStop() { 318 | super.onStop(); 319 | if (aboutDialog != null) { 320 | aboutDialog.dismiss(); 321 | aboutDialog = null; 322 | } 323 | } 324 | 325 | @Override 326 | protected void onDestroy() { 327 | super.onDestroy(); 328 | unregisterReceiver(usbReceiver); 329 | } 330 | } 331 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/ykc3/android/usbi2c/app/I2cDeviceListActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Victor Antonovich 3 | * 4 | * This work is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Lesser General Public License 6 | * as published by the Free Software Foundation; either version 2.1 7 | * of the License, or (at your option) any later version. 8 | * 9 | * This work is distributed in the hope that it will be useful, but 10 | * without any warranty; without even the implied warranty of merchantability 11 | * or fitness for a particular purpose. See the GNU Lesser General Public 12 | * License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with this library; if not, write to the Free Software Foundation, Inc., 16 | * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. 17 | */ 18 | 19 | package com.github.ykc3.android.usbi2c.app; 20 | 21 | import android.annotation.SuppressLint; 22 | import android.content.BroadcastReceiver; 23 | import android.content.Context; 24 | import android.content.Intent; 25 | import android.content.IntentFilter; 26 | import android.hardware.usb.UsbManager; 27 | import android.os.Build; 28 | import android.os.Bundle; 29 | import androidx.appcompat.widget.Toolbar; 30 | import androidx.appcompat.app.AppCompatActivity; 31 | import androidx.appcompat.app.ActionBar; 32 | import androidx.core.app.NavUtils; 33 | import android.view.MenuItem; 34 | 35 | /** 36 | * An activity representing a {@link com.github.ykc3.android.usbi2c.UsbI2cDevice} list screen. This 37 | * activity is only used on narrow width devices. On tablet-size devices, 38 | * item details are presented side-by-side with a list of items 39 | * in a {@link I2cAdapterListActivity}. 40 | */ 41 | public class I2cDeviceListActivity extends AppCompatActivity { 42 | private final BroadcastReceiver usbReceiver = new BroadcastReceiver() { 43 | public void onReceive(Context context, Intent intent) { 44 | String action = intent.getAction(); 45 | if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(action) || 46 | UsbManager.ACTION_USB_DEVICE_DETACHED.equals(action)) { 47 | onUsbDeviceChanged(); 48 | } 49 | } 50 | }; 51 | 52 | @SuppressLint("UnspecifiedRegisterReceiverFlag") 53 | @Override 54 | protected void onCreate(Bundle savedInstanceState) { 55 | super.onCreate(savedInstanceState); 56 | setContentView(R.layout.activity_device_list); 57 | Toolbar toolbar = findViewById(R.id.toolbar); 58 | setSupportActionBar(toolbar); 59 | 60 | // Show the Up button in the action bar. 61 | ActionBar actionBar = getSupportActionBar(); 62 | if (actionBar != null) { 63 | actionBar.setDisplayHomeAsUpEnabled(true); 64 | } 65 | 66 | // savedInstanceState is non-null when there is fragment state 67 | // saved from previous configurations of this activity 68 | // (e.g. when rotating the screen from portrait to landscape). 69 | // In this case, the fragment will automatically be re-added 70 | // to its container so we don't need to manually add it. 71 | // For more information, see the Fragments API guide at: 72 | // 73 | // http://developer.android.com/guide/components/fragments.html 74 | // 75 | if (savedInstanceState == null) { 76 | // Create the detail fragment and add it to the activity 77 | // using a fragment transaction. 78 | Bundle arguments = new Bundle(); 79 | arguments.putParcelable(I2cDeviceListFragment.KEY_USB_DEVICE, 80 | getIntent().getParcelableExtra(I2cDeviceListFragment.KEY_USB_DEVICE)); 81 | I2cDeviceListFragment fragment = new I2cDeviceListFragment(); 82 | fragment.setArguments(arguments); 83 | getSupportFragmentManager().beginTransaction() 84 | .add(R.id.device_list_container, fragment) 85 | .commit(); 86 | } 87 | 88 | IntentFilter usbReceiverFilter = new IntentFilter(I2cAdapterListActivity.ACTION_USB_PERMISSION); 89 | usbReceiverFilter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED); 90 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { 91 | registerReceiver(usbReceiver, usbReceiverFilter, RECEIVER_EXPORTED); 92 | } else { 93 | registerReceiver(usbReceiver, usbReceiverFilter); 94 | } 95 | } 96 | 97 | @Override 98 | protected void onDestroy() { 99 | super.onDestroy(); 100 | unregisterReceiver(usbReceiver); 101 | } 102 | 103 | @Override 104 | public boolean onOptionsItemSelected(MenuItem item) { 105 | int id = item.getItemId(); 106 | if (id == android.R.id.home) { 107 | // This ID represents the Home or Up button. In the case of this 108 | // activity, the Up button is shown. Use NavUtils to allow users 109 | // to navigate up one level in the application structure. For 110 | // more details, see the Navigation pattern on Android Design: 111 | // 112 | // http://developer.android.com/design/patterns/navigation.html#up-vs-back 113 | // 114 | NavUtils.navigateUpTo(this, new Intent(this, I2cAdapterListActivity.class)); 115 | return true; 116 | } 117 | return super.onOptionsItemSelected(item); 118 | } 119 | 120 | private void onUsbDeviceChanged() { 121 | finish(); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/ykc3/android/usbi2c/app/I2cDeviceListFragment.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Victor Antonovich 3 | * 4 | * This work is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Lesser General Public License 6 | * as published by the Free Software Foundation; either version 2.1 7 | * of the License, or (at your option) any later version. 8 | * 9 | * This work is distributed in the hope that it will be useful, but 10 | * without any warranty; without even the implied warranty of merchantability 11 | * or fitness for a particular purpose. See the GNU Lesser General Public 12 | * License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with this library; if not, write to the Free Software Foundation, Inc., 16 | * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. 17 | */ 18 | 19 | package com.github.ykc3.android.usbi2c.app; 20 | 21 | import android.app.Activity; 22 | import android.content.Context; 23 | import android.content.Intent; 24 | import android.hardware.usb.UsbDevice; 25 | import android.hardware.usb.UsbManager; 26 | import android.net.Uri; 27 | import android.os.AsyncTask; 28 | import android.os.Handler; 29 | import androidx.annotation.NonNull; 30 | import android.os.Bundle; 31 | 32 | import com.github.ykc3.android.usbi2c.app.device.info.I2cDeviceInfo; 33 | import com.github.ykc3.android.usbi2c.app.device.info.I2cDeviceInfoRegistry; 34 | import com.github.ykc3.android.usbi2c.app.view.CustomRecyclerView; 35 | import com.google.android.material.snackbar.Snackbar; 36 | import androidx.fragment.app.Fragment; 37 | import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; 38 | import androidx.recyclerview.widget.RecyclerView; 39 | import android.util.Log; 40 | import android.view.LayoutInflater; 41 | import android.view.View; 42 | import android.view.ViewGroup; 43 | import android.widget.TextView; 44 | 45 | import com.github.ykc3.android.usbi2c.UsbI2cAdapter; 46 | import com.github.ykc3.android.usbi2c.UsbI2cDevice; 47 | import com.github.ykc3.android.usbi2c.UsbI2cManager; 48 | 49 | import com.github.ykc3.android.usbi2c.app.device.handler.I2cDeviceHandler; 50 | import com.github.ykc3.android.usbi2c.app.device.handler.I2cDeviceHandlerRegistry; 51 | 52 | import java.io.IOException; 53 | import java.util.ArrayList; 54 | import java.util.List; 55 | 56 | /** 57 | * A fragment representing a {@link com.github.ykc3.android.usbi2c.UsbI2cDevice} list screen. 58 | * This fragment is either contained in a {@link I2cAdapterListActivity} 59 | * in two-pane mode (on tablets) or a {@link I2cDeviceListActivity} 60 | * on handsets. 61 | */ 62 | public class I2cDeviceListFragment extends Fragment { 63 | private final String TAG = I2cDeviceListFragment.class.getSimpleName(); 64 | 65 | static final String KEY_USB_DEVICE = "com.github.ykc3.android.usbi2c.app.USB_DEVICE"; 66 | 67 | private static final int MIN_I2C_ADDRESS = 0x03; 68 | private static final int MAX_I2C_ADDRESS = 0x77; 69 | 70 | private UsbDevice usbDevice; 71 | 72 | private UsbI2cManager usbI2cManager; 73 | 74 | private I2cDeviceHandlerRegistry i2cDeviceHandlerRegistry; 75 | private I2cDeviceInfoRegistry i2cDeviceInfoRegistry; 76 | 77 | private CustomRecyclerView recyclerView; 78 | private RecyclerViewAdapter recyclerViewAdapter; 79 | 80 | private SwipeRefreshLayout deviceListRefreshLayout; 81 | 82 | private AsyncTask scanI2cDevicesTask; 83 | 84 | private static class DeviceItem { 85 | final int deviceAddress; 86 | final List candidateDeviceInfos; 87 | final boolean isDeviceRecognized; 88 | final I2cDeviceInfo recognizedDeviceInfo; 89 | final String deviceDescription; 90 | 91 | DeviceItem(int deviceAddress, List candidateDeviceInfos, 92 | boolean isDeviceRecognized, I2cDeviceInfo recognizedDeviceInfo, 93 | String deviceDescription) { 94 | this.deviceAddress = deviceAddress; 95 | this.candidateDeviceInfos = candidateDeviceInfos; 96 | this.isDeviceRecognized = isDeviceRecognized; 97 | this.recognizedDeviceInfo = recognizedDeviceInfo; 98 | this.deviceDescription = deviceDescription; 99 | } 100 | 101 | String getDeviceHexAddress() { 102 | return String.format("0x%02x", deviceAddress); 103 | } 104 | 105 | String getDeviceInfo() { 106 | StringBuilder builder = new StringBuilder(); 107 | if (isDeviceRecognized && recognizedDeviceInfo != null) { 108 | builder.append(recognizedDeviceInfo.getPartNumber()); 109 | if (deviceDescription != null && !deviceDescription.isEmpty()) { 110 | builder.append(" ("); 111 | builder.append(deviceDescription); 112 | builder.append(")"); 113 | } 114 | } else if (candidateDeviceInfos != null && !candidateDeviceInfos.isEmpty()) { 115 | for (I2cDeviceInfo deviceInfo : candidateDeviceInfos) { 116 | builder.append(deviceInfo.getPartNumber()); 117 | builder.append(' '); 118 | } 119 | } 120 | return (builder.length() > 0) ? builder.toString() : null; 121 | } 122 | 123 | String getDevicePage() { 124 | StringBuilder builder = new StringBuilder("https://i2cdevices.org/"); 125 | if (isDeviceRecognized && recognizedDeviceInfo != null) { 126 | builder.append("devices/"); 127 | builder.append(recognizedDeviceInfo.getPartNumber().toLowerCase()); 128 | } else { 129 | builder.append("addresses/"); 130 | builder.append(getDeviceHexAddress()); 131 | } 132 | return builder.toString(); 133 | } 134 | } 135 | 136 | private final View.OnClickListener onDeviceItemClickListener = new View.OnClickListener() { 137 | @Override 138 | public void onClick(View view) { 139 | if (view.getTag() == null) { 140 | return; 141 | } 142 | DeviceItem clickedDeviceItem = (DeviceItem) view.getTag(); 143 | Intent browserIntent = new Intent(Intent.ACTION_VIEW, 144 | Uri.parse(clickedDeviceItem.getDevicePage())); 145 | I2cDeviceListFragment.this.startActivity(browserIntent); 146 | } 147 | }; 148 | 149 | public class RecyclerViewAdapter extends RecyclerView.Adapter { 150 | private List deviceItems = new ArrayList<>(); 151 | 152 | void addItem(DeviceItem item) { 153 | deviceItems.add(item); 154 | notifyItemInserted(deviceItems.size() - 1); 155 | } 156 | 157 | void clearItems() { 158 | deviceItems.clear(); 159 | notifyDataSetChanged(); 160 | } 161 | 162 | @Override 163 | @NonNull 164 | public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { 165 | View view = LayoutInflater.from(parent.getContext()).inflate( 166 | R.layout.device_list_content, parent, false); 167 | return new ViewHolder(view); 168 | } 169 | 170 | @Override 171 | public void onBindViewHolder(@NonNull final ViewHolder holder, int position) { 172 | DeviceItem deviceItem = deviceItems.get(position); 173 | holder.deviceAddressView.setText(deviceItem.getDeviceHexAddress()); 174 | String deviceInfo = deviceItem.getDeviceInfo(); 175 | holder.deviceInfoView.setText(deviceInfo != null ? deviceInfo : 176 | getString(R.string.unknown_device)); 177 | 178 | holder.itemView.setTag(deviceItems.get(position)); 179 | holder.itemView.setOnClickListener(onDeviceItemClickListener); 180 | } 181 | 182 | @Override 183 | public int getItemCount() { 184 | return deviceItems.size(); 185 | } 186 | 187 | class ViewHolder extends RecyclerView.ViewHolder { 188 | final TextView deviceAddressView; 189 | final TextView deviceInfoView; 190 | 191 | ViewHolder(View view) { 192 | super(view); 193 | deviceAddressView = view.findViewById(R.id.device_address); 194 | deviceInfoView = view.findViewById(R.id.device_info); 195 | } 196 | } 197 | } 198 | 199 | static class ScanI2cDevicesTask extends AsyncTask { 200 | private final String TAG = ScanI2cDevicesTask.class.getSimpleName(); 201 | 202 | private final I2cDeviceListFragment fragment; 203 | 204 | private Snackbar snackbar; 205 | 206 | ScanI2cDevicesTask(I2cDeviceListFragment fragment) { 207 | this.fragment = fragment; 208 | } 209 | 210 | @Override 211 | protected Integer doInBackground(UsbDevice... usbDevices) { 212 | try (UsbI2cAdapter usbI2cAdapter = fragment.usbI2cManager.getAdapter(usbDevices[0])) { 213 | usbI2cAdapter.setClockSpeed(UsbI2cAdapter.CLOCK_SPEED_STANDARD); 214 | usbI2cAdapter.open(); 215 | byte[] buf = new byte[1]; 216 | for (int i2cAddress = MIN_I2C_ADDRESS; i2cAddress <= MAX_I2C_ADDRESS; i2cAddress++) { 217 | try { 218 | UsbI2cDevice i2cDevice = usbI2cAdapter.getDevice(i2cAddress); 219 | i2cDevice.read(buf, 1); // IOException if no device at this address 220 | Log.d(TAG, String.format("found I2C device at 0x%02x", i2cAddress)); 221 | Activity activity = fragment.getActivity(); 222 | if (activity == null) { 223 | break; 224 | } 225 | final DeviceItem deviceItem = getDeviceItem(i2cDevice); 226 | activity.runOnUiThread(() -> fragment.recyclerViewAdapter.addItem(deviceItem)); 227 | } catch (IOException e) { 228 | Log.d(TAG, String.format("no I2C device found at 0x%02x (%s)", 229 | i2cAddress, e.getMessage())); 230 | } 231 | if (isCancelled()) { 232 | break; 233 | } 234 | publishProgress(i2cAddress); 235 | } 236 | } catch (Exception e) { 237 | // TODO Show scan error message 238 | Log.e(TAG, "scan error", e); 239 | } 240 | return null; 241 | } 242 | 243 | DeviceItem getDeviceItem(UsbI2cDevice i2cDevice) { 244 | boolean isDeviceRecognized = false; 245 | List candidateDeviceInfos = null; 246 | I2cDeviceInfo recognizedDeviceInfo = null; 247 | String deviceDescription = null; 248 | 249 | // First trying to find handler for concrete I2C device with given address 250 | I2cDeviceHandler deviceHandler = fragment.i2cDeviceHandlerRegistry.findDeviceHandler( 251 | i2cDevice); 252 | if (deviceHandler != null) { 253 | isDeviceRecognized = true; 254 | try { 255 | recognizedDeviceInfo = fragment.i2cDeviceInfoRegistry.findDeviceByPartNumber( 256 | deviceHandler.getDevicePartNumber(i2cDevice)); 257 | // Handler found, trying to use it to get I2C device info 258 | deviceDescription = deviceHandler.getDeviceDescription(i2cDevice); 259 | } catch (IOException e) { 260 | Log.e(TAG,"can't get device info", e); 261 | } 262 | } else { 263 | // No device handler found, check for list of all known I2C devices w/ this address 264 | if (fragment.i2cDeviceInfoRegistry != null) { 265 | candidateDeviceInfos = fragment.i2cDeviceInfoRegistry.findDevicesByAddress( 266 | i2cDevice.getAddress()); 267 | } 268 | } 269 | 270 | return new DeviceItem(i2cDevice.getAddress(), candidateDeviceInfos, isDeviceRecognized, 271 | recognizedDeviceInfo, deviceDescription); 272 | } 273 | 274 | @Override 275 | protected void onPreExecute() { 276 | fragment.recyclerView.showEmptyView(false); 277 | fragment.recyclerViewAdapter.clearItems(); 278 | snackbar = Snackbar.make(fragment.deviceListRefreshLayout, 279 | fragment.getResources().getString(R.string.device_scan_info, MIN_I2C_ADDRESS), 280 | Snackbar.LENGTH_INDEFINITE) 281 | .setAction(android.R.string.cancel, v -> fragment.cancelScanI2cDevices()); 282 | snackbar.show(); 283 | } 284 | 285 | private void dismissUiProgressItems() { 286 | new Handler().postDelayed(() -> { 287 | fragment.recyclerView.showEmptyView(fragment.recyclerView.isEmpty()); 288 | fragment.deviceListRefreshLayout.setRefreshing(false); 289 | snackbar.dismiss(); 290 | }, isCancelled() ? 0 : 1000); 291 | } 292 | 293 | @Override 294 | protected void onPostExecute(Integer result) { 295 | dismissUiProgressItems(); 296 | } 297 | 298 | @Override 299 | protected void onCancelled() { 300 | dismissUiProgressItems(); 301 | } 302 | 303 | @Override 304 | protected void onProgressUpdate(Integer... values) { 305 | if (fragment.isAdded()) { 306 | snackbar.setText(fragment.getResources().getString(R.string.device_scan_info, 307 | values[values.length-1])); 308 | } 309 | } 310 | } 311 | 312 | /** 313 | * Mandatory empty constructor for the fragment manager to instantiate the 314 | * fragment (e.g. upon screen orientation changes). 315 | */ 316 | public I2cDeviceListFragment() { 317 | } 318 | 319 | @Override 320 | public void onCreate(Bundle savedInstanceState) { 321 | super.onCreate(savedInstanceState); 322 | 323 | Activity activity = this.getActivity(); 324 | assert activity != null; 325 | 326 | Bundle arguments = getArguments(); 327 | assert arguments != null; 328 | 329 | if (!arguments.containsKey(KEY_USB_DEVICE)) { 330 | throw new AssertionError("No USB device found to scan"); 331 | } 332 | 333 | usbDevice = arguments.getParcelable(KEY_USB_DEVICE); 334 | 335 | UsbManager usbManager = (UsbManager) getActivity().getSystemService(Context.USB_SERVICE); 336 | assert usbManager != null; 337 | 338 | usbI2cManager = UsbI2cManager.create(usbManager).build(); 339 | 340 | i2cDeviceHandlerRegistry = new I2cDeviceHandlerRegistry(); 341 | 342 | try { 343 | i2cDeviceInfoRegistry = I2cDeviceInfoRegistry.createFromResource(getResources(), 344 | R.raw.devices); 345 | } catch (IOException e) { 346 | Log.e(TAG, "Can't create I2C device info registry", e); 347 | } 348 | 349 | // CollapsingToolbarLayout appBarLayout = activity.findViewById(R.id.toolbar_layout); 350 | // if (appBarLayout != null) { 351 | // appBarLayout.setTitle(usbDevice.getProductName()); 352 | // } 353 | } 354 | 355 | @Override 356 | public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, 357 | Bundle savedInstanceState) { 358 | View rootView = inflater.inflate(R.layout.device_list, container, false); 359 | 360 | recyclerView = rootView.findViewById(R.id.device_list); 361 | recyclerViewAdapter = new RecyclerViewAdapter(); 362 | recyclerView.setAdapter(recyclerViewAdapter); 363 | recyclerView.setEmptyView(rootView.findViewById(R.id.empty_device_list)); 364 | recyclerView.showEmptyView(false); 365 | 366 | deviceListRefreshLayout = rootView.findViewById(R.id.device_list_refresh); 367 | final SwipeRefreshLayout.OnRefreshListener onRefreshListener = this::scanI2cDevices; 368 | deviceListRefreshLayout.setOnRefreshListener(onRefreshListener); 369 | 370 | deviceListRefreshLayout.post(() -> { 371 | deviceListRefreshLayout.setRefreshing(true); 372 | onRefreshListener.onRefresh(); 373 | }); 374 | 375 | return rootView; 376 | } 377 | 378 | @Override 379 | public void onDestroyView() { 380 | super.onDestroyView(); 381 | cancelScanI2cDevices(); 382 | } 383 | 384 | protected void scanI2cDevices() { 385 | Activity activity = this.getActivity(); 386 | assert activity != null; 387 | 388 | scanI2cDevicesTask = new ScanI2cDevicesTask(this); 389 | scanI2cDevicesTask.execute(usbDevice); 390 | } 391 | 392 | protected void cancelScanI2cDevices() { 393 | if (scanI2cDevicesTask != null) { 394 | scanI2cDevicesTask.cancel(false); 395 | } 396 | } 397 | } 398 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/ykc3/android/usbi2c/app/device/handler/AbstractI2cDeviceHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Victor Antonovich 3 | * 4 | * This work is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Lesser General Public License 6 | * as published by the Free Software Foundation; either version 2.1 7 | * of the License, or (at your option) any later version. 8 | * 9 | * This work is distributed in the hope that it will be useful, but 10 | * without any warranty; without even the implied warranty of merchantability 11 | * or fitness for a particular purpose. See the GNU Lesser General Public 12 | * License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with this library; if not, write to the Free Software Foundation, Inc., 16 | * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. 17 | */ 18 | 19 | package com.github.ykc3.android.usbi2c.app.device.handler; 20 | 21 | import com.github.ykc3.android.usbi2c.UsbI2cDevice; 22 | 23 | public abstract class AbstractI2cDeviceHandler implements I2cDeviceHandler { 24 | @Override 25 | public boolean isDeviceSupported(UsbI2cDevice device) { 26 | return isDeviceAddressMatched(device.getAddress()) && isDeviceRecognized(device); 27 | } 28 | 29 | private boolean isDeviceAddressMatched(int address) { 30 | for (int addr: getDeviceAddresses()) { 31 | if (addr == address) { 32 | return true; 33 | } 34 | } 35 | return false; 36 | } 37 | 38 | protected abstract int[] getDeviceAddresses(); 39 | 40 | protected abstract boolean isDeviceRecognized(UsbI2cDevice device); 41 | } 42 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/ykc3/android/usbi2c/app/device/handler/Bme280Handler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Victor Antonovich 3 | * 4 | * This work is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Lesser General Public License 6 | * as published by the Free Software Foundation; either version 2.1 7 | * of the License, or (at your option) any later version. 8 | * 9 | * This work is distributed in the hope that it will be useful, but 10 | * without any warranty; without even the implied warranty of merchantability 11 | * or fitness for a particular purpose. See the GNU Lesser General Public 12 | * License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with this library; if not, write to the Free Software Foundation, Inc., 16 | * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. 17 | */ 18 | 19 | package com.github.ykc3.android.usbi2c.app.device.handler; 20 | 21 | import android.annotation.SuppressLint; 22 | import android.util.Log; 23 | 24 | import com.github.ykc3.android.usbi2c.UsbI2cDevice; 25 | 26 | import java.io.IOException; 27 | 28 | /** 29 | * Bosch Sensortec BMP280/BME280 digital temperature/pressure/humidity sensor. 30 | * Based on BME280.java 31 | */ 32 | public class Bme280Handler extends AbstractI2cDeviceHandler { 33 | private final String TAG = Bme280Handler.class.getSimpleName(); 34 | 35 | private static final String BMP280_PART_NUMBER = "BMP280"; 36 | private static final String BME280_PART_NUMBER = "BME280"; 37 | 38 | private static final int[] I2C_ADDRESSES = { 0x76, 0x77 }; 39 | 40 | private static final int BMP280_CHIP_ID = 0x58; 41 | private static final int BME280_CHIP_ID = 0x60; 42 | 43 | private static final int REG_TEMP_PRESS_CALIB_DATA = 0x88; 44 | private static final int REG_DIG_H1 = 0xA1; 45 | private static final int REG_DIG_H2 = 0xE1; 46 | private static final int REG_CHIP_ID = 0xD0; 47 | private static final int REG_CTRL_HUM = 0xF2; 48 | private static final int REG_STATUS = 0xF3; 49 | private static final int REG_CTRL_MEAS = 0xF4; 50 | private static final int REG_CONFIG = 0xF5; 51 | private static final int REG_DATA = 0xF7; 52 | 53 | @Override 54 | protected int[] getDeviceAddresses() { 55 | return I2C_ADDRESSES; 56 | } 57 | 58 | @Override 59 | public String getDevicePartNumber(UsbI2cDevice device) throws IOException { 60 | return isHumiditySensorPresent(device) ? BME280_PART_NUMBER : BMP280_PART_NUMBER; 61 | } 62 | 63 | @Override 64 | protected boolean isDeviceRecognized(UsbI2cDevice device) { 65 | try { 66 | int chipId = readChipId(device); 67 | Log.d(TAG, String.format("read chip ID: 0x%02x", chipId)); 68 | return chipId == BMP280_CHIP_ID || chipId == BME280_CHIP_ID; 69 | } catch (IOException e) { 70 | Log.d(TAG,"can't read chip ID", e); 71 | } 72 | return false; 73 | } 74 | 75 | private boolean isHumiditySensorPresent(UsbI2cDevice device) throws IOException { 76 | return readChipId(device) == BME280_CHIP_ID; 77 | } 78 | 79 | private byte readChipId(UsbI2cDevice device) throws IOException { 80 | return device.readRegByte(REG_CHIP_ID); 81 | } 82 | 83 | @SuppressLint("DefaultLocale") 84 | @Override 85 | public String getDeviceDescription(UsbI2cDevice device) throws IOException { 86 | boolean isHumiditySensorPresent = isHumiditySensorPresent(device); 87 | 88 | byte[] b1 = new byte[24]; 89 | device.readRegBuffer(REG_TEMP_PRESS_CALIB_DATA, b1, 24); 90 | 91 | // Convert the data temp coefficients 92 | int dig_T1 = (b1[0] & 0xFF) + ((b1[1] & 0xFF) * 256); 93 | int dig_T2 = (b1[2] & 0xFF) + ((b1[3] & 0xFF) * 256); 94 | if (dig_T2 > 32767) { 95 | dig_T2 -= 65536; 96 | } 97 | int dig_T3 = (b1[4] & 0xFF) + ((b1[5] & 0xFF) * 256); 98 | if (dig_T3 > 32767) { 99 | dig_T3 -= 65536; 100 | } 101 | 102 | // Pressure coefficients 103 | int dig_P1 = (b1[6] & 0xFF) + ((b1[7] & 0xFF) * 256); 104 | int dig_P2 = (b1[8] & 0xFF) + ((b1[9] & 0xFF) * 256); 105 | if (dig_P2 > 32767) { 106 | dig_P2 -= 65536; 107 | } 108 | int dig_P3 = (b1[10] & 0xFF) + ((b1[11] & 0xFF) * 256); 109 | if (dig_P3 > 32767) { 110 | dig_P3 -= 65536; 111 | } 112 | int dig_P4 = (b1[12] & 0xFF) + ((b1[13] & 0xFF) * 256); 113 | if (dig_P4 > 32767) { 114 | dig_P4 -= 65536; 115 | } 116 | int dig_P5 = (b1[14] & 0xFF) + ((b1[15] & 0xFF) * 256); 117 | if (dig_P5 > 32767) { 118 | dig_P5 -= 65536; 119 | } 120 | int dig_P6 = (b1[16] & 0xFF) + ((b1[17] & 0xFF) * 256); 121 | if (dig_P6 > 32767) { 122 | dig_P6 -= 65536; 123 | } 124 | int dig_P7 = (b1[18] & 0xFF) + ((b1[19] & 0xFF) * 256); 125 | if (dig_P7 > 32767) { 126 | dig_P7 -= 65536; 127 | } 128 | int dig_P8 = (b1[20] & 0xFF) + ((b1[21] & 0xFF) * 256); 129 | if (dig_P8 > 32767) { 130 | dig_P8 -= 65536; 131 | } 132 | int dig_P9 = (b1[22] & 0xFF) + ((b1[23] & 0xFF) * 256); 133 | if (dig_P9 > 32767) { 134 | dig_P9 -= 65536; 135 | } 136 | 137 | int dig_H1 = 0, dig_H2 = 0, dig_H3 = 0, dig_H4 = 0, dig_H5 = 0, dig_H6 = 0; 138 | if (isHumiditySensorPresent) { 139 | // Read 1 byte of data from address 0xA1 (161) 140 | dig_H1 = (device.readRegByte(REG_DIG_H1) & 0xFF); 141 | 142 | // Read 7 bytes of data from address 0xE1 (225) 143 | device.readRegBuffer(REG_DIG_H2, b1, 7); 144 | 145 | // Convert the data humidity coefficients 146 | dig_H2 = (b1[0] & 0xFF) + ((b1[1] & 0xFF) * 256); 147 | if (dig_H2 > 32767) { 148 | dig_H2 -= 65536; 149 | } 150 | dig_H3 = b1[2] & 0xFF; 151 | dig_H4 = ((b1[3] & 0xFF) * 16) + (b1[4] & 0xF); 152 | if (dig_H4 > 2047) { 153 | dig_H4 -= 4096; 154 | } 155 | dig_H5 = ((b1[4] & 0xFF) / 16) + ((b1[5] & 0xFF) * 16); 156 | if (dig_H5 > 2047) { 157 | dig_H5 -= 4096; 158 | } 159 | dig_H6 = b1[6] & 0xFF; 160 | if (dig_H6 > 127) { 161 | dig_H6 -= 256; 162 | } 163 | // Select control humidity register 164 | // Humidity over sampling rate = 1 165 | device.writeRegByte(REG_CTRL_HUM, (byte) 0x01); 166 | } 167 | 168 | // Select control measurement register 169 | // Normal mode, temp and pressure over sampling rate = 1 170 | device.writeRegByte(REG_CTRL_MEAS, (byte) 0x27); 171 | // Select config register 172 | // Stand_by time = 1000 ms 173 | device.writeRegByte(REG_CONFIG, (byte) 0xA0); 174 | 175 | // Wait for conversion completion 176 | while ((device.readRegByte(REG_STATUS) & 0x08) != 0) { 177 | try { 178 | Thread.sleep(10); 179 | } catch (InterruptedException ie) { 180 | throw new IOException(ie); 181 | } 182 | } 183 | 184 | // Read 8 bytes of data from address 0xF7 (247) 185 | // pressure msb1, pressure msb, pressure lsb, temp msb1, temp msb, temp lsb, humidity lsb, humidity msb 186 | byte[] data = new byte[8]; 187 | device.readRegBuffer(REG_DATA, data, isHumiditySensorPresent ? 8 : 6); 188 | 189 | // Convert pressure and temperature data to 19-bits 190 | long adc_p = (((long) (data[0] & 0xFF) * 65536) + 191 | ((long) (data[1] & 0xFF) * 256) + (long) (data[2] & 0xF0)) / 16; 192 | long adc_t = (((long) (data[3] & 0xFF) * 65536) + 193 | ((long) (data[4] & 0xFF) * 256) + (long) (data[5] & 0xF0)) / 16; 194 | 195 | // Temperature offset calculations 196 | double var1 = (((double) adc_t) / 16384.0 - ((double) dig_T1) / 1024.0) * ((double) dig_T2); 197 | double var2 = ((((double) adc_t) / 131072.0 - ((double) dig_T1) / 8192.0) * 198 | (((double) adc_t) / 131072.0 - ((double) dig_T1) / 8192.0)) * ((double) dig_T3); 199 | double t_fine = (long) (var1 + var2); 200 | double temp = (var1 + var2) / 5120.0; 201 | 202 | // Pressure offset calculations 203 | var1 = (t_fine / 2.0) - 64000.0; 204 | var2 = var1 * var1 * ((double) dig_P6) / 32768.0; 205 | var2 = var2 + var1 * ((double) dig_P5) * 2.0; 206 | var2 = (var2 / 4.0) + (((double) dig_P4) * 65536.0); 207 | var1 = (((double) dig_P3) * var1 * var1 / 524288.0 + ((double) dig_P2) * var1) / 524288.0; 208 | var1 = (1.0 + var1 / 32768.0) * ((double) dig_P1); 209 | double p = 1048576.0 - (double) adc_p; 210 | p = (p - (var2 / 4096.0)) * 6250.0 / var1; 211 | var1 = ((double) dig_P9) * p * p / 2147483648.0; 212 | var2 = p * ((double) dig_P8) / 32768.0; 213 | double pressure = (p + (var1 + var2 + ((double) dig_P7)) / 16.0) / 100; 214 | 215 | double humidity = 0.; 216 | if (isHumiditySensorPresent) { 217 | // Convert the humidity data 218 | long adc_h = ((long) (data[6] & 0xFF) * 256 + (long) (data[7] & 0xFF)); 219 | // Humidity offset calculations 220 | double var_H = (t_fine - 76800.0); 221 | var_H = (adc_h - (dig_H4 * 64.0 + dig_H5 / 16384.0 * var_H)) * (dig_H2 / 65536.0 * 222 | (1.0 + dig_H6 / 67108864.0 * var_H * (1.0 + dig_H3 / 67108864.0 * var_H))); 223 | humidity = var_H * (1.0 - dig_H1 * var_H / 524288.0); 224 | if (humidity > 100.0) { 225 | humidity = 100.0; 226 | } else if (humidity < 0.0) { 227 | humidity = 0.0; 228 | } 229 | } 230 | 231 | return isHumiditySensorPresent 232 | ? String.format("%.1f°C/%.1f%% RH/%.1f hPa", temp, humidity, pressure) 233 | : String.format("%.1f°C/%.1f hPa", temp, pressure); 234 | } 235 | } 236 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/ykc3/android/usbi2c/app/device/handler/I2cDeviceHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Victor Antonovich 3 | * 4 | * This work is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Lesser General Public License 6 | * as published by the Free Software Foundation; either version 2.1 7 | * of the License, or (at your option) any later version. 8 | * 9 | * This work is distributed in the hope that it will be useful, but 10 | * without any warranty; without even the implied warranty of merchantability 11 | * or fitness for a particular purpose. See the GNU Lesser General Public 12 | * License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with this library; if not, write to the Free Software Foundation, Inc., 16 | * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. 17 | */ 18 | 19 | package com.github.ykc3.android.usbi2c.app.device.handler; 20 | 21 | import com.github.ykc3.android.usbi2c.UsbI2cDevice; 22 | 23 | import java.io.IOException; 24 | 25 | /** 26 | * Specific I2C device handler. 27 | */ 28 | public interface I2cDeviceHandler { 29 | /** 30 | * Check this I2C device is supported by this handler. 31 | * @param device I2C device to check 32 | * @return true if device supported by this handler, false otherwise 33 | */ 34 | boolean isDeviceSupported(UsbI2cDevice device); 35 | 36 | /** 37 | * Get supported I2C device part number. 38 | * @param device device supported I2C device 39 | * @return device part number 40 | * @throws IOException in case of device I/O error 41 | */ 42 | String getDevicePartNumber(UsbI2cDevice device) throws IOException; 43 | 44 | /** 45 | * Get supported I2C device description. 46 | * @param device supported I2C device 47 | * @return device description 48 | * @throws IOException in case of device I/O error 49 | */ 50 | String getDeviceDescription(UsbI2cDevice device) throws IOException; 51 | } 52 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/ykc3/android/usbi2c/app/device/handler/I2cDeviceHandlerRegistry.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Victor Antonovich 3 | * 4 | * This work is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Lesser General Public License 6 | * as published by the Free Software Foundation; either version 2.1 7 | * of the License, or (at your option) any later version. 8 | * 9 | * This work is distributed in the hope that it will be useful, but 10 | * without any warranty; without even the implied warranty of merchantability 11 | * or fitness for a particular purpose. See the GNU Lesser General Public 12 | * License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with this library; if not, write to the Free Software Foundation, Inc., 16 | * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. 17 | */ 18 | 19 | package com.github.ykc3.android.usbi2c.app.device.handler; 20 | 21 | import com.github.ykc3.android.usbi2c.UsbI2cDevice; 22 | 23 | import java.util.ArrayList; 24 | import java.util.List; 25 | 26 | public class I2cDeviceHandlerRegistry { 27 | private final List deviceHandlers; 28 | 29 | public I2cDeviceHandlerRegistry(List deviceHandlers) { 30 | this.deviceHandlers = deviceHandlers; 31 | } 32 | 33 | public I2cDeviceHandlerRegistry() { 34 | this(getDefaultI2cDeviceHandlerList()); 35 | } 36 | 37 | public static List getDefaultI2cDeviceHandlerList() { 38 | List deviceHandlers = new ArrayList<>(); 39 | deviceHandlers.add(new Bme280Handler()); 40 | return deviceHandlers; 41 | } 42 | 43 | public I2cDeviceHandler findDeviceHandler(UsbI2cDevice device) { 44 | for (I2cDeviceHandler handler: deviceHandlers) { 45 | if (handler.isDeviceSupported(device)) { 46 | return handler; 47 | } 48 | } 49 | return null; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/ykc3/android/usbi2c/app/device/info/I2cDeviceInfo.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Victor Antonovich 3 | * 4 | * This work is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Lesser General Public License 6 | * as published by the Free Software Foundation; either version 2.1 7 | * of the License, or (at your option) any later version. 8 | * 9 | * This work is distributed in the hope that it will be useful, but 10 | * without any warranty; without even the implied warranty of merchantability 11 | * or fitness for a particular purpose. See the GNU Lesser General Public 12 | * License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with this library; if not, write to the Free Software Foundation, Inc., 16 | * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. 17 | */ 18 | 19 | package com.github.ykc3.android.usbi2c.app.device.info; 20 | 21 | import java.util.List; 22 | 23 | import com.google.gson.annotations.Expose; 24 | import com.google.gson.annotations.SerializedName; 25 | 26 | /** 27 | * Generated by http://www.jsonschema2pojo.org/ from https://i2cdevices.org/devices.json 28 | */ 29 | public class I2cDeviceInfo { 30 | @SerializedName("part_number") 31 | @Expose 32 | private String partNumber; 33 | @SerializedName("friendly_name") 34 | @Expose 35 | private String friendlyName; 36 | @SerializedName("datasheet") 37 | @Expose 38 | private String datasheet; 39 | @SerializedName("adafruit") 40 | @Expose 41 | private String adafruit; 42 | @SerializedName("sparkfun") 43 | @Expose 44 | private String sparkfun; 45 | @SerializedName("obsolete") 46 | @Expose 47 | private boolean obsolete; 48 | @SerializedName("manufacturer") 49 | @Expose 50 | private String manufacturer; 51 | @SerializedName("is_5v") 52 | @Expose 53 | private boolean is5v; 54 | @SerializedName("is_3v") 55 | @Expose 56 | private boolean is3v; 57 | @SerializedName("is_spi") 58 | @Expose 59 | private boolean isSpi; 60 | @SerializedName("release_date") 61 | @Expose 62 | private int releaseDate; 63 | @SerializedName("scanned_drivers") 64 | @Expose 65 | private boolean scannedDrivers; 66 | @SerializedName("drivers") 67 | @Expose 68 | private List i2cDeviceDriverInfos = null; 69 | @SerializedName("addresses") 70 | @Expose 71 | private List addresses = null; 72 | 73 | public String getPartNumber() { 74 | return partNumber; 75 | } 76 | 77 | public void setPartNumber(String partNumber) { 78 | this.partNumber = partNumber; 79 | } 80 | 81 | public String getFriendlyName() { 82 | return friendlyName; 83 | } 84 | 85 | public void setFriendlyName(String friendlyName) { 86 | this.friendlyName = friendlyName; 87 | } 88 | 89 | public String getDatasheet() { 90 | return datasheet; 91 | } 92 | 93 | public void setDatasheet(String datasheet) { 94 | this.datasheet = datasheet; 95 | } 96 | 97 | public String getAdafruit() { 98 | return adafruit; 99 | } 100 | 101 | public void setAdafruit(String adafruit) { 102 | this.adafruit = adafruit; 103 | } 104 | 105 | public String getSparkfun() { 106 | return sparkfun; 107 | } 108 | 109 | public void setSparkfun(String sparkfun) { 110 | this.sparkfun = sparkfun; 111 | } 112 | 113 | public boolean isObsolete() { 114 | return obsolete; 115 | } 116 | 117 | public void setObsolete(boolean obsolete) { 118 | this.obsolete = obsolete; 119 | } 120 | 121 | public String getManufacturer() { 122 | return manufacturer; 123 | } 124 | 125 | public void setManufacturer(String manufacturer) { 126 | this.manufacturer = manufacturer; 127 | } 128 | 129 | public boolean isIs5v() { 130 | return is5v; 131 | } 132 | 133 | public void setIs5v(boolean is5v) { 134 | this.is5v = is5v; 135 | } 136 | 137 | public boolean isIs3v() { 138 | return is3v; 139 | } 140 | 141 | public void setIs3v(boolean is3v) { 142 | this.is3v = is3v; 143 | } 144 | 145 | public boolean isIsSpi() { 146 | return isSpi; 147 | } 148 | 149 | public void setIsSpi(boolean isSpi) { 150 | this.isSpi = isSpi; 151 | } 152 | 153 | public int getReleaseDate() { 154 | return releaseDate; 155 | } 156 | 157 | public void setReleaseDate(int releaseDate) { 158 | this.releaseDate = releaseDate; 159 | } 160 | 161 | public boolean isScannedDrivers() { 162 | return scannedDrivers; 163 | } 164 | 165 | public void setScannedDrivers(boolean scannedDrivers) { 166 | this.scannedDrivers = scannedDrivers; 167 | } 168 | 169 | public List getI2cDeviceDriverInfos() { 170 | return i2cDeviceDriverInfos; 171 | } 172 | 173 | public void setI2cDeviceDriverInfos(List i2cDeviceDriverInfos) { 174 | this.i2cDeviceDriverInfos = i2cDeviceDriverInfos; 175 | } 176 | 177 | public List getAddresses() { 178 | return addresses; 179 | } 180 | 181 | public void setAddresses(List addresses) { 182 | this.addresses = addresses; 183 | } 184 | 185 | public static class I2cDeviceDriverInfo { 186 | @SerializedName("link") 187 | @Expose 188 | private String link; 189 | @SerializedName("title") 190 | @Expose 191 | private String title; 192 | 193 | public String getLink() { 194 | return link; 195 | } 196 | 197 | public void setLink(String link) { 198 | this.link = link; 199 | } 200 | 201 | public String getTitle() { 202 | return title; 203 | } 204 | 205 | public void setTitle(String title) { 206 | this.title = title; 207 | } 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/ykc3/android/usbi2c/app/device/info/I2cDeviceInfoRegistry.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Victor Antonovich 3 | * 4 | * This work is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Lesser General Public License 6 | * as published by the Free Software Foundation; either version 2.1 7 | * of the License, or (at your option) any later version. 8 | * 9 | * This work is distributed in the hope that it will be useful, but 10 | * without any warranty; without even the implied warranty of merchantability 11 | * or fitness for a particular purpose. See the GNU Lesser General Public 12 | * License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with this library; if not, write to the Free Software Foundation, Inc., 16 | * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. 17 | */ 18 | 19 | package com.github.ykc3.android.usbi2c.app.device.info; 20 | 21 | import android.content.res.Resources; 22 | 23 | import com.google.gson.Gson; 24 | 25 | import java.io.BufferedReader; 26 | import java.io.IOException; 27 | import java.io.InputStream; 28 | import java.io.InputStreamReader; 29 | import java.io.StringWriter; 30 | import java.io.Writer; 31 | import java.util.ArrayList; 32 | import java.util.Arrays; 33 | import java.util.List; 34 | 35 | /** 36 | * I2C device info registry class. 37 | */ 38 | public class I2cDeviceInfoRegistry { 39 | private final List deviceInfos; 40 | 41 | private I2cDeviceInfoRegistry(List deviceInfos) { 42 | this.deviceInfos = deviceInfos; 43 | } 44 | 45 | public static I2cDeviceInfoRegistry createFromJson(String deviceDatabaseJson) { 46 | Gson gson = new Gson(); 47 | I2cDeviceInfo[] i2cDeviceInfoArray = gson.fromJson(deviceDatabaseJson, I2cDeviceInfo[].class); 48 | return new I2cDeviceInfoRegistry(Arrays.asList(i2cDeviceInfoArray)); 49 | } 50 | 51 | public static I2cDeviceInfoRegistry createFromResource(Resources resources, 52 | int deviceDatabaseResourceId) throws IOException { 53 | InputStream resourceReader = resources.openRawResource(deviceDatabaseResourceId); 54 | 55 | Writer writer = new StringWriter(); 56 | try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceReader, "UTF-8"))) { 57 | String line = reader.readLine(); 58 | while (line != null) { 59 | writer.write(line); 60 | line = reader.readLine(); 61 | } 62 | } 63 | 64 | return createFromJson(writer.toString()); 65 | } 66 | 67 | public List findDevicesByAddress(int address) { 68 | List result = new ArrayList<>(); 69 | for (I2cDeviceInfo deviceInfo : deviceInfos) { 70 | if (deviceInfo.getAddresses().contains(address)) { 71 | result.add(deviceInfo); 72 | } 73 | } 74 | return result; 75 | } 76 | 77 | public I2cDeviceInfo findDeviceByPartNumber(String partNumber) { 78 | for (I2cDeviceInfo deviceInfo : deviceInfos) { 79 | if (deviceInfo.getPartNumber().equals(partNumber)) { 80 | return deviceInfo; 81 | } 82 | } 83 | return null; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/ykc3/android/usbi2c/app/view/CustomRecyclerView.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Victor Antonovich 3 | * 4 | * This work is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Lesser General Public License 6 | * as published by the Free Software Foundation; either version 2.1 7 | * of the License, or (at your option) any later version. 8 | * 9 | * This work is distributed in the hope that it will be useful, but 10 | * without any warranty; without even the implied warranty of merchantability 11 | * or fitness for a particular purpose. See the GNU Lesser General Public 12 | * License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with this library; if not, write to the Free Software Foundation, Inc., 16 | * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. 17 | */ 18 | 19 | package com.github.ykc3.android.usbi2c.app.view; 20 | 21 | import android.content.Context; 22 | import android.util.AttributeSet; 23 | import android.view.View; 24 | 25 | import androidx.annotation.NonNull; 26 | import androidx.annotation.Nullable; 27 | import androidx.recyclerview.widget.RecyclerView; 28 | 29 | /** 30 | * Custom {@link RecyclerView} supporting setEmptyView() method. 31 | */ 32 | public class CustomRecyclerView extends RecyclerView { 33 | private View emptyView; 34 | 35 | public CustomRecyclerView(@NonNull Context context) { 36 | super(context); 37 | } 38 | 39 | public CustomRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs) { 40 | super(context, attrs); 41 | } 42 | 43 | public CustomRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 44 | super(context, attrs, defStyleAttr); 45 | } 46 | 47 | final AdapterDataObserver observer = new AdapterDataObserver() { 48 | @Override 49 | public void onChanged() { 50 | super.onChanged(); 51 | checkNotEmpty(); 52 | } 53 | 54 | @Override 55 | public void onItemRangeInserted(int positionStart, int itemCount) { 56 | super.onItemRangeInserted(positionStart, itemCount); 57 | checkNotEmpty(); 58 | } 59 | 60 | @Override 61 | public void onItemRangeRemoved(int positionStart, int itemCount) { 62 | super.onItemRangeRemoved(positionStart, itemCount); 63 | checkNotEmpty(); 64 | } 65 | }; 66 | 67 | private void checkNotEmpty() { 68 | if (!isEmpty()) { 69 | showEmptyView(false); 70 | } 71 | } 72 | 73 | @Override 74 | public void setAdapter(@Nullable Adapter adapter) { 75 | Adapter oldAdapter = getAdapter(); 76 | super.setAdapter(adapter); 77 | 78 | if (oldAdapter != null) { 79 | oldAdapter.unregisterAdapterDataObserver(observer); 80 | } 81 | 82 | if (adapter != null) { 83 | adapter.registerAdapterDataObserver(observer); 84 | } 85 | } 86 | 87 | public boolean isEmpty() { 88 | Adapter adapter = getAdapter(); 89 | return (adapter != null) && (adapter.getItemCount() == 0); 90 | } 91 | 92 | public void showEmptyView(boolean isShowEmptyView) { 93 | if (emptyView != null) { 94 | emptyView.setVisibility(isShowEmptyView ? VISIBLE : GONE); 95 | this.setVisibility(isShowEmptyView ? GONE : VISIBLE); 96 | } 97 | } 98 | 99 | public void setEmptyView(View emptyView) { 100 | this.emptyView = emptyView; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_outline_info_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/layout-w768dp/adapter_list.xml: -------------------------------------------------------------------------------- 1 | 2 | 19 | 20 | 32 | 33 | 38 | 39 | 44 | 45 | 48 | 49 | 58 | 59 | 70 | 71 | 72 | 73 | 74 | 75 | 80 | 81 | -------------------------------------------------------------------------------- /app/src/main/res/layout/about.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 12 | 13 | 20 | 21 | 26 | 27 | 33 | 34 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_adapter_list.xml: -------------------------------------------------------------------------------- 1 | 2 | 19 | 20 | 27 | 28 | 32 | 33 | 39 | 40 | 41 | 42 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_device_list.xml: -------------------------------------------------------------------------------- 1 | 2 | 19 | 20 | 28 | 29 | 33 | 34 | 39 | 40 | 41 | 42 | 47 | 48 | -------------------------------------------------------------------------------- /app/src/main/res/layout/adapter_list.xml: -------------------------------------------------------------------------------- 1 | 2 | 19 | 20 | 25 | 26 | 29 | 30 | 40 | 41 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /app/src/main/res/layout/adapter_list_content.xml: -------------------------------------------------------------------------------- 1 | 2 | 19 | 20 | 24 | 25 | 31 | 32 | 38 | -------------------------------------------------------------------------------- /app/src/main/res/layout/device_list.xml: -------------------------------------------------------------------------------- 1 | 2 | 19 | 20 | 25 | 26 | 29 | 30 | 41 | 42 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /app/src/main/res/layout/device_list_content.xml: -------------------------------------------------------------------------------- 1 | 2 | 19 | 20 | 24 | 25 | 26 | 32 | 38 | -------------------------------------------------------------------------------- /app/src/main/res/menu/toolbar.xml: -------------------------------------------------------------------------------- 1 | 18 | 19 | 21 | 26 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/3cky/usb-i2c-android/956f40840044566fca4ed53c39cc04a4b3474454/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/3cky/usb-i2c-android/956f40840044566fca4ed53c39cc04a4b3474454/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/3cky/usb-i2c-android/956f40840044566fca4ed53c39cc04a4b3474454/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/3cky/usb-i2c-android/956f40840044566fca4ed53c39cc04a4b3474454/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/3cky/usb-i2c-android/956f40840044566fca4ed53c39cc04a4b3474454/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/3cky/usb-i2c-android/956f40840044566fca4ed53c39cc04a4b3474454/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/3cky/usb-i2c-android/956f40840044566fca4ed53c39cc04a4b3474454/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/3cky/usb-i2c-android/956f40840044566fca4ed53c39cc04a4b3474454/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/3cky/usb-i2c-android/956f40840044566fca4ed53c39cc04a4b3474454/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/3cky/usb-i2c-android/956f40840044566fca4ed53c39cc04a4b3474454/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/3cky/usb-i2c-android/956f40840044566fca4ed53c39cc04a4b3474454/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/3cky/usb-i2c-android/956f40840044566fca4ed53c39cc04a4b3474454/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/3cky/usb-i2c-android/956f40840044566fca4ed53c39cc04a4b3474454/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/3cky/usb-i2c-android/956f40840044566fca4ed53c39cc04a4b3474454/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/3cky/usb-i2c-android/956f40840044566fca4ed53c39cc04a4b3474454/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #008577 4 | #00574B 5 | #D81B60 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 19 | 20 | 21 | 16dp 22 | 200dp 23 | 16dp 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #26A69A 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | USB I²C Scanner 3 | USB I²C Adapters 4 | I²C Devices 5 | Scanning for USB I²C adapters… 6 | Scanning I²C address: 0x%1$02x 7 | Unknown device 8 | No USB I²C adapters found\n(pull to refresh) 9 | No I²C devices found\n(pull to refresh) 10 | Permission denied 11 | About 12 | I²C Scanner Logo 13 | I²C device scanner sample app for library allowing communication with USB I²C adapters connected to the Android USB Host: 14 | https://github.com/3cky/usb-i2c-android 15 | Version: %s 16 | Unknown 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | // Project semantic versioning components (https://semver.org/) 4 | def projectVersionMajor = 1 5 | def projectVersionMinor = 4 6 | def projectVersionPatch = 0 7 | 8 | buildscript { 9 | repositories { 10 | google() 11 | mavenCentral() 12 | } 13 | dependencies { 14 | classpath 'com.android.tools.build:gradle:8.8.0' 15 | } 16 | } 17 | 18 | allprojects { 19 | repositories { 20 | google() 21 | mavenCentral() 22 | } 23 | } 24 | 25 | ext { 26 | versions = [ 27 | sdk: [min: 21, target: 34, compile: 35], 28 | project: [major: projectVersionMajor, 29 | minor: projectVersionMinor, 30 | patch: projectVersionPatch, 31 | number: "${projectVersionMajor}.${projectVersionMinor}.${projectVersionPatch}"]] 32 | } 33 | 34 | tasks.register('clean', Delete) { 35 | delete rootProject.buildDir 36 | } 37 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | android.enableJetifier=true 10 | android.useAndroidX=true 11 | org.gradle.jvmargs=-Xmx1536m 12 | android.nonTransitiveRClass=false 13 | android.nonFinalResIds=false 14 | # When configured, Gradle will run in incubating parallel mode. 15 | # This option should only be used with decoupled projects. More details, visit 16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 17 | # org.gradle.parallel=true 18 | 19 | 20 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/3cky/usb-i2c-android/956f40840044566fca4ed53c39cc04a4b3474454/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sun Jul 05 21:00:02 MSK 2020 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-8.10.2-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /jitpack.yml: -------------------------------------------------------------------------------- 1 | jdk: 2 | - openjdk17 3 | 4 | install: 5 | - echo "Publishing library" 6 | - ./gradlew clean -xtest -xlint lib:build lib:publishToMavenLocal 7 | -------------------------------------------------------------------------------- /lib/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'maven-publish' 3 | 4 | android { 5 | namespace 'com.github.ykc3.android.usbi2c' 6 | 7 | defaultConfig { 8 | minSdkVersion versions.sdk.min 9 | targetSdkVersion versions.sdk.target 10 | compileSdk versions.sdk.compile 11 | } 12 | 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | 20 | publishing { 21 | singleVariant("release") { 22 | withSourcesJar() 23 | withJavadocJar() 24 | } 25 | } 26 | } 27 | 28 | dependencies { 29 | implementation fileTree(dir: 'libs', include: ['*.jar']) 30 | } 31 | 32 | afterEvaluate { 33 | publishing { 34 | publications { 35 | release(MavenPublication) { 36 | from components.release 37 | 38 | groupId 'com.github.3cky' 39 | artifactId 'usb-i2c-android' 40 | version versions.project.number 41 | } 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /lib/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /lib/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /lib/src/main/java/com/github/ykc3/android/usbi2c/UsbI2cAdapter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Victor Antonovich 3 | * 4 | * This work is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Lesser General Public License 6 | * as published by the Free Software Foundation; either version 2.1 7 | * of the License, or (at your option) any later version. 8 | * 9 | * This work is distributed in the hope that it will be useful, but 10 | * without any warranty; without even the implied warranty of merchantability 11 | * or fitness for a particular purpose. See the GNU Lesser General Public 12 | * License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with this library; if not, write to the Free Software Foundation, Inc., 16 | * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. 17 | */ 18 | 19 | package com.github.ykc3.android.usbi2c; 20 | 21 | import android.hardware.usb.UsbDevice; 22 | 23 | import java.io.IOException; 24 | 25 | /** 26 | * I2C adapter connected to the USB bus. 27 | */ 28 | public interface UsbI2cAdapter extends AutoCloseable { 29 | /** Standard clock speed (100 Kbit/s) */ 30 | int CLOCK_SPEED_STANDARD = 100000; 31 | /** Fast clock speed (400 Kbit/s) */ 32 | int CLOCK_SPEED_FAST = 400000; 33 | /** Fast plus clock speed (1 Mbit/s) */ 34 | int CLOCK_SPEED_FAST_PLUS = 1000000; 35 | /** High clock speed (3.4 Mbit/s) */ 36 | int CLOCK_SPEED_HIGH = 3400000; 37 | 38 | /** 39 | * Get I2C adapter name. 40 | * 41 | * @return I2C adapter name 42 | */ 43 | String getName(); 44 | 45 | /** 46 | * Get I2C adapter identifier string. 47 | * 48 | * @return I2C adapter identifier string 49 | */ 50 | String getId(); 51 | 52 | /** 53 | * Get reference to underlying {@link UsbDevice} for this I2C adapter. 54 | * 55 | * @return reference to UsbDevice 56 | */ 57 | UsbDevice getUsbDevice(); 58 | 59 | /** 60 | * Open I2C adapter for communicating to connected I2C devices. 61 | * 62 | * @throws IOException in case of I/O error 63 | * @throws IllegalStateException if I2C adapter is already opened 64 | */ 65 | void open() throws IOException; 66 | 67 | /** 68 | * Get reference {@link UsbI2cDevice} connected to this I2C adapter. 69 | * 70 | * @param address I2C device address 71 | * @return reference to I2C device connected to this I2C adapter 72 | * @throws IllegalStateException if I2C adapter is not opened or closed 73 | */ 74 | UsbI2cDevice getDevice(int address); 75 | 76 | /** 77 | * Check I2C bus clock speed is supported by adapter. 78 | * UsbI2cAdapter.SPEED_STANDARD is guaranteed to be supported by all adapters. 79 | * 80 | * @param speed I2C bus clock speed value to check (in bit/s) 81 | * @return true if speed is supported by I2C adapter, false if not supported 82 | * @since 1.2 83 | */ 84 | boolean isClockSpeedSupported(int speed); 85 | 86 | /** 87 | * Set I2C bus clock speed. Default value is {@code UsbI2cAdapter.CLOCK_SPEED_STANDARD}. 88 | * 89 | * @param speed I2C bus clock speed value to set (in Hz) 90 | * @throws IllegalArgumentException if this I2C bus clock speed is not supported by the adapter 91 | * @throws IOException in case of I/O error 92 | * @since 1.2 93 | */ 94 | void setClockSpeed(int speed) throws IOException; 95 | } 96 | -------------------------------------------------------------------------------- /lib/src/main/java/com/github/ykc3/android/usbi2c/UsbI2cDevice.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Victor Antonovich 3 | * 4 | * This work is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Lesser General Public License 6 | * as published by the Free Software Foundation; either version 2.1 7 | * of the License, or (at your option) any later version. 8 | * 9 | * This work is distributed in the hope that it will be useful, but 10 | * without any warranty; without even the implied warranty of merchantability 11 | * or fitness for a particular purpose. See the GNU Lesser General Public 12 | * License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with this library; if not, write to the Free Software Foundation, Inc., 16 | * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. 17 | */ 18 | 19 | package com.github.ykc3.android.usbi2c; 20 | 21 | import java.io.IOException; 22 | 23 | /** 24 | * Slave I2C device connected to the I2C bus and accessible through a {@link UsbI2cAdapter}. 25 | */ 26 | public interface UsbI2cDevice { 27 | 28 | /** 29 | * Get I2C device address. 30 | * 31 | * @return I2C device address 32 | */ 33 | int getAddress(); 34 | 35 | /** 36 | * Read data from the device into buffer. 37 | * 38 | * @param buffer buffer to read data into 39 | * @param length number of bytes to read, may not be larger than the buffer size 40 | * @throws IOException in case of I/O error 41 | */ 42 | void read(byte[] buffer, int length) throws IOException; 43 | 44 | /** 45 | * Read a byte from a given register. 46 | * 47 | * @param reg the register to read from (0x00-0xFF) 48 | * @return the value read from the device 49 | * @throws IOException in case of I/O error 50 | */ 51 | byte readRegByte(int reg) throws IOException; 52 | 53 | /** 54 | * Read two consecutive register values as a 16-bit little-endian word. 55 | * The first register address corresponds to the least significant byte (LSB) in the word, 56 | * followed by the most significant byte (MSB). 57 | * 58 | * @param reg the first register to read from (0x00-0xFF) 59 | * @return the value read from the device 60 | * @throws IOException in case of I/O error 61 | */ 62 | short readRegWord(int reg) throws IOException; 63 | 64 | /** 65 | * Read multiple consecutive register values as an array. 66 | * 67 | * @param reg the start register to read from (0x00-0xFF) 68 | * @param buffer buffer to read data into 69 | * @param length number of bytes to read, may not be larger than the buffer size 70 | * @throws IOException in case of I/O error 71 | */ 72 | void readRegBuffer(int reg, byte[] buffer, int length) throws IOException; 73 | 74 | /** 75 | * Write data to the device. 76 | * 77 | * @param buffer data to write 78 | * @param length number of bytes to write, may not be larger than the buffer size 79 | * @throws IOException in case of I/O error 80 | */ 81 | void write(byte[] buffer, int length) throws IOException; 82 | 83 | /** 84 | * Write a byte to a given register. 85 | * 86 | * @param reg the register to write to (0x00-0xFF) 87 | * @param data value to write 88 | * @throws IOException in case of I/O error 89 | */ 90 | void writeRegByte(int reg, byte data) throws IOException; 91 | 92 | /** 93 | * Write two consecutive register values as a 16-bit little-endian word. 94 | * The first register address corresponds to the least significant byte (LSB) in the word, 95 | * followed by the most significant byte (MSB). 96 | * 97 | * @param reg the first register to write to (0x00-0xFF) 98 | * @param data value to write 99 | * @throws IOException in case of I/O error 100 | */ 101 | void writeRegWord(int reg, short data) throws IOException; 102 | 103 | /** 104 | * Write multiple consecutive register values from an array. 105 | * 106 | * @param reg the start register to write to (0x00-0xFF) 107 | * @param buffer data to write 108 | * @param length number of bytes to write, may not be larger than the buffer size 109 | * @throws IOException in case of I/O error 110 | */ 111 | void writeRegBuffer(int reg, byte[] buffer, int length) throws IOException; 112 | } 113 | -------------------------------------------------------------------------------- /lib/src/main/java/com/github/ykc3/android/usbi2c/UsbI2cManager.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Victor Antonovich 3 | * 4 | * This work is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Lesser General Public License 6 | * as published by the Free Software Foundation; either version 2.1 7 | * of the License, or (at your option) any later version. 8 | * 9 | * This work is distributed in the hope that it will be useful, but 10 | * without any warranty; without even the implied warranty of merchantability 11 | * or fitness for a particular purpose. See the GNU Lesser General Public 12 | * License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with this library; if not, write to the Free Software Foundation, Inc., 16 | * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. 17 | */ 18 | 19 | package com.github.ykc3.android.usbi2c; 20 | 21 | import android.hardware.usb.UsbDevice; 22 | import android.hardware.usb.UsbManager; 23 | 24 | import com.github.ykc3.android.usbi2c.adapter.Ch341UsbI2cAdapter; 25 | import com.github.ykc3.android.usbi2c.adapter.Cp2112UsbI2cAdapter; 26 | import com.github.ykc3.android.usbi2c.adapter.Ft232hUsbI2cAdapter; 27 | import com.github.ykc3.android.usbi2c.adapter.Ft260UsbI2cAdapter; 28 | import com.github.ykc3.android.usbi2c.adapter.TinyUsbI2cAdapter; 29 | 30 | import java.lang.reflect.Constructor; 31 | import java.lang.reflect.InvocationTargetException; 32 | import java.lang.reflect.Method; 33 | import java.util.ArrayList; 34 | import java.util.Collections; 35 | import java.util.List; 36 | import java.util.Objects; 37 | 38 | /** 39 | * Class for finding and creating {@link UsbI2cAdapter}s for connected USB I2C devices. 40 | */ 41 | public class UsbI2cManager { 42 | private final UsbManager usbManager; 43 | 44 | private final List> usbI2cAdapters; 45 | 46 | /** 47 | * Class holding USB device vendor and product identifiers. 48 | *

49 | * This class used to associate {@link UsbI2cAdapter} with supported USB devices. 50 | * Each {@link UsbI2cAdapter} implementation must declare its supported USB device identifiers 51 | * using the static method {@code getSupportedUsbDeviceIdentifiers()}. 52 | *

53 | * @see UsbDevice#getVendorId() 54 | * @see UsbDevice#getProductId() 55 | */ 56 | public static class UsbDeviceIdentifier { 57 | private final int vendorId; 58 | private final int productId; 59 | 60 | public UsbDeviceIdentifier(int vendorId, int productId) { 61 | this.vendorId = vendorId; 62 | this.productId = productId; 63 | } 64 | 65 | /** 66 | * Get USB device vendor identifier. 67 | * @return USB device vendor identifier 68 | */ 69 | public int getVendorId() { 70 | return vendorId; 71 | } 72 | 73 | /** 74 | * Get USB device product identifier. 75 | * @return USB device product identifier 76 | */ 77 | public int getProductId() { 78 | return productId; 79 | } 80 | 81 | @Override 82 | public boolean equals(Object o) { 83 | if (this == o) return true; 84 | if (o == null || getClass() != o.getClass()) return false; 85 | UsbDeviceIdentifier that = (UsbDeviceIdentifier) o; 86 | return vendorId == that.vendorId && productId == that.productId; 87 | } 88 | 89 | @Override 90 | public int hashCode() { 91 | return Objects.hash(vendorId, productId); 92 | } 93 | } 94 | 95 | private UsbI2cManager(UsbManager usbManager, 96 | List> usbI2cAdapters) { 97 | this.usbManager = usbManager; 98 | this.usbI2cAdapters = usbI2cAdapters; 99 | } 100 | 101 | /** 102 | * Create {@link Builder} object for building new {@link UsbI2cManager} 103 | * 104 | * @param usbManager {@link UsbManager} reference 105 | * @return new Builder 106 | */ 107 | public static Builder create(UsbManager usbManager) { 108 | return new Builder(usbManager); 109 | } 110 | 111 | /** 112 | * Get list of supported {@link UsbI2cAdapter}s. 113 | * 114 | * @return mutable list of supported USB I2C adapters 115 | */ 116 | public static List> getSupportedUsbI2cAdapters() { 117 | List> usbI2cAdapters = new ArrayList<>(); 118 | usbI2cAdapters.add(TinyUsbI2cAdapter.class); 119 | usbI2cAdapters.add(Cp2112UsbI2cAdapter.class); 120 | usbI2cAdapters.add(Ch341UsbI2cAdapter.class); 121 | usbI2cAdapters.add(Ft232hUsbI2cAdapter.class); 122 | usbI2cAdapters.add(Ft260UsbI2cAdapter.class); 123 | return usbI2cAdapters; 124 | } 125 | 126 | public UsbManager getUsbManager() { 127 | return usbManager; 128 | } 129 | 130 | /** 131 | * Get list of all supported {@link UsbI2cAdapter}s currently connected to USB port. 132 | * 133 | * @return list of all connected USB I2C adapters 134 | */ 135 | public List getAdapters() { 136 | List adapters = new ArrayList<>(); 137 | for (UsbDevice device : usbManager.getDeviceList().values()) { 138 | UsbI2cAdapter adapter = getAdapter(device); 139 | if (adapter != null) { 140 | adapters.add(adapter); 141 | } 142 | } 143 | return Collections.unmodifiableList(adapters); 144 | } 145 | 146 | /** 147 | * Get {@link UsbI2cAdapter} for given {@link UsbDevice}. 148 | * 149 | * @param device USB device 150 | * @return reference to USB I2C adapter supporting this USB device or null if no 151 | * USB I2C adapter supporting this USB device was found 152 | */ 153 | public UsbI2cAdapter getAdapter(UsbDevice device) { 154 | UsbDeviceIdentifier deviceUsbIdentifier = new UsbDeviceIdentifier(device.getVendorId(), 155 | device.getProductId()); 156 | 157 | for (Class adapterClass : usbI2cAdapters) { 158 | final Method method; 159 | try { 160 | method = adapterClass.getMethod("getSupportedUsbDeviceIdentifiers"); 161 | } catch (SecurityException | NoSuchMethodException e) { 162 | throw new RuntimeException(e); 163 | } 164 | 165 | final UsbDeviceIdentifier[] adapterUsbDeviceIdentifiers; 166 | try { 167 | adapterUsbDeviceIdentifiers = (UsbDeviceIdentifier[]) method.invoke(null); 168 | } catch (IllegalArgumentException | IllegalAccessException 169 | | InvocationTargetException e) { 170 | throw new RuntimeException(e); 171 | } 172 | 173 | for (UsbDeviceIdentifier adapterUsbDeviceIdentifier : adapterUsbDeviceIdentifiers) { 174 | if (!adapterUsbDeviceIdentifier.equals(deviceUsbIdentifier)) { 175 | continue; 176 | } 177 | try { 178 | final Constructor ctor = 179 | adapterClass.getConstructor(UsbI2cManager.class, UsbDevice.class); 180 | return ctor.newInstance(this, device); 181 | } catch (NoSuchMethodException | IllegalArgumentException | InstantiationException | 182 | IllegalAccessException | InvocationTargetException e) { 183 | throw new RuntimeException(e); 184 | } 185 | } 186 | } 187 | 188 | return null; 189 | } 190 | 191 | /** 192 | * Builder class for {@link UsbI2cManager}. 193 | */ 194 | public static class Builder { 195 | private final UsbManager usbManager; 196 | 197 | private List> usbI2cAdapters; 198 | 199 | Builder(UsbManager usbManager) { 200 | this.usbManager = usbManager; 201 | } 202 | 203 | /** 204 | * Set list of USB I2C adapters to use. 205 | *

206 | * If this method not called, list returned by 207 | * {@link #getSupportedUsbI2cAdapters()} used by default. 208 | *

209 | * @param usbI2cAdapters list of USB I2C adapters to use 210 | * @return this Builder object 211 | */ 212 | public Builder setAdapters(List> usbI2cAdapters) { 213 | this.usbI2cAdapters = usbI2cAdapters; 214 | return this; 215 | } 216 | 217 | /** @return new {@link UsbI2cManager} object */ 218 | public UsbI2cManager build() { 219 | if (usbI2cAdapters == null) { 220 | usbI2cAdapters = getSupportedUsbI2cAdapters(); 221 | } 222 | return new UsbI2cManager(usbManager, usbI2cAdapters); 223 | } 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /lib/src/main/java/com/github/ykc3/android/usbi2c/adapter/BaseUsbI2cAdapter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Victor Antonovich 3 | * 4 | * This work is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Lesser General Public License 6 | * as published by the Free Software Foundation; either version 2.1 7 | * of the License, or (at your option) any later version. 8 | * 9 | * This work is distributed in the hope that it will be useful, but 10 | * without any warranty; without even the implied warranty of merchantability 11 | * or fitness for a particular purpose. See the GNU Lesser General Public 12 | * License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with this library; if not, write to the Free Software Foundation, Inc., 16 | * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. 17 | */ 18 | 19 | package com.github.ykc3.android.usbi2c.adapter; 20 | 21 | import android.hardware.usb.UsbDevice; 22 | import android.hardware.usb.UsbDeviceConnection; 23 | import android.hardware.usb.UsbEndpoint; 24 | import android.hardware.usb.UsbInterface; 25 | 26 | import com.github.ykc3.android.usbi2c.UsbI2cAdapter; 27 | import com.github.ykc3.android.usbi2c.UsbI2cDevice; 28 | import com.github.ykc3.android.usbi2c.UsbI2cManager; 29 | 30 | import java.io.IOException; 31 | import java.util.concurrent.locks.ReentrantLock; 32 | 33 | /** 34 | * Base class for USB I2C adapters. 35 | */ 36 | abstract class BaseUsbI2cAdapter implements UsbI2cAdapter { 37 | // Linux kernel flags 38 | static final int I2C_M_RD = 0x01; // read data, from slave to master 39 | 40 | protected static final int USB_TIMEOUT_MILLIS = 1000; 41 | 42 | protected final UsbI2cManager i2cManager; 43 | protected final UsbDevice usbDevice; 44 | 45 | private UsbDeviceConnection usbDeviceConnection; 46 | 47 | private UsbEndpoint usbReadEndpoint; 48 | private UsbEndpoint usbWriteEndpoint; 49 | 50 | protected static final int MAX_MESSAGE_SIZE = 8192; 51 | 52 | private final byte[] buffer = new byte[MAX_MESSAGE_SIZE + 1]; 53 | 54 | protected final ReentrantLock accessLock = new ReentrantLock(); 55 | 56 | protected int clockSpeed = CLOCK_SPEED_STANDARD; 57 | 58 | protected abstract class BaseUsbI2cDevice implements UsbI2cDevice { 59 | final int address; 60 | 61 | BaseUsbI2cDevice(int address) { 62 | this.address = (address & 0x7f); 63 | } 64 | 65 | @Override 66 | public int getAddress() { 67 | return address; 68 | } 69 | 70 | @Override 71 | public byte readRegByte(int reg) throws IOException { 72 | try { 73 | accessLock.lock(); 74 | readRegBuffer(reg, buffer, 1); 75 | return buffer[0]; 76 | } finally { 77 | accessLock.unlock(); 78 | } 79 | } 80 | 81 | @Override 82 | public short readRegWord(int reg) throws IOException { 83 | try { 84 | accessLock.lock(); 85 | readRegBuffer(reg, buffer, 2); 86 | return (short) ((buffer[0] & 0xFF) | (buffer[1] << 8)); 87 | } finally { 88 | accessLock.unlock(); 89 | } 90 | } 91 | 92 | @Override 93 | public void writeRegByte(int reg, byte data) throws IOException { 94 | try { 95 | accessLock.lock(); 96 | buffer[0] = (byte) reg; 97 | buffer[1] = data; 98 | write(buffer, 2); 99 | } finally { 100 | accessLock.unlock(); 101 | } 102 | } 103 | 104 | @Override 105 | public void writeRegWord(int reg, short data) throws IOException { 106 | try { 107 | accessLock.lock(); 108 | buffer[0] = (byte) reg; 109 | buffer[1] = (byte) data; 110 | buffer[2] = (byte) (data >>> 8); 111 | write(buffer, 3); 112 | } finally { 113 | accessLock.unlock(); 114 | } 115 | } 116 | 117 | @Override 118 | public void writeRegBuffer(int reg, byte[] buffer, int length) throws IOException { 119 | try { 120 | accessLock.lock(); 121 | if (length > MAX_MESSAGE_SIZE) { 122 | throw new IllegalArgumentException("Message is too long: " + length + " byte(s)"); 123 | } 124 | BaseUsbI2cAdapter.this.buffer[0] = (byte) reg; 125 | System.arraycopy(buffer, 0, BaseUsbI2cAdapter.this.buffer, 1, length); 126 | write(BaseUsbI2cAdapter.this.buffer, length + 1); 127 | } finally { 128 | accessLock.unlock(); 129 | } 130 | } 131 | 132 | @Override 133 | public void readRegBuffer(int reg, byte[] buffer, int length) throws IOException { 134 | try { 135 | accessLock.lock(); 136 | deviceReadReg(reg, buffer, length); 137 | } finally { 138 | accessLock.unlock(); 139 | } 140 | } 141 | 142 | @Override 143 | public void read(byte[] buffer, int length) throws IOException { 144 | try { 145 | accessLock.lock(); 146 | deviceRead(buffer, length); 147 | } finally { 148 | accessLock.unlock(); 149 | } 150 | } 151 | 152 | @Override 153 | public void write(byte[] buffer, int length) throws IOException { 154 | try { 155 | accessLock.lock(); 156 | deviceWrite(buffer, length); 157 | } finally { 158 | accessLock.unlock(); 159 | } 160 | } 161 | 162 | protected abstract void deviceReadReg(int reg, byte[] buffer, int length) throws IOException; 163 | 164 | protected abstract void deviceWrite(byte[] buffer, int length) throws IOException; 165 | 166 | protected abstract void deviceRead(byte[] buffer, int length) throws IOException; 167 | } 168 | 169 | BaseUsbI2cAdapter(UsbI2cManager i2cManager, UsbDevice usbDevice) { 170 | this.i2cManager = i2cManager; 171 | this.usbDevice = usbDevice; 172 | } 173 | 174 | @Override 175 | public String getId() { 176 | return usbDevice.getDeviceName(); 177 | } 178 | 179 | protected boolean isOpened() { 180 | return (usbDeviceConnection != null); 181 | } 182 | 183 | protected void checkOpened() throws IllegalStateException { 184 | if (!isOpened()) { 185 | throw new IllegalStateException("Adapter is not opened or closed"); 186 | } 187 | } 188 | 189 | @Override 190 | public void open() throws IOException { 191 | if (isOpened()) { 192 | throw new IllegalStateException("Adapter already opened"); 193 | } 194 | 195 | usbDeviceConnection = i2cManager.getUsbManager().openDevice(usbDevice); 196 | if (usbDeviceConnection == null) { 197 | throw new IOException("Can't open adapter"); 198 | } 199 | 200 | for (int i = 0; i < usbDevice.getInterfaceCount(); i++) { 201 | UsbInterface usbDeviceInterface = usbDevice.getInterface(i); 202 | if (!usbDeviceConnection.claimInterface(usbDeviceInterface, true)) { 203 | throw new IOException("Can't claim adapter interfaces"); 204 | } 205 | } 206 | 207 | init(usbDevice); 208 | } 209 | 210 | protected void init(UsbDevice usbDevice) throws IOException { 211 | // Do nothing by default 212 | } 213 | 214 | @Override 215 | public void close() throws Exception { 216 | close(usbDevice); 217 | 218 | if (usbDeviceConnection != null) { 219 | for (int i = 0; i < usbDevice.getInterfaceCount(); i++) { 220 | UsbInterface usbDeviceInterface = usbDevice.getInterface(i); 221 | usbDeviceConnection.releaseInterface(usbDeviceInterface); 222 | } 223 | usbDeviceConnection.close(); 224 | usbDeviceConnection = null; 225 | } 226 | } 227 | 228 | protected void close(UsbDevice usbDevice) throws IOException { 229 | // Do nothing by default 230 | } 231 | 232 | 233 | @Override 234 | public UsbI2cDevice getDevice(int address) { 235 | checkOpened(); 236 | return getDeviceImpl(address); 237 | } 238 | 239 | protected abstract BaseUsbI2cDevice getDeviceImpl(int address); 240 | 241 | @Override 242 | public boolean isClockSpeedSupported(int speed) { 243 | return (speed == CLOCK_SPEED_STANDARD); 244 | } 245 | 246 | protected int getClockSpeed() { 247 | return clockSpeed; 248 | } 249 | 250 | @Override 251 | public void setClockSpeed(int speed) throws IOException { 252 | if (!isClockSpeedSupported(speed)) { 253 | throw new IllegalArgumentException("Clock speed is not supported: " + speed); 254 | } 255 | 256 | this.clockSpeed = speed; 257 | 258 | if (isOpened()) { 259 | try { 260 | accessLock.lock(); 261 | configure(); 262 | } finally { 263 | accessLock.unlock(); 264 | } 265 | } 266 | } 267 | 268 | protected void configure() throws IOException { 269 | // Do nothing by default 270 | } 271 | 272 | @Override 273 | public UsbDevice getUsbDevice() { 274 | return usbDevice; 275 | } 276 | 277 | /** 278 | * Send control transfer request to USB device. 279 | * 280 | * @param requestType control transfer request type 281 | * @param request control transfer request 282 | * @param value control transfer request value 283 | * @param index control transfer request index 284 | * @param data control transfer request data 285 | * @param length control transfer request data length 286 | * @throws IOException in case of control transfer error 287 | */ 288 | final void controlTransfer(int requestType, int request, int value, 289 | int index, byte[] data, int length) throws IOException { 290 | checkOpened(); 291 | int result = usbDeviceConnection.controlTransfer(requestType, request, value, 292 | index, data, length, USB_TIMEOUT_MILLIS); 293 | if (result != length) { 294 | throw new IOException(String.format("controlTransfer(requestType: 0x%x, " + 295 | "request: 0x%x, value: 0x%x, index: 0x%x, length: %d) failed: %d", 296 | requestType, request, value, index, length, result)); 297 | } 298 | } 299 | 300 | protected void setBulkEndpoints(UsbEndpoint readEndpoint, UsbEndpoint writeEndpoint) { 301 | this.usbReadEndpoint = readEndpoint; 302 | this.usbWriteEndpoint = writeEndpoint; 303 | } 304 | 305 | /** 306 | * Read bulk data from USB device to data buffer. 307 | * 308 | * @param data data buffer to read data 309 | * @param offset data buffer offset to read data 310 | * @param length data length to read 311 | * @param timeout data read timeout (in milliseconds) 312 | * @return actual length of read data 313 | * @throws IOException in case of data read error or timeout 314 | */ 315 | final int bulkRead(byte[] data, int offset, int length, int timeout) throws IOException { 316 | checkOpened(); 317 | if (usbReadEndpoint == null) { 318 | throw new IllegalStateException("Bulk read endpoint is not set"); 319 | } 320 | int res = usbDeviceConnection.bulkTransfer(usbReadEndpoint, data, offset, length, timeout); 321 | if (res < 0) { 322 | throw new IOException("Bulk read error: " + res); 323 | } 324 | return res; 325 | } 326 | 327 | /** 328 | * Write bulk data from data buffer to USB device. 329 | * 330 | * @param data data buffer to write data 331 | * @param length data length to write 332 | * @param timeout data read timeout (in milliseconds) 333 | * @throws IOException in case of data write error or timeout 334 | */ 335 | final void bulkWrite(byte[] data, int length, int timeout) throws IOException { 336 | checkOpened(); 337 | if (usbWriteEndpoint == null) { 338 | throw new IllegalStateException("Bulk write endpoint is not set"); 339 | } 340 | int res = usbDeviceConnection.bulkTransfer(usbWriteEndpoint, data, length, timeout); 341 | if (res < 0) { 342 | throw new IOException("Bulk write error: " + res); 343 | } 344 | if (res < length) { 345 | throw new IOException("Bulk write length error, expected: " + length 346 | + " byte(s), written: " + res + " byte(s)"); 347 | } 348 | } 349 | 350 | /** 351 | * Get I2C operation address byte value. 352 | * 353 | * @param address I2C address 354 | * @param isRead true for read I2C operation, false for write I2C operation 355 | * @return I2C operation address byte value 356 | */ 357 | static byte getAddressByte(int address, boolean isRead) { 358 | return (byte) ((address << 1) | (isRead ? 0x01 : 0x00)); 359 | } 360 | 361 | /** 362 | * Check data length. 363 | * 364 | * @param length data length 365 | * @param maxLength max data length 366 | */ 367 | protected void checkDataLength(int length, int maxLength) { 368 | if (length < 1 || length > maxLength) { 369 | throw new IllegalArgumentException(String.format("Invalid data length: %d (min 1, max %d)", 370 | length, maxLength)); 371 | } 372 | } 373 | } 374 | -------------------------------------------------------------------------------- /lib/src/main/java/com/github/ykc3/android/usbi2c/adapter/Ch341UsbI2cAdapter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Victor Antonovich 3 | * 4 | * This work is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Lesser General Public License 6 | * as published by the Free Software Foundation; either version 2.1 7 | * of the License, or (at your option) any later version. 8 | * 9 | * This work is distributed in the hope that it will be useful, but 10 | * without any warranty; without even the implied warranty of merchantability 11 | * or fitness for a particular purpose. See the GNU Lesser General Public 12 | * License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with this library; if not, write to the Free Software Foundation, Inc., 16 | * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. 17 | */ 18 | 19 | package com.github.ykc3.android.usbi2c.adapter; 20 | 21 | import android.hardware.usb.UsbConstants; 22 | import android.hardware.usb.UsbDevice; 23 | import android.hardware.usb.UsbEndpoint; 24 | import android.hardware.usb.UsbInterface; 25 | 26 | import com.github.ykc3.android.usbi2c.UsbI2cManager; 27 | 28 | import static com.github.ykc3.android.usbi2c.UsbI2cManager.UsbDeviceIdentifier; 29 | 30 | import java.io.IOException; 31 | 32 | /** 33 | * CH341 is a USB bus convert chip, providing UART, printer port, parallel and synchronous serial with 34 | * 2-wire or 4-wire through USB bus. 35 | *

36 | * Data Sheet 37 | */ 38 | public class Ch341UsbI2cAdapter extends BaseUsbI2cAdapter { 39 | // Adapter name 40 | public static final String ADAPTER_NAME = "CH341"; 41 | 42 | private static final int CH341_I2C_LOW_SPEED = 0; // low speed - 20kHz 43 | private static final int CH341_I2C_STANDARD_SPEED = 1; // standard speed - 100kHz 44 | private static final int CH341_I2C_FAST_SPEED = 2; // fast speed - 400kHz 45 | private static final int CH341_I2C_HIGH_SPEED = 3; // high speed - 750kHz 46 | 47 | private static final int CH341_CMD_I2C_STREAM = 0xAA; 48 | 49 | private static final int CH341_CMD_I2C_STM_STA = 0x74; 50 | private static final int CH341_CMD_I2C_STM_STO = 0x75; 51 | private static final int CH341_CMD_I2C_STM_OUT = 0x80; 52 | private static final int CH341_CMD_I2C_STM_IN = 0xC0; 53 | private static final int CH341_CMD_I2C_STM_SET = 0x60; 54 | private static final int CH341_CMD_I2C_STM_END = 0x00; 55 | 56 | // CH341 max transfer size 57 | private static final int MAX_TRANSFER_SIZE = 32; 58 | 59 | private final byte[] writeBuffer = new byte[MAX_TRANSFER_SIZE]; 60 | private final byte[] readBuffer = new byte[MAX_TRANSFER_SIZE]; 61 | 62 | class Ch341UsbI2cDevice extends BaseUsbI2cDevice { 63 | Ch341UsbI2cDevice(int address) { 64 | super(address); 65 | } 66 | 67 | @Override 68 | protected void deviceReadReg(int reg, byte[] buffer, int length) throws IOException { 69 | readRegData(address, reg, buffer, length); 70 | } 71 | 72 | @Override 73 | protected void deviceRead(byte[] buffer, int length) throws IOException { 74 | readData(address, buffer, length); 75 | } 76 | 77 | @Override 78 | protected void deviceWrite(byte[] buffer, int length) throws IOException { 79 | writeData(address, buffer, length); 80 | } 81 | } 82 | 83 | 84 | public Ch341UsbI2cAdapter(UsbI2cManager i2cManager, UsbDevice usbDevice) { 85 | super(i2cManager, usbDevice); 86 | } 87 | 88 | @Override 89 | public String getName() { 90 | return ADAPTER_NAME; 91 | } 92 | 93 | @Override 94 | protected BaseUsbI2cDevice getDeviceImpl(int address) { 95 | return new Ch341UsbI2cDevice(address); 96 | } 97 | 98 | @Override 99 | protected void init(UsbDevice usbDevice) throws IOException { 100 | if (usbDevice.getInterfaceCount() == 0) { 101 | throw new IOException("No interfaces found for device: " + usbDevice); 102 | } 103 | 104 | UsbInterface usbInterface = usbDevice.getInterface(0); 105 | if (usbInterface.getEndpointCount() < 2) { 106 | throw new IOException("No endpoints found for device: " + usbDevice); 107 | } 108 | 109 | UsbEndpoint usbReadEndpoint = null, usbWriteEndpoint = null; 110 | for (int i = 0; i < usbInterface.getEndpointCount(); i++) { 111 | UsbEndpoint usbEndpoint = usbInterface.getEndpoint(i); 112 | if (usbEndpoint.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK) { 113 | if (usbEndpoint.getDirection() == UsbConstants.USB_DIR_IN) { 114 | usbReadEndpoint = usbEndpoint; 115 | } else { 116 | usbWriteEndpoint = usbEndpoint; 117 | } 118 | } 119 | } 120 | if (usbReadEndpoint == null || usbWriteEndpoint == null) { 121 | throw new IOException("No read or write bulk endpoint found for device: " + usbDevice); 122 | } 123 | setBulkEndpoints(usbReadEndpoint, usbWriteEndpoint); 124 | 125 | configure(); 126 | } 127 | 128 | protected int getClockSpeedConstant(int speed) { 129 | switch (speed) { 130 | case 20000: 131 | return CH341_I2C_LOW_SPEED; 132 | case CLOCK_SPEED_STANDARD: 133 | return CH341_I2C_STANDARD_SPEED; 134 | case CLOCK_SPEED_FAST: 135 | return CH341_I2C_FAST_SPEED; 136 | case 750000: 137 | return CH341_I2C_HIGH_SPEED; 138 | } 139 | 140 | return -1; 141 | } 142 | 143 | @Override 144 | public boolean isClockSpeedSupported(int speed) { 145 | return (getClockSpeedConstant(speed) >= 0); 146 | } 147 | 148 | protected void configure() throws IOException { 149 | writeBuffer[0] = (byte) CH341_CMD_I2C_STREAM; 150 | writeBuffer[1] = (byte) (CH341_CMD_I2C_STM_SET | getClockSpeedConstant(getClockSpeed())); 151 | writeBuffer[2] = CH341_CMD_I2C_STM_END; 152 | writeBulkData(writeBuffer, 3); 153 | } 154 | 155 | protected void checkDataLength(int length, int bufferDataLength) { 156 | super.checkDataLength(length, Math.min(MAX_TRANSFER_SIZE - 6, bufferDataLength)); 157 | } 158 | 159 | private void writeData(int address, byte[] data, int length) throws IOException { 160 | checkDataLength(length, data.length); 161 | int i = 0; 162 | writeBuffer[i++] = (byte) CH341_CMD_I2C_STREAM; 163 | writeBuffer[i++] = CH341_CMD_I2C_STM_STA; // START condition 164 | writeBuffer[i++] = (byte) (CH341_CMD_I2C_STM_OUT | (length + 1)); // data length + 1 165 | writeBuffer[i++] = getAddressByte(address, false); 166 | System.arraycopy(data, 0, writeBuffer, i, length); 167 | i += length; 168 | writeBuffer[i++] = CH341_CMD_I2C_STM_STO; // STOP condition 169 | writeBuffer[i++] = CH341_CMD_I2C_STM_END; // end of transaction 170 | writeBulkData(writeBuffer, i); 171 | } 172 | 173 | private void readData(int address, byte[] data, int length) throws IOException { 174 | checkDataLength(length, data.length); 175 | checkDevicePresence(address); // to avoid weird phantom devices in scan results 176 | int i = 0; 177 | writeBuffer[i++] = (byte) CH341_CMD_I2C_STREAM; 178 | writeBuffer[i++] = CH341_CMD_I2C_STM_STA; // START condition 179 | writeBuffer[i++] = (byte) (CH341_CMD_I2C_STM_OUT | 1); // zero data length + 1 180 | writeBuffer[i++] = getAddressByte(address, true); 181 | if (length > 0) { 182 | for (int j = 0; j < length - 1; j++) { 183 | writeBuffer[i++] = (byte) (CH341_CMD_I2C_STM_IN | 1); 184 | } 185 | writeBuffer[i++] = (byte) CH341_CMD_I2C_STM_IN; 186 | } 187 | writeBuffer[i++] = CH341_CMD_I2C_STM_STO; // STOP condition 188 | writeBuffer[i++] = CH341_CMD_I2C_STM_END; // end of transaction 189 | transferBulkData(writeBuffer, i, data, length); 190 | } 191 | 192 | private void readRegData(int address, int reg, byte[] data, int length) throws IOException { 193 | checkDataLength(length, data.length); 194 | // Write register number 195 | int i = 0; 196 | writeBuffer[i++] = (byte) CH341_CMD_I2C_STREAM; 197 | writeBuffer[i++] = CH341_CMD_I2C_STM_STA; // START condition 198 | writeBuffer[i++] = (byte) (CH341_CMD_I2C_STM_OUT | 2); // reg data length + 1 199 | writeBuffer[i++] = getAddressByte(address, false); 200 | writeBuffer[i++] = (byte) reg; 201 | writeBulkData(writeBuffer, i); 202 | // Read register data 203 | i = 0; 204 | writeBuffer[i++]= (byte) CH341_CMD_I2C_STREAM; 205 | writeBuffer[i++] = CH341_CMD_I2C_STM_STA; // START condition 206 | writeBuffer[i++] = (byte) (CH341_CMD_I2C_STM_OUT | 1); // zero data length + 1 207 | writeBuffer[i++] = getAddressByte(address, true); 208 | if (length > 0) { 209 | for (int j = 0; j < length - 1; j++) { 210 | writeBuffer[i++] = (byte) (CH341_CMD_I2C_STM_IN | 1); 211 | } 212 | writeBuffer[i++] = (byte) CH341_CMD_I2C_STM_IN; 213 | } 214 | writeBuffer[i++] = CH341_CMD_I2C_STM_STO; // STOP condition 215 | writeBuffer[i++] = CH341_CMD_I2C_STM_END; // end of transaction 216 | transferBulkData(writeBuffer, i, data, length); 217 | } 218 | 219 | private void checkDevicePresence(int address) throws IOException { 220 | int i = 0; 221 | writeBuffer[i++] = (byte) CH341_CMD_I2C_STREAM; 222 | writeBuffer[i++] = CH341_CMD_I2C_STM_STA; // START condition 223 | writeBuffer[i++] = (byte) CH341_CMD_I2C_STM_OUT; 224 | writeBuffer[i++] = getAddressByte(address, true); 225 | writeBuffer[i++] = CH341_CMD_I2C_STM_STO; // STOP condition 226 | writeBuffer[i++] = CH341_CMD_I2C_STM_END; // end of transaction 227 | int res = transferBulkData(writeBuffer, i, writeBuffer, 1); 228 | if (res <= 0 || (writeBuffer[0] & 0x80) != 0) { 229 | throw new IOException(String.format("No device present at address 0x%02x", address)); 230 | } 231 | } 232 | 233 | /** 234 | * Write request and read response, if needed. 235 | * 236 | * @param writeData data buffer to write data 237 | * @param writeLength data length to write 238 | * @param readData data buffer to read data 239 | * @param readLength data length to read (can be zero) 240 | * @return actual length of read data 241 | * @throws IOException in case of data read/write error or timeout 242 | */ 243 | private int transferBulkData(byte[] writeData, int writeLength, 244 | byte[] readData, int readLength) throws IOException { 245 | writeBulkData(writeData, writeLength); 246 | if (readLength > 0) { 247 | readLength = readBulkData(readBuffer, MAX_TRANSFER_SIZE); 248 | System.arraycopy(readBuffer, 0, readData, 0, readLength); 249 | } 250 | return readLength; 251 | } 252 | 253 | private int readBulkData(byte[] data, int length) throws IOException { 254 | return bulkRead(data, 0, length, USB_TIMEOUT_MILLIS); 255 | } 256 | 257 | private void writeBulkData(byte[] data, int length) throws IOException { 258 | bulkWrite(data, length, USB_TIMEOUT_MILLIS); 259 | } 260 | 261 | public static UsbDeviceIdentifier[] getSupportedUsbDeviceIdentifiers() { 262 | return new UsbDeviceIdentifier[] { 263 | new UsbDeviceIdentifier(0x1a86, 0x5512) 264 | }; 265 | } 266 | } 267 | -------------------------------------------------------------------------------- /lib/src/main/java/com/github/ykc3/android/usbi2c/adapter/Cp2112UsbI2cAdapter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Victor Antonovich 3 | * 4 | * This work is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Lesser General Public License 6 | * as published by the Free Software Foundation; either version 2.1 7 | * of the License, or (at your option) any later version. 8 | * 9 | * This work is distributed in the hope that it will be useful, but 10 | * without any warranty; without even the implied warranty of merchantability 11 | * or fitness for a particular purpose. See the GNU Lesser General Public 12 | * License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with this library; if not, write to the Free Software Foundation, Inc., 16 | * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. 17 | */ 18 | 19 | package com.github.ykc3.android.usbi2c.adapter; 20 | 21 | import android.hardware.usb.UsbDevice; 22 | 23 | import com.github.ykc3.android.usbi2c.UsbI2cManager; 24 | 25 | import java.io.IOException; 26 | 27 | import static com.github.ykc3.android.usbi2c.UsbI2cManager.UsbDeviceIdentifier; 28 | 29 | /** 30 | * The Silicon Labs CP2112 chip is a USB HID device which provides an 31 | * SMBus controller for talking to slave devices. 32 | *

33 | * Data Sheet 34 | *

35 | * Programming Interface Specification 36 | */ 37 | public class Cp2112UsbI2cAdapter extends HidUsbI2cAdapter { 38 | // Adapter name 39 | public static final String ADAPTER_NAME = "CP2112"; 40 | 41 | // CP2112 report IDs 42 | private static final int REPORT_ID_SMBUS_CONFIG = 0x06; 43 | private static final int REPORT_ID_DATA_READ_REQUEST = 0x10; 44 | private static final int REPORT_ID_DATA_WRITE_READ_REQUEST = 0x11; 45 | private static final int REPORT_ID_DATA_READ_FORCE_SEND = 0x12; 46 | private static final int REPORT_ID_DATA_READ_RESPONSE = 0x13; 47 | private static final int REPORT_ID_DATA_WRITE_REQUEST = 0x14; 48 | private static final int REPORT_ID_TRANSFER_STATUS_REQUEST = 0x15; 49 | private static final int REPORT_ID_TRANSFER_STATUS_RESPONSE = 0x16; 50 | private static final int REPORT_ID_CANCEL_TRANSFER = 0x17; 51 | 52 | // CP2112 SMBus configuration size (including ReportID) 53 | private static final int SMBUS_CONFIG_SIZE = 14; 54 | 55 | // CP2112 SMBus configuration: Clock Speed 56 | private static final int SMBUS_CONFIG_CLOCK_SPEED_OFFSET = 1; 57 | // CP2112 SMBus configuration: Retry Time 58 | private static final int SMBUS_CONFIG_RETRY_TIME_OFFSET = 12; 59 | 60 | // CP2112 max I2C data write length (single write transfer) 61 | private static final int MAX_DATA_WRITE_LENGTH = 61; 62 | 63 | // CP2112 max I2C data read length (multiple read transfers) 64 | private static final int MAX_DATA_READ_LENGTH = 512; 65 | 66 | private static final int NUM_DRAIN_DATA_REPORTS_RETRIES = 10; 67 | 68 | // CP2112 max data transfer status reads while waiting for transfer completion 69 | private static final int NUM_TRANSFER_STATUS_RETRIES = 10; 70 | 71 | // CP2112 transfer status codes 72 | private static final int TRANSFER_STATUS_IDLE = 0x00; 73 | private static final int TRANSFER_STATUS_BUSY = 0x01; 74 | private static final int TRANSFER_STATUS_COMPLETE = 0x02; 75 | private static final int TRANSFER_STATUS_ERROR = 0x03; 76 | 77 | // CP2112 min clock speed 78 | private static final int MIN_CLOCK_SPEED = 10000; 79 | // CP2112 max clock speed 80 | private static final int MAX_CLOCK_SPEED = CLOCK_SPEED_HIGH; 81 | 82 | class Cp2112UsbI2cDevice extends BaseUsbI2cDevice { 83 | Cp2112UsbI2cDevice(int address) { 84 | super(address); 85 | } 86 | 87 | @Override 88 | protected void deviceReadReg(int reg, byte[] buffer, int length) throws IOException { 89 | readRegData(address, reg, buffer, length); 90 | } 91 | 92 | @Override 93 | protected void deviceRead(byte[] buffer, int length) throws IOException { 94 | readData(address, buffer, length); 95 | } 96 | 97 | @Override 98 | protected void deviceWrite(byte[] buffer, int length) throws IOException { 99 | writeData(address, buffer, length); 100 | } 101 | } 102 | 103 | public Cp2112UsbI2cAdapter(UsbI2cManager manager, UsbDevice usbDevice) { 104 | super(manager, usbDevice); 105 | } 106 | 107 | @Override 108 | public String getName() { 109 | return ADAPTER_NAME; 110 | } 111 | 112 | @Override 113 | protected BaseUsbI2cDevice getDeviceImpl(int address) { 114 | return new Cp2112UsbI2cDevice(address); 115 | } 116 | 117 | @Override 118 | protected void init(UsbDevice usbDevice) throws IOException { 119 | super.init(usbDevice); 120 | // Drain all stale data reports 121 | drainPendingDataReports(); 122 | } 123 | 124 | protected void configure() throws IOException { 125 | // Get current config 126 | getHidFeatureReport(REPORT_ID_SMBUS_CONFIG, buffer, SMBUS_CONFIG_SIZE); 127 | 128 | // Clock Speed (in Hertz, default 0x000186A0 - 100 kHz) 129 | int clockSpeed = getClockSpeed(); 130 | buffer[SMBUS_CONFIG_CLOCK_SPEED_OFFSET] = (byte) (clockSpeed >> 24); 131 | buffer[SMBUS_CONFIG_CLOCK_SPEED_OFFSET + 1] = (byte) (clockSpeed >> 16); 132 | buffer[SMBUS_CONFIG_CLOCK_SPEED_OFFSET + 2] = (byte) (clockSpeed >> 8); 133 | buffer[SMBUS_CONFIG_CLOCK_SPEED_OFFSET + 3] = (byte) clockSpeed; 134 | 135 | // Retry Time (number of retries, default 0 - no limit) 136 | buffer[SMBUS_CONFIG_RETRY_TIME_OFFSET] = 0x00; 137 | buffer[SMBUS_CONFIG_RETRY_TIME_OFFSET + 1] = 0x01; 138 | 139 | setHidFeatureReport(buffer, SMBUS_CONFIG_SIZE); 140 | } 141 | 142 | @Override 143 | public boolean isClockSpeedSupported(int speed) { 144 | return (speed >= MIN_CLOCK_SPEED && speed <= MAX_CLOCK_SPEED); 145 | } 146 | 147 | private void checkWriteDataLength(int length, int bufferLength) { 148 | checkDataLength(length, Math.min(MAX_DATA_WRITE_LENGTH, bufferLength)); 149 | } 150 | 151 | private void checkReadDataLength(int length, int bufferLength) { 152 | checkDataLength(length, Math.min(MAX_DATA_READ_LENGTH, bufferLength)); 153 | } 154 | 155 | private void writeData(int address, byte[] data, int length) throws IOException { 156 | checkWriteDataLength(length, data.length); 157 | buffer[0] = REPORT_ID_DATA_WRITE_REQUEST; 158 | buffer[1] = getAddressByte(address, false); 159 | buffer[2] = (byte) length; 160 | System.arraycopy(data, 0, buffer, 3, length); 161 | sendHidDataReport(buffer, length + 3); 162 | waitTransferComplete(); 163 | } 164 | 165 | private void readData(int address, byte[] data, int length) throws IOException { 166 | checkReadDataLength(length, data.length); 167 | buffer[0] = REPORT_ID_DATA_READ_REQUEST; 168 | buffer[1] = getAddressByte(address, false); // read bit is not required 169 | buffer[2] = (byte) (length >> 8); 170 | buffer[3] = (byte) length; 171 | sendHidDataReport(buffer, 4); 172 | waitTransferComplete(); 173 | readDataFully(data, length); 174 | } 175 | 176 | private void readRegData(int address, int reg, byte[] data, int length) throws IOException { 177 | checkReadDataLength(length, data.length); 178 | buffer[0] = REPORT_ID_DATA_WRITE_READ_REQUEST; 179 | buffer[1] = getAddressByte(address, false); // read bit is not required 180 | buffer[2] = (byte) (length >> 8); 181 | buffer[3] = (byte) length; 182 | buffer[4] = 0x01; // number of bytes in target address (register ID) 183 | buffer[5] = (byte) reg; 184 | sendHidDataReport(buffer, 6); 185 | waitTransferComplete(); 186 | readDataFully(data, length); 187 | } 188 | 189 | private void readDataFully(byte[] data, int length) throws IOException { 190 | int totalReadLen = 0; 191 | while (totalReadLen < length) { 192 | sendForceReadDataRequest(length - totalReadLen); 193 | 194 | getHidDataReport(buffer, USB_TIMEOUT_MILLIS); 195 | 196 | if (buffer[0] != REPORT_ID_DATA_READ_RESPONSE) { 197 | throw new IOException(String.format("Unexpected data read report ID: 0x%02x", 198 | buffer[0])); 199 | } 200 | 201 | if (buffer[1] == TRANSFER_STATUS_ERROR) { 202 | throw new IOException(String.format("Data read status error, condition: 0x%02x", 203 | buffer[2])); 204 | } 205 | 206 | int lastReadLen = buffer[2] & 0xff; 207 | if (lastReadLen > length - totalReadLen) { 208 | throw new IOException(String.format("Too many data read: " + 209 | "%d byte(s), expected: %d byte(s)", lastReadLen, length - totalReadLen)); 210 | } 211 | 212 | System.arraycopy(buffer, 3, data, totalReadLen, lastReadLen); 213 | 214 | totalReadLen += lastReadLen; 215 | } 216 | } 217 | 218 | private void waitTransferComplete() throws IOException { 219 | int tryNum = 1; 220 | while (tryNum++ <= NUM_TRANSFER_STATUS_RETRIES) { 221 | sendTransferStatusRequest(); 222 | 223 | getHidDataReport(buffer, USB_TIMEOUT_MILLIS); 224 | 225 | if (buffer[0] != REPORT_ID_TRANSFER_STATUS_RESPONSE) { 226 | throw new IOException(String.format("Unexpected transfer status report ID: 0x%02x", 227 | buffer[0])); 228 | } 229 | 230 | switch (buffer[1]) { 231 | case TRANSFER_STATUS_BUSY: 232 | continue; 233 | case TRANSFER_STATUS_COMPLETE: 234 | return; 235 | default: 236 | throw new IOException(String.format("Invalid transfer status: 0x%02x", 237 | buffer[1])); 238 | } 239 | } 240 | // Retries limit was reached and TRANSFER_STATUS_COMPLETE status is not reached 241 | cancelTransfer(); 242 | throw new IOException("Transfer retries limit reached"); 243 | } 244 | 245 | private void sendForceReadDataRequest(int length) throws IOException { 246 | buffer[0] = REPORT_ID_DATA_READ_FORCE_SEND; 247 | buffer[1] = (byte) ((length >> 8) & 0xff); 248 | buffer[2] = (byte) length; 249 | sendHidDataReport(buffer, 3); 250 | } 251 | 252 | private void sendTransferStatusRequest() throws IOException { 253 | buffer[0] = REPORT_ID_TRANSFER_STATUS_REQUEST; 254 | buffer[1] = 0x01; 255 | sendHidDataReport(buffer, 2); 256 | } 257 | 258 | private void cancelTransfer() throws IOException { 259 | buffer[0] = REPORT_ID_CANCEL_TRANSFER; 260 | buffer[1] = 0x01; 261 | sendHidDataReport(buffer, 2); 262 | } 263 | 264 | private void drainPendingDataReports() throws IOException { 265 | int tryNum = 1; 266 | while (tryNum++ <= NUM_DRAIN_DATA_REPORTS_RETRIES) { 267 | try { 268 | getHidDataReport(buffer, 5); 269 | } catch (IOException e) { 270 | break; 271 | } 272 | } 273 | if (tryNum >= NUM_DRAIN_DATA_REPORTS_RETRIES) { 274 | throw new IOException("Can't drain pending data reports"); 275 | } 276 | cancelTransfer(); 277 | } 278 | 279 | public static UsbDeviceIdentifier[] getSupportedUsbDeviceIdentifiers() { 280 | return new UsbDeviceIdentifier[] { 281 | new UsbDeviceIdentifier(0x10c4, 0xea90) 282 | }; 283 | } 284 | } 285 | -------------------------------------------------------------------------------- /lib/src/main/java/com/github/ykc3/android/usbi2c/adapter/Ft232hUsbI2cAdapter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Victor Antonovich 3 | * 4 | * This work is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Lesser General Public License 6 | * as published by the Free Software Foundation; either version 2.1 7 | * of the License, or (at your option) any later version. 8 | * 9 | * This work is distributed in the hope that it will be useful, but 10 | * without any warranty; without even the implied warranty of merchantability 11 | * or fitness for a particular purpose. See the GNU Lesser General Public 12 | * License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with this library; if not, write to the Free Software Foundation, Inc., 16 | * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. 17 | */ 18 | 19 | package com.github.ykc3.android.usbi2c.adapter; 20 | 21 | import android.hardware.usb.UsbConstants; 22 | import android.hardware.usb.UsbDevice; 23 | import android.hardware.usb.UsbInterface; 24 | 25 | import com.github.ykc3.android.usbi2c.UsbI2cManager; 26 | 27 | import java.io.IOException; 28 | 29 | import static com.github.ykc3.android.usbi2c.UsbI2cManager.UsbDeviceIdentifier; 30 | 31 | /** 32 | * The FT232H is a single channel USB 2.0 Hi-Speed (480Mb/s) to UART/FIFO IC. 33 | * The FT232H has the Multi-Protocol Synchronous Serial Engine (MPSSE) to simplify 34 | * synchronous serial protocol (USB to JTAG, I2C, SPI (MASTER) or bit-bang) design. 35 | *

36 | * Data Sheet 37 | *

38 | * MPSSE Basics 39 | *

40 | * Command Processor for MPSSE and MCU Host Bus Emulation Modes 41 | *

42 | * USB to I2C Example using the FT232H and FT201X devices 43 | *

44 | */ 45 | public class Ft232hUsbI2cAdapter extends BaseUsbI2cAdapter { 46 | // Adapter name 47 | public static final String ADAPTER_NAME = "FT232H"; 48 | 49 | // Nanoseconds in one millisecond 50 | private static final long NANOS_IN_MS = 1000000L; 51 | 52 | // FT232H control requests 53 | private static final int SIO_RESET_REQUEST = 0x00; 54 | private static final int SIO_SET_EVENT_CHAR_REQUEST = 0x06; 55 | private static final int SIO_SET_ERROR_CHAR_REQUEST = 0x07; 56 | private static final int SIO_SET_LATENCY_TIMER_REQUEST = 0x09; 57 | private static final int SIO_SET_BITMODE_REQUEST = 0x0B; 58 | 59 | // FT232H control request parameters 60 | private static final int SIO_RESET_SIO = 0; 61 | private static final int SIO_RESET_PURGE_RX = 1; 62 | private static final int SIO_RESET_PURGE_TX = 2; 63 | 64 | // FT232H bit modes 65 | private static final int BITMODE_RESET = 0x00; 66 | private static final int BITMODE_MPSSE = 0x02; 67 | 68 | // FT232H AD port I2C lines bit masks 69 | private static final int I2C_SCL_BIT = 0x01; // AD0 70 | private static final int I2C_SDA_O_BIT = 0x02; // AD1 71 | private static final int I2C_SDA_I_BIT = 0x04; // AD2 72 | 73 | // FT232H USB latency timer (in milliseconds) 74 | private static final int LATENCY_TIMER = 16; 75 | 76 | // FT232H min clock speed 77 | private static final int MIN_CLOCK_SPEED = 10000; 78 | // FT232H max clock speed 79 | private static final int MAX_CLOCK_SPEED = CLOCK_SPEED_HIGH; 80 | // FT232H internal bus clock speed 81 | private static final int BUS_CLOCK_SPEED = 30000000; 82 | 83 | // FT232H max transfer size 84 | private static final int MAX_TRANSFER_SIZE = 16384; 85 | 86 | // FT232H response packet header length 87 | private static final int READ_PACKET_HEADER_LENGTH = 2; 88 | 89 | private final byte[] readBuffer = new byte[MAX_TRANSFER_SIZE]; 90 | 91 | private final Ft232Mpsse mpsse = new Ft232Mpsse(); 92 | 93 | class Ft232Mpsse { 94 | // MPSSE commands 95 | private static final int MPSSE_WRITE_BYTES_NVE_MSB = 0x11; 96 | private static final int MPSSE_WRITE_BITS_NVE_MSB = 0x13; 97 | private static final int MPSSE_READ_BYTES_PVE_MSB = 0x20; 98 | private static final int MPSSE_READ_BITS_PVE_MSB = 0x22; 99 | private static final int MPSSE_SET_BITS_LOW = 0x80; 100 | private static final int MPSSE_LOOPBACK_END = 0x85; 101 | private static final int MPSSE_SET_TCK_DIVISOR = 0x86; 102 | private static final int MPSSE_SEND_IMMEDIATE = 0x87; 103 | private static final int MPSSE_DISABLE_CLK_DIV5 = 0x8A; 104 | private static final int MPSSE_ENABLE_CLK_3PHASE = 0x8C; 105 | private static final int MPSSE_DISABLE_CLK_ADAPTIVE = 0x97; 106 | private static final int MPSSE_DRIVE_ZERO = 0x9E; 107 | private static final int MPSSE_DUMMY_REQUEST = 0xAA; 108 | 109 | // MPSSE error response 110 | private static final int MPSSE_ERROR = 0xFA; 111 | 112 | // Number of writes to port to ensure port state is steady 113 | private static final int PORT_WRITE_STEADY_COUNT = 4; 114 | 115 | private final byte[] buffer = new byte[MAX_TRANSFER_SIZE]; 116 | private int index; 117 | 118 | public void bufferClear() { 119 | index = 0; 120 | } 121 | 122 | private void bufferCheck(int length) { 123 | if (index + length > buffer.length) { 124 | throw new IllegalArgumentException(String.format("MPSSE buffer overflow: " + 125 | "requested %d, available %d", length, buffer.length - index)); 126 | } 127 | } 128 | 129 | public void bufferWrite() throws IOException { 130 | try { 131 | dataWrite(buffer, index); 132 | } finally { 133 | bufferClear(); 134 | } 135 | } 136 | 137 | public void checkEnabled() throws IOException { 138 | bufferClear(); 139 | buffer[index++] = (byte) MPSSE_DUMMY_REQUEST; 140 | bufferWrite(); 141 | dataRead(buffer, 2); 142 | if ((buffer[0] & 0xFF) != MPSSE_ERROR || (buffer[1] & 0xFF) != MPSSE_DUMMY_REQUEST) { 143 | throw new IOException("MPSSE is not enabled"); 144 | } 145 | } 146 | 147 | public void i2cInit(int clockSpeed) { 148 | bufferCheck(10); 149 | // Configure clock mode for I2C 150 | buffer[index++] = (byte) MPSSE_DISABLE_CLK_DIV5; 151 | buffer[index++] = (byte) MPSSE_DISABLE_CLK_ADAPTIVE; 152 | buffer[index++] = (byte) MPSSE_ENABLE_CLK_3PHASE; 153 | // Configure drive-zero (open drain) mode for I2C pins 154 | buffer[index++] = (byte) MPSSE_DRIVE_ZERO; 155 | buffer[index++] = (byte) (I2C_SCL_BIT | I2C_SDA_O_BIT | I2C_SDA_I_BIT); // port AD 156 | buffer[index++] = 0; // port AC 157 | // Disable loopback 158 | buffer[index++] = (byte) MPSSE_LOOPBACK_END; 159 | // Set I2c speed clock divisor 160 | int divisor = ((2 * BUS_CLOCK_SPEED / clockSpeed) - 2) / 3; 161 | buffer[index++] = (byte) MPSSE_SET_TCK_DIVISOR; 162 | buffer[index++] = (byte) (divisor); 163 | buffer[index++] = (byte) (divisor >> 8); 164 | } 165 | 166 | public void i2cPortConfig(int levels) { 167 | bufferCheck(3); 168 | buffer[index++] = (byte) MPSSE_SET_BITS_LOW; 169 | buffer[index++] = (byte) levels; // bits set are driven to high level 170 | buffer[index++] = (byte) ~I2C_SDA_I_BIT; // bits set are outputs 171 | } 172 | 173 | public void i2cIdle() { 174 | // Drive both SDA and SCL outputs to high level 175 | i2cPortConfig(0xFF); 176 | } 177 | 178 | public void i2cStart() { 179 | // Bring SDA low while SCL is high 180 | for (int i = 0; i < PORT_WRITE_STEADY_COUNT; i++) { 181 | i2cPortConfig(~I2C_SDA_O_BIT); 182 | } 183 | // Bring both SDA and SCL low 184 | for (int i = 0; i < PORT_WRITE_STEADY_COUNT; i++) { 185 | i2cPortConfig(~(I2C_SDA_O_BIT | I2C_SCL_BIT)); 186 | } 187 | } 188 | 189 | public void i2cSendByteAndReadAck(byte value) { 190 | bufferCheck(4); 191 | buffer[index++] = MPSSE_WRITE_BYTES_NVE_MSB; // clock bytes out MSB first on clock falling edge 192 | buffer[index++] = 0x00; // 193 | buffer[index++] = 0x00; // 0x0000 - send one byte 194 | buffer[index++] = value; // byte value to send 195 | i2cPortConfig(~I2C_SCL_BIT); // put into transfer idle state: SCK low, SDA high 196 | bufferCheck(2); 197 | buffer[index++] = MPSSE_READ_BITS_PVE_MSB; // clock bits in MSB first on clock rising edge 198 | buffer[index++] = 0x00; // clock in one bit (ACK) 199 | } 200 | 201 | public void i2cWriteByteAndCheckAck(byte value) throws IOException { 202 | mpsse.i2cSendByteAndReadAck(value); 203 | mpsse.i2cReceiveImmediate(); 204 | mpsse.bufferWrite(); 205 | dataRead(buffer, 1); 206 | if ((buffer[0] & 0x01) != 0) { 207 | throw new IOException("NACK from slave"); 208 | } 209 | } 210 | 211 | public void i2cReadByte(boolean isAck) { 212 | bufferCheck(6); 213 | buffer[index++] = MPSSE_READ_BYTES_PVE_MSB; // clock bytes in MSB first on clock rising edge 214 | buffer[index++] = 0x00; 215 | buffer[index++] = 0x00; // 0x0000 - receive one byte 216 | buffer[index++] = MPSSE_WRITE_BITS_NVE_MSB; // clock bits out MSB first on clock falling edge 217 | buffer[index++] = 0x00;// clock out one bit 218 | buffer[index++] = (byte) (isAck ? 0x00 : 0xFF); 219 | i2cPortConfig(~I2C_SCL_BIT); // put into transfer idle state: SCK low, SDA high 220 | } 221 | 222 | public void i2cReadBytes(int length) { 223 | for (int i = 0; i < length; i++) { 224 | i2cReadByte(i < (length - 1)); 225 | } 226 | } 227 | 228 | public void i2cStop() { 229 | // Bring SDA low and keep SCL low 230 | for (int i = 0; i < PORT_WRITE_STEADY_COUNT; i++) { 231 | i2cPortConfig(~(I2C_SDA_O_BIT | I2C_SCL_BIT)); 232 | } 233 | // Bring SCL high and keep SDA low 234 | for (int i = 0; i < PORT_WRITE_STEADY_COUNT; i++) { 235 | i2cPortConfig(~I2C_SDA_O_BIT); 236 | } 237 | // Bring SDA high while SCL is high 238 | for (int i = 0; i < PORT_WRITE_STEADY_COUNT; i++) { 239 | i2cPortConfig(0xFF); 240 | } 241 | } 242 | 243 | public void i2cReceiveImmediate() { 244 | bufferCheck(1); 245 | buffer[index++] = (byte) MPSSE_SEND_IMMEDIATE; 246 | } 247 | } 248 | 249 | class Ft232hUsbI2cDevice extends BaseUsbI2cDevice { 250 | Ft232hUsbI2cDevice(int address) { 251 | super(address); 252 | } 253 | 254 | @Override 255 | protected void deviceReadReg(int reg, byte[] buffer, int length) throws IOException { 256 | readRegData(address, reg, buffer, length); 257 | } 258 | 259 | @Override 260 | protected void deviceWrite(byte[] buffer, int length) throws IOException { 261 | writeData(address, buffer, length); 262 | } 263 | 264 | @Override 265 | protected void deviceRead(byte[] buffer, int length) throws IOException { 266 | readData(address, buffer, length); 267 | } 268 | } 269 | 270 | public Ft232hUsbI2cAdapter(UsbI2cManager manager, UsbDevice device) { 271 | super(manager, device); 272 | } 273 | 274 | @Override 275 | public String getName() { 276 | return ADAPTER_NAME; 277 | } 278 | 279 | @Override 280 | protected BaseUsbI2cDevice getDeviceImpl(int address) { 281 | return new Ft232hUsbI2cDevice(address); 282 | } 283 | 284 | @Override 285 | protected void init(UsbDevice usbDevice) throws IOException { 286 | if (usbDevice.getInterfaceCount() == 0) { 287 | throw new IOException("No interfaces found for device: " + usbDevice); 288 | } 289 | 290 | UsbInterface usbInterface = usbDevice.getInterface(0); 291 | if (usbInterface.getEndpointCount() < 2) { 292 | throw new IOException("No endpoints found for device: " + usbDevice); 293 | } 294 | setBulkEndpoints(usbInterface.getEndpoint(0), usbInterface.getEndpoint(1)); 295 | 296 | configure(); 297 | } 298 | 299 | @Override 300 | public boolean isClockSpeedSupported(int speed) { 301 | return (speed >= MIN_CLOCK_SPEED && speed <= MAX_CLOCK_SPEED); 302 | } 303 | 304 | protected void configure() throws IOException { 305 | // Reset device 306 | commandWrite(SIO_RESET_REQUEST, SIO_RESET_SIO); 307 | // Set latency timer 308 | commandWrite(SIO_SET_LATENCY_TIMER_REQUEST, LATENCY_TIMER); 309 | // Drain RX and TX buffers 310 | commandWrite(SIO_RESET_REQUEST, SIO_RESET_PURGE_RX); 311 | commandWrite(SIO_RESET_REQUEST, SIO_RESET_PURGE_TX); 312 | // Disable event and error characters 313 | commandWrite(SIO_SET_EVENT_CHAR_REQUEST, 0); 314 | commandWrite(SIO_SET_ERROR_CHAR_REQUEST, 0); 315 | // Enable MPSSE mode 316 | commandWrite(SIO_SET_BITMODE_REQUEST, BITMODE_RESET << 8); 317 | commandWrite(SIO_SET_BITMODE_REQUEST, BITMODE_MPSSE << 8); 318 | // Check MPSSE mode is enabled 319 | mpsse.checkEnabled(); 320 | // Init MPSSE for I2C 321 | mpsse.i2cInit(getClockSpeed()); 322 | // Set I2C idle state 323 | mpsse.i2cIdle(); 324 | // Write MPSSE commands to FT232H 325 | mpsse.bufferWrite(); 326 | } 327 | 328 | private void checkReadDataLength(int length, int dataBufferLength) { 329 | checkDataLength(length, Math.min(readBuffer.length 330 | - READ_PACKET_HEADER_LENGTH, dataBufferLength)); 331 | } 332 | 333 | private void readRegData(int address, int reg, byte[] data, int length) throws IOException { 334 | checkReadDataLength(length, data.length); 335 | mpsse.bufferClear(); 336 | mpsse.i2cIdle(); 337 | mpsse.i2cStart(); 338 | mpsse.i2cWriteByteAndCheckAck(getAddressByte(address, false)); 339 | mpsse.i2cWriteByteAndCheckAck((byte) reg); 340 | mpsse.i2cIdle(); 341 | mpsse.i2cStart(); 342 | mpsse.i2cWriteByteAndCheckAck(getAddressByte(address, true)); 343 | if (length > 0) { 344 | mpsse.i2cReadBytes(length); 345 | mpsse.i2cReceiveImmediate(); 346 | mpsse.bufferWrite(); 347 | } 348 | dataRead(readBuffer, length); 349 | if (length > 0) { 350 | System.arraycopy(readBuffer, 0, data, 0, length); 351 | } 352 | mpsse.i2cStop(); 353 | mpsse.bufferWrite(); 354 | } 355 | 356 | private void readData(int address, byte[] data, int length) throws IOException { 357 | checkReadDataLength(length, data.length); 358 | mpsse.bufferClear(); 359 | mpsse.i2cIdle(); 360 | mpsse.i2cStart(); 361 | mpsse.i2cWriteByteAndCheckAck(getAddressByte(address, true)); 362 | if (length > 0) { 363 | mpsse.i2cReadBytes(length); 364 | mpsse.i2cReceiveImmediate(); 365 | mpsse.bufferWrite(); 366 | } 367 | dataRead(readBuffer, length); 368 | if (length > 0) { 369 | System.arraycopy(readBuffer, 0, data, 0, length); 370 | } 371 | mpsse.i2cStop(); 372 | mpsse.bufferWrite(); 373 | } 374 | 375 | private void writeData(int address, byte[] data, int length) throws IOException { 376 | checkDataLength(length, data.length); 377 | mpsse.bufferClear(); 378 | mpsse.i2cIdle(); 379 | mpsse.i2cStart(); 380 | mpsse.i2cWriteByteAndCheckAck(getAddressByte(address, false)); 381 | for (int i = 0; i < length; i++) { 382 | mpsse.i2cWriteByteAndCheckAck(data[i]); 383 | } 384 | mpsse.i2cStop(); 385 | mpsse.bufferWrite(); 386 | } 387 | 388 | /** 389 | * Write command to FT232H. 390 | * 391 | * @param cmd command to write 392 | * @param value command value 393 | * @throws IOException in case of command write error 394 | */ 395 | private void commandWrite(int cmd, int value) throws IOException { 396 | controlTransfer(UsbConstants.USB_TYPE_VENDOR | UsbConstants.USB_DIR_OUT, 397 | cmd, value, 1, null, 0); 398 | } 399 | 400 | private static long getMillis() { 401 | return System.nanoTime() / NANOS_IN_MS; 402 | } 403 | 404 | /** 405 | * Read data from FT232H to data buffer until an 406 | * expected number of bytes is returned. 407 | * 408 | * @param data data buffer to read data 409 | * @param length data length to read 410 | * @throws IOException in case of data read error or timeout 411 | */ 412 | private void dataRead(byte[] data, int length) throws IOException { 413 | int read = 0; 414 | int offset = 0; 415 | long startTimestamp = getMillis(); 416 | int timeoutRemain; 417 | while (true) { 418 | timeoutRemain = (int) (USB_TIMEOUT_MILLIS - (getMillis() - startTimestamp)); 419 | if (timeoutRemain <= 0) { 420 | throw new IOException("Data read timeout"); 421 | } 422 | int readOffset = offset + read; 423 | read += bulkRead(data, readOffset, data.length - readOffset, timeoutRemain); 424 | if (read >= READ_PACKET_HEADER_LENGTH) { 425 | int dataRead = read - READ_PACKET_HEADER_LENGTH; 426 | if (dataRead > 0) { 427 | // Cut out packet header 428 | System.arraycopy(data, offset + READ_PACKET_HEADER_LENGTH, data, 429 | offset, dataRead); 430 | } 431 | read = 0; 432 | offset += dataRead; 433 | if (offset >= length) { 434 | break; 435 | } 436 | } 437 | try { 438 | Thread.sleep(LATENCY_TIMER / 2); 439 | } catch (InterruptedException ignored) { 440 | } 441 | } 442 | } 443 | 444 | /** 445 | * Write data from data buffer to FT232H. 446 | * 447 | * @param data data buffer to write data 448 | * @param length data length to write 449 | * @throws IOException in case of data write error 450 | */ 451 | protected void dataWrite(byte[] data, int length) throws IOException { 452 | bulkWrite(data, length, USB_TIMEOUT_MILLIS); 453 | } 454 | 455 | public static UsbDeviceIdentifier[] getSupportedUsbDeviceIdentifiers() { 456 | return new UsbDeviceIdentifier[] { 457 | new UsbDeviceIdentifier(0x403, 0x6014) 458 | }; 459 | } 460 | } 461 | -------------------------------------------------------------------------------- /lib/src/main/java/com/github/ykc3/android/usbi2c/adapter/Ft260UsbI2cAdapter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025 Victor Antonovich 3 | * 4 | * This work is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Lesser General Public License 6 | * as published by the Free Software Foundation; either version 2.1 7 | * of the License, or (at your option) any later version. 8 | * 9 | * This work is distributed in the hope that it will be useful, but 10 | * without any warranty; without even the implied warranty of merchantability 11 | * or fitness for a particular purpose. See the GNU Lesser General Public 12 | * License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with this library; if not, write to the Free Software Foundation, Inc., 16 | * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. 17 | */ 18 | 19 | package com.github.ykc3.android.usbi2c.adapter; 20 | 21 | import android.hardware.usb.UsbDevice; 22 | 23 | import com.github.ykc3.android.usbi2c.UsbI2cManager; 24 | 25 | import static com.github.ykc3.android.usbi2c.UsbI2cManager.UsbDeviceIdentifier; 26 | 27 | import java.io.IOException; 28 | 29 | /** 30 | * The FT260 HID-class USB to UART/I2C bridge IC. 31 | *

32 | * Data Sheet 33 | *

34 | * User Guide 35 | */ 36 | public class Ft260UsbI2cAdapter extends HidUsbI2cAdapter { 37 | // Adapter name 38 | private static final String ADAPTER_NAME = "FT260"; 39 | 40 | // FT260 report IDs 41 | private static final int REPORT_ID_CHIP_VERSION = 0xA0; 42 | private static final int REPORT_ID_SYSTEM_SETTINGS = 0xA1; 43 | private static final int REPORT_ID_I2C_STATUS = 0xC0; 44 | private static final int REPORT_ID_I2C_READ_REQUEST = 0xC2; 45 | private static final int REPORT_ID_I2C_DATA_MIN = 0xD0; 46 | private static final int REPORT_ID_I2C_DATA_MAX = 0xDE; 47 | 48 | // FT260 requests 49 | private static final int REQUEST_I2C_RESET = 0x20; 50 | private static final int REQUEST_I2C_SET_CLOCK_SPEED = 0x22; 51 | 52 | // FT260 report sizes (in bytes, including ReportID) 53 | private static final int REPORT_SIZE_CHIP_VERSION = 13; 54 | private static final int REPORT_SIZE_SYSTEM_STATUS = 25; 55 | private static final int REPORT_SIZE_I2C_STATUS = 5; 56 | 57 | // FT260 chip version report field offsets 58 | private static final int CHIP_VERSION_PART_NUMBER_MAJOR_OFFSET = 1; 59 | private static final int CHIP_VERSION_PART_NUMBER_MINOR_OFFSET = 2; 60 | 61 | // FT260 system status report field offsets 62 | private static final int SYSTEM_STATUS_CHIP_MODE_OFFSET = 1; 63 | 64 | // FT260 I2C status report field offsets 65 | private static final int I2C_STATUS_BUS_STATUS_OFFSET = 1; 66 | 67 | // FT260 I2C read request flags 68 | private static final int I2C_READ_REQUEST_FLAG_NONE = 0x00; 69 | private static final int I2C_READ_REQUEST_FLAG_START = 0x02; 70 | private static final int I2C_READ_REQUEST_FLAG_REPEATED_START = 0x03; 71 | private static final int I2C_READ_REQUEST_FLAG_STOP = 0x04; 72 | private static final int I2C_READ_REQUEST_FLAG_START_STOP = 0x06; 73 | private static final int I2C_READ_REQUEST_FLAG_START_STOP_REPEATED = 0x07; 74 | 75 | // FT260 max data transfer status reads while waiting for transfer completion 76 | private static final int CHECK_TRANSFER_STATUS_MAX_RETRIES = 10; 77 | 78 | // FT260 I2C status flags 79 | private static final int I2C_STATUS_FLAG_CONTROLLER_BUSY = 0x01; 80 | private static final int I2C_STATUS_FLAG_ERROR = 0x02; 81 | 82 | // FT260 min clock speed (60 kHz) 83 | private static final int MIN_CLOCK_SPEED = 60000; 84 | // FT260 max clock speed (3.4 MHz) 85 | private static final int MAX_CLOCK_SPEED = CLOCK_SPEED_HIGH; 86 | 87 | // FT260 max data transfer size 88 | private static final int MAX_DATA_TRANSFER_SIZE = 60; 89 | 90 | class Ft260UsbI2cDevice extends BaseUsbI2cDevice { 91 | Ft260UsbI2cDevice(int address) { 92 | super(address); 93 | } 94 | 95 | @Override 96 | protected void deviceReadReg(int reg, byte[] buffer, int length) throws IOException { 97 | readRegData(address, reg, buffer, length); 98 | } 99 | 100 | @Override 101 | protected void deviceRead(byte[] buffer, int length) throws IOException { 102 | readData(address, buffer, length); 103 | } 104 | 105 | @Override 106 | protected void deviceWrite(byte[] buffer, int length) throws IOException { 107 | writeData(address, buffer, length); 108 | } 109 | } 110 | 111 | public Ft260UsbI2cAdapter(UsbI2cManager manager, UsbDevice device) { 112 | super(manager, device); 113 | } 114 | 115 | @Override 116 | public String getName() { 117 | return ADAPTER_NAME; 118 | } 119 | 120 | 121 | @Override 122 | protected BaseUsbI2cDevice getDeviceImpl(int address) { 123 | return new Ft260UsbI2cDevice(address); 124 | } 125 | 126 | @Override 127 | protected void init(UsbDevice usbDevice) throws IOException { 128 | probe(); 129 | super.init(usbDevice); 130 | } 131 | 132 | private void probe() throws IOException { 133 | // Check chip code 134 | getHidFeatureReport(REPORT_ID_CHIP_VERSION, buffer, REPORT_SIZE_CHIP_VERSION); 135 | if (buffer[CHIP_VERSION_PART_NUMBER_MAJOR_OFFSET] != 0x02 136 | || buffer[CHIP_VERSION_PART_NUMBER_MINOR_OFFSET] != 0x60) { 137 | throw new IOException(String.format("Unknown chip code: %02x%02x", 138 | buffer[CHIP_VERSION_PART_NUMBER_MAJOR_OFFSET], 139 | buffer[CHIP_VERSION_PART_NUMBER_MINOR_OFFSET])); 140 | } 141 | // Check I2C is enabled 142 | getHidFeatureReport(REPORT_ID_SYSTEM_SETTINGS, buffer, REPORT_SIZE_SYSTEM_STATUS); 143 | // I2C is enabled if DCNF0/DCNF1 pins are both set to 0 or DCNF0 is set to 1 144 | if (buffer[SYSTEM_STATUS_CHIP_MODE_OFFSET] != 0 145 | && (buffer[SYSTEM_STATUS_CHIP_MODE_OFFSET] & 0x01) == 0) { 146 | throw new IOException("I2C interface is not enabled by FT260 DCNF0/DCNF1 pins"); 147 | } 148 | } 149 | 150 | @Override 151 | public boolean isClockSpeedSupported(int speed) { 152 | return (speed >= MIN_CLOCK_SPEED && speed <= MAX_CLOCK_SPEED); 153 | } 154 | 155 | @Override 156 | protected void configure() throws IOException { 157 | // Reset I2C master 158 | resetI2cMaster(); 159 | // Set clock speed (in kHz) 160 | int clockSpeedKHz = getClockSpeed() / 1000; 161 | int i = 0; 162 | buffer[i++] = (byte) REPORT_ID_SYSTEM_SETTINGS; 163 | buffer[i++] = REQUEST_I2C_SET_CLOCK_SPEED; 164 | buffer[i++] = (byte) clockSpeedKHz; 165 | buffer[i++] = (byte) (clockSpeedKHz >> 8); 166 | setHidFeatureReport(buffer, i); 167 | } 168 | 169 | private void writeData(int address, byte[] data, int length) throws IOException { 170 | checkDataLength(length, data.length); 171 | // Write data by chunks up to MAX_DATA_TRANSFER_SIZE 172 | int sentBytes = 0; 173 | while (sentBytes < length) { 174 | int chunkLength = Math.min(length - sentBytes, MAX_DATA_TRANSFER_SIZE); 175 | int reportId = REPORT_ID_I2C_DATA_MIN + (chunkLength - 1) / 4; 176 | // Send write request 177 | int flag = I2C_READ_REQUEST_FLAG_NONE; 178 | if (sentBytes > 0) { 179 | if (sentBytes + chunkLength == length) { 180 | flag = I2C_READ_REQUEST_FLAG_STOP; 181 | } 182 | } else if (chunkLength < length) { 183 | flag = I2C_READ_REQUEST_FLAG_START; 184 | } else { 185 | flag = I2C_READ_REQUEST_FLAG_START_STOP; 186 | } 187 | int i = 0; 188 | buffer[i++] = (byte) reportId; 189 | buffer[i++] = (byte) address; 190 | buffer[i++] = (byte) flag; 191 | buffer[i++] = (byte) length; 192 | System.arraycopy(data, sentBytes, buffer, i, chunkLength); 193 | sendHidDataReport(buffer, i + chunkLength); 194 | sentBytes += chunkLength; 195 | // Check transfer status 196 | checkTransferStatus(); 197 | } 198 | } 199 | 200 | private void readData(int address, byte[] data, int length) throws IOException { 201 | checkDataLength(length, data.length); 202 | // Send read request 203 | int i = 0; 204 | buffer[i++] = (byte) REPORT_ID_I2C_READ_REQUEST; 205 | buffer[i++] = (byte) address; 206 | buffer[i++] = (byte) I2C_READ_REQUEST_FLAG_START_STOP; 207 | buffer[i++] = (byte) length; 208 | buffer[i++] = (byte) (length >> 8); 209 | sendHidDataReport(buffer, i); 210 | // Read response 211 | readDataFully(data, length); 212 | // Check transfer status 213 | checkTransferStatus(); 214 | } 215 | 216 | private void readRegData(int address, int reg, byte[] data, int length) throws IOException { 217 | checkDataLength(length, data.length); 218 | // Send register address write request 219 | int i = 0; 220 | buffer[i++] = (byte) REPORT_ID_I2C_DATA_MIN; 221 | buffer[i++] = (byte) address; 222 | buffer[i++] = (byte) I2C_READ_REQUEST_FLAG_START; 223 | buffer[i++] = 0x01; // number of bytes in target address (register ID) 224 | buffer[i++] = (byte) reg; 225 | sendHidDataReport(buffer, i); 226 | // Check register address write transfer status 227 | checkTransferStatus(); 228 | // Send data read request 229 | i = 0; 230 | buffer[i++] = (byte) REPORT_ID_I2C_READ_REQUEST; 231 | buffer[i++] = (byte) address; 232 | buffer[i++] = (byte) I2C_READ_REQUEST_FLAG_START_STOP_REPEATED; 233 | buffer[i++] = (byte) length; 234 | buffer[i++] = (byte) (length >> 8); 235 | sendHidDataReport(buffer, i); 236 | // Register data read response 237 | readDataFully(data, length); 238 | // Check data read transfer status 239 | checkTransferStatus(); 240 | } 241 | 242 | private void readDataFully(byte[] data, int length) throws IOException { 243 | int readLength = 0; 244 | 245 | while (readLength < length) { 246 | getHidDataReport(buffer, USB_TIMEOUT_MILLIS); 247 | 248 | int reportId = buffer[0] & 0xff; 249 | if (reportId < REPORT_ID_I2C_DATA_MIN || reportId > REPORT_ID_I2C_DATA_MAX) { 250 | throw new IOException(String.format("Unexpected data read report ID: 0x%02x", 251 | reportId)); 252 | } 253 | 254 | int bufferLength = buffer[1] & 0xff; 255 | if (bufferLength > length - readLength) { 256 | throw new IOException(String.format("Too long data to read: " + 257 | "%d byte(s), expected: %d byte(s)", bufferLength, length - readLength)); 258 | } 259 | 260 | System.arraycopy(buffer, 2, data, readLength, bufferLength); 261 | 262 | readLength += bufferLength; 263 | } 264 | } 265 | 266 | private void checkTransferStatus() throws IOException { 267 | int tryNum = 1; 268 | 269 | while (tryNum++ <= CHECK_TRANSFER_STATUS_MAX_RETRIES) { 270 | int status = getTransferStatus(); 271 | 272 | if ((status & I2C_STATUS_FLAG_CONTROLLER_BUSY) != 0) { 273 | continue; 274 | } 275 | 276 | if ((status & I2C_STATUS_FLAG_ERROR) != 0) { 277 | throw new IOException(String.format("Transfer error, status: 0x%02x", status)); 278 | } 279 | 280 | // Transfer is complete and no error was occurred 281 | return; 282 | } 283 | 284 | // Can't check transfer status because of timeout 285 | resetI2cMaster(); 286 | throw new IOException("Can't check transfer status: reties limit was reached"); 287 | } 288 | 289 | private int getTransferStatus() throws IOException { 290 | getHidFeatureReport(REPORT_ID_I2C_STATUS, buffer, REPORT_SIZE_I2C_STATUS); 291 | return buffer[I2C_STATUS_BUS_STATUS_OFFSET]; 292 | } 293 | 294 | private void resetI2cMaster() throws IOException { 295 | int i = 0; 296 | buffer[i++] = (byte) REPORT_ID_SYSTEM_SETTINGS; 297 | buffer[i++] = REQUEST_I2C_RESET; 298 | setHidFeatureReport(buffer, i); 299 | } 300 | 301 | public static UsbDeviceIdentifier[] getSupportedUsbDeviceIdentifiers() { 302 | return new UsbDeviceIdentifier[] { 303 | new UsbDeviceIdentifier(0x403, 0x6030) 304 | }; 305 | } 306 | } 307 | -------------------------------------------------------------------------------- /lib/src/main/java/com/github/ykc3/android/usbi2c/adapter/HidUsbI2cAdapter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025 Victor Antonovich 3 | * 4 | * This work is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Lesser General Public License 6 | * as published by the Free Software Foundation; either version 2.1 7 | * of the License, or (at your option) any later version. 8 | * 9 | * This work is distributed in the hope that it will be useful, but 10 | * without any warranty; without even the implied warranty of merchantability 11 | * or fitness for a particular purpose. See the GNU Lesser General Public 12 | * License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with this library; if not, write to the Free Software Foundation, Inc., 16 | * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. 17 | */ 18 | 19 | package com.github.ykc3.android.usbi2c.adapter; 20 | 21 | import android.hardware.usb.UsbConstants; 22 | import android.hardware.usb.UsbDevice; 23 | import android.hardware.usb.UsbEndpoint; 24 | import android.hardware.usb.UsbInterface; 25 | 26 | import com.github.ykc3.android.usbi2c.UsbI2cManager; 27 | 28 | import java.io.IOException; 29 | 30 | /** 31 | * Base class for HID USB I2C adapters. 32 | */ 33 | abstract class HidUsbI2cAdapter extends BaseUsbI2cAdapter { 34 | // HID feature request GET_REPORT value 35 | private static final int HID_FEATURE_REQUEST_REPORT_GET = 0x01; 36 | // HID feature request SET_REPORT value 37 | private static final int HID_FEATURE_REQUEST_REPORT_SET = 0x09; 38 | // HID feature request report type 39 | private static final int HID_FEATURE_REQUEST_REPORT_TYPE = 0x03; 40 | 41 | // Max HID transfer size, in bytes 42 | protected static final int MAX_HID_TRANSFER_SIZE = 64; 43 | 44 | // HID data buffer 45 | protected final byte[] buffer = new byte[MAX_HID_TRANSFER_SIZE]; 46 | 47 | public HidUsbI2cAdapter(UsbI2cManager manager, UsbDevice device) { 48 | super(manager, device); 49 | } 50 | 51 | @Override 52 | protected void init(UsbDevice usbDevice) throws IOException { 53 | if (usbDevice.getInterfaceCount() == 0) { 54 | throw new IOException("No interfaces found for device: " + usbDevice); 55 | } 56 | 57 | UsbInterface usbInterface = usbDevice.getInterface(0); 58 | if (usbInterface.getEndpointCount() < 2) { 59 | throw new IOException("No endpoints found for device: " + usbDevice); 60 | } 61 | 62 | UsbEndpoint usbReadEndpoint = null, usbWriteEndpoint = null; 63 | for (int i = 0; i < usbInterface.getEndpointCount(); i++) { 64 | UsbEndpoint usbEndpoint = usbInterface.getEndpoint(i); 65 | if (usbEndpoint.getType() == UsbConstants.USB_ENDPOINT_XFER_INT) { 66 | if (usbEndpoint.getDirection() == UsbConstants.USB_DIR_IN) { 67 | usbReadEndpoint = usbEndpoint; 68 | } else { 69 | usbWriteEndpoint = usbEndpoint; 70 | } 71 | } 72 | } 73 | if (usbReadEndpoint == null || usbWriteEndpoint == null) { 74 | throw new IOException("No read or write HID endpoint found for device: " + usbDevice); 75 | } 76 | setBulkEndpoints(usbReadEndpoint, usbWriteEndpoint); 77 | 78 | configure(); 79 | } 80 | 81 | private void checkReportId(int reportId) { 82 | if ((reportId & 0xff) != reportId) { 83 | throw new IllegalArgumentException("Invalid report ID: " + reportId); 84 | } 85 | } 86 | 87 | /** 88 | * Get HID feature report from USB device to data buffer. 89 | * 90 | * @param reportId feature report ID 91 | * @param data data buffer to read report into 92 | * @param length feature report data length to read 93 | * @throws IOException in case of I/O error 94 | */ 95 | protected void getHidFeatureReport(int reportId, byte[] data, int length) throws IOException { 96 | checkReportId(reportId); 97 | controlTransfer(UsbConstants.USB_TYPE_CLASS | UsbConstants.USB_DIR_IN 98 | | UsbConstants.USB_INTERFACE_SUBCLASS_BOOT, 99 | HID_FEATURE_REQUEST_REPORT_GET, (HID_FEATURE_REQUEST_REPORT_TYPE << 8) | reportId, 100 | 0, data, length); 101 | } 102 | 103 | /** 104 | * Set HID feature report from data buffer to USB device. 105 | * 106 | * @param data feature report data buffer 107 | * @param length feature report data length to send 108 | * @throws IOException in case of I/O error 109 | */ 110 | protected void setHidFeatureReport(byte[] data, int length) throws IOException { 111 | int reportId = data[0] & 0xff; 112 | controlTransfer(UsbConstants.USB_TYPE_CLASS | UsbConstants.USB_DIR_OUT 113 | | UsbConstants.USB_INTERFACE_SUBCLASS_BOOT, 114 | HID_FEATURE_REQUEST_REPORT_SET, (HID_FEATURE_REQUEST_REPORT_TYPE << 8) | reportId, 115 | 0, data, length); 116 | } 117 | 118 | /** 119 | * Read HID data report from USB device to data buffer. 120 | * 121 | * @param data data buffer to read report into 122 | * @param timeout read timeout in milliseconds 123 | * @throws IOException in case of data report read error or timeout 124 | */ 125 | protected void getHidDataReport(byte[] data, int timeout) throws IOException { 126 | bulkRead(data, 0, data.length, timeout); 127 | } 128 | 129 | /** 130 | * Write HID data report from data buffer to USB device. 131 | * 132 | * @param data data buffer to send report from 133 | * @param length data report length 134 | * @throws IOException in case of data report send error 135 | */ 136 | protected void sendHidDataReport(byte[] data, int length) throws IOException { 137 | bulkWrite(data, length, BaseUsbI2cAdapter.USB_TIMEOUT_MILLIS); 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /lib/src/main/java/com/github/ykc3/android/usbi2c/adapter/TinyUsbI2cAdapter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Victor Antonovich 3 | * 4 | * This work is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Lesser General Public License 6 | * as published by the Free Software Foundation; either version 2.1 7 | * of the License, or (at your option) any later version. 8 | * 9 | * This work is distributed in the hope that it will be useful, but 10 | * without any warranty; without even the implied warranty of merchantability 11 | * or fitness for a particular purpose. See the GNU Lesser General Public 12 | * License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with this library; if not, write to the Free Software Foundation, Inc., 16 | * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. 17 | */ 18 | 19 | package com.github.ykc3.android.usbi2c.adapter; 20 | 21 | import android.hardware.usb.UsbConstants; 22 | import android.hardware.usb.UsbDevice; 23 | 24 | import com.github.ykc3.android.usbi2c.UsbI2cDevice; 25 | import com.github.ykc3.android.usbi2c.UsbI2cManager; 26 | import static com.github.ykc3.android.usbi2c.UsbI2cManager.UsbDeviceIdentifier; 27 | 28 | import java.io.IOException; 29 | 30 | /** 31 | * Cheap and simple I2C to USB interface (github project). 32 | */ 33 | public class TinyUsbI2cAdapter extends BaseUsbI2cAdapter { 34 | // Adapter name 35 | public static final String ADAPTER_NAME = "TinyUSB"; 36 | 37 | // Commands via USB, must match command ids in the firmware 38 | private static final int CMD_ECHO = 0; 39 | private static final int CMD_GET_FUNC = 1; 40 | private static final int CMD_SET_DELAY = 2; 41 | private static final int CMD_GET_STATUS = 3; 42 | 43 | private static final int CMD_I2C_IO = 4; 44 | private static final int CMD_I2C_IO_BEGIN = 1; 45 | private static final int CMD_I2C_IO_END = (1 << 1); 46 | 47 | private static final int USB_RECIP_INTERFACE = 0x01; 48 | 49 | class TinyUsbI2cDevice extends BaseUsbI2cDevice { 50 | private final byte[] regBuffer = new byte[1]; 51 | 52 | TinyUsbI2cDevice(int address) { 53 | super(address); 54 | } 55 | 56 | @Override 57 | protected void deviceReadReg(int reg, byte[] buffer, int length) throws IOException { 58 | checkDataLength(length, buffer.length); 59 | regBuffer[0] = (byte) reg; 60 | usbWrite(CMD_I2C_IO | CMD_I2C_IO_BEGIN, 0, address, regBuffer, 1); 61 | usbRead(CMD_I2C_IO | CMD_I2C_IO_END, I2C_M_RD, address, buffer, length); 62 | } 63 | 64 | @Override 65 | protected void deviceRead(byte[] buffer, int length) throws IOException { 66 | checkDataLength(length, buffer.length); 67 | usbRead(CMD_I2C_IO | CMD_I2C_IO_BEGIN | CMD_I2C_IO_END, I2C_M_RD, address, 68 | buffer, length); 69 | } 70 | 71 | @Override 72 | protected void deviceWrite(byte[] buffer, int length) throws IOException { 73 | checkDataLength(length, buffer.length); 74 | usbWrite(CMD_I2C_IO | CMD_I2C_IO_BEGIN | CMD_I2C_IO_END, 0, address, 75 | buffer, length); 76 | } 77 | } 78 | 79 | public TinyUsbI2cAdapter(UsbI2cManager manager, UsbDevice usbDevice) { 80 | super(manager, usbDevice); 81 | } 82 | 83 | @Override 84 | public String getName() { 85 | return ADAPTER_NAME; 86 | } 87 | 88 | @Override 89 | public TinyUsbI2cDevice getDeviceImpl(int address) { 90 | return new TinyUsbI2cDevice(address); 91 | } 92 | 93 | private void usbRead(int cmd, int value, int index, byte[] data, int len) throws IOException { 94 | controlTransfer(UsbConstants.USB_TYPE_VENDOR | USB_RECIP_INTERFACE | UsbConstants.USB_DIR_IN, 95 | cmd, value, index, data, len); 96 | } 97 | 98 | private void usbWrite(int cmd, int value, int index, byte[] data, int len) throws IOException { 99 | controlTransfer(UsbConstants.USB_TYPE_VENDOR | USB_RECIP_INTERFACE | UsbConstants.USB_DIR_OUT, 100 | cmd, value, index, data, len); 101 | } 102 | 103 | public static UsbDeviceIdentifier[] getSupportedUsbDeviceIdentifiers() { 104 | return new UsbDeviceIdentifier[] { 105 | new UsbDeviceIdentifier(0x403, 0xc631) 106 | }; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /lib/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | USB I2C Library For Android 3 | 4 | -------------------------------------------------------------------------------- /lib/src/main/res/xml/usb_device_filter.xml: -------------------------------------------------------------------------------- 1 | 2 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':lib' 2 | --------------------------------------------------------------------------------