hoffmation-base 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.js +27 -0
- package/.prettierrc.js +9 -0
- package/LICENSE +21 -0
- package/README.md +1 -0
- package/index.js +1 -0
- package/models/connectionCallbacks.ts +13 -0
- package/models/daytime.ts +3 -0
- package/models/deviceConfig.ts +8 -0
- package/models/dimmerSettings.ts +5 -0
- package/models/lampSettings.ts +5 -0
- package/models/ledSettings.ts +19 -0
- package/models/logLevel.ts +9 -0
- package/models/persistence/BasicRoomInfo.ts +3 -0
- package/models/persistence/DailyMovementCount.ts +3 -0
- package/models/persistence/RoomDetailInfo.ts +4 -0
- package/models/persistence/temperaturDataPoint.ts +12 -0
- package/models/persistence/todaysCount.ts +3 -0
- package/models/rooms/RoomBase.ts +357 -0
- package/models/rooms/RoomSettings/RoomSettings.ts +159 -0
- package/models/rooms/RoomSettings/hmIPRoomSettings.ts +53 -0
- package/models/rooms/RoomSettings/iRoomDefaultSettings.ts +17 -0
- package/models/rooms/RoomSettings/readme.md +18 -0
- package/models/rooms/RoomSettings/zigbeeRoomSettings.ts +51 -0
- package/models/rooms/iRoomImportEnforcer.ts +3 -0
- package/models/rooms/readme.md +11 -0
- package/models/temperaturSettings.ts +22 -0
- package/models/timeCallback.ts +90 -0
- package/package.json +57 -0
- package/server/config/config-readme.md +19 -0
- package/server/config/iConfig.ts +53 -0
- package/server/devices/DeviceInfo.ts +66 -0
- package/server/devices/Griffe.ts +31 -0
- package/server/devices/Heizgruppen.ts +91 -0
- package/server/devices/Rollos.ts +48 -0
- package/server/devices/deviceUpdater.ts +72 -0
- package/server/devices/devices.ts +189 -0
- package/server/devices/groups/fensterGroup.ts +175 -0
- package/server/devices/groups/heatGroup.ts +32 -0
- package/server/devices/groups/lampenGroup.ts +88 -0
- package/server/devices/groups/praesenzGroup.ts +182 -0
- package/server/devices/groups/smokeGroup.ts +16 -0
- package/server/devices/groups/sonosGroup.ts +33 -0
- package/server/devices/groups/tasterGroup.ts +48 -0
- package/server/devices/groups/waterGroup.ts +16 -0
- package/server/devices/hmIPDevices/Fenster.ts +114 -0
- package/server/devices/hmIPDevices/FensterPosition.ts +5 -0
- package/server/devices/hmIPDevices/TuerPosition.ts +4 -0
- package/server/devices/hmIPDevices/hmIpBewegung.ts +126 -0
- package/server/devices/hmIPDevices/hmIpDevice.ts +90 -0
- package/server/devices/hmIPDevices/hmIpDeviceType.ts +14 -0
- package/server/devices/hmIPDevices/hmIpGriff.ts +143 -0
- package/server/devices/hmIPDevices/hmIpHeizgruppe.ts +172 -0
- package/server/devices/hmIPDevices/hmIpHeizung.ts +69 -0
- package/server/devices/hmIPDevices/hmIpLampe.ts +119 -0
- package/server/devices/hmIPDevices/hmIpPraezenz.ts +99 -0
- package/server/devices/hmIPDevices/hmIpRoll.ts +133 -0
- package/server/devices/hmIPDevices/hmIpTaste.ts +72 -0
- package/server/devices/hmIPDevices/hmIpTaster.ts +73 -0
- package/server/devices/hmIPDevices/hmIpTherm.ts +19 -0
- package/server/devices/hmIPDevices/hmIpTuer.ts +115 -0
- package/server/devices/hmIPDevices/hmIpWippe.ts +55 -0
- package/server/devices/iDeviceUpdater.ts +4 -0
- package/server/devices/iIoBrokerDevice.ts +44 -0
- package/server/devices/wledDevice.ts +124 -0
- package/server/devices/zigbee/ZigbeeActuator.ts +113 -0
- package/server/devices/zigbee/zigbeeAquaraVibra.ts +171 -0
- package/server/devices/zigbee/zigbeeAquaraWater.ts +94 -0
- package/server/devices/zigbee/zigbeeBlitzShp.ts +77 -0
- package/server/devices/zigbee/zigbeeDevice.ts +115 -0
- package/server/devices/zigbee/zigbeeDeviceType.ts +13 -0
- package/server/devices/zigbee/zigbeeHeimanSmoke.ts +99 -0
- package/server/devices/zigbee/zigbeeIkeaSteckdose.ts +31 -0
- package/server/devices/zigbee/zigbeeIlluActuator.ts +37 -0
- package/server/devices/zigbee/zigbeeIlluDimmer.ts +165 -0
- package/server/devices/zigbee/zigbeeIlluLampe.ts +33 -0
- package/server/devices/zigbee/zigbeeIlluLedRGBCCT.ts +137 -0
- package/server/ioBroker/connection.ts +1655 -0
- package/server/ioBroker/ioBroker.main.ts +99 -0
- package/server/ioBroker/socketIOAuthInfo.ts +5 -0
- package/server/ioBroker/socketIOConnectOptions.ts +6 -0
- package/server/ioBroker/socketIOLogging.ts +29 -0
- package/server/ioBroker/socketIOVisCommand.ts +11 -0
- package/server/services/HTTPSOptions.ts +14 -0
- package/server/services/Sonos/mp3-server.ts +75 -0
- package/server/services/Sonos/polly-service.ts +100 -0
- package/server/services/Sonos/sonos-service.ts +199 -0
- package/server/services/Telegram/telegram-Commands.ts +215 -0
- package/server/services/Telegram/telegram-service.ts +171 -0
- package/server/services/Telegram/telegramMessageCalback.ts +11 -0
- package/server/services/calendar/m/303/274ll-service.ts +224 -0
- package/server/services/dbo/persist.ts +125 -0
- package/server/services/https-service.ts +71 -0
- package/server/services/log-service.ts +69 -0
- package/server/services/news-service.ts +81 -0
- package/server/services/settings-service.ts +23 -0
- package/server/services/time-callback-service.ts +223 -0
- package/server/services/utils/ringstorage.ts +24 -0
- package/server/services/utils/utils.ts +52 -0
- package/server/services/weather/weather-alert.ts +7 -0
- package/server/services/weather/weather-current.ts +26 -0
- package/server/services/weather/weather-daily.ts +22 -0
- package/server/services/weather/weather-feelsLike.ts +6 -0
- package/server/services/weather/weather-hourly.ts +17 -0
- package/server/services/weather/weather-item.ts +6 -0
- package/server/services/weather/weather-minutes.ts +4 -0
- package/server/services/weather/weather-service.ts +277 -0
- package/server/services/weather/weather-temp.ts +8 -0
- package/tsconfig.json +59 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { Devices } from '../devices/devices';
|
|
2
|
+
import { DeviceUpdater } from '../devices/deviceUpdater';
|
|
3
|
+
import { IDeviceUpdater } from '../devices/iDeviceUpdater';
|
|
4
|
+
import { ServerLogService } from '../services/log-service';
|
|
5
|
+
import { TimeCallbackService } from '../services/time-callback-service';
|
|
6
|
+
import { IOBrokerConnection } from './connection';
|
|
7
|
+
import { ConnectionCallbacks } from '/models/connectionCallbacks';
|
|
8
|
+
import { LogLevel } from '/models/logLevel';
|
|
9
|
+
import { RoomBase } from '/models/rooms/RoomBase';
|
|
10
|
+
import { Utils } from '/server/services/utils/utils';
|
|
11
|
+
import { SettingsService } from "/server/services/settings-service";
|
|
12
|
+
|
|
13
|
+
export class ioBrokerMain {
|
|
14
|
+
private static roomConstructors: { [roomName: string]: { new (): RoomBase } } = {};
|
|
15
|
+
private servConn: IOBrokerConnection;
|
|
16
|
+
private deviceUpdater: IDeviceUpdater;
|
|
17
|
+
private states: Record<string, ioBroker.State> = {};
|
|
18
|
+
private connectionCallbacks: ConnectionCallbacks;
|
|
19
|
+
|
|
20
|
+
public static addRoomConstructor(roomName: string, constr: { new (): RoomBase }): void {
|
|
21
|
+
if (ioBrokerMain.roomConstructors[roomName] !== undefined) {
|
|
22
|
+
ServerLogService.writeLog(LogLevel.Error, `Konstruktor für Raum mit Namen "${roomName}" bereits hinzugefügt`);
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
ioBrokerMain.roomConstructors[roomName] = constr;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
public constructor(pDeviceUpdater: DeviceUpdater) {
|
|
29
|
+
this.deviceUpdater = pDeviceUpdater;
|
|
30
|
+
this.connectionCallbacks = new ConnectionCallbacks();
|
|
31
|
+
this.initConnCallbacks();
|
|
32
|
+
|
|
33
|
+
this.servConn = new IOBrokerConnection(
|
|
34
|
+
{
|
|
35
|
+
name: '', // optional - default 'vis.0'
|
|
36
|
+
connLink: SettingsService.settings.ioBrokerUrl, // optional URL of the socket.io adapter
|
|
37
|
+
socketSession: '', // optional - used by authentication
|
|
38
|
+
},
|
|
39
|
+
this.connectionCallbacks,
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
Devices.addIoConnection(this.servConn);
|
|
43
|
+
ioBrokerMain.initRooms();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
private static initRooms(): void {
|
|
47
|
+
for (const key in ioBrokerMain.roomConstructors) {
|
|
48
|
+
new ioBrokerMain.roomConstructors[key]();
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
private initConnCallbacks(): void {
|
|
53
|
+
this.connectionCallbacks.onObjectChange = (pId: string, pObj: ioBroker.Object) => {
|
|
54
|
+
Utils.guardedNewThread(() => {
|
|
55
|
+
this.deviceUpdater.updateObject(pId, pObj);
|
|
56
|
+
}, this);
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
this.connectionCallbacks.onConnChange = (isConnected: boolean) => {
|
|
60
|
+
if (!isConnected) {
|
|
61
|
+
console.log('disconnected');
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
console.log('connected');
|
|
66
|
+
this.servConn.getStates(null, (err, _states) => {
|
|
67
|
+
if (_states === undefined) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
ServerLogService.writeLog(LogLevel.Debug, `Im initialen GetStates Callback`);
|
|
71
|
+
|
|
72
|
+
let count = 0;
|
|
73
|
+
for (const id in _states) {
|
|
74
|
+
this.deviceUpdater.updateState(id, _states[id], true);
|
|
75
|
+
count++;
|
|
76
|
+
}
|
|
77
|
+
console.log('Received ' + count + ' states.');
|
|
78
|
+
this.states = _states;
|
|
79
|
+
TimeCallbackService.performCheck();
|
|
80
|
+
TimeCallbackService.performCheck();
|
|
81
|
+
});
|
|
82
|
+
};
|
|
83
|
+
this.connectionCallbacks.onRefresh = () => {
|
|
84
|
+
//
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
this.connectionCallbacks.onUpdate = (id: string, state: ioBroker.State) => {
|
|
88
|
+
Utils.guardedNewThread(() => {
|
|
89
|
+
// console.log('NEW VALUE of ' + id + ': ' + JSON.stringify(state));
|
|
90
|
+
this.states[id] = state;
|
|
91
|
+
this.deviceUpdater.updateState(id, state);
|
|
92
|
+
}, this);
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
this.connectionCallbacks.onError = (err: any) => {
|
|
96
|
+
console.log(`Cannot execute ${err.command} for ${err.arg}, because of insufficient permissions`);
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export class SocketIoLogging {
|
|
2
|
+
public static LogLevel = 5;
|
|
3
|
+
public static writeLog(pLevel: SocketIoLogLevel, pMessage: string): void {
|
|
4
|
+
if (pLevel > SocketIoLogging.LogLevel) {
|
|
5
|
+
return;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
switch (pLevel) {
|
|
9
|
+
case SocketIoLogLevel.Error:
|
|
10
|
+
console.error(pMessage);
|
|
11
|
+
break;
|
|
12
|
+
case SocketIoLogLevel.Error:
|
|
13
|
+
console.warn(pMessage);
|
|
14
|
+
break;
|
|
15
|
+
default:
|
|
16
|
+
console.log(pMessage);
|
|
17
|
+
break;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export enum SocketIoLogLevel {
|
|
23
|
+
Error = 1,
|
|
24
|
+
Warn = 2,
|
|
25
|
+
Info = 3,
|
|
26
|
+
Debug = 4,
|
|
27
|
+
Trace = 5,
|
|
28
|
+
DeepTrace = 6,
|
|
29
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { ServerLogService } from './log-service';
|
|
2
|
+
import { LogLevel } from '../../models/logLevel';
|
|
3
|
+
|
|
4
|
+
export class HTTPSOptions {
|
|
5
|
+
constructor(
|
|
6
|
+
public hostname: string,
|
|
7
|
+
public path: string,
|
|
8
|
+
public headers: { [id: string]: string } = {},
|
|
9
|
+
public method: string = 'POST',
|
|
10
|
+
public port: number = 443,
|
|
11
|
+
) {
|
|
12
|
+
ServerLogService.writeLog(LogLevel.Debug, `${method} Request at '${hostname}' for '${path}'`);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import * as http from 'http';
|
|
2
|
+
import * as url from 'url';
|
|
3
|
+
import * as fs from 'fs';
|
|
4
|
+
import { ServerLogService } from '../log-service';
|
|
5
|
+
import { LogLevel } from '../../../models/logLevel';
|
|
6
|
+
import { iMp3Settings } from '../../config/iConfig';
|
|
7
|
+
|
|
8
|
+
export class MP3Server {
|
|
9
|
+
public static active: boolean = false;
|
|
10
|
+
public count: number = 0;
|
|
11
|
+
private mp3Path: string = '';
|
|
12
|
+
|
|
13
|
+
public constructor(settings: iMp3Settings) {
|
|
14
|
+
if (!settings) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
this.mp3Path = settings.path;
|
|
19
|
+
MP3Server.active = true;
|
|
20
|
+
http
|
|
21
|
+
.createServer((req, response) => {
|
|
22
|
+
if (req.url === undefined) {
|
|
23
|
+
response.writeHead(500);
|
|
24
|
+
response.end('ungültige Anfrage', 'utf-8');
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const q = url.parse(req.url, true).query;
|
|
28
|
+
|
|
29
|
+
const fName = q.fname;
|
|
30
|
+
|
|
31
|
+
if (!fName || fName.indexOf('.') >= 0) {
|
|
32
|
+
response.writeHead(500);
|
|
33
|
+
response.end('ungültiger Dateiname', 'utf-8');
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
ServerLogService.writeLog(LogLevel.DeepTrace, `Anfrage für ${fName}`);
|
|
38
|
+
const fPath: string = this.mp3Path + fName + '.mp3';
|
|
39
|
+
try {
|
|
40
|
+
if (!fs.existsSync(fPath)) {
|
|
41
|
+
ServerLogService.writeLog(LogLevel.Error, `Die angefragte Datei existiert nicht ${fName}`);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
} catch (err) {
|
|
45
|
+
console.error(err);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const filestream = fs.createReadStream(fPath);
|
|
49
|
+
response.writeHead(200, { 'Content-Type': 'audio/mpeg3' });
|
|
50
|
+
filestream.pipe(response, { end: true });
|
|
51
|
+
|
|
52
|
+
/*
|
|
53
|
+
fs.readFile(fPath, (error, data) => {
|
|
54
|
+
if (error) {
|
|
55
|
+
if(error.code == 'ENOENT'){
|
|
56
|
+
fs.readFile('./404.html', (err, cont) => {
|
|
57
|
+
response.writeHead(200, { 'Content-Type': "audio/mpeg3" });
|
|
58
|
+
response.end(cont, 'utf-8');
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
response.writeHead(500);
|
|
63
|
+
response.end('Sorry, check with the site admin for error: '+error.code+' ..\n');
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
response.writeHead(200, { 'Content-Type': "audio/mpeg3" });
|
|
68
|
+
response.end(data, 'utf-8');
|
|
69
|
+
}
|
|
70
|
+
})
|
|
71
|
+
*/
|
|
72
|
+
})
|
|
73
|
+
.listen(8081);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import AWS from 'aws-sdk';
|
|
2
|
+
import { ServerLogService } from '../log-service';
|
|
3
|
+
import { LogLevel } from '../../../models/logLevel';
|
|
4
|
+
import * as fs from 'fs';
|
|
5
|
+
import getMP3Duration from 'get-mp3-duration';
|
|
6
|
+
import crypto from 'crypto';
|
|
7
|
+
import { iPollySettings } from '../../config/iConfig';
|
|
8
|
+
|
|
9
|
+
export class PollyService {
|
|
10
|
+
private static _mp3Path: string;
|
|
11
|
+
public static active: boolean = false;
|
|
12
|
+
public static polly: AWS.Polly;
|
|
13
|
+
public static voice: string;
|
|
14
|
+
|
|
15
|
+
public static initialize(config: iPollySettings): void {
|
|
16
|
+
this.active = true;
|
|
17
|
+
this._mp3Path = config.mp3Path;
|
|
18
|
+
this.polly = new AWS.Polly({
|
|
19
|
+
region: config.region,
|
|
20
|
+
signatureVersion: config.signatureVersion,
|
|
21
|
+
credentials: {
|
|
22
|
+
accessKeyId: config.accessKeyId,
|
|
23
|
+
secretAccessKey: config.secretAccessKey,
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
this.voice = config.voiceID;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
public static getDuration(name: string): number {
|
|
30
|
+
const fPath: string = this._mp3Path + name + '.mp3';
|
|
31
|
+
try {
|
|
32
|
+
if (fs.existsSync(fPath)) {
|
|
33
|
+
const duration: number = getMP3Duration(fs.readFileSync(fPath));
|
|
34
|
+
return duration;
|
|
35
|
+
}
|
|
36
|
+
} catch (err) {
|
|
37
|
+
console.error(err);
|
|
38
|
+
}
|
|
39
|
+
return 1800000;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
public static preloadTTS(text: string): void {
|
|
43
|
+
if (!this.active) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
this.tts(text, (link, duration) => {
|
|
47
|
+
if (duration <= 0) {
|
|
48
|
+
ServerLogService.writeLog(LogLevel.Error, `Retrieving tts for "${text}" failed as duration is 0 or lower`);
|
|
49
|
+
} else if (!link) {
|
|
50
|
+
ServerLogService.writeLog(LogLevel.Error, `Retrieving tts for "${text}" failed as link is empty`);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
public static tts(text: string, cb: (fileLink: string, duration: number) => void): void {
|
|
56
|
+
const hash: string = `${this.voice}_${crypto.createHash('md5').update(text).digest('hex')}`;
|
|
57
|
+
const fPath: string = `${this._mp3Path}${hash}.mp3`;
|
|
58
|
+
try {
|
|
59
|
+
if (fs.existsSync(fPath)) {
|
|
60
|
+
const duration: number = getMP3Duration(fs.readFileSync(fPath));
|
|
61
|
+
cb(hash, duration);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
} catch (err) {
|
|
65
|
+
console.error(err);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
ServerLogService.writeLog(LogLevel.Debug, `Für die Nachricht "${text}" fehlt die TTS --> bei AWS anfragen`);
|
|
69
|
+
const params: AWS.Polly.SynthesizeSpeechInput = {
|
|
70
|
+
Text: text,
|
|
71
|
+
OutputFormat: 'mp3',
|
|
72
|
+
VoiceId: this.voice,
|
|
73
|
+
};
|
|
74
|
+
this.polly.synthesizeSpeech(params, (err, data) => {
|
|
75
|
+
if (err) {
|
|
76
|
+
ServerLogService.writeLog(LogLevel.Error, `AWS Polly Error: ${err}`);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (!data || data.AudioStream === undefined) {
|
|
81
|
+
ServerLogService.writeLog(LogLevel.Error, `AWS Polly didn't send any data`);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
ServerLogService.writeLog(LogLevel.Debug, `AWS Antwort für (${hash}) erhalten Text: "${text}"`);
|
|
86
|
+
|
|
87
|
+
fs.writeFile(fPath, data.AudioStream as string | NodeJS.ArrayBufferView, (err) => {
|
|
88
|
+
const duration: number = getMP3Duration(data.AudioStream);
|
|
89
|
+
if (err) {
|
|
90
|
+
ServerLogService.writeLog(LogLevel.Error, `AWS Polly: Saving failed`);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
ServerLogService.writeLog(LogLevel.Trace, `AWS Polly: Saving sucessfully`);
|
|
94
|
+
|
|
95
|
+
cb(hash, duration);
|
|
96
|
+
return;
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { SonosDevice, SonosManager } from '@svrooij/sonos/lib';
|
|
2
|
+
import { PlayNotificationOptions } from '@svrooij/sonos/lib/models';
|
|
3
|
+
import { ServerLogService } from '../log-service';
|
|
4
|
+
import { PollyService } from './polly-service';
|
|
5
|
+
import { LogLevel } from '/models/logLevel';
|
|
6
|
+
import { Utils } from '/server/services/utils/utils';
|
|
7
|
+
import { TelegramService } from '/server/services/Telegram/telegram-service';
|
|
8
|
+
import { TimeCallback, TimeCallbackType } from '/models/timeCallback';
|
|
9
|
+
import { TimeCallbackService } from '/server/services/time-callback-service';
|
|
10
|
+
|
|
11
|
+
export class OwnSonosDevice {
|
|
12
|
+
public maxPlayOnAllVolume: number = 80;
|
|
13
|
+
public playTestMessage() {
|
|
14
|
+
SonosService.speakOnDevice(`Ich bin ${this.name}`, this);
|
|
15
|
+
}
|
|
16
|
+
public constructor(
|
|
17
|
+
public name: string,
|
|
18
|
+
public roomName: string,
|
|
19
|
+
public device: SonosDevice | undefined,
|
|
20
|
+
) {};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export class SonosService {
|
|
24
|
+
private static sonosManager: SonosManager;
|
|
25
|
+
private static ownDevices: { [name: string]: OwnSonosDevice } = {};
|
|
26
|
+
|
|
27
|
+
private static isInitialized: boolean;
|
|
28
|
+
public static all: SonosDevice[] = [];
|
|
29
|
+
public static devicesDict: { [name: string]: SonosDevice } = {};
|
|
30
|
+
private static checkTimeCallback: TimeCallback;
|
|
31
|
+
private static reinitializationDevice: OwnSonosDevice | undefined;
|
|
32
|
+
|
|
33
|
+
public static addOwnDevices(
|
|
34
|
+
snDevices: { [name: string]: OwnSonosDevice },
|
|
35
|
+
reinitializationDevice?: OwnSonosDevice
|
|
36
|
+
): void {
|
|
37
|
+
this.ownDevices = snDevices;
|
|
38
|
+
this.reinitializationDevice = reinitializationDevice;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
public static initialize(reinitialize: boolean = false): void {
|
|
42
|
+
ServerLogService.writeLog(LogLevel.Debug, `Initialisiere Sonos Service`);
|
|
43
|
+
if (!reinitialize) {
|
|
44
|
+
this.checkTimeCallback = new TimeCallback(
|
|
45
|
+
'SonosFunctionallityChecker',
|
|
46
|
+
TimeCallbackType.TimeOfDay,
|
|
47
|
+
() => {
|
|
48
|
+
this.checkAll();
|
|
49
|
+
},
|
|
50
|
+
0,
|
|
51
|
+
23,
|
|
52
|
+
30,
|
|
53
|
+
);
|
|
54
|
+
TimeCallbackService.addCallback(this.checkTimeCallback);
|
|
55
|
+
}
|
|
56
|
+
this.all = [];
|
|
57
|
+
this.sonosManager = new SonosManager();
|
|
58
|
+
this.sonosManager
|
|
59
|
+
.InitializeWithDiscovery(10)
|
|
60
|
+
.then(() => {
|
|
61
|
+
this.sonosManager.OnNewDevice((d: SonosDevice) => {
|
|
62
|
+
ServerLogService.writeLog(LogLevel.Info, `SonosDevice ${d.Name} joined`);
|
|
63
|
+
SonosService.initializeDevice(d);
|
|
64
|
+
});
|
|
65
|
+
ServerLogService.writeLog(LogLevel.Debug, `${this.sonosManager.Devices.length} Sonos Geräte gefunden.`);
|
|
66
|
+
this.sonosManager.Devices.forEach((d: SonosDevice) => {
|
|
67
|
+
SonosService.initializeDevice(d);
|
|
68
|
+
});
|
|
69
|
+
this.isInitialized = true;
|
|
70
|
+
if (!reinitialize && this.reinitializationDevice !== undefined) {
|
|
71
|
+
this.speakOnDevice(
|
|
72
|
+
`Sonos System initialisiert und bereit für Sprachausgaben.`,
|
|
73
|
+
this.reinitializationDevice,
|
|
74
|
+
30
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
})
|
|
78
|
+
.catch(console.error);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
public static async checkAll(): Promise<void> {
|
|
82
|
+
let currentDevice: OwnSonosDevice | undefined;
|
|
83
|
+
try {
|
|
84
|
+
for (const deviceName in this.ownDevices) {
|
|
85
|
+
currentDevice = this.ownDevices[deviceName];
|
|
86
|
+
if(currentDevice?.device === undefined) {
|
|
87
|
+
throw `${currentDevice?.name} is missing`;
|
|
88
|
+
}
|
|
89
|
+
await currentDevice.device.GetState();
|
|
90
|
+
}
|
|
91
|
+
if(currentDevice !== undefined) {
|
|
92
|
+
ServerLogService.writeLog(LogLevel.Info, `Alle Geräte okay --> Last checked ${currentDevice.name}`);
|
|
93
|
+
}
|
|
94
|
+
} catch (e) {
|
|
95
|
+
ServerLogService.writeLog(
|
|
96
|
+
LogLevel.Error,
|
|
97
|
+
`Atleast one device failed --> Last checked ${(currentDevice?.name ?? "undefined")}`
|
|
98
|
+
);
|
|
99
|
+
TelegramService.inform(`Sonos device is failing --> Reinitialize whole system`);
|
|
100
|
+
this.initialize(true);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
public static speakOnAll(pMessage: string, volumeOverride: number = -1): void {
|
|
105
|
+
if (!this.isInitialized) {
|
|
106
|
+
ServerLogService.writeLog(LogLevel.Alert, `SonosService noch nicht initialisiert.`);
|
|
107
|
+
}
|
|
108
|
+
PollyService.tts(pMessage, (networkPath: string, duration: number) => {
|
|
109
|
+
const hours: number = new Date().getHours();
|
|
110
|
+
let volume: number = hours < 10 || hours > 22 ? 40 : 80;
|
|
111
|
+
|
|
112
|
+
for (const deviceName in this.ownDevices) {
|
|
113
|
+
SonosService.playOnDevice(
|
|
114
|
+
this.ownDevices[deviceName],
|
|
115
|
+
networkPath,
|
|
116
|
+
duration,
|
|
117
|
+
(volumeOverride > -1) ? volumeOverride : Math.min(volume, this.ownDevices[deviceName].maxPlayOnAllVolume)
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
public static playOnDevice(
|
|
124
|
+
ownSnDevice: OwnSonosDevice,
|
|
125
|
+
mp3Name: string,
|
|
126
|
+
duration: number,
|
|
127
|
+
volume: number | undefined = undefined,
|
|
128
|
+
onlyWhenPlaying: boolean | undefined = undefined,
|
|
129
|
+
resolveAfterRevert: boolean | undefined = false,
|
|
130
|
+
): void {
|
|
131
|
+
const specificTimeout: number = Math.ceil(duration / 1000) + 5;
|
|
132
|
+
const options: PlayNotificationOptions = {
|
|
133
|
+
trackUri: `http://192.168.178.13:8081/file.mp3?fname=${mp3Name}`,
|
|
134
|
+
delayMs: 750,
|
|
135
|
+
onlyWhenPlaying: onlyWhenPlaying,
|
|
136
|
+
resolveAfterRevert: resolveAfterRevert,
|
|
137
|
+
volume: volume,
|
|
138
|
+
specificTimeout: specificTimeout,
|
|
139
|
+
notificationFired: (played) => {
|
|
140
|
+
ServerLogService.writeLog(
|
|
141
|
+
LogLevel.Trace,
|
|
142
|
+
`Sonos Notification ("${mp3Name}") was${played ? '' : "n't"} played in ${ownSnDevice.roomName} (duration: "${specificTimeout}")`,
|
|
143
|
+
);
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
try {
|
|
147
|
+
const device: SonosDevice| undefined = ownSnDevice.device;
|
|
148
|
+
if (device === undefined) {
|
|
149
|
+
ServerLogService.writeLog(LogLevel.Alert, `Sonos Geräte ${ownSnDevice.name} ist nicht initialisiert`);
|
|
150
|
+
Utils.guardedTimeout(
|
|
151
|
+
() => {
|
|
152
|
+
this.initialize();
|
|
153
|
+
},
|
|
154
|
+
500,
|
|
155
|
+
this,
|
|
156
|
+
);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
ServerLogService.writeLog(
|
|
160
|
+
LogLevel.Trace,
|
|
161
|
+
`Spiele nun die Ausgabe für "${mp3Name}" auf "${ownSnDevice.name}"`,
|
|
162
|
+
);
|
|
163
|
+
device.PlayNotificationTwo(options).then((played) => {
|
|
164
|
+
ServerLogService.writeLog(
|
|
165
|
+
LogLevel.Debug,
|
|
166
|
+
`Sonos Notification ("${mp3Name}") was${played ? '' : "n't"} played in ${
|
|
167
|
+
ownSnDevice.roomName
|
|
168
|
+
} (duration: "${specificTimeout}")`,
|
|
169
|
+
);
|
|
170
|
+
});
|
|
171
|
+
} catch (err) {
|
|
172
|
+
ServerLogService.writeLog(LogLevel.Info, `Sonos Error ${err.message}: ${err.stack}`);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
public static speakOnDevice(
|
|
177
|
+
pMessage: string,
|
|
178
|
+
ownSnDevice: OwnSonosDevice,
|
|
179
|
+
volume: number | undefined = undefined,
|
|
180
|
+
onlyWhenPlaying: boolean | undefined = undefined,
|
|
181
|
+
resolveAfterRevert: boolean | undefined = undefined,
|
|
182
|
+
): void {
|
|
183
|
+
PollyService.tts(pMessage, (networkPath: string, duration: number) => {
|
|
184
|
+
SonosService.playOnDevice(ownSnDevice, networkPath, duration, volume, onlyWhenPlaying, resolveAfterRevert);
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
public static speakTestMessageOnEachDevice(): void {
|
|
189
|
+
for (const deviceName in this.ownDevices) {
|
|
190
|
+
this.ownDevices[deviceName].playTestMessage();
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
private static initializeDevice(d: SonosDevice) {
|
|
195
|
+
this.devicesDict[d.Name] = d;
|
|
196
|
+
this.ownDevices
|
|
197
|
+
ServerLogService.writeLog(LogLevel.Debug, `Sonos ${d.Uuid} für ${d.Name} gefunden`);
|
|
198
|
+
}
|
|
199
|
+
}
|