CarHvacManager.java revision 235f8acd3cf83079ecd0f3e1b8368b0c9886de82
1/* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.car.hardware.hvac; 18 19import android.annotation.SystemApi; 20import android.car.Car; 21import android.car.CarManagerBase; 22import android.car.CarNotConnectedException; 23import android.car.VehicleZoneUtil; 24import android.content.Context; 25import android.os.Handler; 26import android.os.IBinder; 27import android.os.Looper; 28import android.os.Message; 29import android.os.RemoteException; 30import android.util.Log; 31 32import java.lang.ref.WeakReference; 33import java.util.ArrayList; 34import java.util.Arrays; 35import java.util.List; 36 37/** 38 * API for controlling HVAC system in cars 39 * @hide 40 */ 41@SystemApi 42public class CarHvacManager implements CarManagerBase { 43 public final static boolean DBG = true; 44 public final static String TAG = "CarHvacManager"; 45 46 /** 47 * Define types of values that are available. Boolean type will be overloaded as an int for 48 * binder calls, and unpacked inside the HvacManager. 49 */ 50 public static final int PROPERTY_TYPE_BOOLEAN = 0; 51 public static final int PROPERTY_TYPE_FLOAT = 1; 52 public static final int PROPERTY_TYPE_INT = 2; 53 public static final int PROPERTY_TYPE_INT_VECTOR = 3; 54 public static final int PROPERTY_TYPE_FLOAT_VECTOR = 4; 55 56 /** 57 * Global HVAC properties. There is only a single instance in a car. 58 * Global properties are in the range of 0-0x3FFF. 59 */ 60 /** 61 * Mirror defrosters state, bool. 62 */ 63 public static final int HVAC_MIRROR_DEFROSTER_ON = 0x0001; 64 /** 65 * HVAC is in automatic mode, bool. 66 */ 67 public static final int HVAC_AUTOMATIC_MODE_ON = 0x0003; 68 /** 69 * Air recirculation is active, bool. 70 */ 71 public static final int HVAC_AIR_RECIRCULATION_ON = 0x0004; 72 /** 73 * Steering wheel temp: negative values indicate cooling, positive values indicate heat, int. 74 */ 75 public static final int HVAC_STEERING_WHEEL_TEMP = 0x0005; 76 77 /** 78 * The maximum id that can be assigned to global (non-zoned) property. 79 */ 80 public static final int MAX_GLOBAL_PROPERTY_ID = 0x3fff; 81 82 /** 83 * HVAC_ZONED_* represents properties available on a per-zone basis. All zones in a car are 84 * are not required to have the same properties. Zone specific properties start at 0x4000 and 85 * above. 86 * 87 * Temperature setpoint desired by the user, in terms of F or C, depending on TEMP_IS_FARENHEIT. 88 * If temp is celsius, the format is 31.1 (i.e. LSB = 0.5C). int. 89 */ 90 public static final int HVAC_ZONED_TEMP_SETPOINT = 0x4001; 91 /** 92 * Actual zone temperature is read only integer, in terms of F or C, int. 93 */ 94 public static final int HVAC_ZONED_TEMP_ACTUAL = 0x4002; 95 /** 96 * Temperature is in degrees fahrenheit if this is true, bool. 97 */ 98 public static final int HVAC_ZONED_TEMP_IS_FAHRENHEIT = 0x4003; 99 /** 100 * Fan speed setpoint is an integer from 0-n, depending on the number of fan speeds available. 101 * Selection determines the fan position, int. 102 */ 103 public static final int HVAC_ZONED_FAN_SPEED_SETPOINT = 0x4004; 104 /** 105 * Actual fan speed is a read-only value, expressed in RPM, int. 106 */ 107 public static final int HVAC_ZONED_FAN_SPEED_RPM = 0x4005; 108 /** 109 * Fan position available is a bitmask of positions available for each zone, int. 110 */ 111 public static final int HVAC_ZONED_FAN_POSITION_AVAILABLE = 0x4006; 112 /** 113 * Current fan position setting, int. 114 */ 115 public static final int HVAC_ZONED_FAN_POSITION = 0x4007; 116 /** 117 * Seat temperature is negative for cooling, positive for heating. Temperature is a setting, 118 * i.e. -3 to 3 for 3 levels of cooling and 3 levels of heating. int. 119 */ 120 public static final int HVAC_ZONED_SEAT_TEMP = 0x4008; 121 /** 122 * Air conditioner state, bool 123 */ 124 public static final int HVAC_ZONED_AC_ON = 0x4009; 125 /** 126 * Defroster is based off of window position 127 */ 128 public static final int HVAC_WINDOW_DEFROSTER_ON = 0x5001; 129 130 // Minimum supported version of the service. 131 private static final int MIN_SUPPORTED_VERSION = 1; 132 133 // Minimum supported version of the callback. 134 private static final int MIN_SUPPORTED_CALLBACK_VERSION = 1; 135 136 // Constants handled in the handler (see mHandler below). 137 private final static int MSG_HVAC_EVENT = 0; 138 139 public static class CarHvacBaseProperty { 140 protected final int mPropertyId; 141 protected final int mType; 142 protected final int mZones; 143 144 public CarHvacBaseProperty(int propId, int type, int zones) { 145 mPropertyId = propId; 146 mType = type; 147 mZones = zones; 148 } 149 150 public int getPropertyId() { 151 return mPropertyId; 152 } 153 154 public int getType() { 155 return mType; 156 } 157 158 /** 159 * Tells if the given property is zoned property or global property 160 */ 161 public boolean isZonedProperty() { 162 return mPropertyId > MAX_GLOBAL_PROPERTY_ID; 163 } 164 165 /** 166 * Return bit flags of supported zones. 167 */ 168 public int getZones() { return mZones; } 169 170 /** 171 * Return an active zone for Hvac event. This will return only one zone. 172 * If there is no valid zone, this will return 0. 173 */ 174 public int getZone() { 175 if (mZones == 0) { 176 return 0; 177 } 178 int flag = 0x1; 179 for (int i = 0; i < 32; i++) { 180 if ((flag & mZones) != 0) { 181 return flag; 182 } 183 flag <<= 1; 184 } 185 return 0; 186 } 187 188 @Override 189 public String toString() { 190 return "CarHvacBaseProperty [mPropertyId=0x" + Integer.toHexString(mPropertyId) + 191 ", mType=0x" + Integer.toHexString(mType) + 192 ", mZones=0x" + Integer.toHexString(mZones) + "]"; 193 } 194 195 protected void assertZonedProperty() { 196 if (!isZonedProperty()) { 197 throw new IllegalArgumentException( 198 "assertZonedProperty called for non-zoned property 0x" + 199 Integer.toHexString(mPropertyId)); 200 } 201 } 202 203 protected void assertNonZonedProperty() { 204 if (isZonedProperty()) { 205 throw new IllegalArgumentException( 206 "assertNonZonedProperty called for zoned property 0x" + 207 Integer.toHexString(mPropertyId)); 208 } 209 } 210 } 211 212 public static final class CarHvacBooleanProperty extends CarHvacBaseProperty { 213 public CarHvacBooleanProperty(int propId, int zone) { 214 super(propId, PROPERTY_TYPE_BOOLEAN, zone); 215 } 216 } 217 218 public static final class CarHvacFloatProperty extends CarHvacBaseProperty { 219 private final float[] mMaxValues; 220 private final float[] mMinValues; 221 222 public CarHvacFloatProperty(int propId, int zones, float[] maxs, float mins[]) { 223 super(propId, PROPERTY_TYPE_FLOAT, zones); 224 int expectedLength = zones == 0 ? 1 : VehicleZoneUtil.getNumberOfZones(zones); 225 if (maxs.length != expectedLength || mins.length != expectedLength) { 226 throw new IllegalArgumentException("Expected length:" + expectedLength + 227 " while maxs length:" + maxs.length + " mins length:" + mins.length + 228 " property:0x" + Integer.toHexString(propId)); 229 } 230 mMaxValues = maxs; 231 mMinValues = mins; 232 } 233 234 /** 235 * Get max value. Should be used only for non-zoned property. 236 */ 237 public float getMaxValue() { 238 assertNonZonedProperty(); 239 return mMaxValues[0]; 240 } 241 242 /** 243 * Get min value. Should be used only for non-zoned property. 244 */ 245 public float getMinValue() { 246 assertNonZonedProperty(); 247 return mMinValues[0]; 248 } 249 250 public float getMaxValue(int zone) { 251 assertZonedProperty(); 252 return mMaxValues[VehicleZoneUtil.zoneToIndex(mZones, zone)]; 253 } 254 255 public float getMinValue(int zone) { 256 assertZonedProperty(); 257 return mMinValues[VehicleZoneUtil.zoneToIndex(mZones, zone)]; 258 } 259 260 @Override 261 public String toString() { 262 return "CarHvacFloatProperty [mMaxValues=" + Arrays.toString(mMaxValues) 263 + ", mMinValues=" + Arrays.toString(mMinValues) + " " + super.toString() + "]"; 264 } 265 } 266 267 public static final class CarHvacIntProperty extends CarHvacBaseProperty { 268 private int[] mMaxValues; 269 private int[] mMinValues; 270 271 public CarHvacIntProperty(int propId, int zones, int[] maxs, int[] mins) { 272 super(propId, PROPERTY_TYPE_INT, zones); 273 int expectedLength = zones == 0 ? 1 : VehicleZoneUtil.getNumberOfZones(zones); 274 if (maxs.length != expectedLength || mins.length != expectedLength) { 275 throw new IllegalArgumentException("Expected length:" + expectedLength + 276 " while maxs length:" + maxs.length + " mins length:" + mins.length + 277 " property:0x" + Integer.toHexString(propId)); 278 } 279 mMaxValues = maxs; 280 mMinValues = mins; 281 } 282 283 /** 284 * Get max value. Should be used only for non-zoned property. 285 */ 286 public int getMaxValue() { 287 assertNonZonedProperty(); 288 return mMaxValues[0]; 289 } 290 291 /** 292 * Get min value. Should be used only for non-zoned property. 293 */ 294 public int getMinValue() { 295 assertNonZonedProperty(); 296 return mMinValues[0]; 297 } 298 299 public int getMaxValue(int zone) { 300 assertZonedProperty(); 301 return mMaxValues[VehicleZoneUtil.zoneToIndex(mZones, zone)]; 302 } 303 304 public int getMinValue(int zone) { 305 assertZonedProperty(); 306 return mMinValues[VehicleZoneUtil.zoneToIndex(mZones, zone)]; 307 } 308 309 @Override 310 public String toString() { 311 return "CarHvacIntProperty [mMaxValues=" + Arrays.toString(mMaxValues) 312 + ", mMinValues=" + Arrays.toString(mMinValues) + " " + super.toString() + "]"; 313 } 314 } 315 316 public static final class CarHvacBooleanValue extends CarHvacBaseProperty { 317 private boolean mValue; 318 319 public CarHvacBooleanValue(int propId, int zones, boolean value) { 320 super(propId, PROPERTY_TYPE_BOOLEAN, zones); 321 mValue = value; 322 } 323 324 public boolean getValue() { return mValue; } 325 326 @Override 327 public String toString() { 328 return "CarHvacBooleanValue [mValue=" + mValue + " " + super.toString() + "]"; 329 } 330 } 331 332 333 public static final class CarHvacFloatValue extends CarHvacBaseProperty { 334 private float mValue; 335 336 public CarHvacFloatValue(int propId, int zones, float value) { 337 super(propId, PROPERTY_TYPE_FLOAT, zones); 338 mValue = value; 339 } 340 341 public float getValue() { return mValue; } 342 343 @Override 344 public String toString() { 345 return "CarHvacFloatValue [mValue=" + mValue + " " + super.toString() + "]"; 346 } 347 } 348 349 public static final class CarHvacIntValue extends CarHvacBaseProperty { 350 private int mValue; 351 352 public CarHvacIntValue(int propId, int zones, int value) { 353 super(propId, PROPERTY_TYPE_INT, zones); 354 mValue = value; 355 } 356 357 public int getValue() { return mValue; } 358 359 @Override 360 public String toString() { 361 return "CarHvacIntValue [mValue=" + mValue + " " + super.toString() + "]"; 362 } 363 } 364 365 public interface CarHvacEventListener { 366 // Called when an HVAC property is updated 367 void onChangeEvent(final CarHvacBaseProperty value); 368 369 // Called when an error is detected with a property 370 void onErrorEvent(final int propertyId, final int zone); 371 } 372 373 private final ICarHvac mService; 374 private CarHvacEventListener mListener = null; 375 private CarHvacEventListenerToService mListenerToService = null; 376 377 private static final class EventCallbackHandler extends Handler { 378 WeakReference<CarHvacManager> mMgr; 379 380 EventCallbackHandler(CarHvacManager mgr, Looper looper) { 381 super(looper); 382 mMgr = new WeakReference<>(mgr); 383 } 384 385 @Override 386 public void handleMessage(Message msg) { 387 switch (msg.what) { 388 case MSG_HVAC_EVENT: 389 CarHvacManager mgr = mMgr.get(); 390 if (mgr != null) { 391 mgr.dispatchEventToClient((CarHvacEvent) msg.obj); 392 } 393 break; 394 default: 395 Log.e(TAG, "Event type not handled?" + msg); 396 break; 397 } 398 } 399 } 400 401 private final Handler mHandler; 402 403 private static class CarHvacEventListenerToService extends ICarHvacEventListener.Stub { 404 private final WeakReference<CarHvacManager> mManager; 405 406 public CarHvacEventListenerToService(CarHvacManager manager) { 407 mManager = new WeakReference<>(manager); 408 } 409 410 @Override 411 public void onEvent(CarHvacEvent event) { 412 CarHvacManager manager = mManager.get(); 413 if (manager != null) { 414 manager.handleEvent(event); 415 } 416 } 417 } 418 419 /** 420 * Get an instance of the CarHvacManager. 421 * 422 * Should not be obtained directly by clients, use {@link Car.getCarManager()} instead. 423 * @hide 424 */ 425 public CarHvacManager(IBinder service, Context context, Looper looper) { 426 mService = ICarHvac.Stub.asInterface(service); 427 mHandler = new EventCallbackHandler(this, looper); 428 } 429 430 public static boolean isZonedProperty(int propertyId) { 431 return propertyId > MAX_GLOBAL_PROPERTY_ID; 432 } 433 434 /** 435 * Register {@link CarHvacEventListener} to get HVAC property changes 436 * 437 * @param listener Implements onEvent() for property change updates 438 * @return 439 */ 440 public synchronized void registerListener(CarHvacEventListener listener) 441 throws CarNotConnectedException { 442 if (mListener != null) { 443 throw new IllegalStateException("Listener already registered. Did you call " + 444 "registerListener() twice?"); 445 } 446 447 mListener = listener; 448 try { 449 mListenerToService = new CarHvacEventListenerToService(this); 450 mService.registerListener(mListenerToService); 451 } catch (RemoteException e) { 452 Log.e(TAG, "Could not connect: " + e.toString()); 453 mListener = null; 454 throw new CarNotConnectedException(e); 455 } catch (IllegalStateException e) { 456 Car.checkCarNotConnectedExceptionFromCarService(e); 457 } 458 } 459 460 /** 461 * Unregister {@link CarHvacEventListener}. 462 * 463 * @param 464 * @return 465 */ 466 public synchronized void unregisterListener() throws CarNotConnectedException { 467 if (DBG) { 468 Log.d(TAG, "unregisterListener"); 469 } 470 try { 471 mService.unregisterListener(mListenerToService); 472 } catch (RemoteException e) { 473 Log.e(TAG, "Could not unregister: " + e.toString()); 474 throw new CarNotConnectedException(e); 475 476 } 477 mListenerToService = null; 478 mListener = null; 479 } 480 481 /** 482 * Returns the list of HVAC properties available. 483 * 484 * @return Caller must check the property type and typecast to the appropriate subclass 485 * (CarHvacBooleanProperty, CarHvacFloatProperty, CarrHvacIntProperty) 486 */ 487 public List<CarHvacBaseProperty> getPropertyList() throws CarNotConnectedException { 488 List<CarHvacBaseProperty> hvacProps = new ArrayList<>(); 489 List<CarHvacProperty> carProps; 490 try { 491 carProps = mService.getHvacProperties(); 492 } catch (RemoteException e) { 493 Log.w(TAG, "Exception in getPropertyList", e); 494 throw new CarNotConnectedException(e); 495 } 496 497 for (CarHvacProperty carProp : carProps) { 498 switch (carProp.getType()) { 499 case PROPERTY_TYPE_BOOLEAN: { 500 CarHvacBooleanProperty newProp = 501 new CarHvacBooleanProperty(carProp.getPropertyId(), carProp.getZones()); 502 hvacProps.add(newProp); 503 } break; 504 case PROPERTY_TYPE_FLOAT: { 505 CarHvacFloatProperty newProp = 506 new CarHvacFloatProperty(carProp.getPropertyId(), carProp.getZones(), 507 carProp.getFloatMaxs(), carProp.getFloatMins()); 508 hvacProps.add(newProp); 509 } break; 510 case PROPERTY_TYPE_INT: { 511 CarHvacIntProperty newProp = 512 new CarHvacIntProperty(carProp.getPropertyId(), carProp.getZones(), 513 carProp.getIntMaxs(), carProp.getIntMins()); 514 hvacProps.add(newProp); 515 } break; 516 } 517 } 518 return hvacProps; 519 } 520 521 /** 522 * Returns value of a bool property 523 * 524 * @param prop Property ID to get 525 * @param zone Zone of the property to get 526 * @return 527 */ 528 public boolean getBooleanProperty(int prop, int zone) throws CarNotConnectedException { 529 CarHvacProperty carProp; 530 if (DBG) { 531 Log.d(TAG, "getBooleanProperty: prop = " + prop + " zone = " + zone); 532 } 533 try { 534 carProp = mService.getProperty(prop, zone); 535 } catch (RemoteException e) { 536 Log.e(TAG, "getProperty failed with " + e.toString()); 537 throw new CarNotConnectedException(e); 538 } 539 540 if (carProp.getType() == PROPERTY_TYPE_BOOLEAN) { 541 return carProp.getBooleanValue(); 542 } else { 543 throw new IllegalArgumentException(); 544 } 545 } 546 547 /** 548 * Returns value of a float property 549 * 550 * @param prop Property ID to get 551 * @param zone Zone of the property to get 552 * @return 553 */ 554 public float getFloatProperty(int prop, int zone) throws CarNotConnectedException { 555 CarHvacProperty carProp; 556 if (DBG) { 557 Log.d(TAG, "getFloatProperty: prop = " + prop + " zone = " + zone); 558 } 559 try { 560 carProp = mService.getProperty(prop, zone); 561 } catch (RemoteException e) { 562 Log.e(TAG, "getProperty failed with " + e.toString()); 563 throw new CarNotConnectedException(e); 564 } 565 566 if (carProp.getType() == PROPERTY_TYPE_FLOAT) { 567 return carProp.getFloatValue(); 568 } else { 569 throw new IllegalArgumentException(); 570 } 571 } 572 573 /** 574 * Returns value of a integer property 575 * 576 * @param prop Property ID to get 577 * @param zone Zone of the property to get 578 * @return 579 */ 580 public int getIntProperty(int prop, int zone) throws CarNotConnectedException { 581 CarHvacProperty carProp; 582 if (DBG) { 583 Log.d(TAG, "getIntProperty: prop = " + prop + " zone = " + zone); 584 } 585 try { 586 carProp = mService.getProperty(prop, zone); 587 } catch (RemoteException e) { 588 Log.e(TAG, "getProperty failed with " + e.toString()); 589 throw new CarNotConnectedException(e); 590 } 591 592 if (carProp.getType() == PROPERTY_TYPE_INT) { 593 return carProp.getIntValue(); 594 } else { 595 throw new IllegalArgumentException(); 596 } 597 } 598 599 600 /** 601 * Modifies a property. If the property modification doesn't occur, an error event shall be 602 * generated and propagated back to the application. 603 * 604 * @param prop Property ID to modify 605 * @param zone Zone(s) to apply the modification. Multiple zones may be OR'd together 606 * @param val Value to set 607 */ 608 public void setBooleanProperty(int prop, int zone, boolean val) 609 throws CarNotConnectedException { 610 if (DBG) { 611 Log.d(TAG, "setBooleanProperty: prop = " + prop + " zone = " + zone + " val = " + val); 612 } 613 try { 614 CarHvacProperty carProp = new CarHvacProperty(prop, zone, val); 615 mService.setProperty(carProp); 616 } catch (RemoteException e) { 617 Log.e(TAG, "setBooleanProperty failed with " + e.toString()); 618 throw new CarNotConnectedException(e); 619 } 620 } 621 622 public void setFloatProperty(int prop, int zone, float val) throws CarNotConnectedException { 623 if (DBG) { 624 Log.d(TAG, "setFloatProperty: prop = " + prop + " zone = " + zone + " val = " + val); 625 } 626 try { 627 // Set floatMin and floatMax to 0, as they are ignored in set() 628 CarHvacProperty carProp = new CarHvacProperty(prop, zone, 629 new float[] { 0 }, new float[] { 0 }, val); 630 mService.setProperty(carProp); 631 } catch (RemoteException e) { 632 Log.e(TAG, "setFloatProperty failed with " + e.toString()); 633 throw new CarNotConnectedException(e); 634 } 635 } 636 637 public void setIntProperty(int prop, int zone, int val) throws CarNotConnectedException { 638 if (DBG) { 639 Log.d(TAG, "setIntProperty: prop = " + prop + " zone = " + zone + " val = " + val); 640 } 641 try { 642 // Set intMin and intMax to 0, as they are ignored in set() 643 CarHvacProperty carProp = new CarHvacProperty(prop, zone, 644 new int[] { 0 }, new int[] { 0 }, val); 645 mService.setProperty(carProp); 646 } catch (RemoteException e) { 647 Log.e(TAG, "setIntProperty failed with " + e.toString()); 648 throw new CarNotConnectedException(e); 649 } 650 } 651 652 private void dispatchEventToClient(CarHvacEvent event) { 653 CarHvacEventListener listener = null; 654 synchronized (this) { 655 listener = mListener; 656 } 657 if (listener != null) { 658 int propertyId = event.getPropertyId(); 659 int zone = event.getZone(); 660 661 switch(event.getEventType()) { 662 case CarHvacEvent.HVAC_EVENT_PROPERTY_CHANGE: 663 CarHvacBaseProperty value = null; 664 switch(event.getPropertyType()) { 665 case PROPERTY_TYPE_BOOLEAN: { 666 value = new CarHvacBooleanValue(propertyId, zone, 667 event.getIntValue() == 1); 668 break; 669 } 670 case PROPERTY_TYPE_FLOAT: { 671 value = new CarHvacFloatValue(propertyId, zone, event.getFloatValue()); 672 break; 673 } 674 case PROPERTY_TYPE_INT: { 675 value = new CarHvacIntValue(propertyId, zone, event.getIntValue()); 676 break; 677 } 678 default: 679 throw new IllegalArgumentException(); 680 } 681 listener.onChangeEvent(value); 682 break; 683 case CarHvacEvent.HVAC_EVENT_ERROR: 684 listener.onErrorEvent(propertyId, zone); 685 break; 686 default: 687 throw new IllegalArgumentException(); 688 } 689 } else { 690 Log.e(TAG, "Listener died, not dispatching event."); 691 } 692 } 693 694 private void handleEvent(CarHvacEvent event) { 695 mHandler.sendMessage(mHandler.obtainMessage(MSG_HVAC_EVENT, event)); 696 } 697 698 /** @hide */ 699 @Override 700 public void onCarDisconnected() { 701 if (mListener != null) { 702 try { 703 unregisterListener(); 704 } catch (CarNotConnectedException e) { 705 // ignore, as car is already disconnected. 706 } 707 } 708 } 709} 710