我(原作者)真的很享受驾驶BMW i3的美好时光。
我可以让这款车发个推介绍自己的驾驶效率,或者将它的坐标上传到我的服务器,当温度太高时我可以打开空调——我能想象出利用汽车数据完成100件有意思的事情。官方应用程序同样具备了一些功能——但是那个APP很慢、极丑,总之非常难用。
BMW曾为“黑客马拉松”提供了可利用API,但是在那之后他们便关闭了它。
那么,现在只有一件事可以做了:黑掉这个大块头!
目标
我们的终极目标是获取通过API暴露出的所有汽车功能。
设备
为了完成这个相对简单的任务,你需要这些东西:
一辆BMW i3; 一个BMW联网驾驶账户; BMW I 远程安卓应用程序(我使用的是非美国版本); 一部安卓手机或平板; 设备中需要具备一种拦截加密通信的方法——我推荐使用Grey Shirts的Packet Capture。
安装了Packet Capture之后,关闭其他所有应用程序,启动日志,然后登陆i Remote应用程序。
你将会看到一堆被抓获的API调用接口。
点击一个,你便会看到全部的API调用接口,以及JSON响应。我已经打码了一些我的个人信息。
重要的是你需要得到授权:无记名令牌。这是一个32个字符的字母数据字符串。
描述
BMW i Remote的最新版本总是可以从 GitHub 上得到。
这些API调用接口允许你与BMW i3进行交互,通过从官方 BMW i Remote安卓应用程序 逆向工程后获得。
https://play.google.com/store/apps/details?id=com.bmwi.remote
你使用这些API调用接口的风险完全由你自己承担。它们既不被官方提供也不被制裁。
服务器
这里有三个API服务器:
https://b2vapi.bmwgroup.cn:8592 中国 https://b2vapi.bmwgroup.us 美国 https://b2vapi.bmwgroup.com 欧洲/及其他
授权
为了验证API,你需要在 宝马连接驱动服务注册 。
需要你提供:
1、你的ConnectedDrive注册邮件地址; 2、ConnectedDrive注册密码; 3、i Remote API Key; 4、i Remote API Secret。
你可以从反编译安卓应用程序或者拦截你手机与BMW服务器之间的通讯来获得i Remote细节。这就当作是留给各位读者的一个练习题吧。
首先,我们使用 Basic身份验证 。这意味着利用API Key和Secret,并用Base64进行解码。
因此,key:secret变为 a2V5OnNlY3JldA==
同时,我们需要发送以下参数
内容类型:应用程序/x-www-form-urlencoded
grant_type=password &username=whatever%40example.com &password=p4ssw0rd &scope=remote_services+vehicle_data
这里描述了如何处理curl:
curl / -H "Authorization: Basic a2V5OnNlY3JldA==" / -H "Content-Type: application/x-www-form-urlencoded" / -d "grant_type=password&username=whatever%40example.com&password=p4ssw0rd&scope=remote_services+vehicle_data" /
如果所有东西都正常运行,你应该可以得到以下JSON:
{ "access_token": "RCQ1hLP4AFaUBW9BjcPUN3i4WgkwF90R", "token_type": "Bearer", "expires_in": 28800, "refresh_token": "7WgKmEJ2kD1ydl9Hefp01eS8qDGzKnzjeORpA6vtsoFIEanz", "scope": "vehicle_data remote_services" }
每次你去发包请求的时候,都要带上以下信息:
授权:不记名
RCQ1hLP4AFaUBW9BjcPUN3i4WgkwF90R
API
每次你去发包请求的时候,都要带上以下信息:
授权:不记名
RCQ1hLP4AFaUBW9BjcPUN3i4WgkwF90R
获取汽车数据
/webapi/v1/user/vehicles/
记住授权:不记名请求报头信息
最重要的是VIN-Vehicle Identification Number。你需要的所有其他API调用以及Authorization Bearer。
{ "vehicleStatus": { "vin": "WAB1C23456V123456", "mileage": 1234, "updateReason": "VEHICLE_SHUTDOWN_SECURED", "updateTime": "2015-10-30T18:45:04+0100", "doorDriverFront": "CLOSED", "doorDriverRear": "CLOSED", "doorPassengerFront": "CLOSED", "doorPassengerRear": "CLOSED", "windowDriverFront": "CLOSED", "windowDriverRear": "CLOSED", "windowPassengerFront": "CLOSED", "windowPassengerRear": "CLOSED", "trunk": "CLOSED", "rearWindow": "INVALID", "convertibleRoofState": "INVALID", "hood": "CLOSED", "doorLockState": "SECURED", "parkingLight": "OFF", "positionLight": "OFF", "remainingFuel": 8.9, "remainingRangeElectric": 73, "remainingRangeElectricMls": 45, "remainingRangeFuel": 126, "remainingRangeFuelMls": 78, "maxRangeElectric": 134, "maxRangeElectricMls": 83, "fuelPercent": 99, "maxFuel": 9, "connectionStatus": "DISCONNECTED", "chargingStatus": "INVALID", "chargingLevelHv": 58, "lastChargingEndReason": "UNKNOWN", "lastChargingEndResult": "FAILED", "position": { "lat": 51.123456, "lon": -1.2345678, "heading": 211, "status": "OK" }, "internalDataTimeUTC": "2015-10-30T18:47:44" } }
mileage is in Km. remainingFuel is in Litres. maxRangeElectric is in Km. maxRangeElectricMls is in miles. chargingLevelHv is the percentage of charge left in the (High voltage?) battery. maxFuel is in Litres. heading is in degrees.
有效充电状态:
CHARGING ERROR FINISHED_FULLY_CHARGED FINISHED_NOT_FULL INVALID NOT_CHARGING WAITING_FOR_CHARGING
有效连接状态:
CHARGING_DONE CHARGING_INTERRUPED [sic] CHARGING_PAUSED CHARGIN_STARTED [sic] CYCLIC_RECHARGING DOOR_STATE_CHANGED NO_CYCLIC_RECHARGING NO_LSC_TRIGGER ON_DEMAND PREDICTION_UPDATE TEMPORARY_POWER_SUPPLY_FAILURE UNKNOWN VEHICLE_MOVING VEHICLE_SECURED VEHICLE_SHUTDOWN VEHICLE_SHUTDOWN_SECURED VEHICLE_UNSECURED
获取最后的历程
获取你最近一次旅行的详情。
·/webapi/v1/user/vehicles/:VIN/statistics/lastTrip
·地点:VIN 便是你汽车的VIN。
不要忘记授权:不记名请求报头信息
{ "lastTrip":{ "efficiencyValue":0.53, "totalDistance":141, "electricDistance":100.1, "avgElectricConsumption":16.6, "avgRecuperation":2, "drivingModeValue":0, "accelerationValue":0.39, "anticipationValue":0.81, "totalConsumptionValue":0.79, "auxiliaryConsumptionValue":0.66, "avgCombinedConsumption":1.9, "electricDistanceRatio":71, "savedFuel":0, "date":"2015-12-01T20:44:00+0100", "duration":124 } }
距离似乎是公里数,而非英里,因此需要进行响应换算,乘以0.621371就能得到英里数。
totalDistance is in Km. electricDistance is in Km. avgElectricConsumption is in kWh/100Km. avgRecuperation is in kWh/100Km. duration is in minutes.
充电时间
显示了汽车充电安排。
/webapi/v1/user/vehicles/:VIN/chargingprofile
不要忘记授权:不记名请求报头信息
{ "weeklyPlanner":{ "climatizationEnabled":true, "chargingMode":"DELAYED_CHARGING", "chargingPreferences":"CHARGING_WINDOW", "timer1":{ "departureTime":"07:30", "timerEnabled":true, "weekdays":[ "MONDAY" ] }, "timer2":{ "departureTime":"13:00", "timerEnabled":false, "weekdays":[ "SATURDAY" ] }, "timer3":{ "departureTime":"08:00", "timerEnabled":false, "weekdays":[ ] }, "overrideTimer":{ "departureTime":"07:30", "timerEnabled":false, "weekdays":[ "MONDAY" ] }, "preferredChargingWindow":{ "enabled":true, "startTime":"05:02", "endTime":"17:31" } } }
离开时间似乎是汽车的地方时
获得车辆目的地
显示了你之前汽车之前去过的地方。
/webapi/v1/user/vehicles/:VIN/destinations
地点:VIN便是你汽车的VIN
授权:不记名请求报头信息
{ "destinations":[ { "lat":51.53053283691406, "lon":-0.08362331241369247, "country":"UNITED KINGDOM", "city":"LONDON", "street":"PITFIELD STREET", "type":"DESTINATION", "createdAt":"2015-09-25T08:06:11+0200" } ] }
地址的数组。
获取全部历程详情
可以得到汽车所有行程的统计数据
/webapi/v1/user/vehicles/:VIN/statistics/allTrips
地点:VIN便是你汽车的VIN
授权:不记名请求报头信息
{ "allTrips": { "avgElectricConsumption": { "communityLow": 0, "communityAverage": 16.33, "communityHigh": 35.53, "userAverage": 14.76 }, "avgRecuperation": { "communityLow": 0, "communityAverage": 3.76, "communityHigh": 14.03, "userAverage": 2.3 }, "chargecycleRange": { "communityAverage": 121.58, "communityHigh": 200, "userAverage": 72.62, "userHigh": 135, "userCurrentChargeCycle": 60 }, "totalElectricDistance": { "communityLow": 1, "communityAverage": 12293.65, "communityHigh": 77533.6, "userTotal": 3158.66 }, "avgCombinedConsumption": { "communityLow": 0, "communityAverage": 1.21, "communityHigh": 6.2, "userAverage": 0.36 }, "savedCO2": 87.58, "savedCO2greenEnergy": 515.177, "totalSavedFuel": 0, "resetDate": "1970-01-01T01:00:00+0100" } }
chargecycleRange is in Km. totalElectricDistance is in Km.
我并不确定其他值的单位。
得到地图范围
生成一个预测汽车范围的多线显示。
/webapi/v1/user/vehicles/:VIN/rangemap
地点:VIN便是你汽车的VIN
授权:不记名请求报头信息
{ "rangemap": { "center": { "lat": 51.123456, "lon": -1.2345678 }, "quality": "AVERAGE", "rangemaps": [ { "type": "ECO_PRO_PLUS", "polyline": [ { "lat": 51.6991281509399, "lon": -2.00423240661621 }, { "lat": 51.6909098625183, "lon": -1.91526889801025 }, ... ] }, { "type": "COMFORT", "polyline": [ { "lat": 51.7212295532227, "lon": -1.7363977432251 }, { "lat": 51.6991496086121, "lon": -1.73077583312988 }, ... ] } ] } }
ECO_PRO_PLUS driving using the efficient Eco mode. COMFORT driving using comfort mode.
给汽车发送信息
这一步稍微有点复杂。
你的app通过API与汽车联系,然后API与汽车3G模式相连,你便可以等待响应。
如果你的汽车信号比较弱,你最好准备好会等得久一点。
基本层面上,你能够发送一个请求(例如锁好车门或进行一次非峰值的充电)。
获取请求状态
展示了一个POSTed请求。
/webapi/v1/user/vehicles/:VIN/serviceExecutionStatus?serviceType=:SERVICE
地点:VIN便是你汽车的VIN
授权:不记名请求报头信息
{ "executionStatus":{ "serviceType":"DOOR_LOCK", "status":"EXECUTED", "eventId":"123456789012345AB1CD1234@bmw.de" } }
有效状态为:
DELIVERED EXECUTED INITIATED NOT_EXECUTED PENDING TIMED_OUT
以下都是有效的:服务类型,但你的车辆可能并不支持。
CHARGE_NOW CHARGING_CONTROL CLIMATE_CONTROL CLIMATE_NOW DOOR_LOCK DOOR_UNLOCK GET_ALL_IMAGES GET_PASSWORD_RESET_INFO GET_VEHICLES GET_VEHICLE_IMAGE GET_VEHICLE_STATUS HORN_BLOW LIGHT_FLASH LOCAL_SEARCH LOCAL_SEARCH_SUGGESTIONS LOGIN LOGOUT SEND_POI_TO_CAR VEHICLE_FINDER
Post一个命令
指导汽车执行一个动作。
/webapi/v1/user/vehicles/:VIN/executeService
地点:VIN便是你汽车的VIN
授权:不记名请求报头信息
可用命令
这些命令通过API可进行利用,但是可能无法被你的汽车支持。
这些只是我目前探寻到的。
数据必须被POST到服务器上。
启动充电
如果汽车接入之后,不进行充电(可能由于非高峰设置?),则有可能强迫汽车进行充电。
serviceType=CHARGE_NOW
气候控制
这将激活汽车上的气候控制。
它似乎局限于你上一次在车内设定的温度。我还没找到一个方法来将汽车设定到指定温度。
serviceType=CLIMATE_NOW
锁门
执行中心锁定。
serviceType=DOOR_LOCK
解锁
这将打开车上所有的门。
请在发送这个命令时保持极度谨慎。确保汽车在你的视野范围之内,并且在必要时可以将其上锁。
serviceType=DOOR_UNLOCK
闪光
如果你无法找到车了,或者需要它把附近照亮,你可以简略地激活前灯。
serviceType=LIGHT_FLASH&count=2
我认为这个count值与保持灯亮的秒数有关?!
充电时间
设置峰值/非峰值充电时间表。
serviceType=CHARGING_CONTROL
我并没有把这个搞清楚,但是返回的错误可能会给你一点指示:
{ "error":{ "code":500, "description":"(SmartPhoneUtil-A-102) Bad value(s) for parameter(s): Invalid chargingProfile, expected weeklyPlanner or twoTimesTimer" } }
车辆仪
serviceType=VEHICLE_FINDER
我并不清楚这是做什么的。
对所有POST命令的响应示例:
{ "executionStatus": { "serviceType": "LIGHT_FLASH", "status": "INITIATED", "eventId": "123456789012345AB1CD1234@bmw.de" } }
接下来会是什么呢?
通过这些命令你便能够复制官方应用程序的功能。
正如你看到的,我设法让我的车发了推特:
如果BMW决定开放他们的官方API,那一定非常有趣,人们便能随意摆弄自己的汽车。API似乎是安全,对车辆的破坏十分有限。
我已经将全部文档上传到了 GitHub 。有兴趣的欢迎交流。
*原文地址: shkspr.mobi ,空白编译,转载请注明来自FreeBuf黑客与极客(FreeBuf.COM)