Android 车载应用开发指南(7)- 使用移动设备控制车辆 HVAC 模块

一 概述

在现代汽车设计中,通过移动设备(通常为智能手机)控制车辆的各个部件已成为一种重要趋势,可提高用户体验和便利性。其中一个部件是 HVAC(暖气、通风和空调)系统,它在确保乘客舒适度方面起着至关重要的作用。在本文中,我们将探讨如何使用 Android 设备控制汽车中的 HVAC 模块,利用 SOME/IP 协议的强大功能。

二 了解 HVAC

HVAC (Heating, Ventilation, and Air Conditioning)代表供暖、通风和空调。在汽车工程领域,HVAC 系统调节车厢内的温度、湿度和空气质量。它包含了加热器、空调、风扇和空气过滤器等组件。有效控制 HVAC 系统有助于提高乘客在旅途中的舒适度和安全性。

三 SOME/IP简介

在 SOME/IP 范式中,通信围绕服务构建,服务封装了特定的功能或数据交换。面向服务的模型中有两个主要角色:

Provider(提供者): 负责向网络内的其他 ECU 提供服务。在汽车环境中,Provider ECU 可能控制物理执行器、读取传感器数据或执行与车辆操作相关的其他任务。例如,在我们的案例中,Provider 将是车辆内域控制器上运行的应用程序。

Provider 通过公开定义可用于交互的方法或数据结构的接口来提供服务。这些接口可以包括控制执行器的操作(例如 HVAC 设置)或读取传感器数据(例如温度、湿度)的方法。

Consumer(消费者): 另一方面,Consumer是利用网络内其他 ECU 提供的服务的 ECU。Consumer可以订阅Provider 提供的特定服务,以接收更新或根据需要调用方法。在汽车环境中,Consumer可能负责解释传感器数据、发送控制命令或根据收到的信息执行其他任务。

Consumer订阅他们感兴趣的服务,并在有新数据可用时接收更新。他们还可以调用服务Provider 提供的方法来触发操作或控制功能。在我们的场景中,Consumer将是运行在 Android VHAL(车辆硬件抽象层)上的应用程序,负责与车辆网络交互并控制 HVAC 设置。

四 SOME/IP 通信流程

SOME/IP 中的通信流程遵循发布-订阅模式,其中Provider 发布数据或服务,Consumer订阅它们以接收更新或调用方法。这种异步通信模型允许网络内的 ECU 之间进行高效、灵活的交互。

在我们的案例中,在域控制器(提供商)上运行的应用程序将发布传感器数据,例如温度、湿度和 HVAC 状态。订阅的Consumer(例如 Android 上的 VHAL 应用程序)将收到这些更新,并可以将控制命令发送回域控制器,以根据用户输入调整 HVAC 设置。

五 利用 Android 中的 VHAL 实现车辆联网

为了与车辆网络进行通信,Android 提供了车辆硬件抽象层 (VHAL)。VHAL 充当 Android 操作系统与车载系统之间的桥梁,使 Android 设备与汽车功能无缝集成。VHAL 抽象了车辆网络协议的复杂性,使开发人员可以专注于实现 HVAC 控制等功能,而无需担心低级通信细节。

六 在 VHAL 中实现 SOMEIP 消费者

为了将 SOMEIP 使用者集成到 Android 14 上的 VHAL,我们将使用 vsomeip 库。以下是实现此解决方案所需的步骤:

克隆 vsomeip 存储库

转到 Android 项目的主目录并创建一个名为 external/sdv 的新目录:

bash 复制代码
 mkdir -p external/sdv
 cd external/sdv
 git clone https://android.googlesource.com/platform/external/sdv/vsomeip

在 VHAL 中实现 SOMEIP 消费者

hardware/interfaces/automotive/vehicle/2.0/default 目录中,可以找到 VHAL 应用程序代码。在VehicleService.cpp文件中,您将找到默认的 VHAL 实现。

arduino 复制代码
 int main(int /* argc */, char* /* argv */ []) {
     auto store = std::make_unique<VehiclePropertyStore>();
     auto connector = std::make_unique<DefaultVehicleConnector>();
     auto hal = std::make_unique<DefaultVehicleHal>(store.get(), connector.get());
     auto service = android::sp<VehicleHalManager>::make(hal.get());
     connector->setValuePool(hal->getValuePool());
     android::hardware::configureRpcThreadpool(4, true /* callerWillJoin */);
     ALOGI("Registering as service...");
     android::status_t status = service->registerAsService();
     if (status != android::OK) {
         ALOGE("Unable to register vehicle service (%d)", status);
         return 1;
     }
     ALOGI("Ready");
     android::hardware::joinRpcThreadpool();
     return 0;
 }

DefaultVehicleHal 中提供了 VHAL 的默认实现,我们需要在VehicleService.cpp中将其替换。

从:

css 复制代码
 auto hal = std::make_unique<DefaultVehicleHal>(store.get(), connector.get());

到:

css 复制代码
 auto hal = std::make_unique<VendorVehicleHal>(store.get(), connector.get());

为了实现此目的,我们将创建一个名为 VendorVehicleHal 的类,并从 DefaultVehicleHal 类继承。我们将重写 set 和 get 函数。

kotlin 复制代码
 class VendorVehicleHal : public DefaultVehicleHal {
 public:
     VendorVehicleHal(VehiclePropertyStore* propStore, VehicleHalClient* client);
  
     VehiclePropValuePtr get(const VehiclePropValue& requestedPropValue,
                             StatusCode* outStatus) override;
     StatusCode set(const VehiclePropValue& propValue) override;
  };

当 Android 系统向 VHAL 请求信息时,会调用 get 函数,当需要设置时,会调用 set 函数。数据在 hardware/interfaces/automotive/vehicle/2.0/types.hal 中定义的 VehiclePropValue 对象中传输。

它包含一个变量 prop,它是我们的属性的标识符。所有属性的列表可以在 types.hal 文件中找到。

我们将仅过滤掉感兴趣的值并将其余值重定向到默认实现。

c 复制代码
 StatusCode VendorVehicleHal::set(const VehiclePropValue& propValue) {
     ALOGD("VendorVehicleHal::set  propId: 0x%x areaID: 0x%x", propValue.prop, propValue.areaId);
  
     switch(propValue.prop)
     {
         case (int)VehicleProperty::HVAC_FAN_SPEED :
         break;
  
         case (int)VehicleProperty::HVAC_FAN_DIRECTION : 
         break;
  
         case (int)VehicleProperty::HVAC_TEMPERATURE_CURRENT :
         break;
  
         case (int)VehicleProperty::HVAC_TEMPERATURE_SET:
         break;
  
         case (int)VehicleProperty::HVAC_DEFROSTER :
         break;
     
         case (int)VehicleProperty::HVAC_AC_ON :
         break;
         
         case (int)VehicleProperty::HVAC_MAX_AC_ON :
         break;
  
         case (int)VehicleProperty::HVAC_MAX_DEFROST_ON :
         break;
  
         case (int)VehicleProperty::EVS_SERVICE_REQUEST :
         break;
  
         case (int)VehicleProperty::HVAC_TEMPERATURE_DISPLAY_UNITS  :
         break;
     }
  
     return DefaultVehicleHal::set(propValue);
 }

现在需要创建一个 SOME/IP 服务消费者。

在我们的示例中,我们将创建一个名为 ZoneHVACService 的类并定义 SOME/IP 服务、实例、方法和事件 ID:

rust 复制代码
 #define ZONE_HVAC_SERVICE_ID                    0x4002
 #define ZONE_HVAC_INSTANCE_ID                   0x0001
  
 #define ZONE_HVAC_SET_TEMPERATURE_ID            0x1011
 #define ZONE_HVAC_SET_FANSPEED_ID               0x1012
 #define ZONE_HVAC_SET_AIR_DISTRIBUTION_ID       0x1013
  
 #define ZONE_HVAC_TEMPERATURE_EVENT_ID          0x2011
 #define ZONE_HVAC_FANSPEED_EVENT_ID             0x2012
 #define ZONE_HVAC_AIR_DISTRIBUTION_EVENT_ID     0x2013
  
 #define ZONE_HVAC_EVENT_GROUP_ID                0x3011
  
 class ZoneHVACService {
 public:
     ZoneHVACService(bool _use_tcp) :
             app_(vsomeip::runtime::get()->create_application(vsomeipAppName)), use_tcp_(
             _use_tcp) {
     }
  
     bool init() {
         if (!app_->init()) {
             LOG(ERROR) << "[SOMEIP] " << __func__ << "Couldn't initialize application";
             return false;
         } 
  
         app_->register_state_handler(
                 std::bind(&ZoneHVACService::on_state, this,
                           std::placeholders::_1));
   
         app_->register_message_handler(
                 ZONE_HVAC_SERVICE_ID, ZONE_HVAC_INSTANCE_ID, vsomeip::ANY_METHOD,
                 std::bind(&ZoneHVACService::on_message, this,
                           std::placeholders::_1));
  
  
         app_->register_availability_handler(ZONE_HVAC_SERVICE_ID, ZONE_HVAC_INSTANCE_ID,
                                             std::bind(&ZoneHVACService::on_availability,
                                                       this,
                                                       std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
  
         std::set<vsomeip::eventgroup_t> its_groups;
         its_groups.insert(ZONE_HVAC_EVENT_GROUP_ID);
         app_->request_event(
                 ZONE_HVAC_SERVICE_ID,
                 ZONE_HVAC_INSTANCE_ID,
                 ZONE_HVAC_TEMPERATURE_EVENT_ID,
                 its_groups,
                 vsomeip::event_type_e::ET_FIELD);
         app_->request_event(
                 ZONE_HVAC_SERVICE_ID,
                 ZONE_HVAC_INSTANCE_ID,
                 ZONE_HVAC_FANSPEED_EVENT_ID,
                 its_groups,
                 vsomeip::event_type_e::ET_FIELD);
         app_->request_event(
                 ZONE_HVAC_SERVICE_ID,
                 ZONE_HVAC_INSTANCE_ID,
                 ZONE_HVAC_AIR_DISTRIBUTION_EVENT_ID,
                 its_groups,
                 vsomeip::event_type_e::ET_FIELD);
         app_->subscribe(ZONE_HVAC_SERVICE_ID, ZONE_HVAC_INSTANCE_ID, ZONE_HVAC_EVENT_GROUP_ID);
  
         return true;
     }
  
     void send_temp(std::string temp)
     {
         LOG(INFO) << "[SOMEIP] " << __func__ <<  " temp: " << temp;
         std::shared_ptr< vsomeip::message > request;
         request = vsomeip::runtime::get()->create_request();
         request->set_service(ZONE_HVAC_SERVICE_ID);
         request->set_instance(ZONE_HVAC_INSTANCE_ID);
         request->set_method(ZONE_HVAC_SET_TEMPERATURE_ID);
  
         std::shared_ptr< vsomeip::payload > its_payload = vsomeip::runtime::get()->create_payload();
         its_payload->set_data((const vsomeip_v3::byte_t *)temp.data(), temp.size());
         request->set_payload(its_payload);
         app_->send(request);
     }
  
     void send_fanspeed(uint8_t speed)
     {
         LOG(INFO) << "[SOMEIP] " << __func__ <<  " speed: " << (int)speed;
         std::shared_ptr< vsomeip::message > request;
         request = vsomeip::runtime::get()->create_request();
         request->set_service(ZONE_HVAC_SERVICE_ID);
         request->set_instance(ZONE_HVAC_INSTANCE_ID);
         request->set_method(ZONE_HVAC_SET_FANSPEED_ID);
  
         std::shared_ptr< vsomeip::payload > its_payload = vsomeip::runtime::get()->create_payload();
         its_payload->set_data(&speed, 1U);
         request->set_payload(its_payload);
         app_->send(request);
     }
   
     void start() {
         app_->start();
     }
  
     void on_state(vsomeip::state_type_e _state) {
         if (_state == vsomeip::state_type_e::ST_REGISTERED) {
             app_->request_service(ZONE_HVAC_SERVICE_ID, ZONE_HVAC_INSTANCE_ID);
         }
     }
  
     void on_availability(vsomeip::service_t _service, vsomeip::instance_t _instance, bool _is_available) {
         LOG(INFO) << "[SOMEIP] " << __func__ <<  "Service ["
                   << std::setw(4) << std::setfill('0') << std::hex << _service << "." << _instance
                   << "] is "
                   << (_is_available ? "available." : "NOT available.");
     }
  
     void on_temperature_message(const std::shared_ptr<vsomeip::message> & message)
     {
         auto payload = message->get_payload();
         temperature_.resize(payload->get_length());
         temperature_.assign((char*)payload->get_data(), payload->get_length());
         LOG(INFO) << "[SOMEIP] " << __func__ <<  " temp: " << temperature_;
  
         if(tempChanged_)
         {
             tempChanged_(temperature_);
         }
     }
  
     void on_fanspeed_message(const std::shared_ptr<vsomeip::message> & message)
     {
         auto payload = message->get_payload();
         fan_speed_ = *payload->get_data();
         LOG(INFO) << "[SOMEIP] " << __func__ <<  " speed: " << (int)fan_speed_;
  
         if(fanspeedChanged_)
         {
             fanspeedChanged_(fan_speed_);
         }
     }
  
     void on_message(const std::shared_ptr<vsomeip::message> & message) {
         if(message->get_method() == ZONE_HVAC_TEMPERATURE_EVENT_ID)
         {
             LOG(INFO) << "[SOMEIP] " << __func__ << "TEMPERATURE_EVENT_ID received";
             on_temperature_message(message);
         }
        else  if(message->get_method() == ZONE_HVAC_FANSPEED_EVENT_ID)
         {
             LOG(INFO) << "[SOMEIP] " << __func__ << "ZONE_HVAC_FANSPEED_EVENT_ID received";
             on_fanspeed_message(message);
         }
     }
  
  
     std::function<void(std::string temp)> tempChanged_;
     std::function<void(uint8_t)> fanspeedChanged_;
  
 private:
     std::shared_ptr< vsomeip::application > app_;
     bool use_tcp_;
  
     std::string temperature_;
     uint8_t fan_speed_;
     uint8_t air_distribution_t;
 };

在我们的示例中,我们将使用回调连接 ZoneHVACService 和 VendorVehicleHal。

scss 复制代码
 hal->fandirectionChanged_ = [&](uint8_t direction) {
  ALOGI("HAL fandirectionChanged_ callback direction: %u", direction);
  hvacService->send_fandirection(direction);
 };
 hal->fanspeedChanged_ = [&](uint8_t speed) {
  ALOGI("HAL fanspeedChanged_ callback speed: %u", speed);
  hvacService->send_fanspeed(speed);
 };

我们要做的最后一件事是为 vsomeip 库创建配置。最好利用库中的示例文件:https ://github.com/COVESA/vsomeip/blob/master/config/vsomeip-local.json

在此文件中,需要更改地址:

"unicast" : "10.0.2.15",

to the address of our Android device.

Additionally, you need to set:

"routing" : "service-sample",

to the name of our application.

vsomeip 堆栈从环境变量中读取应用程序地址和配置文件路径。在 Android 中执行此操作的最简单方法是在创建 ZoneHVACService 对象之前进行设置。

arduino 复制代码
 setenv("VSOMEIP_CONFIGURATION","/vendor/etc/vsomeip-local-hvac.json",1);
 setenv("VSOMEIP_APPLICATION_NAME," "hvac-service",1);

就是这样。现在,我们应该用新版本替换vendor/bin/hw/android.hardware.automotive.vehicle@2.0-default-service ,然后重启 Android。

如果一切配置正确,我们应该看到这样的日志,并且提供商应该收到我们的请求。

yaml 复制代码
 04-25 06:52:12.989  3981  3981 I automotive.vehicle@2.0-default-service: Starting automotive.vehicle@2.0-default-service ...
 04-25 06:52:13.005  3981  3981 I automotive.vehicle@2.0-default-service: Registering as service...
 04-25 06:52:13.077  3981  3981 I automotive.vehicle@2.0-default-service: Ready
 04-25 06:52:13.081  3981  4011 I automotive.vehicle@2.0-default-service: Starting UDP receiver
 04-25 06:52:13.081  3981  4011 I automotive.vehicle@2.0-default-service: Socket created
 04-25 06:52:13.082  3981  4010 I automotive.vehicle@2.0-default-service: HTTPServer starting
 04-25 06:52:13.082  3981  4010 I automotive.vehicle@2.0-default-service: HTTPServer listen
 04-25 06:52:13.091  3981  4012 I automotive.vehicle@2.0-default-service: Initializing SomeIP service ...
 04-25 06:52:13.091  3981  4012 I automotive.vehicle@2.0-default-service: [SOMEIP] initInitialize app
 04-25 06:52:13.209  3981  4012 I automotive.vehicle@2.0-default-service: [SOMEIP] initApp initialized
 04-25 06:52:13.209  3981  4012 I automotive.vehicle@2.0-default-service: [SOMEIP] initClient settings [protocol=UDP]
 04-25 06:52:13.210  3981  4012 I automotive.vehicle@2.0-default-service: [SOMEIP] Initialized SomeIP service result:1
 04-25 06:52:13.214  3981  4028 I automotive.vehicle@2.0-default-service: [SOMEIP] on_availabilityService [4002.1] is NOT available.
 04-25 06:54:35.654  3981  4028 I automotive.vehicle@2.0-default-service: [SOMEIP] on_availabilityService [4002.1] is available.
 04-25 06:54:35.774  3981  4028 I automotive.vehicle@2.0-default-service: [SOMEIP] on_message Message received: [4002.0001.2012] to Client/Session [0000/0002]
 04-25 06:54:35.774  3981  4028 I automotive.vehicle@2.0-default-service: [SOMEIP] on_messageZONE_HVAC_FANSPEED_EVENT_ID received
 04-25 06:54:35.774  3981  4028 I automotive.vehicle@2.0-default-service: [SOMEIP] on_fanspeed_message speed: 1
 04-25 06:54:35.775  3981  4028 I automotive.vehicle@2.0-default-service: SOMEIP fanspeedChanged_ speed: 1
 04-25 06:54:36.602  3981  4028 I automotive.vehicle@2.0-default-service: [SOMEIP] on_message Message received: [4002.0001.2012] to Client/Session [0000/0003]
 04-25 06:54:36.602  3981  4028 I automotive.vehicle@2.0-default-service: [SOMEIP] on_messageZONE_HVAC_FANSPEED_EVENT_ID received
 04-25 06:54:36.603  3981  4028 I automotive.vehicle@2.0-default-service: [SOMEIP] on_fanspeed_message speed: 2
 04-25 06:54:36.603  3981  4028 I automotive.vehicle@2.0-default-service: SOMEIP fanspeedChanged_ speed: 2
 04-25 06:54:37.605  3981  4028 I automotive.vehicle@2.0-default-service: [SOMEIP] on_message Message received: [4002.0001.2012] to Client/Session [0000/0004]
 04-25 06:54:37.606  3981  4028 I automotive.vehicle@2.0-default-service: [SOMEIP] on_messageZONE_HVAC_FANSPEED_EVENT_ID received
 04-25 06:54:37.606  3981  4028 I automotive.vehicle@2.0-default-service: [SOMEIP] on_fanspeed_message speed: 3
 04-25 06:54:37.606  3981  4028 I automotive.vehicle@2.0-default-service: SOMEIP fanspeedChanged_ speed: 3

七 总结

总之,Android 设备与用于控制 HVAC 系统的车辆硬件抽象层 (VHAL) 的集成为汽车技术开辟了新的可能性领域。通过利用 SOME/IP 通信协议和 vsomeip 库的强大功能,开发人员可以创建用于管理车辆 HVAC 功能的强大解决方案。

通过遵循本文概述的步骤,开发人员可以根据自己的特定需求创建自定义 VHAL 实现。从定义服务接口到处理通信回调,集成过程的每个方面都经过了仔细解释,以促进顺利开发。

随着汽车技术的不断发展,Android 设备与汽车系统的融合是迈向更智能、更互联的汽车的重要里程碑。通过 VHAL 和 SOME/IP 集成 HVAC 控制功能不仅展示了现代汽车技术的潜力,还为该领域的未来创新铺平了道路。

相关推荐
fadtes1 小时前
C++ constexpr(八股总结)
开发语言·c++
zhangzhangkeji1 小时前
c/c++ 里的进程间通信 , 管道 pipe 编程举例
c语言·c++
小wanga1 小时前
【C++】特殊类设计
android·c++
天草二十六_简村人1 小时前
微服务框架,Http异步编程中,如何保证数据的最终一致性
java·spring boot·后端·http·微服务·架构
ℒฺℴฺνℯ̶ฺ归̶零̶⋆3181 小时前
0107作业
c++
龙之叶2 小时前
Android13实时刷新频率的实现代码
android·java·ui
无限码力2 小时前
目标值子矩阵的数量
c++·算法·矩阵
Stanford_11062 小时前
关于物联网的基础知识(三)——物联网技术架构:连接万物的智慧之道!连接未来的万物之网!
c++·物联网·学习·微信小程序·架构·twitter·微信开放平台
码农小灰2 小时前
微服务拆分的艺术:构建高效、灵活的系统架构
微服务·架构·系统架构
言之。2 小时前
【微服务】4、服务保护
微服务·云原生·架构