start

Hello

Welcome to my corner on the web! My name is Ignas Bukys and I am excited to share my thoughts, experiences, and insights with you.
On this blog, you will find a diverse range of topics including technology and programming. All my projects (ongoing & finished) are listed here.
Whether you are here for personal growth, to learn something new, or simply to be entertained, I hope you find value in what I have to share. Thank you for stopping by and I hope you enjoy your visit.

—- Visit blog archive

Latest Blog messages

Controlling Tuya Smart Plugs Locally with MicroPython on ESP32

No cloud. No dependencies. Pure local LAN control.

I spent a few days reverse-engineering the Tuya local LAN protocol to get my AUBESS Smart Socket 20A plugs talking to an ESP32 running MicroPython — without relying on the cloud, without any third-party MicroPython libraries. This post documents everything I found, including the quirks that cost me the most time.

Tuya's cloud API is rate-limited, requires internet, and adds ~500ms latency. For home automation on a microcontroller you want local LAN control: direct TCP on port 6668, sub-10ms response and solution that works offline.

There are Python libraries for this on desktop (tinytuya), but nothing clean for MicroPython. This post fills that gap.

  • ESP32 board running MicroPython
  • Tuya-based smart plug (tested: AUBESS Smart Socket 20A)
  • Your plug's Device ID and Local Key (see below)
  • The plug's local IP address

I assume, you already have Tuya app installed on your phone :) Its the only way to get device's Local Key- developer account is needed at Tuya platform. Free trial account is totally suitable, no need to pay.

You need the Device ID (20 chars) and Local Key (16 chars) from Tuya's developer portal.

On your PC:

pip install tinytuya
python -m tinytuya wizard

This walks you through linking your Tuya developer account and dumps all device credentials to devices.json and other JSON files. Dont forget to check those.

⚠️ The Local Key changes every time you remove and re-add a device in the Smart Life app. Don't do that after grabbing the key.

Tuya v3.3 uses plain TCP on port 6668 with AES-128-ECB encrypted JSON payloads. Each frame looks like:

[HEADER 4B][SEQUENCE 4B][COMMAND 4B][LENGTH 4B][AES'ed PAYLOAD][CRC32 4B][FOOTER 4B]

  • Header: 00 00 55 AA
  • Sequence: 00 00 00 01
  • Command: 00 00 00 0a
  • Length (hex): 00 00 00 78
  • Payload- variable length
  • CRC32: 12 34 56 78
  • Footer: 00 00 AA 55

Length is measured PAYLOAD + CRC32 + FOOTER

All my AUBESS plugs are query-style - after connecting to socket Plug waits. You must send a status query packet first.

Standard Tuya v3.3 documentation says the encrypted payload should be prefixed with 3.3\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 (15 bytes). This makes packets 151 bytes. It is required when sending set_switch command

After capturing tinytuya traffic I discovered my plugs want no prefix for the status query — 136-byte packets only. The prefix is only used for control packets (turning on/off).

This single detail isn't documented anywhere I could find.

Packet type Version prefix Packet size
Status query (cmd 0x0a) ❌ No prefix 136 bytes
Heart-beat (cmd 0x09) ❌ No prefix 136 bytes
Control (cmd 0x07) ✅ With prefix 151 bytes

MicroPython's utime.time() epoch starts at 2000-01-01, not 1970-01-01 like Unix.

Tuya validates the t field in the payload and rejects queries where the timestamp is too far from real Unix time.

Fix this by syncing NTP on boot and adding the epoch offset:

boot.py
import network, utime, ntptime
 
sta = network.WLAN(network.STA_IF)
sta.active(True)
sta.connect('YOUR_SSID', 'YOUR_PASSWORD')
while not sta.isconnected():
    utime.sleep_ms(300)
 
ntptime.settime()  # sync to real UTC
# In your main script
EPOCH_OFFSET = 946684800  # seconds between 1970 and 2000
 
def unix_now():
    return utime.time() + EPOCH_OFFSET

The plug returns a dps dictionary. For AUBESS Smart Socket 20A:

Key Unit Description
1 bool Switch state
9 Countdown timer
18 mA Current
19 W × 10 Power (divide by 10)
20 V × 10 Voltage (divide by 10)
21 Power-on mode
22–24 Calibration values
25 mA Leakage current threshold

Mapping could be found in files, generated by tinytuya wizard

  • ECONNRESET immediately — plug rejects the packet content. Check key order in JSON and version prefix.
  • ETIMEDOUT — plug ignores the packet entirely. Check timestamp (NTP sync), and whether you're using prefix or no-prefix for the right packet type.
  • data format error in decrypted response — key is correct but payload format is wrong. Check JSON structure.
  • Only one connection at a time — close the Smart Life app on your phone before connecting from ESP32.

This monkey-patch script helped a lot. Using it i was able to prepare Microptyhon library for Tuya devices

capture.py
# Run this on PC: python capture.py
import tinytuya
import socket
import threading
import time
 
# Pinguin
DEVICE_IP = '192.168.1.55'
DEVICE_ID = '1<hidden>9'
LOCAL_KEY  = '5<hidden>b'
 
# Monkey-patch socket to capture raw bytes
_orig_connect = socket.socket.connect
_orig_send    = socket.socket.send
_orig_recv    = socket.socket.recv
_orig_sendall = socket.socket.sendall
 
def patched_connect(self, addr):
    print('[SOCKET] connect to', addr)
    return _orig_connect(self, addr)
 
def patched_send(self, data, *args):
    print('[SOCKET] send %d bytes:' % len(data),
          ' '.join('%02x' % b for b in data))
    return _orig_send(self, data, *args)
 
def patched_recv(self, size, *args):
    data = _orig_recv(self, size, *args)
    print('[SOCKET] recv %d bytes:' % len(data),
          ' '.join('%02x' % b for b in data))
    return data
 
def patched_sendall(self, data, *args):
    print('[SOCKET] sendall %d bytes:' % len(data), 
          ' '.join('%02x' % b for b in data))
    return _orig_sendall(self, data, *args)
 
socket.socket.connect = patched_connect
socket.socket.send    = patched_send
socket.socket.recv    = patched_recv
socket.socket.sendall = patched_sendall
 
d = tinytuya.OutletDevice(
    dev_id=DEVICE_ID,
    address=DEVICE_IP,
    local_key=LOCAL_KEY,
    version=3.3
)
d.set_socketTimeout(10)
data = d.status()
print('STATUS:', data)
  • Sending heartbeat (0x09) before the status query — some plugs need it, mine didn't
  • utime.time() without NTP sync — plug rejects stale timestamps
  • Version prefix on status query — adds 15 bytes that this plug firmware rejects

This was tested on AUBESS Smart Socket 20A running Tuya firmware v3.3. Other Tuya devices may behave differently — some push status on connect without needing a query, some require a heartbeat first. The packet capture approach (monkey-patching Python's socket in tinytuya) is the most reliable way to debug unknown devices.

All code runs on standard MicroPython ESP32 builds with no additional packages. The only modules used are socket, time, struct, cryptolib, binascii, errno and json— all built-in.

Access it here Library

2026/05/13 18:26 · Ignas · 0 Comments

I like to try various gadgets for my bike. Some enhance my side, some are just, well, meh… :)

With a new bike computer, I have logged more than 1000 kilometers this season, and I’m eager to give my opinion, based on my personal experience. The gadget I’m talking about is the Geoid CC600. The Geoid CC600 uses a ESP32 chip which serves as the CPU. This allows for a feature set that, for its price point, is very impressive.

  • 2.4' color screen with 3 physical buttons
  • Backlight screen with ambient light sensor
  • IPX7 waterproof level, but I would not keep it on rain
  • GPS, Galileo, Beidou, Glonass and QZSS positioning systems
  • Type-C standard charging port
  • 17-24 hours battery life, based on settings and usage scenario
  • Wifi, Bluetooth and ANT+ wireless protocols
  • 100 hours of storage
  • Supported sensors: Speed*, Cadence*, Heartrate* Power meter, Smart trainer, Electronic shifting, Tail light, Radar*

* - Tested: I own XOSS Vortex Speed and Cadence sensors (works in both BT and ANT+), Garmin Heartrate sensor and W100 Radar.

Thing that stood out to me about the device was the display. It is a bright, colored screen that is easy to navigate through even in direct sunlight, which is something I have struggled with in the past. The screen’s brightness coupled with the resolution means that all the essential metrics and even navigation details are squinted in.

I have to say that the CC600's capability to interface with ANT+ and Bluetooth Low Energy (BLE) is a given advantage. I have seamlessly connected it with my heart rate monitor, speed sensor, and cadence sensor, and all of the connections have remained consistent with no surprise disconnections during my rides. Also, the unit has quite a number of data fields which gives me the possibility to customize my display pages to show the information that I want to see at a glance.

The navigation feature is a breadcrumb style which is good for following a set route. I have used it to follow GPX files that I have uploaded from my phone and it has worked unbelievably well. While it lacks the detailed, full-map view of more expensive devices, its simple outline track and turn-by-turn prompts are adequate to keep the user on track. This is a no-frills navigation system that gets the job done quite well. One more thing that I like is that the navigation track is shown in color, which is good for visibility. Also, worth to mention, that routes created in OneLapFit application has additional information on map about sideroads, which will help you to orient where to make your turn. If you drive away from your route, your route will be rerouted if OneLapFit application is open on your phone

After 1000 kilometers, the Geoid CC600 has proven to be a reliable and capable bike computer. It's not perfect—the companion app can be a little clunky at times, and the setup was a bit cryptic initially. However, the hardware itself is solid, and for its price, it offers a great balance of features, especially for someone who primarily wants a device for tracking stats and following simple routes. If you're in the market for a budget-friendly bike computer with a vibrant display and essential features, the Geoid CC600 is definitely worth considering.

2025/10/01 12:57 · Ignas · 0 Comments

In this post, I want to share my personal experience in choosing and configuring a 4G router for my home. Bigger project I was on, required me to install a network solution in a house, and the choice was determined by the ratio of the offered functionality and price.

Out of many choices, the ZBT WE826 model caught my eye. This router had an attractive price and seemed suitable for my needs. With a 4G module, the router costs about €60. I found information that it supports OpenWRT software. I purchased from the Aliexpress platform and received the package.

However, after starting to use this router, problems arose. The 4G connection worked only for 3-8 hours (very randomly), and then disappeared. I wanted to make sure that the device was working properly and fairly stably, so at first I didn't change any settings and tried to use the one I received. I tried to reconfigure the device according to the seller's notes and advice, but it didn't help. The seller shared the firmware for the router based on the LEDE Reboot version from 2018. I decided to install it, but it wasn't successful either - the device worked for 8 minutes and rebooted. Apparently some script was working.

Finally, I decided to install the latest OpenWRT firmware version specifically for my device. I chose the “ZBT W826 T 16M factory image” version. This solution turned out to be correct and my device with the new firmware started up. As expected, the 4G modem did not work and needed to be configured. Two sources helped me do this:

You need to understand that in order to install some packages into router, you need an internet connection. I didn't had a wired connection, so I thought, maybe I can share the internet on my mobile and make the router a Wifi client. It was fairly straight forward. I've managed to enable 4G support by following documentation. Left router to hang overnight to evaluate stability. To my deep disappointment, the initial situation repeated itself - the 4G connection disappeared after a few hours.

Since I was using the latest version of OpenWRT, I decided to describe my problem on the OpenWRT forum. A forum member, guru, AndrewZ, responded quite quickly and asked for additional data from the router. After an initial analysis, he suggested changing the 4G module mode and reconfigure the router. I read the procedure and decided to go this route. I have nothing to lose, even if I break my router!

I reloaded the OpenWRT firmware once again to start from fresh, shared the Internet via my mobile phone. I changed the module configuration to support the QMI protocol using AT commands. I configured the router according to the documentation. This process was a challenge, but in the end, I've managed to achieve the desired result.

Now I have router that gives me low level configuration, has wide support for various software and active community in case something goes wrong. My ZBT WE826 router works stably and without problems. The 4G connection is stable, and the Wi-Fi coverage is very good. In addition, the form factor of the router is perfect for hanging on the wall, which is a big advantage.

This project showed that even with an inexpensive device, good results can be achieved if you are ready to invest some time and effort. The main thing is not to give up and look for solutions, even if everything seems complicated at first.

  • start.txt
  • Last modified: 2026/05/13 18:26
  • by Ignas