mirror of
https://github.com/koush/scrypted.git
synced 2026-05-26 22:50:30 +01:00
Fixes handling the welcome message status from bticino (#1432)
* Fix voicemail status by parsing the VSC more accurately * Implement video clip streaming by fetching and converting the voicemail message
This commit is contained in:
26
plugins/bticino/package-lock.json
generated
26
plugins/bticino/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@scrypted/bticino",
|
||||
"version": "0.0.13",
|
||||
"version": "0.0.15",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/bticino",
|
||||
"version": "0.0.13",
|
||||
"version": "0.0.15",
|
||||
"dependencies": {
|
||||
"@slyoldfox/sip": "^0.0.6-1",
|
||||
"sdp": "^3.0.3",
|
||||
@@ -30,23 +30,23 @@
|
||||
"dependencies": {
|
||||
"@scrypted/sdk": "file:../sdk",
|
||||
"@scrypted/server": "file:../server",
|
||||
"http-auth-utils": "^3.0.2",
|
||||
"node-fetch-commonjs": "^3.1.1",
|
||||
"typescript": "^4.4.3"
|
||||
"http-auth-utils": "^5.0.1",
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^16.9.0"
|
||||
"@types/node": "^20.11.0",
|
||||
"ts-node": "^10.9.2"
|
||||
}
|
||||
},
|
||||
"../../sdk": {
|
||||
"name": "@scrypted/sdk",
|
||||
"version": "0.3.2",
|
||||
"version": "0.3.14",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@babel/preset-typescript": "^7.18.6",
|
||||
"adm-zip": "^0.4.13",
|
||||
"axios": "^0.21.4",
|
||||
"axios": "^1.6.5",
|
||||
"babel-loader": "^9.1.0",
|
||||
"babel-plugin-const-enum": "^1.1.0",
|
||||
"esbuild": "^0.15.9",
|
||||
@@ -1219,10 +1219,10 @@
|
||||
"requires": {
|
||||
"@scrypted/sdk": "file:../sdk",
|
||||
"@scrypted/server": "file:../server",
|
||||
"@types/node": "^16.9.0",
|
||||
"http-auth-utils": "^3.0.2",
|
||||
"node-fetch-commonjs": "^3.1.1",
|
||||
"typescript": "^4.4.3"
|
||||
"@types/node": "^20.11.0",
|
||||
"http-auth-utils": "^5.0.1",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.3.3"
|
||||
}
|
||||
},
|
||||
"@scrypted/sdk": {
|
||||
@@ -1232,7 +1232,7 @@
|
||||
"@types/node": "^18.11.18",
|
||||
"@types/stringify-object": "^4.0.0",
|
||||
"adm-zip": "^0.4.13",
|
||||
"axios": "^0.21.4",
|
||||
"axios": "^1.6.5",
|
||||
"babel-loader": "^9.1.0",
|
||||
"babel-plugin-const-enum": "^1.1.0",
|
||||
"esbuild": "^0.15.9",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@scrypted/bticino",
|
||||
"version": "0.0.13",
|
||||
"version": "0.0.15",
|
||||
"scripts": {
|
||||
"scrypted-setup-project": "scrypted-setup-project",
|
||||
"prescrypted-setup-project": "scrypted-package-json",
|
||||
|
||||
@@ -17,6 +17,12 @@ import { ffmpegLogInitialOutput, safeKillFFmpeg, safePrintFFmpegArguments } from
|
||||
import { PersistentSipManager } from './persistent-sip-manager';
|
||||
import { InviteHandler } from './bticino-inviteHandler';
|
||||
import { SipOptions, SipRequest } from '../../sip/src/sip-manager';
|
||||
import fs from "fs"
|
||||
import url from "url"
|
||||
import path from 'path';
|
||||
import { default as stream } from 'node:stream'
|
||||
import type { ReadableStream } from 'node:stream/web'
|
||||
import { finished } from "stream/promises";
|
||||
|
||||
import { get } from 'http'
|
||||
import { ControllerApi } from './c300x-controller-api';
|
||||
@@ -25,6 +31,7 @@ import { BticinoMuteSwitch } from './bticino-mute-switch';
|
||||
|
||||
const STREAM_TIMEOUT = 65000;
|
||||
const { mediaManager } = sdk;
|
||||
const BTICINO_CLIPS = path.join(process.env.SCRYPTED_PLUGIN_VOLUME, 'bticino-clips');
|
||||
|
||||
export class BticinoSipCamera extends ScryptedDeviceBase implements MotionSensor, DeviceProvider, Intercom, Camera, VideoCamera, Settings, BinarySensor, HttpRequestHandler, VideoClips, Reboot {
|
||||
|
||||
@@ -147,11 +154,87 @@ export class BticinoSipCamera extends ScryptedDeviceBase implements MotionSensor
|
||||
});
|
||||
}
|
||||
|
||||
getVideoClip(videoId: string): Promise<MediaObject> {
|
||||
let c300x = SipHelper.getIntercomIp(this)
|
||||
const url = `http://${c300x}:8080/voicemail?msg=${videoId}/aswm.avi&raw=true`;
|
||||
return mediaManager.createMediaObjectFromUrl(url);
|
||||
async getVideoClip(videoId: string): Promise<MediaObject> {
|
||||
const outputfile = await this.fetchAndConvertVoicemailMessage(videoId);
|
||||
|
||||
const fileURLToPath: string = url.pathToFileURL(outputfile).toString()
|
||||
this.console.log(`Creating mediaObject for url: ${fileURLToPath}`)
|
||||
return await mediaManager.createMediaObjectFromUrl(fileURLToPath);
|
||||
}
|
||||
|
||||
private async fetchAndConvertVoicemailMessage(videoId: string) {
|
||||
let c300x = SipHelper.getIntercomIp(this)
|
||||
|
||||
const response = await fetch(`http://${c300x}:8080/voicemail?msg=${videoId}/aswm.avi&raw=true`);
|
||||
|
||||
const contentLength: number = Number(response.headers.get("Content-Length"));
|
||||
const lastModified: Date = new Date(response.headers.get("Last-Modified-Time"));
|
||||
|
||||
const avifile = `${BTICINO_CLIPS}/${videoId}.avi`;
|
||||
const outputfile = `${BTICINO_CLIPS}/${videoId}.mp4`;
|
||||
|
||||
if (!fs.existsSync(BTICINO_CLIPS)) {
|
||||
this.console.log(`Creating clips dir at: ${BTICINO_CLIPS}`)
|
||||
fs.mkdirSync(BTICINO_CLIPS);
|
||||
}
|
||||
|
||||
if (fs.existsSync(avifile)) {
|
||||
const stat = fs.statSync(avifile);
|
||||
if (stat.size != contentLength || stat.mtime.getTime() != lastModified.getTime()) {
|
||||
this.console.log(`Size ${stat.size} != ${contentLength} or time ${stat.mtime.getTime} != ${lastModified.getTime}`)
|
||||
try {
|
||||
fs.rmSync(avifile);
|
||||
} catch (e) { }
|
||||
try {
|
||||
fs.rmSync(outputfile);
|
||||
} catch (e) { }
|
||||
} else {
|
||||
this.console.log(`Keeping the cached video at ${avifile}`)
|
||||
}
|
||||
}
|
||||
|
||||
if (!fs.existsSync(avifile)) {
|
||||
this.console.log("Starting download.")
|
||||
await finished(stream.Readable.from(response.body as ReadableStream<Uint8Array>).pipe(fs.createWriteStream(avifile)));
|
||||
this.console.log("Download finished.")
|
||||
try {
|
||||
this.console.log(`Setting mtime to ${lastModified}`)
|
||||
fs.utimesSync(avifile, lastModified, lastModified);
|
||||
} catch (e) { }
|
||||
}
|
||||
|
||||
const ffmpegPath = await mediaManager.getFFmpegPath();
|
||||
const ffmpegArgs = [
|
||||
'-hide_banner',
|
||||
'-nostats',
|
||||
'-y',
|
||||
'-i', avifile,
|
||||
outputfile
|
||||
];
|
||||
|
||||
safePrintFFmpegArguments(console, ffmpegArgs);
|
||||
const cp = child_process.spawn(ffmpegPath, ffmpegArgs, {
|
||||
stdio: ['pipe', 'pipe', 'pipe', 'pipe'],
|
||||
});
|
||||
|
||||
const p = new Promise((resolveFunc) => {
|
||||
cp.stdout.on("data", (x) => {
|
||||
this.console.log(x.toString());
|
||||
});
|
||||
cp.stderr.on("data", (x) => {
|
||||
this.console.error(x.toString());
|
||||
});
|
||||
cp.on("exit", (code) => {
|
||||
resolveFunc(code);
|
||||
});
|
||||
});
|
||||
|
||||
let returnCode = await p;
|
||||
|
||||
this.console.log(`Converted file returned code: ${returnCode}`);
|
||||
return outputfile;
|
||||
}
|
||||
|
||||
getVideoClipThumbnail(thumbnailId: string): Promise<MediaObject> {
|
||||
let c300x = SipHelper.getIntercomIp(this)
|
||||
const url = `http://${c300x}:8080/voicemail?msg=${thumbnailId}/aswm.jpg&raw=true`;
|
||||
|
||||
@@ -33,8 +33,10 @@ export class VoicemailHandler extends SipRequestHandler {
|
||||
handle(request: SipRequest) {
|
||||
const lastVoicemailMessageTimestamp : number = Number.parseInt( this.sipCamera.storage.getItem('lastVoicemailMessageTimestamp') ) || -1
|
||||
const message : string = request.content.toString()
|
||||
if( message.startsWith('*#8**40*0*0*') || message.startsWith('*#8**40*1*0*') ) {
|
||||
this.aswmIsEnabled = message.startsWith('*#8**40*1*0*');
|
||||
let matches : Array<RegExpMatchArray> = [...message.matchAll(/\*#8\*\*40\*([01])\*([01])\*/gm)]
|
||||
if( matches && matches.length > 0 && matches[0].length > 0 ) {
|
||||
this.sipCamera.console.debug( "Answering machine state: " + matches[0][1] + " / Welcome message state: " + matches[0][2] );
|
||||
this.aswmIsEnabled = matches[0][1] == '1';
|
||||
if( this.isEnabled() ) {
|
||||
this.sipCamera.console.debug("Handling incoming answering machine reply")
|
||||
const messages : string[] = message.split(';')
|
||||
@@ -60,6 +62,8 @@ export class VoicemailHandler extends SipRequestHandler {
|
||||
this.sipCamera.console.debug("No new messages since: " + lastVoicemailMessageTimestamp + " lastMessage: " + lastMessageTimestamp)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.sipCamera.console.debug("Not handling message: " + message)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user