Wyze Cam: Difference between revisions

From Leo's Notes
This page was last edited on 22 September 2022, at 04:06.
No edit summary
Line 700: Line 700:


==== Official firmware ====
==== Official firmware ====
RTSP firmware download: https://download.wyzecam.com/firmware/rtsp/demo_v2_rtsp_4.28.4.49.bin.zip
You can download the firmware files from their servers at <nowiki>https://download.wyzecam.com/firmware/v2/</nowiki>. There is no directory listing, so you'll have to guess the filenames based on the firmware version numbers. The firmware versions are available from their release notes posted on their website at https://support.wyze.com/hc/en-us/articles/360024852172-Release-Notes-Firmware.
{| class="wikitable"
!Version
!Notes
!Download URL
|-
| 4.28.4.49
|RTSP beta
|https://download.wyzecam.com/firmware/rtsp/demo_v2_rtsp_4.28.4.49.bin.zip
|-
|4.9.6.156
|Jul 2020
|https://download.wyzecam.com/firmware/v2/demo_4.9.6.156.bin.zip
|-
|4.95.115
|Aug 2020
|https://download.wyzecam.com/firmware/v2/demo_4.9.5.115.bin.zip
|-
| 4.9.8.860
|Jan 2022
| https://download.wyzecam.com/firmware/v2/demo_4.9.8.860.zip
|-
| 4.9.8.1002
|Mar 2022
| https://download.wyzecam.com/firmware/v2/demo_4.9.8.1002.zip
|}


==== Hacked firmware ====
==== Hacked firmware ====

Revision as of 04:06, 22 September 2022

Wyze Cams are cheap wireless network cameras by Wyze Labs.

Wyze Cam v3

The Wyze Cam v3 is Wyze Lab's newest IP camera released in late 2020. It improves on the v2 with better night vision, higher FPS (15fps to 20fps for day / 10fps to 15fps for night), and is now better weather-proof by being able to operate between -20°C and 40°C. I have 2 Wyze Cams outdoors and exposed to the elements for the entirety of winter 2021-2022 and they're still working without any issues. However, the camera that faces west and is in direct sunlight appears to have a small fracture in its lens but doesn't appear to greatly affect the image quality.

RTSP support

As of 2022, the only official support for RTSP by Wyze Labs is an old beta version of the RTSP firmware (4.61.0.1). Their official statement is this firmware is not supported and recommends against using it. You may still download the firmware and flash it and the Wyze App still allows you to enable RTSP with this firmware version installed.

Why would you want RTSP? Real-time Streaming Protocol (RTSP) support exposes the camera's live video feed via the RTSP protocol. This is typically useful if you want to watch your camera (such as with VLC or MPC-HC), use some sort of network video recording (NVR), or use some sort of motion monitoring/recording system (like with Kerberos.io or Frigate). If you have multiple IP Cameras with RTSP support, you can use something like aler9's rtsp-simple-server to 'collect' all the streams onto one server.

Hacking the Wyze Cam v3

Because the v3 uses the Ingenic T31 chip with secure boot enabled, you cannot load a modified firmware file (demo.bin) on the SD card as you could with the Wyze Cam v2. Rather, you need to rely on other approaches to modifying the firmware. Currently, there are two known options:

  1. Use WyzeHack's OTA update method where your customized firmware is sent instead of the official firmware.
  2. Exploit the fact that uboot attempts to load a file from the SD card called factory_t31_ZMC6tiIDQN which can then be used to sideload hacks.[1] The firmware on the onboard flash memory will remain in tact, making this a very safe and irreversible option with the only downside that you need a SD card.

Community firmware hacks

These hacks work with the latest official firmware released by Wyze. These methods may be preferable to the beta firmware simply because they're more up to date with known vulnerabilities being patched.

  1. wyze-bridge isn't a firmware hack per-se, but it lets you obtain a RTSP steam from your Wyze Cam. This project achieves this by streaming the video data from the Wyze cloud to a local RTMP server. The downside to this is that it wastes lots of internet bandwidth as the video feed is constantly uploaded (and downloaded if you're recording it locally). See: https://github.com/mrlt8/docker-wyze-bridge
  2. wz_mini_hacks project side-loads firmware changes using a SD card. This does not change the firmware on the flash and is very safe to use and experiment with. This project takes the WyzeHacks work a little further and adds additional features. RTSP and RTMP streaming is supported. Installation is extremely easy. See: https://github.com/gtxaspec/wz_mini_hacks
  3. (Old and unsupported now) Use the WyzeHacks project which pushes a modified firmware by faking the Wyze App. This is technically more involved as you must now spoof your DNS. It may also be slightly more risky as you are modifying the firmware on flash that Wyze did not intend. Using the FiveLeavesLeft modification, you can expose a MPEG video stream via HTTP which can be re-encoded to a RTSP stream using ffmpeg. See: https://github.com/HclX/WyzeHacks

More information on each of these hacks below.

Wyze-bridge

Use the wyze-bridge docker stack available at https://github.com/mrlt8/docker-wyze-bridge. This will set up a container that will retrieve live video data from the Wyze cloud using your Wyze credentials and create a RTMP stream which is then pushed to a RTSP server running in another container. The end result is a local RTSP server that broadcasts your Wyze Cam.

The downside to this approach is that the video data is constantly streamed from your Wyze Cam to the cloud and then subsequently downloaded from the cloud to your local RTSP server. This introduces latency and uses lots of bandwidth. My testing with a relatively quiet video feed shows the camera uploading at around 100kb/s which would result in approximately 250GB being uploaded and an additional 250GB downloaded per month.

wz_mini_hacks

wz_mini_hacks is a collection of neat hacks that bundles a bunch of features. You may find it at https://github.com/gtxaspec/wz_mini_hacks. wz_mini_hacks side loads hacks to the official firmware using a SD card as part of the camera's boot process. This hack is completely reversible by either removing the SD card or removing the factory image that's loaded (factory_t31_ZMC6tiIDQN), since nothing is changed on the camera's flash memory itself.

To install the hack, it's suggested that you update to the latest supported version (4.36.9.139), though any versions from around this time frame should be compatible as they all use the same kernel version. If the install process doesn't work, try upgrading your firmware in the Wyze App or by manually reflashing the firmware. Next, copy the contents of the SD_ROOT directory to a FAT formatted SD card. Insert the SD card into the camera and power it on.

LED control

There is a cmd command that sends commands to iCamera. To control the IR LEDs, you can run cmd irled on and cmd irled off.

RTSP support

To get RTSP working, you will then need to edit the file wz_mini/wz_mini.conf file and enable the RTSP options. Here's an example configuration with 2 streams active.

RTSP_LOGIN="admin"
RTSP_PASSWORD="wyzecam"
RTSP_PORT="8554"

RTSP_HI_RES_ENABLED="true"
RTSP_HI_RES_ENABLE_AUDIO="true"
RTSP_HI_RES_FPS=""
RTSP_HI_RES_MAX_BITRATE="51200"
RTSP_HI_RES_TARGET_BITRATE="2048"
RTSP_HI_RES_ENC_PARAMETER="8"

RTSP_LOW_RES_ENABLED="false"
RTSP_LOW_RES_ENABLE_AUDIO="false"
RTSP_LOW_RES_FPS="5"
RTSP_LOW_RES_MAX_BITRATE="512"
RTSP_LOW_RES_TARGET_BITRATE="256"
RTSP_LOW_RES_ENC_PARAMETER="2"

You can either enable one or both streams. The high quality stream is set to 1920x1080 and the low quality stream is set to at 640x360. These resolutions cannot be changed. However, you may adjust the bitrate and FPS settings for each stream independently. The low quality stream may be useful for live previews and real-time object detection (though 360p might be too low resolution for object detection).

If you enable only the high quality stream, the RTSP URL will be rtsp://admin:wyzecam@camera-address:8554/unicast. If you enable both streams, they will be at rtsp://admin:wyzecam@camera-address:8554/video1_unicast and rtsp://admin:wyzecam@camera-address:8554/video2_unicast.

The bitrate values here get applied to the underlying hardware encoder. When left unset, the target bitrate is set to 720kb/s and max bitrate set to 960kb/s. You may choose to set these bitrates higher at the cost of more bandwidth.

The ENC_PARAMETER value specifies the bitrate control modes. The wz_mini hack has some notes on the possible values, though I'm unable to find any other notes that support these yet. Values are:

  • 0. FIXQP / CQP - Fixed/Constant Quantization Parameter. Not supported here.
  • 1. CBR - Constant Bitrate. This forces the encoder to always use the target bitrate. Not supported here.
  • 2. VBR - Variable Bitrate, or 2-Pass Average Bitrate.
  • 4. CVBR / VBV - Constrained VBR or Video Buffering Verifier. Ensures bitrates are constrained to a certain maximum.
  • 8. CQ / CRF - Constant Quality / Constant Rate Factor. This gives a constant quality for the entire stream.

The default value when left unset is 8, or constant quality. Which one should you use? For surveillance applications, probably constant quality (8) if bandwidth isn't an issue, or variable bitrate (VBR) if bandwidth is constrained. You can read more about these different rate controls: https://slhck.info/video/2017/03/01/rate-control.html

Under the hood, these values are set by calling the firmware built-in /system/bin/impdbg utility. To check the current encoding values, just run /system/bin/impdbg --enc_info and look for the chnAttr->rcAttr->rcMode and chnAttr->rcAttr->CappedQuality values.

RTMP support

If you want the Wyze Cam to also push its video stream to a remote RTMP server, you can use the RTMP_STREAM_ENABLED options. However, this option only supports a few specific services such as YouTube, Twitch, and Facebook (based on the source code on the rtmp-stream.sh script). If you need to push it to a custom RTMP server, you will have to edit this script.

Issues with wz_mini_hacks

RTSP Stream codec: I had issues pushing the RTSP stream to the aler9 rtsp-simple-server using the default ffmpeg options. Because the aler9 RTSP server only accepts H264 and AAC codecs only, and because the Wyze Cam RTSP stream is using H265 and PCM, the audio stream has to be reencoded to AAC using ffmpeg.

The issue however is that when using the default ffmpeg command from the wz_mini_hacks RTMP stream script which uses the AAC HE profile, the aler9 RTSP server will drop the connection, resulting in ffmpeg logging av_interleaved_write_frame(): Broken pipe before quitting. The only way to get this working is to not use the aac_he audio profile. The ffmpeg command that seems to work for me is this:

$ /opt/wz_mini/bin/ffmpeg \
  -rtsp_transport udp -y -i rtsp://admin:password@0.0.0.0:8554/unicast \
  -vcodec copy -f flv \
  -c:a libfdk_aac -afterburner 1 -channels 1 -b:a 128k \
  rtmp://rtsp-server:1935/v3f

Even with this tweak, the ffmpeg stream to the aler9 RTSP server is very unreliable and constantly breaks.

Solution - You can tweak the rtsp-simple-server configuration file to automatically stream from the Wyze Cam directly without needing ffmpeg. Edit the rtsp-simple-server.yml config file and add the following:

camera:
    source: "rtsp://admin:wyzecam@10.1.1.30:8554/unicast"
    sourceProtocol: tcp

Note that I use TCP for this stream. Using UDP causes a lot of smearing to occur on my network. Setting it to TCP seems to fix this for the most part.

SSH not starting: For some reason, the SSH service (dropbear) doesn't start. This seems to only happen when I reboot the device and works again after I go in and delete some empty directories on the root of the SD card. Possibly corrupt FAT causing something to not work which gets fixed when I plug it into my computer?

Power cycling the Wyze Cam when it enters this state one more time seems to fix the issue. Could it be the SD cards I'm using needs a power cycle for it to show up properly to uboot?

Update: This is really starting to look like an issue with this particular SD card. Possibly could be a counterfeit item or the card is just starting to die. I can't replicate this on another camera with a different type of SD card (also 16GB).

Solution: The SD card likely is a counterfeit. It's missing the shiny holographic stripe that my other Kingston cards have. Replacing it seems to fix this particular issue.

aler9 rtsp-simple-server gives 'ERR: Content-Length exceeds 131072 (it's 1666223)': Using rtsp-simple-server v.0.20.0 to relay a RTSP stream from the Wyze Cam, I am getting 'ERR: Content-Length exceeds 131072 (it's 1666223)' with the video stream stalling out. This seemed to happen occasionally after enabling audio with the RTSP stream in wz_mini.conf.

Workaround: Restarting the Wyze Cam seemed to fix the issue. I have a container watching the logs and calling reboot when it sees these errors.

WyzeHacks

Possibly unsupported or not working
WyzeHacks hasn't been updated for over a year and Wyze Labs has recently required newer firmware versions to be used by the cameras. As a result, this method is likely not viable anymore. See the wz_mini_hacks option below as an alternative.

Thanks to the work by the authors of the WyzeHacks project (https://github.com/HclX/WyzeHacks) and its modified version by FiveLeavesLeft with the streaming support available (https://github.com/FiveLeavesLeft/WyzeHacks), you can stream video data directly from the camera without needing to download the data from the cloud as is the case with the wyze-bridge option. The hack sets up a h264 video stream on the Wyze cam (the file utility reports the data as a JVT NAL sequence) that need to be converted using ffmpeg and sent to the RTSP server.

To get this working:

  1. Load FiveLeavesLeft's version of WyzeHacks onto the Wyze Cam. More information on installing the hack is below. This method requires your Wyze credentials and works over the air without needing physical access to the device or the SD card. More in the WyzeHacks section below.
  2. Start a local RTMP server. Easiest thing to do is just run the RTSP server container with the following docker-compose stack. Create a docker-compose.yml file with the following contents. Replace the IP addresses as required. Add/remove the ffmpeg conversion containers as required. Make sure you are publishing to a unique path on the RTSP server.
    version: '3'
    
    services:
      rtsp-server:
        container_name: rtsp-server
        image: aler9/rtsp-simple-server
        environment:
          - RTSP_PROTOCOLS=tcp
        ports:
          - "10.1.1.Y:1935:1935"
          - "10.1.1.Y:8554:8554"
          - "10.1.1.Y:8888:8888"
    
      wyzecam-v2:
        image: registry.steamr.com/docker/alpine-ffmpeg:latest
        command: ["sh", "-c", "wget -q -O - 10.1.3.X:12345/ | ffmpeg -f h264 -i - -tune zerolatency -vcodec copy -f flv rtmp://rtsp-server:1935/v2"]
        restart: "always"
    
      wyzecam-v3:
        image: registry.steamr.com/docker/alpine-ffmpeg:latest
        command: ["sh", "-c", "wget -q -O - 10.1.3.X:12345/ | ffmpeg -f h264 -i - -tune zerolatency -vcodec copy -f flv rtmp://rtsp-server:1935/v3"]
        restart: "always"
    
    Place the contents in a docker-compose.yml file and then start up the stack with docker-compose up -d. This should bring up both the RTSP server and the ffmpeg conversions for each camera.
  3. If things are working right, you should be able to access the video feed at rtsp://10.1.1.Y:8554/camera

I've used this hack on 4 Wyze Cam v3 devices and it works without any issues. The only limiting issue is that there is no audio with these streams since the video frames are retrieved using wget from an external server. Furthermore, there's no way to load this hack on newer units.

Remote access

Depending on which firmware hack you installed, you can connect to the Wyze Cam using either telnet or SSH.

The default username and password set on the Wyze Cam v3 is:

  • Username: root
  • Password: WYom2020 or, on newer firmware versions: WYom20200 (passwd hash is aVG8.5PMEOfnQ)

If you are using wz_mini_hacks, you must SSH keys. The Add your SSH public key into the SD_ROOT/wz_mini/etc/ssh/authorized_keys file.

Official firmware notes

Here are some notes on Wyze Cam v3 official firmware.

Download older firmware files

You can download the firmware files from their servers at https://download.wyzecam.com/firmware/v3/. There is no directory listing, so you'll have to guess the filenames based on the firmware version numbers. The firmware versions are available from their release notes posted on their website at https://support.wyze.com/hc/en-us/articles/360024852172-Release-Notes-Firmware.

Version Notes Download URL
4.61.0.1 RTSP beta. /config/app.ver reports 4.61.0.3 https://download.wyzecam.com/firmware/rtsp/demo_v3_RTSP_4.61.0.1.zip
4.36.0.125 Nov 2020, Initial firmware https://download.wyzecam.com/firmware/v3/demo_wcv3_4.36.0.125.bin.zip
4.36.0.228 https://download.wyzecam.com/firmware/v3/demo_wcv3_4.36.0.228.bin.zip
4.36.0.248 https://download.wyzecam.com/firmware/v3/demo_wcv3_4.36.0.248.bin.zip
4.36.0.280 Mar 2021 https://download.wyzecam.com/firmware/v3/demo_wcv3_4.36.0.280.bin.zip
4.36.1.4 May 2021 https://download.wyzecam.com/firmware/v3/demo_wcv3_4.36.1.4.bin.zip
4.36.2.5 Jun 2021, last to not require internet https://download.wyzecam.com/firmware/v3/demo_wcv3_4.36.2.5.bin.zip
4.36.3.19 Aug 2021 https://download.wyzecam.com/firmware/v3/demo_wcv3_4.36.3.19.zip
4.36.6.17 Nov 2021 https://download.wyzecam.com/firmware/v3/demo_wcv3_4.36.6.17.zip
4.36.7.22 Nov 2021 https://download.wyzecam.com/firmware/v3/demo_wcv3_4.36.7.22.zip
4.36.8.15 Dec 2021 https://download.wyzecam.com/firmware/v3/demo_wcv3_4.36.8.15.zip
4.36.8.26 Feb 2022 https://download.wyzecam.com/firmware/v3/demo_wcv3_4.36.8.26.zip
4.36.8.32 Feb 2022 https://download.wyzecam.com/firmware/v3/demo_wcv3_4.36.8.32.zip
4.36.9.131 Apr 2022 https://download.wyzecam.com/firmware/v3/demo_wcv3_4.36.9.131.zip
4.36.9.139 May 2022 https://download.wyzecam.com/firmware/v3/demo_wcv3_4.36.9.139.zip

How to upgrade / downgrade firmware

To upgrade or revert back to a specific firmware on the Wyze Cam, simply extract the .zip file into the root of a SD card. For the Wyze Cam v3, the firmware file should be named demo_wcv3.bin. Insert the SD card into the camera and then power it on while holding the setup button until both blue + red (appearing purple) LEDs are on.[2] The flashing process should take about a minute, though it will likely depend on the speed of your SD card.

As of January 2022, Wyze has restricted the ability to use certain older firmware versions[3] and is forcing users to use an updated Wyze app. I'm unsure what the minimum Wyze Cam version is, though I was able to roll back to 4.36.2.5 without issue.

Side loading hacks

The firmware files for the v3 are signed and verified using ed25519. The signature is placed at the last 64 bytes of the demo.bin file and is checked by uboot. The files on flash don't appear to be verified which is why WyzeHack's OTA firmware update persists.

Fortunately for us, uboot also checks for a file called factory_t31_ZMC6tiIDQN on the SD card and attempts to boot that as the kernel image without any signature verification. You can build a custom image using the instructions at https://github.com/gtxaspec/wz_mini_hacks/blob/master/documentation/developer_environment.md.

Setup a Wyze Cam without the Wyze App or internet

You can indeed set up a Wyze Cam v3 out of the box without the Wyze App and use it as a normal RTSP-enabled IP Camera. This can be accomplished using the wz_mini_hacks firmware hack on an SD card and some tweaks to get the initial setup complete.

  1. Unbox the Wyze Cam v3
  2. Place the latest firmware file on a SD card (I used 4.36.9.139) and insert it to the Wyze Cam
  3. Hold the SETUP button and power on the device. Hold the button down until you see both blue and red lights on
  4. After the upgrade is done, the camera will reboot and begin flashing the red LED. This red LED probably means the device isn't set up and is awaiting you to do something. Power off the camera.
  5. Insert the SD card containing wz_mini_hacks into the camera. When you set up the SD card, ensure that your SSH keys are set in wz_mini/etc/ssh/authorized_keys. We will need to SSH in once the camera comes up to finish up the setup.
  6. Power on the camera. You should hear "WZ Mini initializing" message.
  7. Press the SETUP button once. You should hear "Ready to connect" and should now be waiting for a QR code.
  8. Present it with a QR code with your WiFi settings. If you need to generate this QR code without the Wyze App, see the WiFi QR code section below.
  9. The camera should join your wireless network and you should be able to SSH into it at this point.
  10. SSH in and ensure that:
    1. Your WiFi settings are saved in /configs/.wifissid and /configs/.wifipasswd
    2. Edit the /configs/.user_config file and ensure the bindOK=0 line is set to bindOK=1. This will allow the camera to use these config files on boot. Without this, the camera will reboot back into the blinking red state.
    3. If you want RTSP, edit /opt/wz_mini/wz_mini.conf and ensure RTSP is enabled.
  11. Reboot the camera and verify it can come up and connect to your network.

WiFi QR code

The initial WiFi configuration is sent to the Wyze Cam using a QR code. This QR code can be generated from the Wyze App without needing to connect the camera to their cloud (Just go through the process of adding a new Camera, enter your WiFi settings, then take a screenshot of the generated QR code for future use).

The QR code generated for the Wyze Cam v3 is a URL encoded string containing 6 variables. The string itself will look like this: b=68c7ebd2-07c6-4c&s=ZGQtd3J0&p=NgcDWVcmOzlZUysDFhs2CgoxOyMbChYGEA==&t=-6&r=USA&ty=1.

Here are each value in a bit more detail:

  1. b=[a-z0-9-]{8}-[a-z0-9-]{4}-[a-z0-9-]{2} - A randomly generated alphanumeric string in 3 sections. 8 characters, dash, 4 characters, dash, 2 characters. I'm not sure if this is actually used anywhere by the camera.
  2. s=base64(SSID) - The WiFi SSID in base64
  3. p=base64(xor(password, key)) - The base64 encoded and XOR encrypted password. The key here is Wfb86GZX82JbwzWkkPZBzkwgqtpswmlqqytjfrfxjzccjsyyaylbbdmzspxymdwz.
  4. t=-6 - Your timezone.
  5. r=USA - The country or region? I'm from Canada but the Wyze app still generated it as USA.
  6. ty=1 - Thank-you? I'll take it.

There's a handy dandy tool at https://codepen.io/ril3y/full/gXyzmO/ which can generate the QR code for you in the browser without needing the Wyze App.

Running without internet

Newer versions of the firmware (4.36.2.5 or later?) requires a constant internet connection to function. On these newer firmware versions, the internal iCamera process will reboot the camera when it's unable to contact the Wyze's servers. The reboot happens within about a minute or so and basically renders the device unusable at this state. The reboot is triggered by iCamera, possibly at a kernel or hardware watchdog level because locking down the reboot command isn't sufficient and killing iCamera eventually triggers a reboot as well. Making the Wyze domains resolve to another server is also ineffective as it also triggers a reboot.

The old firmware versions (4.36.0.x ~ 4.36.2.5 including the RTSP beta firmware 4.61.0.1) does not have the issue and I have had no issues using the Wyze Cam on these older versions after placing it on a non-routable network which effectively blocks all internet traffic to the camera. However, the iCamera process will attempt to restart the wireless connection periodically which results in 4-5 second disconnects every minute or so. The camera is functional even with these short disconnections, but it will cause stuttering and dropped frames.

Fixing the WiFi dropouts in iCamera

The iCamera program appears to bring the WiFi interface down and up when it is unable to reach some servers including Wyze's API server and Google. After 3 retries, the process will bounce the WiFi interface, kill and restart the DHCP client and WPA supplicant processes, and rewrite the hosts and resolv.conf files. It is this bouncing that causes the brief 4-5 second disconnects.

Fortunately, tracing down where exactly in the binary that triggers this behavior within the iCamera binary is made easy with the verbose iCamera debug messages (with source filenames and line numbers!) combined with some strace outputs I collected from the process during these dropout periods. There is a function that handles the network setup which sets up the WiFi interface, obtains an IP from DHCP, then attempts to check whether a domain works (DN something in the logs). When the domain check fails 3 times, it calls a few other functions to restart the network setup process over again.

The dropouts can be 'fixed' by overwriting the domain check with no-ops and ensuring that the retry loop never exits by no-opping out the decrement or store instructions. This will make iCamera retry the domain check rather than bouncing the network interface after 3 tries. There are other jobs that spawn via threadpool messages such as to validate the network (net-valid) and to uploading reboot log (upload-rebootlog). There are other calls to functions that send messages to a dongle (probably some other Wyze peripheral?). All these can be no-op'ed and be bypassed.

Here's the patch script that should work for 4.61.0.1 (RTSP beta) and 4.36.9.139 (latest official as of this writing). You can run this directly on the Wyze Cam and then reboot to apply. To revert, copy the original copy of iCamera script from SD_ROOT/wz_mini/usr/bin/iCamera onto your SD card.

#!/opt/wz_mini/bin/bash
set -e
PATH=$PATH:/opt/wz_mini/tmp/.bin

cd /tmp

# Make a copy to patch
cp /system/bin/iCamera iCamera

Version=""

# Verify the iCamera version is supported
MD5Sum=$(md5sum iCamera | awk '{print $1}')
if [[ "$MD5Sum" == "04b90d6d77be72a4dd8c18da4b31946a" ]] ; then
        Version="4.61.0.1"
fi

if [[ "$MD5Sum" == "b1c96d966226d76db86c96ecdfdd79e9" ]] ; then
        Version="4.36.9.139"
fi

if [ -z "$Version" ] ; then
        echo "Unsupported version. MD5sum: $MD5Sum";
        exit 1
fi


# Check to see if the patched version is installed and is up to date
if [ -f /opt/wz_mini/usr/bin/iCamera.patched ] ; then
	# Check the build date. (this check may be brittle?)
	OriginalDate=$(strings /system/bin/iCamera | grep Build -A 1 | tail -n 1)
	PatchedDate=$(strings /opt/wz_mini/usr/bin/iCamera.patched | grep Build -A 1 | tail -n 1)

	if [[ "$OriginalDate" == "$PatchedDate" ]] ; then
		echo "Patch already applied to current iCamera version."
		exit 0
	fi

	echo "Patched iCamera binary differs in build date. ($OriginalDate vs $PatchedDate)."
	echo "Patch is now reapplying."
fi


# Patch out the calls to test cloud url, which calls that DN function
#  This isn't strictly necessary, though it will cause iCamera to constantly retry the tests and spam the iCamera outputs with messages like
#    DN:854]err: (getaddrinfo) fail:-2(Name or service not known), (domain: www.google.com
#  so we'll comment these calls here to make it hush up
function patch_out_calls_to_test_cloud_url() {
        [[ "$Version" == "4.61.0.1" ]] && Address="0x603b0 0x602d4"
        [[ "$Version" == "4.36.9.139" ]] && Address="0x89938 0x89858"

        echo -e "\n\n====> Calling ${FUNCNAME[0]}\n"

        for i in  $Address ; do
                echo -e "\nOriginal at $i"
                dd if=iCamera bs=1 count=4 skip=$(($i)) 2>/dev/null | xxd

                echo "Patched"
                printf '\x00\x00\x00\x00' | dd conv=notrunc of=iCamera bs=1 seek=$(($i)) 2> /dev/null
                dd if=iCamera bs=1 count=4 skip=$(($i)) 2>/dev/null | xxd
        done
}


# Patch out the threadpool_add_job calls for net-valid and upload-rebootlog, and dongle send
function patch_out_jobs_after_connect () {
        [[ "$Version" == "4.61.0.1" ]]   && Address="$(seq 0x070d0 4 0x07114)"
        [[ "$Version" == "4.36.9.139" ]] && Address="$(seq 0x7b184 4 0x7b1cc)"

        echo -e "\n\n====> Calling ${FUNCNAME[0]}\n"

        # Everything up until the last branch instruction
        for i in $Address ; do
                echo -e "\nOriginal at $i"
                dd if=iCamera bs=1 count=4 skip=$(($i)) 2>/dev/null | xxd

                echo "Patched"
                # noop
                printf '\x00\x00\x00\x00' | dd conv=notrunc of=iCamera bs=1 seek=$(($i)) 2> /dev/null
                dd if=iCamera bs=1 count=4 skip=$(($i)) 2>/dev/null | xxd
        done
}


# When our calls to DN check cloud url fails, we run into the code that calls funky_network_function(0). We don't want that. So noop all this out
# All the code that sets DAT_005e4d54=0, prints debug message, and calls funky_network_func(0) is nooped out here.
# This fixes the network from going back to the idle state and bouncing everything
#  The call to print debug message could probably be left intact... we don't actually prevent 005e4d50=0  though..
function patch_out_network_reset_to_idle () {
        [[ "$Version" == "4.61.0.1" ]]   && Address="$(seq 0x6041c 4 0x6045c)"
        [[ "$Version" == "4.36.9.139" ]] && Address="$(seq 0x899a4 4 0x899e4)"

        echo -e "\n\n====> Calling ${FUNCNAME[0]}\n"

        for i in $Address ; do
                echo -e "\nOriginal at $i"
                dd if=iCamera bs=1 count=4 skip=$(($i)) 2>/dev/null | xxd

                echo "Patched"
                # noop
                printf '\x00\x00\x00\x00' | dd conv=notrunc of=iCamera bs=1 seek=$(($i)) 2> /dev/null
                dd if=iCamera bs=1 count=4 skip=$(($i)) 2>/dev/null | xxd
        done
}


# For some reason on 4.36.9.139, iCamera starts the code_test section which just constantly lists /tmp over and over every 10 seconds.
# I don't know why this is getting triggered, so I'm going to patch this out from being called.
function patch_out_code_test_enable() {
        [[ "$Version" == "4.61.0.1" ]]   && return;  # No need to do this as it doesn't seem to be a problem
        [[ "$Version" == "4.36.9.139" ]] && Address="0x7dfcc"

        echo -e "\n\n====> Calling ${FUNCNAME[0]}\n"

        for i in $Address ; do
                echo -e "\nOriginal at $i"
                dd if=iCamera bs=1 count=4 skip=$(($i)) 2>/dev/null | xxd

                echo "Patched"
                # noop
                printf '\x00\x00\x00\x00' | dd conv=notrunc of=iCamera bs=1 seek=$(($i)) 2> /dev/null
                dd if=iCamera bs=1 count=4 skip=$(($i)) 2>/dev/null | xxd
        done
}

patch_out_calls_to_test_cloud_url
patch_out_jobs_after_connect
patch_out_network_reset_to_idle
patch_out_code_test_enable

echo -en "\n\nPatching done. MD5 hash: "
md5sum iCamera


# Place it on the SD card
cp iCamera  /opt/wz_mini/usr/bin/iCamera.patched

# Patch the iCamera script to use the patched executable
sed -i 's/\/system\/bin\/iCamera/\/opt\/wz_mini\/usr\/bin\/iCamera.patched/' /opt/wz_mini/usr/bin/iCamera

This patch isn't quite complete however. I notice that there are DNS lookups to api.wyzecam.com is still being made every 5-7 seconds. Since this is on an isolated network, it might not really matter.

Configuring iCamera without internet

Here are some tips for those that are attempting to run their Wyze Cam on an isolate network or on a network that blocks internet access to these cameras:

  • The Wyze firmware sets the clock using /system/bin/timesync which includes hard coded NTP servers. If these server's hostnames resolve to a local NTP server, it seems like timesync will update the hardware clock when I run it manually.
  • Alternative ways to set the clock is to use the date command (have something telnet/ssh in periodically to set the clock) or use wz_mini_hacks which include the ntpd busybox applet that can be used to sync the clock through your own NTP server.
  • If you need to change a camera setting that's controlled by the Wyze App, the /configs/.user_configs file might be of interest to you (more about this below).

Camera configuration

The local camera configuration is stored in /configs/.user_config. The file itself looks something like this:

[NET]
bindOk=1
p2pid=####################
r_encr=################

[SETTING]
night_cut_thr=1
nightVision=3
night_led_ex=2
NIGHT_LED_flag=2
verSwitch=2
horSwitch=2
indicator=1
watermark_flag=2
recordType=3
drawBoxSwitch=2
bitRate=120
timezone=-6

[CAMERA_INFO]
TZ_STR=MST+0700MDT+0600,M3.2.0/02:00:00,M11.1.0/02:00:00

The [NET] section contains bindOk which should always be set to 1. Setting this to 0 will force the Wyze Cam to read a QR code before connecting to a wireless network. The p2pid and r_encr can be left empty if you never intend to have the camera talk to the cloud.

The [SETTING] section contains configuration keys and are described below. This is an incomplete list and was what I found by looking at the /configs/.user_configs file after changing each setting in the Wyze App. You should be able to change the values in this file and restart iCamera for it to take effect.

key description values
nightVision Enable IR 1 = on, 2 = off, 3 = auto
night_led_ex IR type? 1 = near, 2 = far
night_cut_thr IR threshold 1 = 'dusk mode', 2 = 'dark mode'
indicator Camera LED status lights 1 = on, 2 = off
verSwitch Flip vertically 1 = no flip, 2 = flip
horSwitch Flip horizontally 1 = no flip, 2 = flip
osdSwitch Show timestamp 1 = show, 2 = hide
watermark_flag Show wyze logo 1 = show, 2 = hide
AST Audio recording 1 = on, 2 = off
recordType Record to SD card 1 = continuous, 2 = events only, 3 = no recording
MASwitch Record when motion detected 1 = enable, 2 = disable
AASwitch Record when audio detected 1 = enable, 2 = disable
SASwitch Record when smoke alarm detected 1 = enable, 2 = disable
CASwitch Record when CO alarm detected 1 = enable, 2 = disable
AMALevel Sound detection sensitivity 2 through 254, higher is more sensitive
MMALevel Motion detection sensitivity 2 through 254, higher is more sensitive
drawBoxSwitch Draw the green boxes around objects 1 = enable, 2 = disable

There are other setting keys that can be found in the iCamera startup logs. See below:

<Reading> /configs/.user_config
[user_config.c:373][USER SET] [indicator    ] [2]
[user_config.c:373][USER SET] [nightVision  ] [3]
[user_config.c:373][USER SET] [bitRate      ] [120]
[user_config.c:373][USER SET] [res          ] [1]
[user_config.c:373][USER SET] [fps          ] [20]
[user_config.c:373][USER SET] [horSwitch    ] [2]
[user_config.c:373][USER SET] [verSwitch    ] [2]
[user_config.c:373][USER SET] [osdSwitch    ] [1]
[user_config.c:373][USER SET] [logSd        ] [1]
[user_config.c:373][USER SET] [logUdisk     ] [1]
[user_config.c:373][USER SET] [telntSwitch  ] [2]
[user_config.c:373][USER SET] [recordType   ] [3]
[user_config.c:373][USER SET] [MASwitch     ] [2]
[user_config.c:373][USER SET] [MALevel      ] [5]
[user_config.c:373][USER SET] [AASwitch     ] [2]
[user_config.c:373][USER SET] [AALevel      ] [5]
[user_config.c:373][USER SET] [SASwitch     ] [2]
[user_config.c:373][USER SET] [CASwitch     ] [2]
[user_config.c:373][USER SET] [TBStart      ] [0]
[user_config.c:373][USER SET] [TBDuration   ] [1440]
[user_config.c:373][USER SET] [drawBoxSwitch] [2]
[user_config.c:360]timezone value -6 read from config file
[timesync_utility.c:160]TZ file is set with UTC-06:00[user_config.c:373][USER SET] [timezone     ] [-6]
[user_config.c:373][USER SET] [MMALevel     ] [100]
[user_config.c:373][USER SET] [AMALevel     ] [100]
[user_config.c:373][USER SET] [MAT          ] [0]
[user_config.c:373][USER SET] [AST          ] [1]
[user_config.c:373][USER SET] [motionTrack  ] [2]
[user_config.c:373][USER SET] [motionCruisin] [2]
[user_config.c:373][USER SET] [AASX         ] [25]
[user_config.c:373][USER SET] [AASY         ] [25]
[user_config.c:373][USER SET] [AALX         ] [50]
[user_config.c:373][USER SET] [AALY         ] [50]
[user_config.c:373][USER SET] [motionbackvalue] [1]
[user_config.c:373][USER SET] [slide_x      ] [0]
[user_config.c:373][USER SET] [slide_y      ] [0]
[user_config.c:373][USER SET] [preset_x     ] [0]
[user_config.c:373][USER SET] [preset_y     ] [0]
[user_config.c:373][USER SET] [ubootflag    ] [0]
[user_config.c:373][USER SET] [exposureModel] [0]
[user_config.c:373][USER SET] [limit_max_x  ] [0]
[user_config.c:373][USER SET] [limit_min_x  ] [0]
[user_config.c:373][USER SET] [limit_max_y  ] [0]
[user_config.c:373][USER SET] [limit_min_y  ] [0]
[user_config.c:373][USER SET] [motor_limit_flag] [0]
[user_config.c:373][USER SET] [resolution_select_flag] [1]
[user_config.c:373][USER SET] [low_bitrate_num] [30]
[user_config.c:373][USER SET] [watermark_flag] [2]
[user_config.c:373][USER SET] [speaker_vol  ] [50]
[user_config.c:373][USER SET] [pir_alarm_switch] [0]
[user_config.c:373][USER SET] [NIGHT_LED_flag] [2]
[user_config.c:373][USER SET] [sdcard_exist ] [0]
[user_config.c:373][USER SET] [autoRecordNum] [0]
[user_config.c:373][USER SET] [autoRecordMem] [0]
[user_config.c:373][USER SET] [dongle_led   ] [1]
[user_config.c:373][USER SET] [alarmDate    ] [255]
[user_config.c:373][USER SET] [alarmInterval] [300]
[user_config.c:373][USER SET] [SDEVICE_ACTION] [0]
[user_config.c:373][USER SET] [voice_switch ] [2]
[user_config.c:373][USER SET] [voice_scene  ] [1]
[user_config.c:373][USER SET] [auto_clarity ] [2]
[user_config.c:373][USER SET] [cmc_upload   ] [1]
[user_config.c:373][USER SET] [p2p_log      ] [2]
[user_config.c:373][USER SET] [night_led_ex ] [2]
[user_config.c:373][USER SET] [night_cut_thr] [1]
[user_config.c:373][USER SET] [slightEnable ] [1]
[user_config.c:373][USER SET] [slightMode   ] [2]
[user_config.c:373][USER SET] [slightLowLight] [2]
[user_config.c:373][USER SET] [slightBright ] [255]
[user_config.c:373][USER SET] [hms_enable   ] [0]
[user_config.c:373][USER SET] [hms_state    ] [0]
[user_config.c:373][USER SET] [slightPreemptSw] [0]
[user_config.c:320]timezone string set to [MST+0700MDT+0600,M3.2.0/02:00:00,M11.1.0/02:00:00]

Note that the TZ_STR value is not what libc would expect. If you intend to set the TZ value in Linux as an environment variable, you need to convert the hour-minute values in the first field to be separated by a colon. For example, MST+0700MDT+0600 should instead be MST7:00MDT6:00 or MST7MDT6. In my case, the TZ value should be: TZ=MST7:00MDT6:00,M3.2.0/02:00:00,M11.1.0/02:00:00

iCamera Logs

To see the iCamera output, you'll have to restart iCamera in the foreground. This can be done by running kill -9 $(pgrep iCamera) and then immediately calling /usr/bin/iCamera. There is a hardware watchdog that needs to be satisfied or else the hardware will automatically reset. As a result, you'll have to make sure you start iCamera within 10 or so seconds.

For the RTSP firmware, you'll have to also kill the liveMediaServer for iCamera to come up properly. Run: kill -9 $(pgrep iCamera.patch) ; kill -9 $(pgrep liveMediaServer)

For the wz_mini_hacks, start iCamera with: LD_PRELOAD=libsetunbuf.so /opt/wz_mini/usr/bin/iCamera -- dslog --priority DEBUG --facility USER iCamera

Some observations about the v3

After getting telnet access, here are some stuff about the v3 which might be of interest.

[root@WyzeCam-ED6A:~]# cat /proc/cpuinfo
system type             : Swan
machine                 : Unknown
processor               : 0
cpu model               : Ingenic Xburst V0.0  FPU V0.0
BogoMIPS                : 1391.00
wait instruction        : yes
microsecond timers      : no
tlb_entries             : 32
extra interrupt vector  : yes
hardware watchpoint     : yes, count: 1, address/irw mask: [0x0fff]
isa                     : mips32r1
ASEs implemented        :
shadow register sets    : 1
kscratch registers      : 7
core                    : 0
VCED exceptions         : not available
VCEI exceptions         : not available

Hardware                : isvp
Serial                  : 00000000 00000000 00000000 00000000

## MTD partitions hard coded to cmdline
[root@WyzeCam-ED6A:~]# cat /proc/cmdline
console=ttyS1,115200n8 mem=96M@0x0 rmem=32M@0x6000000 init=/linuxrc rootfstype=squashfs root=/dev/mtdblock2 rw mtdparts=jz_sfc:256K(boot),1984K(kernel),3904K(rootfs),3904K(app),1984K(kback),3904K(aback),384K(cfg),64K(para)

The firmware version can be found in /system/bin/app.ver:

[root@WyzeCam-7419:system]# cat /system/bin/app.ver
[VER]
appver=4.36.0.280

MTD Partitions

There is a 16MB flash. The MTD partitions are coded into the cmdline: mtdparts=jz_sfc:256K(boot),1984K(kernel),3904K(rootfs),3904K(app),1984K(kback),3904K(aback),384K(cfg),64K(para)

On startup, we see the following kernel message.

# dmesg
[    0.433992] 8 cmdlinepart partitions found on MTD device jz_sfc
[    0.440116] Creating 8 MTD partitions on "jz_sfc":
[    0.445056] 0x000000000000-0x000000040000 : "boot"
[    0.450404] 0x000000040000-0x000000230000 : "kernel"
[    0.455910] 0x000000230000-0x000000600000 : "rootfs"
[    0.461366] 0x000000600000-0x0000009d0000 : "app"
[    0.466624] 0x0000009d0000-0x000000bc0000 : "kback"
[    0.472007] 0x000000bc0000-0x000000f90000 : "aback"
[    0.477436] 0x000000f90000-0x000000ff0000 : "cfg"
[    0.482645] 0x000000ff0000-0x000001000000 : "para"
  • cfg on mtdblock6 is mounted rw as /configs.
  • app on mtdblock3 is mounted ro as /system.
  • rootfs on mtdblock2 is mounted ro as /.

Wyze Cam v2

Firmware

Official firmware

You can download the firmware files from their servers at https://download.wyzecam.com/firmware/v2/. There is no directory listing, so you'll have to guess the filenames based on the firmware version numbers. The firmware versions are available from their release notes posted on their website at https://support.wyze.com/hc/en-us/articles/360024852172-Release-Notes-Firmware.

Version Notes Download URL
4.28.4.49 RTSP beta https://download.wyzecam.com/firmware/rtsp/demo_v2_rtsp_4.28.4.49.bin.zip
4.9.6.156 Jul 2020 https://download.wyzecam.com/firmware/v2/demo_4.9.6.156.bin.zip
4.95.115 Aug 2020 https://download.wyzecam.com/firmware/v2/demo_4.9.5.115.bin.zip
4.9.8.860 Jan 2022 https://download.wyzecam.com/firmware/v2/demo_4.9.8.860.zip
4.9.8.1002 Mar 2022 https://download.wyzecam.com/firmware/v2/demo_4.9.8.1002.zip

Hacked firmware

There are two firmwares available to the v2:

RTSP streaming

There is an official firmware with RTSP streaming support built in. However, you may also do the same RTSP streaming hacks with Wyse-bridge and WyzeHacks as I described on the Wyze Cam v3 section above as it works just the same.

I ended up using the WyzeHacks on the v2 even with the RTSP firmware because the RTSP stream was less stable than the hacked h264 stream. Follow the WyzeHacks instructions in the Hacks section below.

Telnet access

The default username and password set on the Wyze Cam v2 is:

  • Username: root
  • Password: ismart12

Dafang Hacks

Clone the git repository at https://github.com/EliasKotlyar/Xiaomi-Dafang-Hacks. Format a new SD card with FAT32 and copy everything from firmware_mod to the root of the SD card. Edit config/wpa_supplicant.conf to look something like this:

# For more configuration option please see:
# https://w1.fi/cgit/hostap/plain/wpa_supplicant/wpa_supplicant.conf

ctrl_interface=/var/run/wpa_supplicant
ctrl_interface_group=0
ap_scan=1

network={
        ssid="wifi-network-ssid"
        # Uncomment to connect to Hidden SSIDs
        #scan_ssid=1
        key_mgmt=WPA-PSK
        pairwise=CCMP TKIP
        group=CCMP TKIP WEP104 WEP40
        psk="wpa-passphrase"
        priority=2
}

The default username and password is root and ismart12.

NFS notes

NFS can be mounted with

# mount -t nfs -o port=2049,nolock,proto=tcp nas:/data/public /mnt

A JPEG can be captured with

# /system/sdcard/bin/getimage > output.jpg

A quick and dirty way of just capturing pictures every couple seconds would be to create /system/sdcard/config/userscripts/startup/record.sh with

#!/bin/sh

while true ; do

        while true ; do
                if mount | grep -q nfs ; then
                        break
                fi

                echo "Attempting to mount /mnt"
                mount -t nfs -o port=2049,nolock,proto=tcp nas:/data/public /mnt
        done


        Dir="/mnt/wyzecam02/snapshots/`date +%Y%m/%d`"
        if [ ! -d $Dir ] ; then
                mkdir -p $Dir
        fi

        /system/sdcard/bin/getimage > $Dir/`date +%H%M%S`.jpg

        sleep 2
done &


Hacks

Applying the WyzeHacks hack

This option no longer works without spoofing DNS
Wyze Labs has restricted the payload URL and you can no longer arbitrarily pass in a random payload. As a result, this method won't work.

You may try to work around the problem by spoofing the DNS for s3-us-west-2.amazonaws.com and try updating the firmware using this URL: http://s3-us-west-2.amazonaws.com/wuv2/upgrade/WLPP1/firmware/1.2.0.80a.bin

But your mileage may vary in the future as Wyze continues to change the way their API works.

This issue is tracked at https://github.com/HclX/WyzeHacks/issues/132

WyzeHacks contains a series of scripts that push an upload payload via the SD card or over the air that enables additional features including telnet access. To get WyzeHacks working, you will need a Linux computer with Python on the same subnet as the Wyze Cams.

What I did to get the Wyze Cam v3 (and later v2) running telnet was:

  1. Clone the git repository as well as the WyzeUpdater repo
  2. Run build.sh on the root directory. This should generate a new release under the release directory.
  3. Go into the latest release directory cd ./release/release_0_5_06/. Execute the wyze_updater.py script.
  4. If prompted, enter your Wyze credentials. If you get an invalid token error, remove the existing token by running rm ~/.wyze_token.
  5. Any cameras associated with your account should be visible. Update the desired camera.
  6. You will see the script wait and print lots of ...'s. You can exit out of it by hitting Ctrl-C since the script does nothing past this point. Note that the update is automatic past this point. The Wyze Cam should come back online automatically. For the v3, the update took a few minutes. For the v2, it was nearly instantaneous.

The console output should look something like this:

$ ./remote_install.sh
Found local config file, including into firmware update archive...
Upgrade/config.inc
Defaulting to user installation because normal site-packages is not writeable
Requirement already satisfied: requests in /usr/lib/python3.9/site-packages (2.25.1)
Requirement already satisfied: idna<3,>=2.5 in /usr/lib/python3.9/site-packages (from requests) (2.10)
Requirement already satisfied: urllib3<1.27,>=1.21.1 in /usr/lib/python3.9/site-packages (from requests) (1.25.10)
Requirement already satisfied: chardet<5,>=3.0.2 in /usr/lib/python3.9/site-packages (from requests) (4.0.0)
INFO:root:Trying saved credentials from /home/leo/.wyze_token.
INFO:root:Checking device, mac=7C78B23AF941

Device type:      Camera (WYZE_CAKP2JFUS)
Device name:      v3b
Firmware version: 4.36.0.280
IP Address:       10.1.3.252
Pushing firmware to this device? [y/N]:y

INFO:root:Serving firmware file './firmware.bin' as 'http://s3-us-west-2.amazonaws.com/wuv2/upgrade/WLPP1/firmware/1.2.0.80a.bin', md5=7cde34dd8669101e0a93ae962109d0be
INFO:root:Checking device, mac=A4DA222C2591

Press Ctrl+C when all the updates are done...
.......^C
INFO:root:Stopping http server...

Troubleshooting

Broken pipe while updating

Initially, I had issues with the script where it kept on getting a Broken Pipe exception and then bailing out. This somehow solved itself after moving the laptop to another room, so it might be an issue with the network.

DNS spoofing

Recently, Wyze Labs have changed their API such that you can no longer set an arbitrary firmware URL. Attempting to do so will result in a RuntimeError: Request failed, error 3005:UnauthorizedOperation error. To work around this, you will need to:

  1. Spoof s3-us-west-2.amazonaws.com to a local web server hosting your wyze hacks firmware file
  2. Serve the firmware from this web server, so that the URL http://s3-us-west-2.amazonaws.com/wuv2/upgrade/WLPP1/firmware/1.2.0.80a.bin works
  3. Edit the wyze_updater.py script so that the URL is set to this specific URL by adding the following line:
url = build_url(args.addr, args.ssl, args.port)
url = "http://s3-us-west-2.amazonaws.com/wuv2/upgrade/WLPP1/firmware/1.2.0.80a.bin"

Ensure that the MD5 checksum the wyze_updater.py script shows matches the MD5 of the file that's being served. If it mismatches, the update will fail and the camera will simply reboot without any changes.

See Also