Home | History | Annotate | Download | only in hvac
      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 
     17 package android.car.hardware.hvac;
     18 
     19 import static java.lang.Integer.toHexString;
     20 
     21 import android.annotation.IntDef;
     22 import android.annotation.Nullable;
     23 import android.annotation.SystemApi;
     24 import android.car.Car;
     25 import android.car.CarManagerBase;
     26 import android.car.CarNotConnectedException;
     27 import android.car.hardware.CarPropertyConfig;
     28 import android.car.hardware.CarPropertyValue;
     29 import android.content.Context;
     30 import android.os.Handler;
     31 import android.os.IBinder;
     32 import android.os.Looper;
     33 import android.os.Message;
     34 import android.os.RemoteException;
     35 import android.util.ArraySet;
     36 import android.util.Log;
     37 
     38 import java.lang.ref.WeakReference;
     39 import java.util.Collection;
     40 import java.util.List;
     41 
     42 /**
     43  * API for controlling HVAC system in cars
     44  * @hide
     45  */
     46 @SystemApi
     47 public class CarHvacManager implements CarManagerBase {
     48     public final static boolean DBG = true;
     49     public final static String TAG = "CarHvacManager";
     50 
     51     /**
     52      * HVAC property IDs for get/set methods
     53      */
     54     @IntDef({
     55             HvacPropertyId.MIRROR_DEFROSTER_ON,
     56             HvacPropertyId.STEERING_WHEEL_TEMP,
     57             HvacPropertyId.MAX_GLOBAL_PROPERTY_ID,
     58             HvacPropertyId.ZONED_TEMP_SETPOINT,
     59             HvacPropertyId.ZONED_TEMP_ACTUAL,
     60             HvacPropertyId.ZONED_TEMP_IS_FAHRENHEIT,
     61             HvacPropertyId.ZONED_FAN_SPEED_SETPOINT,
     62             HvacPropertyId.ZONED_FAN_SPEED_RPM,
     63             HvacPropertyId.ZONED_FAN_POSITION_AVAILABLE,
     64             HvacPropertyId.ZONED_FAN_POSITION,
     65             HvacPropertyId.ZONED_SEAT_TEMP,
     66             HvacPropertyId.ZONED_AC_ON,
     67             HvacPropertyId.ZONED_AUTOMATIC_MODE_ON,
     68             HvacPropertyId.ZONED_AIR_RECIRCULATION_ON,
     69             HvacPropertyId.WINDOW_DEFROSTER_ON,
     70     })
     71     public @interface HvacPropertyId {
     72         /**
     73          * Global HVAC properties.  There is only a single instance in a car.
     74          * Global properties are in the range of 0-0x3FFF.
     75          */
     76         /** Mirror defrosters state, bool. */
     77         int MIRROR_DEFROSTER_ON = 0x0001;
     78         /** Steering wheel temp:  negative values indicate cooling, positive values indicate
     79          * heat, int. */
     80         int STEERING_WHEEL_TEMP = 0x0002;
     81 
     82         /** The maximum id that can be assigned to global (non-zoned) property. */
     83         int MAX_GLOBAL_PROPERTY_ID = 0x3fff;
     84 
     85         /**
     86          * ZONED_* represents properties available on a per-zone basis.  All zones in a car are
     87          * not required to have the same properties.  Zone specific properties start at 0x4000.
     88          */
     89         /** Temperature setpoint desired by the user, in terms of F or C, depending on
     90          * TEMP_IS_FAHRENHEIT, int */
     91         int ZONED_TEMP_SETPOINT = 0x4001;
     92         /** Actual zone temperature is read only integer, in terms of F or C, int. */
     93         int ZONED_TEMP_ACTUAL = 0x4002;
     94         /** Temperature is in degrees fahrenheit if this is true, bool. */
     95         int ZONED_TEMP_IS_FAHRENHEIT = 0x4003;
     96         /** Fan speed setpoint is an integer from 0-n, depending on the number of fan speeds
     97          * available. Selection determines the fan position, int. */
     98         int ZONED_FAN_SPEED_SETPOINT = 0x4004;
     99         /** Actual fan speed is a read-only value, expressed in RPM, int. */
    100         int ZONED_FAN_SPEED_RPM = 0x4005;
    101         /** Fan position available is a bitmask of positions available for each zone, int. */
    102         int ZONED_FAN_POSITION_AVAILABLE = 0x4006;
    103         /** Current fan position setting, int. */
    104         int ZONED_FAN_POSITION = 0x4007;
    105         /** Seat temperature is negative for cooling, positive for heating.  Temperature is a
    106          * setting, i.e. -3 to 3 for 3 levels of cooling and 3 levels of heating.  int. */
    107         int ZONED_SEAT_TEMP = 0x4008;
    108         /** Air conditioner state, bool */
    109         int ZONED_AC_ON = 0x4009;
    110         /** HVAC is in automatic mode, bool. */
    111         int ZONED_AUTOMATIC_MODE_ON = 0x400A;
    112         /** Air recirculation is active, bool. */
    113         int ZONED_AIR_RECIRCULATION_ON = 0x400B;
    114         /** Defroster is based off of window position, bool */
    115         int WINDOW_DEFROSTER_ON = 0x5001;
    116     }
    117 
    118     // Constants handled in the handler (see mHandler below).
    119     private final static int MSG_HVAC_EVENT = 0;
    120 
    121     /** Callback functions for HVAC events */
    122     public interface CarHvacEventListener {
    123         /** Called when an HVAC property is updated */
    124         void onChangeEvent(final CarPropertyValue value);
    125 
    126         /** Called when an error is detected with a property */
    127         void onErrorEvent(final int propertyId, final int zone);
    128     }
    129 
    130     private final ICarHvac mService;
    131     private final ArraySet<CarHvacEventListener> mListeners = new ArraySet<>();
    132     private CarHvacEventListenerToService mListenerToService = null;
    133 
    134     private static final class EventCallbackHandler extends Handler {
    135         WeakReference<CarHvacManager> mMgr;
    136 
    137         EventCallbackHandler(CarHvacManager mgr, Looper looper) {
    138             super(looper);
    139             mMgr = new WeakReference<>(mgr);
    140         }
    141 
    142         @Override
    143         public void handleMessage(Message msg) {
    144             switch (msg.what) {
    145                 case MSG_HVAC_EVENT:
    146                     CarHvacManager mgr = mMgr.get();
    147                     if (mgr != null) {
    148                         mgr.dispatchEventToClient((CarHvacEvent) msg.obj);
    149                     }
    150                     break;
    151                 default:
    152                     Log.e(TAG, "Event type not handled?" + msg);
    153                     break;
    154             }
    155         }
    156     }
    157 
    158     private final Handler mHandler;
    159 
    160     private static class CarHvacEventListenerToService extends ICarHvacEventListener.Stub {
    161         private final WeakReference<CarHvacManager> mManager;
    162 
    163         public CarHvacEventListenerToService(CarHvacManager manager) {
    164             mManager = new WeakReference<>(manager);
    165         }
    166 
    167         @Override
    168         public void onEvent(CarHvacEvent event) {
    169             CarHvacManager manager = mManager.get();
    170             if (manager != null) {
    171                 manager.handleEvent(event);
    172             }
    173         }
    174     }
    175 
    176     /**
    177      * Get an instance of the CarHvacManager.
    178      *
    179      * Should not be obtained directly by clients, use {@link Car#getCarManager(String)} instead.
    180      * @hide
    181      */
    182     public CarHvacManager(IBinder service, Context context, Looper looper) {
    183         mService = ICarHvac.Stub.asInterface(service);
    184         mHandler = new EventCallbackHandler(this, looper);
    185     }
    186 
    187     /** Returns true if the property is a zoned type. */
    188     public static boolean isZonedProperty(int propertyId) {
    189         return propertyId > HvacPropertyId.MAX_GLOBAL_PROPERTY_ID;
    190     }
    191 
    192     /**
    193      * Register {@link CarHvacEventListener} to get HVAC property changes
    194      *
    195      * @param listener Implements onEvent() for property change updates
    196      */
    197     public synchronized void registerListener(CarHvacEventListener listener)
    198             throws CarNotConnectedException {
    199         if(mListeners.isEmpty()) {
    200             try {
    201                 mListenerToService = new CarHvacEventListenerToService(this);
    202                 mService.registerListener(mListenerToService);
    203             } catch (RemoteException ex) {
    204                 Log.e(TAG, "Could not connect: " + ex.toString());
    205                 throw new CarNotConnectedException(ex);
    206             } catch (IllegalStateException ex) {
    207                 Car.checkCarNotConnectedExceptionFromCarService(ex);
    208             }
    209         }
    210         mListeners.add(listener);
    211     }
    212 
    213     /**
    214      * Unregister {@link CarHvacEventListener}.
    215      * @param listener CarHvacEventListener to unregister
    216      */
    217     public synchronized void unregisterListener(CarHvacEventListener listener)
    218             throws CarNotConnectedException {
    219         if (DBG) {
    220             Log.d(TAG, "unregisterListener");
    221         }
    222         try {
    223             mService.unregisterListener(mListenerToService);
    224         } catch (RemoteException e) {
    225             Log.e(TAG, "Could not unregister: " + e.toString());
    226             throw new CarNotConnectedException(e);
    227 
    228         }
    229         mListeners.remove(listener);
    230         if(mListeners.isEmpty()) {
    231             mListenerToService = null;
    232         }
    233     }
    234 
    235     /**
    236      * Returns the list of HVAC properties available.
    237      *
    238      * @return Caller must check the property type and typecast to the appropriate subclass
    239      * (CarHvacBooleanProperty, CarHvacFloatProperty, CarrHvacIntProperty)
    240      */
    241     public List<CarPropertyConfig> getPropertyList()  throws CarNotConnectedException {
    242         List<CarPropertyConfig> carProps;
    243         try {
    244             carProps = mService.getHvacProperties();
    245         } catch (RemoteException e) {
    246             Log.w(TAG, "Exception in getPropertyList", e);
    247             throw new CarNotConnectedException(e);
    248         }
    249         return carProps;
    250     }
    251 
    252     /**
    253      * Returns value of a bool property
    254      *
    255      * @param prop Property ID to get
    256      * @param area Area of the property to get
    257      */
    258     public boolean getBooleanProperty(int prop, int area) throws CarNotConnectedException {
    259         CarPropertyValue<Boolean> carProp = getProperty(Boolean.class, prop, area);
    260         return carProp != null ? carProp.getValue() : false;
    261     }
    262 
    263     /**
    264      * Returns value of a float property
    265      *
    266      * @param prop Property ID to get
    267      * @param area Area of the property to get
    268      */
    269     public float getFloatProperty(int prop, int area) throws CarNotConnectedException {
    270         CarPropertyValue<Float> carProp = getProperty(Float.class, prop, area);
    271         return carProp != null ? carProp.getValue() : 0f;
    272     }
    273 
    274     /**
    275      * Returns value of a integer property
    276      *
    277      * @param prop Property ID to get
    278      * @param area Zone of the property to get
    279      */
    280     public int getIntProperty(int prop, int area) throws CarNotConnectedException {
    281         CarPropertyValue<Integer> carProp = getProperty(Integer.class, prop, area);
    282         return carProp != null ? carProp.getValue() : 0;
    283     }
    284 
    285     @Nullable
    286     @SuppressWarnings("unchecked")
    287     private <E> CarPropertyValue<E> getProperty(Class<E> clazz, int prop, int area)
    288             throws CarNotConnectedException {
    289         if (DBG) {
    290             Log.d(TAG, "getProperty, prop: 0x" + toHexString(prop)
    291                     + ", area: 0x" + toHexString(area) + ", clazz: " + clazz);
    292         }
    293         try {
    294             CarPropertyValue<E> hvacProperty = mService.getProperty(prop, area);
    295             if (hvacProperty != null && hvacProperty.getValue() != null) {
    296                 Class<?> actualClass = hvacProperty.getValue().getClass();
    297                 if (actualClass != clazz) {
    298                 throw new IllegalArgumentException("Invalid property type. "
    299                         + "Expected: " + clazz + ", but was: " + actualClass);
    300                 }
    301             }
    302             return hvacProperty;
    303         } catch (RemoteException e) {
    304             Log.e(TAG, "getProperty failed with " + e.toString()
    305                     + ", propId: 0x" + toHexString(prop) + ", area: 0x" + toHexString(area), e);
    306             throw new CarNotConnectedException(e);
    307         }
    308     }
    309 
    310     /**
    311      * Modifies a property.  If the property modification doesn't occur, an error event shall be
    312      * generated and propagated back to the application.
    313      *
    314      * @param prop Property ID to modify
    315      * @param area Area to apply the modification.
    316      * @param val Value to set
    317      */
    318     public void setBooleanProperty(int prop, int area, boolean val)
    319             throws CarNotConnectedException {
    320         if (DBG) {
    321             Log.d(TAG, "setBooleanProperty:  prop = " + prop + " area = " + area + " val = " + val);
    322         }
    323         try {
    324             mService.setProperty(new CarPropertyValue<>(prop, area, val));
    325         } catch (RemoteException e) {
    326             Log.e(TAG, "setBooleanProperty failed with " + e.toString(), e);
    327             throw new CarNotConnectedException(e);
    328         }
    329     }
    330 
    331     public void setFloatProperty(int prop, int area, float val) throws CarNotConnectedException {
    332         if (DBG) {
    333             Log.d(TAG, "setFloatProperty:  prop = " + prop + " area = " + area + " val = " + val);
    334         }
    335         try {
    336             mService.setProperty(new CarPropertyValue<>(prop, area, val));
    337         } catch (RemoteException e) {
    338             Log.e(TAG, "setBooleanProperty failed with " + e.toString(), e);
    339             throw new CarNotConnectedException(e);
    340         }
    341     }
    342 
    343     public void setIntProperty(int prop, int area, int val) throws CarNotConnectedException {
    344         if (DBG) {
    345             Log.d(TAG, "setIntProperty:  prop = " + prop + " area = " + area + " val = " + val);
    346         }
    347         try {
    348             mService.setProperty(new CarPropertyValue<>(prop, area, val));
    349         } catch (RemoteException e) {
    350             Log.e(TAG, "setIntProperty failed with " + e.toString(), e);
    351             throw new CarNotConnectedException(e);
    352         }
    353     }
    354 
    355     private void dispatchEventToClient(CarHvacEvent event) {
    356         Collection<CarHvacEventListener> listeners;
    357         synchronized (this) {
    358             listeners = mListeners;
    359         }
    360         if (!listeners.isEmpty()) {
    361             CarPropertyValue hvacProperty = event.getCarPropertyValue();
    362             switch(event.getEventType()) {
    363                 case CarHvacEvent.HVAC_EVENT_PROPERTY_CHANGE:
    364                     for (CarHvacEventListener l: listeners) {
    365                         l.onChangeEvent(hvacProperty);
    366                     }
    367                 case CarHvacEvent.HVAC_EVENT_ERROR:
    368                     for (CarHvacEventListener l: listeners) {
    369                         l.onErrorEvent(hvacProperty.getPropertyId(), hvacProperty.getAreaId());
    370                     }
    371                     break;
    372                 default:
    373                     throw new IllegalArgumentException();
    374             }
    375         } else {
    376             Log.e(TAG, "Listener died, not dispatching event.");
    377         }
    378     }
    379 
    380     private void handleEvent(CarHvacEvent event) {
    381         mHandler.sendMessage(mHandler.obtainMessage(MSG_HVAC_EVENT, event));
    382     }
    383 
    384     /** @hide */
    385     @Override
    386     public void onCarDisconnected() {
    387         for(CarHvacEventListener l: mListeners) {
    388             try {
    389                 unregisterListener(l);
    390             } catch (CarNotConnectedException e) {
    391                 // Ignore, car is disconnecting.
    392             }
    393         }
    394     }
    395 }
    396