Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding optional support for ONVIF Motion Events using Webhooks #1107

Closed
jeffreylanters opened this issue Sep 29, 2023 · 3 comments
Closed

Adding optional support for ONVIF Motion Events using Webhooks #1107

jeffreylanters opened this issue Sep 29, 2023 · 3 comments

Comments

@jeffreylanters
Copy link

jeffreylanters commented Sep 29, 2023

In regards to the issue about motion detection not functioning on specific cameras (cough Tapo), I have done some further investigation. I have successfully replicated the problem using my own cameras using the same library which is being used in Scrypted (onvif), using Pullpoint communication. Upon inspecting the Scrypted source code, I observed (please correct me if I am mistaken) that it exclusively using Pullpoint communication for receiving motion detection events.

It is safe to say that the recent firmware update has, to the best of my ability to reproduce, has broken the camera's Pullpoint communication implementation. It is worth noting that TP-Link has not communicated any alterations in their implementation in the latest firmware updates. In response, I decided to experiment with Webhook communication, and I achieved positive results. Using a minimal server with Webhooks, I managed to receive both Motion and CellMotion detection events when motion initiated and ceased.

const http = require("http");
const { Cam } = require("onvif");
const { xml2js } = require("xml-js");

const serverConfig = {
  port: 8080,
  host: "", // Current machine's IP
};

const camConfig = {
  hostname: "",
  username: "",
  password: "",
  port: 2020,
  timeout: 10000,
};

const cam = new Cam(camConfig, handleCamConnect);
const server = http.createServer(handleRequest);
const uniqueSessionId = Date.now();

function handleServerListen() {
  console.log("Server is running", { serverConfig });
}

function handleCamConnect(error) {
  if (error) {
    throw new Error(error);
  }
  cam.getDeviceInformation(handleGetDeviceInformation);
  cam.getCapabilities(handleGetCapabilities);
}

function handleGetDeviceInformation(error, info, xml) {
  if (error) {
    throw new Error("Error getting device information", error);
  }
  console.log("Device Info", { info });
}

function handleGetCapabilities(error, capabilities, xml) {
  if (error) {
    throw new Error("Error getting capabilities", error);
  }
  if (!capabilities.events) {
    throw new Error("No events capabilities");
  }
  const authority = `http://${serverConfig.host}:${serverConfig.port}`;
  const path = `onvifnotify/${uniqueSessionId}`;
  const url = [authority, path].join("/");

  function renewSubscription() {
    cam.subscribe({ url }, handleSubscribe);
  }

  console.log("Event capabilities", { events: capabilities.events });
  setTimeout(renewSubscription, 1.5 * 60 * 1000);
  renewSubscription();
}

function handleSubscribe(error, subscription, xml) {
  if (error) {
    throw new Error("Error subscribing to events", error);
  }
  console.log("Subscribed", { subscription });
}

function handleRequest(request, response) {
  const data = [];

  function handleRequestData(chunk) {
    data.push(chunk);
  }

  function handleRequestEnd() {
    const text = Buffer.concat(data).toString();
    const body = xml2js(text, { compact: true });
    const notify = body["SOAP-ENV:Envelope"]["SOAP-ENV:Body"]["wsnt:Notify"];
    const notification = notify["wsnt:NotificationMessage"];
    const topicValue = notification["wsnt:Topic"]._text;
    const message = notification["wsnt:Message"]["tt:Message"];
    const messageValue = message["tt:Data"]["tt:SimpleItem"]._attributes.Value;

    console.log("Notification", { topicValue, messageValue });
  }

  request.on("data", handleRequestData);
  request.on("end", handleRequestEnd);
  response.writeHead(200);
  response.end("ok");

  console.log("Request", { url: request.url, method: request.method });
}

server.listen(serverConfig.port, serverConfig.host, handleServerListen);

Regrettably, I have not yet found the time to look into the process of integrating this into Scrypted. However, given that Scrypted does not currently utilize Webhooks at all while most cameras are working just fine using Pullpoint communication, it might not be a good idea to entirely replace the existing implementation. Instead, a more viable approach might involve enabling Webhooks via a toggle in the dashboard.

I am willing to contribute to the project if it is seems to be a valuable addition to the ONVIF plugin. I'm leaving this open for further discussion.

@koush
Copy link
Owner

koush commented Oct 10, 2023

Interesting, thanks I was thinking about writing my own onvif library as the one I'm currently using is a bit of a dated mess. The main issue with this approach is that the server needs to have a reachable address, which should generally be straightforward. However, if the server is behind a nat from the camera, it will not work without forwarding.

@jeffreylanters
Copy link
Author

It indeed is -- taking a look at some other ONVIF libraries, when not taking certificates and encryption into account, it doesn't seem to be too complex to get a basic module up and running. I'm free to help building the implementation, feel free to let me know if you're planning something. Regarding the server, an insecure server seems te be required as well, since the protocol doesn't seem te be able to handle to call a webhook behind a secure protocol.

@koush
Copy link
Owner

koush commented Feb 27, 2024

no plans to implement this due to possible reachability issues and questionable additional value. I'll keep it in mind if/when I get to the onvif reimplementation.

@koush koush closed this as completed Feb 27, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants