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 控制功能不仅展示了现代汽车技术的潜力,还为该领域的未来创新铺平了道路。

相关推荐
苏克贝塔18 分钟前
Qt 图形视图框架3-事件处理与传播
c++·qt
轩情吖25 分钟前
Qt的信号与槽(二)
数据库·c++·qt·信号·connect·信号槽·
胖大和尚26 分钟前
C++项目学习计划
开发语言·c++·学习
Patrick_Wilson1 小时前
青苔漫染待客迟
前端·设计模式·架构
Kotlin上海用户组1 小时前
Koin vs. Hilt——最流行的 Android DI 框架全方位对比
android·架构·kotlin
zzq19962 小时前
Android framework 开发者模式下,如何修改动画过度模式
android
木叶丸2 小时前
Flutter 生命周期完全指南
android·flutter·ios
阿幸软件杂货间2 小时前
阿幸课堂随机点名
android·开发语言·javascript
没有了遇见2 小时前
Android 渐变色整理之功能实现<二>文字,背景,边框,进度条等
android
GiraKoo2 小时前
【GiraKoo】 C++20的新特性
c++