Driver for generic camera jpg image capture & send via Pushover

I am trying to write a device driver for IP cameras that support jpeg image capture URL.

Basically it will get the jpg image from URL and send it via image attachment feature of Pushover.

Since Hubitat does not allow storage or presentation of files, I am considering to write the image data to a byte variable and then post it to Pushover as a payload attachment.

I have 2 questions:

  1. is there a method to http-get image from a URL like : http://192.168.0.5/cgi/camera.jpg and write it to a byte value ?

  2. how can I write a http post function which would replicate the Curl equivalent:

    curl -s
    --form-string "token=APP_TOKEN"
    --form-string "user=USER_KEY"
    --form-string "message=here is an image attachment"
    -F "attachment=@/path/to/your/image.jpg"
    https://api.pushover.net/1/messages.json

Pushover is accepting http post message attached image with headers like:

[other HTTP headers]
Content-Type: multipart/form-data; boundary=--abcdefg

----abcdefg
Content-Disposition: form-data; name="user"

[ your Pushover user key ]
----abcdefg
Content-Disposition: form-data; name="token"

[ your Pushover API token ]
----abcdefg
Content-Disposition: form-data; name="message"

your message here
----abcdefg
Content-Disposition: form-data; name="attachment"; filename="your_image.jpg"
Content-Type: image/jpeg

[ raw binary data of image file here ]
----abcdefg--

I'm interested if people have solutions for this, too. I was thinking about pushing to Google Drive or Dropbox. I was thinking I might need to go to Node Red for this.

I have a basic solution which works on a separate server using nodejs.
Below I will paste the code.
It basically listens on tcp 5151 port and catches /cameraget path (HTTP GET)

You can use it changing the token, pushover user and camera url

this is not a solution to my question. Just an alternative using PI server.

fs = require('fs');
http = require('http');
url = require('url');
request = require('request');
var httpreq = require('httpreq');

function send(parameters, image, callback) {
        var options = { parameters: parameters };
        if(image) options.files = {attachment: image}; 

        httpreq.post("https://api.pushover.net/1/messages.json", options, function (err, res){
                if (callback && typeof callback === "function"){
                        if(err){
                                callback(err);
                        }else{
                                var response = JSON.parse(res.body);
                                if(response.status != 1) {
                                        callback(response);
                                } else {
                                        callback(null, response);
                                }
                        }
                }
        });
}

var download = function(uri, filename, callback){
  request.head(uri, function(err, res, body){
    //console.log('content-type:', res.headers['content-type']);
    //console.log('content-length:', res.headers['content-length']);

    request(uri).pipe(fs.createWriteStream(filename)).on('close', callback);
  });
};

http.createServer(function(req, res){
  var request = url.parse(req.url, true);
  var action = request.pathname;

  if (action == '/camera.jpg') {
     var img = fs.readFileSync('/tmp/camera.jpg');
     res.writeHead(200, {'Content-Type': 'image/jpg' });
     res.end(img, 'binary');
  } else if (action == '/cameraget') {
     download('http://URL_TO_CAMERA_JPG', '/tmp/camera.jpg', function     (){
    // console.log('done');
                        send({
                                token: 'YOUR_TOKEN',
                                user: 'PUSHOVER_USER',
                                title: 'camera image',
                                message: 'camera image received' 
                        }, '/tmp/camera.jpg', null);


     var img = fs.readFileSync('/tmp/camera.jpg');
     res.writeHead(200, {'Content-Type': 'image/jpg' });
     res.end(img, 'binary');
     }); 
  }
  else { 
     res.writeHead(200, {'Content-Type': 'text/plain' });
     res.end('Hello World \n');
  }
}).listen(5151);

I've got the first part with following code:

 try {
    httpGet(params) { response ->
      retval = responseHandler(response)
        log.debug retval
        log.debug "returned"
    }
  }
  catch (ex) {
  }

def responseHandler(response) {
    log.debug response.status
    log.debug response.data.available()
    byte[] array = new byte[response.data.available()];
      response.data.read(array);
}

at this point I have the image payload in byte[] on the "array" variable

but I noticed high cpu usage with this code.

what am I doing wrong here ?

I am still working on this.
But when using httpPost , I am not able to set requestContentType as "multipart/form-data"

if I do that, no http package is coming out from the hub.

Why is that ?
is the "httpPost" command limited with specific content-type requests ?

if that's the case how can I create a custom http package ?

I've finally managed to send multipart form-data in correct format.
But the image is blank.

I'm encoding/decoding the image with below code.
is there any problem in this ?

if(response.data != null) {
        byte[] imageBytes = response.data.decodeBase64()
        
         def apiKey = "xxx"
    def userKey = "xxx"
    def boundary = "--abcdefg"
	def host = "api.pushover.net:80"
    def method = "POST"
    def path = "/1/messages.json"   
    def headers = [:] 
    headers.put("HOST", "$host")
	headers.put("content-type", "multipart/form-data; boundary=--abcdefg")

    String myString = imageBytes.encodeBase64().toString()
    def data1 = ""
//data += "Content-Type: multipart/form-data; boundary="+ boundary +"\r\n\r\n"
data1 += "--" + boundary + "\r\n"
data1 += "Content-Disposition: form-data; name=\"user\"; \r\n\r\n" + userKey +"\r\n"
data1 += "--" + boundary + "\r\n"
data1 += "Content-Disposition: form-data; name=\"token\"; \r\n\r\n" + apiKey +"\r\n"
data1 += "--" + boundary + "\r\n"
data1 += "Content-Disposition: form-data; name=\"message\"; \r\n\r\n" + "mesaj" +"\r\n"
data1 += "--" + boundary + "\r\n";
data1 += "Content-Disposition: form-data; name=\"attachment\"; filename=\"" + "camera.jpg" + "\"\r\n";
data1 += "Content-Type:image/jpeg\r\n\r\n";
data1 += myString
data1 += "--" + boundary + "--"

either I am incorrectly encoding when I get it from response.data, or I'm decoding wrong when embedding it into body.

is this kind of string concatenating with decoded payload correct ?

last step: I need to find a way to correctly put jpeg payload in the body after content-type:application/octetstream

the payload is in a byte[] variable
normally it looks like Pushover is accepting it in string format, so I tried
new String(byte[])

this sort of converts but loses some bytes or incorrectly converts them.

how should I convert the byte[] to a string to be used in octet-stream ?

You shouldn't need to encode the image base64. You're not putting it in the URL you're putting it in the body. As to the rest of it... I dunno... haven't had to manually build a payload in a long time

1 Like

thanks.
but if I don't use base64 encoding , then I have to convert the byte array to a string (?) format that I can use in the body.
what is the correct conversion for this ?

According to the Pushover API docs here:

https://pushover.net/api#attachments

... you should include the [ raw binary data of image file here ]

yes I know that.
but I don't know how to put the byte array as binary in the string body.

any ideas ?

Well, not sure you can do it with the tools allowed in Hubitat, but...

https://docs.hubitat.com/index.php?title=Common_Methods_Object#httpPost

This says if the body is an array they will handle encoding it? I think the limitation here that the body be a string is going to prevent you from doing what you're trying to do.

Have you thought about running this outside Hubitat in something like Node-RED?

I've already done it using a simple nodejs script on my PI server.
However, if possible I prefer avoiding external server use.

That's why I'm still trying to figure out.
And if this become possible on HUbitat, I'm sure many users will be happy to use it.

Because mostly IP cameras support single jpeg capture with httpget.

Well, I'm rooting for you :grinning:

I want to call the endpoint for my tablet running Fully Kiosk Browser when motion is detected and the alarm is in entry / exit delay and then send it / save it somewhere.

Hopefully someone else will have better ideas.

@ardichoke

if I'm not wrong, you are developer of the Unofficial Rİng Integration.
I see you get image data using httpget and then you are using it on UI.

I am able to get the data like you used response.data.read(array)
but then I want to send this "array" byte[] in a httpost message body to Pushover service.

I tried both by base64 encoding and with no encoding.
String myString = array.encodeBase64().toString()
and
String myString = new String(array)

in the first method , I get a blank (or corrupt) image on Pushover.
because I think content-type "multipart/form-data" requires raw image data.

in the second method array is constructed to a string , so it is closer to the requirement.
However, it is not identical to the ones produced with working examples (nodejs)
I guess, "new String()" constructor misses the correct charset encoding.

DO you have any idea whay I should use to prepare the byte[] for embedding into httppost body ?

Otherwise, is there a way to store the data in a file and then read it from there to post ?

I'm just looking for a working solution.

You need to re-read the initial post in the Ring Integration thread. It specifically says that I did not write it. It was written by someone else who no longer wants to work on Hubitat so they pulled the code offline. I just re-released it. Ask for help in the thread, maybe someone else can help, I cannot. I don't have time to work on this.

1 Like

ok. thanks anyway.

in this case, asking for help in that thread will not get me anywhere.
because possibility of anyone reading that thread and having worked on this more than me is very very low...

OK. I give up...

I made it to a point where it would work if sending the image payload in proper format was possible.
But I could not get support from community on this.

If, in future, someone would like to work on this, I can provide details of progress I made until now.

Now I am using my nodejs implementation which works on PI server. It is a very lightweight , simple app which does the job, requiring an external server.

summary of the current issue with direct Hubitat get-image-send-to-pushover approach:

  • image is received from httpget to capture URL of IP camera
  • image data is written to byte[]
  • hubaction is used to send the message to Pushover together with image data
  • Pushover requires multipart/form-data
  • parts of the message (token, user key, message) are sent successfully
  • last part is the image attachment and it is also sent
  • but Pushover requires the attachment to be "raw data" in the body of the message either with application/octet-stream or image/jpeg

problem:
EITHER there is a limitation on Hubitat's hubaction which prevents proper formatting of the message body or breaks it apart, OR the byte array formatting into String is not correctly forming the raw data. I tried with charsets US-ASCII, UTF-8, UTF-16, ISO-8859-1
IF Pushover had accepted Base64 encoded image data, it might have worked.

@bravenel Can you confirm if this is doable with HE?

I've returned to this subject , because it is really a pain to use a RPI for something such doable on HE.
is it possible to store the image (received with http get) on a temporary file on hub and then attach it to a pushover multipart message ?
which methods should I look for ?

1 Like