Advertisement
Guest User

Untitled

a guest
Feb 6th, 2019
157
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. // @flow
  2.  
  3. require('@babel/polyfill');
  4.  
  5. import type { ClimateState, Vehicle, VehicleState } from './util/types';
  6. import {wait} from './util/wait';
  7. import api from './util/api';
  8. import {lock} from './util/mutex';
  9. import callbackify from './util/callbackify';
  10.  
  11. const util = require('util');
  12. const tesla = require('teslajs');
  13.  
  14. let Service, Characteristic;
  15.  
  16. export default function(homebridge: Object) {
  17.   Service = homebridge.hap.Service;
  18.   Characteristic = homebridge.hap.Characteristic;
  19.  
  20.   homebridge.registerAccessory("homebridge-tesla", "Tesla", TeslaAccessory);
  21. }
  22.  
  23. class TeslaAccessory {
  24.   // From config.
  25.   log: Function;
  26.   name: string;
  27.   frunk: ?string;
  28.   trunk: ?string;
  29.   chargePort: ?string;
  30.   vin: string;
  31.   username: string;
  32.   password: string;
  33.   waitMinutes: number;
  34.  
  35.   // Runtime state.
  36.   authToken: ?string;
  37.   vehicleID: ?string;
  38.  
  39.   // Services exposed.
  40.   lockService: Object;
  41.   frunkService: ?Object;
  42.   trunkService: ?Object;
  43.   chargePortService: ?Object;
  44.   climateService: Object;
  45.  
  46.   constructor(log, config) {
  47.     this.log = log;
  48.     this.name = config["name"];
  49.     this.frunk = config["frunk"];
  50.     this.trunk = config["trunk"];
  51.     this.chargePort = config["chargePort"];
  52.     this.vin = config["vin"];
  53.     this.username = config["username"];
  54.     this.password = config["password"];
  55.     this.waitMinutes = config["waitMinutes"] || 1; // default to one minute.
  56.  
  57.     const lockService = new Service.LockMechanism(this.name, 'vehicle');
  58.  
  59.     lockService
  60.       .getCharacteristic(Characteristic.LockCurrentState)
  61.       .on('get', callbackify(this.getLockCurrentState));
  62.  
  63.     lockService
  64.       .getCharacteristic(Characteristic.LockTargetState)
  65.       .on('get', callbackify(this.getLockTargetState))
  66.       .on('set', callbackify(this.setLockTargetState));
  67.  
  68.     this.lockService = lockService;
  69.  
  70.     const climateService = new Service.Switch(this.name);
  71.  
  72.     climateService
  73.       .getCharacteristic(Characteristic.On)
  74.       .on('get', callbackify(this.getClimateOn))
  75.       .on('set', callbackify(this.setClimateOn));
  76.  
  77.     this.climateService = climateService;
  78.  
  79.     if (this.frunk) {
  80.       // Enable the front trunk lock service if requested. Use the name given
  81.       // in your config.
  82.       const frunkService = new Service.LockMechanism(this.frunk, 'frunk');
  83.  
  84.       frunkService
  85.         .getCharacteristic(Characteristic.LockCurrentState)
  86.         .on('get', callbackify(this.getFrunkCurrentState));
  87.  
  88.       frunkService
  89.         .getCharacteristic(Characteristic.LockTargetState)
  90.         .on('get', callbackify(this.getFrunkTargetState))
  91.         .on('set', callbackify(this.setFrunkTargetState));
  92.  
  93.       this.frunkService = frunkService;
  94.     }
  95.  
  96.     if (this.trunk) {
  97.       // Enable the rear trunk lock service if requested. Use the name given
  98.       // in your config.
  99.       const trunkService = new Service.LockMechanism(this.trunk, 'trunk');
  100.  
  101.       trunkService
  102.         .getCharacteristic(Characteristic.LockCurrentState)
  103.         .on('get', callbackify(this.getTrunkCurrentState));
  104.  
  105.       trunkService
  106.         .getCharacteristic(Characteristic.LockTargetState)
  107.         .on('get', callbackify(this.getTrunkTargetState))
  108.         .on('set', callbackify(this.setTrunkTargetState));
  109.  
  110.       this.trunkService = trunkService;
  111.     }
  112.  
  113.     if (this.chargePort) {
  114.       // Enable the charge port trunk lock service if requested. Use the name given
  115.       // in your config.
  116.       const chargePortService = new Service.LockMechanism(this.chargePort, 'chargePort');
  117.  
  118.       chargePortService
  119.         .getCharacteristic(Characteristic.LockCurrentState)
  120.         .on('get', callbackify(this.getChargePortCurrentState));
  121.  
  122.       chargePortService
  123.         .getCharacteristic(Characteristic.LockTargetState)
  124.         .on('get', callbackify(this.getChargePortTargetState))
  125.         .on('set', callbackify(this.setChargePortTargetState));
  126.  
  127.       this.chargePortService = chargePortService;
  128.     }
  129.  
  130.   }
  131.  
  132.   getServices() {
  133.     const {lockService, climateService, frunkService, trunkService, chargePortService} = this;
  134.     return [lockService, climateService, ...frunkService ? [frunkService] : [], ...trunkService ? [trunkService] : [], ...chargePortService ? [chargePortService] :[]];
  135.   }
  136.  
  137.   //
  138.   // Vehicle Lock
  139.   //
  140.  
  141.   getLockCurrentState = async () => {
  142.     const options = await this.getOptions();
  143.  
  144.     // This will only succeed if the car is already online. We don't want to
  145.     // wake it up just to see if climate is on because that could drain battery!
  146.     const state: VehicleState = await api('vehicleState', options);
  147.  
  148.     return state.locked ?
  149.       Characteristic.LockCurrentState.SECURED :
  150.       Characteristic.LockCurrentState.UNSECURED;
  151.   }
  152.  
  153.   getLockTargetState = async () => {
  154.     const options = await this.getOptions();
  155.  
  156.     // This will only succeed if the car is already online. We don't want to
  157.     // wake it up just to see if climate is on because that could drain battery!
  158.     const state: VehicleState = await api('vehicleState', options);
  159.  
  160.     return state.locked ?
  161.       Characteristic.LockTargetState.SECURED :
  162.       Characteristic.LockTargetState.UNSECURED;
  163.   }
  164.  
  165.   setLockTargetState = async (state) => {
  166.     const options = await this.getOptions();
  167.  
  168.     // Wake up, this is important!
  169.     await this.wakeUp();
  170.  
  171.     this.log('Set lock state to', state);
  172.  
  173.     if (state === Characteristic.LockTargetState.SECURED) {
  174.       await api('doorLock', options);
  175.     }
  176.     else {
  177.       await api('doorUnlock', options);
  178.     }
  179.  
  180.     // We succeeded, so update the "current" state as well.
  181.     // We need to update the current state "later" because Siri can't
  182.     // handle receiving the change event inside the same "set target state"
  183.     // response.
  184.     await wait(1);
  185.  
  186.     if (state == Characteristic.LockTargetState.SECURED) {
  187.       this.lockService.setCharacteristic(
  188.         Characteristic.LockCurrentState,
  189.         Characteristic.LockCurrentState.SECURED,
  190.       );
  191.     }
  192.     else {
  193.       this.lockService.setCharacteristic(
  194.         Characteristic.LockCurrentState,
  195.         Characteristic.LockCurrentState.UNSECURED,
  196.       );
  197.     }
  198.   }
  199.  
  200.   //
  201.   // Climate Switch
  202.   //
  203.  
  204.   getClimateOn = async () => {
  205.     const options = await this.getOptions();
  206.  
  207.     // This will only succeed if the car is already online. We don't want to
  208.     // wake it up just to see if climate is on because that could drain battery!
  209.     const state: ClimateState = await api('climateState', options);
  210.  
  211.     const on = state.is_auto_conditioning_on;
  212.  
  213.     this.log('Climate on?', on);
  214.     return on;
  215.   }
  216.  
  217.   setClimateOn = async on => {
  218.     const options = await this.getOptions();
  219.  
  220.     // Wake up, this is important!
  221.     await this.wakeUp();
  222.  
  223.     this.log('Set climate to', on);
  224.  
  225.     if (on) {
  226.       await api('climateStart', options);
  227.     }
  228.     else {
  229.       await api('climateStop', options);
  230.     }
  231.   }
  232.  
  233.   //
  234.   // Front Trunk
  235.   //
  236.  
  237.   getFrunkCurrentState = async () => {
  238.     const options = await this.getOptions();
  239.  
  240.     // This will only succeed if the car is already online. We don't want to
  241.     // wake it up just to see if climate is on because that could drain battery!
  242.     const state: VehicleState = await api('vehicleState', options);
  243.  
  244.     return state.ft ?
  245.       Characteristic.LockCurrentState.UNSECURED :
  246.       Characteristic.LockCurrentState.SECURED;
  247.   }
  248.  
  249.   getFrunkTargetState = async () => {
  250.     const options = await this.getOptions();
  251.  
  252.     // This will only succeed if the car is already online. We don't want to
  253.     // wake it up just to see if climate is on because that could drain battery!
  254.     const state: VehicleState = await api('vehicleState', options);
  255.  
  256.     return state.ft ?
  257.       Characteristic.LockTargetState.UNSECURED :
  258.       Characteristic.LockTargetState.SECURED;
  259.   }
  260.  
  261.   setFrunkTargetState = async (state) => {
  262.     const options = await this.getOptions();
  263.  
  264.     // Wake up, this is important!
  265.     await this.wakeUp();
  266.  
  267.     this.log('Set frunk state to', state);
  268.  
  269.     if (state === Characteristic.LockTargetState.SECURED) {
  270.       throw new Error('Cannot close an open frunk.');
  271.     }
  272.     else {
  273.       await api('openTrunk', options, tesla.FRUNK);
  274.     }
  275.  
  276.     // We succeeded, so update the "current" state as well.
  277.     // We need to update the current state "later" because Siri can't
  278.     // handle receiving the change event inside the same "set target state"
  279.     // response.
  280.     await wait(1);
  281.  
  282.     const {frunkService} = this;
  283.  
  284.     frunkService && frunkService.setCharacteristic(
  285.       Characteristic.LockCurrentState,
  286.       Characteristic.LockCurrentState.UNSECURED,
  287.     );
  288.   }
  289.  
  290.   //
  291.   // Rear Trunk
  292.   //
  293.  
  294.   getTrunkCurrentState = async () => {
  295.     const options = await this.getOptions();
  296.  
  297.     // This will only succeed if the car is already online. We don't want to
  298.     // wake it up just to see if climate is on because that could drain battery!
  299.     const state: VehicleState = await api('vehicleState', options);
  300.  
  301.     return state.rt ?
  302.       Characteristic.LockCurrentState.UNSECURED :
  303.       Characteristic.LockCurrentState.SECURED;
  304.   }
  305.  
  306.   getTrunkTargetState = async () => {
  307.     const options = await this.getOptions();
  308.  
  309.     // This will only succeed if the car is already online. We don't want to
  310.     // wake it up just to see if climate is on because that could drain battery!
  311.     const state: VehicleState = await api('vehicleState', options);
  312.  
  313.     return state.rt ?
  314.       Characteristic.LockTargetState.UNSECURED :
  315.       Characteristic.LockTargetState.SECURED;
  316.   }
  317.  
  318.   setTrunkTargetState = async (state) => {
  319.     const options = await this.getOptions();
  320.  
  321.     // Wake up, this is important!
  322.     await this.wakeUp();
  323.  
  324.     this.log('Set trunk state to', state);
  325.  
  326.     await api('openTrunk', options, tesla.TRUNK);
  327.  
  328.     // We succeeded, so update the "current" state as well.
  329.     // We need to update the current state "later" because Siri can't
  330.     // handle receiving the change event inside the same "set target state"
  331.     // response.
  332.     await wait(1);
  333.  
  334.     if (state == Characteristic.LockTargetState.SECURED) {
  335.       this.trunkService.setCharacteristic(
  336.         Characteristic.LockCurrentState,
  337.         Characteristic.LockCurrentState.SECURED,
  338.       );
  339.     }
  340.     else {
  341.       this.trunkService.setCharacteristic(
  342.         Characteristic.LockCurrentState,
  343.         Characteristic.LockCurrentState.UNSECURED,
  344.       );
  345.     }
  346.   }
  347.  
  348.   // Charge Port
  349.  
  350.   getChargePortCurrentState = async () => {
  351.     const options = await this.getOptions();
  352.  
  353.     // This will only succeed if the car is already online. We don't want to
  354.     // wake it up just to see if climate is on because that could drain battery!
  355.     const state: VehicleData = await api('vehicleData', options);
  356.  
  357.     console.log(state);
  358.  
  359.     return state.charge_state.charge_port_door_open ?
  360.       Characteristic.LockCurrentState.UNSECURED :
  361.       Characteristic.LockCurrentState.SECURED;
  362.   }
  363.  
  364.   getChargePortTargetState = async () => {
  365.     const options = await this.getOptions();
  366.  
  367.     // This will only succeed if the car is already online. We don't want to
  368.     // wake it up just to see if climate is on because that could drain battery!
  369.     const state: VehicleData = await api('vehicleData', options);
  370.  
  371.     return state.charge_state.charge_port_door_open ?
  372.       Characteristic.LockTargetState.UNSECURED :
  373.       Characteristic.LockTargetState.SECURED;
  374.   }
  375.  
  376.   setChargePortTargetState = async (state) => {
  377.     const options = await this.getOptions();
  378.  
  379.     // Wake up, this is important!
  380.     await this.wakeUp();
  381.  
  382.     this.log('Set charge port state to', state);
  383.  
  384.     if (state === Characteristic.LockTargetState.SECURED) {
  385.       await api('closeChargePort', options)
  386.     }
  387.     else {
  388.       await api('openChargePort', options);
  389.     }
  390.  
  391.     // We succeeded, so update the "current" state as well.
  392.     // We need to update the current state "later" because Siri can't
  393.     // handle receiving the change event inside the same "set target state"
  394.     // response.
  395.     await wait(1);
  396.  
  397.     if (state == Characteristic.LockTargetState.SECURED) {
  398.       this.chargePortService.setCharacteristic(
  399.         Characteristic.LockCurrentState,
  400.         Characteristic.LockCurrentState.SECURED,
  401.       );
  402.     }
  403.     else {
  404.       this.chargePortService.setCharacteristic(
  405.         Characteristic.LockCurrentState,
  406.         Characteristic.LockCurrentState.UNSECURED,
  407.       );
  408.     }
  409.   }
  410.  
  411.   //
  412.   // General
  413.   //
  414.  
  415.   getOptions = async (): Promise<{authToken: string, vehicleID: string}> => {
  416.     // Use a mutex to prevent multiple logins happening in parallel.
  417.     const unlock = await lock('getOptions', 20000);
  418.  
  419.     try {
  420.       // First login if we don't have a token.
  421.       const authToken = await this.getAuthToken();
  422.  
  423.       // Grab the string ID of your vehicle.
  424.       const {id_s: vehicleID} = await this.getVehicle();
  425.  
  426.       return {authToken, vehicleID};
  427.     }
  428.     finally {
  429.       unlock();
  430.     }
  431.   }
  432.  
  433.   getAuthToken = async (): Promise<string> => {
  434.     const {username, password, authToken} = this;
  435.  
  436.     // Return cached value if we have one.
  437.     if (authToken) return authToken;
  438.  
  439.     this.log('Logging into Tesla with username/password…');
  440.     const result = await api('login', username, password);
  441.     const token = result.authToken;
  442.  
  443.     // Save it in memory for future API calls.
  444.     this.log('Got a login token.');
  445.     this.authToken = token;
  446.     return token;
  447.   }
  448.  
  449.   getVehicle = async () => {
  450.     const {vin} = this;
  451.  
  452.     // Only way to do this is to get ALL vehicles then filter out the one
  453.     // we want.
  454.     const authToken = await this.getAuthToken();
  455.     const vehicles: Vehicle[] = await api('allVehicles', {authToken});
  456.  
  457.     // Now figure out which vehicle matches your VIN.
  458.     // `vehicles` is something like:
  459.     // [ { id_s: '18488650400306554', vin: '5YJ3E1EA8JF006024', state: 'asleep', ... }, ... ]
  460.     const vehicle = vehicles.find(v => v.vin === vin);
  461.  
  462.     if (!vehicle) {
  463.       this.log('No vehicles were found matching the VIN ${vin} entered in your config.json. Available vehicles:');
  464.       for (const vehicle of vehicles) {
  465.         this.log('${vehicle.vin} [${vehicle.display_name}]');
  466.       }
  467.  
  468.       throw new Error(`Couldn't find vehicle with VIN ${vin}.`);
  469.    }
  470.  
  471.    this.log(`Using vehicle "${vehicle.display_name}" with state "${vehicle.state}"`);
  472.  
  473.    return vehicle;
  474.  }
  475.  
  476.  wakeUp = async () => {
  477.    const options = await this.getOptions();
  478.  
  479.    // Send the command.
  480.    await api('wakeUp', options);
  481.  
  482.    // Wait up to 30 seconds for the car to wake up.
  483.    const start = Date.now();
  484.    let waitTime = 1000;
  485.  
  486.    while ((Date.now() - start) < this.waitMinutes * 60 * 1000) {
  487.  
  488.      // Poll Tesla for the latest on this vehicle.
  489.      const {state} = await this.getVehicle();
  490.  
  491.      if (state === 'online') {
  492.        // Success!
  493.        return;
  494.      }
  495.  
  496.      this.log('Waiting for vehicle to wake up…');
  497.      await wait(waitTime);
  498.  
  499.      // Use exponential backoff with a max wait of 5 seconds.
  500.      waitTime = Math.min(waitTime * 2, 5000);
  501.    }
  502.  
  503.    throw new Error(`Vehicle did not wake up within ${this.waitMinutes} minutes.`);
  504.  }
  505. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement