AndroidThings傳感器添加
Android 傳感器框架支持多種傳感器類型來感知周邊環(huán)境數(shù)據(jù)。使用Androidthings提供的傳感器驅(qū)動接口,可以通過Peripheral I/O接口來添加新的傳感器設(shè)備。對于Android系統(tǒng)中已經(jīng)集成的傳感器的數(shù)據(jù)通過SensorManager的接口即可獲取,另外我們的Androidtings應(yīng)用也可以通過創(chuàng)建新的傳感器驅(qū)動的方式來為系統(tǒng)添加一個新類型的傳感器,比如溫感器,血糖檢測器等傳感器。
創(chuàng)建一個新的傳感器驅(qū)動并將其注冊進系統(tǒng)后,系統(tǒng)將會輪詢監(jiān)聽傳感器來周期性的獲取傳感器數(shù)據(jù), 為了響應(yīng)系統(tǒng)對傳感器數(shù)據(jù)的輪訓(xùn)請求, 我們需要實現(xiàn)UserSensorDriver 類并且重寫read()方法,在read方法中返回傳感器數(shù)據(jù).下面我們來詳細(xì)描述添加一個新的傳感器的過程。
首先要添加系統(tǒng)權(quán)限
<uses-permission android:name="com.google.android.things.permission.MANAGE_SENSOR_DRIVERS" />
然后要實現(xiàn)一個傳感器驅(qū)動,用來讀取傳感器數(shù)據(jù),每次調(diào)用read方法都要返回一個新的包含當(dāng)前傳感器數(shù)據(jù)的UserSensorReading對象。代碼示例如下
UserSensorDriver mDriver = new UserSensorDriver() {
// Sensor data values
float x, y, z;
@Override
public UserSensorReading read() {
try {
// ...讀取傳感器硬件數(shù)據(jù)的步驟此處略過...
// 將讀取到的傳感器數(shù)據(jù)返回
return new UserSensorReading(new float[]{x, y, z});
} (catch Exception e) {
throw new IOException("Unable to read sensor");
}
}
};
創(chuàng)建完傳感器驅(qū)動后,開始為系統(tǒng)添加傳感器,傳感器分為系統(tǒng)已支持的類型和系統(tǒng)還未支持的類型,兩者的添加步驟有區(qū)別。
如果需要添加一個系統(tǒng)支持類型的傳感器設(shè)備:
- 使用 UserSensor.Builder 描述傳感器類型,傳感器類型為Android 目前已經(jīng)支持的 Sensor types.
- 設(shè)置 sensor 名稱以及驅(qū)動供應(yīng)商名稱
- 設(shè)置傳感器數(shù)據(jù)范圍,分辨率,更新頻率,電量需求(如果需要).這些參數(shù)將幫助framework在接到SensorManager的數(shù)據(jù)請求時來選擇最佳的傳感器。
- 使用setDriver()方法attach UserSensorDriver
如果要添加一個系統(tǒng)尚未支持的傳感器類型:
- 使用setCustomType() 方法,設(shè)置sensor type 為大于等于 TYPE_DEVICE_PRIVATE_BASE的類型值
- 設(shè)置傳感器類型名稱,傳感器名稱在系統(tǒng)范圍內(nèi)需要是唯一的
- 設(shè)置傳感器數(shù)據(jù)報告模式
代碼示例如下:
// 添加已支持類型的傳感器
UserSensor accelerometer = UserSensor.builder()
.setName("GroveAccelerometer")
.setVendor("Seeed")
.setType(Sensor.TYPE_ACCELEROMETER)
.setDriver(mDriver)
.build();
// 添加未支持類型的傳感器
UserSensor custom = UserSensor.builder()
.setName("MySensor")
.setVendor("MyCompany")
.setCustomType(Sensor.TYPE_DEVICE_PRIVATE_BASE,
"com.example.mysensor",
Sensor.REPORTING_MODE_CONTINUOUS)
.setDriver(mDriver)
.build();
創(chuàng)建完傳感器后就要將其注冊到系統(tǒng),使用UserDriverManager來 注冊傳感器,將新傳感器連接到系統(tǒng),應(yīng)用就可以可以通過Android傳感器系統(tǒng)服務(wù)來獲取傳感器的數(shù)據(jù)。實例代碼如下
public class SensorDriverService extends Service {
UserSensor mAccelerometer;
@Override
public void onCreate() {
super.onCreate();
...
UserDriverManager manager = UserDriverManager.getManager();
// 創(chuàng)建一個新的傳感器
mAccelerometer = ...;
// 將傳感器注冊到系統(tǒng)
manager.registerSensor(mAccelerometer);
}
}
至此,如何添加一個新的傳感器的步驟我們就描述完了。下面我們介紹如何為Androidthings應(yīng)用添加相機功能,這也是官方提供的一個示例。
Androidthings相機功能添加
首先添加系統(tǒng)權(quán)限:
<uses-permission android:name="android.permission.CAMERA" />
然后將相機連接到開發(fā)板,將相機模塊連接到開發(fā)板上的CS1-2相機接口,如下圖所示(圖片來自于谷歌開發(fā)者網(wǎng)站)
完成上面的準(zhǔn)備工作后,我們正式開始進行圖片獲取相關(guān)的流程,獲取圖片的第一步是需要找到相機并且建立和設(shè)備之間的連接:
- 使用CameraManager 系統(tǒng)服務(wù)的getCameraIdList()找到所有可用的相機設(shè)備列表。
- 創(chuàng)建一個ImageReader 實例用來處理相機原始數(shù)據(jù)并且生成JPEG編碼格式的圖像數(shù)據(jù)。
- 建立和相機設(shè)備的連接,相機被成功打開后將會調(diào)用CameraDevice.StateCallback的onOpened() 回調(diào)方法
示例代碼如下:
public class CameraSample {
// 圖像參數(shù) (device-specific)
private static final int IMAGE_WIDTH = ...;
private static final int IMAGE_HEIGHT = ...;
private static final int MAX_IMAGES = ...;
// 用于處理圖像結(jié)果
private ImageReader mImageReader;
// 激活相機設(shè)備連接
private CameraDevice mCameraDevice;
// 激活圖像捕捉
private CameraCaptureSession mCaptureSession;
// Initialize a new camera device connection
public void initializeCamera(Context context,
Handler backgroundHandler,
ImageReader.OnImageAvailableListener imageAvailableListener) {
// 獲取可用的相機設(shè)備列表
CameraManager manager = (CameraManager) context.getSystemService(CAMERA_SERVICE);
String[] camIds = {};
try {
camIds = manager.getCameraIdList();
} catch (CameraAccessException e) {
Log.d(TAG, "Cam access exception getting IDs", e);
}
if (camIds.length < 1) {
Log.d(TAG, "No cameras found");
return;
}
String id = camIds[0];
// 初始化圖片處理器 imagereader
mImageReader = ImageReader.newInstance(IMAGE_WIDTH, IMAGE_HEIGHT,
ImageFormat.JPEG, MAX_IMAGES);
mImageReader.setOnImageAvailableListener(imageAvailableListener, backgroundHandler);
// 打開相機
try {
manager.openCamera(id, mStateCallback, backgroundHandler);
} catch (CameraAccessException cae) {
Log.d(TAG, "Camera access exception", cae);
}
}
// 相機相關(guān)回調(diào)
private final CameraDevice.StateCallback mStateCallback =
new CameraDevice.StateCallback() {
@Override
public void onOpened(CameraDevice cameraDevice) {
mCameraDevice = cameraDevice;
}
...
};
// 關(guān)閉相機資源
public void shutDown() {
if (mCameraDevice != null) {
mCameraDevice.close();
}
}
}
上面詳細(xì)描述了相機初始化,建立設(shè)備連接相關(guān)的工作,接下來描述一下如何調(diào)用相機來捕獲圖片
和相機建立連接之后, 需要創(chuàng)建一個相機拍照會話,具體步驟如下:
- 使用createCaptureSession() 方法創(chuàng)建一個新的CameraCaptureSession 實例
- 傳入和ImageReader建立連接的surface
- 創(chuàng)建一個 CameraCaptureSession.StateCallback回調(diào)來監(jiān)控相機會話配置以及激活的狀態(tài) 。示例代碼如下
public class CameraSample {
...
public void takePicture() {
// 為了捕獲靜態(tài)圖像,首先創(chuàng)建一個圖像捕捉會話
try {
mCameraDevice.createCaptureSession(
Collections.singletonList(mImageReader.getSurface()),
mSessionCallback,
null);
} catch (CameraAccessException cae) {
Log.d(TAG, "access exception while preparing pic", cae);
}
}
// 通過回調(diào)來監(jiān)控會話狀態(tài)
private CameraCaptureSession.StateCallback mSessionCallback =
new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(CameraCaptureSession cameraCaptureSession) {
// 會話已經(jīng)準(zhǔn)備好,則可以開始拍照.
mCaptureSession = cameraCaptureSession;
triggerImageCapture();
}
@Override
public void onConfigureFailed(CameraCaptureSession cameraCaptureSession) {
Log.w(TAG, "Failed to configure camera");
}
};
}
接下來進入到圖片捕捉階段 :
- 初始化一個新的 CaptureRequest ,如果靜態(tài)圖片使用TEMPLATE_STILL_CAPTURE 參數(shù).
- 設(shè)置其他相機拍照參數(shù), 比如 auto-focus ,auto-exposure等
- 調(diào)用CameraCaptureSession的capture方法來啟動拍照請求,拍照完成后,關(guān)閉capture session。通過CameraCaptureSession.CaptureCallback回調(diào)來處理拍照結(jié)果。具體示例代碼如下:
public class CameraSample {
// Active camera device connection
private CameraDevice mCameraDevice;
// Active camera capture session
private CameraCaptureSession mCaptureSession;
...
private void triggerImageCapture() {
try {
final CaptureRequest.Builder captureBuilder =
mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
captureBuilder.addTarget(mImageReader.getSurface());
captureBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON);
mCaptureSession.capture(captureBuilder.build(), mCaptureCallback, null);
} catch (CameraAccessException cae) {
Log.d(TAG, "camera capture exception");
}
}
// Callback handling capture progress events
private final CameraCaptureSession.CaptureCallback mCaptureCallback =
new CameraCaptureSession.CaptureCallback() {
...
@Override
public void onCaptureCompleted(CameraCaptureSession session,
CaptureRequest request,
TotalCaptureResult result) {
if (session != null) {
session.close();
mCaptureSession = null;
Log.d(TAG, "CaptureSession closed");
}
}
};
}
最后是對獲取到的原始圖像數(shù)據(jù)的處理:
- 通過ImageReader 的onImageAvailable 方法獲取最新的圖像數(shù)據(jù) .
- 通過getBuffer()方法來獲取JPEG編碼格式的數(shù)據(jù)。代碼實例如下:
// Callback to receive captured camera image data
private ImageReader.OnImageAvailableListener mOnImageAvailableListener =
new ImageReader.OnImageAvailableListener() {
@Override
public void onImageAvailable(ImageReader reader) {
// Get the raw image bytes
Image image = reader.acquireLatestImage();
ByteBuffer imageBuf = image.getPlanes()[0].getBuffer();
final byte[] imageBytes = new byte[imageBuf.remaining()];
imageBuf.get(imageBytes);
image.close();
onPictureTaken(imageBytes);
}
};
private void onPictureTaken(final byte[] imageBytes) {
if (imageBytes != null) {
// ...process the captured image...
}
}
這樣就完成了通過相機設(shè)備來獲取靜態(tài)圖像數(shù)據(jù)的整個流程。
參考資料:https://developer.android.google.cn/things/sdk/drivers/sensors.html#describing_the_sensor