Android车载——VehicleHal运行流程(Android 11)

1 概述

本篇主要讲解VehicleHal的主要运行流程,包括设置属性、获取属性、订阅属性、取消订阅、持续上报属性订阅等。

2 获取属性流程

2.1 获取属性流程源码分析

作为服务注册到hwServiceManager中的类是VehicleHalManager,所以,CarService对服务端的调用的hidl接口都是调用到了VehicleHalManager中。

get(VehiclePropValue requestedPropValue)generates (StatusCode status, VehiclePropValue propValue);

IVehicle这个hidl接口中的定义如上

Return<void> VehicleHalManager::get(const VehiclePropValue& requestedPropValue, get_cb _hidl_cb) {const auto* config = getPropConfigOrNull(requestedPropValue.prop);if (config == nullptr) {ALOGE("Failed to get value: config not found, property: 0x%x",requestedPropValue.prop);_hidl_cb(StatusCode::INVALID_ARG, kEmptyValue);return Void();}if (!checkReadPermission(*config)) {_hidl_cb(StatusCode::ACCESS_DENIED, kEmptyValue);return Void();}StatusCode status;auto value = mHal->get(requestedPropValue, &status);_hidl_cb(status, value.get() ? *value : kEmptyValue);return Void();
}

调用之后会调用到VehicleHalManager中的get函数

const VehiclePropConfig* VehicleHalManager::getPropConfigOrNull(int32_t prop) const {return mConfigIndex->hasConfig(prop)? &mConfigIndex->getConfig(prop) : nullptr;
}

首先,会从属性配置列表中是否存在这个属性,这个是初始化的时候缓存的,缓存的是所有的属性配置。
如果获取的属性不在属性配置列表中,则不能够获取,如果存在,会判断访问权限,访问权限校验通过之后,会调用mHal的get函数。

VehicleHal::VehiclePropValuePtr EmulatedVehicleHal::get(const VehiclePropValue& requestedPropValue, StatusCode* outStatus) {auto propId = requestedPropValue.prop;ALOGV("get(%d)", propId);auto& pool = *getValuePool();VehiclePropValuePtr v = nullptr;switch (propId) {case OBD2_FREEZE_FRAME:v = pool.obtainComplex();*outStatus = fillObd2FreezeFrame(requestedPropValue, v.get());break;case OBD2_FREEZE_FRAME_INFO:v = pool.obtainComplex();*outStatus = fillObd2DtcInfo(v.get());break;default:if (mEmulatedUserHal != nullptr && mEmulatedUserHal->isSupported(propId)) {ALOGI("get(): getting value for prop %d from User HAL", propId);const auto& ret = mEmulatedUserHal->onGetProperty(requestedPropValue);if (!ret.ok()) {ALOGE("get(): User HAL returned error: %s", ret.error().message().c_str());*outStatus = StatusCode(ret.error().code());} else {auto value = ret.value().get();if (value != nullptr) {ALOGI("get(): User HAL returned value: %s", toString(*value).c_str());v = getValuePool()->obtain(*value);*outStatus = StatusCode::OK;} else {ALOGE("get(): User HAL returned null value");*outStatus = StatusCode::INTERNAL_ERROR;}}break;}auto internalPropValue = mPropStore->readValueOrNull(requestedPropValue);if (internalPropValue != nullptr) {v = getValuePool()->obtain(*internalPropValue);}*outStatus = v != nullptr ? StatusCode::OK : StatusCode::INVALID_ARG;break;}if (v.get()) {v->timestamp = elapsedRealtimeNano();}return v;
}

首先,会对userhal的一些判断操作,这个EmulatedUserHal是跟用户身份相关的hal定义,用于处理用户切换相关事件。如果是用户hal相关的prop获取,则获取完成之后就直接跳出。如果不是,则走普通的property获取路径,从VehiclePropertyStore中读取。

using PropertyMap = std::map<RecordId, VehiclePropValue>;
PropertyMap mPropertyValues;std::unique_ptr<VehiclePropValue> VehiclePropertyStore::readValueOrNull(int32_t prop, int32_t area, int64_t token) const {RecordId recId = {prop, isGlobalProp(prop) ? 0 : area, token };MuxGuard g(mLock);const VehiclePropValue* internalValue = getValueOrNullLocked(recId);return internalValue ? std::make_unique<VehiclePropValue>(*internalValue) : nullptr;
}const VehiclePropValue* VehiclePropertyStore::getValueOrNullLocked(const VehiclePropertyStore::RecordId& recId) const  {auto it = mPropertyValues.find(recId);return it == mPropertyValues.end() ? nullptr : &it->second;
}

从mPropertyValues这个map中去获取对应propId的property。mPropertyValues里面的值是在哪填充的呢?

bool VehiclePropertyStore::writeValue(const VehiclePropValue& propValue,bool updateStatus) {MuxGuard g(mLock);if (!mConfigs.count(propValue.prop)) return false;RecordId recId = getRecordIdLocked(propValue);VehiclePropValue* valueToUpdate = const_cast<VehiclePropValue*>(getValueOrNullLocked(recId));if (valueToUpdate == nullptr) {mPropertyValues.insert({ recId, propValue });return true;}// propValue is outdated and drops it.if (valueToUpdate->timestamp > propValue.timestamp) {return false;}// update the propertyValue.// The timestamp in propertyStore should only be updated by the server side. It indicates// the time when the event is generated by the server.valueToUpdate->timestamp = propValue.timestamp;valueToUpdate->value = propValue.value;if (updateStatus) {valueToUpdate->status = propValue.status;}return true;
}

是在这个函数中,这个函数是在VHAL初始化的时候调用的,初始化的时候,会遍历一个定义了所有支持属性的列表,并调用writeValue函数将属性配置和属性值缓存到VehiclePropertyStore中。
以上就是CarService从VHAL获取属性的流程,总结来说就是:从VHAL的缓存map中获取属性。

2.2 获取属性流程图

plantuml

@startumlparticipant CarService
box
participant VehicleHalManager
participant EmulatedVehicleHal
participant VehiclePropertyStore
endboxCarService -> VehicleHalManager: get(const VehiclePropValue& \n\trequestedPropValue, get_cb _hidl_cb)
VehicleHalManager -> EmulatedVehicleHal: get(const VehiclePropValue& \n\trequestedPropValue, get_cb _hidl_cb)
EmulatedVehicleHal -> VehiclePropertyStore: readValueOrNull(int32_t prop, \n\tint32_t area, int64_t token)
VehiclePropertyStore -> EmulatedVehicleHal: propValue
EmulatedVehicleHal -> VehicleHalManager: propValue
VehicleHalManager -> CarService: _hidl_cb(propValue)@enduml

在这里插入图片描述

3 设置属性流程

3.1 设置属性流程源码分析

hidl调用后还是从VehicleHalManager开始的

Return<StatusCode> VehicleHalManager::set(const VehiclePropValue &value) {auto prop = value.prop;const auto* config = getPropConfigOrNull(prop);if (config == nullptr) {ALOGE("Failed to set value: config not found, property: 0x%x", prop);return StatusCode::INVALID_ARG;}if (!checkWritePermission(*config)) {return StatusCode::ACCESS_DENIED;}handlePropertySetEvent(value);auto status = mHal->set(value);return Return<StatusCode>(status);
}

首先判断缓存中是否有该属性的属性配置,有才支持后续的set操作
handlePropertySetEvent是对带有EVENTS_FROM_ANDROID这个订阅标签属性的处理,这种属性的设置需要直接上报给上层。
然后是调用EmulatedVehicleHal的set函数

StatusCode EmulatedVehicleHal::set(const VehiclePropValue& propValue) {constexpr bool updateStatus = false;//这里是模拟车辆属性if (propValue.prop == kGenerateFakeDataControllingProperty) {// Send the generator controlling request to the server.// 'updateStatus' flag is only for the value sent by setProperty (propValue in this case)// instead of the generated values triggered by it. 'propValue' works as a control signal// here, since we never send the control signal back, the value of 'updateStatus' flag// does not matter here.auto status = mVehicleClient->setProperty(propValue, updateStatus);return status;//处理空调相关的属性} else if (mHvacPowerProps.count(propValue.prop)) {auto hvacPowerOn = mPropStore->readValueOrNull(toInt(VehicleProperty::HVAC_POWER_ON),(VehicleAreaSeat::ROW_1_LEFT | VehicleAreaSeat::ROW_1_RIGHT |VehicleAreaSeat::ROW_2_LEFT | VehicleAreaSeat::ROW_2_CENTER |VehicleAreaSeat::ROW_2_RIGHT));if (hvacPowerOn && hvacPowerOn->value.int32Values.size() == 1&& hvacPowerOn->value.int32Values[0] == 0) {return StatusCode::NOT_AVAILABLE;}} else {// Handle property specific codeswitch (propValue.prop) {case OBD2_FREEZE_FRAME_CLEAR:return clearObd2FreezeFrames(propValue);case VEHICLE_MAP_SERVICE:// Placeholder for future implementation of VMS property in the default hal. For// now, just returns OK; otherwise, hal clients crash with property not supported.return StatusCode::OK;}}if (propValue.status != VehiclePropertyStatus::AVAILABLE) {// Android side cannot set property status - this value is the// purview of the HAL implementation to reflect the state of// its underlying hardwarereturn StatusCode::INVALID_ARG;}//读取当前值auto currentPropValue = mPropStore->readValueOrNull(propValue);if (currentPropValue == nullptr) {return StatusCode::INVALID_ARG;}if (currentPropValue->status != VehiclePropertyStatus::AVAILABLE) {// do not allow Android side to set() a disabled/error propertyreturn StatusCode::NOT_AVAILABLE;}/*** After checking all conditions, such as the property is available, a real vhal will* sent the events to Car ECU to take actions.*/// Send the value to the vehicle server, the server will talk to the (real or emulated) car//设置属性到VehicleClient,通过这个设置到模拟车辆或者实际车辆auto setValueStatus = mVehicleClient->setProperty(propValue, updateStatus);if (setValueStatus != StatusCode::OK) {return setValueStatus;}return StatusCode::OK;
}

其中mVehicleClient是初始化时传入的EmulatedVehicleConnector对象,所以调用的是EmulatedVehicleConnector的setProperty函数。

StatusCode setProperty(const VehiclePropValue& value, bool updateStatus) override {return this->onSetProperty(value, updateStatus);
}

setProperty函数在EmulatedVehicleConnector的父类IPassThroughConnector中,然后调用onSetProperty函数,这个函数在EmulatedVehicleConnector类中。

StatusCode EmulatedVehicleConnector::onSetProperty(const VehiclePropValue& value,bool updateStatus) {if (mEmulatedUserHal.isSupported(value.prop)) {LOG(INFO) << "onSetProperty(): property " << value.prop << " will be handled by UserHal";const auto& ret = mEmulatedUserHal.onSetProperty(value);if (!ret.ok()) {LOG(ERROR) << "onSetProperty(): HAL returned error: " << ret.error().message();return StatusCode(ret.error().code());}auto updatedValue = ret.value().get();if (updatedValue != nullptr) {LOG(INFO) << "onSetProperty(): updating property returned by HAL: "<< toString(*updatedValue);onPropertyValueFromCar(*updatedValue, updateStatus);}return StatusCode::OK;}return this->VehicleHalServer::onSetProperty(value, updateStatus);
}

首先处理UserHal相关的属性操作。然后调用VehicleHalServer中的onSetProperty函数。

StatusCode VehicleHalServer::onSetProperty(const VehiclePropValue& value, bool updateStatus) {LOG(DEBUG) << "onSetProperty(" << value.prop << ")";// Some properties need to be treated non-triviallyswitch (value.prop) {case kGenerateFakeDataControllingProperty:return handleGenerateFakeDataRequest(value);// set the value from vehicle side, used in end to end test.case kSetIntPropertyFromVehicleForTest: {auto updatedPropValue = createVehiclePropValue(VehiclePropertyType::INT32, 1);updatedPropValue->prop = value.value.int32Values[0];updatedPropValue->value.int32Values[0] = value.value.int32Values[1];updatedPropValue->timestamp = value.value.int64Values[0];updatedPropValue->areaId = value.areaId;onPropertyValueFromCar(*updatedPropValue, updateStatus);return StatusCode::OK;}case kSetFloatPropertyFromVehicleForTest: {auto updatedPropValue = createVehiclePropValue(VehiclePropertyType::FLOAT, 1);updatedPropValue->prop = value.value.int32Values[0];updatedPropValue->value.floatValues[0] = value.value.floatValues[0];updatedPropValue->timestamp = value.value.int64Values[0];updatedPropValue->areaId = value.areaId;onPropertyValueFromCar(*updatedPropValue, updateStatus);return StatusCode::OK;}case kSetBooleanPropertyFromVehicleForTest: {auto updatedPropValue = createVehiclePropValue(VehiclePropertyType::BOOLEAN, 1);updatedPropValue->prop = value.value.int32Values[1];updatedPropValue->value.int32Values[0] = value.value.int32Values[0];updatedPropValue->timestamp = value.value.int64Values[0];updatedPropValue->areaId = value.areaId;onPropertyValueFromCar(*updatedPropValue, updateStatus);return StatusCode::OK;}case AP_POWER_STATE_REPORT:switch (value.value.int32Values[0]) {case toInt(VehicleApPowerStateReport::DEEP_SLEEP_EXIT):case toInt(VehicleApPowerStateReport::SHUTDOWN_CANCELLED):case toInt(VehicleApPowerStateReport::WAIT_FOR_VHAL):// CPMS is in WAIT_FOR_VHAL state, simply move to ON// Send back to HAL// ALWAYS update status for generated property valueonPropertyValueFromCar(*createApPowerStateReq(VehicleApPowerStateReq::ON, 0),true /* updateStatus */);break;case toInt(VehicleApPowerStateReport::DEEP_SLEEP_ENTRY):case toInt(VehicleApPowerStateReport::SHUTDOWN_START):// CPMS is in WAIT_FOR_FINISH state, send the FINISHED command// Send back to HAL// ALWAYS update status for generated property valueonPropertyValueFromCar(*createApPowerStateReq(VehicleApPowerStateReq::FINISHED, 0),true /* updateStatus */);break;case toInt(VehicleApPowerStateReport::ON):case toInt(VehicleApPowerStateReport::SHUTDOWN_POSTPONE):case toInt(VehicleApPowerStateReport::SHUTDOWN_PREPARE):// Do nothingbreak;default:// Unknown statebreak;}break;default:break;}// In the real vhal, the value will be sent to Car ECU.// We just pretend it is done here and send back to HALauto updatedPropValue = getValuePool()->obtain(value);updatedPropValue->timestamp = elapsedRealtimeNano();onPropertyValueFromCar(*updatedPropValue, updateStatus);return StatusCode::OK;
}

上面是模拟属性设置流程,这里就相当于模拟属性设置完成了。更新属性的值和时间戳之后,调用onPropertyValueFromCar模拟属性设置成功的上报操作

3.2 设置属性流程图

plantuml

@startumlparticipant CarService
box
participant VehicleHalManager
participant SubscriptionManager
participant EmulatedVehicleHal
participant IPassThroughConnector
participant EmulatedVehicleConnector
participant VehicleHalServer
endboxCarService -> VehicleHalManager: set(const VehiclePropValue& value)
VehicleHalManager -> VehicleHalManager: handlePropertySetEvent(const VehiclePropValue& value)
VehicleHalManager -> SubscriptionManager: getSubscribedClients(int32_t propId, SubscribeFlags flags)
SubscriptionManager -> VehicleHalManager: value
alt flags == SubscribeFlags::EVENTS_FROM_ANDROIDVehicleHalManager -> CarService: onPropertySet(value)
end
VehicleHalManager -> EmulatedVehicleHal: set(const VehiclePropValue& value)
EmulatedVehicleHal -> IPassThroughConnector: setProperty(const VehiclePropValue& \n\tvalue, bool updateStatus)
IPassThroughConnector -> EmulatedVehicleConnector: onSetProperty(const VehiclePropValue& \n\tvalue, bool updateStatus)
EmulatedVehicleConnector -> VehicleHalServer: onSetProperty(const VehiclePropValue& value, bool updateStatus)@enduml

流程图:
在这里插入图片描述
这里由于没有实际车辆,没有往下设置,OEM厂商需要设置到ECU中。至于设置之后,VHAL缓存的改变则是由设置成功的通知上报之后才会写入缓存的。

4 订阅属性流程

4.1 普通订阅属性流程源码分析

订阅入口也是在VehicleHalManager中

Return<StatusCode> VehicleHalManager::subscribe(const sp<IVehicleCallback> &callback,const hidl_vec<SubscribeOptions> &options) {hidl_vec<SubscribeOptions> verifiedOptions(options);for (size_t i = 0; i < verifiedOptions.size(); i++) {SubscribeOptions& ops = verifiedOptions[i];auto prop = ops.propId;const auto* config = getPropConfigOrNull(prop);if (config == nullptr) {ALOGE("Failed to subscribe: config not found, property: 0x%x",prop);return StatusCode::INVALID_ARG;}if (ops.flags == SubscribeFlags::UNDEFINED) {ALOGE("Failed to subscribe: undefined flag in options provided");return StatusCode::INVALID_ARG;}if (!isSubscribable(*config, ops.flags)) {ALOGE("Failed to subscribe: property 0x%x is not subscribable",prop);return StatusCode::INVALID_ARG;}ops.sampleRate = checkSampleRate(*config, ops.sampleRate);}std::list<SubscribeOptions> updatedOptions;auto res = mSubscriptionManager.addOrUpdateSubscription(getClientId(callback),callback, verifiedOptions,&updatedOptions);if (StatusCode::OK != res) {ALOGW("%s failed to subscribe, error code: %d", __func__, res);return res;}for (auto opt : updatedOptions) {mHal->subscribe(opt.propId, opt.sampleRate);}return StatusCode::OK;
}

首先判断属性配置是否存在,存在才支持订阅
然后判断是否可以订阅

bool VehicleHalManager::isSubscribable(const VehiclePropConfig& config,SubscribeFlags flags) {bool isReadable = config.access & VehiclePropertyAccess::READ;if (!isReadable && (SubscribeFlags::EVENTS_FROM_CAR & flags)) {ALOGW("Cannot subscribe, property 0x%x is not readable", config.prop);return false;}if (config.changeMode == VehiclePropertyChangeMode::STATIC) {ALOGW("Cannot subscribe, property 0x%x is static", config.prop);return false;}return true;
}

判断访问权限,判断flag,如果flag是从car来的,则返回false。然后是判断属性的changeMode,如果changeMode是STATIC,表示属性不变,则不支持订阅。
然后添加订阅addOrUpdateSubscription,添加之前首先创建clientId

using ClientId = uint64_t;
ClientId VehicleHalManager::getClientId(const sp<IVehicleCallback>& callback) {//TODO(b/32172906): rework this to get some kind of unique id for callback interface when this// feature is ready in HIDL.if (callback->isRemote()) {BpHwVehicleCallback* hwCallback = static_cast<BpHwVehicleCallback*>(callback.get());return static_cast<ClientId>(reinterpret_cast<intptr_t>(hwCallback->onAsBinder()));} else {return static_cast<ClientId>(reinterpret_cast<intptr_t>(callback.get()));}
}

根据传入的回调函数的额指针来强转成ClientId这种int类型,作为客户端的唯一标识。

StatusCode SubscriptionManager::addOrUpdateSubscription(ClientId clientId,const sp<IVehicleCallback> &callback,const hidl_vec<SubscribeOptions> &optionList,std::list<SubscribeOptions>* outUpdatedSubscriptions) {outUpdatedSubscriptions->clear();MuxGuard g(mLock);ALOGI("SubscriptionManager::addOrUpdateSubscription, callback: %p", callback.get());const sp<HalClient>& client = getOrCreateHalClientLocked(clientId, callback);if (client.get() == nullptr) {return StatusCode::INTERNAL_ERROR;}for (size_t i = 0; i < optionList.size(); i++) {const SubscribeOptions& opts = optionList[i];ALOGI("SubscriptionManager::addOrUpdateSubscription, prop: 0x%x", opts.propId);client->addOrUpdateSubscription(opts);addClientToPropMapLocked(opts.propId, client);if (SubscribeFlags::EVENTS_FROM_CAR & opts.flags) {SubscribeOptions updated;if (updateHalEventSubscriptionLocked(opts, &updated)) {outUpdatedSubscriptions->push_back(updated);}}}return StatusCode::OK;
}

首先,创建客户端对象HalClient

std::map<ClientId, sp<HalClient>> mClients;sp<HalClient> SubscriptionManager::getOrCreateHalClientLocked(ClientId clientId, const sp<IVehicleCallback>& callback) {auto it = mClients.find(clientId);if (it == mClients.end()) {uint64_t cookie = reinterpret_cast<uint64_t>(clientId);ALOGI("Creating new client and linking to death recipient, cookie: 0x%" PRIx64, cookie);auto res = callback->linkToDeath(mCallbackDeathRecipient, cookie);if (!res.isOk()) {  // Client is already dead?ALOGW("%s failed to link to death, client %p, err: %s",__func__, callback.get(), res.description().c_str());return nullptr;}sp<HalClient> client = new HalClient(callback);mClients.insert({clientId, client});return client;} else {return it->second;}
}

mClients是保存订阅客户端的map,key是ClientId,value是HalClient对象。这里会先判断mClients这个map中是否存在对应的客户端,如果没有,则创建HalClient对象,并加入到这个map之中,如果有则直接返回。
然后for循环遍历所有的订阅项,由于订阅的时候,CarService传入的是SubscribeOptions的列表:

struct SubscribeOptions {/** Property to subscribe */int32_t propId;/*** Sample rate in Hz.** Must be provided for properties with* VehiclePropertyChangeMode::CONTINUOUS. The value must be within* VehiclePropConfig#minSamplingRate .. VehiclePropConfig#maxSamplingRate* for a given property.* This value indicates how many updates per second client wants to receive.*/float sampleRate;/** Flags that indicate to which event sources to listen. */SubscribeFlags flags;
};

所以可以一次性订阅多个属性。但是这多个属性是一次订阅,也是走一个回调函数上去的。这里会遍历所有的SubscribeOptions,然后调用addOrUpdateSubscription函数。

std::map<int32_t, SubscribeOptions> mSubscriptions;void HalClient::addOrUpdateSubscription(const SubscribeOptions &opts)  {ALOGI("%s opts.propId: 0x%x", __func__, opts.propId);auto it = mSubscriptions.find(opts.propId);if (it == mSubscriptions.end()) {mSubscriptions.emplace(opts.propId, opts);} else {const SubscribeOptions& oldOpts = it->second;SubscribeOptions updatedOptions;if (mergeSubscribeOptions(oldOpts, opts, &updatedOptions)) {mSubscriptions.erase(it);mSubscriptions.emplace(opts.propId, updatedOptions);}}
}
bool mergeSubscribeOptions(const SubscribeOptions &oldOpts,const SubscribeOptions &newOpts,SubscribeOptions *outResult) {float updatedRate = std::max(oldOpts.sampleRate, newOpts.sampleRate);SubscribeFlags updatedFlags = SubscribeFlags(oldOpts.flags | newOpts.flags);bool updated = (updatedRate > oldOpts.sampleRate) || (updatedFlags != oldOpts.flags);if (updated) {*outResult = oldOpts;outResult->sampleRate = updatedRate;outResult->flags = updatedFlags;}return updated;
}

首先判断这个SubscribeOptions中包含的propId是否已经存在于mSubscriptions这个map中,如果存在,则更新flag和采样率和相应的SubscribeOptions对象,如果不存在,则添加。
这个map的key是propId,value是SubscribeOptions。

std::map<int32_t, sp<HalClientVector>> mPropToClients;void SubscriptionManager::addClientToPropMapLocked(int32_t propId, const sp<HalClient> &client) {auto it = mPropToClients.find(propId);sp<HalClientVector> propClients;if (it == mPropToClients.end()) {propClients = new HalClientVector();mPropToClients.insert(std::make_pair(propId, propClients));} else {propClients = it->second;}propClients->addOrUpdate(client);
}

mPropToClients是SubscriptionManager类中的一个map,key是propId,value是HalClientVector对象。HalClientVectot中存储的是对于同一个propId订阅的不同HalClient对象。
这里,先判断mPropToClients中是否有对应的propId的HalClientVector,如果没有,则表示这个propId没有客户端订阅过。如果有则将当前的client对象添加到HalClientVector中。
总结一下属性订阅做的事就是:

  1. 根据传入的callback指针,转换成ClientId,作为客户端标识
  2. 创建HalClient对象,作为客户端实例,并保存客户端的callback回调
  3. 缓存所有订阅属性到HalClient对象的mSubscriptions中
  4. 缓存HalClient到mClients这个map中
  5. 缓存HalClient到mPropToClients这个map中

4.2 连续类型属性订阅流程源码分析

连续类型属性,订阅之后需要VHAL周期性上报给CarService
连续属性订阅,其他和上面的普通订阅相同,最后会走到EmulatedVehicleHal中进行处理

StatusCode EmulatedVehicleHal::subscribe(int32_t property, float sampleRate) {ALOGI("%s propId: 0x%x, sampleRate: %f", __func__, property, sampleRate);if (isContinuousProperty(property)) {mRecurrentTimer.registerRecurrentEvent(hertzToNanoseconds(sampleRate), property);}return StatusCode::OK;
}bool EmulatedVehicleHal::isContinuousProperty(int32_t propId) const {const VehiclePropConfig* config = mPropStore->getConfigOrNull(propId);if (config == nullptr) {ALOGW("Config not found for property: 0x%x", propId);return false;}return config->changeMode == VehiclePropertyChangeMode::CONTINUOUS;
}

主要就是通过属性配置的changeMode是否是CONTINUOUS来判断,判断如果还连续属性,调用registerRecurrentEvent

std::unordered_map<int32_t, RecurrentEvent> mCookieToEventsMap;void registerRecurrentEvent(std::chrono::nanoseconds interval, int32_t cookie) {TimePoint now = Clock::now();// Align event time point among all intervals. Thus if we have two intervals 1ms and 2ms,// during every second wake-up both intervals will be triggered.TimePoint absoluteTime = now - Nanos(now.time_since_epoch().count() % interval.count());{std::lock_guard<std::mutex> g(mLock);mCookieToEventsMap[cookie] = { interval, cookie, absoluteTime };}mCond.notify_one();
}

用propId作为key,将定时事件的定时周期,propId,当前转换后时间封装成RecurrentEvent对象,存储到mCookieToEventsMap这个map中,然后通过mCond唤醒线程,至此连续订阅完成,等待特定时间上报。

4.3 订阅流程图

plantuml

@startumlparticipant CarService
box
participant VehicleHalManager
participant SubscriptionManager
participant HalClient
participant HalClientVector
participant EmulatedVehicleHal
participant RecurrentTimer
endboxCarService -> VehicleHalManager: subscribe(const sp<IVehicleCallback> &callback, \n\tconst hidl_vec<SubscribeOptions> &options)
VehicleHalManager -> SubscriptionManager: addOrUpdateSubscription(\n\tClientId clientId, \n\tconst sp<IVehicleCallback> &callback, \n\tconst hidl_vec<SubscribeOptions> &optionList, \n\tstd::list<SubscribeOptions>* outUpdatedSubscriptions)
SubscriptionManager -> SubscriptionManager: getClientId(const sp<IVehicleCallback>& callback)
SubscriptionManager -> SubscriptionManager: getOrCreateHalClientLocked(ClientId clientId, \n\tconst sp<IVehicleCallback>& callback)
alt not in mClientsSubscriptionManager -> HalClient: new HalClient(const sp<IVehicleCallback> &callback)HalClient -> SubscriptionManager: clientSubscriptionManager -> SubscriptionManager: mClients.insert({clientId, client})
end
loop i in optionList.sizeSubscriptionManager -> HalClient: addOrUpdateSubscription(const SubscribeOptions &opts)alt not in mSubscriptionsHalClient -> HalClient: emplace(opts.propId, opts)else in mSubscriptionsHalClient -> HalClient: mergeSubscribeOptions(oldOpts, opts, &updatedOptions)HalClient -> HalClient: erase(it)HalClient -> HalClient: emplace(opts.propId, opts)endSubscriptionManager -> SubscriptionManager: addClientToPropMapLocked(opts.propId, client)alt not in mPropToClientsSubscriptionManager -> HalClientVector: new HalClientVector()HalClientVector -> SubscriptionManager: propClientsSubscriptionManager -> SubscriptionManager: mPropToClients.insert(std::make_pair(propId, propClients));endSubscriptionManager -> SubscriptionManager: propClients->addOrUpdate(client)
end
loop opt in updatedOptionsSubscriptionManager -> EmulatedVehicleHal: subscribe(opt.propId, opt.sampleRate)alt isContinuousPropertyEmulatedVehicleHal -> RecurrentTimer: registerRecurrentEvent(hertzToNanoseconds(sampleRate), property)RecurrentTimer -> RecurrentTimer: mCookieToEventsMap[cookie] = \n\t{ interval, cookie, absoluteTime }end
end@enduml

流程图如下:
在这里插入图片描述

5 取消订阅流程

5.1 取消订阅流程源码分析

取消订阅入口也是在VehicleHalManager中

Return<StatusCode> VehicleHalManager::unsubscribe(const sp<IVehicleCallback>& callback,int32_t propId) {mSubscriptionManager.unsubscribe(getClientId(callback), propId);return StatusCode::OK;
}

根据callback获取ClientId,并转到SubscriptionManager中处理

void SubscriptionManager::unsubscribe(ClientId clientId,int32_t propId) {MuxGuard g(mLock);auto propertyClients = getClientsForPropertyLocked(propId);auto clientIter = mClients.find(clientId);if (clientIter == mClients.end()) {ALOGW("Unable to unsubscribe: no callback found, propId: 0x%x", propId);} else {auto client = clientIter->second;if (propertyClients != nullptr) {propertyClients->remove(client);if (propertyClients->isEmpty()) {mPropToClients.erase(propId);}}bool isClientSubscribedToOtherProps = false;for (const auto& propClient : mPropToClients) {if (propClient.second->indexOf(client) >= 0) {isClientSubscribedToOtherProps = true;break;}}if (!isClientSubscribedToOtherProps) {auto res = client->getCallback()->unlinkToDeath(mCallbackDeathRecipient);if (!res.isOk()) {ALOGW("%s failed to unlink to death, client: %p, err: %s",__func__, client->getCallback().get(), res.description().c_str());}mClients.erase(clientIter);}}if (propertyClients == nullptr || propertyClients->isEmpty()) {mHalEventSubscribeOptions.erase(propId);mOnPropertyUnsubscribed(propId);}
}
sp<HalClientVector> SubscriptionManager::getClientsForPropertyLocked(int32_t propId) const {auto it = mPropToClients.find(propId);return it == mPropToClients.end() ? nullptr : it->second;
}

首先,调用getClientsForPropertyLocked函数,根据propId获取HalClientVector对象。然后从mClients中根据ClientId找到对应的HalClient对象,如果该HalClient存在,则从HalClientVector中移除,如果HalClientVector中是最后一个HalClient,则从mPropToClients中移除这个propId的HalClientVector对象。
如果这个client还订阅了其他propId,则不把这个client从mClients中移除,如果没有订阅其他的propId,则从mClients中移除。
同时,如果这个HalClientVector为空了,则移除mHalEventSubscribeOptions中的对应propId的订阅属性,并调用mOnPropertyUnsubscribed这个回调函数。

void VehicleHalManager::onAllClientsUnsubscribed(int32_t propertyId) {mHal->unsubscribe(propertyId);
}StatusCode EmulatedVehicleHal::unsubscribe(int32_t property) {ALOGI("%s propId: 0x%x", __func__, property);if (isContinuousProperty(property)) {mRecurrentTimer.unregisterRecurrentEvent(property);}return StatusCode::OK;
}void unregisterRecurrentEvent(int32_t cookie) {{std::lock_guard<std::mutex> g(mLock);mCookieToEventsMap.erase(cookie);}mCond.notify_one();}

这个地方就是对连续属性的取消订阅处理,从mCookieToEventsMap中移除。

5.2 取消订阅流程图

plantuml

@startumlparticipant CarService
box
participant VehicleHalManager
participant SubscriptionManager
participant EmulatedVehicleHal
participant RecurrentTimer
endboxCarService -> VehicleHalManager: unsubscribe(const sp<IVehicleCallback>& callback,\n\t int32_t propId)
VehicleHalManager -> SubscriptionManager: unsubscribe(getClientId(callback), propId)
SubscriptionManager -> SubscriptionManager: propertyClients = getClientsForPropertyLocked(propId)
alt client in mClientsSubscriptionManager -> SubscriptionManager: propertyClients->remove(client)alt propertyClients->isEmpty()SubscriptionManager -> SubscriptionManager: mPropToClients.erase(propId)endloop propClient in mPropToClientsalt client in propClientSubscriptionManager -> SubscriptionManager: isClientSubscribedToOtherProps = trueendendalt isClientSubscribedToOtherProps == falseSubscriptionManager -> SubscriptionManager: client->getCallback()->\n\tunlinkToDeath(mCallbackDeathRecipient)end
end
alt propertyClients == nullptr || propertyClients->isEmpty()SubscriptionManager -> VehicleHalManager: onAllClientsUnsubscribed(propertyId)VehicleHalManager -> EmulatedVehicleHal: unsubscribe(property)alt isContinuousPropertyEmulatedVehicleHal -> RecurrentTimer: unregisterRecurrentEvent(property)RecurrentTimer -> RecurrentTimer: mCookieToEventsMap.erase(cookie)end
end@enduml

流程图:
在这里插入图片描述

6 属性上报流程

6.1 属性上报流程源码分析

void onPropertyValueFromCar(const VehiclePropValue& value, bool updateStatus) override {return this->onPropertyValue(value, updateStatus);
}

由于EmulatedVehicleConnector同时继承VehicleHalServer和VehicleHalClient,所以这个onPropertyValue继承于VehicleHalClient,调用的是VehicleHalClient中的onPropertyValue函数。

void VehicleHalClient::onPropertyValue(const VehiclePropValue& value, bool updateStatus) {if (!mPropCallback) {LOG(ERROR) << __func__ << ": PropertyCallBackType is not registered!";return;}return mPropCallback(value, updateStatus);
}

调用mPropCallback这个注册的回调函数,这个回调函数是:

void EmulatedVehicleHal::onPropertyValue(const VehiclePropValue& value, bool updateStatus) {VehiclePropValuePtr updatedPropValue = getValuePool()->obtain(value);if (mPropStore->writeValue(*updatedPropValue, updateStatus)) {getEmulatorOrDie()->doSetValueFromClient(*updatedPropValue);doHalEvent(std::move(updatedPropValue));}
}

首先,将上报的属性写入缓存中。然后调用doSetValueFromClient

void VehicleEmulator::doSetValueFromClient(const VehiclePropValue& propValue) {vhal_proto::EmulatorMessage msg;vhal_proto::VehiclePropValue* val = msg.add_value();populateProtoVehiclePropValue(val, &propValue);msg.set_status(vhal_proto::RESULT_OK);msg.set_msg_type(vhal_proto::SET_PROPERTY_ASYNC);mSocketComm->sendMessage(msg);if (mPipeComm) {mPipeComm->sendMessage(msg);}
}

这里会通过SocketComm和PipeComm通知其他客户端。
然后会调用doHalEvent上报给CarService。

void doHalEvent(VehiclePropValuePtr v) {mOnHalEvent(std::move(v));
}

mOnHalEvent是VehicleHalManager::onHalEvent函数

void VehicleHalManager::onHalEvent(VehiclePropValuePtr v) {mEventQueue.push(std::move(v));
}

将VehiclePropValuePtr传入mEventQueue,这个是初始化的时候与BatchingConsumer中的mQueue绑定的一个queue,用于处理上报事件。同时BatchingConsumer这个类初始化的时候也会创建一个线程来处理上报事件,运行的函数为:

void runInternal(const OnBatchReceivedFunc& onBatchReceived) {if (mState.exchange(State::RUNNING) == State::INIT) {while (State::RUNNING == mState) {mQueue->waitForItems();if (State::STOP_REQUESTED == mState) break;std::this_thread::sleep_for(mBatchInterval);if (State::STOP_REQUESTED == mState) break;std::vector<T> items = mQueue->flush();if (items.size() > 0) {onBatchReceived(items);}}}mState = State::STOPPED;
}void waitForItems() {std::unique_lock<std::mutex> g(mLock);while (mQueue.empty() && mIsActive) {mCond.wait(g);}
}std::vector<T> flush() {std::vector<T> items;MuxGuard g(mLock);if (mQueue.empty() || !mIsActive) {return items;}while (!mQueue.empty()) {items.push_back(std::move(mQueue.front()));mQueue.pop();}return items;
}void push(T&& item) {{MuxGuard g(mLock);if (!mIsActive) {return;}mQueue.push(std::move(item));}mCond.notify_one();
}

当没有上报事件时,会waitForItems阻塞线程。如果有事件,则会在循环中处理,等待一个时间间隔mBatchInterval=10,如果在这个事件内没有上报事件,会等10s,如果有,则会在push的时候立马唤醒线程执行。
然后将queue中的所有事件都取出,调用回调函数处理,回调函数是:

void VehicleHalManager::onBatchHalEvent(const std::vector<VehiclePropValuePtr>& values) {const auto& clientValues =mSubscriptionManager.distributeValuesToClients(values, SubscribeFlags::EVENTS_FROM_CAR);for (const HalClientValues& cv : clientValues) {auto vecSize = cv.values.size();hidl_vec<VehiclePropValue> vec;if (vecSize < kMaxHidlVecOfVehiclPropValuePoolSize) {vec.setToExternal(&mHidlVecOfVehiclePropValuePool[0], vecSize);} else {vec.resize(vecSize);}int i = 0;for (VehiclePropValue* pValue : cv.values) {shallowCopy(&(vec)[i++], *pValue);}auto status = cv.client->getCallback()->onPropertyEvent(vec);if (!status.isOk()) {ALOGE("Failed to notify client %s, err: %s",toString(cv.client->getCallback()).c_str(),status.description().c_str());}}
}

先看下封装

std::list<HalClientValues> SubscriptionManager::distributeValuesToClients(const std::vector<recyclable_ptr<VehiclePropValue>>& propValues,SubscribeFlags flags) const {//创建一个map,key是HalClient,value是VehiclePropValuestd::map<sp<HalClient>, std::list<VehiclePropValue*>> clientValuesMap;{MuxGuard g(mLock);//遍历所有的propValuefor (const auto& propValue: propValues) {VehiclePropValue* v = propValue.get();//获取所有订阅了该属性的客户端HalClient对象auto clients = getSubscribedClientsLocked(v->prop, flags);//遍历所有HalClient,并将其和propValue一起封装加入clientValuesMap中for (const auto& client : clients) {clientValuesMap[client].push_back(v);}}}//遍历map中所有的对象,并封装成HalClientValues对象,存入clientValues中std::list<HalClientValues> clientValues;for (const auto& entry : clientValuesMap) {clientValues.push_back(HalClientValues {.client = entry.first,.values = entry.second});}return clientValues;
}std::list<sp<HalClient>> SubscriptionManager::getSubscribedClientsLocked(int32_t propId, SubscribeFlags flags) const {std::list<sp<HalClient>> subscribedClients;//通过propId获取该id对应的HalClientVector对象sp<HalClientVector> propClients = getClientsForPropertyLocked(propId);if (propClients.get() != nullptr) {//遍历HalClientVector中HalClient,并返回for (size_t i = 0; i < propClients->size(); i++) {const auto& client = propClients->itemAt(i);if (client->isSubscribed(propId, flags)) {subscribedClients.push_back(client);}}}return subscribedClients;
}sp<HalClientVector> SubscriptionManager::getClientsForPropertyLocked(int32_t propId) const {auto it = mPropToClients.find(propId);return it == mPropToClients.end() ? nullptr : it->second;
}

对客户端和属性值进行封装,然后遍历封装后的数组,逐个调用其回调函数onPropertyEvent,这个就是客户端传入的回调函数。

6.2 连续属性上报流程源码分析

连续属性订阅之后,会根据传入的采样率转换后的频率进行上报,订阅时,将订阅事件保存在mCookieToEventsMap这个map中,并唤醒了处理线程。
这个线程是什么时候初始化,在执行什么呢?这个可以参考https://editor.csdn.net/md/?articleId=140752076

void loop(const Action& action) {static constexpr auto kInvalidTime = TimePoint(Nanos::max());std::vector<int32_t> cookies;while (!mStopRequested) {auto now = Clock::now();auto nextEventTime = kInvalidTime;cookies.clear();{std::unique_lock<std::mutex> g(mLock);for (auto&& it : mCookieToEventsMap) {RecurrentEvent& event = it.second;if (event.absoluteTime <= now) {event.updateNextEventTime(now);cookies.push_back(event.cookie);}if (nextEventTime > event.absoluteTime) {nextEventTime = event.absoluteTime;}}}if (cookies.size() != 0) {action(cookies);}std::unique_lock<std::mutex> g(mLock);mCond.wait_until(g, nextEventTime);  // nextEventTime can be nanoseconds::max()}
}

唤醒这个线程,并在这个里面从mCookieToEventsMap中取出RecurrentEvent,判断时间戳,如果已经到时了,则执行里面的action回调函数。

void EmulatedVehicleHal::onContinuousPropertyTimer(const std::vector<int32_t>& properties) {VehiclePropValuePtr v;auto& pool = *getValuePool();for (int32_t property : properties) {if (isContinuousProperty(property)) {auto internalPropValue = mPropStore->readValueOrNull(property);if (internalPropValue != nullptr) {v = pool.obtain(*internalPropValue);}} else {ALOGE("Unexpected onContinuousPropertyTimer for property: 0x%x", property);}if (v.get()) {v->timestamp = elapsedRealtimeNano();doHalEvent(std::move(v));}}
}

如果是连续属性,则从VehiclePropertyStore读取缓存,并调用doHalEvent往上报,这个和6.1节中的上报流程相同。

6.3 属性上报流程图

plantuml

@startumlparticipant CarService
box
participant EmulatedVehicleHal
participant VehicleHalManager
participant SubscriptionManager
participant HalClient
participant IPassThroughConnector
participant VehicleHalClient
participant VehiclePropertyStore
participant VehicleEmulator
participant SocketComm
participant ConcurrentQueue
participant BatchingConsumer
endboxEmulatedVehicleHal -> IPassThroughConnector: onPropertyValueFromCar(*updatedPropValue, updateStatus)
IPassThroughConnector -> VehicleHalClient: onPropertyValue(value, updateStatus)
VehicleHalClient -> EmulatedVehicleHal: onPropertyValue(value, updateStatus)
EmulatedVehicleHal -> VehiclePropertyStore: writeValue(*updatedPropValue, updateStatus)
alt writeValue successEmulatedVehicleHal -> VehicleEmulator: doSetValueFromClient(*updatedPropValue)VehicleEmulator -> SocketComm: sendMessage(msg)EmulatedVehicleHal -> EmulatedVehicleHal: doHalEvent(std::move(updatedPropValue))EmulatedVehicleHal -> VehicleHalManager::onHalEvent(updatedPropValue)VehicleHalManager -> VehicleHalManager: mEventQueue.push(std::move(updatedPropValue))VehicleHalManager -> ConcurrentQueue: mQueue.push(updatedPropValue)ConcurrentQueue -> ConcurrentQueue: mCond.notify_one()ConcurrentQueue -> BatchingConsumer: runInternal(const OnBatchReceivedFunc& onBatchReceived)BatchingConsumer -> VehicleHalManager: onBatchHalEvent(const std::vector<VehiclePropValuePtr>& values)VehicleHalManager -> SubscriptionManager: distributeValuesToClients(values, \n\tSubscribeFlags::EVENTS_FROM_CAR)SubscriptionManager -> VehicleHalManager: clientValuesloop cv in clientValuesVehicleHalManager -> HalClient: getCallback()HalClient -> VehicleHalManager: callbackVehicleHalManager -> CarService: callback->onPropertyEvent(values)end
end@enduml

流程图
在这里插入图片描述
连续属性上报流程图省略,大致和这个上面差不多,只是触发在定时器中。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/440707.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

判断是否为二叉排序树(二叉搜索树,二叉查找树)

1.判断给定的二叉树是否为二叉排序树&#xff0c;如果是返回1&#xff0c;不是返回0。 思想&#xff1a; 二叉树是左子树<根<右子树。中序遍历是递增有序&#xff0c;可以通过比较当前结点与前驱关系来进行判断。 代码&#xff1a; //pre为全局变量&#xff0c;保存当…

数学与生活

多学科交叉 信号处理 小波 经济 政策 计算机 统计 信号处理与市场分析 经济与数据分析 政策与统计 过去的数学家没有一个是纯粹的数学家&#xff1b;生活中各方面工程的&#xff0c;物理的&#xff0c;天文&#xff0c;地理的&#xff0c;赌博&#xff0c;政治的&#xff1b…

5-25 JQuery

jQuery简介 jQuery是什么 jQuery基本语法 测试jQuery <head> <meta charset"utf-8"> <title>无标题文档</title><script type"text/javascript" src"jquery-3.5.1.js"></script><script type"tex…

FastAdmin Apache下设置伪静态

FastAdmin Apache下设置伪静态 一、引言 FastAdmin 是一个基于ThinkPHP和Bootstrap框架开发的快速后台开发框架&#xff0c;它以其简洁、高效、易于扩展的特点&#xff0c;广受开发者的喜爱。在部署FastAdmin项目时&#xff0c;为了提高访问速度和用户体验&#xff0c;我们通…

Redis介绍及整合Spring

目录 Redis介绍 Spring与Redis集成 Redis介绍 Redis是内存数据库&#xff0c;Key-value型NOSQL数据库&#xff0c;项目上经常将一些不经常变化并且反复查询的数据放入Redis缓存&#xff0c;由于数据放在内存中&#xff0c;所以查询、维护的速度远远快于硬盘方式操作数据&#…

【NIO基础】基于 NIO 中的组件实现对文件的操作(文件编程),FileChannel 详解

目录 1、FileChannel (1&#xff09;获取 FileChannel (2&#xff09;读取文件 (3&#xff09;写入文件 (4&#xff09;关闭通道 (5&#xff09;当前位置与文件大小 (6&#xff09;强制写入磁盘 2、两个 FileChannel 之间的数据传输 (1&#xff09;使用 transferTo()…

leetcode-42. 接雨水 单调栈

给定 n 个非负整数表示每个宽度为 1 的柱子的高度图&#xff0c;计算按此排列的柱子&#xff0c;下雨之后能接多少雨水。 示例 1&#xff1a; 输入&#xff1a;height [0,1,0,2,1,0,1,3,2,1,2,1] 输出&#xff1a;6 解释&#xff1a;上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表…

微软发布Windows 11 2024更新,新型Copilot+ AI PC功能亮相

前言 微软在Windows 11的2024更新中加强了对人工智能的应用&#xff0c;推出了新功能Copilot。 此次更新的版本号为26100.1742&#xff0c;Copilot将首先在Windows Insider中推出&#xff0c;计划于11月向特定设备和市场推广&#xff0c;用户需开启“尽快获取最新更新”选项以…

RESTful风格接口+Swagger生成Web API文档

RESTful风格接口Swagger生成Web API文档 文章目录 RESTful风格接口Swagger生成Web API文档1.RESTful风格接口RESTful简介RESTful详细图示常见http状态码springboot实现RESTfulRESTful springboot设计实例demo 2.Swagger生产Web API文档Swagger简介使用Swagger1.加入依赖2.配置S…

基于STM32的超声波测距仪设计

引言 本项目将基于STM32微控制器设计一个超声波测距仪&#xff0c;通过超声波传感器实现距离测量&#xff0c;并将结果显示在液晶屏上。该项目展示了STM32微控制器与超声波传感器、LCD显示器的接口通信&#xff0c;以及信号处理和距离计算的过程。 环境准备 1. 硬件设备 ST…

技术速递|Python in Visual Studio Code 2024年9月发布

排版&#xff1a;Alan Wang 我们很高兴地宣布将于 2024 年 9 月发布适用于 Visual Studio Code 的 Python 和 Jupyter 扩展&#xff01; 此版本包括以下公告&#xff1a; Django 单元测试支持使用 Pylance 从 inlay 提示转到定义 如果您有兴趣&#xff0c;可以在我们的 Pyth…

提升 CI/CD 稳定性:Jenkins 开机自检与推送通知

简介&#xff1a;Jenkins 是一个广泛使用的开源自动化服务器&#xff0c;常用于持续集成和持续交付。在某些情况下&#xff0c;服务器重启可能导致 Jenkins 构建任务中断或失败。为了解决这个问题&#xff0c;可以使用一个自检服务&#xff0c;定期检查系统的启动时间&#xff…

算法题总结(十)——二叉树上

#二叉树的递归遍历 // 前序遍历递归LC144_二叉树的前序遍历 class Solution {public List<Integer> preorderTraversal(TreeNode root) {List<Integer> result new ArrayList<Integer>(); //也可以把result 作为全局变量&#xff0c;只需要一个函数即可。…

Open3D实现点云数据的序列化与网络传输

转载自个人博客&#xff1a;Open3D实现点云数据的序列化与网络传输 在处理点云数据的时候&#xff0c;有时候需要实现点云数据的远程传输。当然可以利用传输文件的方法直接把点云数据序列化成数据流进行传输&#xff0c;但Open3D源码在实现RPC功能时就提供了一套序列化及传输的…

今日指数day8实战补充用户管理模块(下)

ps : 由于前端将userId封装为BigInt类型 , 导致有精度损失, 传入的userId不正确 , 部分功能无法正确实现 , 但是代码已经完善 1.4 更新用户角色信息接口说明 1&#xff09;原型效果 2&#xff09;接口说明 功能描述&#xff1a;更新用户角色信息 服务路径&#xff1a;/user/…

vue-scrollto实现页面组件锚点定位

文章目录 前言背景操作指南安装及配置步骤vue组件中使用 参考文章 前言 博主介绍&#xff1a;✌目前全网粉丝3W&#xff0c;csdn博客专家、Java领域优质创作者&#xff0c;博客之星、阿里云平台优质作者、专注于Java后端技术领域。 涵盖技术内容&#xff1a;Java后端、大数据…

php获取远程https内容时提示 PHP Warning: copy(): Unable to find the wrapper “https“ 解决方法

异常信息&#xff1a; php -r "copy(https://getcomposer.org/installer, composer-setup.php);" PHP Warning: copy(): Unable to find the wrapper "https" - did you forget to enable it when you configured PHP? in Command line code on line 1 P…

鸿蒙harmonyos next flutter混合开发之开发plugin(获取操作系统版本号)

创建Plugin为my_plugin flutter create --org com.example --templateplugin --platformsandroid,ios,ohos my_plugin 创建Application为my_application flutter create --org com.example my_application flutter_application引用flutter_plugin&#xff0c;在pubspec.yam…

万界星空科技MES数据集成平台

制造执行系统MES作为连接企业上层ERP系统和现场控制系统的桥梁&#xff0c;承担了实时数据采集、处理、分析和传递的重要任务。MES数据集成平台是一个集成各类数据源&#xff0c;将数据进行整合和统一管理的系统&#xff0c;通过提供标准化接口和协议&#xff0c;实现数据的无缝…

图像分割恢复方法

传统的图像分割方法主要依赖于图像的灰度值、纹理、颜色等特征&#xff0c;通过不同的算法将图像分割成多个区域。这些方法通常可以分为以下几类&#xff1a; 1.基于阈值的方法 2.基于边缘的方法 3.基于区域的方法 4.基于聚类的方法 下面详细介绍这些方法及其示例代码。 1. 基…