EMHASS: An Energy Management for Home Assistant

@davidusb Hi,

Tried using solar.forecast instead of scrapper but I get this error.

KeyError: 'solar_forecast_kwp'
s6-supervise emhass: warning: unable to spawn ./finish: Permission denied
Traceback (most recent call last):
  File "<frozen runpy>", line 198, in _run_module_as_main
  File "<frozen runpy>", line 88, in _run_code
  File "/usr/local/lib/python3.11/dist-packages/emhass/web_server.py", line 222, in <module>
    params = build_params(params, params_secrets, options, args.addon, app.logger)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/dist-packages/emhass/utils.py", line 612, in build_params
    params['retrieve_hass_conf']['solar_forecast_kwp'] = options.get('optional_solar_forecast_kwp',params['retrieve_hass_conf']['solar_forecast_kwp'])

Does this mean you always need to start the optimization of a deferrable load with 0 W?
If yes, does this meant it will work only with the day-ahead optim, as you are not shifting the time you are running the elaboration, which with MPC would sooner or later correspond to the moment you want to start the load, hence power not being zero anymore and potentially causing the load to be split?

All the sensors contains data. See also this screenshot:

costfun: profit
logging_level: DEBUG
set_total_pv_sell: false
set_nocharge_from_grid: false
set_nodischarge_to_grid: false
sensor_power_photovoltaics: sensor.solaredge_ac_power
sensor_power_load_no_var_loads: sensor.house_usage_in_watt
number_of_deferrable_loads: 2
list_nominal_power_of_deferrable_loads:
  - nominal_power_of_deferrable_loads: 800
  - nominal_power_of_deferrable_loads: 10800
list_operating_hours_of_each_deferrable_load:
  - operating_hours_of_each_deferrable_load: 2
  - operating_hours_of_each_deferrable_load: 4
list_start_timesteps_of_each_deferrable_load:
  - start_timesteps_of_each_deferrable_load: 0
  - start_timesteps_of_each_deferrable_load: 0
list_end_timesteps_of_each_deferrable_load:
  - end_timesteps_of_each_deferrable_load: 0
  - end_timesteps_of_each_deferrable_load: 0
list_peak_hours_periods_start_hours:
  - peak_hours_periods_start_hours: "02:54"
  - peak_hours_periods_start_hours: "17:24"
list_peak_hours_periods_end_hours:
  - peak_hours_periods_end_hours: "15:24"
  - peak_hours_periods_end_hours: "20:24"
list_treat_deferrable_load_as_semi_cont:
  - treat_deferrable_load_as_semi_cont: false
  - treat_deferrable_load_as_semi_cont: false
list_set_deferrable_load_single_constant:
  - set_deferrable_load_single_constant: false
  - set_deferrable_load_single_constant: false
load_peak_hours_cost: 0.1907
load_offpeak_hours_cost: 0.1419
photovoltaic_production_sell_price: 0.065
maximum_power_from_grid: 17250
list_pv_module_model:
  - pv_module_model: Jinko_Solar_Co___Ltd_JKM410M_72HL
list_pv_inverter_model:
  - pv_inverter_model: SolarEdge_Technologies_Ltd___SE9K__208V_
list_surface_tilt:
  - surface_tilt: 45
list_surface_azimuth:
  - surface_azimuth: 210
list_modules_per_string:
  - modules_per_string: 26
list_strings_per_inverter:
  - strings_per_inverter: 1
set_use_battery: true
battery_nominal_energy_capacity: 14336
hass_url: empty
long_lived_token: empty
optimization_time_step: 60
historic_days_to_retrieve: 2
method_ts_round: first
lp_solver: COIN_CMD
lp_solver_path: /usr/bin/cbc
set_battery_dynamic: false
battery_dynamic_max: 0.9
battery_dynamic_min: -0.9
load_forecast_method: naive
battery_discharge_power_max: 3400
battery_charge_power_max: 3400
battery_discharge_efficiency: 0.94
battery_charge_efficiency: 0.94
battery_minimum_state_of_charge: 0.15
battery_maximum_state_of_charge: 0.9
battery_target_state_of_charge: 0.15

No, it means that we need to find a way to count that deferrable starting at a value different than zero as a startup occurrence. But of course the first value can be either zero or non zero, so it should be compatible for all: day-ahead, MPC, etc.

What is your load forecast method?

    {
      "prod_price_forecast": {{
      ((state_attr('sensor.nordpool_purchase_price', 'raw_today') | map(attribute='value') | list  + state_attr('sensor.nordpool_purchase_price', 'raw_tomorrow') | map(attribute='value') | list))[now().hour:][:24]
        | tojson
      }},
      "load_cost_forecast": {{
      ((state_attr('sensor.nordpool_purchase_price', 'raw_today') | map(attribute='value') | list + state_attr('sensor.nordpool_purchase_price', 'raw_tomorrow') | map(attribute='value') | list))[now().hour:][:24]
        | tojson
      }},
      "pv_power_forecast": {{
        ([states('sensor.solaredge_ac_power')|int(0)] +
        state_attr('sensor.solcast_pv_forecast_forecast_today', 'detailedHourly')|selectattr('period_start','gt',utcnow()) | map(attribute='pv_estimate')|map('multiply',1000)|map('int')|list +
        state_attr('sensor.solcast_pv_forecast_forecast_tomorrow', 'detailedHourly')|selectattr('period_start','gt',utcnow()) | map(attribute='pv_estimate')|map('multiply',1000)|map('int')|list
        )| tojson
      }},
      "prediction_horizon": {{
          min(24, (((state_attr('sensor.nordpool_purchase_price', 'raw_today')|map(attribute='value')|list + state_attr('sensor.nordpool_purchase_price', 'raw_tomorrow') | map(attribute='value')| list)[now().hour:][:24]|list|length)))
        }},
      "alpha": 1,
      "beta": 0,        
      "num_def_loads": 2,
      "def_total_hours": [{{states('input_number.def_total_hours_ww')}},{{states('input_number.def_total_hours_ev')}}],
      "treat_def_as_semi_cont": [0,0],
      "set_def_constant": [0,0],
      "soc_init": {{ (states('sensor.sofarcontroller_battery_soc')|int(0))/100 }}      
    }
{
  "prod_price_forecast": [
    0.259,
    0.255,
    0.29,
    0.29,
    0.272,
    0.263,
    0.254,
    0.253,
    0.242,
    0.231,
    0.226,
    0.224,
    0.22,
    0.218,
    0.22,
    0.234,
    0.248,
    0.262,
    0.258,
    0.251,
    0.244,
    0.232,
    0.229,
    0.231
  ],
  "load_cost_forecast": [
    0.259,
    0.255,
    0.29,
    0.29,
    0.272,
    0.263,
    0.254,
    0.253,
    0.242,
    0.231,
    0.226,
    0.224,
    0.22,
    0.218,
    0.22,
    0.234,
    0.248,
    0.262,
    0.258,
    0.251,
    0.244,
    0.232,
    0.229,
    0.231
  ],
  "pv_power_forecast": [
    507,
    2102,
    2,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    26,
    218,
    548,
    744,
    773,
    707,
    560,
    307,
    105,
    2,
    0,
    0,
    0,
    0,
    0,
    0
  ],
  "prediction_horizon": 24,
  "alpha": 1,
  "beta": 0,
  "num_def_loads": 2,
  "def_total_hours": [
    0,
    0
  ],
  "treat_def_as_semi_cont": [
    0,
    0
  ],
  "set_def_constant": [
    0,
    0
  ],
  "soc_init": 0.68
}

But after the prices for tomorrow (available today since 13.30) itā€™s working again. Never had this issue before.

That didnā€™t answered the question about the load forecast method. But if it is working again itā€™s ok. So itā€™s probably related to the availability of those data that you are passing as lists.

Definetly a bug to be fixed!
There were a lot of changes latetly on how we deal with passed configuration parameters and default options. This by-passed by the current tests.

sorry, using the naive method

There is currently an issue with both solcast and solar.forcast parameters being passed in via the add-on config options.
Hopefully both of the current pull requests will fix this issue. Sorry about that, I should be more clear here when I find an issue.

(#169 is a predecessor of #167)

I have a problem:

the command:

curl -i -H "Content-Type: application/json" -X POST -d '{"set_def_constant":[true],"pv_power_forecast":[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],"def_total_hours":[4],"prediction_horizon":10,"load_cost_forecast":[16.895712000000003, 16.65222, 15.634992, 14.522592, 13.86504, 13.5684, 13.159284, 13.22232, 13.748856, 16.443336000000002],"prod_price_forecast":[9.24, 9.23, 9.22, 9.21, 9.2, 9.21, 9.22, 9.23, 9.24, 9.25]}' http://localhost:5000/action/naive-mpc-optim

the error:

2024-02-01 21:34:27,356 - web_server - INFO - Launching the emhass webserver at: http://0.0.0.0:5000
2024-02-01 21:34:27,357 - web_server - INFO - Home Assistant data fetch will be performed using url: http://supervisor/core/api
2024-02-01 21:34:27,357 - web_server - INFO - The data path is: /share
2024-02-01 21:34:27,361 - web_server - INFO - Using core emhass version: 0.7.2
waitress   INFO  Serving on http://0.0.0.0:5000
2024-02-01 21:34:33,361 - web_server - INFO - Setting up needed data
2024-02-01 21:34:33,447 - web_server - INFO - Retrieve hass get data method initiated...
2024-02-01 21:34:33,989 - web_server - INFO - Retrieving weather forecast data using method = list
2024-02-01 21:34:33,992 - web_server - INFO - Retrieving data from hass for load forecast using method = naive
2024-02-01 21:34:33,993 - web_server - INFO - Retrieve hass get data method initiated...
2024-02-01 21:34:34,205 - web_server - ERROR - The inferred freq from data is not equal to the defined freq in passed parameters
2024-02-01 21:34:34,221 - web_server - ERROR - Exception on /action/naive-mpc-optim [POST]
Traceback (most recent call last):
  File "/usr/local/lib/python3.11/dist-packages/flask/app.py", line 1463, in wsgi_app
    response = self.full_dispatch_request()
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/dist-packages/flask/app.py", line 872, in full_dispatch_request
    rv = self.handle_user_exception(e)
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/dist-packages/flask/app.py", line 870, in full_dispatch_request
    rv = self.dispatch_request()
         ^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/dist-packages/flask/app.py", line 855, in dispatch_request
    return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args)  # type: ignore[no-any-return]
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/dist-packages/emhass/web_server.py", line 50, in action_call
    input_data_dict = set_input_data_dict(config_path, str(data_path), costfun,
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/dist-packages/emhass/command_line.py", line 120, in set_input_data_dict
    P_load_forecast = fcst.get_load_forecast(method=optim_conf['load_forecast_method'], set_mix_forecast=True, df_now=df_input_data)
                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/dist-packages/emhass/forecast.py", line 598, in get_load_forecast
    forecast_out.index = self.forecast_dates
    ^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/dist-packages/pandas/core/generic.py", line 6002, in __setattr__
    return object.__setattr__(self, name, value)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "pandas/_libs/properties.pyx", line 69, in pandas._libs.properties.AxisProperty.__set__
  File "/usr/local/lib/python3.11/dist-packages/pandas/core/generic.py", line 730, in _set_axis
    self._mgr.set_axis(axis, labels)
  File "/usr/local/lib/python3.11/dist-packages/pandas/core/internals/managers.py", line 225, in set_axis
    self._validate_set_axis(axis, new_labels)
  File "/usr/local/lib/python3.11/dist-packages/pandas/core/internals/base.py", line 70, in _validate_set_axis
    raise ValueError(
ValueError: Length mismatch: Expected axis has 1 elements, new values have 10 elements

here my config:

costfun: profit
logging_level: INFO
set_total_pv_sell: false
set_nocharge_from_grid: false
set_nodischarge_to_grid: false
sensor_power_photovoltaics: sensor.reading_power_inverter_ac_output
sensor_power_load_no_var_loads: sensor.derived_power_consumption_total_no_var_loads
number_of_deferrable_loads: 1
list_nominal_power_of_deferrable_loads:
  - nominal_power_of_deferrable_loads: 4000
list_operating_hours_of_each_deferrable_load:
  - operating_hours_of_each_deferrable_load: 4
list_start_timesteps_of_each_deferrable_load:
  - start_timesteps_of_each_deferrable_load: 0
list_end_timesteps_of_each_deferrable_load:
  - end_timesteps_of_each_deferrable_load: 0
list_peak_hours_periods_start_hours:
  - peak_hours_periods_start_hours: "00:00"
list_peak_hours_periods_end_hours:
  - peak_hours_periods_end_hours: "06:00"
list_treat_deferrable_load_as_semi_cont:
  - treat_deferrable_load_as_semi_cont: true
list_set_deferrable_load_single_constant:
  - set_deferrable_load_single_constant: false
load_peak_hours_cost: 19.07
load_offpeak_hours_cost: 14.19
photovoltaic_production_sell_price: 12.464
maximum_power_from_grid: 20000
list_pv_module_model:
  - pv_module_model: Canadian_Solar_Inc__CS3U_375P
list_pv_inverter_model:
  - pv_inverter_model: Fronius_International_GmbH__Fronius_Symo_20_0_3_480__480V_
list_surface_tilt:
  - surface_tilt: 38
list_surface_azimuth:
  - surface_azimuth: 205
list_modules_per_string:
  - modules_per_string: 66
list_strings_per_inverter:
  - strings_per_inverter: 1
set_use_battery: false
battery_nominal_energy_capacity: 5000
hass_url: empty
long_lived_token: empty
optimization_time_step: 60
historic_days_to_retrieve: 7
method_ts_round: first
lp_solver: COIN_CMD
lp_solver_path: /usr/bin/cbc
set_battery_dynamic: false
battery_dynamic_max: 0.9
battery_dynamic_min: -0.9
weight_battery_discharge: 1
weight_battery_charge: 1
load_forecast_method: naive
battery_discharge_power_max: 1000
battery_charge_power_max: 1000
battery_discharge_efficiency: 0.95
battery_charge_efficiency: 0.95
battery_minimum_state_of_charge: 0.3
battery_maximum_state_of_charge: 0.9
battery_target_state_of_charge: 0.6

any Idea why this happens?

some background: I had an error today (1 hour ago) but I still was on version 0.5.4 (? - the latest before 0.6). sadly I did not capture it, I thought, itā€™s better to just upgrade to 0.6.1, as this might solve the issue. but it didnā€™t. Iā€™m not sure if the error is the same now, at least it doesnā€™t work eitherā€¦

Whilst trying to diagnose an issue Home Assistant was restored from a backup from a week agoā€¦

Couldnā€™t get EMHASS to reload, and of course finally came to the realisation that the backup doesnā€™t contain the minimum of days days data.

The minimum 2 days is a blocker to new installs, but also who system restores, Iā€™m wondering if there is another way to satisfy EMHASS during these initial setups.

The only reason to keep this 2 day data history need is to produce the needed forecasts: PV production, load consumption, PV sell price and load consumption cost.
If you are able to pass all these 4 forecasts as lists then this 2-day data restriction can be lifted. But I donā€™t think anyone is passing all these 4 forecasts. The load consumption forecast is the hardest, most people let EMHASS handle at least that one.
What method could be used to bypass this restriction?
Maybe a simple method like passing a daily mean value just for the temporary initial period after a fresh install or a backup as it was your case.
Open to any ideas

I guess it is a similar reason my caseā€¦

But in my case it was my own faultā€¦ :see_no_evil:

I changed the load sensor of my heatpump, which is essential for the no_var_loads-sensor:

so the only workaround is to pass a list until there is enough history, correct?

Maybe somehow just use the oldest available data to substitute for any older data which is required but unavailable?

I think a default mean value which can be specified in the configuration could make sense. in case of other data is unavailable.

Never say never, I have another EMHASS instance that I use the load power forecast to inject my air conditioner power consumption as a function of the external temp.(summer time here). Iā€™ll just inject that into the other system and will be back up and runningā€¦

rest_command:
  naive_mpc_optim:
    url: http://localhost:5000/action/naive-mpc-optim
    method: POST
    content_type: 'application/json'
    payload: >-
      {
        "prod_price_forecast": {{
          ([states('sensor.amber_feed_in_price')|float(0)] +
          (state_attr('sensor.amber_feed_in_forecast', 'forecasts')|map(attribute='per_kwh')|list))
          | tojson 
        }},
        "load_cost_forecast": {{
          ([states('sensor.amber_general_price')|float(0)] + 
          state_attr('sensor.amber_general_forecast', 'forecasts') |map(attribute='per_kwh')|list) 
          | tojson 
        }},
        "load_power_forecast": {{
          ([states('sensor.power_load_no_var_loads')|int] +
          states('sensor.hvac_power_forecast') | from_json | map('multiply', 1000) | list 
          )| tojson 
        }}, 
        "pv_power_forecast": {{
          ([states('sensor.solaredge_nopowerlimit')|int(0)] +
          state_attr('sensor.solcast_pv_forecast_forecast_today', 'detailedForecast')|selectattr('period_start','gt',utcnow()) | map(attribute='pv_estimate')|map('multiply',1000)|map('int')|list +
          state_attr('sensor.solcast_pv_forecast_forecast_tomorrow', 'detailedForecast')|selectattr('period_start','gt',utcnow()) | map(attribute='pv_estimate')|map('multiply',1000)|map('int')|list
          )| tojson
        }},
        "prediction_horizon": {{
          min(48, (state_attr('sensor.amber_feed_in_forecast', 'forecasts')|map(attribute='per_kwh')|list|length)+1)
        }},
        "alpha": 1,
        "beta": 0,
        "num_def_loads": 6,
        "def_total_hours": {{[states('sensor.def_total_hours_pool_filter')|int(0),
                              states('sensor.def_total_hours_pool_heatpump')|int(0),
                              states('sensor.def_total_hours_ev')|int(0),
                              states('sensor.def_total_hours_hvac')|int(0),
                              states('sensor.def_total_hours_hws')|int(0),
                              states('sensor.def_total_hours_ev2')|int(0)]}},
        "P_deferrable_nom":  [945,
                              6000, 
                              {{ states('sensor.p_nom_ev')}}, 
                              {{ states('input_number.p_nom_hvac')}}, 
                              600, 
                              {{ states('sensor.p_nom_ev2')}}],
        "treat_def_as_semi_cont": [1, 1, 0, 0, 1, 0],
        "set_def_constant": [0, 1, 0, 0, 0, 0],
        "soc_init": {{ (states('sensor.gateway_battery')|int(0))/100 }},
        "soc_final": {{ max(20,states('sensor.gateway_battery')|int(0))/100 }}
      }
rest_command:
  naive_mpc_optim:
    url: http://localhost:5000/action/naive-mpc-optim
    method: POST
    content_type: 'application/json'
    payload: >-
      {
        "prod_price_forecast": [0.98, 0.41, 0.39, 0.39, 0.39, 0.27, 0.26, 0.3, 0.27, 0.15, 0.09, 0.15, 0.09, 0.09, 0.09, 0.09, 0.07, 0.07, 0.07, 0.08, 0.09, 0.1, 0.09, 0.07, 0.04, 0.02, 0.02, 0.02, 0.0, 0.02, -0.02, -0.02, -0.02, -0.03, -0.03, -0.02, -0.02, -0.01, 0.02, 0.04, 0.04, 0.04, 0.04, 0.04, 0.04, 0.07, 0.1, 0.38, 0.4],
        "load_cost_forecast": [1.3, 0.67, 0.64, 0.64, 0.64, 0.38, 0.37, 0.41, 0.38, 0.24, 0.18, 0.24, 0.18, 0.18, 0.18, 0.18, 0.16, 0.16, 0.15, 0.17, 0.18, 0.19, 0.18, 0.15, 0.12, 0.1, 0.1, 0.1, 0.08, 0.1, 0.05, 0.05, 0.05, 0.04, 0.04, 0.05, 0.05, 0.06, 0.1, 0.11, 0.11, 0.11, 0.12, 0.26, 0.26, 0.29, 0.33, 0.63, 0.66],
        "load_power_forecast": [2807, 1500.0, 1000.0, 1000.0, 1000.0, 1000.0, 1000.0, 1000.0, 1000.0, 1000.0, 1000.0, 1000.0, 1000.0, 1000.0, 1000.0, 1000.0, 1000.0, 1000.0, 1000.0, 1000.0, 1000.0, 1000.0, 1000.0, 1000.0, 1000.0, 1000.0, 1000.0, 1000.0, 3000.0, 3000.0, 4500.0, 4500.0, 6000.0, 6000.0, 7500.0, 7500.0, 7500.0, 7500.0, 7500.0, 7500.0, 6000.0, 6000.0, 6000.0, 6000.0, 4500.0, 4500.0, 4500.0, 4500.0, 1500.0], 
        "pv_power_forecast": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 309, 1208, 2715, 4413, 6219, 7914, 9403, 10719, 11896, 12549, 13124, 13664, 13849, 13738, 13456, 13112, 12831, 12543, 11513, 10360, 9309, 7769, 5973, 4218, 2336, 555, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        "prediction_horizon": 48,
        "alpha": 1,
        "beta": 0,
        "num_def_loads": 6,
        "def_total_hours": [7, 4, 0, 0, 6, 6],
        "P_deferrable_nom":  [945,
                              6000, 
                              7680, 
                              0.0, 
                              600, 
                              7680],
        "treat_def_as_semi_cont": [1, 1, 0, 0, 1, 0],
        "set_def_constant": [0, 1, 0, 0, 0, 0],
        "soc_init": 0.55,
        "soc_final": 0.55
      }

Update: yes that has worked and I have the instance up and running with wonkey load forecast, but the rest is functionalā€¦

2 Likes

So if I use 4 weeks of historical data is next day forecast built using 4 days, each from one week?
How does that work high level?

Iā€™ll try to picture it using these images.

So imagine that our time series is the load power to be predicted and the exogenous variables are just labels that indicate: year, month of the year, day_of_week, day_of_year, day, hour

image

So we now train a machine learning model to fit well with unseen data.
Like this:

image

The final step is to actually use that trained model to produce predictions for future time steps.
Like this:

So to answer your question, all that information about day_of_week, day_of_year, etc, is actually being used to train the model. And if you pass 4 weeks of data, all those 4 week are being used to train the model.

I hope it helps to understand.

Source: https://joaquinamatrodrigo.github.io/skforecast/0.4.3/quick-start/introduction-forecasting.html

4 Likes

Hey @davidusb, since this project is getting bigger by the day, I was wondering if you have considered setting up a project on GitHub : Planning and tracking with Projects - GitHub Docs
I havenā€™t looked into it too much myself yet. But it would be cool to have a GitHub kanban board of all the ideas/plans for improvements to EMHASS itā€™s progression (and have issues/feature/pull requests linked).
I believe it (or similar) would help people to understand whatā€™s the future steps of EMHASS, and whatā€™s been already planned. (Inc. In what prefered order)
I know personally I have some feature requests Iā€™ll like to give. Currently the only way to voice them is via an issue or here. (That I know of)
I think having a feature / enhancement request template on GitHub would also be appreciated personally. Just some ideas :heart:

3 Likes