@scrypted/amcrest
Advanced tools
Comparing version 0.0.76 to 0.0.77
{ | ||
"name": "@scrypted/amcrest", | ||
"version": "0.0.76", | ||
"version": "0.0.77", | ||
"description": "Amcrest Plugin for Scrypted", | ||
@@ -5,0 +5,0 @@ "author": "Scrypted", |
# Amcrest Plugin | ||
The Amcrest Plugin brings Amcrest-branded cameras, doorbells or NVR devices that are IP-based into Scrypted. | ||
Most commonly this plugin is used with 2 plugins: Rebroadcast and HomeKit. | ||
## npm commands | ||
* npm run scrypted-webpack | ||
* npm run scrypted-deploy <ipaddress> | ||
* npm run scrypted-debug <ipaddress> | ||
Device must have built-in motion detection (most Amcrest cameras or NVR's have this). | ||
If the camera or NVR do not have motion detection, you will have to use a separate plugin or device to achieve this (e.g., `dummy-switch`) and group it to the camera. | ||
## scrypted distribution via npm | ||
1. Ensure package.json is set up properly for publishing on npm. | ||
2. npm publish | ||
## Amcrest Camera Codec Settings for HomeKit Compatibility | ||
Configure optimal code settings (as required by HomeKit) using Amcrest configuration (not Scrypted). | ||
## Visual Studio Code configuration | ||
You may use the device's webpage access or one of the following applications: `Amcrest Smart Home` (mobile), `IP Config Software`, or `Amcrest Surveillance Pro` (https://support.amcrest.com/hc/en-us/categories/201939038-All-Downloads). | ||
**NOTE:** Amcrest Smart Home app may not expose all codec or stream settings. Use one of the other applications instead. | ||
* If using a remote server, edit [.vscode/settings.json](blob/master/.vscode/settings.json) to specify the IP Address of the Scrypted server. | ||
* Launch Scrypted Debugger from the launch menu. | ||
The optimal/reliable codec settings can be found in the documentation for the [Homekit Plugin](https://github.com/koush/scrypted/tree/main/plugins/homekit). | ||
## Amcrest Doorbells (e.g. AD110 and AD410) | ||
At this time, 2-way audio works for AD110 and not AD410. | ||
* Specify `Type` is `Doorbell` | ||
* `Username` admin | ||
* `Password` (see below) | ||
* `Default Stream` set to properly configured video codec stream (Main Stream = `Stream 1`; Sub Stream 1 = `Stream 2`; Sub Stream 2 = `Stream 3`; and so on) | ||
* `Amcrest Doorbell` is `checked` | ||
The `admin` user account credentials is required to (1) add doorbell to Scrypted or (2) change codec settings with `IP Config Software` or `Amcrest Surveillance Pro` applications. | ||
The password for `admin` username was set when first configuring device (see 2m49s mark of https://youtu.be/8RDgBMfIhgo). | ||
The `admin` username credential is **not** your Amcrest Smart Home (cloud) account that uses an email address for user/login. | ||
(Unless you happened used the same password for both.) | ||
## Amcrest NVR | ||
Cameras attached or recording through an Amcrest NVR (IP-based) can be used in Amcrest Plugin for Scrypted. | ||
Each 'Channel' or (camera) Device attached to the NVR must be configured as separate Device in Amcrest plugin. | ||
**NOTE:** Snapshots may be inconsistent if using an NVR. A workaround exists if you can access your camera on network without going through NVR (see below `Snapshot URL Override`). If you can only access your camera through an NVR, then snapshots may not be supported. | ||
* `IP Address` NVR's IP Address | ||
* `Snapshot URL Override` camera's IP address (preferred) or specific port number of NVR for that camera (may work). That is: `http://<camera ip address>/cgi-bin/snapshot.cgi` or `http://<NVR ip address>:<NVR port # for camera>/cgi-bin/snapshot.cgi` | ||
* `Channel Number Override` camera's channel number as known to DVR | ||
* `Default Stream` Properly configured video codec stream (Main Stream = `Stream 1`; Sub Stream 1 = `Stream 2`; Sub Stream 2 = `Stream 3`; and so on) | ||
# Troubleshooting | ||
## General | ||
* Is the URL attempting to use HTTPS? Try disabling HTTPS on the device to see if that resolves issue (do not use self-signed certs). | ||
* Does your account (`Username`) have proper permissions ("Authority" in Amcrest speak)? Try granting all Authority for testing. See below `User Account Authority (Camera or NVR)`. | ||
* Amcrest Doorbell: `Username` is **admin** and `Password` is the device/camera password -- not Amcrest Smart Home (Cloud) account password. | ||
* Check that you have specified the correct `Default Stream` number in device (in Scrypted). | ||
* Check that you have configured the correct Stream number's codec settings (in Amcrest admin page (Main Stream or Sub Stream(s)). | ||
## User Account Authority (Camera or NVR) | ||
If you have a non-admin user account setup on your cameras and/or Amcrest NVR, then the account's access permissions must be sufficient to expose motion events and playback. | ||
The following is known to work (and are likely over permissive), but your specific camera model and firmware may be different: | ||
* Camera user Group Authority: `Live`, `Playback`, `Storage`, `Event` | ||
* NVR user Group Authority: `Camera`, `Storage`, `Event Management` |
@@ -17,2 +17,5 @@ import AxiosDigestAuth from '@koush/axios-digest-auth'; | ||
PhoneCallDetectStop = "Code=PhoneCallDetect;action=Stop", | ||
DahuaTalkInvite = "Code=CallNoAnswered;action=Start", | ||
DahuaTalkHangup = "Code=PassiveHungup;action=Start", | ||
DahuaTalkPulse = "Code=_CallNoAnswer_;action=Pulse", | ||
} | ||
@@ -19,0 +22,0 @@ |
106
src/main.ts
@@ -1,3 +0,3 @@ | ||
import sdk, { MediaObject, Camera, ScryptedInterface, Setting, ScryptedDeviceType, Intercom, FFMpegInput, ScryptedMimeTypes, PictureOptions } from "@scrypted/sdk"; | ||
import { Stream } from "stream"; | ||
import sdk, { MediaObject, Camera, ScryptedInterface, Setting, ScryptedDeviceType, Intercom, FFMpegInput, ScryptedMimeTypes, PictureOptions, VideoCameraConfiguration, MediaStreamOptions } from "@scrypted/sdk"; | ||
import { Stream, PassThrough } from "stream"; | ||
import { AmcrestCameraClient, AmcrestEvent, amcrestHttpsAgent } from "./amcrest-api"; | ||
@@ -10,6 +10,10 @@ import { RtspSmartCamera, RtspProvider, Destroyable, UrlMediaStreamOptions } from "../../rtsp/src/rtsp"; | ||
import { listenZero } from "../../../common/src/listen-cluster"; | ||
import { readLength } from "../../../common/src/read-length"; | ||
const { mediaManager } = sdk; | ||
class AmcrestCamera extends RtspSmartCamera implements Camera, Intercom { | ||
const AMCREST_DOORBELL_TYPE = 'Amcrest Doorbell'; | ||
const DAHUA_DOORBELL_TYPE = 'Dahua Doorbell'; | ||
class AmcrestCamera extends RtspSmartCamera implements VideoCameraConfiguration, Camera, Intercom { | ||
eventStream: Stream; | ||
@@ -20,2 +24,22 @@ cp: ChildProcess; | ||
constructor(nativeId: string, provider: RtspProvider) { | ||
super(nativeId, provider); | ||
if (this.storage.getItem('amcrestDoorbell') === 'true') { | ||
this.storage.setItem('doorbellType', AMCREST_DOORBELL_TYPE); | ||
this.storage.removeItem('amcrestDoorbell'); | ||
} | ||
} | ||
async setVideoStreamOptions(options: MediaStreamOptions): Promise<void> { | ||
let bitrate = options?.video?.bitrate; | ||
if (!bitrate) | ||
return; | ||
bitrate = Math.round(bitrate / 1000); | ||
// what is Encode[0]? Is that the camera number? | ||
const response = await this.getClient().digestAuth.request({ | ||
url: `http://${this.getHttpAddress()}/cgi-bin/configManager.cgi?action=setConfig&Encode[0].MainFormat[${this.getChannelFromMediaStreamOptionsId(options.id)}].Video.BitRate=${bitrate}` | ||
}); | ||
this.console.log('reconfigure result', response.data); | ||
} | ||
getClient() { | ||
@@ -35,2 +59,4 @@ if (!this.client) | ||
const events = await client.listenEvents(); | ||
const doorbellType = this.storage.getItem('doorbellType'); | ||
ret.destroy = () => { | ||
@@ -70,3 +96,3 @@ events.removeAllListeners(); | ||
|| event === AmcrestEvent.PhoneCallDetectStart | ||
|| event === AmcrestEvent.AlarmIPCStart) { | ||
|| event === AmcrestEvent.AlarmIPCStart || event === AmcrestEvent.DahuaTalkInvite) { | ||
this.binaryState = true; | ||
@@ -76,10 +102,15 @@ } | ||
|| event === AmcrestEvent.PhoneCallDetectStop | ||
|| event === AmcrestEvent.AlarmIPCStop) { | ||
|| event === AmcrestEvent.AlarmIPCStop || event === AmcrestEvent.DahuaTalkHangup) { | ||
this.binaryState = false; | ||
} | ||
else if (event === AmcrestEvent.TalkPulse) { | ||
else if (event === AmcrestEvent.TalkPulse && doorbellType === AMCREST_DOORBELL_TYPE) { | ||
clearTimeout(pulseTimeout); | ||
pulseTimeout = setTimeout(() => this.binaryState = false, 30000); | ||
pulseTimeout = setTimeout(() => this.binaryState = false, 3000); | ||
this.binaryState = true; | ||
} | ||
else if (event === AmcrestEvent.DahuaTalkPulse && doorbellType === DAHUA_DOORBELL_TYPE) { | ||
clearTimeout(pulseTimeout); | ||
pulseTimeout = setTimeout(() => this.binaryState = false, 3000); | ||
this.binaryState = true; | ||
} | ||
}) | ||
@@ -97,8 +128,12 @@ } | ||
{ | ||
title: 'Amcrest Doorbell', | ||
type: 'boolean', | ||
description: "Enable if this device is an Amcrest Doorbell.", | ||
key: "amcrestDoorbell", | ||
value: (!!this.providedInterfaces?.includes(ScryptedInterface.BinarySensor)).toString(), | ||
} | ||
title: 'Doorbell Type', | ||
choices: [ | ||
'Not a Doorbell', | ||
AMCREST_DOORBELL_TYPE, | ||
DAHUA_DOORBELL_TYPE, | ||
], | ||
description: 'If this device is a doorbell, select the appropriate doorbell type.', | ||
value: this.storage.getItem('doorbellType'), | ||
key: 'doorbellType', | ||
}, | ||
]; | ||
@@ -127,3 +162,3 @@ } | ||
} | ||
async getConstructedVideoStreamOptions(): Promise<UrlMediaStreamOptions[]> { | ||
@@ -155,7 +190,7 @@ let mas = this.maxExtraStreams; | ||
if (key !== 'amcrestDoorbell') | ||
return super.putSetting(key, value); | ||
const doorbellType = this.storage.getItem('doorbellType'); | ||
const isDoorbell = doorbellType === AMCREST_DOORBELL_TYPE || doorbellType === DAHUA_DOORBELL_TYPE; | ||
super.putSetting(key, value); | ||
this.storage.setItem(key, value); | ||
if (value === 'true') | ||
if (isDoorbell) | ||
provider.updateDevice(this.nativeId, this.name, [...provider.getInterfaces(), ScryptedInterface.BinarySensor, ScryptedInterface.Intercom], ScryptedDeviceType.Doorbell); | ||
@@ -183,13 +218,21 @@ else | ||
// seems the dahua doorbells preferred 1024 chunks. should investigate adts | ||
// parsing and sending multipart chunks instead. | ||
const passthrough = new PassThrough(); | ||
this.getClient().digestAuth.request({ | ||
method: 'POST', | ||
url, | ||
headers: { | ||
'Content-Type': 'Audio/AAC', | ||
'Content-Length': '9999999' | ||
}, | ||
httpsAgent: amcrestHttpsAgent, | ||
data: passthrough, | ||
}); | ||
try { | ||
await this.getClient().digestAuth.request({ | ||
method: 'POST', | ||
url, | ||
headers: { | ||
'Content-Type': 'Audio/AAC', | ||
'Content-Length': '9999999' | ||
}, | ||
httpsAgent: amcrestHttpsAgent, | ||
data: socket | ||
}); | ||
while (true) { | ||
const data = await readLength(socket, 1024); | ||
passthrough.push(data); | ||
} | ||
} | ||
@@ -199,2 +242,6 @@ catch (e) { | ||
} | ||
finally { | ||
passthrough.end(); | ||
} | ||
this.cp.kill(); | ||
@@ -215,3 +262,3 @@ }); | ||
this.cp = child_process.spawn(ffmpeg, args); | ||
this.cp.on('killed', () => this.cp = undefined); | ||
this.cp.on('exit', () => this.cp = undefined); | ||
ffmpegLogInitialOutput(this.console, this.cp); | ||
@@ -233,2 +280,3 @@ } | ||
return [ | ||
ScryptedInterface.VideoCameraConfiguration, | ||
ScryptedInterface.Camera, | ||
@@ -235,0 +283,0 @@ ScryptedInterface.AudioSensor, |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 2 instances in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 2 instances in 1 package
258896
757
58