myenergi-api 0.1.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/AuthDigest.d.ts +29 -0
- package/{src/AuthDigest.ts → dist/AuthDigest.js} +50 -53
- package/dist/Digest.d.ts +7 -0
- package/dist/Digest.js +148 -0
- package/dist/MyEnergi.d.ts +23 -0
- package/dist/MyEnergi.js +109 -0
- package/{src/index.ts → dist/index.d.ts} +0 -0
- package/dist/index.js +5 -0
- package/{src/models/Zappi.ts → dist/models/Zappi.d.ts} +0 -0
- package/dist/models/Zappi.js +2 -0
- package/package.json +12 -2
- package/src/Digest.ts +0 -88
- package/src/MyEnergi.ts +0 -91
- package/tests/MyEnergi-spec.ts +0 -12
- package/tests/debug.ts +0 -45
- package/tsconfig.testing.json +0 -14
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export declare class AuthDigest {
|
|
2
|
+
private _password?;
|
|
3
|
+
private _username?;
|
|
4
|
+
private _initialized;
|
|
5
|
+
private _onError?;
|
|
6
|
+
get username(): string | undefined;
|
|
7
|
+
private _realm?;
|
|
8
|
+
get realm(): string | undefined;
|
|
9
|
+
private _nonce?;
|
|
10
|
+
get nonce(): string | undefined;
|
|
11
|
+
private _uri?;
|
|
12
|
+
get uri(): string | undefined;
|
|
13
|
+
private _algorithm;
|
|
14
|
+
get algorithm(): string;
|
|
15
|
+
private _response?;
|
|
16
|
+
get response(): string | undefined;
|
|
17
|
+
private _opaque?;
|
|
18
|
+
get opaque(): string | undefined;
|
|
19
|
+
private _qop?;
|
|
20
|
+
get qop(): string | undefined;
|
|
21
|
+
private _nc;
|
|
22
|
+
get nc(): string | undefined;
|
|
23
|
+
private _cnonce?;
|
|
24
|
+
get cnonce(): string | undefined;
|
|
25
|
+
private md5;
|
|
26
|
+
constructor(username: string, password: string, errorHandler?: (errorMessage: string) => void);
|
|
27
|
+
init(wwwAuthHeader: string): void;
|
|
28
|
+
getAuthorization(httpMethod: string, path: string): string;
|
|
29
|
+
}
|
|
@@ -1,99 +1,98 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
|
|
5
|
+
}) : (function(o, m, k, k2) {
|
|
6
|
+
if (k2 === undefined) k2 = k;
|
|
7
|
+
o[k2] = m[k];
|
|
8
|
+
}));
|
|
9
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
10
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
11
|
+
}) : function(o, v) {
|
|
12
|
+
o["default"] = v;
|
|
13
|
+
});
|
|
14
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
15
|
+
if (mod && mod.__esModule) return mod;
|
|
16
|
+
var result = {};
|
|
17
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
18
|
+
__setModuleDefault(result, mod);
|
|
19
|
+
return result;
|
|
20
|
+
};
|
|
21
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
|
+
exports.AuthDigest = void 0;
|
|
23
|
+
const crypto = __importStar(require("crypto"));
|
|
24
|
+
class AuthDigest {
|
|
25
|
+
constructor(username, password, errorHandler) {
|
|
26
|
+
this._username = username;
|
|
27
|
+
this._password = password;
|
|
28
|
+
this._nc = 0;
|
|
29
|
+
this._initialized = false;
|
|
30
|
+
this._algorithm = 'MD5';
|
|
31
|
+
if (errorHandler)
|
|
32
|
+
this._onError = errorHandler;
|
|
33
|
+
}
|
|
34
|
+
get username() {
|
|
10
35
|
return this._username;
|
|
11
36
|
}
|
|
12
|
-
|
|
13
|
-
public get realm(): string | undefined {
|
|
37
|
+
get realm() {
|
|
14
38
|
return this._realm;
|
|
15
39
|
}
|
|
16
|
-
|
|
17
|
-
public get nonce(): string | undefined {
|
|
40
|
+
get nonce() {
|
|
18
41
|
return this._nonce;
|
|
19
42
|
}
|
|
20
|
-
|
|
21
|
-
public get uri(): string | undefined {
|
|
43
|
+
get uri() {
|
|
22
44
|
return this._uri;
|
|
23
45
|
}
|
|
24
|
-
|
|
25
|
-
public get algorithm(): string {
|
|
46
|
+
get algorithm() {
|
|
26
47
|
return this._algorithm;
|
|
27
48
|
}
|
|
28
|
-
|
|
29
|
-
public get response(): string | undefined {
|
|
49
|
+
get response() {
|
|
30
50
|
return this._response;
|
|
31
51
|
}
|
|
32
|
-
|
|
33
|
-
public get opaque(): string | undefined {
|
|
52
|
+
get opaque() {
|
|
34
53
|
return this._opaque;
|
|
35
54
|
}
|
|
36
|
-
|
|
37
|
-
public get qop(): string | undefined {
|
|
55
|
+
get qop() {
|
|
38
56
|
return this._qop;
|
|
39
57
|
}
|
|
40
|
-
|
|
41
|
-
|
|
58
|
+
get nc() {
|
|
59
|
+
var _a;
|
|
42
60
|
this._nc++;
|
|
43
|
-
var myHex = (
|
|
61
|
+
var myHex = ('0000000' + ((_a = this._nc) === null || _a === void 0 ? void 0 : _a.toString(16))).substr(-8);
|
|
44
62
|
return myHex;
|
|
45
63
|
}
|
|
46
|
-
|
|
47
|
-
public get cnonce(): string | undefined {
|
|
64
|
+
get cnonce() {
|
|
48
65
|
this._cnonce = this.md5(String(new Date().getTime()));
|
|
49
66
|
return this._cnonce;
|
|
50
67
|
}
|
|
51
|
-
|
|
52
|
-
private md5(data: string) {
|
|
68
|
+
md5(data) {
|
|
53
69
|
let md5 = crypto.createHash('md5');
|
|
54
70
|
let result = md5.update(data).digest('hex');
|
|
55
71
|
return result;
|
|
56
72
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
this._username = username;
|
|
60
|
-
this._password = password;
|
|
61
|
-
this._nc = 0;
|
|
62
|
-
this._initialized = false;
|
|
63
|
-
this._algorithm = 'MD5';
|
|
64
|
-
if (errorHandler)
|
|
65
|
-
this._onError = errorHandler;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
public init(wwwAuthHeader: string) {
|
|
69
|
-
|
|
70
|
-
let authSplit = wwwAuthHeader.split(',') as string[];
|
|
71
|
-
|
|
73
|
+
init(wwwAuthHeader) {
|
|
74
|
+
let authSplit = wwwAuthHeader.split(',');
|
|
72
75
|
for (let item of authSplit) {
|
|
73
76
|
if (item.indexOf('realm=') >= 0) {
|
|
74
77
|
let realmSplit = item.split('="');
|
|
75
78
|
this._realm = realmSplit[realmSplit.length - 1];
|
|
76
79
|
this._realm = this._realm.substring(0, this._realm.length - 1);
|
|
77
80
|
}
|
|
78
|
-
|
|
79
81
|
if (item.indexOf('nonce=') >= 0) {
|
|
80
82
|
let nonceSplit = item.split('="');
|
|
81
83
|
this._nonce = nonceSplit[nonceSplit.length - 1];
|
|
82
84
|
this._nonce = this._nonce.substring(0, this._nonce.length - 1);
|
|
83
85
|
}
|
|
84
|
-
|
|
85
86
|
if (item.indexOf('qop=') >= 0) {
|
|
86
87
|
let qopSplit = item.split('="');
|
|
87
88
|
this._qop = qopSplit[qopSplit.length - 1];
|
|
88
89
|
this._qop = this._qop.substring(0, this._qop.length - 1);
|
|
89
90
|
}
|
|
90
|
-
|
|
91
91
|
if (item.indexOf('opaque=') >= 0) {
|
|
92
92
|
let opaqueSplit = item.split('="');
|
|
93
93
|
this._opaque = opaqueSplit[opaqueSplit.length - 1];
|
|
94
94
|
this._opaque = this._opaque.substring(0, this._opaque.length - 1);
|
|
95
95
|
}
|
|
96
|
-
|
|
97
96
|
if (item.indexOf('algorithm=') >= 0) {
|
|
98
97
|
let algorithmSplit = item.split('=');
|
|
99
98
|
this._algorithm = algorithmSplit[algorithmSplit.length - 1];
|
|
@@ -105,17 +104,14 @@ export class AuthDigest {
|
|
|
105
104
|
}
|
|
106
105
|
this._initialized = true;
|
|
107
106
|
}
|
|
108
|
-
|
|
109
|
-
public getAuthorization(httpMethod: string, path: string): string {
|
|
107
|
+
getAuthorization(httpMethod, path) {
|
|
110
108
|
if (!this._initialized)
|
|
111
109
|
return '';
|
|
112
|
-
|
|
113
110
|
const nc = this.nc;
|
|
114
111
|
const cnonce = this.cnonce;
|
|
115
112
|
let HA1 = this.md5(this.username + ':' + this.realm + ':' + this._password);
|
|
116
113
|
let HA2 = this.md5(httpMethod + ':' + path);
|
|
117
114
|
let response = this.md5(HA1 + ':' + this.nonce + ':' + nc + ':' + cnonce + ':' + this.qop + ':' + HA2);
|
|
118
|
-
|
|
119
115
|
let res = `Digest username="${this.username}",`;
|
|
120
116
|
res += `realm="${this.realm}",`;
|
|
121
117
|
res += `nonce="${this.nonce}",`;
|
|
@@ -129,3 +125,4 @@ export class AuthDigest {
|
|
|
129
125
|
return res;
|
|
130
126
|
}
|
|
131
127
|
}
|
|
128
|
+
exports.AuthDigest = AuthDigest;
|
package/dist/Digest.d.ts
ADDED
package/dist/Digest.js
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
|
|
5
|
+
}) : (function(o, m, k, k2) {
|
|
6
|
+
if (k2 === undefined) k2 = k;
|
|
7
|
+
o[k2] = m[k];
|
|
8
|
+
}));
|
|
9
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
10
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
11
|
+
}) : function(o, v) {
|
|
12
|
+
o["default"] = v;
|
|
13
|
+
});
|
|
14
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
15
|
+
if (mod && mod.__esModule) return mod;
|
|
16
|
+
var result = {};
|
|
17
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
18
|
+
__setModuleDefault(result, mod);
|
|
19
|
+
return result;
|
|
20
|
+
};
|
|
21
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
|
+
exports.Digest = void 0;
|
|
23
|
+
const https = __importStar(require("https"));
|
|
24
|
+
const url = __importStar(require("url"));
|
|
25
|
+
const AuthDigest_1 = require("./AuthDigest");
|
|
26
|
+
class Digest {
|
|
27
|
+
constructor(baseUrl, username, password) {
|
|
28
|
+
this._baseUrl = url.parse(baseUrl, true);
|
|
29
|
+
this._authDigest = new AuthDigest_1.AuthDigest(username, password, (err) => {
|
|
30
|
+
console.error(err);
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
request(options, data, retryCount = 0, redirectCount = 0) {
|
|
34
|
+
return new Promise((resolve, reject) => {
|
|
35
|
+
var _a;
|
|
36
|
+
let resData = '';
|
|
37
|
+
if (!options.headers)
|
|
38
|
+
options.headers = {};
|
|
39
|
+
options.headers.Authorization = (_a = this._authDigest) === null || _a === void 0 ? void 0 : _a.getAuthorization(options.method, options.path);
|
|
40
|
+
const req = https.request(options, (res) => {
|
|
41
|
+
var _a, _b;
|
|
42
|
+
if (res.statusCode == 401) {
|
|
43
|
+
if (retryCount > 1) {
|
|
44
|
+
reject('Authentication failed');
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
retryCount++;
|
|
48
|
+
const wwwAuth = res.headers['www-authenticate'];
|
|
49
|
+
if (!wwwAuth.startsWith('Digest')) {
|
|
50
|
+
reject('Unsupported authentication method. Supported authentication schemes: Digest');
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
(_a = this._authDigest) === null || _a === void 0 ? void 0 : _a.init(wwwAuth);
|
|
54
|
+
if (!options.headers)
|
|
55
|
+
options.headers = {};
|
|
56
|
+
options.headers.Authorization = (_b = this._authDigest) === null || _b === void 0 ? void 0 : _b.getAuthorization(options.method, options.path);
|
|
57
|
+
return this.request(options, data, retryCount)
|
|
58
|
+
.then((value) => {
|
|
59
|
+
resolve(value);
|
|
60
|
+
})
|
|
61
|
+
.catch((resaon) => {
|
|
62
|
+
reject(resaon);
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
else if (res.statusCode >= 200 && res.statusCode < 300) {
|
|
66
|
+
// myenergi asn redirect handler.
|
|
67
|
+
const myenergiAsn = res.headers['x_myenergi-asn'];
|
|
68
|
+
if (myenergiAsn && myenergiAsn !== 'undefined' && myenergiAsn !== this._baseUrl.host) {
|
|
69
|
+
if (redirectCount > 2) {
|
|
70
|
+
reject(`Too many redirects: ${myenergiAsn}`);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
this._baseUrl.host = myenergiAsn;
|
|
74
|
+
this._baseUrl.hostname = myenergiAsn;
|
|
75
|
+
options.host = myenergiAsn;
|
|
76
|
+
options.hostname = myenergiAsn;
|
|
77
|
+
redirectCount++;
|
|
78
|
+
return this.request(options, data, retryCount, redirectCount)
|
|
79
|
+
.then((value) => {
|
|
80
|
+
resolve(value);
|
|
81
|
+
})
|
|
82
|
+
.catch((resaon) => {
|
|
83
|
+
reject(resaon);
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
// Plain 200 handling
|
|
87
|
+
res.setEncoding('utf8');
|
|
88
|
+
res.on('data', (chunk) => {
|
|
89
|
+
resData += chunk;
|
|
90
|
+
});
|
|
91
|
+
res.on('end', () => {
|
|
92
|
+
resolve(resData);
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
else if (res.statusCode >= 300 && res.statusCode < 400) {
|
|
96
|
+
if (redirectCount > 2) {
|
|
97
|
+
reject(`Too many redirects: ${res.headers['location']}`);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
const location = res.headers['location'];
|
|
101
|
+
const uri = url.parse(location, true);
|
|
102
|
+
if (uri.host !== this._baseUrl.host) {
|
|
103
|
+
this._baseUrl.host = uri.host;
|
|
104
|
+
}
|
|
105
|
+
redirectCount++;
|
|
106
|
+
return this.request(options, data, retryCount, redirectCount)
|
|
107
|
+
.then((value) => {
|
|
108
|
+
resolve(value);
|
|
109
|
+
})
|
|
110
|
+
.catch((resaon) => {
|
|
111
|
+
reject(resaon);
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
console.error('status code failed!!');
|
|
116
|
+
reject('status code failed!!');
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
req.on('error', (e) => {
|
|
121
|
+
console.error(`problem with request: ${e.message}`);
|
|
122
|
+
reject(e);
|
|
123
|
+
});
|
|
124
|
+
if (data) {
|
|
125
|
+
req.write(data);
|
|
126
|
+
}
|
|
127
|
+
req.end();
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
get(requestUrl, data) {
|
|
131
|
+
const uri = url.parse(requestUrl, true);
|
|
132
|
+
const options = {
|
|
133
|
+
hostname: this._baseUrl.hostname,
|
|
134
|
+
host: this._baseUrl.host,
|
|
135
|
+
port: this._baseUrl.port,
|
|
136
|
+
path: uri.path,
|
|
137
|
+
method: 'GET',
|
|
138
|
+
headers: {
|
|
139
|
+
Connection: 'Keep-Alive',
|
|
140
|
+
'Content-Type': 'application/json',
|
|
141
|
+
Accept: 'application/json',
|
|
142
|
+
Host: uri.hostname,
|
|
143
|
+
},
|
|
144
|
+
};
|
|
145
|
+
return this.request(options, data);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
exports.Digest = Digest;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Zappi } from './models/Zappi';
|
|
2
|
+
export declare enum ZappiChargeMode {
|
|
3
|
+
Off = 4,
|
|
4
|
+
Fast = 1,
|
|
5
|
+
Eco = 2,
|
|
6
|
+
EcoPlus = 3
|
|
7
|
+
}
|
|
8
|
+
export declare enum ZappiBoostMode {
|
|
9
|
+
Manual = 10,
|
|
10
|
+
Smart = 11,
|
|
11
|
+
Stop = 2
|
|
12
|
+
}
|
|
13
|
+
export declare class MyEnergi {
|
|
14
|
+
private _config;
|
|
15
|
+
private _digest;
|
|
16
|
+
constructor(username: string, password: string);
|
|
17
|
+
getStatusAll(): Promise<any>;
|
|
18
|
+
getStatusZappiAll(): Promise<Zappi[]>;
|
|
19
|
+
getStatusZappi(serialNumber: string): Promise<Zappi | null>;
|
|
20
|
+
setZappiChargeMode(serialNo: string, chargeMode: ZappiChargeMode): Promise<any>;
|
|
21
|
+
setZappiBoostMode(serialNo: string, boostMode: ZappiBoostMode, kwh?: number, completeTime?: string): Promise<any>;
|
|
22
|
+
setZappiGreenLevel(serialNo: string, percentage: number): Promise<any>;
|
|
23
|
+
}
|
package/dist/MyEnergi.js
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.MyEnergi = exports.ZappiBoostMode = exports.ZappiChargeMode = void 0;
|
|
13
|
+
const Digest_1 = require("./Digest");
|
|
14
|
+
var ZappiChargeMode;
|
|
15
|
+
(function (ZappiChargeMode) {
|
|
16
|
+
ZappiChargeMode[ZappiChargeMode["Off"] = 4] = "Off";
|
|
17
|
+
ZappiChargeMode[ZappiChargeMode["Fast"] = 1] = "Fast";
|
|
18
|
+
ZappiChargeMode[ZappiChargeMode["Eco"] = 2] = "Eco";
|
|
19
|
+
ZappiChargeMode[ZappiChargeMode["EcoPlus"] = 3] = "EcoPlus";
|
|
20
|
+
})(ZappiChargeMode = exports.ZappiChargeMode || (exports.ZappiChargeMode = {}));
|
|
21
|
+
var ZappiBoostMode;
|
|
22
|
+
(function (ZappiBoostMode) {
|
|
23
|
+
ZappiBoostMode[ZappiBoostMode["Manual"] = 10] = "Manual";
|
|
24
|
+
ZappiBoostMode[ZappiBoostMode["Smart"] = 11] = "Smart";
|
|
25
|
+
ZappiBoostMode[ZappiBoostMode["Stop"] = 2] = "Stop";
|
|
26
|
+
})(ZappiBoostMode = exports.ZappiBoostMode || (exports.ZappiBoostMode = {}));
|
|
27
|
+
class MyEnergi {
|
|
28
|
+
constructor(username, password) {
|
|
29
|
+
this._config = {
|
|
30
|
+
username: '',
|
|
31
|
+
password: '',
|
|
32
|
+
base_url: 'https://s18.myenergi.net',
|
|
33
|
+
eddi_url: '/cgi-jstatus-E',
|
|
34
|
+
zappi_url: '/cgi-jstatus-Z',
|
|
35
|
+
harvi_url: '/cgi-jstatus-H',
|
|
36
|
+
status_url: '/cgi-jstatus-*',
|
|
37
|
+
dayhour_url: '/cgi-jdayhour-',
|
|
38
|
+
zappi_mode_url: '/cgi-zappi-mode-Z',
|
|
39
|
+
zappi_min_green_url: '/cgi-set-min-green-Z',
|
|
40
|
+
//https://s18.myenergi.net/cgi-jdayhour-Znnnnnnnn-YYYY-MM-DD
|
|
41
|
+
};
|
|
42
|
+
this._config.username = username;
|
|
43
|
+
this._config.password = password;
|
|
44
|
+
this._digest = new Digest_1.Digest(this._config.base_url, this._config.username, this._config.password);
|
|
45
|
+
}
|
|
46
|
+
getStatusAll() {
|
|
47
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
48
|
+
const data = yield this._digest.get(this._config.status_url);
|
|
49
|
+
const jsonData = JSON.parse(data);
|
|
50
|
+
return jsonData;
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
getStatusZappiAll() {
|
|
54
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
55
|
+
const data = yield this._digest.get(this._config.zappi_url);
|
|
56
|
+
const jsonData = JSON.parse(data);
|
|
57
|
+
if (jsonData.zappi)
|
|
58
|
+
return Object.assign([], jsonData.zappi);
|
|
59
|
+
else
|
|
60
|
+
return [];
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
getStatusZappi(serialNumber) {
|
|
64
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
65
|
+
const data = yield this._digest.get(this._config.zappi_url);
|
|
66
|
+
const jsonData = JSON.parse(data);
|
|
67
|
+
if (jsonData.zappi) {
|
|
68
|
+
const zappi = Object.assign([], jsonData.zappi).find((zappi) => {
|
|
69
|
+
return zappi.sno === serialNumber;
|
|
70
|
+
});
|
|
71
|
+
if (zappi)
|
|
72
|
+
return Object.assign({}, zappi);
|
|
73
|
+
else
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
else
|
|
77
|
+
return null;
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
setZappiChargeMode(serialNo, chargeMode) {
|
|
81
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
82
|
+
const url = `${this._config.zappi_mode_url}${serialNo}-${chargeMode}-0-0-0000`;
|
|
83
|
+
const data = yield this._digest.get(url);
|
|
84
|
+
const jsonData = JSON.parse(data);
|
|
85
|
+
return jsonData;
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
setZappiBoostMode(serialNo, boostMode, kwh = 0, completeTime = '0000') {
|
|
89
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
90
|
+
if (boostMode === ZappiBoostMode.Stop) {
|
|
91
|
+
kwh = 0;
|
|
92
|
+
completeTime = '0000';
|
|
93
|
+
}
|
|
94
|
+
const url = `${this._config.zappi_mode_url}${serialNo}-0-${boostMode}-${kwh}-${completeTime}`;
|
|
95
|
+
const data = yield this._digest.get(url);
|
|
96
|
+
const jsonData = JSON.parse(data);
|
|
97
|
+
return jsonData;
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
setZappiGreenLevel(serialNo, percentage) {
|
|
101
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
102
|
+
const url = `${this._config.zappi_min_green_url}${serialNo}-${percentage}`;
|
|
103
|
+
const data = yield this._digest.get(url);
|
|
104
|
+
const jsonData = JSON.parse(data);
|
|
105
|
+
return jsonData;
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
exports.MyEnergi = MyEnergi;
|
|
File without changes
|
package/dist/index.js
ADDED
|
File without changes
|
package/package.json
CHANGED
|
@@ -1,12 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "myenergi-api",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "NodeJS implementation of MyEnergi API for controlling Zappi, Eddi and other MyEnergi products",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist/**/*"
|
|
9
|
+
],
|
|
7
10
|
"scripts": {
|
|
8
11
|
"build": "tsc",
|
|
9
|
-
"test": "env TS_NODE_PROJECT='tsconfig.testing.json' mocha -r ts-node/register tests/**/*-spec.ts"
|
|
12
|
+
"test": "env TS_NODE_PROJECT='tsconfig.testing.json' mocha -r ts-node/register tests/**/*-spec.ts",
|
|
13
|
+
"format": "prettier --write \"src/**/*.ts\" \"src/**/*.js\"",
|
|
14
|
+
"lint": "tslint -p tsconfig.json",
|
|
15
|
+
"prepare": "tsc",
|
|
16
|
+
"preversion": "npm test",
|
|
17
|
+
"version": "",
|
|
18
|
+
"postversion": ""
|
|
10
19
|
},
|
|
11
20
|
"repository": {
|
|
12
21
|
"type": "git",
|
|
@@ -33,6 +42,7 @@
|
|
|
33
42
|
"@types/node": "^16.11.9",
|
|
34
43
|
"chai": "^4.3.4",
|
|
35
44
|
"mocha": "^9.1.3",
|
|
45
|
+
"prettier": "^2.4.1",
|
|
36
46
|
"ts-node": "^10.4.0",
|
|
37
47
|
"tslint": "^6.1.3",
|
|
38
48
|
"typescript": "^4.5.2"
|
package/src/Digest.ts
DELETED
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
import * as https from 'https';
|
|
2
|
-
import { RequestOptions } from 'https';
|
|
3
|
-
import * as url from "url";
|
|
4
|
-
import { AuthDigest } from './AuthDigest';
|
|
5
|
-
|
|
6
|
-
export class Digest {
|
|
7
|
-
private _authDigest?: AuthDigest;
|
|
8
|
-
|
|
9
|
-
constructor(username: string, password: string) {
|
|
10
|
-
this._authDigest = new AuthDigest(username, password, (err) => {
|
|
11
|
-
console.error(err);
|
|
12
|
-
});
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
private request(options: RequestOptions, data?: any, retryCount: number = 0): Promise<any> {
|
|
16
|
-
return new Promise<any>((resolve, reject) => {
|
|
17
|
-
let resData: string = '';
|
|
18
|
-
if (!options.headers)
|
|
19
|
-
options.headers = {};
|
|
20
|
-
options.headers.Authorization = this._authDigest?.getAuthorization(options.method as string, options.path as string);
|
|
21
|
-
const req = https.request(options, (res: any) => {
|
|
22
|
-
|
|
23
|
-
if (res.statusCode == 401) {
|
|
24
|
-
if (retryCount > 1) {
|
|
25
|
-
reject("Authentication failed");
|
|
26
|
-
return;
|
|
27
|
-
}
|
|
28
|
-
retryCount++;
|
|
29
|
-
const wwwAuth = res.headers["www-authenticate"] as string;
|
|
30
|
-
if (!wwwAuth.startsWith('Digest')) {
|
|
31
|
-
reject('Unsupported authentication method. Supported authentication schemes: Digest');
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
this._authDigest?.init(wwwAuth);
|
|
35
|
-
if (!options.headers)
|
|
36
|
-
options.headers = {};
|
|
37
|
-
options.headers.Authorization = this._authDigest?.getAuthorization(options.method as string, options.path as string);
|
|
38
|
-
return this.request(options, data, retryCount).then(value => {
|
|
39
|
-
resolve(value);
|
|
40
|
-
}).catch(resaon => {
|
|
41
|
-
reject(resaon);
|
|
42
|
-
});
|
|
43
|
-
} else if (res.statusCode >= 200 && res.statusCode < 300) {
|
|
44
|
-
res.setEncoding('utf8');
|
|
45
|
-
res.on('data', (chunk: string) => {
|
|
46
|
-
resData += chunk;
|
|
47
|
-
});
|
|
48
|
-
res.on('end', () => {
|
|
49
|
-
resolve(resData);
|
|
50
|
-
});
|
|
51
|
-
} else {
|
|
52
|
-
console.error("status code failed!!");
|
|
53
|
-
reject("status code failed!!");
|
|
54
|
-
return;
|
|
55
|
-
}
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
req.on('error', (e: any) => {
|
|
59
|
-
console.error(`problem with request: ${e.message}`);
|
|
60
|
-
reject(e);
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
if (data) {
|
|
64
|
-
req.write(data);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
req.end();
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
public get(requestUrl: string, data?: any): Promise<any> {
|
|
72
|
-
const uri = url.parse(requestUrl, true);
|
|
73
|
-
const options: RequestOptions = {
|
|
74
|
-
hostname: uri.hostname,
|
|
75
|
-
port: uri.port,
|
|
76
|
-
path: uri.pathname,
|
|
77
|
-
method: 'GET',
|
|
78
|
-
headers: {
|
|
79
|
-
'Connection': 'Keep-Alive',
|
|
80
|
-
'Content-Type': 'application/json',
|
|
81
|
-
'Accept': 'application/json',
|
|
82
|
-
'Host': uri.hostname as string,
|
|
83
|
-
}
|
|
84
|
-
};
|
|
85
|
-
return this.request(options, data);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
}
|
package/src/MyEnergi.ts
DELETED
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
import { Digest } from './Digest';
|
|
2
|
-
import { Zappi } from './models/Zappi';
|
|
3
|
-
|
|
4
|
-
export enum ZappiChargeMode {
|
|
5
|
-
Off = 4,
|
|
6
|
-
Fast = 1,
|
|
7
|
-
Eco = 2,
|
|
8
|
-
EcoPlus = 3
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export enum ZappiBoostMode {
|
|
12
|
-
Manual = 10,
|
|
13
|
-
Smart = 11,
|
|
14
|
-
Stop = 2
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export class MyEnergi {
|
|
18
|
-
private _config = {
|
|
19
|
-
username: '',
|
|
20
|
-
password: '',
|
|
21
|
-
eddi_url: 'https://s18.myenergi.net/cgi-jstatus-E',
|
|
22
|
-
zappi_url: 'https://s18.myenergi.net/cgi-jstatus-Z',
|
|
23
|
-
harvi_url: 'https://s18.myenergi.net/cgi-jstatus-H',
|
|
24
|
-
status_url: 'https://s18.myenergi.net/cgi-jstatus-*',
|
|
25
|
-
dayhour_url: 'https://s18.myenergi.net/cgi-jdayhour-',
|
|
26
|
-
zappi_mode_url: 'https://s18.myenergi.net/cgi-zappi-mode-Z',
|
|
27
|
-
zappi_min_green_url: 'https://s18.myenergi.net/cgi-set-min-green-Z'
|
|
28
|
-
//https://s18.myenergi.net/cgi-jdayhour-Znnnnnnnn-YYYY-MM-DD
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
private _digest: Digest;
|
|
32
|
-
|
|
33
|
-
constructor(username: string, password: string) {
|
|
34
|
-
this._config.username = username;
|
|
35
|
-
this._config.password = password;
|
|
36
|
-
this._digest = new Digest(this._config.username, this._config.password);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
public async getStatusAll(): Promise<any> {
|
|
40
|
-
const data = await this._digest.get(this._config.status_url);
|
|
41
|
-
return data;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
public async getStatusZappiAll(): Promise<Zappi[]> {
|
|
45
|
-
const data = await this._digest.get(this._config.zappi_url);
|
|
46
|
-
const jsonData = JSON.parse(data);
|
|
47
|
-
if (jsonData.zappi)
|
|
48
|
-
return Object.assign<Zappi[], any>([] as Zappi[], jsonData.zappi);
|
|
49
|
-
else
|
|
50
|
-
return [] as Zappi[];
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
public async getStatusZappi(serialNumber: string): Promise<Zappi | null> {
|
|
54
|
-
const data = await this._digest.get(this._config.zappi_url);
|
|
55
|
-
const jsonData = JSON.parse(data);
|
|
56
|
-
if (jsonData.zappi) {
|
|
57
|
-
const zappi = (Object.assign<Zappi[], any>([] as Zappi[], jsonData.zappi) as Zappi[]).find(zappi => {
|
|
58
|
-
return zappi.sno === serialNumber;
|
|
59
|
-
});
|
|
60
|
-
if (zappi)
|
|
61
|
-
return Object.assign<Zappi, any>({} as Zappi, zappi);
|
|
62
|
-
else
|
|
63
|
-
return null;
|
|
64
|
-
}
|
|
65
|
-
else
|
|
66
|
-
return null;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
public async setZappiChargeMode(serialNo: string, chargeMode: ZappiChargeMode): Promise<any> {
|
|
70
|
-
const url = `${this._config.zappi_mode_url}${serialNo}-${chargeMode}-0-0-0000`;
|
|
71
|
-
const data = await this._digest.get(url);
|
|
72
|
-
return data;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
public async setZappiBoostMode(serialNo: string, boostMode: ZappiBoostMode, kwh: number = 0, completeTime: string = '0000'): Promise<any> {
|
|
76
|
-
if (boostMode === ZappiBoostMode.Stop) {
|
|
77
|
-
kwh = 0;
|
|
78
|
-
completeTime = '0000';
|
|
79
|
-
}
|
|
80
|
-
const url = `${this._config.zappi_mode_url}${serialNo}-0-${boostMode}-${kwh}-${completeTime}`;
|
|
81
|
-
const data = await this._digest.get(url);
|
|
82
|
-
return data;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
public async setZappiGreenLevel(serialNo: string, percentage: number): Promise<any> {
|
|
86
|
-
const url = `${this._config.zappi_min_green_url}${serialNo}-${percentage}`;
|
|
87
|
-
const data = await this._digest.get(url);
|
|
88
|
-
return data;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
}
|
package/tests/MyEnergi-spec.ts
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { MyEnergi } from '../src/MyEnergi'
|
|
2
|
-
import { expect } from 'chai'
|
|
3
|
-
|
|
4
|
-
describe("MyEnergi Tests", () => {
|
|
5
|
-
it("should be able to test", () => {
|
|
6
|
-
expect(true).to.be.true;
|
|
7
|
-
});
|
|
8
|
-
it("should be able to create instance", () => {
|
|
9
|
-
const myenergi = new MyEnergi('test', 'pwd');
|
|
10
|
-
expect(myenergi).to.be.instanceOf(MyEnergi);
|
|
11
|
-
});
|
|
12
|
-
});
|
package/tests/debug.ts
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { exit } from 'process';
|
|
2
|
-
import { MyEnergi } from '../src';
|
|
3
|
-
import * as dotenv from 'dotenv';
|
|
4
|
-
import { ZappiBoostMode, ZappiChargeMode } from '../src/MyEnergi';
|
|
5
|
-
|
|
6
|
-
dotenv.config();
|
|
7
|
-
const myenergi = new MyEnergi(process.env.USERNAME as string, process.env.PASSWORD as string);
|
|
8
|
-
myenergi.getStatusAll().then(val => {
|
|
9
|
-
console.log(val);
|
|
10
|
-
myenergi.getStatusZappiAll().then(v => {
|
|
11
|
-
console.log(v);
|
|
12
|
-
const sno: string = v[0].sno;
|
|
13
|
-
myenergi.setZappiChargeMode(sno, ZappiChargeMode.Fast).then(x => {
|
|
14
|
-
console.log(x);
|
|
15
|
-
myenergi.setZappiBoostMode(sno, ZappiBoostMode.Stop).then(x => {
|
|
16
|
-
console.log(x);
|
|
17
|
-
myenergi.setZappiGreenLevel(sno, 0).then(x => {
|
|
18
|
-
console.log(x);
|
|
19
|
-
myenergi.getStatusZappi(sno).then(x => {
|
|
20
|
-
console.log(x);
|
|
21
|
-
exit();
|
|
22
|
-
}).catch(err => {
|
|
23
|
-
console.error(err);
|
|
24
|
-
exit();
|
|
25
|
-
});
|
|
26
|
-
}).catch(err => {
|
|
27
|
-
console.error(err);
|
|
28
|
-
exit();
|
|
29
|
-
});
|
|
30
|
-
}).catch(err => {
|
|
31
|
-
console.error(err);
|
|
32
|
-
exit();
|
|
33
|
-
});
|
|
34
|
-
}).catch(err => {
|
|
35
|
-
console.error(err);
|
|
36
|
-
exit();
|
|
37
|
-
});
|
|
38
|
-
}).catch(err => {
|
|
39
|
-
console.error(err);
|
|
40
|
-
exit();
|
|
41
|
-
});
|
|
42
|
-
}).catch(err => {
|
|
43
|
-
console.error(err);
|
|
44
|
-
exit();
|
|
45
|
-
});
|
package/tsconfig.testing.json
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"module": "commonjs",
|
|
4
|
-
"target": "es2015",
|
|
5
|
-
"lib": ["es2017"],
|
|
6
|
-
"outDir": "./dist",
|
|
7
|
-
"declaration": false,
|
|
8
|
-
"noImplicitAny": false,
|
|
9
|
-
"removeComments": true,
|
|
10
|
-
"inlineSourceMap": true,
|
|
11
|
-
"moduleResolution": "node",
|
|
12
|
-
},
|
|
13
|
-
"include": ["src/**/*.ts", "src/**/*.d.ts", "tests/**/*.ts"]
|
|
14
|
-
}
|