Skip to content
This repository has been archived by the owner on Sep 24, 2024. It is now read-only.

Commit

Permalink
v0.0.6 files
Browse files Browse the repository at this point in the history
  • Loading branch information
markh authored Jun 24, 2022
1 parent 562e506 commit 3b15b41
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 21 deletions.
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,4 @@ LABEL org.opencontainers.image.title="Nest_accfactory"
LABEL org.opencontainers.image.description="HomeKit integration for Nest devices based on HAP-NodeJS library"
LABEL org.opencontainers.image.url="https://github.com/n0rt0nthec4t/Nest_accfactory"
LABEL org.opencontainers.image.authors="[email protected]"
LABEL org.opencontainers.image.version="v0.0.5"
LABEL org.opencontainers.image.version="v0.0.6"
37 changes: 26 additions & 11 deletions Nest_accfactory.js
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,12 @@ class NestClass extends EventEmitter {
// Load configuration

if (fs.existsSync(configFile) == true) {
var config = JSON.parse(fs.readFileSync(configFile));
try {
var config = JSON.parse(fs.readFileSync(configFile));
} catch (error) {
// Error loading JSON, means config invalid
console.log("Error in JSON file '%s'", configFile);
}

config && Object.entries(config).forEach(([key, value]) => {
// Process configuration items
Expand Down Expand Up @@ -283,7 +288,7 @@ class NestClass extends EventEmitter {
}
if (key == "H264ENCODER" && typeof value == "string") {
if (value.toUpperCase() == "LIBX264") this.config.H264Encoder = VideoCodecs.LIBX264; // Use libx264, software encoder
if (value.toUpperCase() == "H264_OMX") this.config.H264Encoder = VideoCodecs.H264_OMX; // Use the older RPI hardware h264 encoder
if (value.toUpperCase() == "H264_OMX") this.config.H264Encoder = VideoCodecs.H264_OMX; // Use the older RPI hardware h264 encoder
}
if (key == "HKSVPREBUFFER" && typeof value == "number") {
if (value < 1000) value = value * 1000; // If less 1000, assume seconds value passed in, so convert to milliseconds
Expand All @@ -308,7 +313,11 @@ class NestClass extends EventEmitter {
Object.entries(value).forEach(([subKey, value]) => {
if (subKey.toUpperCase() == "EXCLUDE" && typeof value == "boolean" && value == true) this.excludedDevices.push(key); // Push this devices serial number onto our list
if (subKey.toUpperCase() == "HKSV" && typeof value == "boolean") this.extraOptions[key]["HKSV"] = value; // HomeKit Secure Video for this device?
if (subKey.toUpperCase() == "EVEAPP" && typeof value == "boolean") this.extraOptions[key]["EveApp"] = value; // Evehome app integration
if (subKey.toUpperCase() == "EVEAPP" && typeof value == "boolean") this.extraOptions[key]["EveApp"] = value; // Evehome app integration
if (subKey.toUpperCase() == "H264ENCODER" && typeof value == "string") {
if (value.toUpperCase() == "LIBX264") this.extraOptions[key]["H264Encoder"] = VideoCodecs.LIBX264; // Use libx264, software encoder
if (value.toUpperCase() == "H264_OMX") this.extraOptions[key]["H264Encoder"] = VideoCodecs.H264_OMX; // Use the older RPI hardware h264 encoder
}
if (subKey.toUpperCase() == "HKSVPREBUFFER" && typeof value == "number") {
if (value < 1000) value = value * 1000; // If less 1000, assume seconds value passed in, so convert to milliseconds
this.extraOptions[key]["HKSVPreBuffer"] = value; // HKSV pre-buffer sizing for this device
Expand Down Expand Up @@ -1177,7 +1186,7 @@ CameraClass.prototype.handleRecordingStreamRequest = async function *(streamId)
if (this.MotionServices[0].service.getCharacteristic(Characteristic.MotionDetected).value == true) {
// Audio if enabled on doorbell/camera && audio recording configured for HKSV
var includeAudio = (this.nestObject.nestDevices[this.deviceID].audio_enabled == true && this.controller.recordingManagement.recordingManagementService.getCharacteristic(Characteristic.RecordingAudioActive).value == Characteristic.RecordingAudioActive.ENABLE);
var recordCodec = this.nestObject.config.H264Encoder; // Codec to use for H264 encoding
var recordCodec = this.nestObject.nestDevices[this.deviceID].H264Encoder; // Codec to use for H264 encoding

// Build our ffmpeg command string for the video stream
var ffmpeg = "-hide_banner"
Expand Down Expand Up @@ -1436,7 +1445,7 @@ CameraClass.prototype.handleSnapshotRequest = async function(request, callback)
}
if (image.length == 0) {
// Still empty image buffer, so try old method for a direct grab
await axios.get(this.nestObject.nestDevices[this.deviceID].nexus_api_http_server_url + "/get_image?uuid=" + this.nestObject.nestDevices[this.deviceID].camera_uuid, {responseType: "arraybuffer", headers: {"user-agent": USERAGENT, "accept" : "*/*", [this.nestObject.cameraAPI.key] : this.nestObject.cameraAPI.value + this.nestObject.nestCameraToken}, timeout: NESTAPITIMEOUT/*, retry: 3, retryDelay: 2000 */})
await axios.get(this.nestObject.nestDevices[this.deviceID].nexus_api_http_server_url + "/get_image?uuid=" + this.nestObject.nestDevices[this.deviceID].camera_uuid + "&cachebuster=" + Math.floor(new Date() / 1000), {responseType: "arraybuffer", headers: {"user-agent": USERAGENT, "accept" : "*/*", [this.nestObject.cameraAPI.key] : this.nestObject.cameraAPI.value + this.nestObject.nestCameraToken}, timeout: NESTAPITIMEOUT/*, retry: 3, retryDelay: 2000 */})
.then(response => {
if (response.status == 200) {
image = response.data;
Expand Down Expand Up @@ -1518,7 +1527,7 @@ CameraClass.prototype.handleStreamRequest = async function (request, callback) {
delete this.pendingSessions[request.sessionID]; // remove this pending session information

var includeAudio = (this.nestObject.nestDevices[this.deviceID].audio_enabled == true);
var streamCodec = this.nestObject.config.H264Encoder; // Codec to use for H264 encoding
var streamCodec = this.nestObject.nestDevices[this.deviceID].H264Encoder; // Codec to use for H264 encoding

// Build our ffmpeg command string for the video stream
var ffmpeg = "-hide_banner"
Expand Down Expand Up @@ -1609,7 +1618,7 @@ CameraClass.prototype.handleStreamRequest = async function (request, callback) {
if (payloadType == request.audio.pt) {
// Audio payload type from HomeKit should match our payload type for audio
if (message.length > 50) {
// Only send on audio data if we have a longer audio packet. Helps filter background noise?
// Only send on audio data if we have a longer audio packet. (not sure it makes any difference, as under iOS 15 packets are roughly same length)
this.ongoingSessions[request.sessionID].rtpSplitter.send(message, this.ongoingSessions[request.sessionID].audioTalkbackPort);
}
} else {
Expand All @@ -1629,6 +1638,7 @@ CameraClass.prototype.handleStreamRequest = async function (request, callback) {
+ " -map 0:0"
+ " -codec:a " + AudioCodecs.LIBSPEEX
+ " -frames_per_packet 4"
+ " -vad 1" // testing to filter background noise?
+ " -ac 1"
+ " -ar " + request.audio.sample_rate + "k"
+ " -f data pipe:1";
Expand Down Expand Up @@ -1676,17 +1686,16 @@ CameraClass.prototype.handleStreamRequest = async function (request, callback) {
}

case "stop" : {
this.NexusStreamer.stopLiveStream("HK" + request.sessionID);

if (typeof this.ongoingSessions[request.sessionID] == "object") {
this.NexusStreamer.stopLiveStream("HK" + request.sessionID);
this.ongoingSessions[request.sessionID].rtpSplitter && this.ongoingSessions[request.sessionID].rtpSplitter.close();
this.ongoingSessions[request.sessionID].ffmpeg && this.ongoingSessions[request.sessionID].ffmpeg.forEach(ffmpeg => {
ffmpeg && ffmpeg.kill("SIGKILL"); // Kill this ffmpeg process
})
this.controller.forceStopStreamingSession(request.sessionID);
delete this.ongoingSessions[request.sessionID]; // this session has finished
this.nestObject.config.debug.includes(Debugging.NEST) && console.debug(getTimestamp() + " [NEST] Live stream stopped on '%s'", this.nestObject.nestDevices[this.deviceID].mac_address);
}
this.nestObject.config.debug.includes(Debugging.NEST) && console.debug(getTimestamp() + " [NEST] Live stream stopped on '%s'", this.nestObject.nestDevices[this.deviceID].mac_address);
callback();
break;
}
Expand Down Expand Up @@ -1893,6 +1902,11 @@ WeatherClass.prototype.updateHomeKit = function(HomeKitAccessory, deviceData) {
axios.get("https://home.nest.com/api/0.1/weather/forecast/" + deviceData.latitude + "," + deviceData.longitude, {headers: {"user-agent": USERAGENT, timeout: 10000}})
.then(response => {
if (response.status == 200) {
// Testing
if (response.data.now.current_temperature < 0 || response.data.now.current_temperature > 30) {
console.log("[TESTING] weather range")
console.log(response.data.now)
}
if (response.data.now.current_temperature >= MINWEATHERTEMP && response.data.now.current_temperature <= MAXWEATHERTEMP) {
this.TemperatureService.updateCharacteristic(Characteristic.CurrentTemperature, response.data.now.current_temperature);
this.HumidityService.updateCharacteristic(Characteristic.CurrentRelativeHumidity, response.data.now.current_humidity);
Expand Down Expand Up @@ -2507,6 +2521,7 @@ NestClass.prototype.__processNestData = function(nestData) {
// Insert any extra options we've read in from configuration file for this device
this.nestDevices[camera.serial_number].EveApp = this.config.EveApp; // Global config option for EveHome App integration. Gets overriden below for specific doorbell/camera
this.nestDevices[camera.serial_number].HKSV = this.config.HKSV; // Global config option for HomeKit Secure Video. Gets overriden below for specific doorbell/camera
this.nestDevices[camera.serial_number].H264Encoder = this.config.H264Encoder; // Global config option for using H264Encoder. Gets overriden below for specific doorbell/camera
this.nestDevices[camera.serial_number].HKSVPreBuffer = this.config.HKSVPreBuffer; // Global config option for HKSV pre buffering size. Gets overriden below for specific doorbell/camera
this.nestDevices[camera.serial_number].doorbellCooldown = this.config.doorbellCooldown; // Global default for doorbell press cooldown. Gets overriden below for specific doorbell/camera
this.nestDevices[camera.serial_number].motionCooldown = this.config.motionCooldown; // Global default for motion detected cooldown. Gets overriden below for specific doorbell/camera
Expand Down Expand Up @@ -3093,7 +3108,7 @@ function isFfmpegValid(validLibraries) {
return isValid;
}

function getTimestamp () {
function getTimestamp() {
const pad = (n,s=2) => (`${new Array(s).fill(0)}${n}`).slice(-s);
const d = new Date();

Expand Down
53 changes: 44 additions & 9 deletions nexusstreamer.js
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,9 @@ class NexusStreamer {
this.timer = null; // Internal timer handle
this.pingtimer = null; // Ping timer handle
this.sessionID = null; // no session ID yet.. We'll assign a random one when we connect to the nexus stream
this.deviceID = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx".replace(/x/g, () => {
return Math.floor(Math.random() * 16).toString(16).toUpperCase();
}); // Random UUID v4 device ID. We do this once during creation of the object only

this.host = cameraData.direct_nexustalk_host; // Inital host to connect to

Expand Down Expand Up @@ -290,7 +293,7 @@ NexusStreamer.prototype.startLiveStream = function(sessionID, videoStream, audio

if (this.buffer.active == false && this.socket == null) {
// We not doing any buffering and there isnt an active socket connection, so startup connection to nexus
this.debug && console.debug(getTimestamp() + " [NEXUS Starting connection to '%s'", this.camera.direct_nexustalk_host);
this.debug && console.debug(getTimestamp() + " [NEXUS] Starting connection to '%s'", this.camera.direct_nexustalk_host);
this.__connect(this.camera.direct_nexustalk_host);
this.__startNexusData();
}
Expand Down Expand Up @@ -330,7 +333,7 @@ NexusStreamer.prototype.startRecordStream = function(sessionID, ffmpegRecord, vi

if (this.buffer.active == false && this.socket == null) {
// We not doing any buffering and/or there isnt an active socket connection, so startup connection to nexus
this.debug && console.debug(getTimestamp() + " [NEXUS Starting connection to '%s''", this.camera.direct_nexustalk_host);
this.debug && console.debug(getTimestamp() + " [NEXUS] Starting connection to '%s''", this.camera.direct_nexustalk_host);
this.__connect(this.camera.direct_nexustalk_host);
this.__startNexusData();
}
Expand Down Expand Up @@ -362,7 +365,7 @@ NexusStreamer.prototype.stopLiveStream = function(sessionID) {
// Request to stop an active live stream
var index = this.buffer.streams.findIndex(({ type, id }) => type == "live" && id == sessionID);
if (index != -1) {
this.debug && console.debug(getTimestamp() + " [NEXUS] Stopped live stream from '%s'", this.host);
this.debug && console.debug(getTimestamp() + " [NEXUS] Stopped live stream from '%s'", this.host);
this.buffer.streams[index].timeout && clearTimeout(this.buffer.streams[index].timeout); // Clear any active return audio timer
this.buffer.streams.splice(index, 1); // remove this object
}
Expand Down Expand Up @@ -515,11 +518,11 @@ NexusStreamer.prototype.__connect = function(host) {
}

this.playingBack = false; // Playback ended as socket is closed
this.socket = null; // Clear socket object
this.sessionID = null; // Not an active session anymore

if (reconnect == true) {
// Restart connection
this.socket = null; // Clear socket object
this.sessionID = null; // Not an active session anymore
this.__connect(host);
this.__startNexusData();
}
Expand All @@ -545,6 +548,12 @@ NexusStreamer.prototype.__connect = function(host) {
//this.__ffmpegRouter("video", this.camera_connecting_h264_frame);
//this.__ffmpegRouter("audio", AACMONO48000BLANK);
}
if (this.camera_offline_h264_frame && this.socket == null) {
// Seems we cant access the video stream as we have an empty connection, so feed in our custom h264 frame for playback
// We'll use the camera off h264 frame
this.__ffmpegRouter("video", this.camera_offline_h264_frame);
this.__ffmpegRouter("audio", AACMONO48000BLANK);
}
}, (TIMERINTERVAL / 30)); // output at 30 fps?
}

Expand Down Expand Up @@ -703,8 +712,8 @@ NexusStreamer.prototype.__Authenticate = function(reauthorise) {
this.debug && console.debug(getTimestamp() + " [NEXUS] Performing authentication to '%s'", this.host);
helloBuffer.writeVarintField(1, ProtocolVersion.VERSION_3);
helloBuffer.writeStringField(2, this.camera.camera_uuid);
helloBuffer.writeBooleanField(3, false);
helloBuffer.writeStringField(6, this.camera.serial_number);
helloBuffer.writeBooleanField(3, false); // Doesnt required a connect camera
helloBuffer.writeStringField(6, this.deviceID); // Random UUID v4 device ID
helloBuffer.writeStringField(7, USERAGENT);
helloBuffer.writeVarintField(9, ClientType.IOS);
this.__sendMessage(PacketType.HELLO, helloBuffer.finish());
Expand Down Expand Up @@ -868,6 +877,32 @@ NexusStreamer.prototype.__handleNexusError = function(payload) {
}
}

NexusStreamer.prototype.__handleTalkbackBegin = function(payload) {
// Decode talk begin packet
var packet = payload.readFields(function(tag, obj, protoBuf) {
if (tag === 1) obj.user_id = protoBuf.readString();
else if (tag === 2) obj.session_id = protoBuf.readVarint();
else if (tag === 3) obj.quick_action_id = protoBuf.readVarint();
else if (tag === 4) obj.device_id = protoBuf.readString();
}, {user_id: "", session_id: 0, quick_action_id: 0, device_id: ""});

this.debug && console.debug(getTimestamp() + " [NEXUS] Talkback started on '%s'", packet.device_id);
this.talking = true; // Talk back has started
}

NexusStreamer.prototype.__handleTalkbackEnd = function(payload) {
// Decode talk end packet
var packet = payload.readFields(function(tag, obj, protoBuf) {
if (tag === 1) obj.user_id = protoBuf.readString();
else if (tag === 2) obj.session_id = protoBuf.readVarint();
else if (tag === 3) obj.quick_action_id = protoBuf.readVarint();
else if (tag === 4) obj.device_id = protoBuf.readString();
}, {user_id: "", session_id: 0, quick_action_id: 0, device_id: ""});

this.debug && console.debug(getTimestamp() + " [NEXUS] Talkback ended on '%s'", packet.device_id);
this.talking = false; // Talk back has stopped
}

NexusStreamer.prototype.__handleNexusData = function(data) {
// Process the rawdata from our socket connection and convert into nexus packets to take action against
this.pendingBuffer = (this.pendingBuffer == null ? data : Buffer.concat([this.pendingBuffer, data]));
Expand Down Expand Up @@ -920,12 +955,12 @@ NexusStreamer.prototype.__handleNexusData = function(data) {
}

case PacketType.TALKBACK_BEGIN : {
this.talking = true;
this.__handleTalkbackBegin(payload);
break;
}

case PacketType.TALKBACK_END : {
this.talking = true;
this.__handleTalkbackEnd(payload);
break;
}

Expand Down

0 comments on commit 3b15b41

Please sign in to comment.