diff options
author | Peng Xu <pengxu@google.com> | 2017-04-04 01:31:54 -0700 |
---|---|---|
committer | Peng Xu <pengxu@google.com> | 2017-04-20 23:23:53 +0000 |
commit | 4715d1734d2c60cc91ef70d409678d8eda382fde (patch) | |
tree | 4a7968a6472431119e43f4ad0ad9520fee541c44 /modules/sensors/dynamic_sensor/HidRawSensor.cpp | |
parent | c87b15a2245b6fac68bd2e259cb0f4b4efbcf045 (diff) |
HID dynamic sensor: add support to three types of sensor
Implemented HID dynamic sensor using linux hidraw driver and
HidUtils. Support the following cases:
1) HID Sensor spec defined accelerometer, gyroscope, magnetometer
and orientation (quaternion).
2) Android defined type sensors (HID sensor custom type): ambient
temperature, barometer, light and heart rate sensor.
3) Android custom type sensor (based on HID sensor custom type).
Test: run tests/examples in test/
Test: test with a USB HID hardware (launchpad sensor) and marlin
Bug: 37482951
Bug: 31026607
Change-Id: I9d679fb34d15324a9df1cf19647ea638fd1a0e68
Diffstat (limited to 'modules/sensors/dynamic_sensor/HidRawSensor.cpp')
-rw-r--r-- | modules/sensors/dynamic_sensor/HidRawSensor.cpp | 1044 |
1 files changed, 1044 insertions, 0 deletions
diff --git a/modules/sensors/dynamic_sensor/HidRawSensor.cpp b/modules/sensors/dynamic_sensor/HidRawSensor.cpp new file mode 100644 index 00000000..a64d7a68 --- /dev/null +++ b/modules/sensors/dynamic_sensor/HidRawSensor.cpp @@ -0,0 +1,1044 @@ +/* + * 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 "HidRawSensor.h" +#include "HidSensorDef.h" + +#include <utils/Errors.h> +#include "HidLog.h" + +#include <algorithm> +#include <cfloat> +#include <codecvt> +#include <iomanip> +#include <sstream> + +namespace android { +namespace SensorHalExt { + +namespace { +const std::string CUSTOM_TYPE_PREFIX("com.google.hardware.sensor.hid_dynamic."); +} + +HidRawSensor::HidRawSensor( + SP(HidDevice) device, uint32_t usage, const std::vector<HidParser::ReportPacket> &packets) + : mReportingStateId(-1), mPowerStateId(-1), mReportIntervalId(-1), mInputReportId(-1), + mEnabled(false), mSamplingPeriod(1000ll*1000*1000), mBatchingPeriod(0), + mDevice(device), mValid(false) { + if (device == nullptr) { + return; + } + memset(&mSensor, 0, sizeof(mSensor)); + + const HidDevice::HidDeviceInfo &info = device->getDeviceInfo(); + initFeatureValueFromHidDeviceInfo(&mFeatureInfo, info); + + if (!populateFeatureValueFromFeatureReport(&mFeatureInfo, packets)) { + LOG_E << "populate feature from feature report failed" << LOG_ENDL; + return; + } + + if (!findSensorControlUsage(packets)) { + LOG_E << "finding sensor control usage failed" << LOG_ENDL; + return; + } + + // build translation table + bool translationTableValid = false; + switch (usage) { + using namespace Hid::Sensor::SensorTypeUsage; + using namespace Hid::Sensor::ReportUsage; + case ACCELEROMETER_3D: + // Hid unit default g + // Android unit m/s^2 + // 1g = 9.81 m/s^2 + mFeatureInfo.typeString = SENSOR_STRING_TYPE_ACCELEROMETER; + mFeatureInfo.type = SENSOR_TYPE_ACCELEROMETER; + mFeatureInfo.isWakeUp = false; + + translationTableValid = processTriAxisUsage(packets, + ACCELERATION_X_AXIS, + ACCELERATION_Y_AXIS, + ACCELERATION_Z_AXIS, 9.81); + break; + case GYROMETER_3D: + // Hid unit default degree/s + // Android unit rad/s + // 1 degree/s = pi/180 rad/s + mFeatureInfo.typeString = SENSOR_STRING_TYPE_GYROSCOPE; + mFeatureInfo.type = SENSOR_TYPE_GYROSCOPE; + mFeatureInfo.isWakeUp = false; + + translationTableValid = processTriAxisUsage(packets, + ANGULAR_VELOCITY_X_AXIS, + ANGULAR_VELOCITY_Y_AXIS, + ANGULAR_VELOCITY_Z_AXIS, M_PI/180); + break; + case COMPASS_3D: { + // Hid unit default mGauss + // Android unit uT + // 1uT = 0.1 nGauss + mFeatureInfo.typeString = SENSOR_STRING_TYPE_MAGNETIC_FIELD; + mFeatureInfo.type = SENSOR_TYPE_MAGNETIC_FIELD; + + if (!processTriAxisUsage(packets, + MAGNETIC_FLUX_X_AXIS, + MAGNETIC_FLUX_Y_AXIS, + MAGNETIC_FLUX_Z_AXIS, 0.1)) { + break; + } + const HidParser::ReportItem *pReportAccuracy = find(packets, + MAGNETOMETER_ACCURACY, + HidParser::REPORT_TYPE_INPUT, + mInputReportId); + + if (pReportAccuracy == nullptr) { + LOG_E << "Cannot find accuracy field in usage " + << std::hex << usage << std::dec << LOG_ENDL; + break; + } + if (!pReportAccuracy->isByteAligned()) { + LOG_E << "Accuracy field must align to byte" << LOG_ENDL; + break; + } + if (pReportAccuracy->minRaw != 0 || pReportAccuracy->maxRaw != 2) { + LOG_E << "Accuracy field value range must be [0, 2]" << LOG_ENDL; + break; + } + ReportTranslateRecord accuracyRecord = { + .type = TYPE_ACCURACY, + .maxValue = 2, + .minValue = 0, + .byteOffset = pReportAccuracy->bitOffset / 8, + .byteSize = pReportAccuracy->bitSize / 8, + .a = 1, + .b = 1}; + mTranslateTable.push_back(accuracyRecord); + translationTableValid = true; + break; + } + case DEVICE_ORIENTATION: + translationTableValid = processQuaternionUsage(packets); + break; + case CUSTOM: { + if (!mFeatureInfo.isAndroidCustom) { + LOG_E << "Invalid android custom sensor" << LOG_ENDL; + break; + } + const HidParser::ReportPacket *pPacket = nullptr; + const uint32_t usages[] = { + CUSTOM_VALUE_1, CUSTOM_VALUE_2, CUSTOM_VALUE_3, + CUSTOM_VALUE_4, CUSTOM_VALUE_5, CUSTOM_VALUE_6 + }; + for (const auto &packet : packets) { + if (packet.type == HidParser::REPORT_TYPE_INPUT && std::any_of( + packet.reports.begin(), packet.reports.end(), + [&usages] (const HidParser::ReportItem &d) { + return std::find(std::begin(usages), std::end(usages), d.usage) + != std::end(usages); + })) { + pPacket = &packet; + break; + } + } + + if (pPacket == nullptr) { + LOG_E << "Cannot find CUSTOM_VALUE_X in custom sensor" << LOG_ENDL; + break; + } + + double range = 0; + double resolution = 1; + + for (const auto &digest : pPacket->reports) { + if (digest.minRaw >= digest.maxRaw) { + LOG_E << "Custome usage " << digest.usage << ", min must < max" << LOG_ENDL; + return; + } + + if (!digest.isByteAligned() + || (digest.bitSize != 8 && digest.bitSize != 16 && digest.bitSize != 32)) { + LOG_E << "Custome usage " << std::hex << digest.usage << std::hex + << ", each input must be 8/16/32 bits and must align to byte boundary" + << LOG_ENDL; + return; + } + + ReportTranslateRecord record = { + .minValue = digest.minRaw, + .maxValue = digest.maxRaw, + .byteOffset = digest.bitOffset / 8, + .byteSize = digest.bitSize / 8, + .a = digest.a, + .b = digest.b, + .type = TYPE_FLOAT + }; + // keep track of range and resolution + range = std::max(std::max(std::abs((digest.maxRaw + digest.b) * digest.a), + std::abs((digest.minRaw + digest.b) * digest.a)), + range); + resolution = std::min(digest.a, resolution); + + for (size_t i = 0; i < digest.count; ++i) { + if (mTranslateTable.size() == 16) { + LOG_I << "Custom usage has more than 16 inputs, ignore the rest" << LOG_ENDL; + break; + } + record.index = mTranslateTable.size(); + mTranslateTable.push_back(record); + record.byteOffset += digest.bitSize / 8; + } + if (mTranslateTable.size() == 16) { + break; + } + } + mFeatureInfo.maxRange = range; + mFeatureInfo.resolution = resolution; + mInputReportId = pPacket->id; + translationTableValid = !mTranslateTable.empty(); + break; + } + default: + LOG_I << "unsupported sensor usage " << usage << LOG_ENDL; + } + + bool sensorValid = validateFeatureValueAndBuildSensor(); + mValid = translationTableValid && sensorValid; + LOG_V << "HidRawSensor init, translationTableValid: " << translationTableValid + << ", sensorValid: " << sensorValid << LOG_ENDL; +} + +bool HidRawSensor::processQuaternionUsage(const std::vector<HidParser::ReportPacket> &packets) { + const HidParser::ReportItem *pReportQuaternion + = find(packets, + Hid::Sensor::ReportUsage::ORIENTATION_QUATERNION, + HidParser::REPORT_TYPE_INPUT); + + if (pReportQuaternion == nullptr) { + return false; + } + + const HidParser::ReportItem &quat = *pReportQuaternion; + if ((quat.bitSize != 16 && quat.bitSize != 32) || !quat.isByteAligned()) { + LOG_E << "Quaternion usage input must be 16 or 32 bits and aligned at byte boundary" << LOG_ENDL; + return false; + } + + double min, max; + quat.decode(quat.mask(quat.minRaw), &min); + quat.decode(quat.mask(quat.maxRaw), &max); + if (quat.count != 4 || min > -1 || max < 1) { + LOG_E << "Quaternion usage need 4 inputs with range [-1, 1]" << LOG_ENDL; + return false; + } + + if (quat.minRaw > quat.maxRaw) { + LOG_E << "Quaternion usage min must <= max" << LOG_ENDL; + return false; + } + + ReportTranslateRecord record = { + .minValue = quat.minRaw, + .maxValue = quat.maxRaw, + .byteOffset = quat.bitOffset / 8, + .byteSize = quat.bitSize / 8, + .b = quat.b, + .type = TYPE_FLOAT, + }; + + // Android X Y Z maps to HID X -Z Y + // Android order xyzw, HID order wxyz + // X + record.index = 0; + record.a = quat.a; + record.byteOffset = (quat.bitOffset + quat.bitSize) / 8; + mTranslateTable.push_back(record); + // Y + record.index = 1; + record.a = -quat.a; + record.byteOffset = (quat.bitOffset + 3 * quat.bitSize) / 8; + mTranslateTable.push_back(record); + // Z + record.index = 2; + record.a = quat.a; + record.byteOffset = (quat.bitOffset + 2 * quat.bitSize) / 8; + mTranslateTable.push_back(record); + // W + record.index = 3; + record.a = quat.a; + record.byteOffset = quat.bitOffset / 8; + mTranslateTable.push_back(record); + + mFeatureInfo.typeString = SENSOR_STRING_TYPE_ROTATION_VECTOR; + mFeatureInfo.type = SENSOR_TYPE_ROTATION_VECTOR; + mFeatureInfo.maxRange = 1; + mFeatureInfo.resolution = quat.a; + mFeatureInfo.reportModeFlag = SENSOR_FLAG_CONTINUOUS_MODE; + + mInputReportId = quat.id; + + return true; +} + +bool HidRawSensor::processTriAxisUsage(const std::vector<HidParser::ReportPacket> &packets, + uint32_t usageX, uint32_t usageY, uint32_t usageZ, double defaultScaling) { + const HidParser::ReportItem *pReportX = find(packets, usageX, HidParser::REPORT_TYPE_INPUT); + const HidParser::ReportItem *pReportY = find(packets, usageY, HidParser::REPORT_TYPE_INPUT); + const HidParser::ReportItem *pReportZ = find(packets, usageZ, HidParser::REPORT_TYPE_INPUT); + + if (pReportX == nullptr || pReportY == nullptr|| pReportZ == nullptr) { + LOG_E << "Three axis sensor does not find all 3 axis" << LOG_ENDL; + return false; + } + + const HidParser::ReportItem &reportX = *pReportX; + const HidParser::ReportItem &reportY = *pReportY; + const HidParser::ReportItem &reportZ = *pReportZ; + if (reportX.id != reportY.id || reportY.id != reportZ.id) { + LOG_E << "All 3 axis should be in the same report" << LOG_ENDL; + return false; + } + if (reportX.minRaw >= reportX.maxRaw + || reportX.minRaw != reportY.minRaw + || reportX.maxRaw != reportY.maxRaw + || reportY.minRaw != reportZ.minRaw + || reportY.maxRaw != reportZ.maxRaw) { + LOG_E << "All 3 axis should have same min and max value and min must < max" << LOG_ENDL; + return false; + } + if (reportX.a != reportY.a || reportY.a != reportY.a) { + LOG_E << "All 3 axis should have same resolution" << LOG_ENDL; + return false; + } + if (reportX.count != 1 || reportY.count != 1 || reportZ.count != 1 + || (reportX.bitSize != 16 && reportX.bitSize != 32) + || reportX.bitSize != reportY.bitSize || reportY.bitSize != reportZ.bitSize + || !reportX.isByteAligned() + || !reportY.isByteAligned() + || !reportZ.isByteAligned() ) { + LOG_E << "All 3 axis should have count == 1, same size == 16 or 32 " + "and align at byte boundary" << LOG_ENDL; + return false; + } + + if (reportX.unit != 0 || reportY.unit != 0 || reportZ.unit != 0) { + LOG_E << "Specified unit for usage is not supported" << LOG_ENDL; + return false; + } + + if (reportX.a != reportY.a || reportY.a != reportZ.a + || reportX.b != reportY.b || reportY.b != reportZ.b) { + LOG_W << "Scaling for 3 axis are different. It is recommended to keep them the same" << LOG_ENDL; + } + + // set features + mFeatureInfo.maxRange = std::max( + std::abs((reportX.maxRaw + reportX.b) * reportX.a), + std::abs((reportX.minRaw + reportX.b) * reportX.a)); + mFeatureInfo.resolution = reportX.a * defaultScaling; + mFeatureInfo.reportModeFlag = SENSOR_FLAG_CONTINUOUS_MODE; + + ReportTranslateRecord record = { + .minValue = reportX.minRaw, + .maxValue = reportX.maxRaw, + .byteSize = reportX.bitSize / 8, + .type = TYPE_FLOAT + }; + + // Reorder and swap axis + // + // HID class devices are encouraged, where possible, to use a right-handed + // coordinate system. If a user is facing a device, report values should increase as + // controls are moved from left to right (X), from far to near (Y) and from high to + // low (Z). + // + + // Android X axis = Hid X axis + record.index = 0; + record.a = reportX.a * defaultScaling; + record.b = reportX.b; + record.byteOffset = reportX.bitOffset / 8; + mTranslateTable.push_back(record); + + // Android Y axis = - Hid Z axis + record.index = 1; + record.a = -reportZ.a * defaultScaling; + record.b = reportZ.b; + record.byteOffset = reportZ.bitOffset / 8; + mTranslateTable.push_back(record); + + // Android Z axis = Hid Y axis + record.index = 2; + record.a = reportY.a * defaultScaling; + record.b = reportY.b; + record.byteOffset = reportY.bitOffset / 8; + mTranslateTable.push_back(record); + + mInputReportId = reportX.id; + return true; +} + +const HidParser::ReportItem *HidRawSensor::find( + const std::vector<HidParser::ReportPacket> &packets, + unsigned int usage, int type, int id) { + for (const auto &packet : packets) { + if (packet.type != type) { + continue; + } + auto i = std::find_if( + packet.reports.begin(), packet.reports.end(), + [usage, id](const HidParser::ReportItem &p) { + return p.usage == usage + && (id == -1 || p.id == static_cast<unsigned int>(id)); + }); + if (i != packet.reports.end()) { + return &(*i); + } + } + return nullptr; +}; + +void HidRawSensor::initFeatureValueFromHidDeviceInfo( + FeatureValue *featureValue, const HidDevice::HidDeviceInfo &info) { + featureValue->name = info.name; + + std::ostringstream ss; + ss << info.busType << " " + << std::hex << std::setfill('0') << std::setw(4) << info.vendorId + << ":" << std::setw(4) << info.productId; + featureValue->vendor = ss.str(); + + featureValue->permission = ""; + featureValue->typeString = ""; + featureValue->type = -1; // invalid type + featureValue->version = 1; + + featureValue->maxRange = -1.f; + featureValue->resolution = FLT_MAX; + featureValue->power = 1.f; // default value, does not have a valid source yet + + featureValue->minDelay = 0; + featureValue->maxDelay = 0; + + featureValue->fifoSize = 0; + featureValue->fifoMaxSize = 0; + + featureValue->reportModeFlag = SENSOR_FLAG_SPECIAL_REPORTING_MODE; + featureValue->isWakeUp = false; + memset(featureValue->uuid, 0, sizeof(featureValue->uuid)); + featureValue->isAndroidCustom = false; +} + +bool HidRawSensor::populateFeatureValueFromFeatureReport( + FeatureValue *featureValue, const std::vector<HidParser::ReportPacket> &packets) { + SP(HidDevice) device = PROMOTE(mDevice); + if (device == nullptr) { + return false; + } + + std::vector<uint8_t> buffer; + for (const auto &packet : packets) { + if (packet.type != HidParser::REPORT_TYPE_FEATURE) { + continue; + } + + if (!device->getFeature(packet.id, &buffer)) { + continue; + } + + std::string str; + using namespace Hid::Sensor::PropertyUsage; + for (const auto & r : packet.reports) { + switch (r.usage) { + case FRIENDLY_NAME: + if (!r.isByteAligned() || r.bitSize != 16 || r.count < 1) { + // invalid friendly name + break; + } + if (decodeString(r, buffer, &str) && !str.empty()) { + featureValue->name = str; + } + break; + case SENSOR_MANUFACTURER: + if (!r.isByteAligned() || r.bitSize != 16 || r.count < 1) { + // invalid manufacturer + break; + } + if (decodeString(r, buffer, &str) && !str.empty()) { + featureValue->vendor = str; + } + break; + case PERSISTENT_UNIQUE_ID: + if (!r.isByteAligned() || r.bitSize != 16 || r.count < 1) { + // invalid unique id string + break; + } + if (decodeString(r, buffer, &str) && !str.empty()) { + featureValue->uniqueId = str; + } + break; + case SENSOR_DESCRIPTION: + if (!r.isByteAligned() || r.bitSize != 16 || r.count < 1 + || (r.bitOffset / 8 + r.count * 2) > buffer.size() ) { + // invalid description + break; + } + if (decodeString(r, buffer, &str)) { + mFeatureInfo.isAndroidCustom = detectAndroidCustomSensor(str); + } + break; + default: + // do not care about others + break; + } + } + } + return true; +} + +bool HidRawSensor::validateFeatureValueAndBuildSensor() { + if (mFeatureInfo.name.empty() || mFeatureInfo.vendor.empty() || mFeatureInfo.typeString.empty() + || mFeatureInfo.type <= 0 || mFeatureInfo.maxRange <= 0 + || mFeatureInfo.resolution <= 0) { + return false; + } + + switch (mFeatureInfo.reportModeFlag) { + case SENSOR_FLAG_CONTINUOUS_MODE: + case SENSOR_FLAG_ON_CHANGE_MODE: + if (mFeatureInfo.minDelay < 0) { + return false; + } + if (mFeatureInfo.maxDelay != 0 && mFeatureInfo.maxDelay < mFeatureInfo.minDelay) { + return false; + } + break; + case SENSOR_FLAG_ONE_SHOT_MODE: + if (mFeatureInfo.minDelay != -1 && mFeatureInfo.maxDelay != 0) { + return false; + } + break; + case SENSOR_FLAG_SPECIAL_REPORTING_MODE: + if (mFeatureInfo.minDelay != -1 && mFeatureInfo.maxDelay != 0) { + return false; + } + break; + default: + break; + } + + if (mFeatureInfo.fifoMaxSize < mFeatureInfo.fifoSize) { + return false; + } + + // initialize uuid field, use name, vendor and uniqueId + if (mFeatureInfo.name.size() >= 4 + && mFeatureInfo.vendor.size() >= 4 + && mFeatureInfo.typeString.size() >= 4 + && mFeatureInfo.uniqueId.size() >= 4) { + uint32_t tmp[4], h; + std::hash<std::string> stringHash; + h = stringHash(mFeatureInfo.uniqueId); + tmp[0] = stringHash(mFeatureInfo.name) ^ h; + tmp[1] = stringHash(mFeatureInfo.vendor) ^ h; + tmp[2] = stringHash(mFeatureInfo.typeString) ^ h; + tmp[3] = tmp[0] ^ tmp[1] ^ tmp[2]; + memcpy(mFeatureInfo.uuid, tmp, sizeof(mFeatureInfo.uuid)); + } + + mSensor = (sensor_t) { + mFeatureInfo.name.c_str(), // name + mFeatureInfo.vendor.c_str(), // vendor + mFeatureInfo.version, // version + -1, // handle, dummy number here + mFeatureInfo.type, + mFeatureInfo.maxRange, // maxRange + mFeatureInfo.resolution, // resolution + mFeatureInfo.power, // power + mFeatureInfo.minDelay, // minDelay + (uint32_t)mFeatureInfo.fifoSize, // fifoReservedEventCount + (uint32_t)mFeatureInfo.fifoMaxSize, // fifoMaxEventCount + mFeatureInfo.typeString.c_str(), // type string + mFeatureInfo.permission.c_str(), // requiredPermission + (long)mFeatureInfo.maxDelay, // maxDelay + mFeatureInfo.reportModeFlag | (mFeatureInfo.isWakeUp ? 1 : 0), + { NULL, NULL } + }; + return true; +} + +bool HidRawSensor::decodeString( + const HidParser::ReportItem &report, const std::vector<uint8_t> &buffer, std::string *d) { + if (!report.isByteAligned() || report.bitSize != 16 || report.count < 1) { + return false; + } + + size_t offset = report.bitOffset / 8; + if (offset + report.count * 2 > buffer.size()) { + return false; + } + + std::vector<uint16_t> data(report.count); + auto i = data.begin(); + auto j = buffer.begin() + offset; + for ( ; i != data.end(); ++i, j += sizeof(uint16_t)) { + // hid specified little endian + *i = *j + (*(j + 1) << 8); + } + std::wstring wstr(data.begin(), data.end()); + + std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t> converter; + *d = converter.to_bytes(wstr); + return true; +} + +std::vector<std::string> split(const std::string &text, char sep) { + std::vector<std::string> tokens; + size_t start = 0, end = 0; + while ((end = text.find(sep, start)) != std::string::npos) { + if (end != start) { + tokens.push_back(text.substr(start, end - start)); + } + start = end + 1; + } + if (end != start) { + tokens.push_back(text.substr(start)); + } + return tokens; +} + +bool HidRawSensor::detectAndroidCustomSensor(const std::string &description) { + size_t nullPosition = description.find('\0'); + if (nullPosition == std::string::npos) { + return false; + } + const std::string prefix("#ANDROID#"); + if (description.find(prefix, nullPosition + 1) != nullPosition + 1) { + return false; + } + + std::string str(description.c_str() + nullPosition + 1 + prefix.size()); + + // Format for predefined sensor types: + // #ANDROID#nn,[C|X|T|S],[B|0],[W|N] + // Format for vendor type sensor + // #ANDROID#xxx.yyy.zzz,[C|X|T|S],[B|0],[W|N] + // + // C: continuous + // X: on-change + // T: one-shot + // S: special trigger + // + // B: body permission + // 0: no permission required + std::vector<std::string> segments; + size_t start = 0, end = 0; + while ((end = str.find(',', start)) != std::string::npos) { + if (end != start) { + segments.push_back(str.substr(start, end - start)); + } + start = end + 1; + } + if (end != start) { + segments.push_back(str.substr(start)); + } + + if (segments.size() < 4) { + LOG_E << "Not enough segments in android custom description" << LOG_ENDL; + return false; + } + + // type + bool typeParsed = false; + if (!segments[0].empty()) { + if (::isdigit(segments[0][0])) { + int type = ::atoi(segments[0].c_str()); + // all supported types here + switch (type) { + case SENSOR_TYPE_HEART_RATE: + mFeatureInfo.type = SENSOR_TYPE_HEART_RATE; + mFeatureInfo.typeString = SENSOR_STRING_TYPE_HEART_RATE; + typeParsed = true; + break; + case SENSOR_TYPE_AMBIENT_TEMPERATURE: + mFeatureInfo.type = SENSOR_TYPE_AMBIENT_TEMPERATURE; + mFeatureInfo.typeString = SENSOR_STRING_TYPE_AMBIENT_TEMPERATURE; + typeParsed = true; + case SENSOR_TYPE_LIGHT: + mFeatureInfo.type = SENSOR_TYPE_LIGHT; + mFeatureInfo.typeString = SENSOR_STRING_TYPE_LIGHT; + typeParsed = true; + break; + case SENSOR_TYPE_PRESSURE: + mFeatureInfo.type = SENSOR_TYPE_PRESSURE; + mFeatureInfo.typeString = SENSOR_STRING_TYPE_PRESSURE; + typeParsed = true; + break; + default: + LOG_W << "Android type " << type << " has not been supported yet" << LOG_ENDL; + break; + } + } else { + // assume a xxx.yyy.zzz format + std::ostringstream s; + bool lastIsDot = true; + for (auto c : segments[0]) { + if (::isalpha(c)) { + s << static_cast<char>(c); + lastIsDot = false; + } else if (!lastIsDot && c == '.') { + s << static_cast<char>(c); + lastIsDot = true; + } else { + break; + } + } + if (s.str() == segments[0]) { + mFeatureInfo.type = SENSOR_TYPE_DEVICE_PRIVATE_BASE; + mFeatureInfo.typeString = CUSTOM_TYPE_PREFIX + s.str(); + typeParsed = true; + } + } + } + + // reporting type + bool reportingModeParsed = false; + if (segments[1].size() == 1) { + switch (segments[1][0]) { + case 'C': + mFeatureInfo.reportModeFlag = SENSOR_FLAG_CONTINUOUS_MODE; + reportingModeParsed = true; + break; + case 'X': + mFeatureInfo.reportModeFlag = SENSOR_FLAG_ON_CHANGE_MODE; + reportingModeParsed = true; + break; + case 'T': + mFeatureInfo.reportModeFlag = SENSOR_FLAG_ONE_SHOT_MODE; + reportingModeParsed = true; + break; + case 'S': + mFeatureInfo.reportModeFlag = SENSOR_FLAG_SPECIAL_REPORTING_MODE; + reportingModeParsed = true; + break; + default: + LOG_E << "Undefined reporting mode designation " << segments[1] << LOG_ENDL; + } + } + + // permission parsed + bool permissionParsed = false; + if (segments[2].size() == 1) { + switch (segments[2][0]) { + case 'B': + mFeatureInfo.permission = SENSOR_PERMISSION_BODY_SENSORS; + permissionParsed = true; + break; + case '0': + mFeatureInfo.permission = ""; + permissionParsed = true; + break; + default: + LOG_E << "Undefined permission designation " << segments[2] << LOG_ENDL; + } + } + + // wake up + bool wakeUpParsed = false; + if (segments[3].size() == 1) { + switch (segments[3][0]) { + case 'W': + mFeatureInfo.isWakeUp = true; + wakeUpParsed = true; + break; + case 'N': + mFeatureInfo.isWakeUp = false; + wakeUpParsed = true; + break; + default: + LOG_E << "Undefined wake up designation " << segments[3] << LOG_ENDL; + } + } + + int ret = typeParsed && reportingModeParsed && permissionParsed && wakeUpParsed; + if (!ret) { + LOG_D << "detectAndroidCustomSensor typeParsed: " << typeParsed + << " reportingModeParsed: " << reportingModeParsed + << " permissionParsed: " << permissionParsed + << " wakeUpParsed: " << wakeUpParsed << LOG_ENDL; + } + return ret; +} + +bool HidRawSensor::findSensorControlUsage(const std::vector<HidParser::ReportPacket> &packets) { + using namespace Hid::Sensor::PropertyUsage; + using namespace Hid::Sensor::RawMinMax; + + //REPORTING_STATE + const HidParser::ReportItem *reportingState + = find(packets, REPORTING_STATE, HidParser::REPORT_TYPE_FEATURE); + + if (reportingState == nullptr + || !reportingState->isByteAligned() + || reportingState->bitSize != 8 + || reportingState->minRaw != REPORTING_STATE_MIN + || reportingState->maxRaw != REPORTING_STATE_MAX) { + LOG_W << "Cannot find valid reporting state feature" << LOG_ENDL; + } else { + mReportingStateId = reportingState->id; + mReportingStateOffset = reportingState->bitOffset / 8; + } + + //POWER_STATE + const HidParser::ReportItem *powerState + = find(packets, POWER_STATE, HidParser::REPORT_TYPE_FEATURE); + if (powerState == nullptr + || !powerState->isByteAligned() + || powerState->bitSize != 8 + || powerState->minRaw != POWER_STATE_MIN + || powerState->maxRaw != POWER_STATE_MAX) { + LOG_W << "Cannot find valid power state feature" << LOG_ENDL; + } else { + mPowerStateId = powerState->id; + mPowerStateOffset = powerState->bitOffset / 8; + } + + //REPORT_INTERVAL + const HidParser::ReportItem *reportInterval + = find(packets, REPORT_INTERVAL, HidParser::REPORT_TYPE_FEATURE); + if (reportInterval == nullptr + || !reportInterval->isByteAligned() + || reportInterval->minRaw < 0 + || (reportInterval->bitSize != 16 && reportInterval->bitSize != 32)) { + LOG_W << "Cannot find valid report interval feature" << LOG_ENDL; + } else { + mReportIntervalId = reportInterval->id; + mReportIntervalOffset = reportInterval->bitOffset / 8; + mReportIntervalSize = reportInterval->bitSize / 8; + + mFeatureInfo.minDelay = std::max(static_cast<int64_t>(1), reportInterval->minRaw) * 1000; + mFeatureInfo.maxDelay = std::min(static_cast<int64_t>(1000000), + reportInterval->maxRaw) * 1000; // maximum 1000 second + } + return true; + return (mPowerStateId >= 0 || mReportingStateId >= 0) && mReportIntervalId >= 0; +} + +const sensor_t* HidRawSensor::getSensor() const { + return &mSensor; +} + +void HidRawSensor::getUuid(uint8_t* uuid) const { + memcpy(uuid, mFeatureInfo.uuid, sizeof(mFeatureInfo.uuid)); +} + +int HidRawSensor::enable(bool enable) { + using namespace Hid::Sensor::StateValue; + SP(HidDevice) device = PROMOTE(mDevice); + + if (device == nullptr) { + return NO_INIT; + } + + if (enable == mEnabled) { + return NO_ERROR; + } + + std::vector<uint8_t> buffer; + bool setPowerOk = true; + if (mPowerStateId >= 0) { + setPowerOk = false; + uint8_t id = static_cast<uint8_t>(mPowerStateId); + if (device->getFeature(id, &buffer) + && buffer.size() > mPowerStateOffset) { + buffer[mPowerStateOffset] = enable ? POWER_STATE_FULL_POWER : POWER_STATE_POWER_OFF; + setPowerOk = device->setFeature(id, buffer); + } else { + LOG_E << "enable: changing POWER STATE failed" << LOG_ENDL; + } + } + + bool setReportingOk = true; + if (mReportingStateId >= 0) { + setReportingOk = false; + uint8_t id = static_cast<uint8_t>(mReportingStateId); + if (device->getFeature(id, &buffer) + && buffer.size() > mReportingStateOffset) { + buffer[mReportingStateOffset] + = enable ? REPORTING_STATE_ALL_EVENT : REPORTING_STATE_NO_EVENT; + setReportingOk = device->setFeature(id, buffer); + } else { + LOG_E << "enable: changing REPORTING STATE failed" << LOG_ENDL; + } + } + + if (setPowerOk && setReportingOk) { + mEnabled = enable; + return NO_ERROR; + } else { + return INVALID_OPERATION; + } +} + +int HidRawSensor::batch(int64_t samplingPeriod, int64_t batchingPeriod) { + SP(HidDevice) device = PROMOTE(mDevice); + if (device == nullptr) { + return NO_INIT; + } + + if (samplingPeriod < 0 || batchingPeriod < 0) { + return BAD_VALUE; + } + + bool needRefresh = mSamplingPeriod != samplingPeriod || mBatchingPeriod != batchingPeriod; + std::vector<uint8_t> buffer; + + bool ok = true; + if (needRefresh && mReportIntervalId >= 0) { + ok = false; + uint8_t id = static_cast<uint8_t>(mReportIntervalId); + if (device->getFeature(id, &buffer) + && buffer.size() >= mReportIntervalOffset + mReportIntervalSize) { + int64_t periodMs = samplingPeriod / 1000000; //ns -> ms + switch (mReportIntervalSize) { + case sizeof(uint16_t): + periodMs = std::min(periodMs, static_cast<int64_t>(UINT16_MAX)); + buffer[mReportIntervalOffset] = periodMs & 0xFF; + buffer[mReportIntervalOffset + 1] = (periodMs >> 8) & 0xFF; + case sizeof(uint32_t): + periodMs = std::min(periodMs, static_cast<int64_t>(UINT32_MAX)); + buffer[mReportIntervalOffset] = periodMs & 0xFF; + buffer[mReportIntervalOffset + 1] = (periodMs >> 8) & 0xFF; + buffer[mReportIntervalOffset + 2] = (periodMs >> 16) & 0xFF; + buffer[mReportIntervalOffset + 3] = (periodMs >> 24) & 0xFF; + } + ok = device->setFeature(id, buffer); + } + } + + if (ok) { + mSamplingPeriod = samplingPeriod; + mBatchingPeriod = batchingPeriod; + return NO_ERROR; + } else { + return INVALID_OPERATION; + } +} + +void HidRawSensor::handleInput(uint8_t id, const std::vector<uint8_t> &message) { + if (id != mInputReportId || mEnabled == false) { + return; + } + sensors_event_t event = { + .version = sizeof(event), + .sensor = -1, + .type = mSensor.type + }; + bool valid = true; + for (const auto &rec : mTranslateTable) { + int64_t v = (message[rec.byteOffset + rec.byteSize - 1] & 0x80) ? -1 : 0; + for (int i = static_cast<int>(rec.byteSize) - 1; i >= 0; --i) { + v = (v << 8) | message[rec.byteOffset + i]; // HID is little endian + } + + switch (rec.type) { + case TYPE_FLOAT: + if (v > rec.maxValue || v < rec.minValue) { + valid = false; + } + event.data[rec.index] = rec.a * (v + rec.b); + break; + case TYPE_INT64: + if (v > rec.maxValue || v < rec.minValue) { + valid = false; + } + event.u64.data[rec.index] = v + rec.b; + break; + case TYPE_ACCURACY: + event.magnetic.status = (v & 0xFF) + rec.b; + break; + } + } + if (!valid) { + LOG_V << "Range error observed in decoding, discard" << LOG_ENDL; + } + event.timestamp = -1; + generateEvent(event); +} + +std::string HidRawSensor::dump() const { + std::ostringstream ss; + ss << "Feature Values " << LOG_ENDL + << " name: " << mFeatureInfo.name << LOG_ENDL + << " vendor: " << mFeatureInfo.vendor << LOG_ENDL + << " permission: " << mFeatureInfo.permission << LOG_ENDL + << " typeString: " << mFeatureInfo.typeString << LOG_ENDL + << " type: " << mFeatureInfo.type << LOG_ENDL + << " maxRange: " << mFeatureInfo.maxRange << LOG_ENDL + << " resolution: " << mFeatureInfo.resolution << LOG_ENDL + << " power: " << mFeatureInfo.power << LOG_ENDL + << " minDelay: " << mFeatureInfo.minDelay << LOG_ENDL + << " maxDelay: " << mFeatureInfo.maxDelay << LOG_ENDL + << " fifoSize: " << mFeatureInfo.fifoSize << LOG_ENDL + << " fifoMaxSize: " << mFeatureInfo.fifoMaxSize << LOG_ENDL + << " reportModeFlag: " << mFeatureInfo.reportModeFlag << LOG_ENDL + << " isWakeUp: " << (mFeatureInfo.isWakeUp ? "true" : "false") << LOG_ENDL + << " uniqueId: " << mFeatureInfo.uniqueId << LOG_ENDL + << " uuid: "; + + ss << std::hex << std::setfill('0'); + for (auto d : mFeatureInfo.uuid) { + ss << std::setw(2) << static_cast<int>(d) << " "; + } + ss << std::dec << std::setfill(' ') << LOG_ENDL; + + ss << "Input report id: " << mInputReportId << LOG_ENDL; + for (const auto &t : mTranslateTable) { + ss << " type, index: " << t.type << ", " << t.index + << "; min,max: " << t.minValue << ", " << t.maxValue + << "; byte-offset,size: " << t.byteOffset << ", " << t.byteSize + << "; scaling,bias: " << t.a << ", " << t.b << LOG_ENDL; + } + + ss << "Control features: " << LOG_ENDL; + ss << " Power state "; + if (mPowerStateId >= 0) { + ss << "found, id: " << mPowerStateId + << " offset: " << mPowerStateOffset << LOG_ENDL; + } else { + ss << "not found" << LOG_ENDL; + } + + ss << " Reporting state "; + if (mReportingStateId >= 0) { + ss << "found, id: " << mReportingStateId + << " offset: " << mReportingStateOffset << LOG_ENDL; + } else { + ss << "not found" << LOG_ENDL; + } + + ss << " Report interval "; + if (mReportIntervalId >= 0) { + ss << "found, id: " << mReportIntervalId + << " offset: " << mReportIntervalOffset + << " size: " << mReportIntervalSize << LOG_ENDL; + } else { + ss << "not found" << LOG_ENDL; + } + return ss.str(); +} + +} // namespace SensorHalExt +} // namespace android |