Skip to content

Commit

Permalink
Attempt to improve situation from issue rbroker#18:
Browse files Browse the repository at this point in the history
- If WiFi is disconnected for more than 10 mins after successful startup, try to reboot the ESP32.
- Extend connection wait on boot to 10 minutes.
- Allow user to disable "fallback to AP mode" behaviour if WiFi connection cannot be established.
- Add new LED flash mode (rapid even blinking) if WiFi becomes disconnected
  • Loading branch information
rbroker committed Jan 6, 2024
1 parent 5a0ea97 commit 97243e1
Show file tree
Hide file tree
Showing 8 changed files with 91 additions and 24 deletions.
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,11 @@ Note: If this setting or "WiFi SSID" are unset, the device will continue to boot
| ------- | -------- |
| `""` | Yes |

### Auto-Reset WiFi Settings
Some parameters of your Mitsubishi Ecodan HVAC
| Description | Default |
| Check this option to fall back to broadcasting a "captive portal" WiFi access point if the network connection is lost for a long time (~20 mins). Intended to avoid the need for physically accessing the ESP32 if (e.g. a router is replaced, and the SSID / password are different). If a device password is set, a device password will be required to connect to the captive port access point. | False |

### Hostname
The network hostname the device will use to identify itself.

Expand Down
58 changes: 51 additions & 7 deletions ecodan-ha-local/ecodan-ha-local.ino
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ bool heatpumpInitialized = false;
uint8_t ledTick = 0;
const uint8_t ledTickPatternHpDisconnect[] = { HIGH, LOW, HIGH, LOW, HIGH, HIGH, HIGH, HIGH, LOW };
const uint8_t ledTickPatternMqttDisconnect[] = { HIGH, HIGH, HIGH, LOW, LOW, LOW };
const uint8_t ledTickPatternWiFiDisconnected[] = { HIGH, LOW };
std::chrono::steady_clock::time_point wifiDisconnectDetected = std::chrono::steady_clock::time_point::min();
const auto maxWifiDisconnectLength = std::chrono::minutes{10}; // If the WiFi is disconnected for this length of time, reboot and try to re-initialize.

bool initialize_wifi_access_point()
{
Expand All @@ -38,9 +41,10 @@ bool initialize_wifi_access_point()
}
WiFi.begin(config.WifiSsid.c_str(), config.WifiPassword.c_str());

// Give us ~2 mins to re-establish a WiFi connection before we give up,
// Give us 10 mins to re-establish a WiFi connection before we give up,
// just in case there's a power cut and the router needs time to boot.
for (int i = 0; i < 240; ++i)
auto connectDuration = (std::chrono::seconds{maxWifiDisconnectLength}.count() * 2);
for (int i = 0; i < connectDuration; ++i)
{
if (WiFi.isConnected())
break;
Expand All @@ -51,10 +55,18 @@ bool initialize_wifi_access_point()

if (!WiFi.isConnected())
{
ehal::log_web(F("Couldn't connect to WiFi network on boot, falling back to AP mode."));
config.WifiPassword.clear();
config.WifiSsid.clear();
ehal::save_configuration(config);
if (config.WifiReset)
{
ehal::log_web(F("Couldn't connect to WiFi network on boot, falling back to AP mode."));
config.WifiPassword.clear();
config.WifiSsid.clear();
ehal::save_configuration(config);
}
else
{
ehal::log_web(F("Couldn't connect to WiFi network on boot, restarting board."));
}

ESP.restart();
}
}
Expand Down Expand Up @@ -113,7 +125,11 @@ void update_status_led()

auto& config = ehal::config_instance();

if (!ehal::hp::is_connected())
if (!WiFi.isConnected())
{
digitalWrite(config.StatusLed, ledTickPatternWiFiDisconnected[ledTick++ % sizeof(ledTickPatternWiFiDisconnected)]);
}
else if (!ehal::hp::is_connected())
{
digitalWrite(config.StatusLed, ledTickPatternHpDisconnect[ledTick++ % sizeof(ledTickPatternHpDisconnect)]);
}
Expand Down Expand Up @@ -166,6 +182,32 @@ void log_last_reset_reason()
}
}

void reboot_if_wifi_disconnected_too_long()
{
if (!WiFi.isConnected())
{
if (wifiDisconnectDetected != std::chrono::steady_clock::time_point::min())
{
// If it's been more than maxWifiDisconnectLength since we had a WiFi connection, restart.
if ((std::chrono::steady_clock::now() - wifiDisconnectDetected) > maxWifiDisconnectLength)
ESP.restart();
}
else
{
// This is the first we've seen of the disconnect, set off our timer.
wifiDisconnectDetected = std::chrono::steady_clock::now();
ehal::log_web(F("WiFi disconnection detected... allowing up to %llu minutes to recover..."), maxWifiDisconnectLength.count());
}
}
else if (wifiDisconnectDetected != std::chrono::steady_clock::time_point::min())
{
// If we recovered the connection automatically, reset our disconnect timer.
auto disconnectLength = std::chrono::duration_cast<std::chrono::seconds>(std::chrono::steady_clock::now() - wifiDisconnectDetected).count();
ehal::log_web(F("WiFi connection re-established after (%llu) seconds."), disconnectLength);
wifiDisconnectDetected = std::chrono::steady_clock::time_point::min();
}
}

void setup()
{
if (!ehal::load_saved_configuration())
Expand Down Expand Up @@ -219,6 +261,8 @@ void loop()

update_time(/* force =*/false);
update_status_led();

reboot_if_wifi_disconnected_too_long();
}
catch (std::exception const& ex)
{
Expand Down
12 changes: 7 additions & 5 deletions ecodan-ha-local/ehal_config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,13 @@ namespace ehal
prefs.begin("config", true);

Config& config = config_instance();
config.DevicePassword = prefs.getString("device_pw");
config.DevicePassword = prefs.getString("device_pw");
config.SerialRxPort = prefs.getUShort("serial_rx", 27U);
config.SerialTxPort = prefs.getUShort("serial_tx", 26U);
config.StatusLed = prefs.getUShort("status_led", LED_BUILTIN);
config.DumpPackets = prefs.getBool("dump_pkt", false);
config.CoolEnabled = prefs.getBool("cool_enabled", false);
config.WifiReset = prefs.getBool("wifi_reset", true);
config.HostName = prefs.getString("hostname", "ecodan_ha_local");
config.WifiSsid = prefs.getString("wifi_ssid");
config.WifiPassword = prefs.getString("wifi_pw");
Expand All @@ -45,12 +46,13 @@ namespace ehal
{
Preferences prefs;
prefs.begin("config", /* readonly = */ false);
prefs.putString("device_pw", config.DevicePassword);
prefs.putString("device_pw", config.DevicePassword);
prefs.putUShort("serial_rx", config.SerialRxPort);
prefs.putUShort("serial_tx", config.SerialTxPort);
prefs.putUShort("serial_tx", config.SerialTxPort);
prefs.putUShort("status_led", config.StatusLed);
prefs.putBool("dump_pkt", config.DumpPackets);
prefs.putBool("cool_enabled", config.CoolEnabled);
prefs.putBool("cool_enabled", config.CoolEnabled);
prefs.putBool("wifi_reset", config.WifiReset);
prefs.putString("wifi_ssid", config.WifiSsid);
prefs.putString("wifi_pw", config.WifiPassword);
prefs.putString("hostname", config.HostName);
Expand Down Expand Up @@ -82,7 +84,7 @@ namespace ehal

String get_software_version()
{
return FPSTR("v0.1.4");
return FPSTR("v0.1.5");
}

} // namespace ehal
1 change: 1 addition & 0 deletions ecodan-ha-local/ehal_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ namespace ehal
uint16_t StatusLed;
bool DumpPackets;
bool CoolEnabled;
bool WifiReset;
String WifiSsid;
String WifiPassword;
String HostName;
Expand Down
2 changes: 1 addition & 1 deletion ecodan-ha-local/ehal_diagnostics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ namespace ehal
#define MAX_NUM_ELEMENTS 32U

void log_web(const __FlashStringHelper* fmt, ...)
{
{
std::unique_ptr<char[]> buffer{ new char[MAX_MESSAGE_LENGTH] };

va_list args;
Expand Down
2 changes: 1 addition & 1 deletion ecodan-ha-local/ehal_hp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -438,7 +438,7 @@ namespace ehal::hp
cmd[1] = SET_SETTINGS_FLAG_MODE_TOGGLE;
cmd[3] = on ? 1 : 0;
{
std::lock_guard<std::mutex> lock{cmdQueueMutex};
std::lock_guard<std::mutex> lock{cmdQueueMutex};
cmdQueue.emplace(std::move(cmd));
}

Expand Down
22 changes: 13 additions & 9 deletions ecodan-ha-local/ehal_html.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ namespace ehal
<title>Ecodan: Home Assistant Bridge</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="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/milligram.css">
<link rel="stylesheet" href="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/milligram.css">
<script type="text/javascript" {{PAGE_SCRIPT}}></script>
</head>
<body class="container">
Expand Down Expand Up @@ -69,6 +69,10 @@ namespace ehal
<label class="column column-25" for="dump_pkt">Dump Serial Packets:</label>
<input class="column column-75" type="checkbox" id="dump_pkt" name="dump_pkt" {{dump_pkt}} />
</div>
<div class="row">
<label class="column column-25" for="wifi_reset">Auto-Reset WiFi Settings:</label>
<input class="column column-75" type="checkbox" id="wifi_reset" name="wifi_reset" {{wifi_reset}} />
</div>
<br />
<h2>Heat Pump Configuration:</h2>
<div class="row">
Expand All @@ -78,19 +82,19 @@ namespace ehal
<br />
<h2>WiFi Configuration:</h2>
<div class="row">
<label class="column column-25" for="wifi_ssid">WiFi SSID:</label>
<div class="column column-75">
<label class="column column-25" for="wifi_ssid">WiFi SSID:</label>
<div class="column column-75">
<select id="wifi_ssid" name="wifi_ssid" onchange='update_ssi()' style="width:90%;" required>
<option id="pre-ssid" value="{{wifi_ssid}}">{{wifi_ssid}}</option>
</select>
</select>
<div id="ssi" class="signal-icon">
<div class="signal-bar"></div>
<div class="signal-bar"></div>
<div class="signal-bar"></div>
<div class="signal-bar"></div>
<div class="signal-bar"></div>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<label class="column column-25" for="wifi_pw">WiFi Password:</label>
Expand Down Expand Up @@ -235,7 +239,7 @@ namespace ehal
<tr>
<td>Heat Pump Message Rx Count:</td>
<td>{{hp_rx_count}}</td>
</tr>
</tr>
</table>
<h2>Logs</h2>
<pre><code class="column column-33 column-offset-33" style="max-height:250px;overflow:auto;" id="logs">
Expand Down Expand Up @@ -270,12 +274,12 @@ namespace ehal
<td>{{outside_temp}}&#176;C</td>
</tr>
<thead>
<th colspan="2">Efficiency (Last 24h)</th>
<th colspan="2">Efficiency (Last 24h)</th>
</thead>
<tr>
<td>Space Heating:</td>
<td>{{sh_consumed}}kWh &rarr; {{sh_delivered}}kWh (COP: {{sh_cop}})</td>
</tr>
</tr>
<tr>
<td>DHW:</td>
<td>{{dhw_consumed}}kWh &rarr; {{dhw_delivered}}kWh (COP: {{dhw_cop}})</td>
Expand Down
13 changes: 12 additions & 1 deletion ecodan-ha-local/ehal_http.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,11 @@ namespace ehal::http
else
page.replace(F("{{dump_pkt}}"), "");

if (config.WifiReset)
page.replace(F("{{wifi_reset}}"), F("checked"));
else
page.replace(F("{{wifi_reset}}"), "");

// Heat pump config
if (config.CoolEnabled)
page.replace(F("{{cool_enabled}}"), F("checked"));
Expand Down Expand Up @@ -202,14 +207,20 @@ namespace ehal::http
config.DumpPackets = true;
else
config.DumpPackets = false;

if (server.hasArg(F("cool_enabled")))
config.CoolEnabled = true;
else
config.CoolEnabled = false;

if (server.hasArg(F("wifi_reset")))
config.WifiReset = true;
else
config.WifiReset = false;

config.WifiSsid = server.arg(F("wifi_ssid"));
config.WifiPassword = server.arg(F("wifi_pw"));

config.HostName = server.arg(F("hostname"));
config.MqttServer = server.arg(F("mqtt_server"));
config.MqttPort = server.arg(F("mqtt_port")).toInt();
Expand Down

0 comments on commit 97243e1

Please sign in to comment.