Use a cheap WiFi ear camera on your PC instead of the phone app. These cameras are sold as WiFi otoscope, ear wax removal camera or ear cleaner with camera and normally only work with a smartphone app such as Soulear. NJFE is an open-source alternative: an ESP32-C3 joins the camera's WiFi, speaks its i4season UDP protocol and forwards the live JPEG stream over USB to a Python GUI (tested on Windows). The same camera then also works as a mini endoscope / inspection camera for electronics, DIY and machines.
Note
Vibe-coded with Claude. The entire development of this project was done in conversation with Claude (Anthropic) via Claude Code: research, protocol implementation, ESP32 firmware, PC tools, tests and this documentation. The human part: the idea, the hardware, testing on the real device and the decisions. Read the code with that in mind, and please report anything that looks off.
Tested with the Hopefox Find T ("Ohrenschmalz Entferner mit Kamera", WiFi name
Soulear-xxxxx, firmware HKV41B). Other cameras that use the Soulear app /
i4season protocol will likely work too.
The camera reports itself as BK7231U-XRH-FBPRO (vendor "YPC"), but according to a
teardown on Elektroda and
EarCam the actual SoC is a Taixin TXW816-810
(Taixin hgSDK) with a HI708 image sensor; the Beken name seems to be a leftover
product string. Not verified on our unit (the case is hard to open without damage).
The sensor delivers 480×480 JPEG at ~17 fps; the "1080p" in product listings does not
match the hardware.
Hence the name: Not Just For Ears. The camera is a mini endoscope: a slim, short probe with a camera and LED at the tip and a live image at ~17 fps. That makes it a cheap inspection tool for:
- Electronics work: checking solder joints, reading tiny SMD markings, looking under connectors or into the gaps of closed housings
- DIY and repairs: peeking into small openings, drill holes, crevices and mechanisms you can't get your eye close to
- Mechanical engineering: inspecting bores, threads, gear teeth, bearings and hard-to-reach spots in machines and assemblies
With the PC GUI you get a bigger image than on a phone, plus photos and video recording.
flowchart LR
PC["PC<br/>gui.py / viewer.py"]
ESP["ESP32-C3 SuperMini<br/>bridge firmware<br/>192.168.1.10"]
CAM["Endoscope<br/>(ear camera)<br/>192.168.1.1"]
PC <-->|"USB serial<br/>commands ↓ · JPEG + IMU ↑"| ESP
ESP <-->|"WiFi (camera's own AP)<br/>i4season UDP"| CAM
subgraph isolated["Isolated camera WiFi: no internet, no phone, no app"]
ESP
CAM
end
Electronics inspection: an IC and SMD parts on a PCB.
The camera's USB port is charge-only, and it only offers an open WiFi access point. Normally you would install the vendor's app on your phone and join that WiFi. NJFE replaces both with a small, open-source USB bridge:
- No third-party app on your phone. No closed-source app from an unknown vendor asking for camera, storage, location or network permissions on the device that holds your contacts, photos, messages and banking apps.
- No account, no cloud, no telemetry. The images go from the camera over USB to your PC and nowhere else. Nothing needs internet access.
- Your phone and PC stay on their own networks. Neither has to join the camera's open, unencrypted WiFi. The PC only sees a USB serial port, so the camera network never gets a route into your home network or the internet.
- Images stay local. Photos and videos are plain files in a folder you control (and are git-ignored, since ear images are health data).
- Auditable. Firmware and PC tools are under 1,000 lines of open source code that you can read, build and flash yourself.
What it does not fix: the camera's own WiFi is still open, so someone in radio range could join it while the camera is on. See SECURITY.md.
- Live image, ~17 fps, 480×480 JPEG
- Photo (original JPEG) and video recording (MJPG AVI)
- Auto-rotation from the camera's accelerometer, manual 90° rotation and mirroring
- Round mask (hides the corners that appear when the image is rotated)
- LED on/off, battery level
- WiFi selection: scan, pick a network, stored on the ESP32 across reboots
- OLED status on the ESP32 (connection, fps, data rate, battery)
| Part | Notes |
|---|---|
| ESP32-C3 SuperMini with 0.42" OLED | 72×40 SSD1306, I²C SDA=GPIO5, SCL=GPIO6, native USB (USB-Serial-JTAG) |
| i4season/Soulear WiFi ear camera | camera must be switched on (AP appears as Soulear-*) |
No wiring needed. The ESP32 is only connected to the PC via USB.
Left: the whole setup, camera on, bridge plugged into the PC. Right: the bridge's OLED with live stats (fps, header resolution, data rate, camera battery).
- Flash the bridge firmware (see below).
pip install -r wifi_scanner/tools/requirements.txt- Switch on the camera.
python wifi_scanner/tools/gui.py, select the ESP32 port, click Connect.
The ESP32 connects automatically to the strongest Soulear-* network.
wifi_scanner/platformio.ini has three environments:
| Environment | Source | Purpose |
|---|---|---|
esp32c3_supermini |
src/ |
WiFi scanner, highlights camera networks on the OLED |
soulear_probe |
src_probe/ |
TCP port probe of the camera (diagnostics; set TARGET_SSID first) |
soulear_stream |
src_i4season/ |
The bridge used by the PC tools |
cd wifi_scanner
pio run -e soulear_stream -t upload --upload-port COM10If the upload stops with "No serial data received" after the baud change, flash without the stub loader:
python ~/.platformio/packages/tool-esptoolpy/esptool.py --chip esp32c3 --port COM10 \
--baud 115200 --no-stub write_flash -z 0x10000 .pio/build/soulear_stream/firmware.binToolchain: PlatformIO espressif32@6.11.0 (Arduino core 2.0.17), library U8g2.
Tkinter:
| Control | Function |
|---|---|
| Port, ↻, Connect/Disconnect | choose and open the ESP32 serial port (Espressif ports are listed first) |
| WiFi, Scan, Apply | scan networks (camera networks first), choose one and store it on the ESP32; (auto: Soulear-*) = strongest camera network |
| LED on/off | switch the camera LED, the button shows the state reported by the camera |
| Round mask | circular mask over the image |
| Auto-rotate (IMU) | rotate the image so that "up" stays up, based on the accelerometer |
| Photo / Record | save a JPEG / start and stop an AVI recording |
| Rotate 90°, Mirror | manual orientation; the rotation is remembered in wifi_scanner/tools/gui.ini |
| Folder | open the output folder |
Photos and videos are saved to wifi_scanner/snapshots/ (git-ignored).
Photos are the original JPEG unless the image was transformed.
The status line shows fps, resolution, battery and the IMU angle.
python wifi_scanner/tools/viewer.py --port COM10
python wifi_scanner/tools/viewer.py --port COM10 --test 50 # no window, receive 50 frames, print fpsKeys: q/Esc quit, s snapshot, r rotate 90°, m mirror.
USB CDC, the baud rate setting is ignored.
PC → ESP32, text, one command per line:
| Command | Effect |
|---|---|
LED 1 / LED 0 |
camera LED on/off, reply led on=<0/1> bright=<n> |
SCAN |
reply SCAN-BEGIN, lines AP <rssi> <ssid>, SCAN-END |
STATUS |
connected to <ssid>, own IP <ip> plus LED state, or not connected, target '<ssid>' |
SSID <name> |
store the target network in flash and reconnect; SSID alone = automatic |
ESP32 → PC: text log lines (one stats line per second) interleaved with binary frame records, all little-endian:
AA 55 'J' 'P' 'G' | u32 jpegLen | u32 gsensor | u8 flags | u8 packetType | JPEG bytes
flags bit 0 = gsensor valid. The viewer resyncs on the magic and checks the JPEG
markers (FFD8 … FFD9), so corrupted frames are dropped instead of shown.
UDP, camera at 192.168.1.1. Every message starts with a 12-byte header:
u32 0xFFEEFFEE | u16 seq | u16 cmd | u8 1 | u8 status | u16 payloadLen.
| Cmd | Port | Meaning |
|---|---|---|
0x0001 |
10005 | device info: vendor, product, firmware, SSID, MAC, battery |
0x0002 |
10005 | licence query (sent like the vendor app) |
0x0004 |
10006 | open video: u16 picPort, u16 audioPort, u32 clientId, repeated while no video arrives |
0x000A |
10005 | LED: u8 op (1 = read, 0x11 = write), u8 on, u8 brightness |
Video arrives on picPort (40000) as chunked JPEG. Each chunk carries a 16-byte
(type 1) or 28-byte (type 6) header with sequence, frame id, last-chunk flag and a
packed 3-axis accelerometer sample (9-bit magnitude + sign per axis, ~130 counts/g).
Roll angle = atan2(y, z), plus 180° for products whose sensor is mounted upside down.
Details and the original reverse engineering: otoscope.
| Symptom | Cause / fix |
|---|---|
| OLED shows "waiting for Soulear-*" | camera off or out of range; it is retried automatically |
| GUI connected, but no image | camera was just switched on, wait a few seconds; check the log for open video -> ok |
| Port busy | close other programs using the port (GUI, viewer, serial monitor) before flashing |
| Upload fails after baud change | use the --no-stub command above |
| Status line cut off | widen the window (happens with 150 % display scaling) |
| Wrong auto-rotation direction | not verified on every model, use manual rotation and report the product name |
The camera's WiFi is open (no password), and its protocol has no authentication. See SECURITY.md.
- Not a medical device. NJFE is not intended for diagnosis or treatment. Do not use it as a substitute for a doctor. Inserting objects into the ear canal can injure the ear; if you use the camera in your ear, follow the manufacturer's instructions.
- No liability for damage to people or machines. This project is provided "as is", without warranty of any kind. To the extent permitted by law, the authors accept no liability for any injury to people or animals, or for damage to equipment, machines, devices or data resulting from building or using it (see sections 15 and 16 of the GPL-3.0). This includes damage to the camera, the ESP32, your PC or the object you inspect, e.g. by shorting live electronics with the metal probe or by pushing it into moving or energized machinery. Switch machines off and disconnect power before inspecting them.
- AI-assisted code, limited testing. The code was written with AI assistance (Claude) and tested on one device only.
- No affiliation. Not affiliated with or endorsed by Hopefox or the makers of the Soulear app; names are used only to identify compatible hardware.
Use at your own risk.
This project stands on the shoulders of people who did the hard reverse-engineering work first. Big thanks to:
- otoscope by rubec: an open Android app for cheap WiFi otoscopes. Its i4season implementation was the blueprint for the whole bridge: packet header, commands, video chunking, the accelerometer bit layout, the roll formula, the LED command and the 180° mount offset. Without it this would have taken weeks of packet sniffing. (GPL-3.0, which is why this project is GPL too.)
- Suear Web Viewer by Sean Pesce: research on the closely related Suear/i4season cameras that helped identify the device family and confirm the protocol direction.
- NE3-Scope by haxko: work on another family of cheap WiFi ear cameras; its network names went into the WiFi scanner's list of known camera prefixes.
- EarCam by jduanen: command table probing of
the same Soulear camera (including the warning that command
0x0008powers the camera off) and the chip identification. - Elektroda teardown of the Taixin TXW816-810 based otoscopes: PCB pinout, UART logs and flash dump.
- wifi-otoscope-viewer by
TJRSchmidbauer: browser viewer tested on the identical
BK7231U-XRH-FBPRO/ HKV41B unit, which confirmed the 480×480 sensor resolution.
And to the projects this is built with: U8g2 (OLED), Arduino core for ESP32 / ESP-IDF, PlatformIO, pySerial, OpenCV, NumPy and Pillow.
GPL-3.0-or-later, see LICENSE. The protocol implementation is derived from the GPL-3.0 project otoscope, see NOTICE.



