Merge "Dynamic sensor manager module - framework"
This commit is contained in:
commit
e12a0cc39d
11 changed files with 1111 additions and 0 deletions
77
modules/sensors/dynamic_sensor/Android.mk
Normal file
77
modules/sensors/dynamic_sensor/Android.mk
Normal file
|
@ -0,0 +1,77 @@
|
|||
# Copyright (C) 2017 The Android Open Source Project
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
LOCAL_PATH := $(call my-dir)
|
||||
COMMON_CFLAGS := -Wall -Werror -Wextra
|
||||
|
||||
#
|
||||
# There are two ways to utilize the dynamic sensor module:
|
||||
# 1. Use as an extension in an existing hal: declare dependency on libdynamic_sensor_ext shared
|
||||
# library in existing sensor hal.
|
||||
# 2. Use as a standalone sensor HAL and configure multihal to combine it with sensor hal that
|
||||
# hosts other sensors: add dependency on sensors.dynamic_sensor_hal in device level makefile and
|
||||
# write multihal configuration file accordingly.
|
||||
#
|
||||
# Please take only one of these two options to avoid conflict over hardware resource.
|
||||
#
|
||||
|
||||
#
|
||||
# Option 1: sensor extension module
|
||||
#
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := libdynamic_sensor_ext
|
||||
LOCAL_MODULE_TAGS := optional
|
||||
LOCAL_MODULE_OWNER := google
|
||||
|
||||
LOCAL_CFLAGS += $(COMMON_CFLAGS) -DLOG_TAG=\"DynamicSensorExt\"
|
||||
|
||||
LOCAL_SRC_FILES := \
|
||||
BaseSensorObject.cpp \
|
||||
DynamicSensorManager.cpp \
|
||||
RingBuffer.cpp
|
||||
|
||||
LOCAL_SHARED_LIBRARIES := \
|
||||
libcutils \
|
||||
libutils \
|
||||
liblog
|
||||
|
||||
LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)
|
||||
|
||||
include $(BUILD_SHARED_LIBRARY)
|
||||
|
||||
#
|
||||
# Option 2: independent sensor hal
|
||||
#
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := sensors.dynamic_sensor_hal
|
||||
LOCAL_MODULE_RELATIVE_PATH := hw
|
||||
LOCAL_MODULE_TAGS := optional
|
||||
LOCAL_MODULE_OWNER := google
|
||||
|
||||
LOCAL_CFLAGS += $(COMMON_CFLAGS) -DLOG_TAG=\"DynamicSensorHal\"
|
||||
|
||||
LOCAL_SRC_FILES := \
|
||||
BaseSensorObject.cpp \
|
||||
DynamicSensorManager.cpp \
|
||||
RingBuffer.cpp \
|
||||
sensors.cpp
|
||||
|
||||
LOCAL_SHARED_LIBRARIES := \
|
||||
libcutils \
|
||||
libutils \
|
||||
liblog \
|
||||
|
||||
LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)
|
||||
|
||||
include $(BUILD_SHARED_LIBRARY)
|
39
modules/sensors/dynamic_sensor/BaseDynamicSensorDaemon.h
Normal file
39
modules/sensors/dynamic_sensor/BaseDynamicSensorDaemon.h
Normal file
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* Copyright (C) 2017 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef ANDROID_SENSORHAL_EXT_BASE_DYNAMIC_SENSOR_DAEMON_H
|
||||
#define ANDROID_SENSORHAL_EXT_BASE_DYNAMIC_SENSOR_DAEMON_H
|
||||
|
||||
#include <utils/RefBase.h>
|
||||
|
||||
namespace android {
|
||||
namespace SensorHalExt {
|
||||
|
||||
class DynamicSensorManager;
|
||||
|
||||
class BaseDynamicSensorDaemon : public RefBase {
|
||||
public:
|
||||
BaseDynamicSensorDaemon(DynamicSensorManager& manager) : mManager(manager) {}
|
||||
virtual ~BaseDynamicSensorDaemon() = default;
|
||||
protected:
|
||||
DynamicSensorManager& mManager;
|
||||
};
|
||||
|
||||
} // namespace SensorHalExt
|
||||
} // namespace android
|
||||
|
||||
#endif // ANDROID_SENSORHAL_EXT_BASE_DYNAMIC_SENSOR_DAEMON_H
|
||||
|
60
modules/sensors/dynamic_sensor/BaseSensorObject.cpp
Normal file
60
modules/sensors/dynamic_sensor/BaseSensorObject.cpp
Normal file
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* Copyright (C) 2017 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "BaseSensorObject.h"
|
||||
#include "SensorEventCallback.h"
|
||||
|
||||
#include <utils/Log.h>
|
||||
#include <cstring>
|
||||
|
||||
namespace android {
|
||||
namespace SensorHalExt {
|
||||
|
||||
BaseSensorObject::BaseSensorObject() : mCallback(nullptr) {
|
||||
}
|
||||
|
||||
bool BaseSensorObject::setEventCallback(SensorEventCallback* callback) {
|
||||
if (mCallback != nullptr) {
|
||||
ALOGE("callback is already assigned, cannot change.");
|
||||
return false;
|
||||
}
|
||||
mCallback = callback;
|
||||
return true;
|
||||
}
|
||||
|
||||
void BaseSensorObject::getUuid(uint8_t* uuid) const {
|
||||
// default uuid denoting uuid feature is not supported on this sensor.
|
||||
memset(uuid, 0, 16);
|
||||
}
|
||||
|
||||
int BaseSensorObject::flush() {
|
||||
static const sensors_event_t event = {
|
||||
.type = SENSOR_TYPE_META_DATA,
|
||||
.timestamp = TIMESTAMP_AUTO_FILL // timestamp will be filled at dispatcher
|
||||
};
|
||||
generateEvent(event);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void BaseSensorObject::generateEvent(const sensors_event_t &e) {
|
||||
if (mCallback) {
|
||||
mCallback->submitEvent(this, e);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace SensorHalExt
|
||||
} // namespace android
|
||||
|
67
modules/sensors/dynamic_sensor/BaseSensorObject.h
Normal file
67
modules/sensors/dynamic_sensor/BaseSensorObject.h
Normal file
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* Copyright (C) 2017 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef ANDROID_SENSORHAL_BASE_SENSOR_OBJECT_H
|
||||
#define ANDROID_SENSORHAL_BASE_SENSOR_OBJECT_H
|
||||
|
||||
#include <utils/RefBase.h>
|
||||
#include <utils/Timers.h> // for nsecs_t
|
||||
#include <cstdint>
|
||||
|
||||
struct sensor_t;
|
||||
struct sensors_event_t;
|
||||
|
||||
namespace android {
|
||||
namespace SensorHalExt {
|
||||
|
||||
class SensorEventCallback;
|
||||
|
||||
class BaseSensorObject : virtual public RefBase {
|
||||
public:
|
||||
BaseSensorObject();
|
||||
virtual ~BaseSensorObject() = default;
|
||||
|
||||
// always called by DynamicSensorManager, callback must point to
|
||||
// valid object throughout life cycle of BaseSensorObject
|
||||
bool setEventCallback(SensorEventCallback* callback);
|
||||
|
||||
// virtual functions to get sensor information and operate sensor
|
||||
virtual const sensor_t* getSensor() const = 0;
|
||||
|
||||
// get uuid of sensor, default implementation set it to all zero, means does not have a uuid.
|
||||
virtual void getUuid(uint8_t* uuid) const;
|
||||
|
||||
// enable sensor
|
||||
virtual int enable(bool enable) = 0;
|
||||
|
||||
// set sample period and batching period of sensor.
|
||||
virtual int batch(nsecs_t samplePeriod, nsecs_t batchPeriod) = 0;
|
||||
|
||||
// flush sensor, default implementation will send a flush complete event back.
|
||||
virtual int flush();
|
||||
|
||||
protected:
|
||||
// utility function for sub-class
|
||||
void generateEvent(const sensors_event_t &e);
|
||||
private:
|
||||
SensorEventCallback* mCallback;
|
||||
};
|
||||
|
||||
} // namespace SensorHalExt
|
||||
} // namespace android
|
||||
|
||||
#endif // ANDROID_SENSORHAL_BASE_SENSOR_OBJECT_H
|
||||
|
283
modules/sensors/dynamic_sensor/DynamicSensorManager.cpp
Normal file
283
modules/sensors/dynamic_sensor/DynamicSensorManager.cpp
Normal file
|
@ -0,0 +1,283 @@
|
|||
/*
|
||||
* Copyright (C) 2017 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "BaseDynamicSensorDaemon.h"
|
||||
#include "BaseSensorObject.h"
|
||||
#include "DynamicSensorManager.h"
|
||||
|
||||
#include <utils/Log.h>
|
||||
#include <utils/SystemClock.h>
|
||||
|
||||
#include <cassert>
|
||||
|
||||
namespace android {
|
||||
namespace SensorHalExt {
|
||||
|
||||
DynamicSensorManager* DynamicSensorManager::createInstance(
|
||||
int handleBase, int handleCount, SensorEventCallback *callback) {
|
||||
auto m = new DynamicSensorManager(handleBase, handleBase + handleCount - 1, callback);
|
||||
return m;
|
||||
}
|
||||
|
||||
DynamicSensorManager::DynamicSensorManager(
|
||||
int handleBase, int handleMax, SensorEventCallback* callback) :
|
||||
mHandleRange(handleBase, handleMax),
|
||||
mCallback(callback),
|
||||
mFifo(callback ? 0 : kFifoSize),
|
||||
mNextHandle(handleBase+1) {
|
||||
assert(handleBase > 0 && handleMax > handleBase + 1); // handleBase is reserved
|
||||
|
||||
mMetaSensor = (const sensor_t) {
|
||||
"Dynamic Sensor Manager",
|
||||
"Google",
|
||||
1, // version
|
||||
handleBase, // handle
|
||||
SENSOR_TYPE_DYNAMIC_SENSOR_META,
|
||||
1, // maxRange
|
||||
1, // resolution
|
||||
1e-6f, // power, very small number instead of 0
|
||||
// to avoid sigularity in app
|
||||
(int32_t)(1000), // minDelay
|
||||
0, // fifoReservedEventCount
|
||||
0, // fifoMaxEventCount
|
||||
SENSOR_STRING_TYPE_DYNAMIC_SENSOR_META,
|
||||
"", // requiredPermission
|
||||
(long)(1000), // maxDelay
|
||||
SENSOR_FLAG_SPECIAL_REPORTING_MODE | SENSOR_FLAG_WAKE_UP,
|
||||
{ NULL, NULL }
|
||||
};
|
||||
}
|
||||
|
||||
DynamicSensorManager::~DynamicSensorManager() {
|
||||
// free all daemons first
|
||||
mDaemonVector.clear();
|
||||
}
|
||||
|
||||
bool DynamicSensorManager::owns(int handle) const {
|
||||
return handle >= mHandleRange.first && handle < mHandleRange.second;
|
||||
}
|
||||
|
||||
int DynamicSensorManager::activate(int handle, bool enable) {
|
||||
if (handle == mHandleRange.first) {
|
||||
// ignored
|
||||
return 0;
|
||||
}
|
||||
|
||||
// in case there is a pending report, now it is time to remove it as it is no longer necessary.
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(mLock);
|
||||
mPendingReport.erase(handle);
|
||||
}
|
||||
|
||||
return operateSensor(handle,
|
||||
[&enable] (sp<BaseSensorObject> s)->int {
|
||||
return s->enable(enable);
|
||||
});
|
||||
}
|
||||
|
||||
int DynamicSensorManager::batch(int handle, nsecs_t sample_period, nsecs_t batch_period) {
|
||||
if (handle == mHandleRange.first) {
|
||||
// ignored
|
||||
return 0;
|
||||
}
|
||||
return operateSensor(handle,
|
||||
[&sample_period, &batch_period] (sp<BaseSensorObject> s)->int {
|
||||
return s->batch(sample_period, batch_period);
|
||||
});
|
||||
}
|
||||
|
||||
int DynamicSensorManager::setDelay(int handle, nsecs_t sample_period) {
|
||||
return batch(handle, sample_period, 0);
|
||||
}
|
||||
|
||||
int DynamicSensorManager::flush(int handle) {
|
||||
if (handle == mHandleRange.first) {
|
||||
// TODO: submit a flush complete here
|
||||
static const sensors_event_t event = {
|
||||
.type = SENSOR_TYPE_META_DATA,
|
||||
.sensor = mHandleRange.first,
|
||||
.timestamp = TIMESTAMP_AUTO_FILL, // timestamp will be filled at dispatcher
|
||||
};
|
||||
submitEvent(nullptr, event);
|
||||
return 0;
|
||||
}
|
||||
return operateSensor(handle, [] (sp<BaseSensorObject> s)->int {return s->flush();});
|
||||
}
|
||||
|
||||
int DynamicSensorManager::poll(sensors_event_t * data, int count) {
|
||||
assert(mCallback == nullptr);
|
||||
std::lock_guard<std::mutex> lk(mFifoLock);
|
||||
return mFifo.read(data, count);
|
||||
}
|
||||
|
||||
bool DynamicSensorManager::registerSensor(sp<BaseSensorObject> sensor) {
|
||||
std::lock_guard<std::mutex> lk(mLock);
|
||||
if (mReverseMap.find(sensor.get()) != mReverseMap.end()) {
|
||||
ALOGE("trying to add the same sensor twice, ignore");
|
||||
return false;
|
||||
}
|
||||
int handle = getNextAvailableHandle();
|
||||
if (handle < 0) {
|
||||
ALOGE("Running out of handle, quit.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// these emplace will always be successful
|
||||
mMap.emplace(handle, sensor);
|
||||
mReverseMap.emplace(sensor.get(), handle);
|
||||
sensor->setEventCallback(this);
|
||||
|
||||
auto entry = mPendingReport.emplace(
|
||||
std::piecewise_construct,
|
||||
std::forward_as_tuple(handle),
|
||||
std::forward_as_tuple(handle, sensor));
|
||||
if (entry.second) {
|
||||
submitEvent(nullptr, entry.first->second.generateConnectionEvent(mHandleRange.first));
|
||||
}
|
||||
return entry.second;
|
||||
}
|
||||
|
||||
void DynamicSensorManager::unregisterSensor(sp<BaseSensorObject> sensor) {
|
||||
std::lock_guard<std::mutex> lk(mLock);
|
||||
auto i = mReverseMap.find(sensor.get());
|
||||
if (i == mReverseMap.end()) {
|
||||
ALOGE("cannot remove a non-exist sensor");
|
||||
return;
|
||||
}
|
||||
int handle = i->second;
|
||||
mReverseMap.erase(i);
|
||||
mMap.erase(handle);
|
||||
|
||||
// will not clean up mPendingReport here, it will be cleaned up when at first activate call.
|
||||
// sensorservice is guranteed to call activate upon arrival of dynamic sensor meta connection
|
||||
// event.
|
||||
|
||||
// send disconnection event
|
||||
sensors_event_t event;
|
||||
ConnectionReport::fillDisconnectionEvent(&event, mHandleRange.first, handle);
|
||||
submitEvent(nullptr, event);
|
||||
}
|
||||
|
||||
int DynamicSensorManager::submitEvent(sp<BaseSensorObject> source, const sensors_event_t &e) {
|
||||
int handle;
|
||||
if (source == nullptr) {
|
||||
handle = mHandleRange.first;
|
||||
} else {
|
||||
std::lock_guard<std::mutex> lk(mLock);
|
||||
auto i = mReverseMap.find(source.get());
|
||||
if (i == mReverseMap.end()) {
|
||||
ALOGE("cannot submit event for sensor that has not been registered");
|
||||
return NAME_NOT_FOUND;
|
||||
}
|
||||
handle = i->second;
|
||||
}
|
||||
|
||||
// making a copy of events, prepare for editing
|
||||
sensors_event_t event = e;
|
||||
event.version = sizeof(event);
|
||||
|
||||
// special case of flush complete
|
||||
if (event.type == SENSOR_TYPE_META_DATA) {
|
||||
event.sensor = 0;
|
||||
event.meta_data.sensor = handle;
|
||||
} else {
|
||||
event.sensor = handle;
|
||||
}
|
||||
|
||||
// set timestamp if it is default value
|
||||
if (event.timestamp == TIMESTAMP_AUTO_FILL) {
|
||||
event.timestamp = elapsedRealtimeNano();
|
||||
}
|
||||
|
||||
if (mCallback) {
|
||||
// extention mode, calling callback directly
|
||||
int ret;
|
||||
|
||||
ret = mCallback->submitEvent(nullptr, event);
|
||||
if (ret < 0) {
|
||||
ALOGE("DynamicSensorManager callback failed, ret: %d", ret);
|
||||
}
|
||||
} else {
|
||||
// standalone mode, add event to internal buffer for poll() to pick up
|
||||
std::lock_guard<std::mutex> lk(mFifoLock);
|
||||
if (mFifo.write(&event, 1) < 0) {
|
||||
ALOGE("DynamicSensorManager fifo full");
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int DynamicSensorManager::getNextAvailableHandle() {
|
||||
if (mNextHandle == mHandleRange.second) {
|
||||
return -1;
|
||||
}
|
||||
return mNextHandle++;
|
||||
}
|
||||
|
||||
const sensor_t& DynamicSensorManager::getDynamicMetaSensor() const {
|
||||
return mMetaSensor;
|
||||
}
|
||||
|
||||
DynamicSensorManager::ConnectionReport::ConnectionReport(
|
||||
int handle, sp<BaseSensorObject> sensor) :
|
||||
mSensor(*(sensor->getSensor())),
|
||||
mName(mSensor.name),
|
||||
mVendor(mSensor.vendor),
|
||||
mPermission(mSensor.requiredPermission),
|
||||
mStringType(mSensor.stringType),
|
||||
mGenerated(false) {
|
||||
mSensor.name = mName.c_str();
|
||||
mSensor.vendor = mVendor.c_str();
|
||||
mSensor.requiredPermission = mPermission.c_str();
|
||||
mSensor.stringType = mStringType.c_str();
|
||||
mSensor.handle = handle;
|
||||
memset(&mEvent, 0, sizeof(mEvent));
|
||||
mEvent.version = sizeof(mEvent);
|
||||
sensor->getUuid(mUuid);
|
||||
ALOGV("Connection report init: name = %s, handle = %d", mSensor.name, mSensor.handle);
|
||||
}
|
||||
|
||||
DynamicSensorManager::ConnectionReport::~ConnectionReport() {
|
||||
ALOGV("Connection report dtor: name = %s, handle = %d", mSensor.name, mSensor.handle);
|
||||
}
|
||||
|
||||
const sensors_event_t& DynamicSensorManager::ConnectionReport::
|
||||
generateConnectionEvent(int metaHandle) {
|
||||
if (!mGenerated) {
|
||||
mEvent.sensor = metaHandle;
|
||||
mEvent.type = SENSOR_TYPE_DYNAMIC_SENSOR_META;
|
||||
mEvent.timestamp = elapsedRealtimeNano();
|
||||
mEvent.dynamic_sensor_meta =
|
||||
(dynamic_sensor_meta_event_t) {true, mSensor.handle, &mSensor, {0}};
|
||||
memcpy(&mEvent.dynamic_sensor_meta.uuid, &mUuid, sizeof(mEvent.dynamic_sensor_meta.uuid));
|
||||
mGenerated = true;
|
||||
}
|
||||
return mEvent;
|
||||
}
|
||||
|
||||
void DynamicSensorManager::ConnectionReport::
|
||||
fillDisconnectionEvent(sensors_event_t* event, int metaHandle, int handle) {
|
||||
memset(event, 0, sizeof(sensors_event_t));
|
||||
event->version = sizeof(sensors_event_t);
|
||||
event->sensor = metaHandle;
|
||||
event->type = SENSOR_TYPE_DYNAMIC_SENSOR_META;
|
||||
event->timestamp = elapsedRealtimeNano();
|
||||
event->dynamic_sensor_meta.connected = false;
|
||||
event->dynamic_sensor_meta.handle = handle;
|
||||
}
|
||||
|
||||
} // namespace SensorHalExt
|
||||
} // namespace android
|
137
modules/sensors/dynamic_sensor/DynamicSensorManager.h
Normal file
137
modules/sensors/dynamic_sensor/DynamicSensorManager.h
Normal file
|
@ -0,0 +1,137 @@
|
|||
/*
|
||||
* Copyright (C) 2017 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef ANDROID_SENSORHAL_EXT_DYNAMIC_SENSOR_MANAGER_H
|
||||
#define ANDROID_SENSORHAL_EXT_DYNAMIC_SENSOR_MANAGER_H
|
||||
|
||||
#include "SensorEventCallback.h"
|
||||
#include "RingBuffer.h"
|
||||
#include <hardware/sensors.h>
|
||||
#include <utils/RefBase.h>
|
||||
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace android {
|
||||
namespace SensorHalExt {
|
||||
|
||||
class BaseDynamicSensorDaemon;
|
||||
|
||||
class DynamicSensorManager : public SensorEventCallback {
|
||||
public:
|
||||
// handleBase is reserved for the dynamic sensor meta sensor.
|
||||
// handleMax must be greater than handleBase + 1.
|
||||
// This class has two operation mode depending on callback: 1) extension, 2) stand-alone.
|
||||
// In extension mode, callback must not be nullptr. Sensor event generated will be submitted to
|
||||
// buffer of primary sensor HAL implementation. In stand-alone mode, callback must be nullptr.
|
||||
// Generated sensor events will be added into internal buffer waiting for poll() function to
|
||||
// pick up.
|
||||
//
|
||||
static DynamicSensorManager* createInstance(
|
||||
int handleBase, int handleCount, SensorEventCallback *callback);
|
||||
virtual ~DynamicSensorManager();
|
||||
|
||||
// calls to add or remove sensor, called from sensor daemon
|
||||
bool registerSensor(sp<BaseSensorObject> sensor);
|
||||
void unregisterSensor(sp<BaseSensorObject> sensor);
|
||||
|
||||
// Determine if a sensor handle is in the range defined in constructor.
|
||||
// It does not test if sensor handle is valid.
|
||||
bool owns(int handle) const;
|
||||
|
||||
// handles sensor hal requests.
|
||||
int activate(int handle, bool enable);
|
||||
int batch(int handle, nsecs_t sample_period, nsecs_t batch_period);
|
||||
int setDelay(int handle, nsecs_t sample_period);
|
||||
int flush(int handle);
|
||||
int poll(sensors_event_t * data, int count);
|
||||
|
||||
// SensorEventCallback
|
||||
virtual int submitEvent(sp<BaseSensorObject>, const sensors_event_t &e) override;
|
||||
|
||||
// get meta sensor struct
|
||||
const sensor_t& getDynamicMetaSensor() const;
|
||||
protected:
|
||||
DynamicSensorManager(int handleBase, int handleMax, SensorEventCallback* callback);
|
||||
private:
|
||||
// a helper class used for generate connection and disconnection report
|
||||
class ConnectionReport {
|
||||
public:
|
||||
ConnectionReport() {}
|
||||
ConnectionReport(int handle, sp<BaseSensorObject> sensor);
|
||||
~ConnectionReport();
|
||||
const sensors_event_t& generateConnectionEvent(int metaHandle);
|
||||
static void fillDisconnectionEvent(sensors_event_t* event, int metaHandle, int handle);
|
||||
private:
|
||||
sensor_t mSensor;
|
||||
std::string mName;
|
||||
std::string mVendor;
|
||||
std::string mPermission;
|
||||
std::string mStringType;
|
||||
sensors_event_t mEvent;
|
||||
uint8_t mUuid[16];
|
||||
bool mGenerated;
|
||||
DISALLOW_EVIL_CONSTRUCTORS(ConnectionReport);
|
||||
};
|
||||
|
||||
// returns next available handle to use upon a new sensor connection, or -1 if we run out.
|
||||
int getNextAvailableHandle();
|
||||
|
||||
// TF: int foo(sp<BaseSensorObject> obj);
|
||||
template <typename TF>
|
||||
int operateSensor(int handle, TF f) const {
|
||||
std::lock_guard<std::mutex> lk(mLock);
|
||||
const auto i = mMap.find(handle);
|
||||
if (i == mMap.end()) {
|
||||
return BAD_VALUE;
|
||||
}
|
||||
sp<BaseSensorObject> s = i->second.promote();
|
||||
if (s == nullptr) {
|
||||
// sensor object is already gone
|
||||
return BAD_VALUE;
|
||||
}
|
||||
return f(s);
|
||||
}
|
||||
|
||||
// available sensor handle space
|
||||
const std::pair<int, int> mHandleRange;
|
||||
sensor_t mMetaSensor;
|
||||
|
||||
// immutable pointer to event callback, used in extention mode.
|
||||
SensorEventCallback * const mCallback;
|
||||
|
||||
// RingBuffer used in standalone mode
|
||||
static constexpr size_t kFifoSize = 4096; //4K events
|
||||
mutable std::mutex mFifoLock;
|
||||
RingBuffer mFifo;
|
||||
|
||||
// mapping between handle and SensorObjects
|
||||
mutable std::mutex mLock;
|
||||
int mNextHandle;
|
||||
std::unordered_map<int, wp<BaseSensorObject> > mMap;
|
||||
std::unordered_map<void *, int> mReverseMap;
|
||||
mutable std::unordered_map<int, ConnectionReport> mPendingReport;
|
||||
|
||||
// daemons
|
||||
std::vector<sp<BaseDynamicSensorDaemon>> mDaemonVector;
|
||||
};
|
||||
|
||||
} // namespace SensorHalExt
|
||||
} // namespace android
|
||||
|
||||
#endif // ANDROID_SENSORHAL_EXT_DYNAMIC_SENSOR_MANAGER_H
|
104
modules/sensors/dynamic_sensor/RingBuffer.cpp
Normal file
104
modules/sensors/dynamic_sensor/RingBuffer.cpp
Normal file
|
@ -0,0 +1,104 @@
|
|||
/*
|
||||
* Copyright (C) 2017 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "RingBuffer.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
namespace android {
|
||||
|
||||
RingBuffer::RingBuffer(size_t size)
|
||||
: mSize(size),
|
||||
mData((sensors_event_t *)malloc(sizeof(sensors_event_t) * mSize)),
|
||||
mReadPos(0),
|
||||
mWritePos(0) {
|
||||
}
|
||||
|
||||
RingBuffer::~RingBuffer() {
|
||||
free(mData);
|
||||
mData = NULL;
|
||||
}
|
||||
|
||||
ssize_t RingBuffer::write(const sensors_event_t *ev, size_t size) {
|
||||
Mutex::Autolock autoLock(mLock);
|
||||
|
||||
size_t numAvailableToRead = mWritePos - mReadPos;
|
||||
size_t numAvailableToWrite = mSize - numAvailableToRead;
|
||||
|
||||
if (size > numAvailableToWrite) {
|
||||
size = numAvailableToWrite;
|
||||
}
|
||||
|
||||
size_t writePos = (mWritePos % mSize);
|
||||
size_t copy = mSize - writePos;
|
||||
|
||||
if (copy > size) {
|
||||
copy = size;
|
||||
}
|
||||
|
||||
memcpy(&mData[writePos], ev, copy * sizeof(sensors_event_t));
|
||||
|
||||
if (size > copy) {
|
||||
memcpy(mData, &ev[copy], (size - copy) * sizeof(sensors_event_t));
|
||||
}
|
||||
|
||||
mWritePos += size;
|
||||
|
||||
if (numAvailableToRead == 0 && size > 0) {
|
||||
mNotEmptyCondition.broadcast();
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
ssize_t RingBuffer::read(sensors_event_t *ev, size_t size) {
|
||||
Mutex::Autolock autoLock(mLock);
|
||||
|
||||
size_t numAvailableToRead;
|
||||
for (;;) {
|
||||
numAvailableToRead = mWritePos - mReadPos;
|
||||
if (numAvailableToRead > 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
mNotEmptyCondition.wait(mLock);
|
||||
}
|
||||
|
||||
if (size > numAvailableToRead) {
|
||||
size = numAvailableToRead;
|
||||
}
|
||||
|
||||
size_t readPos = (mReadPos % mSize);
|
||||
size_t copy = mSize - readPos;
|
||||
|
||||
if (copy > size) {
|
||||
copy = size;
|
||||
}
|
||||
|
||||
memcpy(ev, &mData[readPos], copy * sizeof(sensors_event_t));
|
||||
|
||||
if (size > copy) {
|
||||
memcpy(&ev[copy], mData, (size - copy) * sizeof(sensors_event_t));
|
||||
}
|
||||
|
||||
mReadPos += size;
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
} // namespace android
|
||||
|
49
modules/sensors/dynamic_sensor/RingBuffer.h
Normal file
49
modules/sensors/dynamic_sensor/RingBuffer.h
Normal file
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Copyright (C) 2017 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef RING_BUFFER_H_
|
||||
|
||||
#define RING_BUFFER_H_
|
||||
|
||||
#include <media/stagefright/foundation/ABase.h>
|
||||
|
||||
#include <hardware/sensors.h>
|
||||
#include <utils/threads.h>
|
||||
|
||||
namespace android {
|
||||
|
||||
class RingBuffer {
|
||||
public:
|
||||
explicit RingBuffer(size_t size);
|
||||
~RingBuffer();
|
||||
|
||||
ssize_t write(const sensors_event_t *ev, size_t size);
|
||||
ssize_t read(sensors_event_t *ev, size_t size);
|
||||
|
||||
private:
|
||||
Mutex mLock;
|
||||
Condition mNotEmptyCondition;
|
||||
|
||||
size_t mSize;
|
||||
sensors_event_t *mData;
|
||||
size_t mReadPos, mWritePos;
|
||||
|
||||
DISALLOW_EVIL_CONSTRUCTORS(RingBuffer);
|
||||
};
|
||||
|
||||
} // namespace android
|
||||
|
||||
#endif // RING_BUFFER_H_
|
40
modules/sensors/dynamic_sensor/SensorEventCallback.h
Normal file
40
modules/sensors/dynamic_sensor/SensorEventCallback.h
Normal file
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Copyright (C) 2017 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef ANDROID_SENSORHAL_DSE_SENSOR_EVENT_CALLBACK_H
|
||||
#define ANDROID_SENSORHAL_DSE_SENSOR_EVENT_CALLBACK_H
|
||||
|
||||
#include <hardware/sensors.h>
|
||||
#include <utils/RefBase.h>
|
||||
|
||||
namespace android {
|
||||
namespace SensorHalExt {
|
||||
|
||||
class BaseSensorObject;
|
||||
|
||||
// if timestamp in sensors_event_t has this value, it will be filled at dispatcher.
|
||||
constexpr int64_t TIMESTAMP_AUTO_FILL = -1;
|
||||
|
||||
class SensorEventCallback {
|
||||
public:
|
||||
virtual int submitEvent(sp<BaseSensorObject> sensor, const sensors_event_t &e) = 0;
|
||||
virtual ~SensorEventCallback() = default;
|
||||
};
|
||||
|
||||
} // namespace SensorHalExt
|
||||
} // namespace android
|
||||
|
||||
#endif // ANDROID_SENSORHAL_DSE_SENSOR_EVENT_CALLBACK_H
|
167
modules/sensors/dynamic_sensor/sensors.cpp
Normal file
167
modules/sensors/dynamic_sensor/sensors.cpp
Normal file
|
@ -0,0 +1,167 @@
|
|||
/*
|
||||
* Copyright (C) 2017 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
#include "DynamicSensorManager.h"
|
||||
#include "sensors.h"
|
||||
|
||||
#include <cutils/properties.h>
|
||||
#include <media/stagefright/foundation/ADebug.h>
|
||||
#include <utils/Log.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
using namespace android;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
SensorContext::SensorContext(const struct hw_module_t *module) {
|
||||
memset(&device, 0, sizeof(device));
|
||||
|
||||
device.common.tag = HARDWARE_DEVICE_TAG;
|
||||
device.common.version = SENSORS_DEVICE_API_VERSION_1_3;
|
||||
device.common.module = const_cast<hw_module_t *>(module);
|
||||
device.common.close = CloseWrapper;
|
||||
device.activate = ActivateWrapper;
|
||||
device.setDelay = SetDelayWrapper;
|
||||
device.poll = PollWrapper;
|
||||
device.batch = BatchWrapper;
|
||||
device.flush = FlushWrapper;
|
||||
|
||||
// initialize dynamic sensor manager
|
||||
int32_t base = property_get_int32("sensor.dynamic_sensor_hal.handle_base", kDynamicHandleBase);
|
||||
int32_t count =
|
||||
property_get_int32("sensor.dynamic_sensor_hal.handle_count", kMaxDynamicHandleCount);
|
||||
mDynamicSensorManager.reset(DynamicSensorManager::createInstance(base, count, nullptr));
|
||||
}
|
||||
|
||||
int SensorContext::close() {
|
||||
delete this;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int SensorContext::activate(int handle, int enabled) {
|
||||
return mDynamicSensorManager->activate(handle, enabled);
|
||||
}
|
||||
|
||||
int SensorContext::setDelay(int handle, int64_t delayNs) {
|
||||
return mDynamicSensorManager->setDelay(handle, delayNs);
|
||||
}
|
||||
|
||||
int SensorContext::poll(sensors_event_t *data, int count) {
|
||||
return mDynamicSensorManager->poll(data, count);
|
||||
}
|
||||
|
||||
int SensorContext::batch(
|
||||
int handle,
|
||||
int64_t sampling_period_ns,
|
||||
int64_t max_report_latency_ns) {
|
||||
return mDynamicSensorManager->batch(handle, sampling_period_ns, max_report_latency_ns);
|
||||
}
|
||||
|
||||
int SensorContext::flush(int handle) {
|
||||
return mDynamicSensorManager->flush(handle);
|
||||
}
|
||||
|
||||
// static
|
||||
int SensorContext::CloseWrapper(struct hw_device_t *dev) {
|
||||
return reinterpret_cast<SensorContext *>(dev)->close();
|
||||
}
|
||||
|
||||
// static
|
||||
int SensorContext::ActivateWrapper(
|
||||
struct sensors_poll_device_t *dev, int handle, int enabled) {
|
||||
return reinterpret_cast<SensorContext *>(dev)->activate(handle, enabled);
|
||||
}
|
||||
|
||||
// static
|
||||
int SensorContext::SetDelayWrapper(
|
||||
struct sensors_poll_device_t *dev, int handle, int64_t delayNs) {
|
||||
return reinterpret_cast<SensorContext *>(dev)->setDelay(handle, delayNs);
|
||||
}
|
||||
|
||||
// static
|
||||
int SensorContext::PollWrapper(
|
||||
struct sensors_poll_device_t *dev, sensors_event_t *data, int count) {
|
||||
return reinterpret_cast<SensorContext *>(dev)->poll(data, count);
|
||||
}
|
||||
|
||||
// static
|
||||
int SensorContext::BatchWrapper(
|
||||
struct sensors_poll_device_1 *dev,
|
||||
int handle,
|
||||
int flags,
|
||||
int64_t sampling_period_ns,
|
||||
int64_t max_report_latency_ns) {
|
||||
(void) flags;
|
||||
return reinterpret_cast<SensorContext *>(dev)->batch(
|
||||
handle, sampling_period_ns, max_report_latency_ns);
|
||||
}
|
||||
|
||||
// static
|
||||
int SensorContext::FlushWrapper(struct sensors_poll_device_1 *dev, int handle) {
|
||||
return reinterpret_cast<SensorContext *>(dev)->flush(handle);
|
||||
}
|
||||
|
||||
size_t SensorContext::getSensorList(sensor_t const **list) {
|
||||
*list = &(mDynamicSensorManager->getDynamicMetaSensor());
|
||||
return 1;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
static sensor_t const *sensor_list;
|
||||
|
||||
static int open_sensors(
|
||||
const struct hw_module_t *module,
|
||||
const char *,
|
||||
struct hw_device_t **dev) {
|
||||
SensorContext *ctx = new SensorContext(module);
|
||||
ctx->getSensorList(&sensor_list);
|
||||
*dev = &ctx->device.common;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct hw_module_methods_t sensors_module_methods = {
|
||||
.open = open_sensors
|
||||
};
|
||||
|
||||
static int get_sensors_list(
|
||||
struct sensors_module_t *,
|
||||
struct sensor_t const **list) {
|
||||
*list = sensor_list;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int set_operation_mode(unsigned int mode) {
|
||||
return (mode) ? -EINVAL : 0;
|
||||
}
|
||||
|
||||
struct sensors_module_t HAL_MODULE_INFO_SYM = {
|
||||
.common = {
|
||||
.tag = HARDWARE_MODULE_TAG,
|
||||
.version_major = 1,
|
||||
.version_minor = 0,
|
||||
.id = SENSORS_HARDWARE_MODULE_ID,
|
||||
.name = "Google Dynamic Sensor Manager",
|
||||
.author = "Google",
|
||||
.methods = &sensors_module_methods,
|
||||
.dso = NULL,
|
||||
.reserved = {0},
|
||||
},
|
||||
.get_sensors_list = get_sensors_list,
|
||||
.set_operation_mode = set_operation_mode,
|
||||
};
|
88
modules/sensors/dynamic_sensor/sensors.h
Normal file
88
modules/sensors/dynamic_sensor/sensors.h
Normal file
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef SENSORS_H_
|
||||
#define SENSORS_H_
|
||||
|
||||
#include <hardware/hardware.h>
|
||||
#include <hardware/sensors.h>
|
||||
#include <media/stagefright/foundation/ABase.h>
|
||||
#include <utils/RefBase.h>
|
||||
|
||||
#include <memory>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
namespace android {
|
||||
namespace SensorHalExt {
|
||||
class DynamicSensorManager;
|
||||
} // namespace BaseSensorObject
|
||||
} // namespace android
|
||||
|
||||
using android::SensorHalExt::DynamicSensorManager;
|
||||
|
||||
class SensorContext {
|
||||
public:
|
||||
struct sensors_poll_device_1 device;
|
||||
|
||||
explicit SensorContext(const struct hw_module_t *module);
|
||||
|
||||
size_t getSensorList(sensor_t const **list);
|
||||
|
||||
private:
|
||||
|
||||
int close();
|
||||
int activate(int handle, int enabled);
|
||||
int setDelay(int handle, int64_t delayNs);
|
||||
int poll(sensors_event_t *data, int count);
|
||||
|
||||
int batch(int handle, int64_t sampling_period_ns,
|
||||
int64_t max_report_latency_ns);
|
||||
|
||||
int flush(int handle);
|
||||
|
||||
// static wrappers
|
||||
static int CloseWrapper(struct hw_device_t *dev);
|
||||
|
||||
static int ActivateWrapper(
|
||||
struct sensors_poll_device_t *dev, int handle, int enabled);
|
||||
|
||||
static int SetDelayWrapper(
|
||||
struct sensors_poll_device_t *dev, int handle, int64_t delayNs);
|
||||
|
||||
static int PollWrapper(
|
||||
struct sensors_poll_device_t *dev, sensors_event_t *data, int count);
|
||||
|
||||
static int BatchWrapper(
|
||||
struct sensors_poll_device_1 *dev,
|
||||
int handle,
|
||||
int flags,
|
||||
int64_t sampling_period_ns,
|
||||
int64_t max_report_latency_ns);
|
||||
|
||||
static int FlushWrapper(struct sensors_poll_device_1 *dev, int handle);
|
||||
|
||||
// default ~16 million handles for dynamic sensor use, can be overriden by system property
|
||||
static constexpr int32_t kDynamicHandleBase = 0x10000;
|
||||
static constexpr int32_t kDynamicHandleEnd = 0x1000000;
|
||||
static constexpr int32_t kMaxDynamicHandleCount = kDynamicHandleEnd - kDynamicHandleBase;
|
||||
|
||||
std::unique_ptr<DynamicSensorManager> mDynamicSensorManager;
|
||||
|
||||
DISALLOW_EVIL_CONSTRUCTORS(SensorContext);
|
||||
};
|
||||
|
||||
#endif // SENSORS_H_
|
Loading…
Reference in a new issue