Skip to content

Commit 089484f

Browse files
Enable real time control for GECloud (#2698)
* Enable real time control for GECloud Add log file search * [pre-commit.ci lite] apply automatic fixes --------- Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
1 parent 010abe9 commit 089484f

4 files changed

Lines changed: 202 additions & 2 deletions

File tree

‎apps/predbat/gecloud.py‎

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -520,6 +520,26 @@ async def publish_meter(self, device, meter):
520520
self.base.dashboard_item(entity_name + "_grid_import_total", state=meter[key][subkey].get("import", 0), attributes=attribute_table.get("grid_import_total", {}), app="gecloud")
521521
self.base.dashboard_item(entity_name + "_grid_export_total", state=meter[key][subkey].get("export", 0), attributes=attribute_table.get("grid_export_total", {}), app="gecloud")
522522

523+
async def enable_real_time_control(self, device, registers):
524+
for key in registers:
525+
reg_name = registers[key].get("name", None)
526+
value = registers[key].get("value", None)
527+
if "real-time control" in reg_name.lower():
528+
if value:
529+
self.log("GECloud: Real-time control already enabled for {}".format(device))
530+
return True
531+
else:
532+
self.log("GECloud: Enabling real-time control for {} as current value is {}".format(device, value))
533+
result = await self.async_write_inverter_setting(device, key, True)
534+
if result and ("value" in result):
535+
registers[key]["value"] = result["value"]
536+
await self.publish_registers(device, self.settings[device], select_key=key)
537+
return True
538+
else:
539+
self.log("GECloud: Failed to enable real-time control for {}".format(device))
540+
return False
541+
return False
542+
523543
async def publish_registers(self, device, registers, select_key=None):
524544
"""
525545
Publish the registers
@@ -531,7 +551,7 @@ async def publish_registers(self, device, registers, select_key=None):
531551
validation_rules = registers[key].get("validation_rules", None)
532552
validation = registers[key].get("validation", None)
533553
value = registers[key].get("value", None)
534-
ha_name = reg_name.lower().replace(" ", "_").replace("%", "percent")
554+
ha_name = reg_name.lower().replace(" ", "_").replace("%", "percent").replace("-", "_")
535555
attributes = {}
536556
attributes["friendly_name"] = reg_name
537557

@@ -750,6 +770,8 @@ async def start(self):
750770
if seconds == 0 or self.polling_mode or (device == ems_device) or (device == gateway_device):
751771
self.settings[device] = await self.async_get_inverter_settings(device, first=False, previous=self.settings.get(device, {}))
752772
await self.publish_registers(device, self.settings[device])
773+
if seconds == 0:
774+
await self.enable_real_time_control(device, self.settings[device])
753775

754776
except Exception as e:
755777
self.log("Error: GECloud: Exception in main loop {}".format(e))

‎apps/predbat/predbat.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
import requests
3737
import asyncio
3838

39-
THIS_VERSION = "v8.25.2"
39+
THIS_VERSION = "v8.25.3"
4040

4141
# fmt: off
4242
PREDBAT_FILES = ["predbat.py", "config.py", "prediction.py", "gecloud.py","utils.py", "inverter.py", "ha.py", "download.py", "unit_test.py", "web.py", "web_helper.py", "predheat.py", "futurerate.py", "octopus.py", "solcast.py","execute.py", "plan.py", "fetch.py", "output.py", "userinterface.py", "energydataservice.py", "alertfeed.py", "compare.py", "db_manager.py", "db_engine.py", "plugin_system.py", "ohme.py", "components.py", "fox.py"]

‎apps/predbat/web.py‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1307,6 +1307,12 @@ async def html_log(self, request):
13071307
text += '<button id="pauseResumeBtn" onclick="toggleUpdates()" style="margin-left: 10px; padding: 4px 8px;">Pause</button>'
13081308
text += "</div>"
13091309

1310+
text += '<div class="log-search-container">'
1311+
text += '<input type="text" id="logSearchInput" class="log-search-input" placeholder="Search log entries..." oninput="filterLogEntries()" />'
1312+
text += '<button id="clearSearchBtn" class="clear-search-button" onclick="clearLogSearch()">Clear</button>'
1313+
text += '<div id="searchStatus" class="search-status"></div>'
1314+
text += "</div>"
1315+
13101316
text += '<div id="logStatus" class="log-status">Loading log data...</div>'
13111317
text += "<table width=100% id='logTable'>\n"
13121318
text += "<tbody id='logTableBody'>\n"

‎apps/predbat/web_helper.py‎

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1804,6 +1804,102 @@ def get_log_css():
18041804
background-color: #4CAF50;
18051805
color: white;
18061806
}
1807+
1808+
/* Log search container styles */
1809+
.log-search-container {
1810+
display: flex;
1811+
align-items: center;
1812+
margin-bottom: 10px;
1813+
padding: 8px;
1814+
background-color: #f8f9fa;
1815+
border: 1px solid #ddd;
1816+
border-radius: 4px;
1817+
gap: 8px;
1818+
}
1819+
1820+
.log-search-input {
1821+
flex-grow: 1;
1822+
padding: 6px 10px;
1823+
border: 1px solid #ccc;
1824+
border-radius: 4px;
1825+
font-size: 14px;
1826+
min-width: 200px;
1827+
}
1828+
1829+
.log-search-input:focus {
1830+
outline: none;
1831+
border-color: #4CAF50;
1832+
box-shadow: 0 0 0 2px rgba(76, 175, 80, 0.2);
1833+
}
1834+
1835+
.clear-search-button {
1836+
padding: 6px 12px;
1837+
background-color: #6c757d;
1838+
color: white;
1839+
border: none;
1840+
border-radius: 4px;
1841+
cursor: pointer;
1842+
font-size: 14px;
1843+
white-space: nowrap;
1844+
}
1845+
1846+
.clear-search-button:hover {
1847+
background-color: #5a6268;
1848+
}
1849+
1850+
.search-status {
1851+
font-size: 12px;
1852+
color: #6c757d;
1853+
white-space: nowrap;
1854+
}
1855+
1856+
/* Dark mode search styles */
1857+
body.dark-mode .log-search-container {
1858+
background-color: #2d3748;
1859+
border-color: #4a5568;
1860+
}
1861+
1862+
body.dark-mode .log-search-input {
1863+
background-color: #4a5568;
1864+
border-color: #718096;
1865+
color: #fff;
1866+
}
1867+
1868+
body.dark-mode .log-search-input:focus {
1869+
border-color: #4CAF50;
1870+
}
1871+
1872+
body.dark-mode .log-search-input::placeholder {
1873+
color: #a0aec0;
1874+
}
1875+
1876+
body.dark-mode .clear-search-button {
1877+
background-color: #4a5568;
1878+
}
1879+
1880+
body.dark-mode .clear-search-button:hover {
1881+
background-color: #2d3748;
1882+
}
1883+
1884+
body.dark-mode .search-status {
1885+
color: #a0aec0;
1886+
}
1887+
1888+
/* Hide filtered log entries */
1889+
.log-entry-hidden {
1890+
display: none !important;
1891+
}
1892+
1893+
/* Highlight matching text */
1894+
.search-highlight {
1895+
background-color: #ffeb3b;
1896+
font-weight: bold;
1897+
}
1898+
1899+
body.dark-mode .search-highlight {
1900+
background-color: #ffa000;
1901+
color: #000;
1902+
}
18071903
</style>
18081904
"""
18091905

@@ -2262,6 +2358,15 @@ def get_logfile_js(filter_type):
22622358
}}
22632359
}});
22642360
2361+
// Apply current search filter to new entries
2362+
const searchTerm = document.getElementById('logSearchInput').value.toLowerCase();
2363+
if (searchTerm.trim() !== '') {{
2364+
// Re-apply filter to include new entries
2365+
setTimeout(() => {{
2366+
filterLogEntries();
2367+
}}, 10);
2368+
}}
2369+
22652370
// Auto-scroll to top for new entries (since newest are at top)
22662371
if (newEntriesAdded > 0 && shouldAutoScroll) {{
22672372
setTimeout(() => {{
@@ -2283,6 +2388,73 @@ def get_logfile_js(filter_type):
22832388
}});
22842389
}}
22852390
2391+
// Search functionality
2392+
function filterLogEntries() {{
2393+
const searchTerm = document.getElementById('logSearchInput').value.toLowerCase();
2394+
const rows = document.querySelectorAll('#logTableBody tr[data-line]');
2395+
const statusDiv = document.getElementById('searchStatus');
2396+
2397+
let visibleCount = 0;
2398+
let totalCount = rows.length;
2399+
2400+
// If search is empty, show all entries
2401+
if (searchTerm.trim() === '') {{
2402+
rows.forEach(row => {{
2403+
row.classList.remove('log-entry-hidden');
2404+
// Remove any existing highlights
2405+
const cells = row.querySelectorAll('td');
2406+
cells.forEach(cell => {{
2407+
cell.innerHTML = cell.innerHTML.replace(/<mark class="search-highlight">(.*?)<\\/mark>/gi, '$1');
2408+
}});
2409+
visibleCount++;
2410+
}});
2411+
statusDiv.textContent = '';
2412+
return;
2413+
}}
2414+
2415+
// Filter and highlight entries
2416+
rows.forEach(row => {{
2417+
const rowText = row.textContent.toLowerCase();
2418+
2419+
if (rowText.includes(searchTerm)) {{
2420+
row.classList.remove('log-entry-hidden');
2421+
visibleCount++;
2422+
2423+
// Highlight matching text
2424+
const cells = row.querySelectorAll('td');
2425+
cells.forEach(cell => {{
2426+
// First remove any existing highlights
2427+
cell.innerHTML = cell.innerHTML.replace(/<mark class="search-highlight">(.*?)<\\/mark>/gi, '$1');
2428+
2429+
// Then add new highlights
2430+
const regex = new RegExp(`(${{escapeRegExp(searchTerm)}})`, 'gi');
2431+
cell.innerHTML = cell.innerHTML.replace(regex, '<mark class="search-highlight">$1</mark>');
2432+
}});
2433+
}} else {{
2434+
row.classList.add('log-entry-hidden');
2435+
// Remove highlights from hidden rows
2436+
const cells = row.querySelectorAll('td');
2437+
cells.forEach(cell => {{
2438+
cell.innerHTML = cell.innerHTML.replace(/<mark class="search-highlight">(.*?)<\\/mark>/gi, '$1');
2439+
}});
2440+
}}
2441+
}});
2442+
2443+
// Update status
2444+
statusDiv.textContent = `Showing ${{visibleCount}} of ${{totalCount}} entries`;
2445+
}}
2446+
2447+
// Clear search function
2448+
function clearLogSearch() {{
2449+
document.getElementById('logSearchInput').value = '';
2450+
filterLogEntries();
2451+
}}
2452+
2453+
// Escape special regex characters
2454+
function escapeRegExp(string) {{
2455+
return string.replace(/[.*+?^${{}}()|[\\]\\\\]/g, '\\\\$&');
2456+
}}
2457+
22862458
// Fetch new log entries
22872459
async function updateLog() {{
22882460
if (isUpdating || isPaused) return;

0 commit comments

Comments
 (0)