Skyweave: Taking Direct Control of the MV2402's OpenWrt Heart
In the world of embedded systems, we often find devices that are powerful yet constrained by the software they ship with. The Magnadyne RVLink system, specifically its roof-mounted MV2402 unit, is one such device. At its core, it's a capable router running OpenWrt, but its functionality is funnelled through an indoor control unit, the RV2458. What if you want to bypass that and speak to the device directly?
This post details the journey of creating Skyweave, a custom, on-device Lua API to achieve direct, authenticated, and programmatic control over the MV2402. The mission: to reverse engineer the internal API and build a clean, modern replacement from the ground up.
First, I think it's worth mentioning that it's very likely you could just flash a standard OpenWrt image to this device. All signs point to the MV2402 being a COMFAST CF-E5 device in a different shell and missing an ethernet port. The device has been supported by OpenWrt for some time now.
Laying the Foundation: A Standard OpenWrt Environment
Before any custom code could be written, the first challenge was to establish a workable development environment on the device itself. The stock firmware presented a few hurdles. While it was based on OpenWrt, the package management system was non-functional.
The breakthrough came from modifying the opkg configuration file (/etc/opkg.conf) to point to the official OpenWrt "Chaos Calmer" 15.05 archive repositories. This small but critical change resurrected the package manager, allowing for the installation of standard, community-trusted OpenWrt packages like uhttpd and the necessary Lua modules.
With a working package manager, I chose to install uhttpd as the web server for the new API. This was a significant architectural decision. The stock system uses nginx and the original webmgnt binary implements the FastCGI protocol to handle requests. Replicating that would have required a more complex FastCGI wrapper. By opting for uhttpd, I could leverage the much simpler standard CGI interface, which is natively supported by Lua and made the development process far more straightforward.
The Mission: Achieving Independence
With a proper foundation in place, the primary goal was to make the MV2402 roof unit fully independent of the indoor controller. This meant we could no longer rely on the proprietary daemons that managed its configuration.
The first crucial step was disabling the wtpd and heart_beat services. This allowed the standard OpenWrt netifd and UCI (Unified Configuration Interface) to take persistent control of the system's networking, paving the way for our own script to manage the device.
With the device freed, the next step was to replicate the core functionalities of the original API, focusing on key endpoints like:
- System status retrieval (
guide_config) - Wi-Fi network scanning
- System log access
- Connecting to a new Wi-Fi network (
motorhome_config_set)
The Blueprint: Deconstructing the Original API
Before building, I had to understand how the original system worked. This meant diving into the firmware.
The original control interface, a CGI binary named webmgnt, was the key. Using Ghidra, a software reverse engineering tool, I analyzed its C disassembly. This process, while painstaking, was invaluable for understanding the device's logic.
A major discovery was how the device applied settings. It wasn't a single command. The webmgnt binary would:
- Use C functions to manipulate UCI settings (
uci_config_commit). - Set an environment variable (
CHANGED_CONFIGS) to signal which services were modified. - Fork and execute a shell script,
/lib/webcfg/apply.sh.
This external shell script was the final piece of the puzzle. It read the environment variable and ran the necessary service reloads and restarts (wifi reload, /etc/init.d/network restartall, etc.). This self-contained mechanism explained why the original API worked even after I had disabled the main daemons and was the blueprint for Skyweave's own "apply" logic.
The Build: Crafting Endpoints in Lua
With a clear understanding of the original system, it was time to build the new API using Lua, a lightweight scripting language perfect for embedded environments like OpenWrt. The API was built with a modern, modular structure, using separate handlers for different functions and utility modules for shared code.
Here’s how some of the key endpoints were implemented:
Wi-Fi Scanning (get_wifi_scan)
The original firmware used the iwinfo library. The Lua implementation does the same, but by calling the command-line tool directly and parsing the output:
-- skyweave/handlers/wifi.lua
function wifi_handler.get_wifi_scan(request_context)
local ifname = request_context.query.ifname or "radio0" -- Default to radio0
-- Validate ifname to known radio interfaces to prevent command injection
if not (ifname == "radio0" or ifname == "radio1") then
return { errCode = -11, errMsg = "Invalid interface name specified: " .. tostring(ifname) }
end
local command = "iwinfo " .. ifname .. " scan"
local handle = io.popen(command)
-- ... parsing logic ...
end
Enhanced System Logs (get_system_log_parsed)
The original API simply dumped the output of the logread command. Skyweave improves on this by not only fetching the logs but parsing each line into a structured JSON object, complete with a SHA256 hash to identify unique entries. Since Lua 5.1 on the device lacks a built-in SHA256 function, this was accomplished with a clever io.popen call to the sha256sum utility.
The Final Boss: Connecting to Wi-Fi (set_connect_wifi)
This was the most critical endpoint. This POST request accepts JSON with network credentials and performs a series of uci commands to configure and connect to the new network. As noted in the project log, the most reliable way to apply the changes was to mimic the original developer's method and restart the entire network stack.
Future Horizons: Next Steps for Skyweave
The work on Skyweave is far from over. With a solid foundation in place, the project has several exciting directions:
- Decommission Legacy Services: The final step in this digital takeover is to fully decommission the original
nginxserver and thewebcfgservice, reconfiguringuhttpdto run on the standard port 80. This will cement Skyweave as the sole control interface on the device. - Legacy API Compatibility: To make Skyweave a true, functional replacement for the original firmware, I plan to add routes to support the legacy API calls. While my own indoor unit is no longer in use, this would allow anyone to adopt Skyweave while retaining the use of their stock controller.
- Intelligent Roaming: A major goal is to implement a
/roamendpoint. This will be more than a simple command; it will involve developing an algorithm to intelligently scan for networks, select the best candidate, and automatically connect. It builds directly on handlers likeset_connect_wifiand represents a move toward true network automation. - Unlocking the LTE Modem: Perhaps the most ambitious goal is to begin the disassembly of
modemsvc, another custom binary that controls the unit's EC-25 LTE modem. A quick IMEI check confirmed the modem is compatible with major carriers. Unlocking it would enable a much-needed LTE failover—a game-changer for staying connected at remote campsites. This venture will cap the project by unlocking the full hardware potential of the device.
Key Takeaways
This journey from a locked-down device to one with a fully custom API offered several key lessons:
- Standard tools are powerful: OpenWrt's standard utilities (
uci,iwinfo,logread) were sufficient to replicate the core functionality of the proprietary API. - Reverse engineering is invaluable: Carefully analyzing the C disassembly was essential to understanding the original firmware's logic, especially its configuration "apply" mechanism.
- Creativity overcomes constraints: Developing for older platforms like OpenWrt Chaos Calmer (which uses Lua 5.1) may require workarounds for missing features like native bitwise operators or SHA256 hashing.
The Skyweave project successfully replaced a restrictive, hard-coded interface with a flexible and powerful Lua-based API, opening the door for much more advanced automation and control of the RVLink system.