Getting Wyze Cameras to work with openHAB - An approach Rube Goldberg would approve

I’ll start off by making is clear that I’m not advocating for others to adopt this approach. It’s overly reliant on far too many moving parts to be of general practical use and it depends on a number of work arounds. However, some parts of this may be useful to others so I’ll post it here.

Problem Statement

I have a couple of Wyze cameras. I’d like to be able to see stills every ten seconds or so on my MainUI page so I can quickly check to see if the door is open and verify it’s safe to open/close the door.

However, Wyze doesn’t supply an API (which is a shame because I bet there are thousands on this very forum who would buy their devices if there were a binding). They do supply an alternative firmware that enables local access to an RTSP stream but that firmware is not kept up to date and it can cause havoc on your local network. All of the reverse engineered and cracked firmwares have been abandoned. And if you use this firmware, you have to give up on access to any new features or services Wyze provides.

You may have better luck with the alternative firmware. But I’m stubborn and wanted to have access to the streams with the stock firmware.

Unfortunately all the big players in CCTV software such as ZoneMinder, Shinobi, or BlueIris all require one to replace the firmware to enable the RTSP stream. But there is one CCTV program that can access the Wyze camera stream through Wyze’s cloud service, TinyCam Pro.

The problem with TinyCam Pro is it’s an Android only app.

Approach

  1. Install Android into a VM with Tasker and droid-VNC-NG for some convenience
  2. Install TinyCam Pro onto that VM
  3. Configure TinyCam Pro to access the Wyze Cameras (note if you are still on a Nest account it can also connect to Nest Hello and Nest cameras)
  4. Configure TinyCam Pro’s recording and notification capabilities
  5. Configure openHAB’s IP Camera binding
  6. Future work/possibilities

1. Android in a VM

I already have an ESXi server so it made the most sense for me to install Android in a VM using https://www.android-x86.org/. This is based on LineageOS 14.1 which is Android version 7. While that’s not great it’s adequate for our purposes. However, alternative approaches include:

Approach Advantages Disadvantages
Use an old Android phone or tablet Less work up front Awkward form factor, uncomfortable running a server on completely unsupported software
Use Android on an RPi Requires a screen, keyboard and mouse or touch screen to set up and configure, no headless config Requires an RPi 3 or RPi 4
Put it on your “production” phone Less work up front Disables the camera feed when not on the LAN or end up using lots of mobile data constantly streaming video
Virtual Machine on a server Always running and on local RAM, more power than a typical phone Probably not the best choice unless already running VMs as it’s a lot of work to get set up initially.

As is clear, I chose the VM option.

I’m running ESXi 6.7 so I followed the instructions at How to Install Android on VMware: A Step-by-Step Guide. Some places where I deviated from those instructions:

  • I downloaded and used the 14.1-r5 64-bit ISO file.
  • The OS type was "Other 4.x or later Linux (64-bit).
  • I gave the VM more local storage than 8 GB, this is after all going to become a CCTV server.
  • I also gave the virtual video card a bit more memory, though I don’t know if that does anything worth while.
  • When you get to the GRUB menu what you need to do differs slightly from that tutorial. You do need to edit the first entry as described but after making the changes (i.e. replacing “quiet” with “nomodeset xforcevesa”) you need to hit ENTER. Then hit “b” to boot to the newly edited boot config. This will not be saved.
  • Pressing CTRL-F1 to open a terminal didn’t work for me, probably because I’m using the ESXi console and the local OS captures the keypresses. So to edit the grub config as described I just rebooted the VM without making changes to the GRUB entry. That prevents the GUI from loaded and gives you a command prompt. There is a Terminal Emulator app which I discovered later that might have also worked without the reboot.

Once you are up and running one of the first things you’ll want to do is prevent the VM from going to sleep. There is no need and it’s a pain to swipe up with the mouse to wake it up. To do this you need to enable the Developer options menu. Open Settings → About Tablet and click on the build number entry seven times. Then you can back up and go into the new Developer options menu and turn off Sleep by toggling on “Stay awake”.

Go through the menu and enable, disable any other settings desired.

Now go through the apps and remove/disable any that you don’t need like the phone app.

If you use static IPs, now is the time to configure the DHCP server or configure a static IP in the WiFi settings of the VM.

Tasker

You do not need Tasker for this. For an app it’s on the expensive side but if you already have paid for it there is no reason not to use it. In this case one of the problems is that we do not have VM Tools installed and we don’t have a physical power button. So the only way to reboot the VM is to do the equivalent of yanking the power. So I create a task in Tasker to reboot the phone and added a shortcut to that on the desktop. Add one for shutdown too if desired.

droid-VNC-NG

This is an app that installs a VNC server on Android and it doesn’t even require root which is nice. This will make it easier to remotely access the VM/computer. It can be found in the Play store or any of the alternative app stores you may prefer.

However note the limitations, it will only work with the soft keyboard in Android and not work with keyboard events from a VNC client. So make sure to keep the keyboard app enabled.

I’ve actually not played with this much as I’m OK accessing it through the ESXi UI.

TinyCam Pro

Install TinyCam Pro. It has been so long I don’t remember the difference between the free and paid app. I use the paid app.

I’m not going to go through everything. There are docs for that. I’m just going to touch on the outlines of the configuration.

  1. Go to “Manage cameras”
  2. Click the + icon to create a camera and select “Add IP camera, NVR/DVR”. Note that TinyCam can be used to turn an android phone into a WiFi camera and there is some network discovery built in.
  3. Give the camera a name and select Wyze Labs as the Camera brand. Select the model of your camera. Keep the protocol as Cloud (if you have RTSP you can skip all this and use the IP Camera Addon in OH for all of this). Enter your Wyze account credentials and under Advanced settings you’ll need to select the camera feed. In my experience you’ll need to figure out which camera goes with which number feed via trial and error.
  4. Set all your preferred recording settings. I use 8 GB for the recording quota and I’ve a NextCloud service configured for cloud recording. I will probably adjust this as time goes on to keep just a couple days worth of recordings. You can also record to an FTP server on your LAN which might be easier to set up. You will want to get the recordings off the “tablet” for ease of access one way or the other.
  5. Review and set any Motion Detection and Audio settings desired. Note we will be coming back to configure the Webhook on motion later.

Repeat for each camera you want to use with TinyCam Pro.

Additional settings to consider:

  • Under “UI & Behavior” turn on “Auto start sequence mode on live view” to automatically rotate between the cameras, turn on “Auto start live view on boot”, turn on “Auto start background mode on boot” and turn on “Auto start web server on boot”.
  • If you are on physical hardware, you might get some improvement by using Hardware for the H.264/H.265 decoder under “Video settings”.
  • Under “Recording settings” there are options to set the size of each video size and how much time before and after a motion event to record (only available when background mode is on).
  • Under “Web server” you will need to configure a password for the admin user and/or the Guest user.
  • Make sure that the Web server is turned on. It will tell you it’s running in a notification and in the UI with the URI for the server.

IP Camera Add-on

My needs for these cameras in openHAB are very modest. All I want is to show the view with a refresh rate of around 10 seconds. There is a ton more one can do with the IP Camera add-on.

  1. Install the binding
  2. Create an RTSP/HTTP IP Camera Thing
  3. Enter all the relevant configuration settings.
    • IP Address: hostname or IP address of the Android VM
    • Port: 8083 unless changed in TinyCam Pro
    • Snapshot URL: http://<ip address>:<port>/axis-cgi/jpg/image.cgi?camera=<TinyCam Pro camera number>
    • MJPEG URL: http://<ip address>:<port>/axis-cgi/mjpg/video.cgi?camera=<TinyCam Pro camera number>
    • Server Port: a unique number not already in use on this machine, each camera needs it’s own port number
    • Username: admin or guest depending on how you configured the webserver access in TinyCam Pro
    • Password: password that corresponds with the username
    • Poll Time: smaller number means more resources
    • Update Image Channel When: Image channel follows pollImage

I am running in Docker so don’t really have access to ffmpeg so didn’t do much there. Here is an example Thing.

UID: ipcamera:generic:garage
label: Garage Camera
thingTypeUID: ipcamera:generic
configuration:
  mjpegOptions: -q:v 5 -r 2 -vf scale=640:-2 -update 1
  ipAddress: arachne
  mjpegUrl: http://arachne:8083/axis-cgi/mjpg/video.cgi?camera=1
  updateImageWhen: "1"
  gifPreroll: 0
  ffmpegLocation: /usr/bin/ffmpeg
  serverPort: 9999
  ffmpegOutput: /etc/openhab/html/camera1/
  ipWhitelist: DISABLE
  mp4OutOptions: -c:v copy -c:a copy
  pollTime: 10000
  password: password
  port: 8083
  snapshotUrl: http://arachne:8083/axis-cgi/jpg/image.cgi?camera=1
  snapshotOptions: -an -vsync vfr -q:v 2 -update 1
  username: admin
  ffmpegInput: http://arachne:8083/axis-cgi/mjpg/video.cgi?camera=1
  hlsOutOptions: -strict -2 -f lavfi -i aevalsrc=0 -acodec aac -vcodec copy
    -hls_flags delete_segments -hls_time 2 -hls_list_size 4
  gifOutOptions: -r 2 -filter_complex
    scale=-2:360:flags=lanczos,setpts=0.5*PTS,split[o1][o2];[o1]palettegen[p];[o2]fifo[o3];[o3][p]paletteuse

Given my use case I’ve only linked Items to two of the Channels.

Item Type Channel Purpose
Switch Poll Image When ON the binding will poll for the latest frame from the camera based on the Poll Time setting
Image Image When the Poll Image Item is ON, it will be populated with the actual latest image from the camera based on the Poll Time setting

If all went to plan the Image Item will start to be set to the camera feed periodically when the Switch is turned ON.

Page Widget

To show how I’m using this here is the widget I created to put on my overview page.

This shows the camera in the Garage and lets me trigger the garage door openers.

NOTE: Make sure the date/time get’s stamped on the images in the Wyze app or in TinyCam Pro so you can tell if you are looking at a recent view.

uid: garage_widget
tags:
  - card
  - garage
props:
  parameters: []
  parameterGroups: []
timestamp: Feb 12, 2021, 10:02:20 AM
component: f7-card
config:
  title: Garage Doors
slots:
  default:
    - component: f7-row
      slots:
        default:
          - component: oh-image
            config:
              item: GarageCamera_Image
              style:
                width: 100%
                height: auto
    - component: f7-row
      config:
        class:
          - justify-content-left
      slots:
        default:
          - component: f7-col
            slots:
              default:
                - component: oh-label-card
                  config:
                    footer: Small Garage Door Opener
                    action: command
                    actionItem: Small_Garagedoor_Opener
                    actionCommand: ON
                    label: '=(items.Small_Garagedoor_Sensor.state == "OPEN") ? "close" : "open"'
                    item: Small_Garagedoor_Sensor
                    icon: '=(items.Small_Garagedoor_Sensor.state == "CLOSED") ? "f7:house" : "f7:house_fill"'
                    iconColor: '=(items.Small_Garagedoor_Sensor.state == "CLOSED") ? "green" : "orange"'
          - component: f7-col
            slots:
              default:
                - component: oh-label-card
                  config:
                    footer: Large Garage Door Opener
                    action: command
                    actionItem: Large_Garagedoor_Opener
                    actionCommand: ON
                    label: '=(items.Large_Garagedoor_Sensor.state == "OPEN") ? "close" : "open"'
                    icon: '=(items.Large_Garagedoor_Sensor.state == "CLOSED") ? "f7:house" : "f7:house_fill"'
                    iconColor: '=(items.Large_Garagedoor_Sensor.state == "CLOSED") ? "green" : "orange"'

Webhook Config (may not be working)

TinyCam supports webhooks but it only supports HTTP GET commands. OH doesn’t support updating Items with GET commands. However, one way to work around that is to build an HTML page with a little bit of JavaScript to parse out the arguments passed in the URL and make the correct OH REST API call for us. This is based on the work found at OAuth2 using just OH Rules and myopenhab.org.

Create a String Item for each camera which will receive the motion events from TinyCam Pro. I’ll use GarageCamera_Motion.

Next navigate to each camera’s settings and open the Motion detection settings. Configure the Webhook on motion setting to

http://<ip of openHAB>:8080/static/tinycam.html?item=<item name>&motion=%MOTION_TYPE%

where <ip of openHAB> is the IP or hostname of the openHAB server and ` is the name of the Item that captures the motion detections from this camera.

Copy the following into $OH_CONF/html/tinycam.html. This will be a web page that will receive the GET request from TinyCam, parse out the arguments in the address and command the item with the value of motion.

<!DOCTYPE html>
<html>
	<head>
		<title>OAuth2 Catcher</title>
	<script type="text/javascript">
	function getAllUrlParams(url) {

  // get query string from url (optional) or window
  var queryString = url ? url.split('?')[1] : window.location.search.slice(1);

  // we'll store the parameters here
  var obj = {};

  // if query string exists
  if (queryString) {

    // stuff after # is not part of query string, so get rid of it
    queryString = queryString.split('#')[0];

    // split our query string into its component parts
    var arr = queryString.split('&');

    for (var i=0; i<arr.length; i++) {
      // separate the keys and the values
      var a = arr[i].split('=');

      // in case params look like: list[]=thing1&list[]=thing2
      var paramNum = undefined;
      var paramName = a[0].replace(/\[\d*\]/, function(v) {
        paramNum = v.slice(1,-1);
        return '';
      });

      // set parameter value (use 'true' if empty)
      var paramValue = typeof(a[1])==='undefined' ? true : a[1];

      // if parameter name already exists
      if (obj[paramName]) {
        // convert value to array (if still string)
        if (typeof obj[paramName] === 'string') {
          obj[paramName] = [obj[paramName]];
        }
        // if no array index number specified...
        if (typeof paramNum === 'undefined') {
          // put the value on the end of the array
          obj[paramName].push(paramValue);
        }
        // if array index number specified...
        else {
          // put the value at that index number
          obj[paramName][paramNum] = paramValue;
        }
      }
      // if param name doesn't exist yet, set it
      else {
        obj[paramName] = paramValue;
      }
    }
  }

  return obj;
}
var code = getAllUrlParams().code;
	</script>
	</head>
<script>
var params = getAllUrlParams();
var item = params.item
var cmd = params.motion
document.write("Preparing to command " + item + " with " + cmd);
var xhr = new XMLHttpRequest();
xhr.open('POST', "http://argus:8080/rest/items/"+item);
xhr.setRequestHeader("Content-Type", "text/plain");
xhr.setRequestHeader("Accept", "application/json");
xhr.onreadystatechange = function() {
	if(xhr.readyState == XMLHttpRequest.DONE && xhr.status == 200) {
		document.write("Success! Commanded " + items + " to " + cmd);
	}
	else if(xhr.status != 200) {
		document.write("Error: " + xhr.status + " " + xhr.responseText);
	}
}
document.write("Sent " + cmd + " to " + item);
xhr.send(cmd);
</script>

</html>

NOTE: This is really easy to make generic to support converting any HTTP GET request to an Item command or update. I might post a separate tutorial on that.

You will need to modify the URL to be the path to your openHAB instance. Note that this will be executed on the caller’s machine so that needs to be the URL that TinyCam can use to reach openHAB.

Future Work

There may be other cases where the only way a device can be interacted with is through its Android app. This approach could be use to host that app and then use Tasker and various Tasker add-ons like AutoNotification and AutoInput or it’s relatively new ability to read and react to the system log to interact with the device’s Android app and send/receive information from openHAB.

That of course is an exercise for the reader.

2 Likes

This was a fun read, Rich. Another possible alternative to the VM would be an Android TV box or a Chromecast with Google TV. However, apps would have to be sideloaded if they didn’t have native Android TV functionality.

As you say, it’s a lot of work, and really only worthwhile if someone already has a bunch of Wyze cameras. Anyone else should just buy a camera that’s known to work with OH.

I imagine it was very satisfying the first time you saw the image pop up on your screen. :slight_smile:

The Wyze camera part was really more of a contrived reason to post the tutorial than anything. For the most part, I expect that piece parts of the above tutorial will be useful for people to solve other similar problems.

1 Like

@rlkoshak which vms do you use for cameras with rtsp support? shinobi, zoneminder, blueris, etc?

I don’t run any of those. As the tutorial indicates, I run an Android VM with TinyCam Pro to connect to the Wyze cameras. TinyCam Pro has a web service that works with openHAB’s IP Camera binding. I’ve only the two Wyze cameras and do not intend to expand beyond that.

Have not used any of them but I bump into a lot of info on them around the web.

I think https://shinobi.video/ if your after open source as they now have tensorflow lite object detection from what I hear. A few people use it with openhab and have posted good things.

Blue iris is good but it costs and prefers a windows machine with lots of grunt.

I ran into an official firmware update to the Wyze Camera I have, that enables RTSP directly (but also disables many of the features). I updated the firmware to enable RTSP, then I used the IpCamera Binding to connect to it. Before the official firmware was released I had tried using those open source firmware images that just make you feel dirty and possibly compromised when you use them, and it wasn’t useable in my opinion.

Using the official firmware has been quite nice, compared to the open source alternatives. its pretty stable and has decent quality, overall I’m pleased with it.

I however still am not convinced the camera still isn’t calling home, the setup process still required connecting to their online service. and I haven’t figured out how to block the internet but not local networks for it yet with just a netgear router.