Note
This repository is a maintained fork of dewenni/ESP32-Jarolift-Controller.
The project keeps the original Jarolift controller functionality while focusing on a reproducible build, a reduced attack surface and the hardware configuration actually used and tested by this fork.
Current version: 2026.3.0
Version scheme:
year.major.bugfix
Examples:
2026.1.0
2026.1.1
2026.2.0
2027.1.0
ESP32-Jarolift-Controller controls Jarolift TDEF-compatible 433 MHz roller shutters using an ESP32 and a CC1101 transceiver.
The ESP32 acts as an additional Jarolift radio transmitter. It maintains its own sender serial number and KeeLoq rolling counter and can control shutters through:
- WebUI
- MQTT
- Home Assistant via MQTT Discovery
- timers
- predefined groups
- direct bitmask groups
Signals from existing Jarolift remote controls can also be received and reported through MQTT.
- Web-based configuration and control
- MQTT control and status messages
- Home Assistant MQTT Discovery
- up to 16 shutter channels
- up to 6 predefined groups
- arbitrary groups through a 16-bit bitmask
- timer control
- sunrise and sunset triggers
- reception of existing Jarolift remote controls
- local firmware update through the WebUI
- configuration import/export
- persistent KeeLoq device counter
- WiFi support
- optional W5500 Ethernet support
This fork currently contains several changes compared with the original project:
- reproducible Docker-based build workflow
- Docker-based flashing without a host installation of PlatformIO or esptool
- selected project dependencies vendored into the repository
- local build secrets kept outside Git
- individual configuration encryption key instead of a shared hard-coded key
- WPA2-protected Setup Mode access point
- mandatory WebUI authentication during normal operation
- removal of ArduinoOTA
- removal of GitHub-based OTA updates
- removal of the active Telnet interface
- local authenticated WebUI firmware updates retained
- protection of persistent state such as the KeeLoq device counter
- corrected CC1101 transmit-power initialization
- validation of the Jarolift controller sender serial number
- protection against transmitting with an invalid controller sender serial
The tested build target of this fork is currently:
Classic ESP32 with 4 MB flash
The development and hardware validation is currently done with an ESP32-WROOM-32 based DevKit.
The source tree still contains PlatformIO environments for other ESP32 variants inherited from upstream, but they are currently not part of the tested build and release workflow of this fork.
Recommended:
- classic ESP32
- ESP32-WROOM-32
- 4 MB flash
Tested hardware:
- EBYTE E07-M1101D-SMA V2.0
Other compatible 433 MHz CC1101 modules may work as well.
Important
The CC1101 is a 3.3 V device.
Do not power the radio module with 5 V.
Attach a suitable 433 MHz antenna before transmitting.
Default wiring used by this project:
| CC1101 | ESP32 |
|---|---|
| VCC | 3.3 V |
| GND | GND |
| GDO0 | GPIO 21 |
| GDO2 | GPIO 22 |
| SCK | GPIO 18 |
| MOSI | GPIO 23 |
| MISO | GPIO 19 |
| CSN | GPIO 5 |
The GPIO assignment can later be changed in the WebUI.
Existing upstream hardware documentation and images can be found in the
Doc directory.
Support for a W5500 Ethernet controller is retained from upstream.
The CC1101 uses the primary SPI interface, therefore the W5500 uses a separate SPI interface.
Typical upstream configuration:
| Signal | ESP32 |
|---|---|
| CLK | GPIO 25 |
| MOSI | GPIO 26 |
| MISO | GPIO 27 |
| CS | GPIO 32 |
| INT | GPIO 33 |
| RST | GPIO 17 |
Ethernet is not started while the controller is in Setup Mode.
The supported build and flash workflow requires:
- Git
- Docker
- USB access to the ESP32 when flashing
PlatformIO, Python packages and esptool do not have to be installed on the host system.
Clone the repository:
git clone https://github.com/ElHanko/ESP32-Jarolift-Controller.git
cd ESP32-Jarolift-ControllerThe firmware requires a local secrets file which is intentionally excluded from Git.
Create it from the example:
cp include/local_secrets.example.h include/local_secrets.hThen edit:
include/local_secrets.h
Two values are required.
#define SETUP_AP_PASSWORD "change-this-password"The password must contain between 8 and 63 characters.
It protects the temporary WiFi access point created in Setup Mode.
The file also contains a 16-byte key:
static constexpr unsigned char CONFIG_ENCRYPTION_KEY[16] = {
...
};Generate an individual random value before using the controller.
Do not commit the real key.
Important
Keep a secure backup of include/local_secrets.h.
Firmware updates should continue to use the same
CONFIG_ENCRYPTION_KEY. Changing this key can make passwords already stored
in the configuration impossible to decrypt.
Create and customize the private secrets file as described above, then build the firmware with:
./build/build.shThis mode uses only include/local_secrets.h and fails if the file is missing.
The build runs inside Docker.
The resulting files are written to:
build/artifacts/
Relevant artifacts:
firmware.bin
firmware_merged.bin
bootloader.bin
partitions.bin
SHA256SUMS
firmware.bin is the application/OTA image. firmware_merged.bin is the
complete flash image.
SHA256SUMS contains checksums for all generated binary files.
The supported build script currently builds the esp32 PlatformIO
environment.
Build public release artifacts with:
./build/build.sh releaseThis mode uses the public values from include/local_secrets.example.h. It
creates include/default_local_secrets.h only in the temporary /work
workspace and uses it there as include/local_secrets.h.
In addition to the generic artifacts, release mode creates:
esp32_jarolift_ota_<VERSION>.bin
esp32_jarolift_flash_<VERSION>.bin
The versioned OTA file is identical to firmware.bin; the versioned flash
file is identical to firmware_merged.bin.
Warning
Prebuilt public release binaries contain public default secrets. Their
SETUP_AP_PASSWORD and CONFIG_ENCRYPTION_KEY are not secret.
For permanent installations, an individual build with a private
include/local_secrets.h is strongly recommended. Never commit this file.
Changing CONFIG_ENCRYPTION_KEY can make previously stored encrypted
credentials unreadable.
The flash workflow also runs esptool inside Docker.
The default serial device is:
/dev/ttyUSB0
A different port can be supplied as the second argument.
./build/flash.sh installor:
./build/flash.sh install /dev/ttyUSB0This writes:
firmware_merged.bin
at:
0x0
The script requires explicit confirmation before the write.
Warning
install is intended for the first installation.
The complete image overwrites the persistent NVS area. Existing configuration and the Jarolift rolling counter may therefore be lost.
For normal firmware updates use:
./build/flash.sh updateor:
./build/flash.sh update /dev/ttyUSB0This writes only:
firmware.bin
at:
0x10000
NVS and LittleFS are preserved.
The flash script also:
- verifies
SHA256SUMS - checks communication with the ESP32
- verifies that a 4 MB flash chip is detected
- aborts if the hardware check fails
For an already configured controller, use update unless a complete
reinstallation is explicitly required.
Setup Mode is used for initial configuration or recovery.
It can be triggered through the multiple-reset detector by restarting the ESP32 repeatedly within the configured time window.
Setup Mode is also entered when required network or WebUI configuration is missing.
While Setup Mode is active, the controller creates:
SSID: ESP32-Jarolift
For the prebuilt release binaries, the WPA2 password is:
change-this-password
This password comes from the public default secrets and is therefore not secret.
After connecting to the access point, open:
http://192.168.4.1
WebUI authentication is disabled in Setup Mode because access is protected by the dedicated WPA2 network.
For a permanent installation, building the firmware yourself is recommended. Configure your own SETUP_AP_PASSWORD and CONFIG_ENCRYPTION_KEY in:
include/local_secrets.h
Use the following file as a template:
include/local_secrets.example.h
The default secrets contained in the prebuilt release binaries are publicly known and should not be considered individual security credentials.
Authentication is mandatory during normal operation.
Configure:
- username
- password
before leaving Setup Mode.
If valid WebUI credentials are missing, the controller returns to Setup Mode instead of starting normal operation without authentication.
Important
The WebUI uses HTTP.
Do not expose the controller directly to the public Internet.
The WebUI contains settings for:
- WiFi
- optional W5500 Ethernet
- WebUI authentication
- NTP
- MQTT
- Home Assistant
- GPIO
- Jarolift protocol
- shutters
- groups
- timers
- known remote controls
- language
Changes are saved automatically.
Some hardware and Jarolift settings require a restart before they take full effect.
The Jarolift protocol settings require the appropriate KeeLoq master-key configuration.
The keys are not stored in this repository.
The ESP32 acts as its own Jarolift transmitter and therefore needs its own sender serial prefix.
The configured base serial is a 20-bit value and must be represented by six hexadecimal digits:
000001
...
0FFFFF
The first hexadecimal digit must therefore always be:
0
Examples:
000010
012345
0abcde
0fffff
Invalid:
123456
5c9163
ffffff
Important
Do not use a six-digit value whose first digit is non-zero.
The complete on-air Jarolift serial is 28 bits. The controller appends the channel number to the configured 20-bit base serial.
Values larger than 0x0FFFFF would overlap with the function bits in the
transmitted telegram and can turn normal commands into different Jarolift
functions.
The firmware therefore rejects sender serials outside:
000001 .. 0FFFFF
and refuses to transmit if an invalid value is present in the configuration.
Jarolift uses KeeLoq rolling codes.
The ESP32 therefore maintains a persistent device counter.
Normal firmware updates preserve this counter.
Avoid resetting or overwriting NVS on an already learned controller unless you deliberately intend to reinitialize it.
Up to 16 shutter channels can be configured.
Each channel can have:
- enabled/disabled state
- individual name
- Jarolift channel assignment
The configured shutters can then be controlled through the WebUI and MQTT.
A controller channel can be taught to a motor in the same general way as an additional Jarolift remote control.
- Put the motor into learn mode using its programming button.
- The motor confirms this with a short movement/vibration.
- Within the learning window, press the corresponding Learn button in the controller WebUI.
- The motor should confirm the new transmitter.
With an already learned compatible Jarolift remote:
- Select the required channel.
- Press UP + DOWN simultaneously.
- Press STOP eight times on the existing remote.
- The motor confirms that it is ready to learn another transmitter.
- Within the learning window, press the corresponding Learn button in the controller WebUI.
- The controller transmits the required learn sequence.
- The motor should confirm the new transmitter.
Existing physical remotes remain paired when an additional ESP32 sender is learned.
The controller can also receive compatible Jarolift remote-control telegrams.
Existing remotes can be configured in the WebUI so that received commands can be associated with shutters.
This makes it possible to update the internally inferred shutter state when a physical remote is used.
Reception is useful for automation but should not be treated as guaranteed state feedback.
Up to six predefined shutter groups can be configured.
MQTT also supports arbitrary groups using a 16-bit bitmask.
The least significant bit represents shutter 1.
Example:
0000000000010101
selects:
1, 3, 5
Equivalent payload representations:
0b0000000000010101
0x15
21
The integrated timer can control individual shutters or groups using four modes:
- fixed time
- Astro / sunrise or sunset
- later time
- earlier time
Later time uses the later of the astronomical event and the configured comparison time. Earlier time uses the earlier of those two times.
The WebUI includes a configuration file manager.
The controller configuration is stored in:
config.json
It can be exported and later imported again.
Keep configuration backups secure because they contain information about your local controller setup.
A local firmware update remains available through the authenticated WebUI.
Build the firmware first:
./build/build.shThen upload:
build/artifacts/firmware.bin
through the firmware update page in the WebUI.
Only the application image should be used for this type of update.
The following upstream update methods are intentionally not used by this fork:
- GitHub automatic OTA
- ArduinoOTA
- unauthenticated remote flashing
The configured base MQTT topic is represented below as:
<topic>
topic: <topic>/cmd/shutter/1 ... <topic>/cmd/shutter/16
payload: UP
OPEN
0
topic: <topic>/cmd/shutter/1 ... <topic>/cmd/shutter/16
payload: DOWN
CLOSE
1
topic: <topic>/cmd/shutter/1 ... <topic>/cmd/shutter/16
payload: STOP
2
topic: <topic>/cmd/shutter/1 ... <topic>/cmd/shutter/16
payload: SHADE
3
<topic>/cmd/group/1
...
<topic>/cmd/group/6
Supported commands:
UP
OPEN
0
DOWN
CLOSE
1
STOP
2
SHADE
3
<topic>/cmd/group/up
<topic>/cmd/group/down
<topic>/cmd/group/stop
<topic>/cmd/group/shade
The payload is the desired 16-bit shutter mask.
Example for shutters 1, 3 and 5:
0b0000000000010101
or:
0x15
or:
21
The controller publishes an internally inferred shutter state.
Typical values:
OPEN -> 0
CLOSED -> 100
SHADE -> 90
Important
This is not direct position feedback from the motor.
Jarolift TDEF motors do not provide an absolute shutter position through the radio protocol used here. The controller derives the state from commands it knows about.
Physical operation, missed radio telegrams or stopping the shutter from another source can therefore make the reported state differ from reality.
Configured physical remotes can be published through:
<topic>/status/remote/<serial-number>
Example structure:
{
"name": "<alias-name>",
"cmd": "<UP, DOWN, STOP, SHADE>",
"chBin": "<channel-binary>",
"chDec": "<channel-decimal>"
}Home Assistant integration is available through MQTT Discovery.
When enabled, configured shutters are announced automatically to Home Assistant through the configured MQTT broker.
The reported shutter state has the same limitation as the normal MQTT status: it is inferred and is not absolute position feedback from the motor.
Migration from an existing madmartin/Jarolift_MQTT installation is possible.
Important values to preserve are:
- GPIO configuration
- Jarolift master-key configuration
- controller sender serial
- KeeLoq device counter
- shutter/channel assignment
The sender serial and device counter together define the rolling-code state of the transmitter.
Do not arbitrarily reset the counter of an already learned sender.
If you intentionally choose a new ESP32 sender identity, learn that new transmitter into the motors instead.
The original project is configured as the Git upstream of this fork.
Typical local repository layout:
origin -> ElHanko/ESP32-Jarolift-Controller
upstream -> dewenni/ESP32-Jarolift-Controller
Upstream changes should be reviewed before being integrated because this fork intentionally differs in security, build and update architecture.
This fork uses:
year.major.bugfix
Example:
2026.1.0
Meaning:
2026 = year
1 = major release within that year
0 = bugfix level
A bugfix release increments the last component:
2026.1.1
A larger feature release increments the middle component:
2026.2.0
This controller operates inside a trusted local network.
Recommended practice:
- use an individual Setup Mode password
- use a unique configuration encryption key
- use a strong WebUI password
- keep
include/local_secrets.hprivate - do not expose the WebUI directly to the Internet
- back up configuration and local secrets securely
- use
update, notinstall, for normal firmware upgrades
This project is derived from:
dewenni/ESP32-Jarolift-Controller
which itself builds on ideas and code from:
The original Jarolift protocol analysis and controller work also traces back to work by Steffen Hille and the Bastelbudenbuben project.
This fork does not claim authorship of the original project or the underlying Jarolift protocol implementation.
This is an independent open-source project.
It is not affiliated with, endorsed by or supported by the manufacturer of Jarolift products.
Jarolift is a trademark of its respective owner.
Radio transmitters are subject to local regulations. The user is responsible for operating compatible hardware within the legal limits applicable at their location.
Use this software at your own risk.
See LICENSE and the individual license files of vendored and
third-party components.