diff --git a/README.md b/README.md index a3dfbf0..c836a05 100644 --- a/README.md +++ b/README.md @@ -1,32 +1,46 @@ -# Smappee-NodeJS -Smappee nodejs project to read smappee data. +Please note : this fork is just sending the last 5 min consumption and current charging session to mqtt at this stage. I did never code in Node JS so I'll see what I can do... +Todo list : +- Store token in file ✅ +- Read token from file ✅ +- Request new token if token is expired ✅ +- Implement EV line API requests ✅ (read current session only) +- Send data to MQTT server : partial +- Remove deprecated dependency : https://github.com/request/request/issues/3142 + +# Smappee2MQTT (with NodeJS) +This nodejs project allows to read data from the smappee API and send it to an MQTT server. Tested with Mosquitto MQTT server and openHab to import Electric Car (EV) charging session status and house consumption. Based on https://support.smappee.com/hc/en-us/articles/202153935-Where-can-I-find-the-API-documentation- ## Installation ```bash -npm install smappee-nodejs --save +npm install smappee2mqtt --save ``` ## Usage ### Create a new file 'my-smappee.js' ```javascript -var SmappeeAPI = require('smappee-nodejs'); +var SmappeeAPI = require('smappee2mqtt'); var smappee = new SmappeeAPI({ debug: false, + noAPIcall: false, clientId: "xxx", clientSecret: "xxx", username: "xxx", - password: "xxx" + password: "xxx", + + mqtt_server: "xxx", + mqtt_port: "xxx", + mqtt_baseTopic : "smappee/" }); module.exports = smappee; ``` -### In another nodejs file +### In another nodejs file : test.js ```javascript var smappee = require('./my-smappee'); @@ -34,6 +48,8 @@ smappee.getServiceLocations(function(output) { console.log(output); }) ``` +### To run your test.js file type : +> node test.js The following functions are available: `getServiceLocations(callback)`, `getServiceLocationInfo(serviceLocationId, callback)`, ... @@ -73,4 +89,27 @@ var to = moment().utc().valueOf(); smappee.getConsumptions("0000", smappee.AGGREGATION_TYPES.MONTHLY, from, to, function(output) { console.log(output); }) -``` \ No newline at end of file +``` + +### getLatestConsumption(serviceLocationId, callback) +Get a list of latest consumptions + +```javascript +var smappee = require('./my-smappee'); + +smappee.getLatestConsumption(serviceLocationId, function(output) { + console.log("getLatestConsumption (multiply WH by 12 if aggregate by 5 minutes since 5*12 = one hour to get Watts) : ",output); +}) +``` + +### getCurrentChargingSession(serviceLocationId,aggregation, from, to, callback) +Get a list of current charging session (will return -1 if no car is charging) + +```javascript +var smappee = require('./my-smappee'); +smappee.getCurrentChargingSession(chargingStationSN, function(output){ + console.log(output); +}) +``` + +TIP: To convert the 5 minute interval from Energy [Wh] to Power [W], like the Smappee Dashboard reports these, you have to do these values times 12 as there are 12 x 5 minute intervals in an hour. diff --git a/example/my-smappee.js b/example/my-smappee.js index 76955b3..c4376d1 100644 --- a/example/my-smappee.js +++ b/example/my-smappee.js @@ -1,4 +1,4 @@ -var SmappeeAPI = require('smappee-nodejs'); +var SmappeeAPI = require('smappee2mqtt'); var smappee = new SmappeeAPI({ debug: true, @@ -7,7 +7,11 @@ var smappee = new SmappeeAPI({ clientSecret: "xxx", username: "xxx", - password: "xxx" + password: "xxx", + + mqtt_server: "192.168.0.1", + mqtt_port: "1883", + mqtt_baseTopic : "smappee/" }); module.exports = smappee; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..b2fa2cb --- /dev/null +++ b/package-lock.json @@ -0,0 +1,500 @@ +{ + "name": "smappee2mqtt", + "version": "2.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "smappee2mqtt", + "version": "2.0", + "license": "OSL-3.0", + "dependencies": { + "moment": "^2.10.6", + "request": "^2.67.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/asn1": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", + "license": "MIT", + "dependencies": { + "safer-buffer": "~2.1.0" + } + }, + "node_modules/assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", + "license": "Apache-2.0", + "engines": { + "node": "*" + } + }, + "node_modules/aws4": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.0.tgz", + "integrity": "sha512-3AungXC4I8kKsS9PuS4JH2nc+0bVY/mjgrephHTIi8fpEeGsTHBUJeosp0Wc1myYMElmD0B3Oc4XL/HVJ4PV2g==", + "license": "MIT" + }, + "node_modules/bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", + "license": "BSD-3-Clause", + "dependencies": { + "tweetnacl": "^0.14.3" + } + }, + "node_modules/caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", + "license": "Apache-2.0" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", + "license": "MIT" + }, + "node_modules/dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", + "license": "MIT", + "dependencies": { + "assert-plus": "^1.0.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", + "license": "MIT", + "dependencies": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, + "node_modules/extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", + "engines": [ + "node >=0.6.0" + ], + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "license": "MIT" + }, + "node_modules/forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", + "license": "Apache-2.0", + "engines": { + "node": "*" + } + }, + "node_modules/form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", + "license": "MIT", + "dependencies": { + "assert-plus": "^1.0.0" + } + }, + "node_modules/har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==", + "license": "ISC", + "engines": { + "node": ">=4" + } + }, + "node_modules/har-validator": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", + "deprecated": "this library is no longer supported", + "license": "MIT", + "dependencies": { + "ajv": "^6.12.3", + "har-schema": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==", + "license": "MIT", + "dependencies": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + }, + "engines": { + "node": ">=0.8", + "npm": ">=1.3.7" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", + "license": "MIT" + }, + "node_modules/isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", + "license": "MIT" + }, + "node_modules/jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", + "license": "MIT" + }, + "node_modules/json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", + "license": "(AFL-2.1 OR BSD-3-Clause)" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "license": "MIT" + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "license": "ISC" + }, + "node_modules/jsprim": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", + "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", + "license": "MIT", + "dependencies": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.4.0", + "verror": "1.10.0" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/moment": { + "version": "2.30.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", + "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "license": "Apache-2.0", + "engines": { + "node": "*" + } + }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", + "license": "MIT" + }, + "node_modules/psl": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", + "license": "MIT" + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", + "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142", + "license": "Apache-2.0", + "dependencies": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/sshpk": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz", + "integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==", + "license": "MIT", + "dependencies": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + }, + "bin": { + "sshpk-conv": "bin/sshpk-conv", + "sshpk-sign": "bin/sshpk-sign", + "sshpk-verify": "bin/sshpk-verify" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "license": "BSD-3-Clause", + "dependencies": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", + "license": "Unlicense" + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "license": "MIT", + "bin": { + "uuid": "bin/uuid" + } + }, + "node_modules/verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", + "engines": [ + "node >=0.6.0" + ], + "license": "MIT", + "dependencies": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + } + } +} diff --git a/package.json b/package.json index 94db04d..6007a9e 100644 --- a/package.json +++ b/package.json @@ -1,21 +1,25 @@ { - "name": "smappee-nodejs", - "version": "1.0.0", + "name": "smappee2mqtt", + "version": "1.0.5", + "description": "Connect to the Smappee API and push the data into your MQTT server", "main": "./smappee-api.js", - "author": "CONNCTED", + "author": "hyzteric", "keywords": [ "smappee", - "energy monitoring" + "energy monitoring", + "async-mqtt" ], + "license": "OSL-3.0", + "homepage": "https://github.com/hyzteric/Smappee2MQTT/", "repository": { "type": "git", - "url": "git://github.com/CONNCTED/Smappee-NodeJS.git" + "url": "git://github.com/hyzteric/Smappee2MQTT.git" }, "bugs": { - "url": "http://github.com/CONNCTED/Smappee-NodeJS/issues" + "url": "https://github.com/hyzteric/Smappee2MQTT/issues" }, "dependencies": { - "moment": "^2.10.6", - "request": "^2.67.0" + "moment": "latest", + "request": "latest" } } diff --git a/smappee-api.js b/smappee-api.js index 6506925..93168f0 100644 --- a/smappee-api.js +++ b/smappee-api.js @@ -2,6 +2,8 @@ var http = require('http'); var request = require('request'); var querystring = require('querystring'); var moment = require('moment'); +const fs = require('node:fs'); +const mqtt = require('async-mqtt'); function SmappeeAPI(settings) { @@ -9,7 +11,14 @@ function SmappeeAPI(settings) { var clientSecret = settings.clientSecret; var username = settings.username; var password = settings.password; - + var mqtt_server = settings.mqtt_server; + var mqtt_port = settings.mqtt_port; + var mqtt_baseTopic = settings.mqtt_baseTopic; + var mqtt_clientID = settings.mqtt_clientID || "smappee2pqtt" ; + var mqtt_protocol = settings.mqtt_protocol || "mqtt" ; + mqtt_protocol + + this.noAPIcall = settings.noAPIcall || false; this.debug = settings.debug || false; var thisObject = this; @@ -34,7 +43,7 @@ function SmappeeAPI(settings) { * @param handler function that will be called when request is completed. */ this.getServiceLocations = function(handler) { - _get('https://app1pub.smappee.net/dev/v1/servicelocation', {}, handler); + _get('https://app1pub.smappee.net/dev/v3/servicelocation', {}, handler); }; /** @@ -46,7 +55,7 @@ function SmappeeAPI(settings) { * @param handler function that will be called when request is completed. */ this.getServiceLocationInfo = function(serviceLocationId, handler) { - var url = 'https://app1pub.smappee.net/dev/v1/servicelocation/' + serviceLocationId + '/info'; + var url = 'https://app1pub.smappee.net/dev/v3/servicelocation/' + serviceLocationId + '/info'; _get(url, {}, handler); }; @@ -62,33 +71,81 @@ function SmappeeAPI(settings) { * @param handler function that will be called when request is completed. */ this.getConsumptions = function(serviceLocationId, aggregation, from, to, handler) { - var url = 'https://app1pub.smappee.net/dev/v1/servicelocation/' + serviceLocationId + '/consumption'; + var url = 'https://app1pub.smappee.net/dev/v3/servicelocation/' + serviceLocationId + '/consumption'; var fields = { aggregation: aggregation, from: from, to: to }; - _get(url, fields, handler); + //_get(url, fields, handler); + _get(url, fields, function(output) { + try { + var strOutput = JSON.stringify(output); + if (thisObject.debug) { + console.log("getConsumptions output : "+ strOutput); + } + if (strOutput.length>0) { + if (thisObject.debug) { + console.log("publishing getConsumptions output to mqtt"); + } + _publishMQTT(mqtt_baseTopic+"consumptions",JSON.stringify(output)); + handler(output); + } else { + if (thisObject.debug) { + console.log("getConsumptions output null"); + } + _publishMQTT(mqtt_baseTopic+"consumptions","no consumptions"); + handler(undefined); + } + } catch (e) { + if (thisObject.debug) { + console.log("getConsumptions output null"); + } + _publishMQTT(mqtt_baseTopic+"consumptions","no consumptions"); + handler(undefined); + } + }); }; this.getLatestConsumption = function(serviceLocationId, handler) { - var url = 'https://app1pub.smappee.net/dev/v1/servicelocation/' + serviceLocationId + '/consumption'; + var url = 'https://app1pub.smappee.net/dev/v3/servicelocation/' + serviceLocationId + '/consumption'; var fields = { aggregation: this.AGGREGATION_TYPES.MINUTES, - from: moment().subtract(30, 'minutes').utc().valueOf(), - to: moment().add(30, 'minutes').utc().valueOf() + from: moment().subtract(20, 'minutes').utc().valueOf(), + to: moment().add(5, 'minutes').utc().valueOf() }; _get(url, fields, function(output) { - if (output.consumptions.length > 0) { - handler(output.consumptions[output.consumptions.length - 1]); - } else { - handler(undefined); - } + try { + var strOutput = JSON.stringify(output.consumptions[output.consumptions.length - 1]); + if (thisObject.debug) { + console.log("getConsumptions output 1 : "+ JSON.stringify(output)); + } + if (strOutput.length > 0) { + if (thisObject.debug) { + console.log("publishing getConsumptions output to mqtt"); + } + _publishMQTT(mqtt_baseTopic+"consumptions",strOutput); + handler(output.consumptions[output.consumptions.length - 1]); + } else { + if (thisObject.debug) { + console.log("getConsumptions output is null"); + } + console.log("getConsumptions avant publish "); + _publishMQTT(mqtt_baseTopic+"consumptions","no consumptions"); + handler(undefined); + } + } catch (e) { + if (thisObject.debug) { + console.log("getConsumptions output null"); + } + _publishMQTT(mqtt_baseTopic+"consumptions","no consumptions"); + handler(undefined); + } }); }; this.getMonthlyConsumptionsForLastYear = function(serviceLocationId, handler) { - var url = 'https://app1pub.smappee.net/dev/v1/servicelocation/' + serviceLocationId + '/consumption'; + var url = 'https://app1pub.smappee.net/dev/v3/servicelocation/' + serviceLocationId + '/consumption'; var fields = { aggregation: this.AGGREGATION_TYPES.MONTHLY, from: moment().subtract(1, 'year').utc().valueOf(), @@ -98,7 +155,7 @@ function SmappeeAPI(settings) { }; this.getEvents = function(serviceLocationId, applianceId, from, to, maxNumber, handler) { - var url = 'https://app1pub.smappee.net/dev/v1/servicelocation/' + serviceLocationId + '/events'; + var url = 'https://app1pub.smappee.net/dev/v3/servicelocation/' + serviceLocationId + '/events'; var fields = { applienceId: applianceId, from: from, @@ -109,21 +166,126 @@ function SmappeeAPI(settings) { }; this.turnActuatorOn = function(serviceLocationId, actuatorId, duration, handler) { - var url = 'https://app1pub.smappee.net/dev/v1/servicelocation/' + serviceLocationId + '/actuator/' + actuatorId + '/on'; + var url = 'https://app1pub.smappee.net/dev/v3/servicelocation/' + serviceLocationId + '/actuator/' + actuatorId + '/on'; _post(url, "{'duration': " + duration + "}", handler); }; this.turnActuatorOff = function(serviceLocationId, actuatorId, duration, handler) { - var url = 'https://app1pub.smappee.net/dev/v1/servicelocation/' + serviceLocationId + '/actuator/' + actuatorId + '/off'; + var url = 'https://app1pub.smappee.net/dev/v3/servicelocation/' + serviceLocationId + '/actuator/' + actuatorId + '/off'; _post(url, "{'duration': " + duration + "}", handler); }; + this.getCurrentChargingSession = function(chargingStationSN, handler) { + var url = 'https://app1pub.smappee.net/dev/v3/chargingstations/' + chargingStationSN + '/sessions'; - // HELPER METHODS ++++++++++++++++++++++++++++++++++++++++ + if (thisObject.debug) { + console.log("getCurrentChargingSession url : "+ url); + } + var fields = { + active: true, + range:"1635721200000" + }; + _get(url, fields, function(output) { + try { + var strOutput = JSON.stringify(output); + if (strOutput.length>0 && strOutput!="[]") { + var apiResponse = strOutput; + if (apiResponse.startsWith("[")){ + apiResponse = apiResponse.substring(1, apiResponse.length-1);//remove invalid [ ] around response + } + let timestampNow = Date.now(); + var timestampNowSeconds = timestampNow/1000; + apiResponse=apiResponse.substring(0, apiResponse.length-1); + apiResponse+=",\"timestamp\":"+timestampNowSeconds.toString()+"}"; + if (thisObject.debug) { + console.log("apiResponse with timestamp : "+ apiResponse); + } + _publishMQTT(mqtt_baseTopic+"currentChargingSession",apiResponse); + handler(apiResponse); + } else { + _publishMQTT(mqtt_baseTopic+"currentChargingSession","{\"id\":-1}"); + handler(undefined); + } + } catch (e) { + _publishMQTT(mqtt_baseTopic+"currentChargingSession","{\"id\":-1}"); + handler(undefined); + } + }); + }; + + + // HELPER METHODS ++++++++++++++++++++++++++++++++++++++++ + var _getAccessToken = function(handler) { - if (typeof accessToken == 'undefined') { + var tokenFile = null; + var existingToken = null; + let timestampNow = Date.now(); + var timestampNowSeconds = timestampNow/1000; + + //Try to read Access token from file + if (fs.existsSync('./token.json') && fs.existsSync('./tokenBirth.txt')) { + tokenFile=fs.readFileSync('./token.json'); + existingToken = JSON.parse(tokenFile); + if (thisObject.debug) { + console.log("Existing Token found : "+tokenFile); + } + var tokenBirth=fs.readFileSync('./tokenBirth.txt'); + var tokenDeath = Number(tokenBirth)+existingToken.expires_in-60; //token death with 60sec threshold + if (tokenDeath { + if (err) { + console.error('Could not save token to file', err); + } + }); + + fs.writeFileSync('./tokenBirth.txt', timestampNowSeconds.toString(), err => { + if (err) { + console.error('Could not save token birth to file', err); + } + }); + }); + } else { + //Using existing Token from file + accessToken=existingToken; + } + } + + if (accessToken==null){ + //Still got no Token, requesting a new one var body = { client_id: clientId, client_secret: clientSecret, @@ -137,7 +299,7 @@ function SmappeeAPI(settings) { } var options = { - url: 'https://app1pub.smappee.net/dev/v1/oauth2/token', + url: 'https://app1pub.smappee.net/dev/v3/oauth2/token', headers: { 'Host': 'app1pub.smappee.net' }, @@ -149,14 +311,29 @@ function SmappeeAPI(settings) { return console.error('Request failed:', err); } if (thisObject.debug) { - console.log('Server responded with:', body); + //console.log('Server responded with:', body); } accessToken = JSON.parse(body); - handler(accessToken); + + fs.writeFileSync('./token.json', body, err => { + if (err) { + console.error('Could not save token to file', err); + } + }); + + fs.writeFileSync('./tokenBirth.txt', timestampNowSeconds.toString(), err => { + if (err) { + console.error('Could not save token birth to file', err); + } + }); }); + } + + if (accessToken!=null){ + handler(accessToken); } else { - handler(accessToken) + return console.error('Could not get valid token'); } }; @@ -180,7 +357,7 @@ function SmappeeAPI(settings) { return console.error('Request failed:', err); } if (thisObject.debug) { - console.log('Server responded with:', body); + //console.log('Server responded with:', body); } handler({status: 'OK'}); @@ -189,34 +366,107 @@ function SmappeeAPI(settings) { }; var _get = function(url, fields, handler) { - _getAccessToken(function(accessToken) { - var query = querystring.stringify(fields); - if (thisObject.debug) { - console.log("Request to " + url); - console.log("With parameters: " + query); - } + if (thisObject.noAPIcall){ + console.log('Did not call API as noAPIcall is true'); + handler(); + } else { + _getAccessToken(function(accessToken) { + var query = querystring.stringify(fields); + if (thisObject.debug) { + console.log("Request to " + url); + console.log("With parameters: " + query); + } + + var options = { + url: url + "?" + query, + headers: { + 'Authorization': 'Bearer ' + accessToken.access_token + } + }; + + request.get(options, function (err, httpResponse, body) { + if (err) { + return console.error('Request failed:', err); + } + if (thisObject.debug) { + //console.log('Server responded with:', body); + } + var output = JSON.parse(body); + handler(output); + }); //end of GET request + }); //end of access token request + } - var options = { - url: url + "?" + query, - headers: { - 'Authorization': 'Bearer ' + accessToken.access_token - } - }; + }; - request.get(options, function (err, httpResponse, body) { - if (err) { - return console.error('Request failed:', err); - } - if (thisObject.debug) { - console.log('Server responded with:', body); - } - var output = JSON.parse(body); - handler(output); - }); //end of GET request - }); //end of access token request - }; + //var _publishMQTT = function(topic, value){ + async function _publishMQTT(topic, value){ + const options = { + protocol: mqtt_protocol, + host: mqtt_server, + port: mqtt_port, + clientId: mqtt_clientID + }; + + if (thisObject.debug) { + console.log('Connecting to mqtt : ',mqtt_server,":",mqtt_port); + } + try { + //const client = mqtt.connect(options); + const client = await mqtt.connect(options); + if (thisObject.debug) { + console.log('After connection attempt ',client.connected); + } + + client.on('offline', () => { + console.log('Client is offline'); + }); + + client.on('reconnect', () => { + console.log('Reconnecting to MQTT broker'); + }); + + client.on('close', () => { + console.log('Client MQTT received closed event'); + }); + + client.on('disconnect', () => { + console.log('Client MQTT received closed event'); + }); + + client.on('error', (err) => { + console.log('MQTT error: ',err); + }); + + client.on('end', () => { + console.log('Connection to MQTT broker ended'); + process.exit(); + }); + + + console.log('Connected to MQTT broker'); + await client.publish(topic, value, { retain: true }, (err) => { + if (thisObject.debug) { + console.log('Publishing to mqtt'); + } + if (err) { + console.error('Failed to publish message: ', err); + } else { + if (thisObject.debug) { + console.log('Message published with retain flag set to true'); + } + client.end(); + } + }); + client.end(); + } catch (e){ + // Do something about it! + console.log(e.stack); + process.exit(); + } + } } module.exports = SmappeeAPI; \ No newline at end of file