Support v2 UI for sip and bticino plugins (#1521)

* Fix an undefined error that might occur when sip debug is off
Slight cleanup
Add support for v2 UI

* Decouple voicemail lock device from camera device and only add it for c300x models (c100x doesn't have voicemail)
Add support for v2 UI
Allow changing devaddr setting from UI

* Fix an undefined error that might occur when sip debug is off
Slight cleanup
Add support for v2 UI
This commit is contained in:
slyoldfox
2024-07-10 18:31:26 +02:00
committed by GitHub
parent c81cdd0df1
commit 3ca6841ea2
11 changed files with 5816 additions and 1972 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "@scrypted/bticino",
"version": "0.0.16",
"version": "0.0.17",
"scripts": {
"scrypted-setup-project": "scrypted-setup-project",
"prescrypted-setup-project": "scrypted-package-json",
@@ -23,6 +23,8 @@
"name": "BTicino SIP Plugin",
"type": "DeviceProvider",
"interfaces": [
"ScryptedSystemDevice",
"ScryptedDeviceCreator",
"DeviceProvider",
"DeviceCreator"
],
@@ -32,14 +34,14 @@
]
},
"dependencies": {
"@scrypted/common": "file:../../common",
"@scrypted/sdk": "file:../../sdk",
"@slyoldfox/sip": "^0.0.6-1",
"sdp": "^3.0.3",
"stun": "^2.1.0"
},
"devDependencies": {
"@scrypted/common": "file:../../common",
"@scrypted/sdk": "file:../../sdk",
"@types/node": "^16.9.6",
"@types/node": "^20.11.30",
"cross-env": "^7.0.3",
"ts-node": "^10.9.1"
}

View File

@@ -4,9 +4,12 @@ import { VoicemailHandler } from "./bticino-voicemailHandler";
export class BticinoAswmSwitch extends ScryptedDeviceBase implements OnOff, HttpRequestHandler {
private timeout : NodeJS.Timeout
private voicemailHandler : VoicemailHandler
constructor(private camera: BticinoSipCamera, private voicemailHandler : VoicemailHandler) {
constructor(private camera: BticinoSipCamera) {
super( camera.nativeId + "-aswm-switch")
this.voicemailHandler = new VoicemailHandler(camera)
camera.requestHandlers.add(this.voicemailHandler)
this.timeout = setTimeout( () => this.syncStatus() , 5000 )
}
@@ -29,6 +32,7 @@ export class BticinoAswmSwitch extends ScryptedDeviceBase implements OnOff, Http
if( this.timeout ) {
clearTimeout(this.timeout)
}
this.voicemailHandler?.cancelTimer()
}
public async onRequest(request: HttpRequest, response: HttpResponse): Promise<void> {

View File

@@ -29,7 +29,6 @@ import { ControllerApi } from './c300x-controller-api';
import { BticinoAswmSwitch } from './bticino-aswm-switch';
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');
@@ -42,7 +41,6 @@ export class BticinoSipCamera extends ScryptedDeviceBase implements MotionSensor
public requestHandlers: CompositeSipMessageHandler = new CompositeSipMessageHandler()
public incomingCallRequest : SipRequest
private settingsStorage: BticinoStorageSettings = new BticinoStorageSettings( this )
private voicemailHandler : VoicemailHandler = new VoicemailHandler(this)
private inviteHandler : InviteHandler = new InviteHandler(this)
private controllerApi : ControllerApi = new ControllerApi(this)
private muteSwitch : BticinoMuteSwitch
@@ -60,7 +58,7 @@ export class BticinoSipCamera extends ScryptedDeviceBase implements MotionSensor
constructor(nativeId: string, public provider: BticinoSipPlugin) {
super(nativeId)
this.requestHandlers.add( this.voicemailHandler ).add( this.inviteHandler )
this.requestHandlers.add( this.inviteHandler )
this.persistentSipManager = new PersistentSipManager( this );
(async() => {
this.doorbellWebhookUrl = await this.doorbellWebhookEndpoint()
@@ -349,19 +347,13 @@ export class BticinoSipCamera extends ScryptedDeviceBase implements MotionSensor
this.forwarder = undefined
}
resetStreamTimeout() {
this.log.d('starting/refreshing stream')
clearTimeout(this.refreshTimeout)
this.refreshTimeout = setTimeout(() => this.stopSession(), STREAM_TIMEOUT)
}
hasActiveCall() {
return this.session;
}
stopSession() {
if (this.session) {
this.log.d('ending sip session')
this.console.log('ending sip session')
this.session.stop()
this.session = undefined
}
@@ -406,7 +398,7 @@ export class BticinoSipCamera extends ScryptedDeviceBase implements MotionSensor
if (this.session === sip)
this.session = undefined
try {
this.log.d('cleanup(): stopping sip session.')
this.console.log('cleanup(): stopping sip session.')
sip?.stop()
this.currentMediaObject = undefined
}
@@ -617,7 +609,7 @@ export class BticinoSipCamera extends ScryptedDeviceBase implements MotionSensor
async getDevice(nativeId: string) : Promise<any> {
if( nativeId && nativeId.endsWith('-aswm-switch')) {
this.aswmSwitch = new BticinoAswmSwitch(this, this.voicemailHandler)
this.aswmSwitch = new BticinoAswmSwitch(this)
return this.aswmSwitch
} else if( nativeId && nativeId.endsWith('-mute-switch') ) {
this.muteSwitch = new BticinoMuteSwitch(this)
@@ -633,7 +625,6 @@ export class BticinoSipCamera extends ScryptedDeviceBase implements MotionSensor
this.muteSwitch.cancelTimer()
} else {
this.stopIntercom()
this.voicemailHandler.cancelTimer()
this.persistentSipManager.cancelTimer()
this.controllerApi.cancelTimer()
}

View File

@@ -51,6 +51,9 @@ export class ControllerApi {
res.on("end", () => {
try {
let parsedBody = JSON.parse( body )
if( !parsedBody["model"] ) {
reject( new Error("Cannot determine model, update your c300x-controller.") )
}
if( parsedBody["errors"].length > 0 ) {
reject( new Error( parsedBody["errors"][0] ) )
} else {

View File

@@ -9,6 +9,13 @@ export class BticinoSipPlugin extends ScryptedDeviceBase implements DeviceProvid
devices = new Map<string, BticinoSipCamera>()
constructor() {
super();
this.systemDevice = {
deviceCreator: 'Bticino Doorbell',
};
}
async getCreateDeviceSettings(): Promise<Setting[]> {
return [
{
@@ -76,17 +83,18 @@ export class BticinoSipPlugin extends ScryptedDeviceBase implements DeviceProvid
name: name + ' Muted',
type: ScryptedDeviceType.Switch,
interfaces: [ScryptedInterface.OnOff, ScryptedInterface.HttpRequestHandler],
}
}
const devices = setupData["model"] === 'c100x' ? [lockDevice, muteSwitchDevice] : [lockDevice, aswmSwitchDevice, muteSwitchDevice]
await deviceManager.onDevicesChanged({
providerNativeId: nativeId,
devices: [lockDevice, aswmSwitchDevice, muteSwitchDevice],
devices: devices
})
let sipCamera : BticinoSipCamera = await this.getDevice(nativeId)
sipCamera.putSetting("sipfrom", "scrypted-" + sipCamera.id + "@127.0.0.1")
sipCamera.putSetting("sipto", "c300x@" + setupData["ipAddress"] )
sipCamera.putSetting("sipto", setupData["model"] + "@" + setupData["ipAddress"] )
sipCamera.putSetting("sipdomain", setupData["domain"])
sipCamera.putSetting("sipdebug", true )

View File

@@ -49,11 +49,23 @@ export class BticinoStorageSettings {
description: 'Enable SIP debugging',
placeholder: 'true or false',
},
DEVADDR: {
title: 'Device address (DEVADDR)',
type: 'string',
description: 'Only specify if this is different than 20. For c100x this is a UUID, see: tcpdump -i lo port 5060',
defaultValue: '20',
placeholder: '20',
},
notifyVoicemail: {
title: 'Notify on new voicemail messages',
type: 'boolean',
description: 'Enable voicemail alerts',
placeholder: 'true or false',
onGet: async () => {
return {
hide: this.storageSettings.values.sipto.indexOf('c100x') == 0,
}
}
},
doorbellWebhookUrl: {
title: 'Doorbell Sensor Webhook',

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "@scrypted/sip",
"version": "0.0.10",
"version": "0.0.11",
"scripts": {
"scrypted-setup-project": "scrypted-setup-project",
"prescrypted-setup-project": "scrypted-package-json",
@@ -23,6 +23,8 @@
"name": "SIP Plugin",
"type": "DeviceProvider",
"interfaces": [
"ScryptedSystemDevice",
"ScryptedDeviceCreator",
"DeviceProvider",
"DeviceCreator"
],
@@ -32,6 +34,8 @@
]
},
"dependencies": {
"@scrypted/common": "file:../../common",
"@scrypted/sdk": "file:../../sdk",
"@slyoldfox/sip": "^0.0.6-1",
"pick-port": "^1.0.0",
"rxjs": "^7.8.1",
@@ -39,9 +43,7 @@
"stun": "^2.1.0"
},
"devDependencies": {
"@scrypted/common": "file:../../common",
"@scrypted/sdk": "file:../../sdk",
"@types/node": "^16.9.6",
"@types/node": "^20.11.30",
"cross-env": "^7.0.3"
}
}

View File

@@ -1,6 +1,6 @@
import { closeQuiet, createBindZero, listenZero } from '@scrypted/common/src/listen-cluster';
import { StorageSettings } from '@scrypted/sdk/storage-settings';
import sdk, { BinarySensor, Camera, Device, DeviceProvider, DeviceCreator, DeviceCreatorSettings, FFmpegInput, Intercom, MediaObject, MediaStreamUrl, PictureOptions, RequestMediaStreamOptions, RequestPictureOptions, ResponseMediaStreamOptions, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, ScryptedMimeTypes, Setting, Settings, SettingValue, VideoCamera } from '@scrypted/sdk';
import sdk, { BinarySensor, Camera, DeviceProvider, DeviceCreator, DeviceCreatorSettings, FFmpegInput, Intercom, MediaObject, PictureOptions, ResponseMediaStreamOptions, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, ScryptedMimeTypes, Setting, Settings, SettingValue, VideoCamera } from '@scrypted/sdk';
import child_process, { ChildProcess } from 'child_process';
import { ffmpegLogInitialOutput, safePrintFFmpegArguments } from "@scrypted/common/src/media-helpers";
import dgram from 'dgram';
@@ -430,7 +430,9 @@ export class SipCamProvider extends ScryptedDeviceBase implements DeviceProvider
constructor(nativeId?: string) {
super(nativeId);
this.systemDevice = {
deviceCreator: 'SIP Camera',
};
for (const camId of deviceManager.getNativeIds()) {
if (camId)
this.getDevice(camId);

View File

@@ -232,7 +232,8 @@ export class SipManager {
}
} else if( m.method == 'ACK' || m.method == 'BYE' ) {
m.headers.to.uri = toWithDomain
m.uri = this.registrarContact
if(this.registrarContact)
m.uri = this.registrarContact
} else if( (m.method == undefined && m.status) && m.headers.cseq ) {
if( m.status == '200' ) {
// Response on invite