diff options
| -rw-r--r-- | .travis.yml | 6 | ||||
| -rw-r--r-- | Dockerfile-arm32v6 | 6 | ||||
| -rw-r--r-- | Dockerfile-arm32v7 | 6 | ||||
| -rw-r--r-- | README.md | 23 | ||||
| -rw-r--r-- | logger-config.json | 5 | ||||
| -rw-r--r-- | package-lock.json | 47 | ||||
| -rw-r--r-- | package.json | 3 | ||||
| -rw-r--r-- | services/data-logger.js | 41 |
8 files changed, 97 insertions, 40 deletions
diff --git a/.travis.yml b/.travis.yml index 60a044b..ed6ac12 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,13 +9,17 @@ install: script: - npm run dist + - mkdir release + - zip -j release/tplink-monitor-linux.zip dist/tplink-monitor-linux logger-config.json + - zip -j release/tplink-monitor-macos.zip dist/tplink-monitor-macos logger-config.json + - zip -j release/tplink-monitor-win.zip dist/tplink-monitor-win.exe logger-config.json deploy: provider: releases api_key: secure: "Un4XrV5DDJ9LPVYhILWcfiN60UDGQaqZ8WNHUqt9iAOcvXk/bxJZlYQpwgLv588g1om/SDcbNjljGWnruWi+QYrZmvtrkQ06oMn25FXvi4EbrxFiVZEjStpDAld2GkWT+WeftDo6sQvIBl0lEb0xZ2bRSkbPYCwW5ku2ITXTk0W+237GY7OjvyLpmvmMYfSeZpoyyfAQxNEZK6fYUMf/lA/L7nHEw2NdyfXZrUvtESRNkljVc0btaDASEntuMLpCsMZRPXsJIjTCCsW83r183jOv8ZD4S4q66eI64fdF6dq6G8l1pDAXCQFJRU1RAkX5NES+BD/8FfX5yyrOF7H3S5+t6866GWEshz+5hyvsK7TLkQsI0J9stXF470sB4DUF90dg5wZSrfSHNEYC+GQ3NbTSp7lmOtKo97YftVyq746MDSHfeDMiNyxUb+JzCqxQ3iB3/c0yLWlOk63uNMyDu2SEPEta4dIKqGC8J/1jvYM4LfNnRIhtirusQluETuu+LH+TfFtcq++RK5HRcGgpzTmexMYmhTebuDosoRCqonNZiCH/APMHbSf80ShobdNN1BO9Mz+geApf479W3UeUvD5CJXJtHlEm/NwPyq3kqmyjOX9DcqAxg652+MMe3fI6d0a2KAh0hlQObEVMjYD8hoLTA1BIqgJmEGl1/0s7fno=" file_glob: true - file: dist/* + file: release/* skip_cleanup: true on: tags: true
\ No newline at end of file diff --git a/Dockerfile-arm32v6 b/Dockerfile-arm32v6 new file mode 100644 index 0000000..300939a --- /dev/null +++ b/Dockerfile-arm32v6 @@ -0,0 +1,6 @@ +FROM hypriot/rpi-node:8.1-slim +WORKDIR /opt/tplink-monitor +COPY . . +RUN npm install +EXPOSE 3000 +CMD ["npm", "start"] diff --git a/Dockerfile-arm32v7 b/Dockerfile-arm32v7 new file mode 100644 index 0000000..acca9e8 --- /dev/null +++ b/Dockerfile-arm32v7 @@ -0,0 +1,6 @@ +FROM arm32v7/node:8.11-slim +WORKDIR /opt/tplink-monitor +COPY . . +RUN npm install +EXPOSE 3000 +CMD ["npm", "start"] @@ -23,7 +23,7 @@ Written in Node.js + Express, and fully responsive so works well on mobile devic You can use any of the following methods to get the project running: ### Packaged executable -The easiest way to run the project is to download one of the packaged executables from the [releases page](https://github.com/jamesbarnett91/tplink-monitor/releases). These are single file executables with all dependencies included. Just download the relevant file for your OS (Windows, Linux and MacOS available) and double click the file. Then go to `localhost:3000` in your browser to access the dashboard. +The easiest way to run the project is to download one of the packaged executables from the [releases page](https://github.com/jamesbarnett91/tplink-monitor/releases). These are zip files containing a single executable file and some config. Just download the relevant file for your OS (Windows, Linux and MacOS available), extract the zip somewhere and double click executable. Then go to `localhost:3000` in your browser to access the dashboard. ### Docker Alternatively, you can pull the `jbarnett/tplink-energy-monitor` image and run that. @@ -42,29 +42,38 @@ $ npm start ``` # Logging -By default this app will log the current power usage of each plug every minute, and store 24 hours worth of entries (removing the older entries as new ones are added). This log interval and max retention limit are configurable in the `logger-config.json` file in the root project directory. +By default this app will log the current power usage of each plug every minute, and store 24 hours worth of entries (removing the older entries as new ones are added) to files in the root project directory. This log interval, max retention limit and log directory are configurable in the `logger-config.json` file in the root project directory. ``` { + // Directory path specifying where log files should be stored. It will be created if it doesn't already exist. + "logDirPath": "path/to/logs", + // The number of seconds between each log entry - "logIntervalSeconds": 60, - + "logIntervalSeconds": 60, + // The maximum number of log entries to store "maxLogEntries": 1440 // 24hrs at 1 log/min } ``` + +You can also specify the path to a custom logger config file as a command line argument, and the application will load that config rather than the default one in the project root e.g. +``` +npm start /home/username/tplink-logger-config.json +``` + The logged data is shown on the 'Logged Usage' graph on the dashboard. -Logs are written in JSON format to the project root directory, with the filename `<plug-id>-log.json` e.g. `8FCA808B79-log.json`. Each file contains all the log entries for that plug, up to the maximum configured number, at which point it will remove the oldest entry when adding a new one. +Logs are written in JSON format, with the filename `<plug-id>-log.json` e.g. `8FCA808B79-log.json`. Each file contains all the log entries for that plug, up to the maximum configured number, at which point it will remove the oldest entry when adding a new one. If you are running the app from the Docker image and you want to change the logger config, you can mount your desired config file into `/opt/tplink-monitor/`. The logs can be accessed in the same way. -Each logfile is a JSON array of entries. Each entry contains a timestamp in unix/epoch format `ts`, and a power reading in watts `pw`. +Each logfile is a JSON array of entries. Each entry contains a timestamp in unix/epoch format `ts`, and a power reading in watts `pw`. If you want to analyse the log files in Excel or similar office tools you can convert the JSON file into csv format. This can be done numerous ways including online converters such as [konklone.io/json](https://konklone.io/json/), or if you are on a Unix system (or otherwise have access to `sed`) user [ballachango](https://github.com/jamesbarnett91/tplink-energy-monitor/issues/6#issuecomment-433663873) has posted this sed command `sed -e 's/},{/\\\n/g' -e 's/[]["tspw:}{\\]//g' <input.json> > log.csv` # Note Because the server needs access to your local network to scan for TP-Link device, you must run the server on the same network which your TP Link plugs are connected to. For the vast majority of people this shouldn't be an issue, and you can still use different network interfaces (i.e. plug(s) on WiFi and server on ethernet) as long as they all connect to the same network. -A note for Windows users: There seems to be an issue with the UDP broadcast the server performs to scan for devices which occurs when you also have VirtualBox installed on your Windows machine. I think this is because the response from the plug is routed to the VirtualBox Host-Only network adapter, rather than your primary network interface (for some reason). +A note for Windows users: There seems to be an issue with the UDP broadcast the server performs to scan for devices which occurs when you also have VirtualBox installed on your Windows machine. I think this is because the response from the plug is routed to the VirtualBox Host-Only network adapter, rather than your primary network interface (for some reason). If you hit this issue you can try disabling the VirtualBox adapter in `Control Panel > Network and Internet > Network Connections` and see if that solves the problem. diff --git a/logger-config.json b/logger-config.json index 4746469..d44ae5a 100644 --- a/logger-config.json +++ b/logger-config.json @@ -1,4 +1,5 @@ { - "logIntervalSeconds": 60, + "logDirPath": ".", + "logIntervalSeconds": 60, "maxLogEntries": 1440 -}
\ No newline at end of file +} diff --git a/package-lock.json b/package-lock.json index 08502b0..5f2fc66 100644 --- a/package-lock.json +++ b/package-lock.json @@ -129,8 +129,7 @@ "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, "basic-auth": { "version": "2.0.0", @@ -180,7 +179,6 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -287,8 +285,7 @@ "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, "content-disposition": { "version": "0.5.2", @@ -600,8 +597,7 @@ "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, "getpass": { "version": "0.1.7", @@ -624,7 +620,6 @@ "version": "7.1.2", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", - "dev": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -762,7 +757,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, "requires": { "once": "^1.3.0", "wrappy": "1" @@ -773,6 +767,11 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" }, + "interpret": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.2.0.tgz", + "integrity": "sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw==" + }, "ipaddr.js": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.6.0.tgz", @@ -950,7 +949,6 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, "requires": { "brace-expansion": "^1.1.7" } @@ -1043,7 +1041,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, "requires": { "wrappy": "1" } @@ -1093,14 +1090,12 @@ "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" }, "path-parse": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz", - "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=", - "dev": true + "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=" }, "path-to-regexp": { "version": "0.1.7", @@ -1293,6 +1288,14 @@ "util-deprecate": "~1.0.1" } }, + "rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", + "requires": { + "resolve": "^1.1.6" + } + }, "regenerator-runtime": { "version": "0.11.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", @@ -1354,7 +1357,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.4.0.tgz", "integrity": "sha512-aW7sVKPufyHqOmyyLzg/J+8606v5nevBgaliIlV7nUpVMsDnoBGV/cbSLNjZAg9q0Cfd/+easKVKQ8vOu8fn1Q==", - "dev": true, "requires": { "path-parse": "^1.0.5" } @@ -1415,6 +1417,16 @@ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" }, + "shelljs": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.3.tgz", + "integrity": "sha512-fc0BKlAWiLpwZljmOvAOTE/gXawtCoNrP5oaY7KIaQbbyHeQVg01pSEuEGvGh3HEdBU4baCD7wQBwADmM/7f7A==", + "requires": { + "glob": "^7.0.0", + "interpret": "^1.0.0", + "rechoir": "^0.6.2" + } + }, "simple-bufferstream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/simple-bufferstream/-/simple-bufferstream-1.0.0.tgz", @@ -1689,8 +1701,7 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "ws": { "version": "2.3.1", diff --git a/package.json b/package.json index 05ad6c6..43e0f7c 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,8 @@ "morgan": "~1.9.0", "tplink-smarthome-api": "0.22.0", "moment": "2.22.0", - "express-ws": "3.0.0" + "express-ws": "3.0.0", + "shelljs": "0.8.3" }, "devDependencies": { "pkg": "4.3.1" diff --git a/services/data-logger.js b/services/data-logger.js index 9c23de1..700bed0 100644 --- a/services/data-logger.js +++ b/services/data-logger.js @@ -1,6 +1,9 @@ const fs = require('fs'); +const path = require('path'); +const shell = require('shelljs'); const dataBroadcaster = require('./data-broadcaster'); +let logDirPath; let logIntervalMs; let maxLogEntries; @@ -8,16 +11,30 @@ loadLogConfig(); function loadLogConfig() { try { - let config = JSON.parse(fs.readFileSync('logger-config.json', 'utf8')); + // Use logger config file specified on command line, otherwise use default one. + if (process.argv.length > 2) { + loggerConfigPath = process.argv[2]; + } else { + loggerConfigPath = 'logger-config.json'; + } + console.log('Logger config file: "' + loggerConfigPath + '"') + + let config = JSON.parse(fs.readFileSync(loggerConfigPath, 'utf8')); + logDirPath = config.logDirPath; logIntervalMs = (config.logIntervalSeconds * 1000); maxLogEntries = config.maxLogEntries; } catch (err) { console.warn('Error reading logger config. Reverting to defaults.', err); + logDirPath = '.' // Current directory logIntervalMs = 60000 // 1 min maxLogEntries = 1440 // 24 hrs at 1/min - } + } + + // Create log directory path if it doesn't already exist. + console.log('Log directory path: "' + logDirPath + '"') + shell.mkdir('-p', logDirPath); } function startLogging(device) { @@ -26,15 +43,17 @@ function startLogging(device) { } function writeLog(filePath, log) { - fs.writeFile(filePath, JSON.stringify(log), { flag: 'w' }, (err) => { - if (err) { - console.warn('Error writing log for ' + device.alias + ' [' + device.deviceId + ']', err); - } - }); + try { + // Switched to sync write for now. TODO investigate issue from PR #19 + fs.writeFileSync(filePath, JSON.stringify(log), { flag: 'w' }); + } + catch (err) { + console.warn('Error writing log for ' + device.alias + ' [' + device.deviceId + ']', err); + } } function getLogEntries(filePath, callback) { - + fs.access(filePath, fs.constants.F_OK, (err) => { if(err) { // No log file, init empty one @@ -68,7 +87,7 @@ function log(device) { getLogEntries(filePath, (entries) => { entries.push(logEntry) - + // Remove old entries entries.splice(0, entries.length - maxLogEntries); @@ -80,7 +99,7 @@ function log(device) { } function getLogPath(deviceId) { - return deviceId + '-log.json'; + return path.join(logDirPath, deviceId + '-log.json'); } function getLogEntriesForDevice(deviceId, callback) { @@ -91,4 +110,4 @@ module.exports = { startLogging: startLogging, log: log, getLogEntriesForDevice: getLogEntriesForDevice -}
\ No newline at end of file +} |