Improving the Motorola Blink Baby Monitor/Camera
So we recently purchased the Motorola Blink1 Wifi Baby Monitor (Is this the first blog post acknowledging the baby? He's due any day!) and it's neat hardware with SHIT software. Straight up, it doesn't do most things you would want. I'm working to reverse engineer this and make it workable...particularly on linux.
Before I start...MAD PROPS to Simon Aldrich, whose article laid the foundation to what I'm trying to do here. This got me started: Hacking the Motorola Blink 1 Baby Monitor (Part 1) and Hacking the Motorola Blink 1 Baby Monitor (Part 2)
So, here's the goals for this product that we're trying to produce...I'll bold/underline the ones that DON'T come out of the box:
- At-home video on a mobile device
- At-Home video on a PC
- At-home continuous audio on a mobile device
- At-home continuous audio on a PC
- At-home continuous audio/video on a mobile device
- At-home continuous audio/video on a PC
- At-home notifications of events in the room
- At-home motion control of the camera
- Remote viewing with access control
- Remote video on a mobile device
- Remote video on a PC
- Remote continuous audio on a mobile device
- Remote continuous audio on a PC
- Remote continuous audio/video on a mobile device
- Remote continuous audio/video on a PC
- Remote notifications of events in the room
- Remote motion control of the camera
- Remote 2-way communication with the baby on a mobile device
function createImageLayer() {
var img = new Image();
img.style.position = "absolute";
img.style.zIndex = -1;
img.onload = imageOnload;
img.onclick = imageOnclick;
img.width = 640;
img.height = 480;
img.src = "/?action=snapshot&n=" + (++imageNr);
var webcam = document.getElementById("webcam");
webcam.insertBefore(img, webcam.firstChild);
}
// Two layers are always present (except at the very beginning), to avoid flicker
function imageOnload() {
this.style.zIndex = imageNr; // Image finished, bring to front!
while (1 < finished.length) {
var del = finished.shift(); // Delete old image(s) from document
del.parentNode.removeChild(del);
}
finished.push(this);
current_time = new Date();
delta = current_time.getTime() - previous_time.getTime();
fps = (1000.0 / delta).toFixed(3);
previous_time = current_time;
if (!paused) createImageLayer();
}
This is done so that you're never waiting for a redraw. You always have an image up, and the one loading is behind it. You can see that imageNr variable gets out of hand...I'm thinking about implementing something like "If imageNr gets to 10,000 then set it to 1" which will set the number over. It would create a single potential flicker for one frame...if we consider that it seems to run at about 25fps, that's 1 flicker, for 1/25th of a second, every 7 minutes. I think that's reasonable to achieve "always on" functionality for the monitor.
This "video only" page also has ALL the controls listed, and NO access control. That's absurd. I often leave on vacation and leave a camera on for my dog, and then my parents can look in (since they don't travel much). I wanted to have this feature, but the only ways it seems to provide access are with an account (it's 1 account/camera pair, so I'd have to give viewers my real login to plug into an app OR the Motorola web interface) or to point them to the totally unprotected URL on the LAN. Did I mention you can talk to the baby and play music to the baby from the camera? I don't want strangers doing that! My goal here is to use a supplemental web server to route content, where I can implement access control.
Now, there's the 2nd page. This is http://IP/java.html. This one actually has an embedded java player which plays streaming audio and video. Now...I use chrome on linux, and since NPAPI is now depreciated I can't play the applet, but since wife uses windows, I was able to view it there. Looking at the source, you see the following code block:
<applet code="com.charliemouse.cambozola.Viewer" archive="/cambozola.jar" width="512px" height="384px" >
<param name="url" value="/?action=appletvastream"/>
<param name="watermarks" value="/favicon.png|left,top"/>
</applet>
Cambozola...a little googling, voila. It's an open source embedded java video player: website. This tells me, if we can find the stream, maybe we can use an HTML5 player in a website to pass access. This could be the holy grail.
So, I took the address for the stream and plugged it into VLC...BOOM video. Viewing the codec information, it's a Motion JPEG Video (MJPG) that is decoded into Planar 4:2:0 YUV full scale. In VLC...I get no audio whatsoever. On a hunch, I plugged the following streams into VLC as well: /?action=appletvstream and /?action=appletastream Both resolve! Now, the audio doesn't produce anything, but it seems there's 3 options for streams. The audio stream is exactly what the wife is looking for, so this is where we need to focus the research.
When I load the java applet on wife's windows computer, I get audio. When I load the stream in VLC on the same computer, no audio. Now, the links all resolve in VLC, so I have to assume that the reason it works in the java player is that they have baked some sort of custom codec into the Cambozola player. I'll need to pull this out.
So, let's go back to Cambozola. Technically, it's GPL v2, so the source should be available, right? HMM NOT FINDING IT OOPS, Stallman is goan be PISSED. Nevertheless, I have a Java decompiler, so I'm good. It seems they baked in v0.80. On cambozola's website, it doesn't link to it directly, but the code is there: http://www.charliemouse.com:8080/code/cambozola/cambozola-0.80.tar.gz If we open up the .jar included with the camera, there's 2 suspicious classes which are not in the stock cambozola jar...ADPCM and ADPCM Decoder. There's another one called "PlayAudio" which I'm interested in. Since this is all GPLv2...let's dig in:
PlayAudio.Class
package com.charliemouse.cambozola.shared;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.Line;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.SourceDataLine;
public class PlayAudio extends Thread
{
private AudioFormat m_audfm;
private SourceDataLine m_line;
private boolean m_bRunAudio;
private boolean m_bPlayAudio;
private int m_aud_idx;
private int m_audRec_idx;
private byte[][] m_AudBuf;
private int[] m_AudBufLen;
private int[] m_AudBufStatus;
public PlayAudio()
{
this.m_bRunAudio = true;
this.m_bPlayAudio = true;
this.m_aud_idx = 0;
this.m_AudBuf = new byte[16][16160];
this.m_AudBufLen = new int[16];
this.m_AudBufStatus = new int[16];
this.m_audRec_idx = 0;
this.m_audfm = new AudioFormat(8000.0F, 16, 1, true, false);
try
{
this.m_line = AudioSystem.getSourceDataLine(this.m_audfm);
this.m_line.open(this.m_audfm, 16160);
}
catch (LineUnavailableException e)
{
}
}
public void init()
{
}
public void setPlayAudio(boolean bPlayAudio)
{
this.m_bPlayAudio = false;
for (int i = 0; i < 16; i++)
{
if (this.m_AudBufStatus[i] != 0)
{
Util.memSet(this.m_AudBuf[i], 1010, (byte)0);
}
}
}
public void getAudio(byte[] audioData, int audioLen) {
int i = 0;
if (!this.m_bPlayAudio) {
return;
}
if (audioLen > 4040)
{
return;
}
while (i < audioLen)
{
int len;
int len;
if (i + 16160 <= audioLen)
len = 16160;
else {
len = audioLen - i;
}
if (this.m_AudBufStatus[this.m_audRec_idx] == 0)
{
this.m_AudBufStatus[this.m_audRec_idx] = 1;
System.arraycopy(audioData, i, this.m_AudBuf[this.m_audRec_idx], 0, len);
this.m_AudBufLen[this.m_audRec_idx] = len;
this.m_AudBufStatus[this.m_audRec_idx] = 2;
i += len;
this.m_audRec_idx = ((this.m_audRec_idx + 1) % 16);
}
else
{
try
{
Thread.sleep(100L);
} catch (InterruptedException ie) {
break;
}
}
}
}
public void run()
{
this.m_line.start();
while (true) if (this.m_bRunAudio)
{
if (this.m_bPlayAudio == true)
{
if (this.m_AudBufStatus[this.m_aud_idx] != 2) {
try
{
Thread.sleep(20L);
} catch (InterruptedException ie) {
break label176;
}
}
this.m_AudBufStatus[this.m_aud_idx] = 1;
if (this.m_AudBufLen[this.m_aud_idx] > 0)
{
this.m_line.write(this.m_AudBuf[this.m_aud_idx], 0, this.m_AudBufLen[this.m_aud_idx]);
this.m_AudBufLen[this.m_aud_idx] = 0;
this.m_AudBufStatus[this.m_aud_idx] = 0;
this.m_aud_idx = ((this.m_aud_idx + 1) % 16); continue;
}
this.m_AudBufStatus[this.m_aud_idx] = 0;
this.m_aud_idx = ((this.m_aud_idx + 1) % 16); continue;
}
try
{
Thread.sleep(1000L);
}
catch (InterruptedException ie)
{
}
}
label176: this.m_line.drain();
this.m_line.flush();
this.m_line.close();
}
}
Now...I wanted to get some info about the actual web server this is running...so I ran this command:
surfrock66@sr66-darter:~/Downloads$ wget --save-headers 192.168.1.68/blinkhome.html
--2014-08-16 13:43:34-- http://192.168.1.68/blinkhome.html
Connecting to 192.168.1.68:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: unspecified
Saving to: ‘blinkhome.html’
[ <=> ] 11,594 --.-K/s in 0.03s
2014-08-16 13:43:34 (416 KB/s) - ‘blinkhome.html’ saved [11594]
surfrock66@sr66-darter:~/Downloads$ cat blinkhome.html
HTTP/1.0 200 OK
Content-type: text/html
Connection: close
Server: MJPG-Streamer/0.2
Cache-Control: no-store, no-cache, must-revalidate, pre-check=0, post-check=0, max-age=0
Pragma: no-cache
Expires: Mon, 3 Jan 2000 12:34:56 GMT
So...this is "MJPG-Streamer" version 0.2. Turns out, open source! GPLv3! https://code.google.com/p/mjpg-streamer/ Now we maybe can get into the API a bit. Right off the bat, we see a new stream address...http://IP/?action=stream which DOES resolve in VLC...but still no sound. That I can tell, mjpg-streamer comes with no audio support...I imagine the stream calls to appletvastream and appletastream are custom by Motorola.
In digging through the source, I found this: https://code.google.com/p/mjpg-streamer/source/browse/trunk/mjpg-streamer-1.6.3/plugins/input_uvc/dynctrl.c If we could get the modified source from Motorola, we could probably identify the specific commands this camera can accept...including the audio in, playing lullabyes, etc.
Now that we've identified the 2 open source components, which have both been heavily modified...I think it's time to chase Motorola down, as they're now legally obliged to provide us the modifications they made to these 2 utilities. I opened the manual included...no info about the GPL v2 or v3 at all. I submitted a ticket to Motorola...and pinged the FSF in case Motorola won't cough up the source.
Here's a reference to the pages I've found on the device so far:
- http://IP/index2.html
- http://IP/blinkhome.html
- http://IP/java.html
- http://IP/javascript.html
- http://IP/routersetup.html
- http://IP/wifisetup.html
- http://IP:8080/fwupgrade.html
- http://IP:8080/cgi-bin/online_upgrade
- http://IP:8080/cgi-bin/upload
- http://IP:8080/style.css
- http://IP:8080/fix.css
- http://IP:8080/functions.js
- http://IP:8080/cambozola.jar
- snapshot - Take a picture
- stream - Stream video only
- command - Send a command
- appletvstream - Stream video only
- appletastream - Stream audio only
- appletvastream - Stream audio and video
- reset_pan_tilt - Reset Camera Position (NO EFFECT, returns -1)
- value_contract - Unknown
- value_contrast - Retrieve Contrast Value (NO EFFECT, returns -1)
- value_brightness - Retrieve Brightness
- value_temperature - Retrieve Temperature
- value_resolution - Retrieve Resolution
- contrast_plus - Increase Contrast
- contrast_minus - Decrease Contrast
- brightness_plus - Increase Brightness
- brightness_minus - Decrease Brightness
- fb_stop - Stop ongoing up-down tilt command
- move_backwardx.y - Tilt Up, amount x between -3 and 3, y between 0-9
- move_forwardx.y - Tilt Down, amount x between -3 and 3, y between 0-9
- lr_stop - Stop ongoing left-right tilt command
- move_leftx.y - Tilt Left, amount x between -3 and 3, y between 0-9
- move_rightx.y - Tilt Right, amount x between -3 and 3, y between 0-9
- VGA640_480 - Set resolution to 640x480
- QVGA320_240 - Set resolution to 320x240
- QQVGA160_120 - Set resolution to 160x120
- led_on - Turns the LED ON (NO EFFECT, returns -1)
- led_off - Turns the LED OFF (NO EFFECT, returns -1)
- led_auto - (NO EFFECT, returns -1)
- led_blink - Blinks the LED (NO EFFECT, returns -1)
- reset_factory - Factory Reset the camera
- pan_plus - Move Right (NO EFFECT, returns -1)
- pan_minus - Move Left (NO EFFECT, returns -1)
- pan_set&value=## - Set Pan Position (NO EFFECT, returns -1)
- tilt_minus - Move Down (NO EFFECT, returns -1)
- tilt_plus - Move Up (NO EFFECT, returns -1)
- tilt_set&value=## - Set Tilt Position (NO EFFECT, returns -1)
- setup_wireless_read - Unknown (NO EFFECT, returns -1)
- dummy_request - Unknown (NO EFFECT, error page)
- gain_plus - Increase Gain (NO EFFECT, returns -1)
- gain_minus - Decrease Gain (NO EFFECT, returns -1)
- focus_plus - Increase Focus (NO EFFECT, returns -1)
- focus_minus - Decrease Focus (NO EFFECT, returns -1)
- focus_set&value=## - Set Focus Value (NO EFFECT, returns -1)
- saturation_minus - Decrease Saturation (NO EFFECT, returns -1)
- saturation_plus - Increase Saturation (NO EFFECT, returns -1)
- reset - Unknown (NO EFFECT, returns -1)
Great stuff, great continuation of Simon’s site. What I’m really trying to do is edit the firmware on the camera–I’m sure it’s possible to change one of the audio files located within the camera to something more useful.
Keep posting, I’ll keep reading.
I’m about to post my part 2…I’m still waiting on this. I have the firmware, and I see the files…it’s got to be as simple as overwriting the wav files. I actually tried to recompile the firmware by making my own romfs bin with a new page on the web server…the filesize was much bigger and it never flashed. I have a question into a moto developer but I don’t know if i’ll get a response.
Thank you for doing this.
The PTZ and lullaby commands work on the IOs applications. To see the HTML commands you can sniff the air with AirPcap or a wireless LAN controller. Some AP will also let you sniff to a cap file. Then use wire shark to decode the packets and reassemble the data streams.
Great stuff here. Any understanding of how to access the camera outside of the LAN? There is a camera ID printed on the camera, and I am wondering if there is some combination of the ID and a monitoreverywhere domain name that will resolve to the camrea.
Working on it. From a very baseline level, port forward 80 to it from some other port on your router (choose a random one so creeps can’t get in). Then you can access it from remotely, using the blinkhome.html. You get no audio at this point. BTW…I use a cool cpanel script to always update a subdomain of this website to point to my home IP; if comcast changes my IP, from inside the network I have a cronjob on my linux server ready to communicate it back up to cpanel.
Now, if Motorola EVER gets me the source, there’s 2 things. By allowing Cross orign resource sharing, we can embed the actual images in a page and then you can implement access control on that page (so, I could have surfrock66.com/babycam with an embedded feed which you need to enter a password to reach). The other problem is the java player…which I can replace with a custom html5 one, if I can figure out what they’re doing with the audio. The audio is still a complete mystery. mjpg-streamer has no audio support, but they’ve added it, and even though VLC can’t hear it…their custom cambozola spin can. Once they open up the source (again, both are GPL so they HAVE to publish their changes) I think I’ll be able to either replace the player, or possibly update the actual streamer to do something cooler like mp3 for easier compatibility.
I just got one from meh.com, a Blink1-S.
It’s fresh out of the box and I haven’t downloaded any phone apps or set it up yet. I turned it on and noticed that it’s broadcasting an SSD with the same name as the “ID” printed on the bottom, “Camera-xxxxx”. I connected my computer to it and I got a 192.168.2.10 ip address. The 192.168.2.1 address (camera) has port 80 and port 8080 open according to nmap. I try to connect my web browser to both ports and it asks for a user name and password. I can’t figure it out though, so I can’t get in. Any clues?
No idea, mine didn’t ask for a login, can you try 192.168.2.1/blinkhome.html or 192.168.2.1/java.html ?
Try user / pass as the username / PW requirements.
Looks like the same base firmware described for the motorola FOCUS66 @ http://atom0s.com/forums/viewtopic.php?f=2&t=45
In the new models of this monitor.
they have secured the camera with very hard to predict user and password for web interface.
Could anyone tell if he has any idea about these default credentials or where can i get them from ??
Thanks for advice
Hay.
user
pass
and it works!
OK. more info on Google.
Your hack is much better than those, but any way
http://blok.tiyun.de/2015/view-your-hubble-camera-stream-whereever-you-want
http://atom0s.com/forums/viewtopic.php?t=45
http://simon.aldrich.eu/blog/2013/08/motorola-blink1-hacking/
I’ve written some instructions to embed the live feed in a web page having picked up various bits from here and there, this page included. You can check it out at http://blackchili.co.uk/tech-info/focus66embed/ – Paul
Ignore above for now – my camera got its firmware auto-updated last night and now it no longer works. D’oh!
The fact that the running code unpacks in each run SUCKS. It means each change has to be a firmware update…I tried doing one of those with a custom firmware…it didn’t go well and I bricked the camera 🙁
Is anyone still hacking away at this camera? I used it on an old iPad until it mistakenly got updated to iOS 12 and not the ME app is dead. I can’t browse to the web interface of the camera but it works on a REALLY old device that ME runs okay on. Any help would be appreciated on getting access to this cam… I’m running ISPY and considered using it to manage the camera but I can’t get to it. Thanks in advance for any input!
Sort of, I use the built-in web server to do timelapses on my 4 blinks. I never reverse-engineered the audio stream, and while Motorola released the source I bricked a camera trying to compile my own firmware.
I use Android so the app is still fine.