-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
4f04483
commit 1a019e2
Showing
3 changed files
with
209 additions
and
135 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
root = true | ||
|
||
[*] | ||
indent_style = tab | ||
indent_size = 4 | ||
tab_width = 4 | ||
end_of_line = lf | ||
charset = utf-8 | ||
trim_trailing_whitespace = true | ||
insert_final_newline = true | ||
|
||
[{*.json,*.json.example,*.gyp,*.yml,*.yaml}] | ||
indent_style = space | ||
indent_size = 2 | ||
|
||
[{*.py,*.asm}] | ||
indent_style = space | ||
|
||
[*.py] | ||
indent_size = 4 | ||
|
||
[*.asm] | ||
indent_size = 8 | ||
|
||
[*.md] | ||
trim_trailing_whitespace = false | ||
|
||
# Ideal settings - some plugins might support these. | ||
[*.js] | ||
quote_type = single | ||
|
||
[{*.c,*.cc,*.h,*.hh,*.cpp,*.hpp,*.m,*.mm,*.mpp,*.js,*.java,*.go,*.rs,*.php,*.ng,*.jsx,*.ts,*.d,*.cs,*.swift}] | ||
curly_bracket_next_line = false | ||
spaces_around_operators = true | ||
spaces_around_brackets = outside | ||
# close enough to 1TB | ||
indent_brace_style = K&R |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,170 +1,194 @@ | ||
#!/usr/local/bin/node | ||
const ms = require('ms') | ||
const os = require('os') | ||
const ini = require('ini') | ||
const args = require('args') | ||
const fs = require('fs-extra') | ||
const { join } = require('path') | ||
const fetch = require('node-fetch') | ||
const sleep = require('then-sleep') | ||
const onWake = require('wake-event') | ||
const sio = require('socket.io-client') | ||
const snakeCase = require('snake-case') | ||
const { parse, resolve } = require('url') | ||
const debug = require('debug')('nightscout-ps1') | ||
|
||
const { name } = require('../package.json') | ||
const ms = require('ms'); | ||
const os = require('os'); | ||
const ini = require('ini'); | ||
const args = require('args'); | ||
const {join} = require('path'); | ||
const fetch = require('node-fetch'); | ||
const sleep = require('then-sleep'); | ||
const onWake = require('wake-event'); | ||
const sio = require('socket.io-client'); | ||
const snakeCase = require('snake-case'); | ||
const {writeFile} = require('fs-extra'); | ||
const {parse, resolve} = require('url'); | ||
const debug = require('debug')('nightscout-ps1'); | ||
|
||
const {name} = require('../package.json'); | ||
|
||
const toSnakeCase = _o => { | ||
if (!_o) return _o | ||
const o = {} | ||
for (const k of Object.keys(_o)) { | ||
const v = _o[k] | ||
o[snakeCase(k)] = typeof v === 'object' ? toSnakeCase(v) : v | ||
} | ||
return o | ||
if (!_o) { | ||
return _o; | ||
} | ||
const o = {}; | ||
for (const k of Object.keys(_o)) { | ||
const v = _o[k]; | ||
o[snakeCase(k)] = typeof v === 'object' ? toSnakeCase(v) : v; | ||
} | ||
return o; | ||
}; | ||
|
||
function *flatten(input, prefix = '') { | ||
for (const key of Object.keys(input)) { | ||
let value = input[key]; | ||
const name = prefix + snakeCase(key); | ||
if (Array.isArray(value)) { | ||
value = value.join(','); | ||
} else if (typeof value === 'object') { | ||
yield *flatten(value, `${name}_`); | ||
value = null; | ||
} else { | ||
value = String(value); | ||
} | ||
if (value !== null) { | ||
yield [name, value]; | ||
} | ||
} | ||
} | ||
|
||
args | ||
.option('nightscout', 'URL of your Nightscout deployment', '') | ||
.option( | ||
'cache-file', | ||
'Path to write the latest reading file', | ||
join(os.homedir(), '.nightscout-latest-entry') | ||
) | ||
|
||
const flags = args.parse(process.argv, { name }) | ||
.option('nightscout', 'URL of your Nightscout deployment', '') | ||
.option( | ||
'cache-file', | ||
'Path to write the latest reading file', | ||
join(os.homedir(), '.nightscout-latest-entry') | ||
); | ||
|
||
const flags = args.parse(process.argv, {name}); | ||
while (args.sub.length > 0) { | ||
const command = args.sub.shift() | ||
if (command === 'daemon') { | ||
// ignore… | ||
} else { | ||
// assume it's a Nightscout URL | ||
flags.nightscout = command | ||
} | ||
const command = args.sub.shift(); | ||
if (command === 'daemon') { | ||
// ignore… | ||
} else { | ||
// assume it's a Nightscout URL | ||
flags.nightscout = command; | ||
} | ||
} | ||
if (!flags.nightscout) { | ||
console.error( | ||
'FATAL: The `--nightscout` parameter must point to your Nightscout URL' | ||
) | ||
process.exit(1) | ||
console.error( | ||
'FATAL: The `--nightscout` parameter must point to your Nightscout URL' | ||
); | ||
process.exit(1); | ||
} | ||
|
||
const resolvePrefix = async _url => { | ||
let url = _url | ||
if (!parse(url).protocol) { | ||
url = `http://${url}` | ||
} | ||
const res = await fetch(url) | ||
debug('Resolved URL %o to %o', _url, res.url) | ||
return res.url | ||
} | ||
|
||
const resolvedNightscout = resolvePrefix(flags.nightscout) | ||
let socket | ||
let statusPromise | ||
let entries = new Map() | ||
exit(pollStatus()) | ||
exit(main()) | ||
let url = _url; | ||
if (!parse(url).protocol) { | ||
url = `http://${url}`; | ||
} | ||
const res = await fetch(url); | ||
debug('Resolved URL %o to %o', _url, res.url); | ||
return res.url; | ||
}; | ||
|
||
const resolvedNightscout = resolvePrefix(flags.nightscout); | ||
let socket; | ||
let statusPromise; | ||
const entries = new Map(); | ||
exit(pollStatus()); | ||
exit(main()); | ||
|
||
// reset Socket.io connection after the computer wakes from sleep | ||
onWake(() => { | ||
debug('Wake event') | ||
exit(main()) | ||
}) | ||
debug('Wake event'); | ||
exit(main()); | ||
}); | ||
|
||
function exit(promise) { | ||
promise.catch(err => { | ||
console.error(err) | ||
process.exit(1) | ||
}) | ||
promise.catch(err => { | ||
console.error(err); | ||
process.exit(1); | ||
}); | ||
} | ||
|
||
function emit(e, ...args) { | ||
return new Promise((resolve, reject) => e.emit(...args, resolve)) | ||
return new Promise((resolve, reject) => e.emit(...args, resolve)); | ||
} | ||
|
||
function once(e, name) { | ||
return new Promise((resolve, reject) => e.once(name, resolve)) | ||
return new Promise((resolve, reject) => e.once(name, resolve)); | ||
} | ||
|
||
async function updateStatus() { | ||
const nightscout = await resolvedNightscout | ||
const endpoint = resolve(nightscout, '/api/v1/status.json') | ||
debug('Updating status %o', endpoint) | ||
const res = await fetch(endpoint) | ||
const body = await res.json() | ||
debug('Status updated') | ||
return body | ||
const nightscout = await resolvedNightscout; | ||
const endpoint = resolve(nightscout, '/api/v1/status.json'); | ||
debug('Updating status %o', endpoint); | ||
const res = await fetch(endpoint); | ||
const body = await res.json(); | ||
debug('Status updated'); | ||
return body; | ||
} | ||
|
||
async function pollStatus() { | ||
statusPromise = updateStatus() | ||
await statusPromise | ||
// let that response be cached for a little while | ||
await sleep(ms('5m')) | ||
return pollStatus() | ||
statusPromise = updateStatus(); | ||
await statusPromise; | ||
// let that response be cached for a little while | ||
await sleep(ms('5m')); | ||
return pollStatus(); | ||
} | ||
|
||
async function onDataUpdate(event) { | ||
debug('Got event %o', event) | ||
const { sgvs } = event | ||
if (!sgvs) return | ||
for (const entry of sgvs) { | ||
entries.set(entry.mills, entry) | ||
} | ||
|
||
const sorted = Array.from(entries.keys()).sort() | ||
|
||
// Purge old entries, keep up to two | ||
const count = Math.max(0, sorted.length - 2) | ||
debug('Purging %o entries', count); | ||
for (let i = 0; i < count; i++) { | ||
const key = sorted.shift() | ||
entries.delete(key) | ||
} | ||
|
||
const latestEntry = entries.get(sorted.pop()) | ||
debug('Latest entry %o', latestEntry) | ||
|
||
const previousEntry = entries.get(sorted.pop() || latestEntry.mills) | ||
debug('Previous entry %o', previousEntry) | ||
|
||
const { settings } = await Promise.resolve(statusPromise) | ||
|
||
const data = { | ||
previousEntry, | ||
latestEntry, | ||
settings | ||
} | ||
|
||
await fs.writeFile(flags.cacheFile, ini.stringify(toSnakeCase(data))) | ||
debug('Wrote %o', flags.cacheFile) | ||
debug('Got event %o', event); | ||
const {sgvs} = event; | ||
if (!sgvs) { | ||
return; | ||
} | ||
for (const entry of sgvs) { | ||
entries.set(entry.mills, entry); | ||
} | ||
|
||
const sorted = Array.from(entries.keys()).sort(); | ||
|
||
// Purge old entries, keep up to two | ||
const count = Math.max(0, sorted.length - 2); | ||
debug('Purging %o entries', count); | ||
for (let i = 0; i < count; i++) { | ||
const key = sorted.shift(); | ||
entries.delete(key); | ||
} | ||
|
||
const latestEntry = entries.get(sorted.pop()); | ||
debug('Latest entry %o', latestEntry); | ||
|
||
const previousEntry = entries.get(sorted.pop() || latestEntry.mills); | ||
debug('Previous entry %o', previousEntry); | ||
|
||
const {settings} = await Promise.resolve(statusPromise); | ||
|
||
const data = { | ||
previousEntry, | ||
latestEntry, | ||
settings | ||
}; | ||
|
||
const envs = Array.from(flatten(data)).map(([key, value]) => `local ${key}=${JSON.stringify(value)}\n`) | ||
.join(''); | ||
await writeFile(flags.cacheFile, envs); | ||
debug('Wrote %o', flags.cacheFile); | ||
} | ||
|
||
async function main() { | ||
const nightscout = await resolvedNightscout | ||
|
||
if (socket) { | ||
debug('socket.close()') | ||
socket.close() | ||
} | ||
|
||
debug('Creating socket.io connection %o', nightscout) | ||
socket = sio.connect(nightscout) | ||
socket.on('dataUpdate', e => exit(onDataUpdate(e))) | ||
|
||
const auth = await emit(socket, 'authorize', { | ||
client: 'web', | ||
secret: null, | ||
token: null, | ||
history: 1 | ||
}) | ||
debug('Auth results %o', auth) | ||
debug('Waiting for "dataUpdate" events') | ||
|
||
await once(socket, 'close') | ||
socket = null | ||
debug('Socket closed. Establishing new connection') | ||
return main() | ||
const nightscout = await resolvedNightscout; | ||
|
||
if (socket) { | ||
debug('socket.close()'); | ||
socket.close(); | ||
} | ||
|
||
debug('Creating socket.io connection %o', nightscout); | ||
socket = sio.connect(nightscout); | ||
socket.on('dataUpdate', e => exit(onDataUpdate(e))); | ||
|
||
const auth = await emit(socket, 'authorize', { | ||
client: 'web', | ||
secret: null, | ||
token: null, | ||
history: 1 | ||
}); | ||
debug('Auth results %o', auth); | ||
debug('Waiting for "dataUpdate" events'); | ||
|
||
await once(socket, 'close'); | ||
socket = null; | ||
debug('Socket closed. Establishing new connection'); | ||
return main(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters