UNPKG

@ha4us/script.adapter

Version:

Scripting Adapter for the ha4us

319 lines (318 loc) 13.6 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); const rxjs_1 = require("rxjs"); const operators_1 = require("rxjs/operators"); const adapter_1 = require("@ha4us/adapter"); const core_1 = require("@ha4us/core"); const ha4us_script_1 = require("./ha4us-script"); const us_scheduler_1 = require("us-scheduler"); const ADAPTER_OPTIONS = { // h,m,u,p,l,n name: 'scripts', path: __dirname + '/..', logo: 'scripts-logo.png', args: { dir: { alias: 'dir', demandOption: false, describe: 'directory of script files', type: 'string', }, lat: { alias: 'lat', default: 54.46, demandOption: false, describe: 'Latitude for sun calculation', type: 'number', }, long: { alias: 'long', default: 9.95, demandOption: false, describe: 'Longitutde for sun calculation', type: 'number', }, watch: { alias: 'w', demandOption: false, default: true, describe: 'watch for script file changes', type: 'boolean', }, }, imports: [ '$log', '$args', '$states', '$yaml', '$objects', // '$event', '$media', ], }; function Adapter($log, $args, $states, $yaml, $objects, $media // $event: EventService ) { const scripts = new Map(); const scriptEvent$ = new rxjs_1.Subject(); let sub; function createScriptObjects(script) { $log.debug(`creating objects for ${script.name}`); return $objects .create({ state: { role: 'Value/Script/State', type: core_1.Ha4usObjectType.String, can: { trigger: true }, }, log: { role: 'Value/Script/Log', type: core_1.Ha4usObjectType.Object, can: { trigger: true }, }, }, { root: core_1.MqttUtil.join('$', script.topic) }) .toPromise() .then(res => { $log.debug(`Topics ${res.objects.map(obj => obj.topic).join(',')} installed`); return script; }); } function installScript(aScript) { return __awaiter(this, void 0, void 0, function* () { if (scripts.has(aScript.name)) { throw new core_1.Ha4usError(409, `script "${aScript.name} "already exists`); } $log.debug(`creating script ${aScript.name}`); scripts.set(aScript.name, aScript); scriptEvent$.next(aScript); aScript.log$ .pipe(operators_1.mergeMap(event => $states.status(core_1.MqttUtil.join(aScript.name, 'log'), event, true))) .subscribe(msg => { $log.debug(`Message send`, msg.topic, msg.val); }, e => $log.error('error in script event listener', e), () => $log.info('Script event listener completed')); aScript.status$ .pipe(operators_1.mergeMap(status => $states.status(core_1.MqttUtil.join(aScript.name, 'state'), status, true))) .subscribe(msg => { $log.debug(`Status update`, msg.topic, msg.val); }, e => $log.error('error in script event listener', e), () => $log.info('Script event listener completed')); return aScript .init() .then(script => createScriptObjects(aScript)) .then(script => script.compile()) .then(script => { if (script && script.autostart) { return script.start(); } else { return script; } }) .catch(e => { $log.error(`${aScript.name} errored`, e); return undefined; }); }); } function $onInit() { return __awaiter(this, void 0, void 0, function* () { yield $objects.connect(); yield $media.connect(); const res = yield $objects .create([ { role: core_1.Ha4usRole.ScriptAdapter, }, { sun: [ { role: 'Device/Sun', }, { azimuth: { role: 'Value/SunAzimuth', type: core_1.Ha4usObjectType.Number, template: '${val}°', can: { trigger: true }, }, altitude: { role: 'Value/SunAltitude', template: '${val}°', type: core_1.Ha4usObjectType.Number, can: { trigger: true }, }, time: { role: 'Value/SunTime', type: core_1.Ha4usObjectType.String, can: { trigger: true }, }, }, ], }, ], { mode: 'update', root: '$' }) .toPromise(); $log.info(`${res.updated} Objects updated. ${res.inserted} created`); // first loading script from database // $objects .observe(core_1.MqttUtil.join($args.name, '+')) .pipe(operators_1.filter(script => script.role === 'Script'), operators_1.mergeMap(scriptObject => installScript(new ha4us_script_1.Ha4usScript(scriptObject, { $args, $log, $yaml, $states, $objects, $media, }))), operators_1.filter(script => !!script)) .subscribe(script => { $log.info('Loaded script from database', script.name); }); // listen for object changes (insert, update, delete) sub = $states .observe('/$object/+') .pipe(operators_1.filter(event => event.val.object.role === core_1.Ha4usRole.Script), operators_1.switchMap(event => { const action = event.val.action; const name = event.val.object.topic; const script = scripts.get(event.val.object.topic); return rxjs_1.of([action, name, script]).pipe(operators_1.mergeMap(data => { $log.debug('script update arrived', event.val.action); switch (event.val.action) { case 'update': if (!script) { throw new core_1.Ha4usError(409, `script ${event.val.object.topic} does not exist`); } script.source = event.val.object.native.source; script.autostart = event.val.object.native.autostart || typeof event.val.object.native.autostart === 'undefined'; $log.debug('Setting new source with autostart', event.val.object.native.autostart); return script.compile().then(() => script.restart()); case 'insert': if (script) { throw new core_1.Ha4usError(404, `script ${event.val.object.topic} already exists`); } return installScript(new ha4us_script_1.Ha4usScript(event.val.object, { $args, $log, $yaml, $states, $objects, $media, })); case 'delete': if (script) { return script.stop().then(stoppedScript => { scripts.delete(stoppedScript.name); return stoppedScript; }); } else { throw new core_1.Ha4usError(404, `script ${event.val.object.topic} already exists`); } default: throw new core_1.Ha4usError(405, `method ${event.val.action} not known`); } }), operators_1.catchError(e => { $log.error(`action errored: ${e.message}`); return rxjs_1.NEVER; })); })) .subscribe(script => { $log.info(`action for script ${script.name} success`); }); sub.add($states .observe('/$set/+/state') .pipe(operators_1.switchMap(msg => { const [scriptName] = msg.match.params; $log.debug(`Setting state of ${scriptName} to ${msg.val}`); const storedScript = scripts.get(core_1.MqttUtil.join($args.name, scriptName)); return rxjs_1.of(storedScript).pipe(operators_1.mergeMap(aScript => { if (aScript) { if (msg.val === true) { return aScript.compile().then(() => aScript.start()); } else { return aScript.stop(); } } else { throw new core_1.Ha4usError(404, `script ${scriptName} does not exists`); } }), operators_1.catchError(e => { $log.error(`Error setting state of ${scriptName} to ${msg.val} because ${e.message}`); return rxjs_1.NEVER; })); })) .subscribe((script) => { $log.info(`script ${script.name} is ${script.status}`); }, e => { $log.error(`BUMMER`, e); }, () => { $log.error('Script STATE Listener completed'); })); if (!$args.scriptsDir) { throw new Error('No script directory given'); } const st = new us_scheduler_1.SunTimes({ latitude: $args.lat, longitude: $args.long, }); sub.add(rxjs_1.timer(0, 60000) // means all 5 minutes .pipe(operators_1.map(() => st.sun), operators_1.map(position => { $log.debug(`Current sunposition ${position.altitude}° - ${position.azimuth}°`); $states.status('$sun/azimuth', position.azimuth, true); $states.status('$sun/altitude', position.altitude, true); return position.altitude; }), operators_1.map(altitude => { /* var times = SunCalc.times = [ [-0.833, 'sunrise', 'sunset' ], [ -0.3, 'sunriseEnd', 'sunsetStart' ], [ -6, 'dawn', 'dusk' ], [ -12, 'nauticalDawn', 'nauticalDusk'], [ -18, 'nightEnd', 'night' ], [ 6, 'goldenHourEnd', 'goldenHour' ]*/ if (altitude > -0.3) { return 'day'; } else if (altitude > -6) { return 'dawn'; } else { return 'night'; } }), operators_1.distinctUntilChanged()) .subscribe(position => { $log.debug('Current suntime', position); $states.status('$sun/time', position, true); })); $states.connected = 2; return true; }); } function $onDestroy() { return __awaiter(this, void 0, void 0, function* () { $log.info('Destroying scripts', scripts.keys()); yield rxjs_1.from(scripts.values()) .pipe(operators_1.mergeMap(script => script.stop()), operators_1.map(() => sub.unsubscribe()), operators_1.toArray(), operators_1.delay(2000) // waiting for 2 seconds to let all state emissions finish ) .toPromise(); }); } return { $onInit, $onDestroy, }; } adapter_1.ha4us(ADAPTER_OPTIONS, Adapter).catch(e => { console.error('Error occurred', e); process.exit(1); }); //# sourceMappingURL=index.js.map