@ha4us/script.adapter
Version:
Scripting Adapter for the ha4us
319 lines (318 loc) • 13.6 kB
JavaScript
;
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