2

I have an XBMC box hooked up to my TV. In addition, I have 2 Foscam IP cameras, which I use for baby-monitoring my two little girls.

A while back I thought it'd be cool to write a couple of scripts, so that when one of the Foscam's alarms triggers, I get a notification on XBMC along with live images from the relevant Foscam camera. With this, I could watch TV, while keeping a close eye on the kiddos.

So, on the XBMC box I have a shell script running, which checks the Foscam alarm statuses every second. If an alarm is triggered, it sends a command to XBMC to fire an XBMC script, suspends its checks for 30 seconds, and resumes checking the alarm statuses. The XBMC script shows a 30-second notification, containing the name of one of my daughters (depending on which camera triggered) and a cute picture of them, and a live snapshot from the relevant Foscam, updated every half a second.

This all worked perfectly fine, and was totally awesome :) However, last week I upgraded the Foscam's firmware. The only change in the new firmware (mentioned in the firmware description) was to change the camera's HTTP authorization method from basic to digest. And ever since then, I have been having problems with my XBMC script.

So first, here is the current version of the script:

# Import the XBMC/XBMCGUI modules.
from requests.auth import HTTPDigestAuth
import xbmc, xbmcgui, xbmcvfs, xbmcaddon
import sys, os, requests, time


# Class to manage the notification image
class CamView(xbmcgui.WindowDialog):

    urlpath   = "/snapshot.cgi?resolution=16"
    imagename = "snapshot.jpg"

    def __init__(self, camname,camport,idx,username,password):

        # Construct correct URL
        self.baseurl = 'http://' + camname + 'cam:' + camport

        # Cams use digest authentication
        self.auth = HTTPDigestAuth(username, password)

        # Set
        path = xbmc.translatePath('special://profile/addon_data/%s' % xbmcaddon.Addon().getAddonInfo('id'))
        if not xbmcvfs.exists(path):
            xbmcvfs.mkdir(path)
        self.imagefile = os.path.join(path, self.imagename)

        # Message
        self.msg = {
            "1": camname.capitalize() + ' moved',
            "3": camname.capitalize() + ' made a sound',
        }.get(idx, camname.capitalize() + 'cam fired alarm')

        # set the initial image before the window is shown
        self.image = xbmcgui.ControlImage(870, 383, 380, 253, "")
        self.addControl(self.image)


    def update_image(self):

        f = requests.get(self.baseurl+self.urlpath, auth=self.auth)
        with open(self.imagefile, "wb") as local_file:
            local_file.write(f.content)

        self.image.setImage("")
        self.image.setImage(self.imagefile)

    def __enter__(self):
        return self

    def __exit__(self,type,value,traceback):
        os.remove(self.imagefile)


def main():

    for i in range(1,len(sys.argv)):
        str,dummy,val = sys.argv[i].partition("=")
        if str == "alarm_id":   idx      = val
        if str == "cam_id"  :   camname  = val
        if str == "cam_port":   camport  = val
        if str == "username":   username = val
        if str == "password":   password = val

    with CamView(camname,camport,idx,username,password) as viewer:

        viewer.show()

        start_time = time.time()
        firstimage = True
        while(time.time() - start_time <= 30):

            viewer.update_image()
            curr_time = round(time.time()-start_time, 0)

            if firstimage:

                firstimage = False
                nowtime = time.strftime("%I:%M %p")

                viewer.image.setAnimations([('conditional', 'effect=fade start=0 end=100 time=750 delay=125 condition=true'),
                                            ('conditional', 'effect=slide start=400,0 end=0,0 time=750 condition=true')])

                xoptions = ("Notification(\"" + viewer.msg + "\", " + nowtime + ", 29500, special://masterprofile/addon_data/" +
                            xbmcaddon.Addon().getAddonInfo('id') + "/" + camname + ".png)")
                xbmc.executebuiltin(xoptions)


            elif curr_time == 30:
                viewer.image.setAnimations([('conditional', 'effect=fade start=100 end=0 time=750 condition=true'),
                                            ('conditional', 'effect=slide start=0,0 end=400,0 time=750 condition=true')])

            else:
                viewer.image.setAnimations([('conditional', 'effect=fade start=100 end=100 time=0 condition=true')])


            xbmc.sleep(500)


if __name__ == "__main__":

    if xbmc.getInfoLabel("Window(10000).Property(foscamScriptRunning)") == "True":
        xbmc.log('Script already running', level=xbmc.LOGERROR)
    else:
        xbmc.log('Foscam alarm triggered', level=xbmc.LOGNOTICE)
        xbmcgui.Window(10000).setProperty("foscamScriptRunning", "True")
        main()
        xbmcgui.Window(10000).setProperty("foscamScriptRunning", "False")

The original script used urllib, which I found did not support digest authentication in any sort of convenient way. So I changed to urllib2. This did not work, in that the image in my XBMC popup did not ever update after the first image. Sometimes, there was not even an image at all.

So I did a bit of digging, and I quickly found that getting the snapshots with urllib2 with Digest authentication took a little over 7 seconds! (with the old firmware, this took a little under 0.1 seconds). Thinking that this might be the cause of the images not being updated, I changed everything to the requests module. Profiling showed that obtaining a single snapshot from the camera now took somewhere around 0.25 seconds; still rather slow IMHO, but perhaps acceptable. However, also with this method, the notification images are not updating.

I'm triggering the script via remote SSH, so I can check the XBMC logs etc. I also checked the time stamps on the snapshot.jpg files as they were created, and they seem to agree with the script trigger time and the 0.25 second delay by requests. In the XBMC script, I changed the order of clearing the image and setting it to the new snapshot to every possible ordering you can think of, without success. If I put a delay between clearing and re-setting the image, I see a flickering image, suggesting that it all works. However, it is always re-set to the exact same snapshot.

So, I'm really quite stuck. What am I overlooking here?

Rody Oldenhuis
  • 37,726
  • 7
  • 50
  • 96
  • is there a chance you could roll back to the previous firmware ??? It is very hard for anyone to help unless we have the same hardware.. :(( – mlwn Jul 21 '14 at 09:11
  • @mlwn not really, the camera doesn't even have a "save current firmware" option..... I was hoping it was an issue with the python code, or somebody had experienced similar problems with the used modules in combination with digest authentication... – Rody Oldenhuis Jul 21 '14 at 16:33
  • I am interested in this subject.. and would like to offer my help. But I have never worked with such hardware... do you think there could be a simulator somewhere online ??? I am ready to help in any convenient way... – mlwn Jul 21 '14 at 16:36

1 Answers1

1

For what it's worth:

I finally "fixed" the problem by saving each individual snapshot under a unique name (names generated based on time with microseconds), and removing all these individual files afterwards.

This hints in the direction of some caching issue with xbmcgui.ControlImage.setImage(), but I've been unable to find any documentation mentioning caching...

One problem I've experienced with this approach is that if you press Esc while the notification is showing (because all XBMC control is lost when that is the case), is that the images are not always cleaned up properly. Relatively minor issue, but it is a clear indication that this is a smelly solution :)

Rody Oldenhuis
  • 37,726
  • 7
  • 50
  • 96