emhass 0.9.1__py3-none-any.whl → 0.10.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- emhass/command_line.py +206 -5
- emhass/forecast.py +13 -7
- emhass/optimization.py +261 -82
- emhass/retrieve_hass.py +55 -7
- emhass/utils.py +59 -102
- emhass/web_server.py +32 -7
- {emhass-0.9.1.dist-info → emhass-0.10.0.dist-info}/METADATA +111 -8
- {emhass-0.9.1.dist-info → emhass-0.10.0.dist-info}/RECORD +12 -12
- {emhass-0.9.1.dist-info → emhass-0.10.0.dist-info}/LICENSE +0 -0
- {emhass-0.9.1.dist-info → emhass-0.10.0.dist-info}/WHEEL +0 -0
- {emhass-0.9.1.dist-info → emhass-0.10.0.dist-info}/entry_points.txt +0 -0
- {emhass-0.9.1.dist-info → emhass-0.10.0.dist-info}/top_level.txt +0 -0
    
        emhass/command_line.py
    CHANGED
    
    | @@ -3,6 +3,7 @@ | |
| 3 3 |  | 
| 4 4 | 
             
            import argparse
         | 
| 5 5 | 
             
            import os
         | 
| 6 | 
            +
            import time
         | 
| 6 7 | 
             
            import pathlib
         | 
| 7 8 | 
             
            import logging
         | 
| 8 9 | 
             
            import json
         | 
| @@ -285,6 +286,18 @@ def perfect_forecast_optim(input_data_dict: dict, logger: logging.Logger, | |
| 285 286 | 
             
                if not debug:
         | 
| 286 287 | 
             
                    opt_res.to_csv(
         | 
| 287 288 | 
             
                        input_data_dict['emhass_conf']['data_path'] / filename, index_label='timestamp')
         | 
| 289 | 
            +
                    
         | 
| 290 | 
            +
             | 
| 291 | 
            +
                if not isinstance(input_data_dict["params"],dict):
         | 
| 292 | 
            +
                    params = json.loads(input_data_dict["params"])
         | 
| 293 | 
            +
                else:
         | 
| 294 | 
            +
                    params = input_data_dict["params"]
         | 
| 295 | 
            +
             | 
| 296 | 
            +
                # if continual_publish, save perfect results to data_path/entities json
         | 
| 297 | 
            +
                if input_data_dict["retrieve_hass_conf"].get("continual_publish",False) or params["passed_data"].get("entity_save",False):
         | 
| 298 | 
            +
                    #Trigger the publish function, save entity data and not post to HA
         | 
| 299 | 
            +
                    publish_data(input_data_dict, logger, entity_save=True, dont_post=True)   
         | 
| 300 | 
            +
             | 
| 288 301 | 
             
                return opt_res
         | 
| 289 302 |  | 
| 290 303 |  | 
| @@ -330,7 +343,19 @@ def dayahead_forecast_optim(input_data_dict: dict, logger: logging.Logger, | |
| 330 343 | 
             
                    filename = "opt_res_latest.csv"
         | 
| 331 344 | 
             
                if not debug:
         | 
| 332 345 | 
             
                    opt_res_dayahead.to_csv(
         | 
| 333 | 
            -
             | 
| 346 | 
            +
                      input_data_dict['emhass_conf']['data_path'] / filename, index_label='timestamp')
         | 
| 347 | 
            +
                
         | 
| 348 | 
            +
                if not isinstance(input_data_dict["params"],dict):
         | 
| 349 | 
            +
                    params = json.loads(input_data_dict["params"])
         | 
| 350 | 
            +
                else:
         | 
| 351 | 
            +
                    params = input_data_dict["params"]
         | 
| 352 | 
            +
             | 
| 353 | 
            +
                
         | 
| 354 | 
            +
                # if continual_publish, save day_ahead results to data_path/entities json
         | 
| 355 | 
            +
                if input_data_dict["retrieve_hass_conf"].get("continual_publish",False) or params["passed_data"].get("entity_save",False):
         | 
| 356 | 
            +
                    #Trigger the publish function, save entity data and not post to HA
         | 
| 357 | 
            +
                    publish_data(input_data_dict, logger, entity_save=True, dont_post=True)   
         | 
| 358 | 
            +
                    
         | 
| 334 359 | 
             
                return opt_res_dayahead
         | 
| 335 360 |  | 
| 336 361 |  | 
| @@ -384,7 +409,18 @@ def naive_mpc_optim(input_data_dict: dict, logger: logging.Logger, | |
| 384 409 | 
             
                    filename = "opt_res_latest.csv"
         | 
| 385 410 | 
             
                if not debug:
         | 
| 386 411 | 
             
                    opt_res_naive_mpc.to_csv(
         | 
| 387 | 
            -
             | 
| 412 | 
            +
                      input_data_dict['emhass_conf']['data_path'] / filename, index_label='timestamp')
         | 
| 413 | 
            +
                    
         | 
| 414 | 
            +
                if not isinstance(input_data_dict["params"],dict):
         | 
| 415 | 
            +
                    params = json.loads(input_data_dict["params"])
         | 
| 416 | 
            +
                else:
         | 
| 417 | 
            +
                    params = input_data_dict["params"]
         | 
| 418 | 
            +
             | 
| 419 | 
            +
                # if continual_publish, save mpc results to data_path/entities json
         | 
| 420 | 
            +
                if input_data_dict["retrieve_hass_conf"].get("continual_publish",False) or params["passed_data"].get("entity_save",False):
         | 
| 421 | 
            +
                    #Trigger the publish function, save entity data and not post to HA
         | 
| 422 | 
            +
                    publish_data(input_data_dict, logger, entity_save=True, dont_post=True)   
         | 
| 423 | 
            +
             | 
| 388 424 | 
             
                return opt_res_naive_mpc
         | 
| 389 425 |  | 
| 390 426 |  | 
| @@ -648,9 +684,12 @@ def regressor_model_predict(input_data_dict: dict, logger: logging.Logger, | |
| 648 684 | 
             
                return prediction
         | 
| 649 685 |  | 
| 650 686 |  | 
| 687 | 
            +
             | 
| 651 688 | 
             
            def publish_data(input_data_dict: dict, logger: logging.Logger, 
         | 
| 652 689 | 
             
                             save_data_to_file: Optional[bool] = False, 
         | 
| 653 | 
            -
                             opt_res_latest: Optional[pd.DataFrame] = None | 
| 690 | 
            +
                             opt_res_latest: Optional[pd.DataFrame] = None, 
         | 
| 691 | 
            +
                             entity_save: Optional[bool] = False,
         | 
| 692 | 
            +
                             dont_post: Optional[bool] = False) -> pd.DataFrame:
         | 
| 654 693 | 
             
                """
         | 
| 655 694 | 
             
                Publish the data obtained from the optimization results.
         | 
| 656 695 |  | 
| @@ -662,15 +701,55 @@ def publish_data(input_data_dict: dict, logger: logging.Logger, | |
| 662 701 | 
             
                :type save_data_to_file: bool, optional
         | 
| 663 702 | 
             
                :return: The output data of the optimization readed from a CSV file in the data folder
         | 
| 664 703 | 
             
                :rtype: pd.DataFrame
         | 
| 704 | 
            +
                :param entity_save: Save built entities to data_path/entities
         | 
| 705 | 
            +
                :type entity_save: bool, optional
         | 
| 706 | 
            +
                :param dont_post: Do not post to Home Assistant. Works with entity_save
         | 
| 707 | 
            +
                :type dont_post: bool, optional
         | 
| 665 708 |  | 
| 666 709 | 
             
                """
         | 
| 667 710 | 
             
                logger.info("Publishing data to HASS instance")
         | 
| 711 | 
            +
             | 
| 712 | 
            +
                if not isinstance(input_data_dict["params"],dict):
         | 
| 713 | 
            +
                    params = json.loads(input_data_dict["params"])
         | 
| 714 | 
            +
                else:
         | 
| 715 | 
            +
                    params = input_data_dict["params"]
         | 
| 716 | 
            +
                
         | 
| 668 717 | 
             
                # Check if a day ahead optimization has been performed (read CSV file)
         | 
| 669 718 | 
             
                if save_data_to_file:
         | 
| 670 719 | 
             
                    today = datetime.now(timezone.utc).replace(
         | 
| 671 720 | 
             
                        hour=0, minute=0, second=0, microsecond=0
         | 
| 672 721 | 
             
                    )
         | 
| 673 722 | 
             
                    filename = "opt_res_dayahead_" + today.strftime("%Y_%m_%d") + ".csv"
         | 
| 723 | 
            +
                # If publish_prefix is passed, check if there is saved entities in data_path/entities with prefix, publish to results
         | 
| 724 | 
            +
                elif params["passed_data"].get("publish_prefix","") != "" and not dont_post:
         | 
| 725 | 
            +
                    opt_res_list = []
         | 
| 726 | 
            +
                    opt_res_list_names = []
         | 
| 727 | 
            +
                    publish_prefix = params["passed_data"]["publish_prefix"]
         | 
| 728 | 
            +
                    entity_path = input_data_dict['emhass_conf']['data_path'] / "entities"
         | 
| 729 | 
            +
                    
         | 
| 730 | 
            +
                    # Check if items in entity_path
         | 
| 731 | 
            +
                    if os.path.exists(entity_path) and len(os.listdir(entity_path)) > 0:
         | 
| 732 | 
            +
                        # Obtain all files in entity_path
         | 
| 733 | 
            +
                        entity_path_contents =  os.listdir(entity_path)    
         | 
| 734 | 
            +
                        for entity in entity_path_contents:
         | 
| 735 | 
            +
                            if entity != "metadata.json": 
         | 
| 736 | 
            +
                                # If publish_prefix is "all" publish all saved entities to Home Assistant 
         | 
| 737 | 
            +
                                # If publish_prefix matches the prefix from saved entities, publish to Home Assistant
         | 
| 738 | 
            +
                                if publish_prefix in entity or publish_prefix == "all":
         | 
| 739 | 
            +
                                    entity_data = publish_json(entity,input_data_dict,entity_path,logger)    
         | 
| 740 | 
            +
                                    if not isinstance(entity_data, bool):
         | 
| 741 | 
            +
                                        opt_res_list.append(entity_data)
         | 
| 742 | 
            +
                                        opt_res_list_names.append(entity.replace(".json", ""))
         | 
| 743 | 
            +
                                    else:
         | 
| 744 | 
            +
                                        return False      
         | 
| 745 | 
            +
                        # Build a DataFrame with published entities
         | 
| 746 | 
            +
                        opt_res = pd.concat(opt_res_list, axis=1)
         | 
| 747 | 
            +
                        opt_res.columns = opt_res_list_names
         | 
| 748 | 
            +
                        return opt_res
         | 
| 749 | 
            +
                    else:
         | 
| 750 | 
            +
                        logger.warning("no saved entity json files in path:" + str(entity_path))     
         | 
| 751 | 
            +
                        logger.warning("falling back to opt_res_latest")
         | 
| 752 | 
            +
                        filename = "opt_res_latest.csv"            
         | 
| 674 753 | 
             
                else:
         | 
| 675 754 | 
             
                    filename = "opt_res_latest.csv"
         | 
| 676 755 | 
             
                if opt_res_latest is None:
         | 
| @@ -698,7 +777,6 @@ def publish_data(input_data_dict: dict, logger: logging.Logger, | |
| 698 777 | 
             
                if idx_closest == -1:
         | 
| 699 778 | 
             
                    idx_closest = opt_res_latest.index.get_indexer([now_precise], method="nearest")[0]
         | 
| 700 779 | 
             
                # Publish the data
         | 
| 701 | 
            -
                params = json.loads(input_data_dict["params"])
         | 
| 702 780 | 
             
                publish_prefix = params["passed_data"]["publish_prefix"]
         | 
| 703 781 | 
             
                # Publish PV forecast
         | 
| 704 782 | 
             
                custom_pv_forecast_id = params["passed_data"]["custom_pv_forecast_id"]
         | 
| @@ -710,6 +788,8 @@ def publish_data(input_data_dict: dict, logger: logging.Logger, | |
| 710 788 | 
             
                    custom_pv_forecast_id["friendly_name"],
         | 
| 711 789 | 
             
                    type_var="power",
         | 
| 712 790 | 
             
                    publish_prefix=publish_prefix,
         | 
| 791 | 
            +
                    save_entities=entity_save,
         | 
| 792 | 
            +
                    dont_post=dont_post
         | 
| 713 793 | 
             
                )
         | 
| 714 794 | 
             
                # Publish Load forecast
         | 
| 715 795 | 
             
                custom_load_forecast_id = params["passed_data"]["custom_load_forecast_id"]
         | 
| @@ -721,6 +801,8 @@ def publish_data(input_data_dict: dict, logger: logging.Logger, | |
| 721 801 | 
             
                    custom_load_forecast_id["friendly_name"],
         | 
| 722 802 | 
             
                    type_var="power",
         | 
| 723 803 | 
             
                    publish_prefix=publish_prefix,
         | 
| 804 | 
            +
                    save_entities=entity_save,
         | 
| 805 | 
            +
                    dont_post=dont_post
         | 
| 724 806 | 
             
                )
         | 
| 725 807 | 
             
                cols_published = ["P_PV", "P_Load"]
         | 
| 726 808 | 
             
                # Publish deferrable loads
         | 
| @@ -742,6 +824,8 @@ def publish_data(input_data_dict: dict, logger: logging.Logger, | |
| 742 824 | 
             
                            custom_deferrable_forecast_id[k]["friendly_name"],
         | 
| 743 825 | 
             
                            type_var="deferrable",
         | 
| 744 826 | 
             
                            publish_prefix=publish_prefix,
         | 
| 827 | 
            +
                            save_entities=entity_save,
         | 
| 828 | 
            +
                            dont_post=dont_post
         | 
| 745 829 | 
             
                        )
         | 
| 746 830 | 
             
                        cols_published = cols_published + ["P_deferrable{}".format(k)]
         | 
| 747 831 | 
             
                # Publish battery power
         | 
| @@ -760,6 +844,8 @@ def publish_data(input_data_dict: dict, logger: logging.Logger, | |
| 760 844 | 
             
                            custom_batt_forecast_id["friendly_name"],
         | 
| 761 845 | 
             
                            type_var="batt",
         | 
| 762 846 | 
             
                            publish_prefix=publish_prefix,
         | 
| 847 | 
            +
                            save_entities=entity_save,
         | 
| 848 | 
            +
                            dont_post=dont_post
         | 
| 763 849 | 
             
                        )
         | 
| 764 850 | 
             
                        cols_published = cols_published + ["P_batt"]
         | 
| 765 851 | 
             
                        custom_batt_soc_forecast_id = params["passed_data"][
         | 
| @@ -773,6 +859,8 @@ def publish_data(input_data_dict: dict, logger: logging.Logger, | |
| 773 859 | 
             
                            custom_batt_soc_forecast_id["friendly_name"],
         | 
| 774 860 | 
             
                            type_var="SOC",
         | 
| 775 861 | 
             
                            publish_prefix=publish_prefix,
         | 
| 862 | 
            +
                            save_entities=entity_save,
         | 
| 863 | 
            +
                            dont_post=dont_post
         | 
| 776 864 | 
             
                        )
         | 
| 777 865 | 
             
                        cols_published = cols_published + ["SOC_opt"]
         | 
| 778 866 | 
             
                # Publish grid power
         | 
| @@ -785,6 +873,8 @@ def publish_data(input_data_dict: dict, logger: logging.Logger, | |
| 785 873 | 
             
                    custom_grid_forecast_id["friendly_name"],
         | 
| 786 874 | 
             
                    type_var="power",
         | 
| 787 875 | 
             
                    publish_prefix=publish_prefix,
         | 
| 876 | 
            +
                    save_entities=entity_save,
         | 
| 877 | 
            +
                    dont_post=dont_post
         | 
| 788 878 | 
             
                )
         | 
| 789 879 | 
             
                cols_published = cols_published + ["P_grid"]
         | 
| 790 880 | 
             
                # Publish total value of cost function
         | 
| @@ -798,7 +888,10 @@ def publish_data(input_data_dict: dict, logger: logging.Logger, | |
| 798 888 | 
             
                    custom_cost_fun_id["friendly_name"],
         | 
| 799 889 | 
             
                    type_var="cost_fun",
         | 
| 800 890 | 
             
                    publish_prefix=publish_prefix,
         | 
| 891 | 
            +
                    save_entities=entity_save,
         | 
| 892 | 
            +
                    dont_post=dont_post
         | 
| 801 893 | 
             
                )
         | 
| 894 | 
            +
                # cols_published = cols_published + col_cost_fun
         | 
| 802 895 | 
             
                # Publish the optimization status
         | 
| 803 896 | 
             
                custom_cost_fun_id = params["passed_data"]["custom_optim_status_id"]
         | 
| 804 897 | 
             
                if "optim_status" not in opt_res_latest:
         | 
| @@ -814,6 +907,8 @@ def publish_data(input_data_dict: dict, logger: logging.Logger, | |
| 814 907 | 
             
                    custom_cost_fun_id["friendly_name"],
         | 
| 815 908 | 
             
                    type_var="optim_status",
         | 
| 816 909 | 
             
                    publish_prefix=publish_prefix,
         | 
| 910 | 
            +
                    save_entities=entity_save,
         | 
| 911 | 
            +
                    dont_post=dont_post
         | 
| 817 912 | 
             
                )
         | 
| 818 913 | 
             
                cols_published = cols_published + ["optim_status"]
         | 
| 819 914 | 
             
                # Publish unit_load_cost
         | 
| @@ -826,6 +921,8 @@ def publish_data(input_data_dict: dict, logger: logging.Logger, | |
| 826 921 | 
             
                    custom_unit_load_cost_id["friendly_name"],
         | 
| 827 922 | 
             
                    type_var="unit_load_cost",
         | 
| 828 923 | 
             
                    publish_prefix=publish_prefix,
         | 
| 924 | 
            +
                    save_entities=entity_save,
         | 
| 925 | 
            +
                    dont_post=dont_post
         | 
| 829 926 | 
             
                )
         | 
| 830 927 | 
             
                cols_published = cols_published + ["unit_load_cost"]
         | 
| 831 928 | 
             
                # Publish unit_prod_price
         | 
| @@ -838,6 +935,8 @@ def publish_data(input_data_dict: dict, logger: logging.Logger, | |
| 838 935 | 
             
                    custom_unit_prod_price_id["friendly_name"],
         | 
| 839 936 | 
             
                    type_var="unit_prod_price",
         | 
| 840 937 | 
             
                    publish_prefix=publish_prefix,
         | 
| 938 | 
            +
                    save_entities=entity_save,
         | 
| 939 | 
            +
                    dont_post=dont_post
         | 
| 841 940 | 
             
                )
         | 
| 842 941 | 
             
                cols_published = cols_published + ["unit_prod_price"]
         | 
| 843 942 | 
             
                # Create a DF resuming what has been published
         | 
| @@ -845,6 +944,108 @@ def publish_data(input_data_dict: dict, logger: logging.Logger, | |
| 845 944 | 
             
                    opt_res_latest.index[idx_closest]]]
         | 
| 846 945 | 
             
                return opt_res
         | 
| 847 946 |  | 
| 947 | 
            +
            def continual_publish(input_data_dict,entity_path,logger):
         | 
| 948 | 
            +
                """
         | 
| 949 | 
            +
                If continual_publish is true and a entity file saved in /data_path/entities, continually publish sensor on freq rate, updating entity current state value based on timestamp
         | 
| 950 | 
            +
             | 
| 951 | 
            +
                :param input_data_dict: A dictionnary with multiple data used by the action functions
         | 
| 952 | 
            +
                :type input_data_dict: dict
         | 
| 953 | 
            +
                :param entity_path: Path for entities folder in data_path
         | 
| 954 | 
            +
                :type entity_path: Path
         | 
| 955 | 
            +
                :param logger: The passed logger object
         | 
| 956 | 
            +
                :type logger: logging.Logger
         | 
| 957 | 
            +
             | 
| 958 | 
            +
                """
         | 
| 959 | 
            +
                logger.info("Continual publish thread service started")
         | 
| 960 | 
            +
                freq = input_data_dict['retrieve_hass_conf'].get("freq", pd.to_timedelta(1, "minutes"))
         | 
| 961 | 
            +
                entity_path_contents = []
         | 
| 962 | 
            +
             | 
| 963 | 
            +
                while True:
         | 
| 964 | 
            +
                    # Sleep for x seconds (using current time as a reference for time left)
         | 
| 965 | 
            +
                    time.sleep(max(0,freq.total_seconds() - (datetime.now(input_data_dict["retrieve_hass_conf"]["time_zone"]).timestamp() % 60)))
         | 
| 966 | 
            +
             | 
| 967 | 
            +
                    # Loop through all saved entity files
         | 
| 968 | 
            +
                    if os.path.exists(entity_path) and len(os.listdir(entity_path)) > 0:
         | 
| 969 | 
            +
                        entity_path_contents =  os.listdir(entity_path)    
         | 
| 970 | 
            +
                        for entity in entity_path_contents:
         | 
| 971 | 
            +
                            if entity != "metadata.json":
         | 
| 972 | 
            +
                                # Call publish_json with entity file, build entity, and publish                     
         | 
| 973 | 
            +
                                publish_json(entity,input_data_dict,entity_path,logger,"continual_publish")
         | 
| 974 | 
            +
                    pass 
         | 
| 975 | 
            +
                # This function should never return           
         | 
| 976 | 
            +
                return False 
         | 
| 977 | 
            +
             | 
| 978 | 
            +
            def publish_json(entity,input_data_dict,entity_path,logger,reference: Optional[str] = ""):
         | 
| 979 | 
            +
                """
         | 
| 980 | 
            +
                Extract saved entity data from .json (in data_path/entities), build entity, post results to post_data
         | 
| 981 | 
            +
             | 
| 982 | 
            +
                :param entity: json file containing entity data
         | 
| 983 | 
            +
                :type entity: dict
         | 
| 984 | 
            +
                :param input_data_dict: A dictionnary with multiple data used by the action functions
         | 
| 985 | 
            +
                :type input_data_dict: dict
         | 
| 986 | 
            +
                :param entity_path: Path for entities folder in data_path
         | 
| 987 | 
            +
                :type entity_path: Path
         | 
| 988 | 
            +
                :param logger: The passed logger object
         | 
| 989 | 
            +
                :type logger: logging.Logger
         | 
| 990 | 
            +
                :param reference: String for identifying who ran the function  
         | 
| 991 | 
            +
                :type reference: str, optional
         | 
| 992 | 
            +
             | 
| 993 | 
            +
                """
         | 
| 994 | 
            +
             | 
| 995 | 
            +
                # Retrieve entity metadata from file
         | 
| 996 | 
            +
                if os.path.isfile(entity_path / "metadata.json"):
         | 
| 997 | 
            +
                    with open(entity_path / "metadata.json", "r") as file:
         | 
| 998 | 
            +
                        metadata = json.load(file) 
         | 
| 999 | 
            +
                        if not metadata.get("lowest_freq",None) == None:
         | 
| 1000 | 
            +
                            freq = pd.to_timedelta(metadata["lowest_freq"], "minutes")
         | 
| 1001 | 
            +
                else:
         | 
| 1002 | 
            +
                    logger.error("unable to located metadata.json in:" + entity_path)
         | 
| 1003 | 
            +
                    return False            
         | 
| 1004 | 
            +
             | 
| 1005 | 
            +
                # Round current timecode (now)
         | 
| 1006 | 
            +
                now_precise = datetime.now(input_data_dict["retrieve_hass_conf"]["time_zone"]).replace(second=0, microsecond=0)
         | 
| 1007 | 
            +
             | 
| 1008 | 
            +
                # Retrieve entity data from file
         | 
| 1009 | 
            +
                entity_data = pd.read_json(entity_path / entity , orient='index')
         | 
| 1010 | 
            +
                
         | 
| 1011 | 
            +
                # Remove ".json" from string for entity_id
         | 
| 1012 | 
            +
                entity_id = entity.replace(".json", "")
         | 
| 1013 | 
            +
                
         | 
| 1014 | 
            +
                # Adjust Dataframe from received entity json file
         | 
| 1015 | 
            +
                entity_data.columns = [metadata[entity_id]["name"]]
         | 
| 1016 | 
            +
                entity_data.index.name = "timestamp"
         | 
| 1017 | 
            +
                entity_data.index = pd.to_datetime(entity_data.index).tz_convert(input_data_dict["retrieve_hass_conf"]["time_zone"])
         | 
| 1018 | 
            +
                entity_data.index.freq = pd.to_timedelta(int(metadata[entity_id]["freq"]), "minutes")    
         | 
| 1019 | 
            +
                # Calculate the current state value
         | 
| 1020 | 
            +
                if input_data_dict["retrieve_hass_conf"]["method_ts_round"] == "nearest":
         | 
| 1021 | 
            +
                    idx_closest = entity_data.index.get_indexer([now_precise], method="nearest")[0]
         | 
| 1022 | 
            +
                elif input_data_dict["retrieve_hass_conf"]["method_ts_round"] == "first":
         | 
| 1023 | 
            +
                    idx_closest = entity_data.index.get_indexer([now_precise], method="ffill")[0]
         | 
| 1024 | 
            +
                elif input_data_dict["retrieve_hass_conf"]["method_ts_round"] == "last":
         | 
| 1025 | 
            +
                    idx_closest = entity_data.index.get_indexer([now_precise], method="bfill")[0]
         | 
| 1026 | 
            +
                if idx_closest == -1:
         | 
| 1027 | 
            +
                    idx_closest = entity_data.index.get_indexer([now_precise], method="nearest")[0]
         | 
| 1028 | 
            +
                
         | 
| 1029 | 
            +
                # Call post data 
         | 
| 1030 | 
            +
                if reference == "continual_publish":
         | 
| 1031 | 
            +
                    logger.debug("Auto Published sensor:")
         | 
| 1032 | 
            +
                    logger_levels = "DEBUG"
         | 
| 1033 | 
            +
                else: 
         | 
| 1034 | 
            +
                    logger_levels = "INFO"
         | 
| 1035 | 
            +
                
         | 
| 1036 | 
            +
                #post/save entity
         | 
| 1037 | 
            +
                input_data_dict["rh"].post_data(
         | 
| 1038 | 
            +
                    data_df=entity_data[metadata[entity_id]["name"]],
         | 
| 1039 | 
            +
                    idx=idx_closest,
         | 
| 1040 | 
            +
                    entity_id=entity_id,
         | 
| 1041 | 
            +
                    unit_of_measurement=metadata[entity_id]["unit_of_measurement"],
         | 
| 1042 | 
            +
                    friendly_name=metadata[entity_id]["friendly_name"],
         | 
| 1043 | 
            +
                    type_var=metadata[entity_id].get("type_var",""),
         | 
| 1044 | 
            +
                    save_entities=False,
         | 
| 1045 | 
            +
                    logger_levels=logger_levels
         | 
| 1046 | 
            +
                )
         | 
| 1047 | 
            +
                return entity_data[metadata[entity_id]["name"]]
         | 
| 1048 | 
            +
             | 
| 848 1049 |  | 
| 849 1050 | 
             
            def main():
         | 
| 850 1051 | 
             
                r"""Define the main command line entry function.
         | 
| @@ -979,7 +1180,7 @@ def main(): | |
| 979 1180 | 
             
                    prediction = regressor_model_predict(input_data_dict, logger, debug=args.debug,mlr=mlr)
         | 
| 980 1181 | 
             
                    opt_res = None
         | 
| 981 1182 | 
             
                elif args.action == "publish-data":
         | 
| 982 | 
            -
                    opt_res = publish_data(input_data_dict, | 
| 1183 | 
            +
                    opt_res = publish_data(input_data_dict,logger)
         | 
| 983 1184 | 
             
                else:
         | 
| 984 1185 | 
             
                    logger.error("The passed action argument is not valid")
         | 
| 985 1186 | 
             
                    logger.error("Try setting --action: perfect-optim, dayahead-optim, naive-mpc-optim, forecast-model-fit, forecast-model-predict, forecast-model-tune or publish-data")
         | 
    
        emhass/forecast.py
    CHANGED
    
    | @@ -186,7 +186,7 @@ class Forecast(object): | |
| 186 186 | 
             
                    self.logger.info("Retrieving weather forecast data using method = "+method)
         | 
| 187 187 | 
             
                    self.weather_forecast_method = method # Saving this attribute for later use to identify csv method usage
         | 
| 188 188 | 
             
                    if method == 'scrapper':
         | 
| 189 | 
            -
                        freq_scrap = pd.to_timedelta(60, "minutes") # The scrapping time step is 60min
         | 
| 189 | 
            +
                        freq_scrap = pd.to_timedelta(60, "minutes") # The scrapping time step is 60min on clearoutside
         | 
| 190 190 | 
             
                        forecast_dates_scrap = pd.date_range(start=self.start_forecast,
         | 
| 191 191 | 
             
                                                             end=self.end_forecast-freq_scrap, 
         | 
| 192 192 | 
             
                                                             freq=freq_scrap).round(freq_scrap, ambiguous='infer', nonexistent='shift_forward')
         | 
| @@ -204,7 +204,7 @@ class Forecast(object): | |
| 204 204 | 
             
                        col_names = [list_names[i].get_text() for i in selected_cols]
         | 
| 205 205 | 
             
                        list_tables = [list_tables[i] for i in selected_cols]
         | 
| 206 206 | 
             
                        # Building the raw DF container
         | 
| 207 | 
            -
                        raw_data = pd.DataFrame(index=range( | 
| 207 | 
            +
                        raw_data = pd.DataFrame(index=range(len(forecast_dates_scrap)), columns=col_names, dtype=float)
         | 
| 208 208 | 
             
                        for count_col, col in enumerate(col_names):
         | 
| 209 209 | 
             
                            list_rows = list_tables[count_col].find_all('li')
         | 
| 210 210 | 
             
                            for count_row, row in enumerate(list_rows):
         | 
| @@ -235,7 +235,8 @@ class Forecast(object): | |
| 235 235 | 
             
                            "Authorization": "Bearer " + self.retrieve_hass_conf['solcast_api_key'],
         | 
| 236 236 | 
             
                            "content-type": "application/json",
         | 
| 237 237 | 
             
                            }
         | 
| 238 | 
            -
                         | 
| 238 | 
            +
                        days_solcast = int(len(self.forecast_dates)*self.freq.seconds/3600)
         | 
| 239 | 
            +
                        url = "https://api.solcast.com.au/rooftop_sites/"+self.retrieve_hass_conf['solcast_rooftop_id']+"/forecasts?hours="+str(days_solcast)
         | 
| 239 240 | 
             
                        response = get(url, headers=headers)
         | 
| 240 241 | 
             
                        '''import bz2 # Uncomment to save a serialized data for tests
         | 
| 241 242 | 
             
                        import _pickle as cPickle
         | 
| @@ -263,7 +264,11 @@ class Forecast(object): | |
| 263 264 | 
             
                            self.retrieve_hass_conf['solar_forecast_kwp'] = 5
         | 
| 264 265 | 
             
                        if  self.retrieve_hass_conf['solar_forecast_kwp'] == 0:
         | 
| 265 266 | 
             
                            self.logger.warning("The solar_forecast_kwp parameter is set to zero, setting to default 5")
         | 
| 266 | 
            -
                            self.retrieve_hass_conf['solar_forecast_kwp'] = 5 | 
| 267 | 
            +
                            self.retrieve_hass_conf['solar_forecast_kwp'] = 5
         | 
| 268 | 
            +
                        if self.optim_conf['delta_forecast'].days > 1:
         | 
| 269 | 
            +
                            self.logger.warning("The free public tier for solar.forecast only provides one day forecasts")
         | 
| 270 | 
            +
                            self.logger.warning("Continuing with just the first day of data, the other days are filled with 0.0.")
         | 
| 271 | 
            +
                            self.logger.warning("Use the other available methods for delta_forecast > 1")
         | 
| 267 272 | 
             
                        headers = {
         | 
| 268 273 | 
             
                            "Accept": "application/json"
         | 
| 269 274 | 
             
                            }
         | 
| @@ -289,7 +294,8 @@ class Forecast(object): | |
| 289 294 | 
             
                            mask_down_data_df = data_tmp.copy(deep=True).fillna(method = "bfill").isnull()
         | 
| 290 295 | 
             
                            data_tmp.loc[data_tmp.index[mask_up_data_df['yhat']==True],:] = 0.0
         | 
| 291 296 | 
             
                            data_tmp.loc[data_tmp.index[mask_down_data_df['yhat']==True],:] = 0.0
         | 
| 292 | 
            -
                            data_tmp.interpolate(inplace=True)
         | 
| 297 | 
            +
                            data_tmp.interpolate(inplace=True, limit=1)
         | 
| 298 | 
            +
                            data_tmp = data_tmp.fillna(0.0)
         | 
| 293 299 | 
             
                            if len(data) == 0:
         | 
| 294 300 | 
             
                                data = copy.deepcopy(data_tmp)
         | 
| 295 301 | 
             
                            else:
         | 
| @@ -417,9 +423,9 @@ class Forecast(object): | |
| 417 423 | 
             
                            # Setting the main parameters of the PV plant
         | 
| 418 424 | 
             
                            location = Location(latitude=self.lat, longitude=self.lon)
         | 
| 419 425 | 
             
                            temp_params = TEMPERATURE_MODEL_PARAMETERS['sapm']['close_mount_glass_glass']
         | 
| 420 | 
            -
                            cec_modules = | 
| 426 | 
            +
                            cec_modules = bz2.BZ2File(self.emhass_conf['root_path'] / 'data/cec_modules.pbz2', "rb")
         | 
| 421 427 | 
             
                            cec_modules = cPickle.load(cec_modules)
         | 
| 422 | 
            -
                            cec_inverters = bz2.BZ2File( | 
| 428 | 
            +
                            cec_inverters = bz2.BZ2File(self.emhass_conf['root_path'] / 'data/cec_inverters.pbz2', "rb")
         | 
| 423 429 | 
             
                            cec_inverters = cPickle.load(cec_inverters)
         | 
| 424 430 | 
             
                            if type(self.plant_conf['module_model']) == list:
         | 
| 425 431 | 
             
                                P_PV_forecast = pd.Series(0, index=df_weather.index)
         |