[Release] - an app for AI camera/webcam motion/object detection/recognition (motion, person, pet, vehicle, face using tinyCam)

Very good! Pleased you were able to get it working.

Let us know feedback on how the cameras perform for motion/object tracking on your porch. Sometimes cameras are not idea for motion tracking because they can see any pixel change (like lights going on/off) as "motion". So it can depend on your use case and maybe careful selection of motion zones in tinyCam could be important along with the AI object tracking.

Anyway, good luck with it!
Cheers.

Some small edits to the Child app to prevent it throwing an error in the logs if it tries to send a command to a virtual motion device that is not set up (for example, if the camera senses a Pet or Vehicle, but the user did not yet set up those virtual motion sensors in the app for that category of AI object detection). The error in the logs doesn't affect operation of the code, but it's tidier to clean it up :slight_smile:

Enjoy!

Updated Child App:

/**

   Hubitat App to allow ip/web cameras in tinyCam to drive Hubitat Virtual Motion Sensors

   tinyCam is an Android app that can discriminate between different types of motion:
    - motion
    - person
    - pet
    - vehicle
    - face (non specific/personal)

   Set-Up:

   In tinyCam:
   - install the tinyCam Monitor Pro app (there is a free add-supported version)
   - add your cameras
   - in camera settings, motion detection, enable all the motion types
   - add a webhook http://[your_hub_ip/apps/api/XXXX/?access_token=YYYY&motion=%MOTION_TYPE%
     (where XXXX is the ID of this app once installed and YYYY is the access code reported in this app (remember to activate oath when installing)

   In Hubitat:
   - simply set-up virtual motion sensor devices (1 for each motion type)
   - then select these when you run the app
   - you can also use an html tile in a dashboard to view the live webcam (if you activate the webserver function in tinyCam)

 -----------------------------------------------------------------------------------------------------------------------------
   Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
   in compliance with the License. You may obtain a copy of the License at:
 
       http://www.apache.org/licenses/LICENSE-2.0
 
   Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
   on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
   for the specific language governing permissions and limitations under the License. 
 ------------------------------------------------------------------------------------------------------------------------------
 
   If modifying this project, please keep the above header intact and add your comments/credits below - Thank you!
 
 ------------------------------------------------------------------------------------------------------------------------------

  Changes:

  0.0.4 -  8th July 2020 - Added conditions on the switches to prevent log errors when trying to address virtual switches that don't exist [Angus Marshall]
  0.0.3 - 27th June 2020 - Set up parent/child structure (heavily leveraging @bptworld's great work - thank you!) [Angus Marshall]
  0.0.2 - 20th June 2020 - Added selection of virtual motion sensors, simplified url/params, code to activate motion sensors,
                           code to rename the app [Angus Marshall]
  0.0.1 - 18th June 2020 - Initial code [Markus Liljergren]

**/

def setVersion(){
    state.name = "tinyCam Connector"
	state.version = "0.0.4"
}

definition(
    name: "tinyCam Connector Child",
    namespace: "Angus_M",
    author: "Angus Marshall (Angus_M)",
    description: "set virtual motion sensors to active when http received from tinyCam (or other similar webcam app)",
    category: "Convenience",
	parent: "Angus_M:tinyCam Connector",
    iconUrl:   "",
    iconX2Url: "",
    iconX3Url: "",
    documentationLink: ""
) {
}

preferences {
    page(name: "pageMain", title: "Main Page", install: true, uninstall: true)
    page(name: "pageEnableAPI")
    page(name: "pageDisableAPI")

    mappings {
        path("/") {
            action: [
            GET: "getDevice"
            ]
        }
    }
}

void installed() {
	display()
    log.debug "Installed with settings: ${settings}"
    initializeAPIEndpoint()
}

def updated() {
	display()
    log.debug "Updated with settings: ${settings}"
    unsubscribe()
    initializeAPIEndpoint()
}

String initializeAPIEndpoint() {
    if(!state.accessToken) {
        if(createAccessToken() != null) {
            state.endpoint = getApiServerUrl()
            state.localAPIEndpoint = getFullLocalApiServerUrl()
            state.remoteAPIEndpoint = getFullApiServerUrl()
        }
    }
    return state.accessToken
}

/* Pages */
Map pageDisableAPI() {
    dynamicPage(name: "pageDisableAPI") {
	    display()
        section() {
            if (state.accessToken != null) {
                state.accessToken = null
                state.endpoint = null
                paragraph("SUCCESS: API Access Token REVOKED! Tap Done to continue")
            }
        }
    }
}

Map pageEnableAPI() {
    dynamicPage(name: "pageEnableAPI", title: "") {
	    display()
        section() {
            if(state.accessToken == null) {
                initializeAPIEndpoint()
            }
            if (state.accessToken == null){
                paragraph("FAILURE: API NOT Initialized!")
            } else {
                paragraph("SUCCESS: API Initialized! Tap Done to continue")
            }
        }
    }
}

Map pageMain() {
    dynamicPage(name: "pageMain") {
        display()
        section() {
            if(state.accessToken == null) {
                paragraph("API is not yet Initialized!")
                href(name: "hrefPageEnableAPI", title: "Enable API", description: "", page: "pageEnableAPI")
            } else {
		        section("Instructions:", hideable: true, hidden: true) {
                    paragraph("Put the following URL into the tinyCam webhook for motion on the selected camera. This app will read the webhook when transmitted by tinyCam and activate the virtual motion sensor selected below depending on the motion type (eg. simple motion, or AI-identified motion from Person, Vehicle, Pet, or (non-personal) face).")
                }
                
  		        section(getFormat("header-blue", "URLs")) {
                    String localURL = "${state.localAPIEndpoint}/?access_token=${state.accessToken}&motion=%MOTION_TYPE%"
                    String remoteURL = "${state.remoteAPIEndpoint}/?access_token=${state.accessToken}&motion=%MOTION_TYPE%"
                    paragraph("LOCAL API (devices): <a href=\"$localURL\" target=\"_blank\">$localURL</a>")
                    paragraph("REMOTE API: <a href=\"$remoteURL\" target=\"_blank\">$remoteURL</a>")
                }
            
  		        section(getFormat("header-blue", "Virtual Motion Devices")) {
                    input "motionDevice", "capability.motionSensor", title: "Motion: Which motion sensor to control?", multiple: false, required: true
                    input "personDevice", "capability.motionSensor", title: "Person: Which motion sensor to control?", multiple: false
                    input "petDevice", "capability.motionSensor", title: "Pet: Which motion sensor to control?", multiple: false
                    input "vehicleDevice", "capability.motionSensor", title: "Vehicle: Which motion sensor to control?", multiple: false
                    input "faceDevice", "capability.motionSensor", title: "Face: Which motion sensor to control?", multiple: false
                }
                
  		        section(getFormat("header-blue", "Child App name (optional to change)")) {
                    label title: "Enter a name for this app", required: false
                }
                
            }
        }
			display2()
    }
}

Map getDevice() {
    log.debug("Running getDevice() $params")
    
    if ((params.motion == "Motion") && (motionDevice)) {
        motionDevice.active()
    } else if ((params.motion == "Person") && (personDevice)) {
        personDevice.active()
    } else if ((params.motion == "Pet") && (petDevice)) {
        petDevice.active()
    } else if ((params.motion == "Vehicle") && (vehicleDevice)) {
        vehicleDevice.active()
    } else if ((params.motion == "Face") && (faceDevice)) {
        faceDevice.active()
    }
    
}

def getFormat(type, myText="") {			// Modified from @Stephack Code   
	if(type == "header-blue") return "<div style='color:#ffffff;font-weight: bold;background-color:#5da2c2;border: 1px solid;box-shadow: 2px 3px 5px 5px #5da2c2'>${myText}</div>"
    if(type == "line") return "<hr style='background-color:#5da2c2; height: 1px; border: 0;'>"
    if(type == "title") return "<h2 style='color:#5da2c2;font-weight: bold'>${myText}</h2>"
}

def display() {
    setVersion()
    getHeaderAndFooter()
    theName = app.label
    if(theName == null || theName == "") theName = "New Child App"
    section (getFormat("title", "${state.name} (${theName})")) {
        paragraph "${state.headerMessage}"
		paragraph getFormat("line")
	}
}

def display2() {
	section() {
		paragraph getFormat("line")
		paragraph "<div style='color:#5da2c2;text-align:center;font-size:20px;font-weight:bold'>${state.name} - ${state.version}</div>"
        paragraph "${state.footerMessage}"
	}       
}

def getHeaderAndFooter() {
    if(logEnable) log.debug "In getHeaderAndFooter (${state.version})"
    def params = [
		timeout: 30
	]
    
    try {
        def result = null
        httpGet(params) { resp ->
            state.headerMessage = resp.data.headerMessage
            state.footerMessage = resp.data.footerMessage
        }
        if(logEnable) log.debug "In getHeaderAndFooter - headerMessage: ${state.headerMessage}"
        if(logEnable) log.debug "In getHeaderAndFooter - footerMessage: ${state.footerMessage}"
    }
    catch (e) {
        state.headerMessage = "<div style='color:#5da2c2'>@Angus_M Apps and Drivers</div>"
        state.footerMessage = "<div style='color:#5da2c2;text-align:center'>Angus Marshall<br><a href='https://paypal.me/angusmarshall' target='_blank'><img src='https://www.paypalobjects.com/webstatic/mktg/logo/pp_cc_mark_37x23.jpg' border='0' alt='PayPal Logo'><br>Paypal contributions welcome!</a></div>"
    }
}

Nice work @Angus_M

I love your idea of...

'I don't use them to trigger lights but I use them to help keep them on. It works perfectly. Even when my Xiaomi motion sensors don't register movement because I'm sat watching TV, the camera will sense me as a "person" and keep the lights on'

Now that's thinking outside of the box.

I'll be following this with great interest, again, nice work :+1:

1 Like

Thanks @chillibenny. I'm sat here tonight listening to Spotify and the lights are on! My dashboard is telling me tinyCam views me as a "person" ha ha ha. Life is good!

2 Likes

Isn’t it just great when all the hard work pays off and everything just works :partying_face:

1 Like

@Angus_M I’m sure you’ve already said, but I’m lazy and don’t have enough time in the world, thanks to my 2 wonderful and full of energy kids :crazy_face:, what IP cameras are you using?

No problem. Actually I've just got an old Samsung smartcam in the lounge here (but it's working very fine on tinyCam) and 5 Arlos dotted around (3 inside, 2 outdoors). I've come to the conclusion that battery cameras are really not ideal for my set up now because you can't leave them on. I want to keep my cameras running under tinyCam which can do all the motion/object tracking and recording when needed as well as interacting with Rule Machine based on this app. And that way they can feed the video constantly to my dashboard. So I will probably switch those for something else in the future or run power to them so that I can leave them on.

1 Like

@Angus_M Cheers bud

I wonder if this will work with motioneye as well, given that it has the capability to call a webhook? I assume the same webhook URL should work within motioneye?

I don't know what motioneye is. If you mean it can output the same Webhook command to HE as the example above for tinyCam then it should work. Otherwise we'd have to reconfigure the app to receive a different Webhook which should not be too difficult.

Just read about it on the Google Play Store. It looks like it's still in beta and has very limited functionality. Not sure why you'd use that over tinyCam to be honest, unless I'm missing something.

motioneyeos description here. The app on the play store appears to be a client app for the linux based program which I have installed on my pi3. ATM I use it to convert RTSP cameras (eg wyze with the RTSP firmware) to display on my dashboard. I also have it installed on pi zeros (+cam) to them into small surveillance cameras.

I'll have a play with your code with motioneye this weekend. I imagine it should work.

Just didn't want another device hanging off the HA system. Although tinycam does seem to have impressive motion detection options.

Sounds good. Unfortunately tinyCam doesnt run on a Pi (well, there is an unsupported version that works on 3b only that I found). So yeah, have at it and see what you can do! :slight_smile:

1 Like

Dude, that looks awesome, how did you get the indicator in the top left of the view? I'm poking around, but I'm very new to HE.

Hiyah. Do you mean the little blue circle showing the motion, person or face identification? This is a custom dashboard built in html/javascript/css so it's really possible to do anything you like using this approach, although it's more effort of course to set up.

First I bring the camera live video in from the tinyCam webserver and display that in the dashboard using CSS to style the view into a circle. Then I display the appropriate little blue image according to which of the virtual motion detectors is triggered from my app in this thread. I switch the images using javascript which listens in to the eventsocket from Hubitat. So if I see the person object sensing virtual motion device trigger in the eventsocket stream, I just switch the src of the image to the path to my little blue person image. It works really nicely. I'm using the same approach for other devices, to show motion, doors, etc as dynamic graphics that change on my dashboard as devices trigger or send updates like temperature, open/close etc. I've posted about that elsewhere on the forum.

It's been quite a large project given I'm just a keen tinkerer not a professional coder and was learning as I went. But when I get an idea I usually keep at it until the technical challenge is somehow completed :smiley:

Wow, send like I have a lot to learn.
I was trying to start simple and see if there was a way to pass the parameters to the device so we could have one device so all of the different monitoring....

Guess I'll start looking into using CSS on here. I'm also going to have to find a way around the self signed cert issue to get an image from the tinycam server...

Thanks for the info!

You'd have to tweak the app code I think to achieve your single device approach. At the moment the parameter in the Webhook sent from tinyCam drives different virtual motion sensors as you know. So you could tweak the code to interpret these events instead to drive a single device I suppose. Depends really on your end game, whatever you are trying to achieve. Maybe keep it simple as it is now and use Rule Machine to drive other automations?

I didn't run into any certificate issues with tinyCam so I don't think I can help you with that, sorry. Hope you get it resolved. Please keep us posted on your progress!

Good Evening. Angus_M, could you please guide me on setup a camera with tinyCam? I am using iPhone and HE C-7. Where from I can get the driver and Apps? Is there any tested camera that can be used? I can use POE, or RJ45 with power supply. Please help. Thanks

Hi.

Best to check online for setup instructions for tinyCam and to select a camera that is supported by tinyCAM. This may help...

https://tinycammonitor.com/support.html

I'm just running a powered Samsung Smartcam and it works perfectly well.

Remember you need to have tinyCam running on an always-on computer somewhere on your LAN (eg. a tablet or old phone you can leave in the property to run the tinyCam webserver).

Then install the apps (parent and child) on your HE hub by copying the code above in this thread (around post 19 for the parent app and post 42 for the updated child app). Yeah, I know I should get these into HPM :weary:

As long as the connection between your camera(s) and tinyCam works in the "live view" of tinyCam then everything else should be fine. Remember to put your camera in tinyCam into "background mode" if you want to run other apps on your always-on device and tinyCam will go into the background but still sense on motion/objects.

Please also note that this approach using my app in the thread above is best suited to powered cameras, not battery powered cameras. This is because tinyCam will continually access the camera for motion/object detection, leading to rapid power decline if it's battery powered.

There is no special driver required. Just create a virtual motion sensor in HE for each type of motion/object detection you want for each camera. So you could have 3 for a camera (eg. cam1_motion, cam1_person, cam1_face if you want to trigger on motion, person and face detection for that camera). Then when you run the parent app which you use to create a child for each camera, select those virtual motion devices to link to the Webhook that the app generates. Then stick that Webhook into the motion menu of tinyCam for that camera.

I posted more instructions above and various screenshots of the menus to assist above in the thread (around post 28 I think).

Good luck!

1 Like

Good Morning
Thanks for detailed information. I am going to order camera soon and install them as per suggestions, Thank you