Control del módulo HVAC automotriz mediante Android
![](https://i1.wp.com/grapeup.com/wp-content/uploads/2024/05/VHAL_2-kopia-16.jpg?w=1920&resize=1920,960&ssl=1)
En el diseño de automóviles moderno, el control de varios componentes de un vehículo a través de un dispositivo móvil se ha convertido en una tendencia importante, lo que mejora la experiencia y la comodidad del usuario. Uno de esos componentes es el sistema HVAC (calefacción, ventilación y aire acondicionado), que desempeña un papel crucial para garantizar la comodidad de los pasajeros. En este artículo, exploraremos cómo controlar el módulo HVAC en un automóvil usando un dispositivo Android, aprovechando el poder del protocolo SOME/IP.
Entendemos HVAC
HVAC significa Calefacción, Ventilación y Aire Acondicionado. En el ámbito de la ingeniería automovilística, el sistema HVAC regula la temperatura, la humedad y la calidad del aire en el habitáculo del vehículo. Incluye componentes como calentadores, aires acondicionados, ventiladores y filtros de aire. El control del sistema HVAC contribuye eficientemente al confort y seguridad de los pasajeros durante el viaje.
Introducción a XI/IP
En el paradigma SOME/IP, la comunicación se estructura en torno a servicios, que encapsulan funcionalidades específicas o intercambios de datos. Hay dos roles principales dentro del modelo orientado a servicios:
Proveedor: El proveedor es responsable de ofrecer servicios a otras ECU dentro de la red. En el contexto automotriz, la ECU de un proveedor puede controlar actuadores físicos, leer datos de sensores o realizar otras tareas relacionadas con el funcionamiento del vehículo. Por ejemplo, en nuestro caso, el proveedor sería una aplicación que se ejecuta en un controlador de dominio en el vehículo.
El proveedor ofrece servicios exponiendo interfaces que definen los métodos o estructuras de datos disponibles para la interacción. Estas interfaces pueden incluir operaciones para controlar actuadores (por ejemplo, configuraciones de HVAC) o métodos para leer datos de sensores (por ejemplo, temperatura, humedad).
Consumidor: El consumidor, por otra parte, es una ECU que utiliza servicios proporcionados por otras ECU de la red. Los consumidores pueden suscribirse a servicios específicos ofrecidos por proveedores para recibir actualizaciones o invocar métodos según sea necesario. En el contexto automotriz, un consumidor puede ser responsable de interpretar los datos de los sensores, enviar comandos de control o realizar otras tareas basadas en la información recibida.
Los consumidores se suscriben a los servicios que les interesan y reciben actualizaciones cada vez que hay nuevos datos disponibles. También podrán invocar métodos proporcionados por el proveedor de servicios para iniciar acciones o funcionalidades de control. En nuestro escenario, el consumidor sería una aplicación que se ejecuta en Android VHAL (Vehicle Hardware Abstraction Layer), responsable de interactuar con la red del vehículo y controlar la configuración de HVAC.
Flujo de comunicación XI/IP
El flujo de comunicación en XI/IP sigue un modelo de publicación-suscripción, donde los proveedores publican datos o servicios y los consumidores se suscriben a ellos para recibir actualizaciones o invocar métodos. Este modelo de comunicación asíncrona permite una interacción eficiente y flexible entre las ECU dentro de la red.
![diagrama de flujo de comunicación en IP](https://grapeup.com/wp-content/uploads/2024/05/IP-Communication-Flow-1024x484.jpg)
![diagrama de flujo de comunicación en IP](https://grapeup.com/wp-content/uploads/2024/05/IP-Communication-Flow-1024x484.jpg)
Fuente: https://github.com/COVESA/vsomeip/wiki/vsomeip-in-10-minutos
En nuestro caso, la aplicación que se ejecuta en el controlador de dominio (proveedor) publica datos de sensores como temperatura, humedad y estado de HVAC. Los consumidores suscritos, como la aplicación VHAL en Android, reciben estas actualizaciones y pueden enviar comandos de control al controlador de dominio para ajustar la configuración de HVAC según la entrada del usuario.
Aprovechando VHAL en Android para redes de vehículos
Para comunicarse con la red del vehículo, Android proporciona la capa de abstracción de hardware del vehículo (VHAL). VHAL actúa como puente entre el sistema operativo Android y los sistemas a bordo del vehículo, permitiendo una integración perfecta de los dispositivos Android con las funcionalidades del vehículo. VHAL abstrae las complejidades de los protocolos de redes de vehículos, lo que permite a los desarrolladores centrarse en implementar funciones como el control HVAC sin preocuparse por los detalles de comunicación de bajo nivel.
![Diagrama de arquitectura HVAC](https://grapeup.com/wp-content/uploads/2024/05/Leveraging-VHAL-in-Android-for-Vehicle-Networking-1019x1024.jpg)
![Diagrama de arquitectura HVAC](https://grapeup.com/wp-content/uploads/2024/05/Leveraging-VHAL-in-Android-for-Vehicle-Networking-1019x1024.jpg)
Fuente: https://source.android.com/docs/automotive/vhal/previous/properties
Implementar el consumidor SOMEIP en VHAL
Para integrar un consumidor SOMEIP en VHAL en Android 14, usaremos la biblioteca vsomeip. A continuación se detallan los pasos necesarios para implementar esta solución:
Clonación del repositorio vsomeip
Vaya al directorio principal de su proyecto de Android y cree un nuevo directorio llamado external/sdv:
mkdir -p external/sdv
cd external/sdv
git clone https://android.googlesource.com/platform/external/sdv/vsomeip
Implementar el consumidor SOMEIP en VHAL
Jul hardware/interfaces/coches/vehículo/2.0/default directorio, puede encontrar el código de la aplicación VHAL. Jul ServicioVehículo.cpp archivo, encontrará la implementación VHAL predeterminada.
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;
}
La implementación VHAL predeterminada se proporciona en PredeterminadoVehículoHal que queremos cambiar en ella ServicioVehículo.cpp.
De:
auto hal = std::make_unique<DefaultVehicleHal>(store.get(), connector.get());
A:
auto hal = std::make_unique<VendorVehicleHal>(store.get(), connector.get());
Para nuestra implementación, crearemos una clase llamada ProveedorVehículoHal y heredar de la PredeterminadoVehículoHal clase. Superaremos el conjunto y conseguiremos funciones.
class VendorVehicleHal : public DefaultVehicleHal {
public:
VendorVehicleHal(VehiclePropertyStore* propStore, VehicleHalClient* client);
VehiclePropValuePtr get(const VehiclePropValue& requestedPropValue,
StatusCode* outStatus) override;
StatusCode set(const VehiclePropValue& propValue) override;
};
La función get se invoca cuando el sistema Android solicita información de VHAL y se configura cuando quiere configurarla. Los datos se transmiten en un objeto VehiclePropValue definido en hardware/interfaces/automotive/vehicle/2.0/types.hal.
Contiene una variable, prop, que es nuestro identificador de propiedad. La lista de todas las propiedades se puede encontrar en el archivo tipos.hal.
Solo filtraremos los valores de interés y redirigiremos el resto a la implementación predeterminada.
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);
}
Ahora necesitamos crear un consumidor de servicios XI/IP. Si no está familiarizado con el protocolo SOME/IP o la biblioteca vsomeip, le recomiendo leer la guía «vsomeip en 10 minutos».
Proporciona una descripción paso a paso de cómo crear un proveedor y un consumidor para XI/IP.
En nuestro ejemplo, crearemos una clase llamada ZoneHVACService y definiremos ALGUNOS ID/IP del servicio, instancia, método y evento:
#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;
};
En nuestro ejemplo, conectaremos ZoneHVACService y VendorVehicleHal mediante devoluciones de llamada.
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);
};
Lo último que nos queda por hacer es crear una configuración para la biblioteca vsomeip. Lo mejor es utilizar un archivo de muestra de la biblioteca: https://github.com/COVESA/vsomeip/blob/master/config/vsomeip-local.json
En este archivo, debe cambiar la dirección:
«unidifusión»: «10.0.2.15»,
en la dirección de nuestro dispositivo Android.
Además, debes configurar:
«enrutamiento»: «muestra de servicio»,
para el nombre de nuestra aplicación.
La pila vsomeip lee la dirección de la aplicación y la ruta al archivo de configuración desde la variable de entorno. La forma más sencilla de hacer esto en Android es detenerse antes de crear el objeto ZoneHVACService.
setenv("VSOMEIP_CONFIGURATION","/vendor/etc/vsomeip-local-hvac.json",1);
setenv("VSOMEIP_APPLICATION_NAME," "hvac-service",1);
Eso es todo. Ahora tenemos que reemplazar proveedor/bin/hw/android.hardware.automotive.vehicle@2.0-default-service con nuestra nueva compilación y reinicie Android.
Si todo se configuró correctamente, deberíamos ver registros como estos y el proveedor debería recibir nuestras solicitudes.
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
Resumen
En conclusión, la integración de dispositivos Android con Vehicle Hardware Abstraction Layer (VHAL) para el control de sistemas HVAC abre un nuevo campo de posibilidades para la tecnología automotriz. Al aprovechar el poder del protocolo de comunicación SOME/IP y la biblioteca vsomeip, los desarrolladores pueden crear soluciones sólidas para administrar las funcionalidades HVAC de los vehículos.
Siguiendo los pasos descritos en este artículo, los desarrolladores pueden crear implementaciones VHAL personalizadas adaptadas a sus necesidades específicas. Desde la definición de interfaces de servicio hasta el manejo de devoluciones de llamadas de comunicación, cada aspecto del proceso de integración se ha explicado cuidadosamente para facilitar un desarrollo fluido.
A medida que la tecnología automotriz continúa evolucionando, la convergencia de dispositivos Android y sistemas de vehículos representa un paso significativo en el camino hacia vehículos más inteligentes y conectados. La integración de funcionalidades de control de HVAC a través de VHAL y SOME/IP no sólo demuestra el potencial de la tecnología automotriz moderna sino que también allana el camino para futuras innovaciones en este campo.