|
11 | 11 |
|
12 | 12 | from datetime import datetime, timedelta |
13 | 13 | from utils import minutes_to_time, str2time, dp0, dp1, dp2, dp3, dp4, time_string_to_stamp, minute_data |
14 | | -from config import MINUTE_WATT, PREDICT_STEP, TIME_FORMAT, TIME_FORMAT_SECONDS, PREDBAT_MODE_OPTIONS, PREDBAT_MODE_CONTROL_SOC, PREDBAT_MODE_CONTROL_CHARGEDISCHARGE, PREDBAT_MODE_CONTROL_CHARGE, PREDBAT_MODE_MONITOR, TIME_FORMAT_DAILY |
| 14 | +from config import MINUTE_WATT, PREDICT_STEP, TIME_FORMAT, PREDBAT_MODE_OPTIONS, PREDBAT_MODE_CONTROL_SOC, PREDBAT_MODE_CONTROL_CHARGEDISCHARGE, PREDBAT_MODE_CONTROL_CHARGE, PREDBAT_MODE_MONITOR |
15 | 15 | from futurerate import FutureRate |
16 | 16 |
|
17 | 17 |
|
@@ -430,109 +430,6 @@ def minute_data_load(self, now_utc, entity_name, max_days_previous, load_scaling |
430 | 430 | age_days = 0 |
431 | 431 | return load_minutes, age_days |
432 | 432 |
|
433 | | - def prune_today(self, data, prune=True, group=15, prune_future=False, intermediate=False): |
434 | | - """ |
435 | | - Remove data from before today |
436 | | - """ |
437 | | - results = {} |
438 | | - last_time = None |
439 | | - prev_value = None |
440 | | - for key in data: |
441 | | - # Convert key in format '2024-09-07T15:40:09.799567+00:00' into a datetime |
442 | | - if "." in key: |
443 | | - timekey = datetime.strptime(key, TIME_FORMAT_SECONDS) |
444 | | - else: |
445 | | - timekey = datetime.strptime(key, TIME_FORMAT) |
446 | | - if last_time and (timekey - last_time).seconds < group * 60: |
447 | | - continue |
448 | | - if intermediate and last_time and ((timekey - last_time).seconds > group * 60): |
449 | | - # Large gap, introduce intermediate data point |
450 | | - seconds_gap = int((timekey - last_time).total_seconds()) |
451 | | - for i in range(1, seconds_gap // int(group * 60)): |
452 | | - new_time = last_time + timedelta(seconds=i * group * 60) |
453 | | - results[new_time.strftime(TIME_FORMAT)] = prev_value |
454 | | - if not prune or (timekey > self.midnight_utc): |
455 | | - if prune_future and (timekey > self.now_utc): |
456 | | - continue |
457 | | - results[key] = data[key] |
458 | | - last_time = timekey |
459 | | - prev_value = data[key] |
460 | | - return results |
461 | | - |
462 | | - def history_attribute(self, history, state_key="state", last_updated_key="last_updated", scale=1.0, attributes=False, daily=False, offset_days=0, first=True, pounds=False): |
463 | | - results = {} |
464 | | - last_updated_time = None |
465 | | - last_day_stamp = None |
466 | | - |
467 | | - if not isinstance(history, list): |
468 | | - return results |
469 | | - |
470 | | - if history and len(history) >= 1: |
471 | | - history = history[0] |
472 | | - |
473 | | - if not isinstance(history, list): |
474 | | - self.log("Warn: history_attribute expects a list of history items, got {}".format(type(history))) |
475 | | - return results |
476 | | - |
477 | | - # Process history |
478 | | - for item in history: |
479 | | - if last_updated_key not in item: |
480 | | - continue |
481 | | - |
482 | | - if attributes: |
483 | | - if state_key not in item["attributes"]: |
484 | | - continue |
485 | | - state = item["attributes"][state_key] |
486 | | - else: |
487 | | - # Ignore data without correct keys |
488 | | - if state_key not in item: |
489 | | - continue |
490 | | - |
491 | | - # Unavailable or bad values |
492 | | - if item[state_key] == "unavailable" or item[state_key] == "unknown": |
493 | | - continue |
494 | | - |
495 | | - state = item[state_key] |
496 | | - |
497 | | - # Get the numerical key and the timestamp and ignore if in error |
498 | | - try: |
499 | | - state = float(state) * scale |
500 | | - if pounds: |
501 | | - state = dp2(state / 100) |
502 | | - except (ValueError, TypeError): |
503 | | - if isinstance(state, str): |
504 | | - if state.lower() in ["on", "true", "yes"]: |
505 | | - state = 1 |
506 | | - elif state.lower() in ["off", "false", "no"]: |
507 | | - state = 0 |
508 | | - else: |
509 | | - continue |
510 | | - else: |
511 | | - continue |
512 | | - |
513 | | - try: |
514 | | - last_updated_time = item[last_updated_key] |
515 | | - last_updated_stamp = str2time(last_updated_time) |
516 | | - except (ValueError, TypeError): |
517 | | - continue |
518 | | - |
519 | | - day_stamp = last_updated_stamp.astimezone().replace(hour=0, minute=0, second=0, microsecond=0) |
520 | | - if offset_days: |
521 | | - day_stamp += timedelta(days=offset_days) |
522 | | - |
523 | | - if first and daily and day_stamp == last_day_stamp: |
524 | | - continue |
525 | | - last_day_stamp = day_stamp |
526 | | - |
527 | | - # Add the state to the result |
528 | | - if daily: |
529 | | - # Convert day stamp from UTC into localtime |
530 | | - results[day_stamp.strftime(TIME_FORMAT_DAILY)] = state |
531 | | - else: |
532 | | - results[last_updated_time] = state |
533 | | - |
534 | | - return results |
535 | | - |
536 | 433 | def fetch_sensor_data(self): |
537 | 434 | """ |
538 | 435 | Fetch all the data, e.g. energy rates, load, PV predictions, car plan etc. |
@@ -944,6 +841,50 @@ def fetch_sensor_data(self): |
944 | 841 | else: |
945 | 842 | self.load_inday_adjustment = 1.0 |
946 | 843 |
|
| 844 | + def fetch_pv_forecast(self): |
| 845 | + """ |
| 846 | + Fetch PV forecast data from one or more sensors |
| 847 | + """ |
| 848 | + pv_forecast_minute = {} |
| 849 | + pv_forecast_minute10 = {} |
| 850 | + |
| 851 | + # Get data from forecast sensor |
| 852 | + entity_id = "sensor." + self.prefix + "_pv_forecast_raw" |
| 853 | + pv_forecast_packed_ld = self.get_state_wrapper(entity_id=entity_id, attribute="forecast") |
| 854 | + pv_forecast10_packed_ld = self.get_state_wrapper(entity_id=entity_id, attribute="forecast10") |
| 855 | + |
| 856 | + # Convert keys to integers and values to floats |
| 857 | + pv_forecast_packed = {} |
| 858 | + pv_forecast10_packed = {} |
| 859 | + |
| 860 | + if pv_forecast_packed_ld: |
| 861 | + for key, value in pv_forecast_packed_ld.items(): |
| 862 | + try: |
| 863 | + minute = int(key) |
| 864 | + pv_forecast_packed[minute] = float(value) |
| 865 | + except (ValueError, TypeError): |
| 866 | + pass |
| 867 | + |
| 868 | + if pv_forecast10_packed_ld: |
| 869 | + for key, value in pv_forecast10_packed_ld.items(): |
| 870 | + try: |
| 871 | + minute = int(key) |
| 872 | + pv_forecast10_packed[minute] = float(value) |
| 873 | + except (ValueError, TypeError): |
| 874 | + pass |
| 875 | + |
| 876 | + # Unpack the forecast data |
| 877 | + max_minute = max(pv_forecast_packed.keys()) if pv_forecast_packed else 0 |
| 878 | + last_value = 0 |
| 879 | + last_value10 = 0 |
| 880 | + for minute in range(0, max_minute + 1): |
| 881 | + last_value = pv_forecast_packed.get(minute, last_value) |
| 882 | + last_value10 = pv_forecast10_packed.get(minute, last_value10) |
| 883 | + pv_forecast_minute[minute] = last_value |
| 884 | + pv_forecast_minute10[minute] = last_value10 |
| 885 | + |
| 886 | + return pv_forecast_minute, pv_forecast_minute10 |
| 887 | + |
947 | 888 | def predict_battery_temperature(self, battery_temperature_history, step): |
948 | 889 | """ |
949 | 890 | Given historical battery temperature data, predict the future temperature |
|
0 commit comments