QuantumEntangledAndy / neolink

An RTSP bridge to Reolink IP cameras
GNU Affero General Public License v3.0
322 stars 44 forks source link

Keep track of camera battery status, log INFO and WARN on low power. #41

Closed dkerr64 closed 1 year ago

dkerr64 commented 1 year ago

The camera is regularly sending battery status info...

[2023-02-28T03:02:54Z DEBUG neolink_core::bc::xml] Struct: start to parse "body"
[2023-02-28T03:02:54Z DEBUG yaserde::de] Fetched StartElement(body, {"": "", "xml": "http://www.w3.org/XML/1998/namespace", "xmlns": "http://www.w3.org/2000/xmlns/"})
[2023-02-28T03:02:54Z DEBUG yaserde::de] Fetched StartElement(BatteryList, {"": "", "xml": "http://www.w3.org/XML/1998/namespace", "xmlns": "http://www.w3.org/2000/xmlns/"}, [version -> 1.1])
[2023-02-28T03:02:54Z DEBUG yaserde::de] Fetched StartElement(BatteryInfo, {"": "", "xml": "http://www.w3.org/XML/1998/namespace", "xmlns": "http://www.w3.org/2000/xmlns/"})
[2023-02-28T03:02:54Z DEBUG yaserde::de] Fetched StartElement(channelId, {"": "", "xml": "http://www.w3.org/XML/1998/namespace", "xmlns": "http://www.w3.org/2000/xmlns/"})
[2023-02-28T03:02:54Z DEBUG yaserde::de] Fetched Characters(0)
[2023-02-28T03:02:54Z DEBUG yaserde::de] Fetched EndElement(channelId)
[2023-02-28T03:02:54Z DEBUG yaserde::de] Fetched StartElement(chargeStatus, {"": "", "xml": "http://www.w3.org/XML/1998/namespace", "xmlns": "http://www.w3.org/2000/xmlns/"})
[2023-02-28T03:02:54Z DEBUG yaserde::de] Fetched Characters(none)
[2023-02-28T03:02:54Z DEBUG yaserde::de] Fetched EndElement(chargeStatus)
[2023-02-28T03:02:54Z DEBUG yaserde::de] Fetched StartElement(adapterStatus, {"": "", "xml": "http://www.w3.org/XML/1998/namespace", "xmlns": "http://www.w3.org/2000/xmlns/"})
[2023-02-28T03:02:54Z DEBUG yaserde::de] Fetched Characters(adapter)
[2023-02-28T03:02:54Z DEBUG yaserde::de] Fetched EndElement(adapterStatus)
[2023-02-28T03:02:54Z DEBUG yaserde::de] Fetched StartElement(voltage, {"": "", "xml": "http://www.w3.org/XML/1998/namespace", "xmlns": "http://www.w3.org/2000/xmlns/"})
[2023-02-28T03:02:54Z DEBUG yaserde::de] Fetched Characters(4069)
[2023-02-28T03:02:54Z DEBUG yaserde::de] Fetched EndElement(voltage)
[2023-02-28T03:02:54Z DEBUG yaserde::de] Fetched StartElement(current, {"": "", "xml": "http://www.w3.org/XML/1998/namespace", "xmlns": "http://www.w3.org/2000/xmlns/"})
[2023-02-28T03:02:54Z DEBUG yaserde::de] Fetched Characters(0)
[2023-02-28T03:02:54Z DEBUG yaserde::de] Fetched EndElement(current)
[2023-02-28T03:02:54Z DEBUG yaserde::de] Fetched StartElement(temperature, {"": "", "xml": "http://www.w3.org/XML/1998/namespace", "xmlns": "http://www.w3.org/2000/xmlns/"})
[2023-02-28T03:02:54Z DEBUG yaserde::de] Fetched Characters(-3)
[2023-02-28T03:02:54Z DEBUG yaserde::de] Fetched EndElement(temperature)
[2023-02-28T03:02:54Z DEBUG yaserde::de] Fetched StartElement(batteryPercent, {"": "", "xml": "http://www.w3.org/XML/1998/namespace", "xmlns": "http://www.w3.org/2000/xmlns/"})
[2023-02-28T03:02:54Z DEBUG yaserde::de] Fetched Characters(90)
[2023-02-28T03:02:54Z DEBUG yaserde::de] Fetched EndElement(batteryPercent)
[2023-02-28T03:02:54Z DEBUG yaserde::de] Fetched StartElement(lowPower, {"": "", "xml": "http://www.w3.org/XML/1998/namespace", "xmlns": "http://www.w3.org/2000/xmlns/"})
[2023-02-28T03:02:54Z DEBUG yaserde::de] Fetched Characters(0)
[2023-02-28T03:02:54Z DEBUG yaserde::de] Fetched EndElement(lowPower)
[2023-02-28T03:02:54Z DEBUG yaserde::de] Fetched StartElement(batteryVersion, {"": "", "xml": "http://www.w3.org/XML/1998/namespace", "xmlns": "http://www.w3.org/2000/xmlns/"})
[2023-02-28T03:02:54Z DEBUG yaserde::de] Fetched Characters(2)
[2023-02-28T03:02:54Z DEBUG yaserde::de] Fetched EndElement(batteryVersion)
[2023-02-28T03:02:54Z DEBUG yaserde::de] Fetched EndElement(BatteryInfo)
[2023-02-28T03:02:54Z DEBUG yaserde::de] Fetched EndElement(BatteryList)
[2023-02-28T03:02:54Z DEBUG neolink_core::bc_protocol::connection::bcconn] Ignoring uninteresting message id 252 (number: 0)

Neolink should catch these and keep track of status. On startup it could print an INFO message. On low battery level can print a WARN message.

I made a start on this and added structs to xml.rs, but stopped work when I realized that I did not know how to catch the '252' messages. https://github.com/QuantumEntangledAndy/neolink/commit/1e7ee1d43d405879e4bdaf5d9c67c0fe674dbb4e

QuantumEntangledAndy commented 1 year ago

You can react to a message in the same way as in the keepalive.rs. Where we register a callback. If you post the xml here and the header details I can work it up

QuantumEntangledAndy commented 1 year ago

Ah here it is. Just captured it easier to spot when you know the ID number

Magic Message ID Message Length Encryption Offset Status Code Message Class Payload Offset
f0 de bc 0a fc 00 00 00 16 02 00 00 00 00 00 00 c8 00 00 00 00 00 00 00
<?xml version="1.0" encoding="UTF-8" ?>
<body>
    <BatteryList version="1.1">
        <BatteryInfo>
            <channelId>0</channelId>
            <chargeStatus>charging</chargeStatus>
            <adapterStatus>solarPanel</adapterStatus>
            <voltage>3998</voltage>
            <current>0</current>
            <temperature>37</temperature>
            <batteryPercent>100</batteryPercent>
            <lowPower>0</lowPower>
            <batteryVersion>2</batteryVersion>
        </BatteryInfo>
    </BatteryList>
</body>
QuantumEntangledAndy commented 1 year ago

I'm only seeing one message at the start of the stream though. It's not continous

QuantumEntangledAndy commented 1 year ago

Should also be able to extract the graph of battery in last 30 days too somehow

dkerr64 commented 1 year ago

I'm only seeing one message at the start of the stream though. It's not continous

I am seeing it midstream -- but that may be because of the disconnect/reconnects that are going on. It is also possible that it only sends on status change. Try unplugging the solar panel and see if that triggers it.

I don't know whether to be envious of your 37 degree ambient temperature, or grateful. I am -3 and snowing right now!!

I see you have "charging" and "solarPanel". When not charging the first field looks to be empty. When connected to power adapter the second one is "adapter". When discharging the first field also is empty, but current shows a negative value. I assume milliamps. And voltage I assume is millivolts. I think it refuses to charge when the temperature gets too cold, I don't know what the threshold is, but my camera has not charged for the last couple of days.

QuantumEntangledAndy commented 1 year ago

One of the joys of living in Thailand is its always sunny. One of the woes of living in Thailand it's always sunny. It's the same thing really only now it's too hot to go outside most of the time instead of too cold.

I'm not home at the moment so can't really tinker with the cable atm. Tried to get the battery graph to show up in wireshark like it does on the new app. But ran into a snag, when I use my legacy reolink client I can see all the packets coz there is not AES encyption. But the legacy client dosent support the graph. When I use the newer client I get only AES in wireshark... I'm going to have to decode this one packet at a time outside wireshark

QuantumEntangledAndy commented 1 year ago

Going to post this here for reference

chargeStatus changes to chargeComplete when full <chargeStatus>chargeComplete</chargeStatus>

<body>
    <BatteryInfo>
    <channelId>0</channelId>
    <chargeStatus>chargeComplete</chargeStatus>
    <adapterStatus>solarPanel</adapterStatus>
    <voltage>3999</voltage>
    <current>0</current>
    <temperature>39</temperature>
    <batteryPercent>100</batteryPercent>
    <lowPower>0</lowPower>
    <batteryVersion>2</batteryVersion>
    </BatteryInfo>
    </body>
QuantumEntangledAndy commented 1 year ago

Ah this is not a message sent from the camera periodically but one requested on demand by the client.

This is the request:

Magic Message ID Message Length Encryption Offset Status Code Message Class Payload Offset
f0 de bc 0a fd 00 00 00 68 00 00 00 00000009 00 00 14 64 68 00 00 00

Extension:

<Extension version="1.1">
    <channelId>0</channelId>
    </Extension>

Payload: None


Reply

Magic Message ID Message Length Encryption Offset Status Code Message Class Payload Offset
f0 de bc 0a fd 00 00 00 6d 01 00 00 00000009 c8 00 00 00 00 00 00 00

Extention: none

Payload:

<body>
    <BatteryInfo>
    <channelId>0</channelId>
    <chargeStatus>chargeComplete</chargeStatus>
    <adapterStatus>solarPanel</adapterStatus>
    <voltage>3999</voltage>
    <current>0</current>
    <temperature>39</temperature>
    <batteryPercent>100</batteryPercent>
    <lowPower>0</lowPower>
    <batteryVersion>2</batteryVersion>
    </BatteryInfo>
    </body>
QuantumEntangledAndy commented 1 year ago

So summary:

ID: 252 sent by the camera after login. Contains a LIST of batteries

ID: 253 request for battery level from client to camera

dkerr64 commented 1 year ago

Humm... so if we send a 253 request we get a 253 reply. But during login the information comes in a 252 message. So requesting can be same as anything else neolink requests, but we also need to accept messages that come in unsolicited, like during login.

David.

dkerr64 commented 1 year ago

Excellent detective work btw.

QuantumEntangledAndy commented 1 year ago

Yes I am working up the code for both on demand and accepting these events (I'm assuming the camera will send it when low as a kinda event). It's almost done but it's getting late now so I'll finish tomorrow

dkerr64 commented 1 year ago

So, for the last 12 hours I have been pulling a jpeg from my camera every 20 minutes. Camera has been disconnected from adapter, and it is zero Celsius. Battery has dropped from 90% to 88%. I think I will reduce the interval to every 10 minutes. What would be really cool is to be able to monitor the battery level, so if it drops below say 50% then I increase the interval.

I'm doing this all in a bash script on linux. It would be nice to be able to tell neolink to return raw data in XML or JSON format that can then be processed by the calling script.

QuantumEntangledAndy commented 1 year ago

We do have a mqtt interface. We could add your functions to that and then your script would have control over neolink and could get the results from that too

QuantumEntangledAndy commented 1 year ago

Current implementation here #42

dkerr64 commented 1 year ago

Posted also in the comment thread for the PR...

I get a panic when parsing battery info. I am guessing it's because the "current" field is a negative number?

[2023-03-01T13:19:21Z DEBUG neolink_core::bc::xml] Struct: start to parse "body"
[2023-03-01T13:19:21Z DEBUG yaserde::de] Fetched StartElement(body, {"": "", "xml": "http://www.w3.org/XML/1998/namespace", "xmlns": "http://www.w3.org/2000/xmlns/"})
[2023-03-01T13:19:21Z DEBUG neolink_core::bc::xml] Struct: start to parse "BatteryList"
[2023-03-01T13:19:21Z DEBUG yaserde::de] Fetched StartElement(BatteryList, {"": "", "xml": "http://www.w3.org/XML/1998/namespace", "xmlns": "http://www.w3.org/2000/xmlns/"}, [version -> 1.1])
[2023-03-01T13:19:21Z DEBUG neolink_core::bc::xml] Struct: start to parse "BatteryInfo"
[2023-03-01T13:19:21Z DEBUG yaserde::de] Fetched StartElement(BatteryInfo, {"": "", "xml": "http://www.w3.org/XML/1998/namespace", "xmlns": "http://www.w3.org/2000/xmlns/"})
[2023-03-01T13:19:21Z DEBUG yaserde::de] Fetched StartElement(channelId, {"": "", "xml": "http://www.w3.org/XML/1998/namespace", "xmlns": "http://www.w3.org/2000/xmlns/"})
[2023-03-01T13:19:21Z DEBUG yaserde::de] Fetched Characters(0)
[2023-03-01T13:19:21Z DEBUG yaserde::de] Fetched EndElement(channelId)
[2023-03-01T13:19:21Z DEBUG yaserde::de] Fetched StartElement(chargeStatus, {"": "", "xml": "http://www.w3.org/XML/1998/namespace", "xmlns": "http://www.w3.org/2000/xmlns/"})
[2023-03-01T13:19:21Z DEBUG yaserde::de] Fetched Characters(none)
[2023-03-01T13:19:21Z DEBUG yaserde::de] Fetched EndElement(chargeStatus)
[2023-03-01T13:19:21Z DEBUG yaserde::de] Fetched StartElement(adapterStatus, {"": "", "xml": "http://www.w3.org/XML/1998/namespace", "xmlns": "http://www.w3.org/2000/xmlns/"})
[2023-03-01T13:19:21Z DEBUG yaserde::de] Fetched Characters(none)
[2023-03-01T13:19:21Z DEBUG yaserde::de] Fetched EndElement(adapterStatus)
[2023-03-01T13:19:21Z DEBUG yaserde::de] Fetched StartElement(voltage, {"": "", "xml": "http://www.w3.org/XML/1998/namespace", "xmlns": "http://www.w3.org/2000/xmlns/"})
[2023-03-01T13:19:21Z DEBUG yaserde::de] Fetched Characters(4029)
[2023-03-01T13:19:21Z DEBUG yaserde::de] Fetched EndElement(voltage)
[2023-03-01T13:19:21Z DEBUG yaserde::de] Fetched StartElement(current, {"": "", "xml": "http://www.w3.org/XML/1998/namespace", "xmlns": "http://www.w3.org/2000/xmlns/"})
thread 'tokio-runtime-worker' panicked at 'called `Result::unwrap()` on an `Err` value: ParseIntError { kind: InvalidDigit }', crates/core/src/bc/xml.rs:485:41
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
dkerr64 commented 1 year ago

I see you declared current as u32 which is probably the problem. Current is negative while discharging.

dkerr64 commented 1 year ago

temperature also needs to be declared as i32 to allow for negative temperatures. And in addition to 'solarPanel' the adapter status can also be 'adapter' and none.

I changed both current and temperature to i32 and it works.

dkerr64 commented 1 year ago

Is there a reason that you are not printing voltage and current in the INFO message?

dkerr64 commented 1 year ago

It would also be helpful to have the battery info printed on stdout rather than just stderr. Right now I have to2>&1 redirect before pipe into grep to find "Charge:". When I capture a jpeg I'd like to capture the charge info at the same time so that I can manage how frequently I hit the camera for a new image.

Providing some easily parsable output would be best (ie, no percentage or degree C symbols).

Not a huge issue, just an observation. I'm sure I can work with what is does now.

dkerr64 commented 1 year ago

I'm really liking this. I have my JPEG capture running in a loop, grabs image every 10 minutes. I extract the charge and temperature from the stderr then use ffmpeg to superimpose battery charge and temperature onto the image. ffmpeg also pads the image to 4x3 aspect ratio (to match that coming from other cameras) and compresses the image more, so browser page loads much faster. Now when I go to the web page for my cameras, all six images appear instantly (because I am caching a recent image on the web server). Five of the cameras are PoE wired, so I pull an image (with curl) once a minute, for the battery WiFi camera the image could be 10 minutes old... but the act of opening the web page fires of an immediate refresh with neolink.

Really really cool.

dkerr64 commented 1 year ago

btw... for temperature, which could be negative, it may be safest to declare it as floats rather than integers. They are signed integers right now, but who knows if in the future temperature could be reported with decimals.

QuantumEntangledAndy commented 1 year ago

Ah right negative temperatures, I've been working in Kelvin too much for my work :). I'm going to put them as i32 until we actually see a float though.

If you have a gstreamer pipeline that adds the OSD to the image we can put that into neolink if you want.

QuantumEntangledAndy commented 1 year ago

What do you want in parseability. I can encode to xml without too much trouble. But if you want to encode to anything else then I'll need to restructure the library to use serde rather than yaserde libraries (yaserde is the one we use and is xml focused)

QuantumEntangledAndy commented 1 year ago

Ok so I went ahead and made the printing of status messages configurable.

In the config you can add

[[cameras]]
# Usual cam stuff
print_format = "Xml"

Valid values are None (default), Human and Xml

dkerr64 commented 1 year ago

This is great, thank you. Xml to stdout is perfect, it is working well.

dkerr64 commented 1 year ago

If you have a gstreamer pipeline that adds the OSD to the image we can put that into neolink if you want.

I think I prefer to keep it outside of neolink. I am using ffmpeg for a couple of processes and printing information like battery and temperature is easy to add... and gives me lot of flexibility on positioning.

Thanks.

QuantumEntangledAndy commented 1 year ago

Meged it into master so I'll close this