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,125 @@
|
|
|
1
|
+
import { Mongo } from 'meteor/mongo';
|
|
2
|
+
import { ServerLogService } from '../log-service';
|
|
3
|
+
import { LogLevel } from '../../../models/logLevel';
|
|
4
|
+
import { TemperaturDataPoint } from '../../../models/persistence/temperaturDataPoint';
|
|
5
|
+
import { CountToday } from '../../../models/persistence/todaysCount';
|
|
6
|
+
import { HmIpHeizgruppe } from '../../devices/hmIPDevices/hmIpHeizgruppe';
|
|
7
|
+
import { HmIPDevice } from '../../devices/hmIPDevices/hmIpDevice';
|
|
8
|
+
import { RoomBase } from '../../../models/rooms/RoomBase';
|
|
9
|
+
import { BasicRoomInfo } from '../../../models/persistence/BasicRoomInfo';
|
|
10
|
+
import { RoomDetailInfo } from '../../../models/persistence/RoomDetailInfo';
|
|
11
|
+
import { iTemperaturDataPoint } from '/imports/api/heizung';
|
|
12
|
+
import { DailyMovementCount } from '../../../models/persistence/DailyMovementCount';
|
|
13
|
+
|
|
14
|
+
export const TemperatureHistoryCollection = new Mongo.Collection<TemperaturDataPoint>('TemperaturData');
|
|
15
|
+
export const HeatGroupCollection = new Mongo.Collection<TemperaturDataPoint>('HeatGroupCollection');
|
|
16
|
+
export const BasicRoomCollection = new Mongo.Collection<BasicRoomInfo>('BasicRooms');
|
|
17
|
+
export const RoomDetailsCollection = new Mongo.Collection<RoomDetailInfo>('RoomDetailsCollection');
|
|
18
|
+
export const CountTodayCollection = new Mongo.Collection<CountToday>('PresenceToday');
|
|
19
|
+
export const DailyMovementCountTodayCollection = new Mongo.Collection<DailyMovementCount>('DailyMovementCount');
|
|
20
|
+
export class Persist {
|
|
21
|
+
public static MeteorBound: (callback: any) => void;
|
|
22
|
+
public static addTemperaturDataPoint(hzGrp: HmIpHeizgruppe): void {
|
|
23
|
+
const dataPoint: iTemperaturDataPoint = new TemperaturDataPoint(
|
|
24
|
+
hzGrp.info.customName,
|
|
25
|
+
hzGrp.iTemperatur,
|
|
26
|
+
hzGrp.desiredTemperatur,
|
|
27
|
+
hzGrp.iLevel,
|
|
28
|
+
hzGrp.humidity,
|
|
29
|
+
new Date(),
|
|
30
|
+
);
|
|
31
|
+
ServerLogService.writeLog(LogLevel.Trace, `Persisting Temperatur Data for ${hzGrp.info.customName}`);
|
|
32
|
+
this.MeteorBound(() => {
|
|
33
|
+
TemperatureHistoryCollection.insert(dataPoint);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
this.MeteorBound(() => {
|
|
37
|
+
HeatGroupCollection.update({ name: dataPoint.name }, dataPoint, { upsert: true });
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
public static addRoom(room: RoomBase): void {
|
|
42
|
+
ServerLogService.writeLog(LogLevel.Trace, `Persisting Room for ${room.roomName}`);
|
|
43
|
+
this.MeteorBound(() => {
|
|
44
|
+
BasicRoomCollection.update({ roomName: room.roomName }, new BasicRoomInfo(room.roomName, room.Settings.etage), {
|
|
45
|
+
upsert: true,
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
const detailed = new RoomDetailInfo(room.roomName, room.Settings.etage);
|
|
49
|
+
for (const h of room.HeatGroup.heaters) {
|
|
50
|
+
detailed.heaters.push(h.info.customName);
|
|
51
|
+
}
|
|
52
|
+
this.MeteorBound(() => {
|
|
53
|
+
RoomDetailsCollection.update({ roomName: room.roomName }, detailed, { upsert: true });
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
public static async getCount(device: HmIPDevice): Promise<CountToday> {
|
|
58
|
+
const result = new Promise<CountToday>((resolve) => {
|
|
59
|
+
this.MeteorBound(() => {
|
|
60
|
+
const options = {
|
|
61
|
+
limit: 1,
|
|
62
|
+
};
|
|
63
|
+
const databaseValue: CountToday[] = CountTodayCollection.find(
|
|
64
|
+
{ deviceID: device.info.fullID },
|
|
65
|
+
options,
|
|
66
|
+
).fetch();
|
|
67
|
+
if (databaseValue.length === 0) {
|
|
68
|
+
ServerLogService.writeLog(
|
|
69
|
+
LogLevel.Debug,
|
|
70
|
+
`Es gibt noch keinen persistierten Counter für ${device.info.fullName}`,
|
|
71
|
+
);
|
|
72
|
+
resolve(new CountToday(device.info.fullID, 0));
|
|
73
|
+
} else {
|
|
74
|
+
resolve(databaseValue[0]);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
return result;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
public static persistTodayCount(device: HmIPDevice, count: number, oldCount: number): void {
|
|
83
|
+
this.MeteorBound(() => {
|
|
84
|
+
const result = CountTodayCollection.update(
|
|
85
|
+
{ deviceID: device.info.fullID },
|
|
86
|
+
new CountToday(device.info.fullID, count),
|
|
87
|
+
{ upsert: true },
|
|
88
|
+
);
|
|
89
|
+
if (count === 0) {
|
|
90
|
+
const date = new Date();
|
|
91
|
+
date.setHours(-24, 0, 0, 0);
|
|
92
|
+
const result2 = DailyMovementCountTodayCollection.update(
|
|
93
|
+
{ deviceID: device.info.fullID, date: date },
|
|
94
|
+
new DailyMovementCount(device.info.fullID, oldCount, device.info.room, date),
|
|
95
|
+
{ upsert: true },
|
|
96
|
+
);
|
|
97
|
+
ServerLogService.writeLog(
|
|
98
|
+
LogLevel.Trace,
|
|
99
|
+
`Persisting Daily Movement Count for ${device.info.customName} to ${oldCount} resolved with "${result2}"`,
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
ServerLogService.writeLog(
|
|
103
|
+
LogLevel.Trace,
|
|
104
|
+
`Persisting PresenceToday Data for ${device.info.customName} to ${count} resolved with "${result}"`,
|
|
105
|
+
);
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
public static async readTemperaturDataPoint(
|
|
110
|
+
hzGrp: HmIpHeizgruppe,
|
|
111
|
+
limit: number = -1,
|
|
112
|
+
): Promise<TemperaturDataPoint[]> {
|
|
113
|
+
const result = new Promise<TemperaturDataPoint[]>((resolve) => {
|
|
114
|
+
this.MeteorBound(() => {
|
|
115
|
+
const options = {
|
|
116
|
+
limit: limit > 0 ? limit : undefined,
|
|
117
|
+
sort: { date: -1 },
|
|
118
|
+
};
|
|
119
|
+
resolve(TemperatureHistoryCollection.find({ name: hzGrp.info.customName }, options).fetch());
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
return result;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { HTTPSOptions } from './HTTPSOptions';
|
|
2
|
+
import { ServerLogService } from './log-service';
|
|
3
|
+
import { LogLevel } from '../../models/logLevel';
|
|
4
|
+
import * as fs from 'fs';
|
|
5
|
+
import HTTPS from 'https';
|
|
6
|
+
import { Utils } from './utils/utils';
|
|
7
|
+
|
|
8
|
+
export class HTTPSService {
|
|
9
|
+
private static defaultCallback(data: string, statuscode: number): void {
|
|
10
|
+
ServerLogService.writeLog(LogLevel.DeepTrace, `Response statusCode:"${statuscode}"\nData:"${data}"`);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
public static request(
|
|
14
|
+
options: HTTPSOptions,
|
|
15
|
+
postData: string = '',
|
|
16
|
+
retryOnError: number = 5,
|
|
17
|
+
responseCallback: (data: string, statuscode: number) => void = HTTPSService.defaultCallback,
|
|
18
|
+
): void {
|
|
19
|
+
const responseData: string[] = [];
|
|
20
|
+
const req = HTTPS.request(options, (res: any) => {
|
|
21
|
+
res.on('data', (data: Buffer) => {
|
|
22
|
+
responseData.push(data.toString());
|
|
23
|
+
});
|
|
24
|
+
res.on('end', () => {
|
|
25
|
+
responseCallback(responseData.join(''), res.statusCode);
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
req.on('error', (e: any) => {
|
|
29
|
+
if (retryOnError > 0) {
|
|
30
|
+
ServerLogService.writeLog(LogLevel.DeepTrace, `HTTPS request failed --> ${retryOnError} retries left`);
|
|
31
|
+
Utils.guardedTimeout(() => {
|
|
32
|
+
HTTPSService.request(options, postData, retryOnError - 1, responseCallback);
|
|
33
|
+
}, 100);
|
|
34
|
+
} else {
|
|
35
|
+
ServerLogService.writeLog(LogLevel.Error, `HTTPS request failed after retries`);
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
if (postData !== '') {
|
|
39
|
+
req.write(postData);
|
|
40
|
+
}
|
|
41
|
+
req.end();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
public static async downloadFile(url: string, filePath: string): Promise<boolean> {
|
|
45
|
+
return new Promise((resolve, reject) => {
|
|
46
|
+
const file = fs.createWriteStream(filePath);
|
|
47
|
+
let fileInfo = null;
|
|
48
|
+
|
|
49
|
+
const request = HTTPS.get(url, (response: any) => {
|
|
50
|
+
if (response.statusCode !== 200) {
|
|
51
|
+
reject(new Error(`Failed to get '${url}' (${response.statusCode})`));
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
fileInfo = {
|
|
56
|
+
mime: response.headers['content-type'],
|
|
57
|
+
size: parseInt(response.headers['content-length'], 10),
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
response.pipe(file);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// The destination stream is ended by the time it's called
|
|
64
|
+
file.on('finish', () => resolve(true));
|
|
65
|
+
|
|
66
|
+
request.on('error', (err: any) => {
|
|
67
|
+
fs.unlink(filePath, () => resolve(false));
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { HmIpDeviceType } from '../devices/hmIPDevices/hmIpDeviceType';
|
|
2
|
+
import { ZigbeeDeviceType } from '../devices/zigbee/zigbeeDeviceType';
|
|
3
|
+
import { TelegramService } from './Telegram/telegram-service';
|
|
4
|
+
import { LogLevel } from '../../models/logLevel';
|
|
5
|
+
|
|
6
|
+
export class ServerLogService {
|
|
7
|
+
public static logLevel: number = 4;
|
|
8
|
+
public static telegramLevel: number = -1; // Controlled from within Config File
|
|
9
|
+
public static writeLog(pLevel: LogLevel, pMessage: string): void {
|
|
10
|
+
if (pLevel > ServerLogService.logLevel) {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
console.log(pMessage);
|
|
15
|
+
|
|
16
|
+
if (pLevel <= ServerLogService.telegramLevel) {
|
|
17
|
+
const title: string = LogLevel[pLevel];
|
|
18
|
+
TelegramService.sendMessage(TelegramService.subscribedIDs, `${title}: ${pMessage}`);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
public static addedDeviceToRoom(
|
|
23
|
+
pRoomName: string,
|
|
24
|
+
pDeviceType: HmIpDeviceType,
|
|
25
|
+
pRoomIndex: number,
|
|
26
|
+
forceDebug: boolean = false,
|
|
27
|
+
): void {
|
|
28
|
+
const logLevel = forceDebug ? LogLevel.Debug : LogLevel.Trace;
|
|
29
|
+
ServerLogService.writeLog(
|
|
30
|
+
logLevel,
|
|
31
|
+
`${HmIpDeviceType[pDeviceType]} (Raumindex: ${pRoomIndex}) zum Raum "${pRoomName}" hinzugefügt"`,
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
public static missingRoomHandling(pRoomName: string, pDeviceType: HmIpDeviceType): void {
|
|
36
|
+
ServerLogService.writeLog(
|
|
37
|
+
LogLevel.Warn,
|
|
38
|
+
`Raum "${pRoomName}" hat keine Definition für den Typ "${HmIpDeviceType[pDeviceType]}"`,
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
public static missingRoomIndexHandling(pRoomName: string, pIndex: number, pDeviceType: HmIpDeviceType): void {
|
|
43
|
+
ServerLogService.writeLog(
|
|
44
|
+
LogLevel.Warn,
|
|
45
|
+
`Raum "${pRoomName}" hat keine Definition für den Typ "${HmIpDeviceType[pDeviceType]} mit Index ${pIndex}"`,
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
public static addedZigbeeDeviceToRoom(pRoomName: string, pDeviceType: ZigbeeDeviceType, pRoomIndex: number): void {
|
|
50
|
+
ServerLogService.writeLog(
|
|
51
|
+
LogLevel.Trace,
|
|
52
|
+
`${ZigbeeDeviceType[pDeviceType]} (Raumindex: ${pRoomIndex}) zum Raum "${pRoomName}" hinzugefügt"`,
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
public static missingZigbeeRoomHandling(pRoomName: string, pDeviceType: ZigbeeDeviceType): void {
|
|
57
|
+
ServerLogService.writeLog(
|
|
58
|
+
LogLevel.Warn,
|
|
59
|
+
`Raum "${pRoomName}" hat keine Definition für den Zigbee Typ "${ZigbeeDeviceType[pDeviceType]}"`,
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
public static missingZigbeeRoomIndexHandling(pRoomName: string, pIndex: number, pDeviceType: ZigbeeDeviceType): void {
|
|
64
|
+
ServerLogService.writeLog(
|
|
65
|
+
LogLevel.Warn,
|
|
66
|
+
`Raum "${pRoomName}" hat keine Definition für den Typ "${ZigbeeDeviceType[pDeviceType]} mit Index ${pIndex}"`,
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { HTTPSService } from './https-service';
|
|
2
|
+
import { HTTPSOptions } from './HTTPSOptions';
|
|
3
|
+
import { ServerLogService } from './log-service';
|
|
4
|
+
import { LogLevel } from '../../models/logLevel';
|
|
5
|
+
import * as fs from 'fs';
|
|
6
|
+
import { OwnSonosDevice, SonosService } from './Sonos/sonos-service';
|
|
7
|
+
import { PollyService } from './Sonos/polly-service';
|
|
8
|
+
import { Utils } from './utils/utils';
|
|
9
|
+
import { SettingsService } from "/server/services/settings-service";
|
|
10
|
+
|
|
11
|
+
export class NewsService {
|
|
12
|
+
public static oneDay: number = 1000 * 60 * 60 * 24;
|
|
13
|
+
public static lastNewsName: string;
|
|
14
|
+
private static hourlyInterval: NodeJS.Timeout;
|
|
15
|
+
|
|
16
|
+
public static initialize(): void {
|
|
17
|
+
NewsService.hourlyInterval = Utils.guardedInterval(NewsService.getLastNews, 3600000);
|
|
18
|
+
NewsService.getLastNews();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
public static getLastNews(): void {
|
|
22
|
+
HTTPSService.request(
|
|
23
|
+
new HTTPSOptions('www1.wdr.de', '/mediathek/audio/wdr-aktuell-news/index.html', {}, 'GET', 443),
|
|
24
|
+
'',
|
|
25
|
+
5,
|
|
26
|
+
(response: string) => {
|
|
27
|
+
try {
|
|
28
|
+
const cutAfterDownload: string = response.split(`" title="Audio Download">`)[0];
|
|
29
|
+
const cutsPriorDownload: string[] = cutAfterDownload.split(`<a class="button download fsk0" href="`);
|
|
30
|
+
const target: string = 'https:' + cutsPriorDownload[cutsPriorDownload.length - 1];
|
|
31
|
+
const splits: string[] = target.split('/');
|
|
32
|
+
const fileName: string = splits[splits.length - 1];
|
|
33
|
+
const filePath = `${SettingsService.settings.mp3Server?.path}${fileName}`;
|
|
34
|
+
ServerLogService.writeLog(LogLevel.Debug, `NewsService: Die aktuelle News ist "${target}"`);
|
|
35
|
+
if (fs.existsSync(filePath)) {
|
|
36
|
+
NewsService.lastNewsName = fileName.split('.mp3')[0];
|
|
37
|
+
ServerLogService.writeLog(LogLevel.Debug, `Wir haben bereits die neuste WDR Nachrichten heruntergeladen.`);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
HTTPSService.downloadFile(target, `//HOMESERVER/Users/Public/Documents/ttsMP3/${fileName}`).then(
|
|
41
|
+
(success: boolean) => {
|
|
42
|
+
if (!success) {
|
|
43
|
+
ServerLogService.writeLog(LogLevel.Debug, `Fehler beim Herunterladen der Nachrichten von WDR`);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
NewsService.lastNewsName = fileName.split('.mp3')[0];
|
|
48
|
+
},
|
|
49
|
+
);
|
|
50
|
+
} catch (e) {
|
|
51
|
+
ServerLogService.writeLog(LogLevel.Debug, `Fehler beim Parsen der WDR Antwort Error: ${e}`);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
public static playLastNews(ownSonosDevice: OwnSonosDevice, volume: number = 30, retries: number = 5): void {
|
|
59
|
+
if (!NewsService.lastNewsName) {
|
|
60
|
+
if (retries > 0) {
|
|
61
|
+
ServerLogService.writeLog(
|
|
62
|
+
LogLevel.Warn,
|
|
63
|
+
`Der NewsService ist noch nicht bereit --> warten, verbleibende Neuversuche ${retries - 1}`,
|
|
64
|
+
);
|
|
65
|
+
Utils.guardedTimeout(() => {
|
|
66
|
+
NewsService.playLastNews(ownSonosDevice, volume, retries - 1);
|
|
67
|
+
}, 1000);
|
|
68
|
+
} else {
|
|
69
|
+
ServerLogService.writeLog(LogLevel.Error, `Der NewsService ist trotz Warten nicht bereit --> Abbruch`);
|
|
70
|
+
}
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
SonosService.playOnDevice(
|
|
75
|
+
ownSonosDevice,
|
|
76
|
+
NewsService.lastNewsName,
|
|
77
|
+
PollyService.getDuration(NewsService.lastNewsName),
|
|
78
|
+
volume,
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { iConfig } from '../config/iConfig';
|
|
2
|
+
|
|
3
|
+
export class SettingsService {
|
|
4
|
+
public static settings: iConfig;
|
|
5
|
+
|
|
6
|
+
public static get TelegramActive(): boolean {
|
|
7
|
+
return this.settings.telegram !== undefined;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
public static get Mp3Active(): boolean {
|
|
11
|
+
return this.settings.mp3Server !== undefined;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
public static initialize(config: iConfig): void {
|
|
15
|
+
if (this.validateConfig(config)) {
|
|
16
|
+
this.settings = config;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
private static validateConfig(config: iConfig): boolean {
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import { TimeCallback, TimeCallbackType } from '../../models/timeCallback';
|
|
2
|
+
import { getSunrise, getSunset } from 'sunrise-sunset-js';
|
|
3
|
+
import { ServerLogService } from './log-service';
|
|
4
|
+
import { LogLevel } from '../../models/logLevel';
|
|
5
|
+
import { Devices } from '../devices/devices';
|
|
6
|
+
import { Utils } from './utils/utils';
|
|
7
|
+
import { SettingsService } from './settings-service';
|
|
8
|
+
import { iTimePair } from '../config/iConfig';
|
|
9
|
+
|
|
10
|
+
export enum TimeOfDay {
|
|
11
|
+
BeforeSunrise = 1,
|
|
12
|
+
Daylight = 2,
|
|
13
|
+
AfterSunset = 3,
|
|
14
|
+
Night = 4,
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export class SunTimeOffsets {
|
|
18
|
+
public constructor(
|
|
19
|
+
public sunrise: number = 0,
|
|
20
|
+
public sunset: number = 0,
|
|
21
|
+
public minimumHours: number = 6,
|
|
22
|
+
public minimumMinutes: number = 24,
|
|
23
|
+
public maximumHours: number = 22,
|
|
24
|
+
public maximumMinutes: number = 30,
|
|
25
|
+
) {}
|
|
26
|
+
|
|
27
|
+
public getNextMinimumSunrise(): Date {
|
|
28
|
+
const today: Date = new Date(new Date().setHours(this.minimumHours, this.minimumMinutes));
|
|
29
|
+
if (today > new Date()) {
|
|
30
|
+
return today;
|
|
31
|
+
}
|
|
32
|
+
return new Date(today.getTime() + 24 * 60 * 60 * 1000);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
public getNextMaximumSunset(): Date {
|
|
36
|
+
const today = new Date(new Date().setHours(this.maximumHours, this.maximumMinutes));
|
|
37
|
+
if (today > new Date()) {
|
|
38
|
+
return today;
|
|
39
|
+
}
|
|
40
|
+
return new Date(today.getTime() + 24 * 60 * 60 * 1000);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export class TimeCallbackService {
|
|
45
|
+
private static _todaySunRise: Date;
|
|
46
|
+
private static _todaySunSet: Date;
|
|
47
|
+
private static _nextSunRise: Date;
|
|
48
|
+
private static _nextSunSet: Date;
|
|
49
|
+
private static _callbacks: TimeCallback[] = [];
|
|
50
|
+
private static _iCheck: NodeJS.Timeout;
|
|
51
|
+
private static _lastCheck: Date = new Date(0);
|
|
52
|
+
|
|
53
|
+
public static dayType(pOffset: SunTimeOffsets): TimeOfDay {
|
|
54
|
+
const now: Date = new Date();
|
|
55
|
+
|
|
56
|
+
const hours = now.getHours();
|
|
57
|
+
const minutes = now.getMinutes();
|
|
58
|
+
|
|
59
|
+
const nightEnd: iTimePair = SettingsService.settings.timeSettings.nightEnd;
|
|
60
|
+
const nightStart: iTimePair = SettingsService.settings.timeSettings.nightStart;
|
|
61
|
+
|
|
62
|
+
if (nightStart.hours < nightEnd.hours) {
|
|
63
|
+
if (
|
|
64
|
+
(hours < nightEnd.hours && hours > nightStart.hours) ||
|
|
65
|
+
(hours === nightEnd.hours && minutes < nightEnd.minutes) ||
|
|
66
|
+
(hours === nightStart.hours && minutes > nightStart.minutes)
|
|
67
|
+
) {
|
|
68
|
+
return TimeOfDay.Night;
|
|
69
|
+
}
|
|
70
|
+
} else {
|
|
71
|
+
if (
|
|
72
|
+
hours > nightStart.hours ||
|
|
73
|
+
hours < nightEnd.hours ||
|
|
74
|
+
(hours === nightStart.hours && minutes >= nightStart.minutes) ||
|
|
75
|
+
(hours === nightEnd.hours && minutes <= nightEnd.minutes)
|
|
76
|
+
) {
|
|
77
|
+
return TimeOfDay.Night;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
let sunset: Date = new Date(TimeCallbackService._todaySunSet.getTime() + pOffset.sunset * 60 * 1000);
|
|
82
|
+
const maximumSunset: Date = pOffset.getNextMaximumSunset();
|
|
83
|
+
if (maximumSunset < sunset && maximumSunset.getDate() === sunset.getDate()) {
|
|
84
|
+
ServerLogService.writeLog(
|
|
85
|
+
LogLevel.Trace,
|
|
86
|
+
`Maximum Sunset vor nächstem Sunset: Sunset ${sunset.toLocaleString()}\t\t${maximumSunset.toLocaleString()}`,
|
|
87
|
+
);
|
|
88
|
+
sunset = maximumSunset;
|
|
89
|
+
}
|
|
90
|
+
if (now > sunset) {
|
|
91
|
+
return TimeOfDay.AfterSunset;
|
|
92
|
+
}
|
|
93
|
+
const minimumSunrise: Date = pOffset.getNextMinimumSunrise();
|
|
94
|
+
let sunrise: Date = new Date(TimeCallbackService._todaySunRise.getTime() + pOffset.sunset * 60 * 1000);
|
|
95
|
+
if (minimumSunrise > sunrise && minimumSunrise.getDate() === sunrise.getDate()) {
|
|
96
|
+
ServerLogService.writeLog(
|
|
97
|
+
LogLevel.Trace,
|
|
98
|
+
`Minimum Sunset nach aktuellem Sunrise: Sunset ${sunrise.toLocaleString()}\t\t${minimumSunrise.toLocaleString()}`,
|
|
99
|
+
);
|
|
100
|
+
sunrise = minimumSunrise;
|
|
101
|
+
}
|
|
102
|
+
if (now > sunrise) {
|
|
103
|
+
return TimeOfDay.Daylight;
|
|
104
|
+
}
|
|
105
|
+
return TimeOfDay.BeforeSunrise;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
public static darkOutsideOrNight(dayType: TimeOfDay): boolean {
|
|
109
|
+
switch (dayType) {
|
|
110
|
+
case TimeOfDay.Night:
|
|
111
|
+
case TimeOfDay.BeforeSunrise:
|
|
112
|
+
case TimeOfDay.AfterSunset:
|
|
113
|
+
return true;
|
|
114
|
+
default:
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
public static get nextSunRise(): Date {
|
|
120
|
+
return TimeCallbackService._nextSunRise;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
public static get nextSunSet(): Date {
|
|
124
|
+
return TimeCallbackService._nextSunSet;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
public static init(): void {
|
|
128
|
+
const dailyRecalc: TimeCallback = new TimeCallback(
|
|
129
|
+
'Midnight Recalc',
|
|
130
|
+
TimeCallbackType.TimeOfDay,
|
|
131
|
+
() => {
|
|
132
|
+
TimeCallbackService.recalcSunTimes();
|
|
133
|
+
Devices.midnightReset();
|
|
134
|
+
},
|
|
135
|
+
0,
|
|
136
|
+
0,
|
|
137
|
+
0,
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
const daily3oClockRecalc: TimeCallback = new TimeCallback(
|
|
141
|
+
'Daily3oClockRecalc',
|
|
142
|
+
TimeCallbackType.TimeOfDay,
|
|
143
|
+
() => {
|
|
144
|
+
Devices.resetPraesenzCount();
|
|
145
|
+
},
|
|
146
|
+
0,
|
|
147
|
+
3,
|
|
148
|
+
0,
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
TimeCallbackService.recalcSunTimes();
|
|
152
|
+
TimeCallbackService.addCallback(dailyRecalc);
|
|
153
|
+
TimeCallbackService.addCallback(daily3oClockRecalc);
|
|
154
|
+
this._iCheck = Utils.guardedInterval(TimeCallbackService.performCheck, 60000);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
public static addCallback(pCallback: TimeCallback): void {
|
|
158
|
+
TimeCallbackService._callbacks.push(pCallback);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
public static performCheck(): void {
|
|
162
|
+
ServerLogService.writeLog(LogLevel.Trace, `Perform TimeCallBackCheck`);
|
|
163
|
+
const now: Date = new Date();
|
|
164
|
+
for (const tc of TimeCallbackService._callbacks) {
|
|
165
|
+
if (tc.nextToDo === undefined || tc.nextToDo < tc.lastDone) {
|
|
166
|
+
tc.recalcNextToDo(now);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (tc.nextToDo === undefined) {
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (tc.nextToDo < now && tc.nextToDo > TimeCallbackService._lastCheck) {
|
|
174
|
+
tc.cFunction();
|
|
175
|
+
tc.lastDone = now;
|
|
176
|
+
tc.nextToDo = undefined;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (TimeCallbackService._nextSunRise < now) {
|
|
181
|
+
const tomorow: Date = new Date(new Date().setHours(2, 0, 0, 0) + 24 * 60 * 60 * 1000);
|
|
182
|
+
TimeCallbackService.updateSunRise(tomorow);
|
|
183
|
+
}
|
|
184
|
+
if (TimeCallbackService._nextSunSet < now) {
|
|
185
|
+
const tomorow: Date = new Date(new Date().setHours(2, 0, 0, 0) + 24 * 60 * 60 * 1000);
|
|
186
|
+
TimeCallbackService.updateSunSet(tomorow);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
TimeCallbackService._lastCheck = now;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
public static recalcSunTimes(): void {
|
|
193
|
+
TimeCallbackService._todaySunRise = getSunrise(51.529556852253826, 7.097266042276687, new Date());
|
|
194
|
+
|
|
195
|
+
TimeCallbackService._todaySunSet = getSunset(51.529556852253826, 7.097266042276687, new Date());
|
|
196
|
+
TimeCallbackService.updateSunSet();
|
|
197
|
+
TimeCallbackService.updateSunRise();
|
|
198
|
+
ServerLogService.writeLog(
|
|
199
|
+
LogLevel.Info,
|
|
200
|
+
`Nächster Sonnenaufgang um ${TimeCallbackService.nextSunRise.toLocaleTimeString('de-DE')}
|
|
201
|
+
Nächster Sonnenuntergang um ${TimeCallbackService._nextSunSet.toLocaleTimeString('de-DE')}`,
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
public static removeCallback(pCallback: TimeCallback): void {
|
|
206
|
+
for (let i: number = 0; i < TimeCallbackService._callbacks.length; i++) {
|
|
207
|
+
const cb: TimeCallback = TimeCallbackService._callbacks[i];
|
|
208
|
+
if (cb.name !== pCallback.name) {
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
TimeCallbackService._callbacks.splice(i, 1);
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
public static updateSunRise(pDay: Date = new Date()): void {
|
|
217
|
+
TimeCallbackService._nextSunRise = getSunrise(51.529556852253826, 7.097266042276687, pDay);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
public static updateSunSet(pDay: Date = new Date()): void {
|
|
221
|
+
TimeCallbackService._nextSunSet = getSunset(51.529556852253826, 7.097266042276687, pDay);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export class ringStorage {
|
|
2
|
+
private storage: string[] = [];
|
|
3
|
+
private pointer: number = 0;
|
|
4
|
+
|
|
5
|
+
public constructor(private maxSize: number = 10) {}
|
|
6
|
+
|
|
7
|
+
public add(text: string): void {
|
|
8
|
+
this.pointer = (this.pointer + 1) % this.maxSize;
|
|
9
|
+
this.storage[this.pointer] = text;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
public readAmount(amount: number): string[] {
|
|
13
|
+
const result: string[] = [];
|
|
14
|
+
amount = Math.max(amount, this.maxSize);
|
|
15
|
+
let pos = this.pointer;
|
|
16
|
+
while (amount > 0) {
|
|
17
|
+
result.push(this.storage[pos]);
|
|
18
|
+
// Um negative Modulo zu umgehen.
|
|
19
|
+
pos = (((pos - 1) % this.maxSize) + this.maxSize) % this.maxSize;
|
|
20
|
+
amount--;
|
|
21
|
+
}
|
|
22
|
+
return result;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { ServerLogService } from '../log-service';
|
|
2
|
+
import { LogLevel } from '../../../models/logLevel';
|
|
3
|
+
|
|
4
|
+
export class Utils {
|
|
5
|
+
public static guardedFunction(func: (...args: unknown[]) => void, thisContext: unknown | undefined): void {
|
|
6
|
+
try {
|
|
7
|
+
if (thisContext) {
|
|
8
|
+
func.bind(thisContext)();
|
|
9
|
+
} else {
|
|
10
|
+
func();
|
|
11
|
+
}
|
|
12
|
+
} catch (e) {
|
|
13
|
+
ServerLogService.writeLog(LogLevel.Error, `Guarded Function failed: ${e.message}\n Stack: ${e.stack}`);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
public static nowMS(): number {
|
|
18
|
+
return new Date().getTime();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
public static guardedNewThread(func: (...args: unknown[]) => void, thisContext?: unknown | undefined): void {
|
|
22
|
+
Utils.guardedTimeout(func, 1, thisContext);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
public static guardedTimeout(
|
|
26
|
+
func: (...args: unknown[]) => void,
|
|
27
|
+
time: number,
|
|
28
|
+
thisContext?: unknown | undefined,
|
|
29
|
+
): NodeJS.Timeout {
|
|
30
|
+
return setTimeout(() => {
|
|
31
|
+
Utils.guardedFunction(func, thisContext);
|
|
32
|
+
}, time);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
public static guardedInterval(
|
|
36
|
+
func: (...args: unknown[]) => void,
|
|
37
|
+
time: number,
|
|
38
|
+
thisContext?: unknown | undefined,
|
|
39
|
+
fireImmediate: boolean = false,
|
|
40
|
+
): NodeJS.Timeout {
|
|
41
|
+
if (fireImmediate) {
|
|
42
|
+
Utils.guardedFunction(func, thisContext);
|
|
43
|
+
}
|
|
44
|
+
return setInterval(() => {
|
|
45
|
+
Utils.guardedFunction(func, thisContext);
|
|
46
|
+
}, time);
|
|
47
|
+
}
|
|
48
|
+
public static nowString(): string {
|
|
49
|
+
const d: Date = new Date();
|
|
50
|
+
return `${d.toLocaleTimeString('de-DE')}.${d.getMilliseconds()}`;
|
|
51
|
+
}
|
|
52
|
+
}
|