Skip to content

Commit 561b845

Browse files
authored
Merge pull request #159 from Hacksore/develop
2 parents 027b2d2 + 36f21fa commit 561b845

11 files changed

Lines changed: 392 additions & 145 deletions

File tree

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ Run an install for all the dependencies, `npm install`
5656
Now you can invoke the debug.ts script with `npm run debug`
5757

5858
## Documentation
59-
Checkout out the [bluelinky-docs](https://hacksore.github.io/bluelinky-docs/) for more info.
59+
Checkout out the [docs](https://bluelinky.readme.io) for more info.
6060

6161
Important information for login problems:
6262
- If you experience login problems, please logout from the app on your phone and login again. You might need to ' upgrade ' your account to a generic Kia/Hyundai account, or create a new password or PIN.
@@ -80,6 +80,7 @@ The JSON file must respect [this format](https://github.com/neoPix/bluelinky-sta
8080
- startCharge
8181
- monthlyReport
8282
- tripInfo
83+
- EV: driveHistory
8384
- EV: getChargeTargets
8485
- EV: setChargeLimits
8586

__tests__/util.spec.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ describe('Utility', () => {
3434
expect(parseDate('20210118153031')).toEqual(new Date('2021-01-18:15:30:31'));
3535
});
3636

37+
it('parseDate converts shortdate to date', () => {
38+
expect(parseDate('20210117')).toEqual(new Date('2021-01-17:00:00:00'));
39+
});
40+
3741
it('addTime can add minutes to a date', () => {
3842
const start = new Date('2021-01-18:12:00:00');
3943
expect(addMinutes(start, 30)).toEqual(new Date('2021-01-18:12:30:00'));

debug.ts

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { Vehicle } from './src/vehicles/vehicle';
99
const apiCalls = [
1010
{ name: 'exit', value: 'exit' },
1111
{ name: 'start', value: 'start' },
12+
{ name: 'vehicles', value: 'vehicles' },
1213
{ name: 'odometer', value: 'odometer' },
1314
{ name: 'stop', value: 'stop' },
1415
{ name: 'status (on server cache)', value: 'status' },
@@ -21,12 +22,16 @@ const apiCalls = [
2122
{ name: 'locate', value: 'locate' },
2223
{ name: '[EU] monthly report', value: 'monthlyReport' },
2324
{ name: '[EU] trip informations', value: 'tripInfo' },
24-
{ name: '[EU][EV] get charge targets', value: 'getChargeTargets' },
25-
{ name: '[EU][EV] set charge targets', value: 'setChargeTargets' },
25+
{ name: '[EU] drive informations', value: 'drvInfo' },
26+
{ name: '[EV] get charge targets', value: 'getChargeTargets' },
27+
{ name: '[EV] set charge targets', value: 'setChargeTargets' },
28+
{ name: '[EV] start charging', value: 'startCharge' },
29+
{ name: '[EV] stop charging', value: 'stopCharge' },
2630
];
2731

32+
let client: BlueLinky;
2833
let vehicle;
29-
const { username, password, vin, pin } = config;
34+
const { username, password, pin } = config;
3035

3136
const onReadyHandler = <T extends Vehicle>(vehicles: T[]) => {
3237
vehicle = vehicles[0];
@@ -61,7 +66,8 @@ const askForRegionInput = () => {
6166
};
6267

6368
const createInstance = (region, brand) => {
64-
const client = new BlueLinky({
69+
// global abuse :)
70+
client = new BlueLinky({
6571
username,
6672
password,
6773
region,
@@ -104,6 +110,14 @@ async function performCommand(command) {
104110
const odometer = await vehicle.odometer();
105111
console.log('odometer', JSON.stringify(odometer, null, 2));
106112
break;
113+
case 'vehicles':
114+
const vehicles = await client.getVehicles();
115+
const response = vehicles.map(v => {
116+
const { name, vin, nickname, regDate } = v.vehicleConfig;
117+
return { name, vin, nickname, regDate };
118+
});
119+
console.log('vehicles', JSON.stringify(response, null, 2));
120+
break;
107121
case 'status':
108122
const status = await vehicle.status({
109123
refresh: false,
@@ -165,6 +179,10 @@ async function performCommand(command) {
165179
const report = await vehicle.monthlyReport();
166180
console.log('monthyReport : ' + JSON.stringify(report, null, 2));
167181
break;
182+
case 'drvInfo':
183+
const info = await vehicle.driveHistory();
184+
console.log('drvInfo : ', info);
185+
break;
168186
case 'tripInfo':
169187
const currentYear = new Date().getFullYear();
170188
const { year, month, day } = await inquirer
@@ -195,6 +213,12 @@ async function performCommand(command) {
195213
const targets = await vehicle.getChargeTargets();
196214
console.log('targets : ' + JSON.stringify(targets, null, 2));
197215
break;
216+
case 'startCharge':
217+
await vehicle.startCharge();
218+
break;
219+
case 'stopCharge':
220+
await vehicle.stopCharge();
221+
break;
198222
case 'setChargeTargets':
199223
const { fast, slow } = await inquirer
200224
.prompt([

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "bluelinky",
3-
"version": "7.4.1",
3+
"version": "7.5.0",
44
"description": "An unofficial nodejs API wrapper for Hyundai bluelink",
55
"main": "dist/index.js",
66
"types": "dist/index.d.ts",

src/constants.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ export enum REGIONS {
1818
EU = 'EU',
1919
}
2020

21+
// ev stuffz
22+
export type ChargeTarget = 50 | 60 | 70 | 80 | 90 | 100;
23+
export const POSSIBLE_CHARGE_LIMIT_VALUES = [50, 60, 70, 80, 90, 100];
24+
2125
export const DEFAULT_VEHICLE_STATUS_OPTIONS: VehicleStatusOptions = {
2226
refresh: false,
2327
parsed: false,

src/constants/canada.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ export interface CanadianBrandEnvironment {
2121
verifyAccountToken: string;
2222
verifyPin: string;
2323
verifyToken: string;
24+
setChargeTarget: string;
25+
stopCharge: string;
26+
startCharge: string;
2427
}
2528
}
2629

@@ -37,6 +40,9 @@ const getEndpoints = (baseUrl: string) => ({
3740
unlock: `${baseUrl}/tods/api/drulck`,
3841
start: `${baseUrl}/tods/api/evc/rfon`,
3942
stop: `${baseUrl}/tods/api/evc/rfoff`,
43+
startCharge: `${baseUrl}/tods/api/evc/rcstrt`,
44+
stopCharge: `${baseUrl}/tods/api/evc/rcstp`,
45+
setChargeTarget: `${baseUrl}/tods/api/evc/setsoc`,
4046
locate: `${baseUrl}/tods/api/fndmcr`,
4147
hornlight: `${baseUrl}/tods/api/hornlight`,
4248
// System
@@ -78,4 +84,4 @@ export const getBrandEnvironment = (brand: Brand): CanadianBrandEnvironment => {
7884
default:
7985
throw new Error(`Constructor ${brand} is not managed.`);
8086
}
81-
};
87+
};

src/interfaces/european.interfaces.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,34 @@ export interface EUPOIInformation {
2121
placeid: string;
2222
name: string;
2323
}
24+
25+
export enum historyDrivingPeriod {
26+
DAY = 0,
27+
MONTH = 1,
28+
ALL = 2
29+
}
30+
31+
export enum historyCumulatedTypes {
32+
TOTAL = 0,
33+
AVERAGE = 1,
34+
TODAY = 2
35+
}
36+
37+
export interface EUDriveHistory {
38+
period: historyCumulatedTypes,
39+
consumption: {
40+
total: number,
41+
engine: number,
42+
climate: number,
43+
devices: number,
44+
battery: number
45+
},
46+
regen: number,
47+
distance: number
48+
}
49+
50+
export interface EUDatedDriveHistory extends Omit<EUDriveHistory, 'period'> {
51+
period: historyDrivingPeriod,
52+
rawDate: string;
53+
date: Date,
54+
}

src/util.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,9 @@ export const parseDate = (str: string): Date => {
6363
const year = parseInt(str.substring(0, 4));
6464
const month = parseInt(str.substring(4, 6));
6565
const day = parseInt(str.substring(6, 8));
66+
if (str.length <= 8) {
67+
return new Date(year, month - 1, day);
68+
}
6669
const hour = parseInt(str.substring(8, 10));
6770
const minute = parseInt(str.substring(10, 12));
6871
const second = parseInt(str.substring(12, 14));

src/vehicles/canadian.vehicle.ts

Lines changed: 95 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
import got from 'got';
22
import logger from '../logger';
33

4-
import { REGIONS, DEFAULT_VEHICLE_STATUS_OPTIONS } from '../constants';
4+
import {
5+
REGIONS,
6+
DEFAULT_VEHICLE_STATUS_OPTIONS,
7+
ChargeTarget,
8+
POSSIBLE_CHARGE_LIMIT_VALUES,
9+
} from '../constants';
510

611
import {
712
VehicleStartOptions,
@@ -12,11 +17,13 @@ import {
1217
VehicleStatusOptions,
1318
RawVehicleStatus,
1419
FullVehicleStatus,
20+
EVChargeModeTypes,
1521
} from '../interfaces/common.interfaces';
1622

1723
import { Vehicle } from './vehicle';
1824
import { celciusToTempCode, parseDate } from '../util';
1925
import { CanadianController } from '../controllers/canadian.controller';
26+
import { ManagedBluelinkyError } from '../tools/common.tools';
2027

2128
export default class CanadianVehicle extends Vehicle {
2229
public region = REGIONS.CA;
@@ -40,7 +47,9 @@ export default class CanadianVehicle extends Vehicle {
4047
};
4148
logger.debug('Begin status request, polling car: ' + input.refresh);
4249
try {
43-
const endpoint = statusConfig.refresh ? this.controller.environment.endpoints.remoteStatus : this.controller.environment.endpoints.status;
50+
const endpoint = statusConfig.refresh
51+
? this.controller.environment.endpoints.remoteStatus
52+
: this.controller.environment.endpoints.status;
4453
const response = await this.request(endpoint, {});
4554
const vehicleStatus = response.result?.status;
4655

@@ -148,13 +157,19 @@ export default class CanadianVehicle extends Vehicle {
148157
const airTemp = startConfig.airTempvalue;
149158
// TODO: can we use getTempCode here from util?
150159
if (airTemp != null) {
151-
body.hvacInfo['airTemp'] = { value: celciusToTempCode(REGIONS.CA, airTemp), unit: 0, hvacTempType: 1 };
160+
body.hvacInfo['airTemp'] = {
161+
value: celciusToTempCode(REGIONS.CA, airTemp),
162+
unit: 0,
163+
hvacTempType: 1,
164+
};
152165
} else if ((startConfig.airCtrl ?? false) || (startConfig.defrost ?? false)) {
153166
throw 'air temperature should be specified';
154167
}
155168

156169
const preAuth = await this.getPreAuth();
157-
const response = await this.request(this.controller.environment.endpoints.start, body, { pAuth: preAuth });
170+
const response = await this.request(this.controller.environment.endpoints.start, body, {
171+
pAuth: preAuth,
172+
});
158173

159174
logger.debug(response);
160175

@@ -197,6 +212,77 @@ export default class CanadianVehicle extends Vehicle {
197212
}
198213
}
199214

215+
/**
216+
* Warning only works on EV vehicles
217+
* @returns
218+
*/
219+
public async stopCharge(): Promise<void> {
220+
logger.debug('Begin stopCharge');
221+
const { stopCharge } = this.controller.environment.endpoints;
222+
try {
223+
const preAuth = await this.getPreAuth();
224+
const response = await this.request(stopCharge, {
225+
pin: this.controller.userConfig.pin,
226+
pAuth: preAuth,
227+
});
228+
return response;
229+
} catch (err) {
230+
throw 'error: ' + err;
231+
}
232+
}
233+
234+
/**
235+
* Warning only works on EV vehicles
236+
* @returns
237+
*/
238+
public async startCharge(): Promise<void> {
239+
logger.debug('Begin startCharge');
240+
const { startCharge } = this.controller.environment.endpoints;
241+
try {
242+
const preAuth = await this.getPreAuth();
243+
const response = await this.request(startCharge, {
244+
pin: this.controller.userConfig.pin,
245+
pAuth: preAuth,
246+
});
247+
return response;
248+
} catch (err) {
249+
throw 'error: ' + err;
250+
}
251+
}
252+
253+
/**
254+
* Warning only works on EV vehicles
255+
* @param limits
256+
* @returns Promise<void>
257+
*/
258+
public async setChargeTargets(limits: { fast: ChargeTarget; slow: ChargeTarget }): Promise<void> {
259+
logger.debug('Begin setChargeTarget');
260+
if (
261+
!POSSIBLE_CHARGE_LIMIT_VALUES.includes(limits.fast) ||
262+
!POSSIBLE_CHARGE_LIMIT_VALUES.includes(limits.slow)
263+
) {
264+
throw new ManagedBluelinkyError(
265+
`Charge target values are limited to ${POSSIBLE_CHARGE_LIMIT_VALUES.join(', ')}`
266+
);
267+
}
268+
269+
const { setChargeTarget } = this.controller.environment.endpoints;
270+
try {
271+
const preAuth = await this.getPreAuth();
272+
const response = await this.request(setChargeTarget, {
273+
pin: this.controller.userConfig.pin,
274+
pAuth: preAuth,
275+
tsoc: [
276+
{ plugType: EVChargeModeTypes.FAST, level: limits.fast },
277+
{ plugType: EVChargeModeTypes.SLOW, level: limits.slow },
278+
],
279+
});
280+
return response;
281+
} catch (err) {
282+
throw 'error: ' + err;
283+
}
284+
}
285+
200286
// TODO: @Seb to take a look at doing this
201287
public odometer(): Promise<VehicleOdometer | null> {
202288
throw new Error('Method not implemented.');
@@ -206,7 +292,11 @@ export default class CanadianVehicle extends Vehicle {
206292
logger.debug('Begin locate request');
207293
try {
208294
const preAuth = await this.getPreAuth();
209-
const response = await this.request(this.controller.environment.endpoints.locate, {}, { pAuth: preAuth });
295+
const response = await this.request(
296+
this.controller.environment.endpoints.locate,
297+
{},
298+
{ pAuth: preAuth }
299+
);
210300
this._location = response.result as VehicleLocation;
211301
return this._location;
212302
} catch (err) {

0 commit comments

Comments
 (0)