Skip to content

Commit f48cd12

Browse files
Fixes to ge cloud timeout
fixes to logfile search on web fixes to code for dynamic load
1 parent c76c0de commit f48cd12

5 files changed

Lines changed: 150 additions & 74 deletions

File tree

‎apps/predbat/gecloud.py‎

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
import requests
88
from datetime import timedelta, datetime, timezone
9-
from utils import str2time, dp1
9+
from utils import str2time, dp1, dp2
1010
import asyncio
1111
import random
1212
import time
@@ -224,7 +224,7 @@ def wait_api_started(self):
224224
"""
225225
self.log("GECloud: Waiting for API to start")
226226
count = 0
227-
while not self.api_started and count < 120:
227+
while not self.api_started and count < 240:
228228
time.sleep(1)
229229
count += 1
230230
if not self.api_started:
@@ -1391,5 +1391,29 @@ def download_ge_data(self, now_utc):
13911391
self.import_today_now = self.import_today.get(0, 0) - self.import_today.get(self.minutes_now, 0)
13921392
self.export_today_now = self.export_today.get(0, 0) - self.export_today.get(self.minutes_now, 0)
13931393
self.pv_today_now = self.pv_today.get(0, 0) - self.pv_today.get(self.minutes_now, 0)
1394+
1395+
1396+
self.log("GE GECloud load_last_period is {} kW".format(dp2(self.load_last_period)))
1397+
# More up to date sensors for current values if set
1398+
if "load_today" in self.args:
1399+
load_minutes, load_minutes_age = self.minute_data_load(self.now_utc, "load_today", self.max_days_previous, required_unit="kWh", load_scaling=self.load_scaling)
1400+
self.load_minutes_now = max(load_minutes.get(0, 0) - load_minutes.get(self.minutes_now, 0), 0)
1401+
self.load_last_period = (load_minutes.get(0, 0) - load_minutes.get(PREDICT_STEP, 0)) * 60 / PREDICT_STEP
1402+
self.log("GE GECloud load_last_period from immediate sensor is {} kW".format(dp2(self.load_last_period)))
1403+
1404+
if "import_today" in self.args:
1405+
import_today = self.minute_data_import_export(self.now_utc, "import_today", scale=self.import_export_scaling, required_unit="kWh")
1406+
self.import_today_now = max(import_today.get(0, 0) - import_today.get(self.minutes_now, 0), 0)
1407+
1408+
# Load export today data
1409+
if "export_today" in self.args:
1410+
export_today = self.minute_data_import_export(self.now_utc, "export_today", scale=self.import_export_scaling, required_unit="kWh")
1411+
self.export_today_now = max(export_today.get(0, 0) - export_today.get(self.minutes_now, 0), 0)
1412+
1413+
# PV today data
1414+
if "pv_today" in self.args:
1415+
pv_today = self.minute_data_import_export(self.now_utc, "pv_today", required_unit="kWh")
1416+
self.pv_today_now = max(pv_today.get(0, 0) - pv_today.get(self.minutes_now, 0), 0)
1417+
13941418
self.log("Downloaded {} datapoints from GE going back {} days".format(len(self.load_minutes), self.load_minutes_age))
13951419
return True

‎apps/predbat/plan.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from datetime import datetime, timedelta
1414
from multiprocessing import Pool, cpu_count
1515
from config import PREDICT_STEP, TIME_FORMAT, MINUTE_WATT
16-
from utils import calc_percent_limit, dp0, dp1, dp2, dp3, dp4, remove_intersecting_windows, calc_percent_limit
16+
from utils import calc_percent_limit, dp0, dp1, dp2, dp3, dp4, remove_intersecting_windows, calc_percent_limit, in_car_slot
1717
from prediction import Prediction, wrapped_run_prediction_single, wrapped_run_prediction_charge, wrapped_run_prediction_charge_min_max, wrapped_run_prediction_export, wrapped_run_prediction_charge_min_max
1818

1919
"""
@@ -64,7 +64,7 @@ def dynamic_load(self):
6464
if self.load_last_status == "high":
6565
have_printed = False
6666
for minute_absolute in range(minutes_now, minutes_end_slot, PREDICT_STEP):
67-
car_load = self.in_car_slot(minute_absolute)
67+
car_load = sum(in_car_slot(minute_absolute, self.num_cars, self.car_charging_slots))
6868
load_last_period = self.load_last_period / 60 * PREDICT_STEP
6969
load_last_period = max(load_last_period - car_load, 0)
7070
if load_last_period > 0:

‎apps/predbat/prediction.py‎

Lines changed: 3 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
from datetime import timedelta
1212
from config import PREDICT_STEP, RUN_EVERY, TIME_FORMAT
13-
from utils import remove_intersecting_windows, get_charge_rate_curve, get_discharge_rate_curve, find_charge_rate, calc_percent_limit
13+
from utils import remove_intersecting_windows, get_charge_rate_curve, get_discharge_rate_curve, find_charge_rate, calc_percent_limit, in_iboost_slot, in_car_slot
1414

1515

1616
# Only assign globals once to avoid re-creating them with processes are forked
@@ -366,47 +366,6 @@ def find_charge_window_optimised(self, charge_windows, charge_limit, is_export=F
366366
charge_window_optimised[minute] = window_n
367367
return charge_window_optimised
368368

369-
def in_iboost_slot(self, minute):
370-
"""
371-
Is the given minute inside a car slot
372-
"""
373-
load_amount = 0
374-
375-
if self.iboost_plan:
376-
for slot in self.iboost_plan:
377-
start_minutes = slot["start"]
378-
end_minutes = slot["end"]
379-
kwh = slot["kwh"]
380-
slot_minutes = end_minutes - start_minutes
381-
slot_hours = slot_minutes / 60.0
382-
383-
# Return the load in that slot
384-
if minute >= start_minutes and minute < end_minutes:
385-
load_amount = abs(kwh / slot_hours)
386-
break
387-
return load_amount
388-
389-
def in_car_slot(self, minute):
390-
"""
391-
Is the given minute inside a car slot
392-
"""
393-
load_amount = [0 for car_n in range(self.num_cars)]
394-
395-
for car_n in range(self.num_cars):
396-
if self.car_charging_slots[car_n]:
397-
for slot in self.car_charging_slots[car_n]:
398-
start_minutes = slot["start"]
399-
end_minutes = slot["end"]
400-
kwh = slot["kwh"]
401-
slot_minutes = end_minutes - start_minutes
402-
slot_hours = slot_minutes / 60.0
403-
404-
# Return the load in that slot
405-
if minute >= start_minutes and minute < end_minutes:
406-
load_amount[car_n] = abs(kwh / slot_hours)
407-
break
408-
return load_amount
409-
410369
def run_prediction(self, charge_limit, charge_window, export_window, export_limits, pv10, end_record, save=None, step=PREDICT_STEP, cache=False):
411370
"""
412371
Run a prediction scenario given a charge limit, return the results
@@ -679,7 +638,7 @@ def run_prediction(self, charge_limit, charge_window, export_window, export_limi
679638
car_freeze = False
680639

681640
if car_enable:
682-
car_load = self.in_car_slot(minute_absolute)
641+
car_load = in_car_slot(minute_absolute, self.num_cars, self.car_charging_slots)
683642

684643
# Car charging?
685644
for car_n in range(self.num_cars):
@@ -724,7 +683,7 @@ def run_prediction(self, charge_limit, charge_window, export_window, export_limi
724683
# IBoost solar diverter on load, don't do on discharge
725684
# IBoost based on plan for given rates
726685
if self.iboost_plan and (self.iboost_on_export or (export_window_n < 0)):
727-
iboost_load = self.in_iboost_slot(minute_absolute) * step / 60.0
686+
iboost_load = in_iboost_slot(minute_absolute, self.iboost_plan) * step / 60.0
728687
iboost_amount = min(iboost_load, self.iboost_max_power * step, max(self.iboost_max_energy - iboost_today_kwh, 0))
729688

730689
# IBoost based on Predbat charging

‎apps/predbat/utils.py‎

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,47 @@
1212
from config import MINUTE_WATT, PREDICT_STEP, TIME_FORMAT, TIME_FORMAT_SECONDS, TIME_FORMAT_OCTOPUS
1313

1414

15+
def in_iboost_slot(minute, iboost_plan):
16+
"""
17+
Is the given minute inside a car slot
18+
"""
19+
load_amount = 0
20+
21+
if iboost_plan:
22+
for slot in iboost_plan:
23+
start_minutes = slot["start"]
24+
end_minutes = slot["end"]
25+
kwh = slot["kwh"]
26+
slot_minutes = end_minutes - start_minutes
27+
slot_hours = slot_minutes / 60.0
28+
29+
# Return the load in that slot
30+
if minute >= start_minutes and minute < end_minutes:
31+
load_amount = abs(kwh / slot_hours)
32+
break
33+
return load_amount
34+
35+
def in_car_slot(minute, num_cars, car_charging_slots):
36+
"""
37+
Is the given minute inside a car slot
38+
"""
39+
load_amount = [0 for car_n in range(num_cars)]
40+
41+
for car_n in range(num_cars):
42+
if car_charging_slots[car_n]:
43+
for slot in car_charging_slots[car_n]:
44+
start_minutes = slot["start"]
45+
end_minutes = slot["end"]
46+
kwh = slot["kwh"]
47+
slot_minutes = end_minutes - start_minutes
48+
slot_hours = slot_minutes / 60.0
49+
50+
# Return the load in that slot
51+
if minute >= start_minutes and minute < end_minutes:
52+
load_amount[car_n] = abs(kwh / slot_hours)
53+
break
54+
return load_amount
55+
1556
def time_string_to_stamp(time_string):
1657
"""
1758
Convert a time string to a timestamp

‎apps/predbat/web_helper.py‎

Lines changed: 78 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1891,12 +1891,14 @@ def get_log_css():
18911891
}
18921892
18931893
/* Highlight matching text */
1894-
.search-highlight {
1894+
mark.search-highlight {
18951895
background-color: #ffeb3b;
18961896
font-weight: bold;
1897+
padding: 0;
1898+
border-radius: 2px;
18971899
}
18981900
1899-
body.dark-mode .search-highlight {
1901+
body.dark-mode mark.search-highlight {
19001902
background-color: #ffa000;
19011903
color: #000;
19021904
}
@@ -2364,7 +2366,7 @@ def get_logfile_js(filter_type):
23642366
// Re-apply filter to include new entries
23652367
setTimeout(() => {{
23662368
filterLogEntries();
2367-
}}, 10);
2369+
}}, 50);
23682370
}}
23692371
23702372
// Auto-scroll to top for new entries (since newest are at top)
@@ -2393,7 +2395,7 @@ def get_logfile_js(filter_type):
23932395
const searchTerm = document.getElementById('logSearchInput').value.toLowerCase();
23942396
const rows = document.querySelectorAll('#logTableBody tr[data-line]');
23952397
const statusDiv = document.getElementById('searchStatus');
2396-
2398+
23972399
let visibleCount = 0;
23982400
let totalCount = rows.length;
23992401
@@ -2402,10 +2404,7 @@ def get_logfile_js(filter_type):
24022404
rows.forEach(row => {{
24032405
row.classList.remove('log-entry-hidden');
24042406
// 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-
}});
2407+
clearHighlights(row);
24092408
visibleCount++;
24102409
}});
24112410
statusDiv.textContent = '';
@@ -2415,36 +2414,89 @@ def get_logfile_js(filter_type):
24152414
// Filter and highlight entries
24162415
rows.forEach(row => {{
24172416
const rowText = row.textContent.toLowerCase();
2418-
2417+
24192418
if (rowText.includes(searchTerm)) {{
24202419
row.classList.remove('log-entry-hidden');
24212420
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-
}});
2421+
2422+
// Clear existing highlights first
2423+
clearHighlights(row);
2424+
2425+
// Apply new highlights
2426+
highlightTextInElement(row, searchTerm);
24332427
}} else {{
24342428
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-
}});
2429+
// Clear highlights from hidden rows
2430+
clearHighlights(row);
24402431
}}
24412432
}});
24422433
24432434
// Update status
24442435
statusDiv.textContent = `Showing ${{visibleCount}} of ${{totalCount}} entries`;
24452436
}}
24462437
2447-
// Clear search function
2438+
// Clear all highlights in an element
2439+
function clearHighlights(element) {{
2440+
const highlightedElements = element.querySelectorAll('.search-highlight');
2441+
highlightedElements.forEach(highlighted => {{
2442+
const parent = highlighted.parentNode;
2443+
parent.replaceChild(document.createTextNode(highlighted.textContent), highlighted);
2444+
parent.normalize(); // Merge adjacent text nodes
2445+
}});
2446+
}}
2447+
2448+
// Highlight text in an element without corrupting HTML
2449+
function highlightTextInElement(element, searchTerm) {{
2450+
const walker = document.createTreeWalker(
2451+
element,
2452+
NodeFilter.SHOW_TEXT,
2453+
null,
2454+
false
2455+
);
2456+
2457+
const textNodes = [];
2458+
let node;
2459+
while (node = walker.nextNode()) {{
2460+
textNodes.push(node);
2461+
}}
2462+
2463+
textNodes.forEach(textNode => {{
2464+
const text = textNode.textContent;
2465+
const lowerText = text.toLowerCase();
2466+
const lowerSearchTerm = searchTerm.toLowerCase();
2467+
2468+
if (lowerText.includes(lowerSearchTerm)) {{
2469+
const parent = textNode.parentNode;
2470+
const fragment = document.createDocumentFragment();
2471+
2472+
let lastIndex = 0;
2473+
let index = lowerText.indexOf(lowerSearchTerm, 0);
2474+
2475+
while (index !== -1) {{
2476+
// Add text before the match
2477+
if (index > lastIndex) {{
2478+
fragment.appendChild(document.createTextNode(text.substring(lastIndex, index)));
2479+
}}
2480+
2481+
// Add highlighted match
2482+
const mark = document.createElement('mark');
2483+
mark.className = 'search-highlight';
2484+
mark.textContent = text.substring(index, index + searchTerm.length);
2485+
fragment.appendChild(mark);
2486+
2487+
lastIndex = index + searchTerm.length;
2488+
index = lowerText.indexOf(lowerSearchTerm, lastIndex);
2489+
}}
2490+
2491+
// Add remaining text
2492+
if (lastIndex < text.length) {{
2493+
fragment.appendChild(document.createTextNode(text.substring(lastIndex)));
2494+
}}
2495+
2496+
parent.replaceChild(fragment, textNode);
2497+
}}
2498+
}});
2499+
}} // Clear search function
24482500
function clearLogSearch() {{
24492501
document.getElementById('logSearchInput').value = '';
24502502
filterLogEntries();

0 commit comments

Comments
 (0)