Repository: davidusb-geek/emhass (language: python)

Issue:
Only automatically set the DateTimeIndex frequency if no "freq" is set in retrieve_hass_conf
... otherwise, explicitly set the frequency to match the value of `freq` in `retrieve_hass_conf`.

The DateTimeIndex frequency needs to match the `freq` in `retrieve_hass_conf`, because lots of input data is being provided in "timesteps", and indexed within that input data by index.

Without this change, you get the following error message:

ERROR - web_server - Exception on /action/naive-mpc-optim [POST] Traceback (most recent call last):
  File "/usr/local/lib/python3.11/dist-packages/flask/app.py", line 1473, in wsgi_app
    response = self.full_dispatch_request()
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/dist-packages/flask/app.py", line 882, in full_dispatch_request
    rv = self.handle_user_exception(e)
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/dist-packages/flask/app.py", line 880, in full_dispatch_request
    rv = self.dispatch_request()
         ^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/dist-packages/flask/app.py", line 865, 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 122, in action_call
    input_data_dict = set_input_data_dict(emhass_conf, costfun,
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/dist-packages/emhass/command_line.py", line 164, in set_input_data_dict
    df_input_data_dayahead.index[0]: df_input_data_dayahead.index[prediction_horizon - 1]]
                                     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/dist-packages/pandas/core/indexes/base.py", line 5175, in __getitem__
    return getitem(key)
           ^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/dist-packages/pandas/core/arrays/datetimelike.py", line 370, in __getitem__
    "Union[DatetimeLikeArrayT, DTScalarOrNaT]", super().__getitem__(key)
                                                ^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/dist-packages/pandas/core/arrays/_mixins.py", line 272, in __getitem__
    result = self._ndarray[key]
             ~~~~~~~~~~~~~^^^^^
IndexError: index 47 is out of bounds for axis 0 with size 25


Discussion / hints:

Fix patch:
diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile
deleted file mode 100644
index 337f928d..00000000
--- a/.devcontainer/Dockerfile
+++ /dev/null
@@ -1,5 +0,0 @@
-FROM mcr.microsoft.com/devcontainers/python:0-3.11
-# EXPOSE 5000:5000
-COPY .devcontainer/setup.sh ./
-COPY requirements.txt ./
-RUN ./setup.sh
diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
index 7b4662b8..41c3b13e 100644
--- a/.devcontainer/devcontainer.json
+++ b/.devcontainer/devcontainer.json
@@ -3,31 +3,39 @@
 {
 	"name": "EMHASS",
 	"build": {
-		"dockerfile": "Dockerfile",
-		"context": ".."
+		"dockerfile": "../Dockerfile",
+		"context": "../",
+		"args": { "TARGETARCH": "amd64"}
 	},
 	"features": {
-		"ghcr.io/devcontainers/features/docker-outside-of-docker:1": {}
+		"ghcr.io/devcontainers/features/common-utils:2": {
+			"installZsh": "true",
+			"configureZshAsDefaultShell": "true",
+			"installOhMyZsh": "false",
+			"installOhMyZshConfig": "false"
+		}
+		// "ghcr.io/devcontainers/features/docker-outside-of-docker:1": {}
 	},
 	//"appPort": ["5000:5000"] //used to access app from external device (User discretion advised)
 
+
+	// Security issue, may be needed for Podman
+	// "runArgs": [
+	// 	"--userns=keep-id",
+	// 	"--pid=host"
+	// ],
+	// "remoteUser": "root",
+	// "containerUser": "root",
+
+
 	"customizations": {
 		// Configure properties specific to VS Code.
 		"vscode": {
 		  // Add the IDs of extensions you want installed when the container is created.
 		  "extensions": ["ms-python.debugpy", "ms-python.python"]
 		}
-	  }
-	
-	
-	// Features to add to the dev container. More info: https://containers.dev/features.
-	// "features": {},
-	// Use 'forwardPorts' to make a list of ports inside the container available locally.
-	// "forwardPorts": [],
-	// Use 'postCreateCommand' to run commands after the container is created.
-	// "postCreateCommand": "pip3 install --user -r requirements.txt",
-	// Configure tool-specific properties.
-	// "customizations": {},
-	// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
-	// "remoteUser": "root"
-}
\ No newline at end of file
+	  },
+
+	"postCreateCommand": ["pip3", "install", "requests-mock", "--break-system-packages"]
+	  
+}
diff --git a/.devcontainer/setup.sh b/.devcontainer/setup.sh
deleted file mode 100755
index 2939a230..00000000
--- a/.devcontainer/setup.sh
+++ /dev/null
@@ -1,27 +0,0 @@
-#!/usr/bin/env bash
-set -euo pipefail
-
-main() {
-    set -x
-    apt-get update
-    apt-get install -y --no-install-recommends \
-        coinor-cbc \
-        coinor-libcbc-dev \
-        gcc \
-        gfortran \
-        libhdf5-dev \
-        libhdf5-serial-dev \
-        libnetcdf-dev \
-        netcdf-bin
-
-    ln -s /usr/include/hdf5/serial /usr/include/hdf5/include
-    export HDF5_DIR=/usr/include/hdf5
-    pip install netCDF4
-
-    pip install -r requirements.txt
-    pip install requests-mock
-
-    rm -rf "$0"
-}
-
-main
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
index 34bd304f..ea24c0a9 100644
--- a/.github/workflows/codeql.yml
+++ b/.github/workflows/codeql.yml
@@ -29,7 +29,7 @@ jobs:
     strategy:
       fail-fast: false
       matrix:
-        language: [ 'python' ]
+        language: [ 'python' , 'javascript' ]
         # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
         # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
 
diff --git a/.github/workflows/publish_docker.yaml b/.github/workflows/publish_docker.yaml
index a64cbe3b..f518cd99 100644
--- a/.github/workflows/publish_docker.yaml
+++ b/.github/workflows/publish_docker.yaml
@@ -1,4 +1,4 @@
-#template modified from: https://docs.docker.com/build/ci/github-actions/multi-platform/
+#template modified from: https://docs.docker.com/build/ci/github-actions/multi-platform/ & https://docs.github.com/en/actions/use-cases-and-examples/publishing-packages/publishing-docker-images
 name: "Publish Docker"
 
 on:
@@ -6,48 +6,61 @@ on:
     types: [published]
   workflow_dispatch:
 
+env:
+  REGISTRY: ghcr.io
+  IMAGE_NAME: ${{ github.repository }}
+
 jobs:
   build:
     runs-on: ubuntu-latest  
+    permissions:
+      contents: read
+      packages: write
+      attestations: write
+      id-token: write
     strategy:
       fail-fast: false
       matrix:
         platform: [
-          {buildx: linux/amd64, target_arch: amd64, os_version: debian},
-          {buildx: linux/arm/v7, target_arch: armv7, os_version: debian},
-          {buildx: linux/arm/v7, target_arch: armhf, os_version: raspbian},
-          {buildx: linux/arm64,  target_arch: aarch64, os_version: debian}
+          {target_arch: amd64, os_version: debian},
+          {target_arch: armv7, os_version: debian},
+          {target_arch: armhf, os_version: raspbian},
+          {target_arch: aarch64, os_version: debian}
         ]
     steps:
+    # Pull git repo and build each architecture image separately (with QEMU and Buildx)
+      - name: lowercase repo
+        run: |
+          echo "IMAGE_NAME_LOWER=${GITHUB_REPOSITORY,,}" >>${GITHUB_ENV}
       - name: Checkout the repository
         uses: actions/checkout@v4
-      - name: Docker meta
-        id: meta
-        uses: docker/metadata-action@v5
-        with:
-          images: |
-            ${{ secrets.DOCKERHUB_USERNAME }}/emhass-docker-standalone
       - name: Set up QEMU
         uses: docker/setup-qemu-action@v3
       - name: Set up Docker Buildx
         uses: docker/setup-buildx-action@v3
-      - name: Login to Docker Hub
+      - name: Log in to the Container registry
         uses: docker/login-action@v3
         with:
-          username: ${{ secrets.DOCKERHUB_USERNAME }}
-          password: ${{ secrets.DOCKERHUB_TOKEN }}
+          registry: ${{ env.REGISTRY }}
+          username: ${{ github.actor }}
+          password: ${{ secrets.GITHUB_TOKEN }}
+      - name: Extract metadata (tags, labels) for Docker
+        id: meta
+        uses: docker/metadata-action@v5
+        with:
+          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_LOWER }}
       - name: Build and push by digest
         id: build
         uses: docker/build-push-action@v5
         with:
           context: .
-          platforms: ${{ matrix.platform.buildx }}
+          platforms: linux/${{ matrix.platform.target_arch }}
           build-args: |
-            build_version=standalone
             TARGETARCH=${{ matrix.platform.target_arch }}
             os_version=${{ matrix.platform.os_version }}
           labels: ${{ steps.meta.outputs.labels }}
-          outputs: type=image,name=${{ secrets.DOCKERHUB_USERNAME }}/emhass-docker-standalone,push-by-digest=true,name-canonical=true,push=true
+          outputs: type=image,name=${{ env.REGISTRY }}/${{ env.IMAGE_NAME_LOWER }},push-by-digest=true,name-canonical=true,push=true
+      # Export the build images as artifact for the next job of merging
       - name: Export digest
         run: |
           mkdir -p /tmp/digests
@@ -60,38 +73,45 @@ jobs:
           path: /tmp/digests/*
           if-no-files-found: error
           retention-days: 1
+ # Merge platforms into images into a multi-platform image 
   merge:
     if: always()
     runs-on: ubuntu-latest
+    permissions:
+      contents: read
+      packages: write
+      attestations: write
+      id-token: write
     needs:
       - build
     steps:
+      - name: lowercase repo  
+        run: |
+          echo "IMAGE_NAME_LOWER=${GITHUB_REPOSITORY,,}" >>${GITHUB_ENV}
       - name: Download digests
         uses: actions/download-artifact@v4
         with:
           path: /tmp/digests
           pattern: digests-*
-          merge-multiple: true
+          merge-multiple: true   
       - name: Set up Docker Buildx
-        uses: docker/setup-buildx-action@v3
-      - name: Docker meta
+        uses: docker/setup-buildx-action@v3     
+      - name: Extract metadata (tags, labels) for Docker
         id: meta
         uses: docker/metadata-action@v5
         with:
-          images: ${{ secrets.DOCKERHUB_USERNAME }}/emhass-docker-standalone
-          tags: |
-            type=semver,pattern={{version}}
-            type=raw,value=latest
-      - name: Login to Docker Hub
+          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_LOWER }}
+      - name: Log in to the Container registry
         uses: docker/login-action@v3
         with:
-          username: ${{ secrets.DOCKERHUB_USERNAME }}
-          password: ${{ secrets.DOCKERHUB_TOKEN }}
+          registry: ${{ env.REGISTRY }}
+          username: ${{ github.actor }}
+          password: ${{ secrets.GITHUB_TOKEN }}  
       - name: Create manifest list and push
         working-directory: /tmp/digests
         run: |
           docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
-            $(printf '${{ secrets.DOCKERHUB_USERNAME }}/emhass-docker-standalone@sha256:%s ' *)          
+            $(printf '${{ env.REGISTRY }}/${{ env.IMAGE_NAME_LOWER }}@sha256:%s ' *)
       - name: Inspect image
         run: |
-          docker buildx imagetools inspect ${{ secrets.DOCKERHUB_USERNAME }}/emhass-docker-standalone:${{ steps.meta.outputs.version }}
+          docker buildx imagetools inspect ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_LOWER }}:${{ steps.meta.outputs.version }}
diff --git a/.gitignore b/.gitignore
index 2e8d38d6..c79a4eee 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,12 +2,13 @@
 test.py
 # Secret yaml file
 secrets_emhass.yaml
+# config json file 
+config.json
 *.tar
 .vscode/*
 .vscode/launch.json
 .vscode/settings.json
 .vscode/tasks.json
-*.html
 *.pkl
 **/app
 
diff --git a/.vscode/launch.json b/.vscode/launch.json
index 1800d75c..abcbe8ec 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -19,41 +19,29 @@
       ],
       "justMyCode": true,
       "env": {
-        "CONFIG_PATH": "/workspaces/emhass/config_emhass.yaml",
-        "OPTIONS_PATH": "/workspaces/emhass/options.json",
+        //"LEGACY_CONFIG_PATH": "/workspaces/emhass/tests/config_emhass.yaml",
+        "CONFIG_PATH": "/workspaces/emhass/config.json",
         "SECRETS_PATH": "/workspaces/emhass/secrets_emhass.yaml",
         "DATA_PATH": "/workspaces/emhass/data/",
-        "LOGGING_LEVEL": "DEBUG"
-      }
+        "LOGGING_LEVEL": "DEBUG" 
+      },
     },
     {
-      "name": "EMHASS run ADDON",
+      "name": "EMHASS with options.json",
       "type": "debugpy",
       "request": "launch",
       "module": "emhass.web_server",
       "console": "integratedTerminal",
-      "args": [
-        "--addon",
-        "true",
-        "--no_response",
-        "true"
-      ],
       "purpose": [
         "debug-in-terminal"
       ],
       "justMyCode": true,
       "env": {
-        "CONFIG_PATH": "/workspaces/emhass/config_emhass.yaml",
+        "CONFIG_PATH": "/workspaces/emhass/config.json",
         "OPTIONS_PATH": "/workspaces/emhass/options.json",
-        "SECRETS_PATH": "/workspaces/emhass/secrets_emhass.yaml",
         "DATA_PATH": "/workspaces/emhass/data/",
-        "EMHASS_URL": "http://HAIPHERE:8123/", //change
-        "EMHASS_KEY": "PLACEKEYHERE", //change
-        "TIME_ZONE": "Europe/Paris", //optional change
-        "LAT": "45.83", //optional change
-        "LON": "6.86", //optional change
-        "ALT": "4807.8", //optional change
-        "LOGGING_LEVEL": "DEBUG" //optional change
+        "LOGGING_LEVEL": "DEBUG", 
+        //"SUPERVISOR_TOKEN": "test" //test
       },
     }
   ]
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 2947cfe8..a3b632e7 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -9,5 +9,8 @@
         "test_*.py"
     ],
     "python.testing.pytestEnabled": false,
-    "python.testing.unittestEnabled": true
+    "python.testing.unittestEnabled": true,
+    "cSpell.words": [
+        "automations"
+    ]
 }
\ No newline at end of file
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
index ee23d121..9188cd10 100644
--- a/.vscode/tasks.json
+++ b/.vscode/tasks.json
@@ -12,6 +12,7 @@
                 "install",
                 "--no-deps",
                 "--force-reinstall",
+                "--break-system-packages",
                 "--editable",
                 "."
             ],
@@ -19,7 +20,7 @@
                 "echo": true,
                 "panel": "shared",
                 "focus": true
-            }
+            },
         },
         {
             "label": "EMHASS install with dependencies",
@@ -29,7 +30,7 @@
                 "isDefault": true
             },
             "args": [
-                "install", "--force-reinstall", "."
+                "install", "--break-system-packages", "--force-reinstall", "."
             ],
             "presentation": {
                 "echo": true,
diff --git a/Dockerfile b/Dockerfile
old mode 100644
new mode 100755
index 5873a51a..80fde80d
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,28 +1,24 @@
 ## EMHASS Docker
 ## Docker run addon testing example:
-    ## docker build -t emhass/docker --build-arg build_version=addon-local .
-    ## docker run -it -p 5000:5000 --name emhass-container -e LAT="45.83" -e LON="6.86" -e ALT="4807.8" -e TIME_ZONE="Europe/Paris" emhass/docker --url YOURHAURLHERE --key YOURHAKEYHERE
-## Docker run standalone example:
-    ## docker build -t emhass/docker --build-arg build_version=standalone .
-    ## docker run -it -p 5000:5000 --name emhass-container -v $(pwd)/config_emhass.yaml:/app/config_emhass.yaml -v $(pwd)/secrets_emhass.yaml:/app/secrets_emhass.yaml emhass/docker
+    ## docker build -t emhass .
+    ## OR docker build --build-arg TARGETARCH=amd64 -t emhass .
+    ## docker run --rm -it -p 5000:5000 --name emhass-container -v ./config.json:/share/config.json -v ./secrets_emhass.yaml:/app/secrets_emhass.yaml emhass
 
-#build_version options are: addon, addon-pip, addon-git, addon-local, standalone (default)
-ARG build_version=standalone
-
-
-#armhf=raspbian, amd64,armv7,aarch64=debian
+# armhf,amd64,armv7,aarch64
+ARG TARGETARCH
+# armhf=raspbian, amd64,armv7,aarch64=debian
 ARG os_version=debian
 
 FROM ghcr.io/home-assistant/$TARGETARCH-base-$os_version:bookworm AS base
 
-#check if TARGETARCH was passed by build-arg
+# check if TARGETARCH was passed by build-arg
 ARG TARGETARCH
 ENV TARGETARCH=${TARGETARCH:?}
 
 WORKDIR /app
 COPY requirements.txt /app/
 
-#apt package install
+# apt package install
 RUN apt-get update \
     && apt-get install -y --no-install-recommends \
     libffi-dev \
@@ -48,19 +44,19 @@ RUN apt-get update \
     glpk-utils \
     libatlas-base-dev \
     libopenblas-dev
-#specify hdf5
+# specify hdf5
 RUN ln -s /usr/include/hdf5/serial /usr/include/hdf5/include && export HDF5_DIR=/usr/include/hdf5
 
-#install packages from pip, use piwheels if arm 32bit
+# install packages from pip, use piwheels if arm 32bit
 RUN [[ "${TARGETARCH}" == "armhf" || "${TARGETARCH}" == "armv7" ]] &&  pip3 install --index-url=https://www.piwheels.org/simple --no-cache-dir --break-system-packages -r requirements.txt ||  pip3 install --no-cache-dir --break-system-packages -r requirements.txt
 
-#try, symlink apt cbc, to pulp cbc, in python directory (for 32bit)
+# try, symlink apt cbc, to pulp cbc, in python directory (for 32bit)
 RUN [[ "${TARGETARCH}" == "armhf" || "${TARGETARCH}" == "armv7"  ]] &&  ln -sf /usr/bin/cbc /usr/local/lib/python3.11/dist-packages/pulp/solverdir/cbc/linux/32/cbc || echo "cbc symlink didnt work/not required"
 
-#if armv7, try install libatomic1 to fix scipy issue
+# if armv7, try install libatomic1 to fix scipy issue
 RUN [[ "${TARGETARCH}" == "armv7" ]] && apt-get update && apt-get install libatomic1 || echo "libatomic1 cant be installed"
 
-#remove build only packages
+# remove build only packages
 RUN apt-get purge -y --auto-remove \
     gcc \
     patchelf \
@@ -74,98 +70,50 @@ RUN apt-get purge -y --auto-remove \
     libnetcdf-dev \
     && rm -rf /var/lib/apt/lists/*
 
-#copy config file
-COPY config_emhass.yaml /app/
-
-#make sure data directory exists
+# make sure data directory exists
 RUN mkdir -p /app/data/
 
-#-------------------------
-##EMHASS-Add-on default (this has no emhass package)
-FROM base as addon
+# make sure emhass share directory exists
+RUN mkdir -p /share/emhass/
 
-LABEL \
-    io.hass.name="emhass" \
-    io.hass.description="EMHASS: Energy Management for Home Assistant" \
-    io.hass.version=${BUILD_VERSION} \
-    io.hass.type="addon" \
-    io.hass.arch="aarch64|amd64|armhf|armv7"
-
-#-----------
-#EMHASS-ADD-ON testing with pip emhass (EMHASS-Add-on testing reference)
-FROM addon as addon-pip
-#set build arg for pip version
-ARG build_pip_version=""
-RUN pip3 install --no-cache-dir --break-system-packages --upgrade --force-reinstall --no-deps --upgrade-strategy=only-if-needed -U emhass${build_pip_version}
-
-COPY options.json /app/
-
-ENTRYPOINT [ "python3", "-m", "emhass.web_server","--addon", "True", "--no_response", "True"]
-
-#-----------
-#EMHASS-Add-on testing from local files
-FROM addon as addon-local
+# copy required EMHASS files
 COPY src/emhass/ /app/src/emhass/
-COPY src/emhass/templates/ /app/src/emhass/templates/
-COPY src/emhass/static/ /app/src/emhass/static/
-COPY src/emhass/static/img/ /app/src/emhass/static/img/
-COPY src/emhass/data/ /app/src/emhass/data/
-COPY data/opt_res_latest.csv /app/data/
-#add options.json, this otherwise would be generated via HA
-COPY options.json /app/
-COPY README.md /app/
-COPY setup.py /app/
-#compile EMHASS locally
-RUN pip3 install --no-cache-dir --break-system-packages --no-deps --force-reinstall  .
-ENTRYPOINT [ "python3", "-m", "emhass.web_server","--addon", "True" , "--no_response", "True"]
-
-
-#-----------
-#EMHASS-Add-on testing with git
-FROM addon as addon-git
-ARG build_repo=https://github.com/davidusb-geek/emhass.git
-ARG build_branch=master
-WORKDIR /tmp/
-#Repo
-RUN git clone $build_repo
-WORKDIR /tmp/emhass
-#Branch
-RUN git checkout $build_branch
-RUN mkdir -p /app/src/emhass/data/
-RUN cp -r /tmp/emhass/src/emhass/. /app/src/emhass/
-RUN cp /tmp/emhass/src/emhass/data/*  /app/src/emhass/data/
-RUN cp /tmp/emhass/data/opt_res_latest.csv  /app/data/
-RUN cp /tmp/emhass/setup.py /app/
-RUN cp /tmp/emhass/README.md /app/
-#add options.json, this otherwise would be generated via HA
-RUN cp /tmp/emhass/options.json /app/
-WORKDIR /app
-RUN pip3 install --no-cache-dir --break-system-packages --no-deps --force-reinstall  .
-ENTRYPOINT [ "python3", "-m", "emhass.web_server","--addon", "True" , "--no_response", "True"]
 
-#-------------------------
-#EMHASS standalone
-FROM base as standalone
-
-COPY src/emhass/ /app/src/emhass/
+# webserver files
 COPY src/emhass/templates/ /app/src/emhass/templates/
 COPY src/emhass/static/ /app/src/emhass/static/
+COPY src/emhass/static/data/ /app/src/emhass/static/data/
 COPY src/emhass/static/img/ /app/src/emhass/static/img/
+
+# emhass extra packadge data 
 COPY src/emhass/data/ /app/src/emhass/data/
+
+# pre generated optimization results 
 COPY data/opt_res_latest.csv /app/data/
 COPY README.md /app/
 COPY setup.py /app/
-#secrets file can be copied manually at docker run
+
+# secrets file (secrets_emhass.yaml) can be copied into the container with volume mounts with docker run
+# options.json file will be automatically generated and passed from Home Assistant using the addon
 
 #set python env variables
 ENV PYTHONDONTWRITEBYTECODE 1
 ENV PYTHONUNBUFFERED 1
 
-#build EMHASS
+# Docker Labels for hass
+LABEL \
+    io.hass.name="emhass" \
+    io.hass.description="EMHASS: Energy Management for Home Assistant" \
+    io.hass.version=${BUILD_VERSION} \
+    io.hass.type="addon" \
+    io.hass.arch="aarch64|amd64|armhf|armv7"
+
+# build EMHASS
 RUN pip3 install --no-cache-dir --break-system-packages --no-deps --force-reinstall  .
 ENTRYPOINT [ "python3", "-m", "emhass.web_server"]
-#-------------------------
-
 
-#check build arguments and build
-FROM ${build_version} AS final
\ No newline at end of file
+# for running Unittest
+#COPY tests/ /app/tests
+#RUN apt-get update &&  apt-get install python3-requests-mock -y
+#COPY data/ /app/data/
+#ENTRYPOINT ["python3","-m","unittest","discover","-s","./tests","-p","test_*.py"]
diff --git a/README.md b/README.md
index 32e04c48..8cd4f94e 100644
--- a/README.md
+++ b/README.md
@@ -90,90 +90,122 @@ Installation instructions and example Home Assistant automation configurations a
 
 You must follow these steps to make EMHASS work properly:
 
-1) Define all the parameters in the configuration file according to your installation method. For the add-on method, you need to use the configuration pane directly on the add-on page. For other installation methods, it should be needed to set the variables using the `config_emhass.yaml` file. See below for details on the installation methods. See the description for each parameter in the **configuration** section. If you have a PV installation then this dedicated web app can be useful for finding your inverter and solar panel models: [https://emhass-pvlib-database.streamlit.app/](https://emhass-pvlib-database.streamlit.app/)
+1) Install and run EMHASS.
+    - There are multiple methods of installing and Running EMHASS. See [Installation Method](##Installation-Methods) below to pick a method that best suits your use case.
 
-2) You most notably will need to define the main data entering EMHASS. This will be the `sensor.power_photovoltaics` for the name of your hass variable containing the PV produced power and the variable `sensor.power_load_no_var_loads` for the load power of your household excluding the power of the deferrable loads that you want to optimize.
+2) Define all the parameters in the configuration file *(`config.json`)* or configuration page *(`YOURIP:5000/configuration`)*. 
+    - See the description for each parameter in the [configuration](https://emhass.readthedocs.io/en/latest/config.html) docs. 
+        - You will most notably need to define the main data entering EMHASS. This will be the Home Assistant sensor/variable `sensor.power_photovoltaics` for the name of your Home Assistant variable containing the PV produced power, and the sensor/variable `sensor.power_load_no_var_loads`, for the load power of your household excluding the power of the deferrable loads that you want to optimize.
+      - If you have a PV installation then this dedicated web app can be useful for finding your inverter and solar panel models: [https://emhass-pvlib-database.streamlit.app/](https://emhass-pvlib-database.streamlit.app/)
 
-3) Launch the actual optimization and check the results. This can be done manually using the buttons in the web UI or with a `curl` command like this: `curl -i -H 'Content-Type:application/json' -X POST -d '{}' http://localhost:5000/action/dayahead-optim`.
+4) Launch the optimization and check the results. 
+    - This can be done manually using the buttons in the web UI
+    - Or with a `curl` command like this: `curl -i -H 'Content-Type:application/json' -X POST -d '{}' http://localhost:5000/action/dayahead-optim`.
 
-4) If you’re satisfied with the optimization results then you can set the optimization and data publish task commands in an automation. You can read more about this in the **usage** section below.
+5) If you’re satisfied with the optimization results then you can set the optimization and data publish task commands in an automation. 
+    - You can read more about this in the [usage](##usage) section below.
 
-5) The final step is to link the deferrable loads variables to real switches on your installation. An example code for this using automations and the shell command integration is presented below in the **usage** section.
+6) The final step is to link the deferrable loads variables to real switches on your installation. 
+    - An example code for this using automations and the shell command integration is presented below in the [usage](##usage) section.
 
 A more detailed workflow is given below:
 
-![](https://raw.githubusercontent.com/davidusb-geek/emhass/master/docs/images/workflow.png)
+![workflow.png](https://raw.githubusercontent.com/davidusb-geek/emhass/master/docs/images/workflow.png)
+
+## Installation Methods
 
 ### Method 1) The EMHASS add-on for Home Assistant OS and supervised users
 
-For Home Assistant OS and HA Supervised users, I've developed an add-on that will help you use EMHASS. The add-on is more user-friendly as the configuration can be modified directly in the add-on options pane and as with the standalone docker it exposes a web UI that can be used to inspect the optimization results and manually trigger a new optimization.
+For Home Assistant OS and HA Supervised users, A [EMHASS an add-on repository](https://github.com/davidusb-geek/emhass-add-on) has been developed to allow the EMHASS Docker container to run as a [Home Assistant Addon](https://www.home-assistant.io/addons/). The add-on is more user-friendly as the Home Assistant secrets (URL and API key) are automatically placed inside of the EMHASS container, and web server port *(default 5000)* is already opened.
 
 You can find the add-on with the installation instructions here: [https://github.com/davidusb-geek/emhass-add-on](https://github.com/davidusb-geek/emhass-add-on)
 
-The add-on usage instructions can be found on the documentation pane of the add-on once installed or directly here: [EMHASS Add-on documentation](https://github.com/davidusb-geek/emhass-add-on/blob/main/emhass/DOCS.md)
-
 These architectures are supported: `amd64`, `armv7`, `armhf` and `aarch64`.
 
-### Method 2) Using Docker in standalone mode
+_Note: Both EMHASS via Docker and EMHASS-Add-on contain the same Docker image. The EMHASS-Add-on repository however, stores Home Assistant addon specific configuration information and maintains EMHASS image version control._
+
+### Method 2) Running EMHASS in Docker
 
-You can also install EMHASS using docker. This can be in the same machine as Home Assistant (if using the supervised install method) or in a different distant machine. To install first pull the latest image from docker hub:
+You can also install EMHASS using Docker as a container. This can be in the same machine as Home Assistant (if your running Home Assistant as a Docker container) or in a different distant machine. To install first pull the latest image:
 ```bash
-docker pull davidusb/emhass-docker-standalone
+# pull Docker image
+docker docker pull ghcr.io/davidusb-geek/emhass:latest
+# run Docker image, mounting config.json and secrets_emhass.yaml from host
+docker run --rm -it --restart always  -p 5000:5000 --name emhass-container -v ./config.json:/share/config.json -v ./secrets_emhass.yaml:/app/secrets_emhass.yaml ghcr.io/davidusb-geek/emhass:latest
 ```
+*Note it is not recommended to install the latest EMHASS image with `:latest` *(as you would likely want to control when you update EMHASS version)*. Instead, find the [latest version tag](https://github.com/davidusb-geek/emhass/pkgs/container/emhass) (E.g: `v0.2.1`) and replace `latest`*
 
-You can also build your image locally. For this clone this repository, setup your `config_emhass.yaml` file and use the provided make file with this command:
+You can also build your image locally. For this clone this repository, and build the image from the Dockerfile:
 ```bash
-make -f deploy_docker.mk clean_deploy
-```
-Then load the image in the .tar file:
+# git clone EMHASS repo
+git clone docker pull ghcr.io/geoderp/emhass:v0.21.3
+# move to EMHASS directory 
+cd emhass
+# build Docker image 
+# may need to set architecture tag (docker build --build-arg TARGETARCH=amd64 -t emhass-local .)
+docker build -t emhass-local . 
+# run built Docker image, mounting config.json and secrets_emhass.yaml from host
+docker run --rm -it --restart always  -p 5000:5000 --name emhass-container -v ./config.json:/share/config.json -v ./secrets_emhass.yaml:/app/secrets_emhass.yaml emhass-local
+```
+
+Before running the docker container, make sure you have a designated folder for emhass on your host device and a `secrets_emhass.yaml` file. You can get a example of the secrets file from [`secrets_emhass(example).yaml`](https://github.com/davidusb-geek/emhass/blob/master/secrets_emhass(example).yaml) file on this repository.
 ```bash
-docker load -i <TarFileName>.tar
-```
-Finally, check your image tag with `docker images` and launch the docker itself:
-```bash
-docker run -it --restart always -p 5000:5000 -e LOCAL_COSTFUN="profit" -v $(pwd)/config_emhass.yaml:/app/config_emhass.yaml -v $(pwd)/secrets_emhass.yaml:/app/secrets_emhass.yaml --name DockerEMHASS <REPOSITORY:TAG>
-```
-  - If you wish to keep a local, persistent copy of the EMHASS-generated data, create a local folder on your device, then mount said folder inside the container.  
-    ```bash
-    mkdir -p $(pwd)/data #linux: create data folder on local device
-
-    docker run -it --restart always -p 5000:5000 -e LOCAL_COSTFUN="profit" -v $(pwd)/config_emhass.yaml:/app/config_emhass.yaml -v $(pwd)/data:/app/data  -v $(pwd)/secrets_emhass.yaml:/app/secrets_emhass.yaml --name DockerEMHASS <REPOSITORY:TAG>
-    ```
+# cli example of creating an emhass directory and appending a secrets_emhass.yaml file inside
+mkdir ~/emhass
+cd ~/emhass 
+cat <<EOT >> ~/emhass/secrets_emhass.yaml
+hass_url: https://myhass.duckdns.org/
+long_lived_token: thatverylongtokenhere
+time_zone: Europe/Paris
+Latitude: 45.83
+Longitude: 6.86
+Altitude: 4807.8
+EOT
+docker run --rm -it --restart always  -p 5000:5000 --name emhass-container -v ./config.json:/share/config.json -v ./secrets_emhass.yaml:/app/secrets_emhass.yaml ghcr.io/davidusb-geek/emhass:latest
+```
+
+#### Docker, things to note 
+
+- You can create a `config.json` file prior to running emhass. *(obtain a example from: [config_defaults.json](https://github.com/davidusb-geek/emhass/blob/enhass-standalone-addon-merge/src/emhass/data/config_defaults.json)* Alteratively, you can insert your parameters into the configuration page on the EMHASS web server. (for EMHASS to auto create a config.json) With either option, the volume mount `-v ./config.json:/share/config.json` should be applied to make sure your config is stored on the host device. (to be not deleted when the EMHASS container gets removed/image updated)*
+
+- If you wish to keep a local, semi-persistent copy of the EMHASS-generated data, create a local folder on your device, then mount said folder inside the container.  
+  ```bash
+  #create data folder 
+  mkdir -p ~/emhass/data 
+  docker run -it --restart always -p 5000:5000 -e LOCAL_COSTFUN="profit" -v ~/emhass/config.json:/app/config.json -v ~/emhass/data:/app/data  -v ~/emhass/secrets_emhass.yaml:/app/secrets_emhass.yaml --name DockerEMHASS <REPOSITORY:TAG>
+  ```
     
-If you wish to set the web_server's diagrams to a timezone other than UTC, set `TZ` environment variable on:
-```bash
-docker run -it --restart always -p 5000:5000  -e TZ="Europe/Paris"  -e LOCAL_COSTFUN="profit" -v $(pwd)/config_emhass.yaml:/app/config_emhass.yaml -v $(pwd)/secrets_emhass.yaml:/app/secrets_emhass.yaml --name DockerEMHASS <REPOSITORY:TAG>
-```  
-### Method 3) Legacy method using a Python virtual environment
+- If you wish to set the web_server's homepage optimization diagrams to a timezone other than UTC, set `TZ` environment variable on docker run:
+  ```bash
+  docker run -it --restart always -p 5000:5000  -e TZ="Europe/Paris" -v ~/emhass/config.json:/app/config.json -v ~/emhass/secrets_emhass.yaml:/app/secrets_emhass.yaml --name DockerEMHASS <REPOSITORY:TAG>
+  ```  
+### Method 3) Legacy method using a Python virtual environment *(Legacy CLI)*
+If you wish to run EMHASS optimizations with cli commands. *(no persistent web server session)* you can run EMHASS via the python package alone *(not wrapped in a Docker container)*.
 
 With this method it is recommended to install on a virtual environment.
-Create and activate a virtual environment:
-```bash
-python3 -m venv emhassenv
-cd emhassenv
-source bin/activate
-```
-Install using the distribution files:
-```bash
-python3 -m pip install emhass
-```
-Clone this repository to obtain the example configuration files.
-We will suppose that this repository is cloned to:
-```
-/home/user/emhass
-```
-This will be the root path containing the yaml configuration files (`config_emhass.yaml` and `secrets_emhass.yaml`) and the different needed folders (a `data` folder to store the optimizations results and a `scripts` folder containing the bash scripts described further below).
-
-To upgrade the installation in the future just use:
-```bash
-python3 -m pip install --upgrade emhass
-```
+- Create and activate a virtual environment:
+  ```bash
+  python3 -m venv ~/emhassenv
+  cd ~/emhassenv
+  source bin/activate
+  ```
+- Install using the distribution files:
+  ```bash
+  python3 -m pip install emhass
+  ```
+- Create and store configuration (config.json), secret (secrets_emhass.yaml) and data (/data) files in the emhass dir (`~/emhassenv`)  
+Note: You may wish to copy the `config.json` (config_defaults.json), `secrets_emhass.yaml` (secrets_emhass(example).yaml) and/or `/scripts/` files from this repository to the `~/emhassenv` folder for a starting point and/or to run the bash scripts described below. 
+
+- To upgrade the installation in the future just use:
+  ```bash
+  python3 -m pip install --upgrade emhass
+  ```
 
 ## Usage
 
-### Method 1) Add-on and docker standalone
+### Method 1) Add-on and Docker
 
-If using the add-on or the standalone docker installation, it exposes a simple webserver on port 5000. You can access it directly using your browser, ex: http://localhost:5000.
+If using the add-on or the Docker installation, it exposes a simple webserver on port 5000. You can access it directly using your browser. (E.g.: http://localhost:5000)
 
 With this web server, you can perform RESTful POST commands on multiple ENDPOINTS with the prefix `action/*`:
 
@@ -192,25 +224,28 @@ A `curl` command can then be used to launch an optimization task like this: `cur
 To run a command simply use the `emhass` CLI command followed by the needed arguments.
 The available arguments are:
 - `--action`: This is used to set the desired action, options are: `perfect-optim`, `dayahead-optim`, `naive-mpc-optim`, `publish-data`, `forecast-model-fit`, `forecast-model-predict` and `forecast-model-tune`.
-- `--config`: Define the path to the config.yaml file (including the yaml file itself)
+- `--config`: Define the path to the config.json file (including the yaml file itself)
+- `--secrets`: Define secret parameter file (secrets_emhass.yaml) path
 - `--costfun`: Define the type of cost function, this is optional and the options are: `profit` (default), `cost`, `self-consumption`
 - `--log2file`: Define if we should log to a file or not, this is optional and the options are: `True` or `False` (default)
 - `--params`: Configuration as JSON. 
 - `--runtimeparams`: Data passed at runtime. This can be used to pass your own forecast data to EMHASS.
 - `--debug`: Use `True` for testing purposes.
 - `--version`: Show the current version of EMHASS.
+- `--root`: Define path emhass root (E.g. ~/emhass )
+- `--data`: Define path to the Data files (.csv & .pkl) (E.g. ~/emhass/data/ )
 
 For example, the following line command can be used to perform a day-ahead optimization task:
 ```bash
-emhass --action 'dayahead-optim' --config '/home/user/emhass/config_emhass.yaml' --costfun 'profit'
+emhass --action 'dayahead-optim' --config ~/emhass/config.json --costfun 'profit'
 ```
-Before running any valuable command you need to modify the `config_emhass.yaml` and `secrets_emhass.yaml` files. These files should contain the information adapted to your own system. To do this take a look at the special section for this in the [documentation](https://emhass.readthedocs.io/en/latest/config.html).
+Before running any valuable command you need to modify the `config.json` and `secrets_emhass.yaml` files. These files should contain the information adapted to your own system. To do this take a look at the special section for this in the [documentation](https://emhass.readthedocs.io/en/latest/config.html).
 
-## Home Assistant integration
+## Home Assistant Automation
 
-To integrate with Home Assistant we will need to define some shell commands in the `configuration.yaml` file and some basic automations in the `automations.yaml` file.
-In the next few paragraphs, we are going to consider the `dayahead-optim` optimization strategy, which is also the first that was implemented, and we will also cover how to publish the results.
-Then additional optimization strategies were developed, that can be used in combination with/replace the `dayahead-optim` strategy, such as MPC, or to expand the funcitonalities such as the Machine Learning method to predict your household consumption. Each of them has some specificities and features and will be considered in dedicated sections.
+To automate EMHASS with Home Assistant, we will need to define some shell commands in the Home Assistant `configuration.yaml` file and some basic automations in the `automations.yaml` file.
+In the next few paragraphs, we are going to consider the `dayahead-optim` optimization strategy, which is also the first that was implemented, and we will also cover how to publish the optimization  results.  
+Additional optimization strategies were developed later, that can be used in combination with/replace the `dayahead-optim` strategy, such as MPC, or to expand the functionalities such as the Machine Learning method to predict your household consumption. Each of them has some specificities and features and will be considered in dedicated sections.
 
 ### Dayahead Optimization - Method 1) Add-on and docker standalone
 
@@ -225,27 +260,27 @@ shell_command:
 In `configuration.yaml`:
 ```yaml
 shell_command:
-  dayahead_optim: /home/user/emhass/scripts/dayahead_optim.sh
-  publish_data: /home/user/emhass/scripts/publish_data.sh
+  dayahead_optim: ~/emhass/scripts/dayahead_optim.sh
+  publish_data: ~/emhass/scripts/publish_data.sh
 ```
 Create the file `dayahead_optim.sh` with the following content:
 ```bash
 #!/bin/bash
-. /home/user/emhassenv/bin/activate
-emhass --action 'dayahead-optim' --config '/home/user/emhass/config_emhass.yaml'
+. ~/emhassenv/bin/activate
+emhass --action 'dayahead-optim' --config ~/emhass/config.json
 ```
 And the file `publish_data.sh` with the following content:
 ```bash
 #!/bin/bash
-. /home/user/emhassenv/bin/activate
-emhass --action 'publish-data' --config '/home/user/emhass/config_emhass.yaml'
+. ~/emhassenv/bin/activate
+emhass --action 'publish-data' --config ~/emhass/config.json
 ```
 Then specify user rights and make the files executables:
 ```bash
-sudo chmod -R 755 /home/user/emhass/scripts/dayahead_optim.sh
-sudo chmod -R 755 /home/user/emhass/scripts/publish_data.sh
-sudo chmod +x /home/user/emhass/scripts/dayahead_optim.sh
-sudo chmod +x /home/user/emhass/scripts/publish_data.sh
+sudo chmod -R 755 ~/emhass/scripts/dayahead_optim.sh
+sudo chmod -R 755 ~/emhass/scripts/publish_data.sh
+sudo chmod +x ~/emhass/scripts/dayahead_optim.sh
+sudo chmod +x ~/emhass/scripts/publish_data.sh
 ```
 ### Common for any installation method
 
@@ -280,15 +315,15 @@ In `automations.yaml`:
   - service: shell_command.dayahead_optim
   - service: shell_command.publish_data
 ```
-in configuration page/`config_emhass.yaml` 
+in configuration page/`config.json` 
 ```json
-"method_ts_round": "first"
-"continual_publish": true
+'method_ts_round': "first"
+'continual_publish': true
 ```
 In this automation, the day-ahead optimization is performed once a day, every day at 5:30am. 
-If the `freq` parameter is set to `30` *(default)* in the configuration, the results of the day-ahead optimization will generate 48 values *(for each entity)*, a value for every 30 minutes in a day *(i.e. 24 hrs x 2)*.
+If the `optimization_time_step` parameter is set to `30` *(default)* in the configuration, the results of the day-ahead optimization will generate 48 values *(for each entity)*, a value for every 30 minutes in a day *(i.e. 24 hrs x 2)*.
 
-Setting the parameter `continual_publish` to `true` in the configuration page will allow EMHASS to store the optimization results as entities/sensors into separate json files. `continual_publish` will periodically (every `freq` amount of minutes) run a publish, and publish the optimization results of each generated entities/sensors to Home Assistant. The current state of the sensor/entity being updated every time publish runs, selecting one of the 48 stored values, by comparing the stored values' timestamps, the current timestamp and [`"method_ts_round": "first"`](#the-publish-data-specificities) to select the optimal stored value for the current state.
+Setting the parameter `continual_publish` to `true` in the configuration page will allow EMHASS to store the optimization results as entities/sensors into separate json files. `continual_publish` will periodically (every `optimization_time_step` amount of minutes) run a publish, and publish the optimization results of each generated entities/sensors to Home Assistant. The current state of the sensor/entity being updated every time publish runs, selecting one of the 48 stored values, by comparing the stored values' timestamps, the current timestamp and [`'method_ts_round': "first"`](#the-publish-data-specificities) to select the optimal stored value for the current state.
 
 option 1 and 2 are very similar, however, option 2 (`continual_publish`) will require a CPU thread to constantly be run inside of EMHASS, lowering efficiency. The reason why you may pick one over the other is explained in more detail below in [continual_publish](#continual_publish-emhass-automation).
 
@@ -325,7 +360,7 @@ automation:
     - service: homeassistant.turn_off
       entity_id: switch.water_heater_switch
 ```
-These automations will turn on and off the Home Assistant entity `switch.water_heater_switch` using the current state from the EMHASS entity `sensor.p_deferrable0`. `sensor.p_deferrable0`  being the entity generated from the EMHASS day-ahead optimization and published by examples above. The `sensor.p_deferrable0` entity's current state is updated every 30 minutes (or `freq` minutes) via an automated publish option 1 or 2. *(selecting one of the 48 stored data values)*
+These automations will turn on and off the Home Assistant entity `switch.water_heater_switch` using the current state from the EMHASS entity `sensor.p_deferrable0`. `sensor.p_deferrable0`  being the entity generated from the EMHASS day-ahead optimization and published by examples above. The `sensor.p_deferrable0` entity's current state is updated every 30 minutes (or `optimization_time_step` minutes) via an automated publish option 1 or 2. *(selecting one of the 48 stored data values)*
 
 ## The publish-data specificities
 
@@ -356,7 +391,7 @@ Below you can find a list of the variables resulting from EMHASS computation, sh
 | --------------- | ---------- | --------------------------------|
 | P_PV | Forecasted power generation from your solar panels (Watts). This helps you predict how much solar energy you will produce during the forecast period. | sensor.p_pv_forecast |
 | P_Load | Forecasted household power consumption (Watts). This gives you an idea of how much energy your appliances are expected to use. | sensor.p_load_forecast |
-| P_deferrableX<br/>[X = 0, 1, 2, ...] | Forecasted power consumption of deferrable loads (Watts). Deferable loads are appliances that can be managed by EMHASS. EMHASS helps you optimise energy usage by prioritising solar self-consumption and minimizing reliance on the grid or by taking advantage or supply and feed-in tariff volatility. You can have multiple deferable loads and you use this sensor in HA to control these loads via smart switch or other IoT means at your disposal. | sensor.p_deferrableX |
+| P_deferrableX<br/>[X = 0, 1, 2, ...] | Forecasted power consumption of deferrable loads (Watts). Deferable loads are appliances that can be managed by EMHASS. EMHASS helps you optimize energy usage by prioritizing solar self-consumption and minimizing reliance on the grid or by taking advantage or supply and feed-in tariff volatility. You can have multiple deferable loads and you use this sensor in HA to control these loads via smart switch or other IoT means at your disposal. | sensor.p_deferrableX |
 | P_grid_pos | Forecasted power imported from the grid (Watts). This indicates the amount of energy you are expected to draw from the grid when your solar production is insufficient to meet your needs or it is advantageous to consume from the grid. | - |
 | P_grid_neg | Forecasted power exported to the grid (Watts). This indicates the amount of excess solar energy you are expected to send back to the grid during the forecast period. | - |
 | P_batt | Forecasted (dis)charge power load (Watts) for the battery (if installed). If negative it indicates the battery is charging, if positive that the battery is discharging. | sensor.p_batt_forecast |
@@ -381,7 +416,7 @@ In EMHASS we have 4 forecasts to deal with:
 
 - PV production selling price forecast: at what price are you selling your excess PV production in the next 24 hours. This is given in EUR/kWh.
 
-The sensor containing the load data should be specified in the parameter `var_load` in the configuration file. As we want to optimize household energy, we need to forecast the load power consumption. The default method for this is a naive approach using 1-day persistence. The load data variable should not contain the data from the deferrable loads themselves. For example, let's say that you set your deferrable load to be the washing machine. The variables that you should enter in EMHASS will be: `var_load: 'sensor.power_load_no_var_loads'` and `sensor.power_load_no_var_loads = sensor.power_load - sensor.power_washing_machine`. This is supposing that the overall load of your house is contained in the variable: `sensor.power_load`. The sensor `sensor.power_load_no_var_loads` can be easily created with a new template sensor in Home Assistant.
+The sensor containing the load data should be specified in the parameter `sensor_power_load_no_var_loads` in the configuration file. As we want to optimize household energy, we need to forecast the load power consumption. The default method for this is a naive approach using 1-day persistence. The load data variable should not contain the data from the deferrable loads themselves. For example, let's say that you set your deferrable load to be the washing machine. The variables that you should enter in EMHASS will be: `sensor_power_load_no_var_loads: 'sensor.power_load_no_var_loads'` and `sensor.power_load_no_var_loads = sensor.power_load - sensor.power_washing_machine`. This is supposing that the overall load of your house is contained in the variable: `sensor.power_load`. The sensor `sensor.power_load_no_var_loads` can be easily created with a new template sensor in Home Assistant.
 
 If you are implementing an MPC controller, then you should also need to provide some data at the optimization runtime using the key `runtimeparams`.
 
@@ -398,20 +433,20 @@ curl -i -H 'Content-Type:application/json' -X POST -d {} http://localhost:5000/a
 # Then publish teh results of dayahead
 curl -i -H 'Content-Type:application/json' -X POST -d {} http://localhost:5000/action/publish-data
 ```
-*Note, the published entities from the publish-data action will not automatically update the entities' current state (current state being used to check when to turn on and off appliances via Home Assistant automatons). To update the EMHASS entities state, another publish would have to be re-run later when the current time matches the next value's timestamp (e.g. every 30 minutes). See examples below for methods to automate the publish-action.*
+*Note, the published entities from the publish-data action will not automatically update the entities' current state (current state being used to check when to turn on and off appliances via Home Assistant automations). To update the EMHASS entities state, another publish would have to be re-run later when the current time matches the next value's timestamp (e.g. every 30 minutes). See examples below for methods to automate the publish-action.*
 
 #### continual_publish *(EMHASS Automation)*
-As discussed in [Common for any installation method - option 2](#option-2-emhass-automate-publish), setting `continual_publish` to `true` in the configuration saves the output of the optimization into the `data_path/entities` folder *(a .json file for each sensor/entity)*. A constant loop (in `freq` minutes) will run, observe the .json files in that folder, and publish the saved files periodically (updating the current state of the entity by comparing date.now with the saved data value timestamps). 
+As discussed in [Common for any installation method - option 2](#option-2-emhass-automate-publish), setting `continual_publish` to `true` in the configuration saves the output of the optimization into the `data_path/entities` folder *(a .json file for each sensor/entity)*. A constant loop (in `optimization_time_step` minutes) will run, observe the .json files in that folder, and publish the saved files periodically (updating the current state of the entity by comparing date.now with the saved data value timestamps). 
 
-For users that wish to run multiple different optimizations, you can set the runtime parameter: `publish_prefix` to something like: `"mpc_"` or `"dh_"`. This will generate unique entity_id names per optimization and save these unique entities as separate files in the folder. All the entity files will then be updated when the next loop iteration runs. If a different `freq` integer was passed as a runtime parameter in an optimization, the `continual_publish` loop will be based on the lowest `freq` saved. An example:
+For users that wish to run multiple different optimizations, you can set the runtime parameter: `publish_prefix` to something like: `"mpc_"` or `"dh_"`. This will generate unique entity_id names per optimization and save these unique entities as separate files in the folder. All the entity files will then be updated when the next loop iteration runs. If a different `optimization_time_step` integer was passed as a runtime parameter in an optimization, the `continual_publish` loop will be based on the lowest `optimization_time_step` saved. An example:
 
 ```bash
-# RUN dayahead, with freq=30 (default), prefix=dh_ 
+# RUN dayahead, with optimization_time_step=30 (default), prefix=dh_ 
 curl -i -H 'Content-Type:application/json' -X POST -d '{"publish_prefix":"dh_"}' http://localhost:5000/action/dayahead-optim
-# RUN MPC, with freq=5, prefix=mpc_
-curl -i -H 'Content-Type:application/json' -X POST -d '{"freq":5,"publish_prefix":"mpc_"}' http://localhost:5000/action/naive-mpc-optim
+# RUN MPC, with optimization_time_step=5, prefix=mpc_
+curl -i -H 'Content-Type:application/json' -X POST -d '{'optimization_time_step':5,"publish_prefix":"mpc_"}' http://localhost:5000/action/naive-mpc-optim
 ```
-This will tell continual_publish to loop every 5 minutes based on the freq passed in MPC. All entities from the output of dayahead "dh_" and MPC "mpc_" will be published every 5 minutes.
+This will tell continual_publish to loop every 5 minutes based on the optimization_time_step passed in MPC. All entities from the output of dayahead "dh_" and MPC "mpc_" will be published every 5 minutes.
 
 </br>
 
@@ -419,13 +454,13 @@ This will tell continual_publish to loop every 5 minutes based on the freq passe
 
 #### Mixture of continual_publish and manual *(Home Assistant Automation for Publish)*
 
-You can choose to save one optimization for continual_publish and bypass another optimization by setting `"continual_publish":false` runtime parameter:
+You can choose to save one optimization for continual_publish and bypass another optimization by setting `'continual_publish':false` runtime parameter:
 ```bash
-# RUN dayahead, with freq=30 (default), prefix=dh_, included into continual_publish
+# RUN dayahead, with optimization_time_step=30 (default), prefix=dh_, included into continual_publish
 curl -i -H 'Content-Type:application/json' -X POST -d '{"publish_prefix":"dh_"}' http://localhost:5000/action/dayahead-optim
 
-# RUN MPC, with freq=5, prefix=mpc_, Manually publish, excluded from continual_publish loop
-curl -i -H 'Content-Type:application/json' -X POST -d '{"continual_publish":false,"freq":5,"publish_prefix":"mpc_"}' http://localhost:5000/action/naive-mpc-optim
+# RUN MPC, with optimization_time_step=5, prefix=mpc_, Manually publish, excluded from continual_publish loop
+curl -i -H 'Content-Type:application/json' -X POST -d '{'continual_publish':false,'optimization_time_step':5,"publish_prefix":"mpc_"}' http://localhost:5000/action/naive-mpc-optim
 # Publish MPC output
 curl -i -H 'Content-Type:application/json' -X POST -d {} http://localhost:5000/action/publish-data
 ```
@@ -435,16 +470,16 @@ This example saves the dayahead optimization into `data_path/entities` as .json
 
 For users who wish to have full control of exactly when they would like to run a publish and have the ability to save multiple different optimizations. The `entity_save` runtime parameter has been created to save the optimization output entities to .json files whilst  `continual_publish` is set to `false` in the configuration. Allowing the user to reference the saved .json files manually via a publish:
 
-in configuration page/`config_emhass.yaml` :
+in configuration page/`config.json` :
 ```json
-"continual_publish": false
+'continual_publish': false
 ```
 POST action :
 ```bash
-# RUN dayahead, with freq=30 (default), prefix=dh_, save entity
+# RUN dayahead, with optimization_time_step=30 (default), prefix=dh_, save entity
 curl -i -H 'Content-Type:application/json' -X POST -d '{"entity_save": true, "publish_prefix":"dh_"}' http://localhost:5000/action/dayahead-optim
-# RUN MPC, with freq=5, prefix=mpc_, save entity
-curl -i -H 'Content-Type:application/json' -X POST -d '{"entity_save": true", "freq":5,"publish_prefix":"mpc_"}' http://localhost:5000/action/naive-mpc-optim
+# RUN MPC, with optimization_time_step=5, prefix=mpc_, save entity
+curl -i -H 'Content-Type:application/json' -X POST -d '{"entity_save": true", 'optimization_time_step':5,"publish_prefix":"mpc_"}' http://localhost:5000/action/naive-mpc-optim
 ```
 You can then reference these .json saved entities via their `publish_prefix`. Include the same `publish_prefix` in the `publish_data` action:
 ```bash
@@ -470,7 +505,7 @@ curl -i -H 'Content-Type:application/json' -X POST -d '{"pv_power_forecast":[0,
 ```
 Or if using the legacy method using a Python virtual environment:
 ```bash
-emhass --action 'dayahead-optim' --config '/home/user/emhass/config_emhass.yaml' --runtimeparams '{"pv_power_forecast":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 70, 141.22, 246.18, 513.5, 753.27, 1049.89, 1797.93, 1697.3, 3078.93, 1164.33, 1046.68, 1559.1, 2091.26, 1556.76, 1166.73, 1516.63, 1391.13, 1720.13, 820.75, 804.41, 251.63, 79.25, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]}'
+emhass --action 'dayahead-optim' --config ~/emhass/config.json --runtimeparams '{"pv_power_forecast":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 70, 141.22, 246.18, 513.5, 753.27, 1049.89, 1797.93, 1697.3, 3078.93, 1164.33, 1046.68, 1559.1, 2091.26, 1556.76, 1166.73, 1516.63, 1391.13, 1720.13, 820.75, 804.41, 251.63, 79.25, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]}'
 ```
 
 The possible dictionary keys to pass data are:
@@ -485,25 +520,25 @@ The possible dictionary keys to pass data are:
 
 ### Passing other data at runtime
 
-It is possible to also pass other data during runtime to automate energy management. For example, it could be useful to dynamically update the total number of hours for each deferrable load (`def_total_hours`) using for instance a correlation with the outdoor temperature (useful for water heater for example). 
+It is possible to also pass other data during runtime to automate energy management. For example, it could be useful to dynamically update the total number of hours for each deferrable load (`operating_hours_of_each_deferrable_load`) using for instance a correlation with the outdoor temperature (useful for water heater for example). 
 
 Here is the list of the other additional dictionary keys that can be passed at runtime:
 
-- `num_def_loads` for the number of deferrable loads to consider.
+- `number_of_deferrable_loads` for the number of deferrable loads to consider.
 
-- `P_deferrable_nom` for the nominal power for each deferrable load in Watts.
+- `nominal_power_of_deferrable_loads` for the nominal power for each deferrable load in Watts.
 
-- `def_total_hours` for the total number of hours that each deferrable load should operate.
+- `operating_hours_of_each_deferrable_load` for the total number of hours that each deferrable load should operate.
 
-- `def_start_timestep` for the timestep from which each deferrable load is allowed to operate (if you don't want the deferrable load to use the whole optimization timewindow).
+- `start_timesteps_of_each_deferrable_load` for the timestep from which each deferrable load is allowed to operate (if you don't want the deferrable load to use the whole optimization timewindow).
 
-- `def_end_timestep` for the timestep before which each deferrable load should operate (if you don't want the deferrable load to use the whole optimization timewindow).
+- `end_timesteps_of_each_deferrable_load` for the timestep before which each deferrable load should operate (if you don't want the deferrable load to use the whole optimization timewindow).
 
 - `def_current_state` Pass this as a list of booleans (True/False) to indicate the current deferrable load state. This is used internally to avoid incorrectly penalizing a deferrable load start if a forecast is run when that load is already running.
 
-- `treat_def_as_semi_cont` to define if we should treat each deferrable load as a semi-continuous variable.
+- `treat_deferrable_load_as_semi_cont` to define if we should treat each deferrable load as a semi-continuous variable.
 
-- `set_def_constant` to define if we should set each deferrable load as a constant fixed value variable with just one startup for each optimization task.
+- `set_deferrable_load_single_constant` to define if we should set each deferrable load as a constant fixed value variable with just one startup for each optimization task.
 
 - `solcast_api_key` for the SolCast API key if you want to use this service for PV power production forecast.
 
@@ -511,15 +546,15 @@ Here is the list of the other additional dictionary keys that can be passed at r
 
 - `solar_forecast_kwp` for the PV peak installed power in kW used for the solar.forecast API call. 
 
-- `SOCmin` the minimum possible SOC.
+- `battery_minimum_state_of_charge` the minimum possible SOC.
 
-- `SOCmax` the maximum possible SOC.
+- `battery_maximum_state_of_charge` the maximum possible SOC.
 
-- `SOCtarget` for the desired target value of the initial and final SOC.
+- `battery_target_state_of_charge` for the desired target value of the initial and final SOC.
 
-- `Pd_max` for the maximum battery discharge power.
+- `battery_discharge_power_max` for the maximum battery discharge power.
 
-- `Pc_max` for the maximum battery charge power.
+- `battery_charge_power_max` for the maximum battery charge power.
 
 - `publish_prefix` use this key to pass a common prefix to all published data. This will add a prefix to the sensor name but also the forecast attribute keys within the sensor.
 
@@ -544,20 +579,20 @@ When applying this controller, the following `runtimeparams` should be defined:
 
 - `soc_final` for the final value of the battery SOC for the current iteration of the MPC. 
 
-- `def_total_hours` for the list of deferrable loads functioning hours. These values can decrease as the day advances to take into account receding horizon daily energy objectives for each deferrable load.
+- `operating_hours_of_each_deferrable_load` for the list of deferrable loads functioning hours. These values can decrease as the day advances to take into account receding horizon daily energy objectives for each deferrable load.
 
-- `def_start_timestep` for the timestep from which each deferrable load is allowed to operate (if you don't want the deferrable load to use the whole optimization timewindow). If you specify a value of 0 (or negative), the deferrable load will be optimized as from the beginning of the complete prediction horizon window.
+- `start_timesteps_of_each_deferrable_load` for the timestep from which each deferrable load is allowed to operate (if you don't want the deferrable load to use the whole optimization timewindow). If you specify a value of 0 (or negative), the deferrable load will be optimized as from the beginning of the complete prediction horizon window.
 
-- `def_end_timestep` for the timestep before which each deferrable load should operate (if you don't want the deferrable load to use the whole optimization timewindow). If you specify a value of 0 (or negative), the deferrable load optimization window will extend up to the end of the prediction horizon window. 
+- `end_timesteps_of_each_deferrable_load` for the timestep before which each deferrable load should operate (if you don't want the deferrable load to use the whole optimization timewindow). If you specify a value of 0 (or negative), the deferrable load optimization window will extend up to the end of the prediction horizon window. 
 
 A correct call for an MPC optimization should look like this:
 
 ```bash
 curl -i -H 'Content-Type:application/json' -X POST -d '{"pv_power_forecast":[0, 70, 141.22, 246.18, 513.5, 753.27, 1049.89, 1797.93, 1697.3, 3078.93], "prediction_horizon":10, "soc_init":0.5,"soc_final":0.6}' http://192.168.3.159:5000/action/naive-mpc-optim
 ```
-*Example with :`def_total_hours`, `def_start_timestep`, `def_end_timestep`.*
+*Example with :`operating_hours_of_each_deferrable_load`, `start_timesteps_of_each_deferrable_load`, `end_timesteps_of_each_deferrable_load`.*
 ```bash
-curl -i -H 'Content-Type:application/json' -X POST -d '{"pv_power_forecast":[0, 70, 141.22, 246.18, 513.5, 753.27, 1049.89, 1797.93, 1697.3, 3078.93], "prediction_horizon":10, "soc_init":0.5,"soc_final":0.6,"def_total_hours":[1,3],"def_start_timestep":[0,3],"def_end_timestep":[0,6]}' http://localhost:5000/action/naive-mpc-optim
+curl -i -H 'Content-Type:application/json' -X POST -d '{"pv_power_forecast":[0, 70, 141.22, 246.18, 513.5, 753.27, 1049.89, 1797.93, 1697.3, 3078.93], "prediction_horizon":10, "soc_init":0.5,"soc_final":0.6,'operating_hours_of_each_deferrable_load':[1,3],'start_timesteps_of_each_deferrable_load':[0,3],'end_timesteps_of_each_deferrable_load':[0,6]}' http://localhost:5000/action/naive-mpc-optim
 ```
 
 ## A machine learning forecaster
diff --git a/config.json b/config.json
new file mode 100644
index 00000000..02cedd0e
--- /dev/null
+++ b/config.json
@@ -0,0 +1,117 @@
+{
+    "logging_level": "INFO",
+    "costfun": "profit",
+    "optimization_time_step": 30,
+    "historic_days_to_retrieve": 2,
+    "method_ts_round": "nearest",
+    "continual_publish": false,
+    "data_path": "default",
+    "set_total_pv_sell": false,
+    "lp_solver": "default",
+    "lp_solver_path": "empty",
+    "set_nocharge_from_grid": false,
+    "set_nodischarge_to_grid": true,
+    "set_battery_dynamic": false,
+    "battery_dynamic_max": 0.9,
+    "battery_dynamic_min": -0.9,
+    "weight_battery_discharge": 1.0,
+    "weight_battery_charge": 1.0,
+    "sensor_power_photovoltaics": "sensor.power_photovoltaics",
+    "sensor_power_load_no_var_loads": "sensor.power_load_no_var_loads",
+    "sensor_replace_zero": [
+        "sensor.power_photovoltaics",
+        "sensor.power_load_no_var_loads"
+    ],
+    "sensor_linear_interp": [
+        "sensor.power_photovoltaics",
+        "sensor.power_load_no_var_loads"
+    ],
+    "load_negative": false,
+    "set_zero_min": true,
+    "number_of_deferrable_loads": 2,
+    "nominal_power_of_deferrable_loads": [
+        3000.0,
+        750.0
+    ],
+    "operating_hours_of_each_deferrable_load": [
+        4,
+        0
+    ],
+    "weather_forecast_method": "scrapper",
+    "load_forecast_method": "naive",
+    "delta_forecast_daily": 1,
+    "load_cost_forecast_method": "hp_hc_periods",
+    "start_timesteps_of_each_deferrable_load": [
+        0,
+        0
+    ],
+    "end_timesteps_of_each_deferrable_load": [
+        0,
+        0
+    ],
+    "load_peak_hour_periods": {
+        "period_hp_1": [
+            {
+                "start": "02:54"
+            },
+            {
+                "end": "15:24"
+            }
+        ],
+        "period_hp_2": [
+            {
+                "start": "17:24"
+            },
+            {
+                "end": "20:24"
+            }
+        ]
+    },
+    "treat_deferrable_load_as_semi_cont": [
+        true,
+        true
+    ],
+    "set_deferrable_load_single_constant": [
+        false,
+        false
+    ],
+    "set_deferrable_startup_penalty": [
+        0.0,
+        0.0
+    ],
+    "load_peak_hours_cost": 0.1907,
+    "load_offpeak_hours_cost": 0.1419,
+    "production_price_forecast_method": "constant",
+    "photovoltaic_production_sell_price": 0.1419,
+    "maximum_power_from_grid": 9000,
+    "maximum_power_to_grid": 9000,
+    "pv_module_model": [
+        "CSUN_Eurasia_Energy_Systems_Industry_and_Trade_CSUN295_60M"
+    ],
+    "pv_inverter_model": [
+        "Fronius_International_GmbH__Fronius_Primo_5_0_1_208_240__240V_"
+    ],
+    "surface_tilt": [
+        30
+    ],
+    "surface_azimuth": [
+        205
+    ],
+    "modules_per_string": [
+        16
+    ],
+    "strings_per_inverter": [
+        1
+    ],
+    "inverter_is_hybrid": false,
+    "compute_curtailment": false,
+    "set_use_battery": false,
+    "battery_discharge_power_max": 1000,
+    "battery_charge_power_max": 1000,
+    "battery_discharge_efficiency": 0.95,
+    "battery_charge_efficiency": 0.95,
+    "battery_nominal_energy_capacity": 5000,
+    "battery_minimum_state_of_charge": 0.3,
+    "battery_maximum_state_of_charge": 0.9,
+    "battery_target_state_of_charge": 0.6
+}
\ No newline at end of file
diff --git a/docs/config.md b/docs/config.md
index e3cd9cd2..d56d40c2 100644
--- a/docs/config.md
+++ b/docs/config.md
@@ -1,30 +1,29 @@
 # Configuration file
 
-In this section, we will explain all the parts of the `config_emhass.yaml` needed to properly run EMHASS.
+In this section, we will explain all parameters used by EMHASS.
 
-We will find three main parts in the configuration file:
-
-- The parameters needed to retrieve data from Home Assistant (retrieve_hass_conf)
-- The parameters to define the optimization problem (optim_conf)
-- The parameters used to model the system (plant_conf)
+Note: For some context, the parameters bellow are grouped in configuration catagories. EMHASS will receive the and secrets parameters from `config.json` file and secret locations, then sort and format the parameters into their retrospective categories when migrating from the `config` dictionary to the `params` dictionary.
+- The parameters needed to retrieve data from Home Assistant (`retrieve_hass_conf`)
+- The parameters to define the optimization problem (`optim_conf`)
+- The parameters used to model the system (`plant_conf`)
 
 ## Retrieve HASS data configuration
 
 We will need to define these parameters to retrieve data from Home Assistant. There are no optional parameters. In the case of a list, an empty list is a valid entry.
 
-- `freq`: The time step to resample retrieved data from hass. This parameter is given in minutes. It should not be defined too low or you will run into memory problems when defining the Linear Programming optimization. Defaults to 30. 
-- `days_to_retrieve`: We will retrieve data from now to days_to_retrieve days. Defaults to 2.
-- `var_PV`: This is the name of the photovoltaic power-produced sensor in Watts from Home Assistant. For example: 'sensor.power_photovoltaics'.
-- `var_load`: The name of the household power consumption sensor in Watts from Home Assistant. The deferrable loads that we will want to include in the optimization problem should be subtracted from this sensor in HASS. For example: 'sensor.power_load_no_var_loads'
+- `optimization_time_step`: The time step to resample retrieved data from hass. This parameter is given in minutes. It should not be defined too low or you will run into memory problems when defining the Linear Programming optimization. Defaults to 30. 
+- `historic_days_to_retrieve`: We will retrieve data from now to historic_days_to_retrieve days. Defaults to 2.
+- `sensor_power_photovoltaics`: This is the name of the photovoltaic power-produced sensor in Watts from Home Assistant. For example: 'sensor.power_photovoltaics'.
+- `sensor_power_load_no_var_loads`: The name of the household power consumption sensor in Watts from Home Assistant. The deferrable loads that we will want to include in the optimization problem should be subtracted from this sensor in HASS. For example: 'sensor.power_load_no_var_loads'
 - `load_negative`: Set this parameter to True if the retrieved load variable is negative by convention. Defaults to False.
 - `set_zero_min`: Set this parameter to True to give a special treatment for a minimum value saturation to zero for power consumption data. Values below zero are replaced by nans. Defaults to True.
 - `var_replace_zero`: The list of retrieved variables that we would want to replace nans (if they exist) with zeros. For example:
 	- 'sensor.power_photovoltaics'
-- `var_interp`: The list of retrieved variables that we would want to interpolate nans values using linear interpolation. For example:
+- `sensor_linear_interp`: The list of retrieved variables that we would want to interpolate nans values using linear interpolation. For example:
 	- 'sensor.power_photovoltaics'
 	- 'sensor.power_load_no_var_loads'
 - `method_ts_round`: Set the method for timestamp rounding, options are: first, last and nearest.
-- `continual_publish`: set to True to save entities to .json after an optimization run. Then automatically republish the saved entities *(with updated current state value)* every freq minutes. *entity data saved to data_path/entities.*
+- `continual_publish`: set to True to save entities to .json after an optimization run. Then automatically republish the saved entities *(with updated current state value)* every `optimization_time_step` minutes. *entity data saved to data_path/entities.*
 
 A second part of this section is given by some privacy-sensitive parameters that should be included in a `secrets_emhass.yaml` file alongside the `config_emhass.yaml` file.
 
@@ -42,43 +41,43 @@ The parameters in the `secrets_emhass.yaml` file are:
 These are the parameters needed to properly define the optimization problem.
 
 - `set_use_battery`: Set to True if we should consider an energy storage device such as a Li-Ion battery. Defaults to False.
-- `delta_forecast`: The number of days for forecasted data. Defaults to 1.
-- `num_def_loads`: Define the number of deferrable loads to consider. Defaults to 2.
-- `P_deferrable_nom`: The nominal power for each deferrable load in Watts. This is a list with a number of elements consistent with the number of deferrable loads defined before. For example:
+- `delta_forecast_daily`: The number of days for forecasted data. Defaults to 1.
+- `number_of_deferrable_loads`: Define the number of deferrable loads to consider. Defaults to 2.
+- `nominal_power_of_deferrable_loads`: The nominal power for each deferrable load in Watts. This is a list with a number of elements consistent with the number of deferrable loads defined before. For example:
 	- 3000
 	- 750
-- `def_total_hours`: The total number of hours that each deferrable load should operate. For example:
+- `operating_hours_of_each_deferrable_load`: The total number of hours that each deferrable load should operate. For example:
 	- 5
 	- 8
-- `def_start_timestep`: The timestep as from which each deferrable load is allowed to operate (if you don't want the deferrable load to use the whole optimization time window). If you specify a value of 0 (or negative), the deferrable load will be optimized as from the beginning of the complete prediction horizon window. For example:
+- `start_timesteps_of_each_deferrable_load`: The timestep as from which each deferrable load is allowed to operate (if you don't want the deferrable load to use the whole optimization time window). If you specify a value of 0 (or negative), the deferrable load will be optimized as from the beginning of the complete prediction horizon window. For example:
     - 0
     - 1 
-- `def_end_timestep`: The timestep before which each deferrable load should operate. The deferrable load is not allowed to operate after the specified time step. If a value of 0 (or negative) is provided, the deferrable load is allowed to operate in the complete optimization window). For example:
+- `end_timesteps_of_each_deferrable_load`: The timestep before which each deferrable load should operate. The deferrable load is not allowed to operate after the specified time step. If a value of 0 (or negative) is provided, the deferrable load is allowed to operate in the complete optimization window). For example:
 	- 0
 	- 3
-- `treat_def_as_semi_cont`: Define if we should treat each deferrable load as a semi-continuous variable. Semi-continuous variables (`True`) are variables that must take a value that can be either their maximum or minimum/zero (for example On = Maximum load, Off = 0 W). Non semi-continuous (which means continuous) variables (`False`) can take any values between their maximum and minimum. For example:
+- `treat_deferrable_load_as_semi_cont`: Define if we should treat each deferrable load as a semi-continuous variable. Semi-continuous variables (`True`) are variables that must take a value that can be either their maximum or minimum/zero (for example On = Maximum load, Off = 0 W). Non semi-continuous (which means continuous) variables (`False`) can take any values between their maximum and minimum. For example:
 	- True
 	- True
-- `set_def_constant`: Define if we should set each deferrable load as a constant fixed value variable with just one startup for each optimization task. For example:
+- `set_deferrable_load_single_constant`: Define if we should set each deferrable load as a constant fixed value variable with just one startup for each optimization task. For example:
 	- False
 	- False
-- `def_start_penalty`: Set to a list of floats. For each deferrable load with a penalty `P`, each time the deferrable load turns on will incur an additional cost of `P * P_deferrable_nom * cost_of_electricity` at that time. 
+- `set_deferrable_startup_penalty`: Set to a list of floats. For each deferrable load with a penalty `P`, each time the deferrable load turns on will incur an additional cost of `P * nominal_power_of_deferrable_loads * cost_of_electricity` at that time. 
 - `weather_forecast_method`: This will define the weather forecast method that will be used. The options are 'scrapper' for a scrapping method for weather forecast from clearoutside.com and 'csv' to load a CSV file. When loading a CSV file this will be directly considered as the PV power forecast in Watts. The default CSV file path that will be used is '/data/data_weather_forecast.csv'. Defaults to 'scrapper' method.
 - `load_forecast_method`: The load forecast method that will be used. The options are 'csv' to load a CSV file or 'naive' for a simple 1-day persistence model. The default CSV file path that will be used is '/data/data_load_forecast.csv'. Defaults to 'naive'.
 - `load_cost_forecast_method`: Define the method that will be used for load cost forecast. The options are 'hp_hc_periods' for peak and non-peak hours contracts and 'csv' to load custom cost from CSV file. The default CSV file path that will be used is '/data/data_load_cost_forecast.csv'.
 The following parameters and definitions are only needed if load_cost_forecast_method='hp_hc_periods':
-	- `list_hp_periods`: Define a list of peak hour periods for load consumption from the grid. This is useful if you have a contract with peak and non-peak hours. For example for two peak hour periods: 
+	- `load_peak_hour_periods`: Define a list of peak hour periods for load consumption from the grid. This is useful if you have a contract with peak and non-peak hours. For example for two peak hour periods: 
 		- period_hp_1:
 			- start: '02:54'
 			- end: '15:24'
 		- period_hp_2:
 			- start: '17:24'
 			- end: '20:24'
-	- `load_cost_hp`: The cost of the electrical energy from the grid during peak hours in €/kWh. Defaults to 0.1907.
-	- `load_cost_hc`: The cost of the electrical energy from the grid during non-peak hours in €/kWh. Defaults to 0.1419.
+	- `load_peak_hours_cost`: The cost of the electrical energy from the grid during peak hours in €/kWh. Defaults to 0.1907.
+	- `load_offpeak_hours_cost`: The cost of the electrical energy from the grid during non-peak hours in €/kWh. Defaults to 0.1419.
 
-- `prod_price_forecast_method`: Define the method that will be used for PV power production price forecast. This is the price that is paid by the utility for energy injected into the grid. The options are 'constant' for a constant fixed value or 'csv' to load custom price forecasts from a CSV file. The default CSV file path that will be used is '/data/data_prod_price_forecast.csv'.
-- `prod_sell_price`: The paid price for energy injected to the grid from excedent PV production in €/kWh. Defaults to 0.065. This parameter is only needed if prod_price_forecast_method='constant'.
+- `production_price_forecast_method`: Define the method that will be used for PV power production price forecast. This is the price that is paid by the utility for energy injected into the grid. The options are 'constant' for a constant fixed value or 'csv' to load custom price forecasts from a CSV file. The default CSV file path that will be used is '/data/data_prod_price_forecast.csv'.
+- `photovoltaic_production_sell_price`: The paid price for energy injected to the grid from excedent PV production in €/kWh. Defaults to 0.065. This parameter is only needed if production_price_forecast_method='constant'.
 - `set_total_pv_sell`: Set this parameter to true to consider that all the PV power produced is injected to the grid. No direct self-consumption. The default is false, for a system with direct self-consumption.
 - `lp_solver`: Set the name of the linear programming solver that will be used. Defaults to 'COIN_CMD'. The options are 'PULP_CBC_CMD', 'GLPK_CMD' and 'COIN_CMD'. 
 - `lp_solver_path`: Set the path to the LP solver. Defaults to '/usr/bin/cbc'. 
@@ -94,16 +93,16 @@ The following parameters and definitions are only needed if load_cost_forecast_m
 
 These are the technical parameters of the energy system of the household.
 
-- `P_from_grid_max`: The maximum power that can be supplied by the utility grid in Watts (consumption). Defaults to 9000.
-- `P_to_grid_max`: The maximum power that can be supplied to the utility grid in Watts (injection). Defaults to 9000.
+- `maximum_power_from_grid`: The maximum power that can be supplied by the utility grid in Watts (consumption). Defaults to 9000.
+- `maximum_power_to_grid`: The maximum power that can be supplied to the utility grid in Watts (injection). Defaults to 9000.
 
 We will define the technical parameters of the PV installation. For the modeling task we rely on the PVLib Python package. For more information see: [https://pvlib-python.readthedocs.io/en/stable/](https://pvlib-python.readthedocs.io/en/stable/)
 A dedicated web app will help you search for your correct PV module and inverter names: [https://emhass-pvlib-database.streamlit.app/](https://emhass-pvlib-database.streamlit.app/)
 If your specific model is not found in these lists then solution (1) is to pick another model as close as possible as yours in terms of the nominal power.
 Solution (2) would be to use SolCast and pass that data directly to emhass as a list of values from a template. Take a look at this example here: [https://emhass.readthedocs.io/en/latest/forecasts.html#example-using-solcast-forecast-amber-prices](https://emhass.readthedocs.io/en/latest/forecasts.html#example-using-solcast-forecast-amber-prices)
 
-- `module_model`: The PV module model. For example: 'CSUN_Eurasia_Energy_Systems_Industry_and_Trade_CSUN295_60M'. This parameter can be a list of items to enable the simulation of mixed orientation systems, for example one east-facing array (azimuth=90) and one west-facing array (azimuth=270). When finding the correct model for your installation remember to replace all the special characters in the model name with '_'. The name of the table column for your device on the webapp will already have the correct naming convention.
-- `inverter_model`: The PV inverter model. For example: 'Fronius_International_GmbH__Fronius_Primo_5_0_1_208_240__240V_'. This parameter can be a list of items to enable the simulation of mixed orientation systems, for example, one east-facing array (azimuth=90) and one west-facing array (azimuth=270). When finding the correct model for your installation remember to replace all the special characters in the model name with '_'. The name of the table column for your device on the web app will already have the correct naming convention.
+- `pv_module_model`: The PV module model. For example: 'CSUN_Eurasia_Energy_Systems_Industry_and_Trade_CSUN295_60M'. This parameter can be a list of items to enable the simulation of mixed orientation systems, for example one east-facing array (azimuth=90) and one west-facing array (azimuth=270). When finding the correct model for your installation remember to replace all the special characters in the model name with '_'. The name of the table column for your device on the webapp will already have the correct naming convention.
+- `pv_inverter_model`: The PV inverter model. For example: 'Fronius_International_GmbH__Fronius_Primo_5_0_1_208_240__240V_'. This parameter can be a list of items to enable the simulation of mixed orientation systems, for example, one east-facing array (azimuth=90) and one west-facing array (azimuth=270). When finding the correct model for your installation remember to replace all the special characters in the model name with '_'. The name of the table column for your device on the web app will already have the correct naming convention.
 - `surface_tilt`: The tilt angle of your solar panels. Defaults to 30. This parameter can be a list of items to enable the simulation of mixed orientation systems, for example, one east-facing array (azimuth=90) and one west-facing array (azimuth=270). 
 - `surface_azimuth`: The azimuth of your PV installation. Defaults to 205. This parameter can be a list of items to enable the simulation of mixed orientation systems, for example, one east-facing array (azimuth=90) and one west-facing array (azimuth=270). 
 - `modules_per_string`: The number of modules per string. Defaults to 16. This parameter can be a list of items to enable the simulation of mixed orientation systems, for example, one east-facing array (azimuth=90) and one west-facing array (azimuth=270). 
@@ -113,11 +112,11 @@ Solution (2) would be to use SolCast and pass that data directly to emhass as a
 
 If your system has a battery (set_use_battery=True), then you should define the following parameters:
 
-- `Pd_max`: The maximum discharge power in Watts. Defaults to 1000.
-- `Pc_max`: The maximum charge power in Watts. Defaults to 1000.
-- `eta_disch`: The discharge efficiency. Defaults to 0.95.
-- `eta_ch`: The charge efficiency. Defaults to 0.95.
-- `Enom`: The total capacity of the battery stack in Wh. Defaults to 5000.
-- `SOCmin`: The minimum allowable battery state of charge. Defaults to 0.3.
-- `SOCmax`: The maximum allowable battery state of charge. Defaults to 0.9.
-- `SOCtarget`: The desired battery state of charge at the end of each optimization cycle. Defaults to 0.6.
+- `battery_discharge_power_max`: The maximum discharge power in Watts. Defaults to 1000.
+- `battery_charge_power_max`: The maximum charge power in Watts. Defaults to 1000.
+- `battery_discharge_efficiency`: The discharge efficiency. Defaults to 0.95.
+- `battery_charge_efficiency`: The charge efficiency. Defaults to 0.95.
+- `battery_nominal_energy_capacity`: The total capacity of the battery stack in Wh. Defaults to 5000.
+- `battery_minimum_state_of_charge`: The minimum allowable battery state of charge. Defaults to 0.3.
+- `battery_maximum_state_of_charge`: The maximum allowable battery state of charge. Defaults to 0.9.
+- `battery_target_state_of_charge`: The desired battery state of charge at the end of each optimization cycle. Defaults to 0.6.
diff --git a/docs/develop.md b/docs/develop.md
index 5c30cb0f..6cd9ca4c 100644
--- a/docs/develop.md
+++ b/docs/develop.md
@@ -1,9 +1,9 @@
 # EMHASS Development
 
-There are multiple different approaches to developing EMHASS.  
-The choice depends on EMHASS mode (standalone/add-on) and preference (Python venv/DevContainer/Docker).  
+There are multiple different approaches to developing for EMHASS.  
+The choice depends on your and preference (Python venv/DevContainer/Docker).  
 Below are some development workflow examples:  
-_Note: It is preferred to run both addon mode, standalone mode and unittest once before submitting and pull request._
+_Note: It is preferred to run the actions and unittest once before submitting and pull request._
 
 ## Step 1 - Fork
 
@@ -27,13 +27,12 @@ To develop and test code choose one of the following methods:
 ### Method 1 - Python Virtual Environment
 
 We can use python virtual environments to build, develop and test/unittest the code.
-This method works well with standalone mode.
 
 _confirm terminal is in the root `emhass` directory before starting_
 
 **Install requirements**
 ```bash
-python3 -m pip install -r requirements.txt #if arm try setting --extra-index-url=https://www.piwheels.org/simple
+python3 -m pip install -r requirements.txt #if on ARM, try setting --extra-index-url=https://www.piwheels.org/simple
 ```
 
 **Create a developer environment:**
@@ -69,16 +68,16 @@ python3 -m pip install -e .
 - Linux
   ```bash
   export OPTIONS_PATH="${PWD}/options.json" && export USE_OPTIONS="True" ##optional to test options.json
-  export CONFIG_PATH="${PWD}/config_emhass.yaml"
-  export SECRETS_PATH="${PWD}/secrets_emhass.yaml"
+  export CONFIG_PATH="${PWD}/config.yaml"
+  export SECRETS_PATH="${PWD}/secrets_emhass.yaml" ##optional to test secrets_emhass.yaml
   export DATA_PATH="${PWD}/data/"
   ```
 - windows
-  ```cmd
+  ```batch
   set "OPTIONS_PATH=%cd%/options.json"  & ::  optional to test options.json
   set "USE_OPTIONS=True"                & ::  optional to test options.json
-  set "CONFIG_PATH=%cd%/config_emhass.yaml"
-  set "SECRETS_PATH=%cd%/secrets_emhass.yaml"
+  set "CONFIG_PATH=%cd%/config.json"
+  set "SECRETS_PATH=%cd%/secrets_emhass.yaml" & ::  optional to test secrets_emhass.yam
   set "DATA_PATH=%cd%/data/"
   ```
 
@@ -86,21 +85,24 @@ _Make sure `secrets_emhass.yaml` has been created and set. Copy `secrets_emhass(
 
 **Run EMHASS**
 
-```
+```bash
 python3 src/emhass/web_server.py
 ```
+or 
+``` bash
+emhass --action 'dayahead-optim' --config ./config.json --root ./src/emhass --costfun 'profit' --data ./data
+```
 
 **Run unittests**
 
-```
+```bash
 python3 -m unittest discover -s ./tests -p 'test_*.py'
 ```
+_Note:unittest will need to be installed prior_
 
-_unittest will need to be installed prior_
-
-### Method 2: VS-Code Debug and Run via DevContainer
+### Method 2: VS-Code Debug and Run via Dev Container
 
-In VS-Code, you can run a Docker DevContainer to set up a virtual environment. There you can edit and test EMHASS.
+In VS-Code, you can run a Docker Dev Container to set up a virtual environment. The Dev Container's Container will be almost identical to the one build for EMHASS (Docker/Add-on). There you can edit and test EMHASS.
 
 The recommended steps to run are:
 
@@ -109,149 +111,70 @@ The recommended steps to run are:
 - Edit some code...
 - Compile emhass by pressing `control+shift+p` > `Tasks: Run Task` > `EMHASS Install`.
   This has been set up in the [tasks.json](https://github.com/davidusb-geek/emhass/blob/master/.vscode/tasks.json) file. - Before _run & debug_, re-run `EMHASS Install` task every time a change has been made to emhass.
+- Launch and debug the program via the [`Run and Debug`](https://code.visualstudio.com/docs/editor/debugging) tab /`Ctrl+Shift+D` > `EMHASS run` This has been set up in the [Launch.json](https://github.com/davidusb-geek/emhass/blob/master/.vscode/launch.json) .
 
-#### Standalone Mode
-- Launch and debug the application via selecting the [`Run and Debug`](https://code.visualstudio.com/docs/editor/debugging) tab /`Ctrl+Shift+D` > `EMHASS run` (standalone). This has been set up in the [Launch.json](https://github.com/davidusb-geek/emhass/blob/master/.vscode/launch.json) .
-- you will need input your HomeAssistant URL and HomeAssistant KEY inside of secrets_emhass.yaml
-    - Both Add-On and Standalone mods can also accept secrets via environment variables, see [Docker section bellow](method-3---docker-virtual-environment) for examples of environment variole secrets in use.
-- to change your parameters, you can edit emhass_config.yaml file before launch.
-
-#### Add-On Mode
-- Launch and debug the application via selecting the [`Run and Debug`](https://code.visualstudio.com/docs/editor/debugging) tab /`Ctrl+Shift+D` > `EMHASS run Addon` (Add-on). This has been set up in the [Launch.json](https://github.com/davidusb-geek/emhass/blob/master/.vscode/launch.json) .
-- You will need to modify the `EMHASS_URL` _(http://HAIPHERE:8123/)_ and `EMHASS_KEY` _(PLACEKEYHERE)_ inside of Launch.json that matches your HA environment before running.
-- to change your parameters, you can edit options.json file before launch.
+#### Simulate Docker Method or Add-on method
+Since the main difference between the two methods are how secrets are passed. You can switch between the two methods by:  
+  
+**Docker**:  
+  - Create a `secrets_emhass.yaml` file and append your secret parameters  
 
+**Add-on**:  
+  - Modify the `options.json` file to contain you secret parameters  
 
 #### Unittests
-You can run all the unittests by heading to the [`Testing`](https://code.visualstudio.com/docs/python/testing) tab on the left hand side.  
-  This is recommended before creating a pull request.
+Lastly, you can run all the unittests by heading to the [`Testing`](https://code.visualstudio.com/docs/python/testing) tab on the left hand side.  This is recommended before creating a pull request.
 
 ### Method 3 - Docker Virtual Environment
 
-With Docker, you can test the production EMHASS environment in both standalone and add-on mode via modifying the build argument: `build_version` with values: `standalone`, `addon-pip`, `addon-git`, `addon-local`.  
+With Docker, you can test the production EMHASS environment for both Docker and Add-on methods.
 
-Depending on your choice of running standalone or addon, `docker run` will require different passed variables/arguments to function. See following examples:
-Depending on your siltation, you may wish to build EMHASS using a version from a particular git/branch or pip PyPI version. There are examples bellow for these alternative builds. 
+Depending on the method you wish to test, the `docker run` command will require different passed arguments to function. See following examples:
 
-_Note: Make sure your terminal is in the root `emhass` directory before running the docker build._
+_Note: Make sure your terminal is in the root `emhass` repository directory before running the docker build._
 
-#### Docker run add-on via with local files:
-
-**addon-local** copies the local emhass files (from your device) to compile and run in addon mode.
+#### Docker run Add-on Method:
 
 ```bash
-docker build -t emhass/docker --build-arg build_version=addon-local .
+docker build -t emhass/test .
 
-docker run -it -p 5000:5000 --name emhass-container -e LAT="45.83" -e LON="6.86" -e ALT="4807.8" -e TIME_ZONE="Europe/Paris" emhass/docker --url YOURHAURLHERE --key YOURHAKEYHERE
+# pass secrets via options.json (similar to what Home Assistant automatically creates from the addon configuration page)
+docker run -it -p 5000:5000 --name emhass-test -v ./options.json:/data/options.json emhass/test
 ```
 
 **Note:**
+- to apply a file change in the local EMHASS repository, you will need to re-build and re-run the Docker image/container in order for the change to take effect. (excluding volume mounted (-v) files/folders)
+- if you are planning to modify the configs: `options.json`, `secrets_emhass.yaml` or `config.json`, you can [volume mount](https://docs.docker.com/engine/storage/bind-mounts/) them with `-v`. This syncs the Host file to the file inside the container.
+*If running inside of podman, add :z at the end of the volume mount E.g:`-v ./options.json:/data/options.json:z`*
 
-- `addon` mode can have secret parameters passed in at run via variables `-e`, arguments (`--key`,`--url`) or via `secrets_emhass.yaml` with a volume mount
-- on file change, you will need to re-build and re-run the Docker image/container in order for the change to take effect. (excluding volume mounted configs)
-- if you are planning to modify the configs: options.json, secrets_emhass.yaml or config_emhass.yaml, you can volume mount them with `-v`:
-
-  ```bash
-  docker build -t emhass/docker --build-arg build_version=addon-local .
-  
-  docker run -it -p 5000:5000 --name emhass-container -v $(pwd)/options.json:/app/options.json -e LAT="45.83" -e LON="6.86" -e ALT="4807.8" -e TIME_ZONE="Europe/Paris" emhass/docker --url YOURHAURLHERE --key YOURHAKEYHERE
-  ```
-
-  This allows the editing of config files without re-building the Docker Image. On config change, restart the container to take effect:
-
-  ```bash
-  docker stop emhass-container
-  
-  docker start emhass-container
-  ```
-
-#### Docker run Standalone with local files:
-
-**standalone** copies the local emhass files (from your device) to compile and run in standalone mode.
+#### Docker run for Docker Method:
 
 ```bash
-docker build -t emhass/docker --build-arg build_version=standalone .
+docker build -t emhass/test .
 
-docker run -it -p 5000:5000 --name emhass-container -v $(pwd)/config_emhass.yaml:/app/config_emhass.yaml -v $(pwd)/secrets_emhass.yaml:/app/secrets_emhass.yaml emhass/docker
-```
-
-_Standalone mode can use `secrets_emhass.yaml` to pass secret parameters (overriding secrets provided by ARG/ENV's). Copy `secrets_emhass(example).yaml` for an example._
-
-#### Docker run add-on with Git or pip:
-
-If you would like to test with the current production/master versions of emhass, you can do so via pip or Git. With Git, you can also specify other repos/branches outside of `davidusb-geek/emhass:master`. 
-
-**addon-pip** will be the closest environment to the production emhass-add-on.  
-However, both come with the disadvantage of not easily being able to edit the emhass package itself.
-
-**Docker run add-on git**
-
-```bash
-docker build -t emhass/docker --build-arg build_version=addon-git .
-
-docker run -it -p 5000:5000 --name emhass-container -e LAT="45.83" -e LON="6.86" -e ALT="4807.8" -e TIME_ZONE="Europe/Paris" -v $(pwd)/options.json:/app/options.json emhass/docker --url YOURHAURLHERE --key YOURHAKEYHERE
-```
-
-To test a repo and branch outside of `davidusb-geek/emhass:master`:
-_(Utilizing build args `build_repo` and `build_branch`)_  
-_Linux:_
-```bash
-repo=https://github.com/davidusb-geek/emhass.git
-branch=master
-
-docker build -t emhass/docker --build-arg build_version=addon-git --build-arg build_repo=$repo --build-arg build_branch=$branch .
-
-docker run -it -p 5000:5000 --name emhass-container -e LAT="45.83" -e LON="6.86" -e ALT="4807.8" -e TIME_ZONE="Europe/Paris" -v $(pwd)/options.json:/app/options.json emhass/docker --url YOURHAURLHERE --key YOURHAKEYHERE
-```
-
-**Docker run add-on pip:**
-
-```bash
-docker build -t emhass/docker --build-arg build_version=addon-pip .
-
-docker run -it -p 5000:5000 --name emhass-container -e LAT="45.83" -e LON="6.86" -e ALT="4807.8" -e TIME_ZONE="Europe/Paris" -v $(pwd)/options.json:/app/options.json emhass/docker --url YOURHAURLHERE --key YOURHAKEYHERE
-```
-To build with a specific pip version, set with build arg: `build_pip_version`: 
-```bash
-docker build -t emhass/docker --build-arg build_version=addon-pip --build-arg build_pip_version='==0.7.7' .
-
-docker run -it -p 5000:5000 --name emhass-container -e LAT="45.83" -e LON="6.86" -e ALT="4807.8" -e TIME_ZONE="Europe/Paris" -v $(pwd)/options.json:/app/options.json emhass/docker --url YOURHAURLHERE --key YOURHAKEYHERE
-```
-</br>
-
-_You can add or remove file volume mounts with the `-v` tag, this should override the file in the container (ex. options.json)_
-  
-#### EMHASS older then **0.7.9** 
-For older versions of EMHASS, you may wish to specify the _config_, _data_ and _options_ paths to avoid errors:
-```bash
-docker run ... -e OPTIONS_PATH='/app/options.json' -e CONFIG_PATH='/app/config_emhass.yaml' -e DATA_PATH='/app/data/'  ...
-```
-For example pip:
-```bash
-docker build -t emhass/docker --build-arg build_version=addon-pip .
-
-docker run -it -p 5000:5000 --name emhass-container -e LAT="45.83" -e LON="6.86" -e ALT="4807.8" -e TIME_ZONE="Europe/Paris"  -e CONFIG_PATH='/app/config_emhass.yaml' -e DATA_PATH='/app/data/' -e OPTIONS_PATH='/app/options.json' -v $(pwd)/options.json:/app/options.json emhass/docker --url YOURHAURLHERE --key YOURHAKEYHERE
+# pass the secrets_emhass.yaml
+docker run -it -p 5000:5000 --name emhass-test -v ./secrets_emhass.yaml:/app/secrets_emhass.yaml emhass/test
 ```
 
 #### Sync with local data folder 
-For those who wish to mount/sync the local `data` folder with the data folder from the docker container, volume mount the data folder with `-v` .
+For those who wish to mount/sync the local `data` folder with the data folder from inside the docker container, volume mount the data folder with `-v` .
 ```bash
-docker run ... -v $(pwd)/data/:/app/data ...
+docker run ... -v ./data/:/app/data ...
 ```
 
-You can also mount data (ex .csv)  files separately
+You can also mount data files (ex .csv)  separately
 ```bash
-docker run... -v $(pwd)/data/heating_prediction.csv:/app/data/ ...
+docker run... -v ./data/heating_prediction.csv:/app/data/ ...
 ```
 
 #### Issue with TARGETARCH
 If your docker build fails with an error related to `TARGETARCH`. It may be best to add your device's architecture manually:
 
-Example with armhf architecture 
+Example with `armhf` architecture 
 ```bash
 docker build ... --build-arg TARGETARCH=armhf --build-arg os_version=raspbian ...
 ```
-*For `armhf` only, create a build-arg for `os_version=raspbian`* 
+*For `armhf` only, also pass a build-arg for `os_version=raspbian`* 
 
 
 #### Delete built Docker image
@@ -259,9 +182,11 @@ docker build ... --build-arg TARGETARCH=armhf --build-arg os_version=raspbian ..
 We can delete the Docker image and container via:
 
 ```bash
-docker rm -f emhass-container #force delete Docker container
+# force delete Docker container
+docker rm -f emhass-test 
 
-docker rmi emhass/docker #delete Docker image
+# delete Docker image
+docker rmi emhass/test 
 ```
 
 #### Other Docker Options
@@ -272,20 +197,18 @@ As editing and testing EMHASS via docker may be repetitive (rebuilding image and
 **For rapid Docker testing, try a command chain:**  
 _Linux:_
 ```bash
-docker build -t emhass/docker --build-arg build_version=addon-local . && docker run --rm -it -p 5000:5000 -v $(pwd)/secrets_emhass.yaml:/app/secrets_emhass.yaml --name emhass-container emhass/docker 
+docker build -t emhass/test . && docker run --rm -it -p 5000:5000 -v ./secrets_emhass.yaml:/app/secrets_emhass.yaml --name emhass-test emhass/test 
 ```
-
-_The example command chain rebuilds the Docker image and runs a new container with the newly built image. `--rm` has been added to the `docker run` to delete the container once ended to avoid manual deletion every time._  
-_This use case may not require any volume mounts (unless you use secrets_emhass.yaml) as the Docker build process will pull the latest versions of the configs as it builds._
-
+_The example command chain rebuilds the Docker image, and runs a new container with the newly built image. The `--rm` has been added to the `docker run` to delete the container once ended to avoid manual deletion every time._  
+_This use case may not require any volume mounts (unless you use secrets_emhass.yaml) as the Docker build process will pull the latest configs as it builds._
 
 **Environment Variables**  
-Running addon mode, you can also pass location, key and url secret parameters via environment variables.
+ you can also pass location, key and url secret parameters via environment variables.
 
 ```bash
-docker build -t emhass/docker --build-arg build_version=addon-local .
+docker build -t emhass/test --build-arg build_version=addon-local .
 
-docker run -it -p 5000:5000 --name emhass-container -e URL="YOURHAURLHERE" -e KEY="YOURHAKEYHERE" -e LAT="45.83" -e LON="6.86" -e ALT="4807.8" -e TIME_ZONE="Europe/Paris" emhass/docker
+docker run -it -p 5000:5000 --name emhass-test -e URL="YOURHAURLHERE" -e KEY="YOURHAKEYHERE" -e LAT="45.83" -e LON="6.86" -e ALT="4807.8" -e TIME_ZONE="Europe/Paris" emhass/test
 ```
 
 This allows the user to set variables before the build
@@ -299,13 +222,13 @@ export LAT="45.83"
 export LON="6.86"
 export ALT="4807.8"
 
-docker build -t emhass/docker --build-arg build_version=addon-local .
+docker build -t emhass/test --build-arg build_version=addon-local .
 
-docker run -it -p 5000:5000 --name emhass-container -e EMHASS_KEY -e EMHASS_URL -e TIME_ZONE -e LAT -e LON -e ALT emhass/docker
+docker run -it -p 5000:5000 --name emhass-test -e EMHASS_KEY -e EMHASS_URL -e TIME_ZONE -e LAT -e LON -e ALT emhass/test
 ```
 
 ### Example Docker testing pipeline 
-The following pipeline will run unittest and most of the EMHASS actions in both Standalone and Add-on mode. This may be a good options for those who wish to test their changes against the production EMHASS environment.
+The following pipeline will run unittest and most of the EMHASS actions. This may be a good option for those who wish to test their changes against the production EMHASS environment.
 
 *Linux:*  
 *Assuming docker and git installed*
@@ -323,12 +246,12 @@ git checkout $branch
 ```
 
 ```bash
-#testing addon (build and run)
-docker build -t emhass/docker --build-arg build_version=addon-local .
-docker run --rm -it -p 5000:5000 --name emhass-container -v $(pwd)/data/heating_prediction.csv:/app/data/heating_prediction.csv -v $(pwd)/options.json:/app/options.json -e LAT="45.83" -e LON="6.86" -e ALT="4807.8" -e TIME_ZONE="Europe/Paris" emhass/docker --url $HAURL --key $HAKEY
+# testing with option.json (replace -v options.json with secrets_emhass.yaml to test both secret files)
+docker build -t emhass/test .
+docker run --rm -it -p 5000:5000 --name emhass-test -v $(pwd)/data/heating_prediction.csv:/app/data/heating_prediction.csv -v $(pwd)/options.json:/app/options.json emhass/test
 ```
 ```bash
-#run actions on a separate terminal
+# run actions one-by-one, on a separate terminal
 curl -i -H 'Content-Type:application/json' -X POST -d '{"pv_power_forecast":[0, 70, 141.22, 246.18, 513.5, 753.27, 1049.89, 1797.93, 1697.3, 3078.93], "prediction_horizon":10, "soc_init":0.5,"soc_final":0.6}' http://localhost:5000/action/naive-mpc-optim
 curl -i -H 'Content-Type:application/json' -X POST -d {} http://localhost:5000/action/perfect-optim
 curl -i -H 'Content-Type:application/json' -X POST -d {} http://localhost:5000/action/dayahead-optim
@@ -341,49 +264,22 @@ curl -i -H 'Content-Type:application/json' -X POST -d {} http://localhost:5000/a
 ```
 
 ```bash
-#testing standalone (build and run)
-docker build -t emhass/docker --build-arg build_version=standalone .
-#make secrets_emhass
-cat <<EOT > secrets_emhass.yaml
-hass_url: $HAURL
-long_lived_token: $HAKEY
-time_zone: Europe/Paris
-lat: 45.83
-lon: 6.86
-alt: 4807.8
-EOT
-docker run --rm -it -p 5000:5000 --name emhass-container -v $(pwd)/data/heating_prediction.csv:/app/data/heating_prediction.csv -v $(pwd)/config_emhass.yaml:/app/config_emhass.yaml -v $(pwd)/secrets_emhass.yaml:/app/secrets_emhass.yaml emhass/docker 
+# testing unittest (add extra necessary files via volume mount)
+docker run --rm -it -p 5000:5000 --name emhass-test -v $(pwd)/tests/:/app/tests/ -v $(pwd)/data/:/app/data/ -v $(pwd)/"secrets_emhass(example).yaml":/app/"secrets_emhass(example).yaml" -v $(pwd)/options.json:/app/options.json -v $(pwd)/config_emhass.yaml:/app/config_emhass.yaml -v $(pwd)/secrets_emhass.yaml:/app/secrets_emhass.yaml emhass/test
 ```
 ```bash
-#run actions on a separate terminal
-curl -i -H 'Content-Type:application/json' -X POST -d '{"pv_power_forecast":[0, 70, 141.22, 246.18, 513.5, 753.27, 1049.89, 1797.93, 1697.3, 3078.93], "prediction_horizon":10, "soc_init":0.5,"soc_final":0.6}' http://localhost:5000/action/naive-mpc-optim
-curl -i -H 'Content-Type:application/json' -X POST -d {} http://localhost:5000/action/perfect-optim
-curl -i -H 'Content-Type:application/json' -X POST -d {} http://localhost:5000/action/dayahead-optim
-curl -i -H 'Content-Type:application/json' -X POST -d {} http://localhost:5000/action/forecast-model-fit
-curl -i -H 'Content-Type:application/json' -X POST -d {} http://localhost:5000/action/forecast-model-predict
-curl -i -H 'Content-Type:application/json' -X POST -d {} http://localhost:5000/action/forecast-model-tune
-curl -i -H "Content-Type:application/json" -X POST -d  '{"csv_file": "heating_prediction.csv", "features": ["degreeday", "solar"], "target": "hour", "regression_model": "RandomForestRegression", "model_type": "heating_hours_degreeday", "timestamp": "timestamp", "date_features": ["month", "day_of_week"], "new_values": [12.79, 4.766, 1, 2] }' http://localhost:5000/action/regressor-model-fit
-curl -i -H "Content-Type:application/json" -X POST -d  '{"mlr_predict_entity_id": "sensor.mlr_predict", "mlr_predict_unit_of_measurement": "h", "mlr_predict_friendly_name": "mlr predictor", "new_values": [8.2, 7.23, 2, 6], "model_type": "heating_hours_degreeday" }' http://localhost:5000/action/regressor-model-predict
-curl -i -H 'Content-Type:application/json' -X POST -d {} http://localhost:5000/action/publish-data
-```
-
-```bash
-#testing unittest (run standalone with extra files)
-docker run --rm -it -p 5000:5000 --name emhass-container -v $(pwd)/tests/:/app/tests/ -v $(pwd)/data/:/app/data/ -v $(pwd)/"secrets_emhass(example).yaml":/app/"secrets_emhass(example).yaml" -v $(pwd)/options.json:/app/options.json -v $(pwd)/config_emhass.yaml:/app/config_emhass.yaml -v $(pwd)/secrets_emhass.yaml:/app/secrets_emhass.yaml emhass/docker
-```
-```bash
-#run unittest's on separate terminal
-docker exec emhass-container apt-get update 
-docker exec emhass-container apt-get install python3-requests-mock -y
-docker exec emhass-container python3 -m unittest discover -s ./tests -p 'test_*.py' | grep error
+# run unittest's on separate terminal after installing requests-mock
+docker exec emhass-test apt-get update 
+docker exec emhass-test apt-get install python3-requests-mock -y
+docker exec emhass-test python3 -m unittest discover -s ./tests -p 'test_*.py' | grep error
 ```
+*Note: may need to set `--build-arg TARGETARCH=YOUR-ARCH` in docker build*
 
-User may wish to re-test with tweaked parameters such as `lp_solver`, `weather_forecast_method` and `load_forecast_method`, in `config_emhass.yaml` *(standalone)* or `options.json` *(addon)*, to broaden the testing scope. 
-*see [EMHASS & EMHASS-Add-on differences](https://emhass.readthedocs.io/en/latest/differences.html) for more information on how these config_emhass & options files differ*
+User may wish to re-test with tweaked parameters such as `lp_solver`, `weather_forecast_method` and `load_forecast_method`, in `config.json` to broaden the testing scope. 
+*See [Differences](https://emhass.readthedocs.io/en/latest/differences.html) for more information on how the different methods of running EMHASS differ.*
 
-*Note: may need to set `--build-arg TARGETARCH=YOUR-ARCH` in docker build*
 
 ## Step 3 - Pull request
 
-Once developed, commit your code, and push to your fork.
-Then submit a pull request with your fork to the [davidusb-geek/emhass@master](https://github.com/davidusb-geek/emhass) repository.
+Once developed, commit your code, and push the commit to your fork on Github.
+Once ready, submit a pull request with your fork to the [davidusb-geek/emhass@master](https://github.com/davidusb-geek/emhass) repository.
diff --git a/docs/differences.md b/docs/differences.md
index 837a22e2..50e349c0 100644
--- a/docs/differences.md
+++ b/docs/differences.md
@@ -1,19 +1,20 @@
 # EMHASS & EMHASS-Add-on differences
-Users will pass parameters into EMHASS differently, based on running *Standalone* mode or *addon* Mode.   
-This page tries to help to resolve the common confusion between the two.  
-_It's best to see EMHASS-Add-on as a Home Assistant Docker wrapper for EMHASS. However, because of this containerization, certain changes are made between the two modes._
+Users will pass parameters and secrets into EMHASS differently, based on what method your running emhass. (Add-on, Docker, Python)
+This page tries to help to resolve the common confusion between the different methods.
 
-## Configuration & parameter differences
-Both EMHASS & EMHASS-Add-on utilize `config_emhass.yaml` for receiving parameters.  
-Where they diverge is EMHASS-Add-ons additional use of `options.json`, generated by Home Assistants `Configuration Page`.  
-Any passed parameters given in `options.json` will overwrite the parameters hidden in the `config_emhass.yaml` file in EMHASS. _(this results in `config_emhass.yaml` used for parameter default fallback if certain required parameters were missing in `options.json`)_
+## Legacy Parameter definitions  
+After EMHASS version:`0.10.6`, EMHASS has merged the parameter config from the legacy modes (`config_emhass.yaml` & `options.json`) to a central `config.json`.
 
-The parameters naming convention has also been changed in `options.json`, designed to make it easier for the user to understand.
+The resulting change saw a migration of the parameter naming conventions.   
+*Many of the new parameter definitions seen in `config.json` are copied from the Add-on, however not all.*
 
-See below for a list of associations between the parameters from `config_emhass.yaml` and `options.json`:  
-*You can view the current parameter differences in the [`Utils.py`](https://github.com/davidusb-geek/emhass/blob/master/src/emhass/utils.py) file under the `build_params` function.*
+To simply convert from the legacy method (EMHASS>=0.10.6) to the new `config.json` method, see this video guide:  
+- Standalone Mode: https://youtu.be/T85DAdXnGFY?feature=shared&t=938
+- Addon Mode: https://youtu.be/T85DAdXnGFY?feature=shared&t=1341
 
-| config | config_emhass.yaml | options.json | options.json list dictionary key |
+See below for a list of associations between the parameters from `config_emhass.yaml` *(Legacy Standalone mode)*, `options.json` *(Legacy Add-on mode)* and the `config.json` parameter definitions:  
+
+| config catagories | config_emhass.yaml *(Legacy)* | config.json | options.json list dictionary key *(Legacy)* |
 | ------ | ------------------ | ------------ | -------------------------------- |
 | retrieve_hass_conf |  freq |  optimization_time_step | |
 | retrieve_hass_conf |  days_to_retrieve |  historic_days_to_retrieve | |
@@ -74,35 +75,46 @@ See below for a list of associations between the parameters from `config_emhass.
 | plant_conf |  SOCtarget |  battery_target_state_of_charge | |
 
 Descriptions of each parameter can be found at:
--  [`Configuration file`](https://emhass.readthedocs.io/en/latest/config.html) on EMHASS
--  [`en.yaml`](https://github.com/davidusb-geek/emhass-add-on/blob/main/emhass/translations/en.yaml) on EMHASS-Add-on
+-  [`Configuration Documentation`](https://emhass.readthedocs.io/en/latest/config.html) 
+- Configuration page on EMHASS web server (E.g. http://localhost:5000/configuration)
 
 ## Passing in secret parameters
-Secret parameters get passed differently, depending on which mode you choose. Alternative options are also present for passing secrets if running EMHASS separately from Home Assistant. _(I.e. not via EMHASS-Add-on)_ 
+Secret parameters are passed differently, depending on which method you choose. Alternative options are also present for passing secrets, if you are running EMHASS separately from Home Assistant. _(I.e. not via EMHASS-Add-on)_ 
+
+### EMHASS with Docker or Python
+Running EMHASS in Docker or Python by default retrieves all secret parameters via a passed `secrets_emhass.yaml` file. An example template has been provided under the name `secrets_emhass(example).yaml` on the EMHASS repo.
 
-### EMHASS (with standalone mode)
-Running EMHASS in standalone mode's default workflow retrieves all secret parameters via a passed `secrets_emhass.yaml` file. An example template has been provided under the name `secrets_emhass(example).yaml`.
+To pass the the secrets file:
+- On Docker: *(via volume mount)*
+```bash
+Docker run ... -v ./secrets_emhass.yaml:/app/secrets_emhass.yaml ...
+```
+- On Python: *(optional: specify path as a argument)*
+```bash
+emhass ... --secrets=./secrets_emhass.yaml ...
+```
 
 #### Alternative Options
-For users who are running EMHASS with methods other than EMHASS-Add-on, secret parameters can be passed with the use of arguments and/or environment variables. _(instead of `secrets_emhass.yaml`)_
+For users who are running EMHASS with methods other than EMHASS-Add-on, secret parameters can be passed with the use of environment variables. _(instead of `secrets_emhass.yaml`)_
 
-Some arguments include:  `--url` and `--key`  
 Some environment variables include: `TIME_ZONE`, `LAT`, `LON`, `ALT`, `EMHASS_URL`, `EMHASS_KEY`
 
-_Note: As of writing, EMHASS standalone will override ARG/ENV secret parameters if the file is present._
+_Note: As of writing, EMHASS will override ENV secret parameters if the file is present._
 
 For more information on passing arguments and environment variables using docker, have a look at some examples from [Configuration and Installation](https://emhass.readthedocs.io/en/latest/intro.html#configuration-and-installation) and [EMHASS Development](https://emhass.readthedocs.io/en/latest/develop.html) pages. 
 
-### EMHASS-Add-on (addon mode)
-By default, the `URL` and `KEY` parameters have been set to `empty`/blank. This results in EMHASS calling its Supervisor API to gain access locally. This is the easiest method, as there is no user input necessary.  
+### EMHASS-Add-on *(Emhass Add-on)*
+By default, the `URL` and `KEY` parameters have been set to `empty`/blank in the Home Assistant configuration page for EMHASS addon. This results in EMHASS calling its Local `Supervisor API` to gain access. This is the easiest method, as there is no user input necessary.  
 
-However, if you wish to receive/send sensor data to a different Home Assistant environment, set url and key values in the `hass_url` & `long_lived_token` hidden parameters.  
+However, if you wish to receive/send sensor data to a different Home Assistant environment, set url and key values in the `hass_url` & `long_lived_token` hidden parameters on the Home Assistant EMHASS addon configuration page. *(E.g. http://localhost:8123/hassio/addon/emhass/config)*
 -  `hass_url` example: `https://192.168.1.2:8123/`  
 -  `long_lived_token` generated from the `Long-lived access tokens` section in your user profile settings
 </br></br>
 
-Secret Parameters such as: `time_zone`, `lon`, `lat` and `alt` are also automatically passed in via the Home Assistants environment. _(Values set in the Home Assistants config/general page)_  
+Secret Parameters such as: `solcast_api_key`, `solcast_rooftop_id` and `solar_forecast_kwp` _(used by their respective `weather_forecast_method` parameter values)_, can also be set via hidden parameters in the addon configuration page.
+
+Secret Parameters such as: `time_zone`, `lon`, `lat` and `alt` are also automatically passed in via the Home Assistants `Supervisor API`. _(Values set in the Home Assistants config/general page)_  
 _Note: Local currency could also be obtained via the Home Assistant environment, however as of writing, this functionality has not yet been developed._
 
-Secret Parameters such as: `solcast_api_key`, `solcast_rooftop_id` and `solar_forecast_kwp` _(used by their respective `weather_forecast_method` parameter values)_, can also be set via hidden parameters in the configuration page.
+
 
diff --git a/docs/forecasts.md b/docs/forecasts.md
index 76c60247..864d4ade 100644
--- a/docs/forecasts.md
+++ b/docs/forecasts.md
@@ -17,7 +17,7 @@ Then there are the methods that are specific to each type of forecast and that p
 The `get_power_from_weather` method is proposed here to convert irradiance data to electrical power. The PVLib module is used to model the PV plant. A dedicated web app will help you search for your correct PV module and inverter: [https://emhass-pvlib-database.streamlit.app/](https://emhass-pvlib-database.streamlit.app/)
 
 The specific methods for the load forecast is a first method (`naive`) that uses a naive approach, also called persistence. It simply assumes that the forecast for 
-a future period will be equal to the observed values in a past period. The past period is controlled using the parameter `delta_forecast`. A second method (`mlforecaster`)
+a future period will be equal to the observed values in a past period. The past period is controlled using the parameter `delta_forecast_daily`. A second method (`mlforecaster`)
 uses an internal custom forecasting model using machine learning. There is a section in the documentation explaining how to use this method.
     
 ```{note} 
@@ -93,7 +93,7 @@ If you use the Solar.Forecast or Solcast methods, or explicitly pass the PV powe
 
 ## Load power forecast
 
-The default method for load forecast is a naive method, also called persistence. This is obtained using `method=naive`. This method simply assumes that the forecast for a future period will be equal to the observed values in a past period. The past period is controlled using the parameter `delta_forecast` and the default value for this is 24h.
+The default method for load forecast is a naive method, also called persistence. This is obtained using `method=naive`. This method simply assumes that the forecast for a future period will be equal to the observed values in a past period. The past period is controlled using the parameter `delta_forecast_daily` and the default value for this is 24h.
 
 This is presented graphically here:
 
@@ -126,15 +126,15 @@ When using this method you can provide a list of peak-hour periods, so you can a
 
 As an example for a two peak-hour periods contract you will need to define the following list in the configuration file:
 
-    - list_hp_periods:
+    - load_peak_hour_periods:
         - period_hp_1:
             - start: '02:54'
             - end: '15:24'
         - period_hp_2:
             - start: '17:24'
             - end: '20:24'
-    - load_cost_hp: 0.1907
-    - load_cost_hc: 0.1419
+    - load_peak_hours_cost: 0.1907
+    - load_offpeak_hours_cost: 0.1419
 
 This example is presented graphically here:
 
@@ -144,7 +144,7 @@ This example is presented graphically here:
 
 The default method for this forecast is simply a constant value. This can be obtained using `method=constant`.
 
-Then you will need to define the `prod_sell_price` variable to provide the correct price for energy injected to the grid from excedent PV production in €/kWh.
+Then you will need to define the `photovoltaic_production_sell_price` variable to provide the correct price for energy injected to the grid from excedent PV production in €/kWh.
 
 ## Passing your own forecast data
 
@@ -169,7 +169,7 @@ The possible dictionary keys to pass data are:
 
 - `prod_price_forecast` for the PV production selling price forecast.
 
-For example, if using the add-on or the standalone docker installation you can pass this data as a list of values to the data dictionary during the `curl` POST:
+For example, if using the add-on or the docker method, you can pass this data as a list of values to the data dictionary during the `curl` POST:
 ```bash
 curl -i -H "Content-Type: application/json" -X POST -d '{"pv_power_forecast":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 70, 141.22, 246.18, 513.5, 753.27, 1049.89, 1797.93, 1697.3, 3078.93, 1164.33, 1046.68, 1559.1, 2091.26, 1556.76, 1166.73, 1516.63, 1391.13, 1720.13, 820.75, 804.41, 251.63, 79.25, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]}' http://localhost:5000/action/dayahead-optim
 ```
@@ -220,7 +220,7 @@ An MPC call may look like this for 4 deferrable loads:
           state_attr('sensor.amber_feed_in_forecast', 'forecasts')|map(attribute='per_kwh')|list)[:48]) 
           }}, \"pv_power_forecast\":{{states('sensor.solcast_24hrs_forecast')
           }}, \"prediction_horizon\":48,\"soc_init\":{{(states('sensor.powerwall_charge')|float(0))/100
-          }},\"soc_final\":0.05,\"def_total_hours\":[2,0,0,0]}' http://localhost:5000/action/naive-mpc-optim"
+          }},\"soc_final\":0.05,\"operating_hours_of_each_deferrable_load\":[2,0,0,0]}' http://localhost:5000/action/naive-mpc-optim"
 ```
 
 Thanks to [@purcell_labs](https://github.com/purcell-lab) for this example configuration.
@@ -317,7 +317,7 @@ shell_command:
 
 ## Now/current values in forecasts
 
-When implementing MPC applications with high optimization frequencies it can be interesting if, at each MPC iteration, the forecast values are updated with the real now/current values measured from live data. This is useful to improve the accuracy of the short-term forecasts. As shown in some of the references below, mixing with a persistence model makes sense since this type of model performs very well at low temporal resolutions (intra-hour).
+When implementing MPC applications with high optimization_time_step it can be interesting if, at each MPC iteration, the forecast values are updated with the real now/current values measured from live data. This is useful to improve the accuracy of the short-term forecasts. As shown in some of the references below, mixing with a persistence model makes sense since this type of model performs very well at low temporal resolutions (intra-hour).
 
 A simple integration of current/now values for PV and load forecast is implemented using a mixed one-observation persistence model and the one-step-ahead forecasted values from the current passed method. 
 
diff --git a/docs/lpems.md b/docs/lpems.md
index 4c56eb29..03b86a0e 100644
--- a/docs/lpems.md
+++ b/docs/lpems.md
@@ -218,16 +218,16 @@ When applying this controller, the following `runtimeparams` should be defined:
 
 - `soc_final` for the final value of the battery SOC for the current iteration of the MPC. 
 
-- `def_total_hours` for the list of deferrable loads functioning hours. These values can decrease as the day advances to take into account receding horizon daily energy objectives for each deferrable load.
+- `operating_hours_of_each_deferrable_load` for the list of deferrable loads functioning hours. These values can decrease as the day advances to take into account receding horizon daily energy objectives for each deferrable load.
 
-- `def_start_timestep` for the timestep as from which each deferrable load is allowed to operate (if you don't want the deferrable load to use the whole optimization timewindow). If you specify a value of 0 (or negative), the deferrable load will be optimized as from the beginning of the complete prediction horizon window.
+- `start_timesteps_of_each_deferrable_load` for the timestep as from which each deferrable load is allowed to operate (if you don't want the deferrable load to use the whole optimization timewindow). If you specify a value of 0 (or negative), the deferrable load will be optimized as from the beginning of the complete prediction horizon window.
 
-- `def_end_timestep` for the timestep before which each deferrable load should operate (if you don't want the deferrable load to use the whole optimization timewindow). If you specify a value of 0 (or negative), the deferrable load will be optimized over the complete prediction horizon window.
+- `end_timesteps_of_each_deferrable_load` for the timestep before which each deferrable load should operate (if you don't want the deferrable load to use the whole optimization timewindow). If you specify a value of 0 (or negative), the deferrable load will be optimized over the complete prediction horizon window.
 
 In a practical use case, the values for `soc_init` and `soc_final` for each MPC optimization can be taken from the initial day-ahead optimization performed at the beginning of each day.
 
 ### Time windows for deferrable loads
-Since v0.7.0, the user has the possibility to limit the operation of each deferrable load to a specific timewindow, which can be smaller than the prediction horizon. This is done by means of the `def_start_timestep` and `def_end_timestep` parameters. These parameters can either be set in the configuration screen of the Home Assistant EMHASS add-on, or in the config_emhass.yaml file, or provided as runtime parameters.
+Since v0.7.0, the user has the possibility to limit the operation of each deferrable load to a specific timewindow, which can be smaller than the prediction horizon. This is done by means of the `start_timesteps_of_each_deferrable_load` and `end_timesteps_of_each_deferrable_load` parameters. These parameters can either be set in the configuration screen of the Home Assistant EMHASS add-on, or in the config_emhass.yaml file, or provided as runtime parameters.
 
 Take the example of two electric vehicles that need to charge, but which are not available during the whole prediction horizon:
 ![image](./images/deferrable_timewindow_evexample.png)
@@ -238,7 +238,7 @@ Either in the Home Assistant add-on config screen:
 
 Either as runtime parameter:
 ```
-curl -i -H 'Content-Type:application/json' -X POST -d '{"prediction_horizon":30, "def_total_hours":[4,2],"def_start_timestep":[4,0],"def_end_timestep":[27,23]}' http://localhost:5000/action/naive-mpc-optim
+curl -i -H 'Content-Type:application/json' -X POST -d '{"prediction_horizon":30, 'operating_hours_of_each_deferrable_load':[4,2],'start_timesteps_of_each_deferrable_load':[4,0],'end_timesteps_of_each_deferrable_load':[27,23]}' http://localhost:5000/action/naive-mpc-optim
 ```
 
 Please note that the proposed deferrable load time windows will be submitted to a validation step & can be automatically corrected.
diff --git a/docs/mlforecaster.md b/docs/mlforecaster.md
index 5933bae8..e3ae35bc 100644
--- a/docs/mlforecaster.md
+++ b/docs/mlforecaster.md
@@ -16,10 +16,10 @@ To train a model use the `forecast-model-fit` end point.
 
 Some parameters can be optionally defined at runtime:
 
-- `days_to_retrieve`: the total days to retrieve from Home Assistant for model training. Define this to retrieve as much history data as possible.
+- `historic_days_to_retrieve`: the total days to retrieve from Home Assistant for model training. Define this to retrieve as much history data as possible.
 
 ```{note}
-The minimum number of `days_to_retrieve` is hard coded to 9 by default. However, it is advised to provide more data for better accuracy by modifying your Home Assistant recorder settings. 
+The minimum number of `historic_days_to_retrieve` is hard coded to 9 by default. However, it is advised to provide more data for better accuracy by modifying your Home Assistant recorder settings. 
 ```
 
 - `model_type`: define the type of model forecast that this will be used for. For example: `load_forecast`. This should be a unique name if you are using multiple custom forecast models.
@@ -37,7 +37,7 @@ The minimum number of `days_to_retrieve` is hard coded to 9 by default. However,
 The default values for these parameters are:
 ```yaml
 runtimeparams = {
-    "days_to_retrieve": 9,
+    'historic_days_to_retrieve': 9,
     "model_type": "load_forecast",
     "var_model": "sensor.power_load_no_var_loads",
     "sklearn_model": "KNeighborsRegressor",
@@ -147,7 +147,7 @@ The hyperparameter tuning using Bayesian optimization improves the bare KNN regr
 
 ```{warning} 
 
-The tuning routine can be computing intense. If you have problems with computation times, try to reduce the `days_to_retrieve` parameter. In the example shown, for a 240-day train period, the optimization routine took almost 20 min to finish on an amd64 Linux architecture machine with an i5 processor and 8 GB of RAM. This is a task that should be performed once in a while, for example, every week.
+The tuning routine can be computing intense. If you have problems with computation times, try to reduce the `historic_days_to_retrieve` parameter. In the example shown, for a 240-day train period, the optimization routine took almost 20 min to finish on an amd64 Linux architecture machine with an i5 processor and 8 GB of RAM. This is a task that should be performed once in a while, for example, every week.
 ```
 
 ## How does this work? 
diff --git a/docs/mlregressor.md b/docs/mlregressor.md
index 620c564e..defd5dd7 100644
--- a/docs/mlregressor.md
+++ b/docs/mlregressor.md
@@ -143,16 +143,16 @@ The predict method will publish the result to a Home Assistant sensor.
 
 ## Storing CSV files  
 
-### Standalone container - how to mount a .csv files in data_path folder
-If running EMHASS as a standalone container, you will need to volume mount a folder to be the `data_path`, or mount a single .csv file inside `data_path`
+### Docker container - how to mount a .csv files in data_path folder
+If running EMHASS with the Docker method, you will need to volume mount a folder to be the `data_path`, or mount a single .csv file inside `data_path`
 
 Example of mounting a folder as data_path *(.csv files stored inside)*
 ```bash
-docker run -it --restart always -p 5000:5000 -e LOCAL_COSTFUN="profit" -v $(pwd)/data:/app/data -v $(pwd)/config_emhass.yaml:/app/config_emhass.yaml -v $(pwd)/secrets_emhass.yaml:/app/secrets_emhass.yaml --name DockerEMHASS <REPOSITORY:TAG>
+docker run -it --restart always -p 5000:5000 -e LOCAL_COSTFUN="profit" -v ./data:/app/data -v ./config_emhass.yaml:/app/config_emhass.yaml -v ./secrets_emhass.yaml:/app/secrets_emhass.yaml --name DockerEMHASS <REPOSITORY:TAG>
 ```
 Example of mounting a single CSV file
 ```bash
-docker run -it --restart always -p 5000:5000 -e LOCAL_COSTFUN="profit" -v $(pwd)/data/heating_prediction.csv:/app/data/heating_prediction.csv -v $(pwd)/config_emhass.yaml:/app/config_emhass.yaml -v $(pwd)/secrets_emhass.yaml:/app/secrets_emhass.yaml --name DockerEMHASS <REPOSITORY:TAG>
+docker run -it --restart always -p 5000:5000 -e LOCAL_COSTFUN="profit" -v ./data/heating_prediction.csv:/app/data/heating_prediction.csv -v ./config_emhass.yaml:/app/config_emhass.yaml -v ./secrets_emhass.yaml:/app/secrets_emhass.yaml --name DockerEMHASS <REPOSITORY:TAG>
 ```
 
 ### Add-on - How to store data in a CSV file from Home Assistant
diff --git a/docs/study_case.md b/docs/study_case.md
index 26de4f78..4d73488b 100644
--- a/docs/study_case.md
+++ b/docs/study_case.md
@@ -86,7 +86,7 @@ For this system, the total value of the obtained cost function is -1.23 EUR, a s
 
 As we showed in the forecast module section, we can pass our own forecast data using lists of values passed at runtime using templates. However, it is possible to also pass other data during runtime to automate energy management.
 
-For example, let's suppose that for the default configuration with two deferrable loads, we want to correlate and control them to the outside temperature. This will be used to build a list of the total number of hours for each deferrable load (`def_total_hours`). In this example, the first deferrable load is a water heater and the second is the pool pump.
+For example, let's suppose that for the default configuration with two deferrable loads, we want to correlate and control them to the outside temperature. This will be used to build a list of the total number of hours for each deferrable load (`operating_hours_of_each_deferrable_load`). In this example, the first deferrable load is a water heater and the second is the pool pump.
 
 We will begin by defining a temperature sensor on a 12 hours sliding window using the filter platform for the outside temperature:
 ```
@@ -121,7 +121,7 @@ The values for the total number of operating hours were tuned by trial and error
 Finally, my two shell commands for EMHASS will look like this:
 ```
 shell_command:
-  dayahead_optim: "curl -i -H \"Content-Type: application/json\" -X POST -d '{\"def_total_hours\":{{states('sensor.list_operating_hours_of_each_deferrable_load')}}}' http://localhost:5000/action/dayahead-optim"
+  dayahead_optim: "curl -i -H \"Content-Type: application/json\" -X POST -d '{\"operating_hours_of_each_deferrable_load\":{{states('sensor.list_operating_hours_of_each_deferrable_load')}}}' http://localhost:5000/action/dayahead-optim"
   publish_data: "curl -i -H \"Content-Type: application/json\" -X POST -d '{}' http://localhost:5000/action/publish-data"
 ```
 The dedicated automation for these shell commands can be for example:
diff --git a/options.json b/options.json
index 01f83b52..6c4686ce 100644
--- a/options.json
+++ b/options.json
@@ -1,156 +1,13 @@
 {
   "hass_url": "empty",
   "long_lived_token": "empty",
-  "logging_level": "INFO",
-  "costfun": "profit",
-  "optimization_time_step": 30,
-  "historic_days_to_retrieve": 2,
-  "method_ts_round": "nearest",
-  "continual_publish": false,
-  "optional_solcast_api_key": "empty",
-  "optional_solcast_rooftop_id": "empty",
-  "optional_solar_forecast_kwp": 5,
-  "data_path": "default",
-  "set_total_pv_sell": false,
-  "lp_solver": "COIN_CMD",
-  "lp_solver_path": "/usr/bin/cbc",
-  "set_nocharge_from_grid": false,
-  "set_nodischarge_to_grid": true,
-  "set_battery_dynamic": false,
-  "battery_dynamic_max": 0.9,
-  "battery_dynamic_min": -0.9,
-  "weight_battery_discharge": 1.0,
-  "weight_battery_charge": 1.0,
-  "sensor_power_photovoltaics": "sensor.power_photovoltaics",
-  "sensor_power_load_no_var_loads": "sensor.power_load_no_var_loads",
-  "load_negative": false,
-  "set_zero_min": true,
-  "number_of_deferrable_loads": 2,
-  "list_nominal_power_of_deferrable_loads": [
-    {
-      "nominal_power_of_deferrable_loads": 3000
-    },
-    {
-      "nominal_power_of_deferrable_loads": 750
-    }
-  ],
-  "list_operating_hours_of_each_deferrable_load": [
-    {
-      "operating_hours_of_each_deferrable_load": 4
-    },
-    {
-      "operating_hours_of_each_deferrable_load": 0
-    }
-  ],
-  "weather_forecast_method": "scrapper",
+  "solcast_api_key": "empty",
+  "solcast_rooftop_id": "empty",
+  "solar_forecast_kwp": 0,
   "time_zone": "Europe/Paris",
-  "Latitude": 45.83,
-  "Longitude": 6.86,
-  "Altitude": 4807.8,
-  "load_forecast_method": "naive",
-  "delta_forecast_daily": 1,
-  "load_cost_forecast_method": "hp_hc_periods",
-  "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:54"
-    }
-  ],  
-  "list_treat_deferrable_load_as_semi_cont": [
-    {
-      "treat_deferrable_load_as_semi_cont": true
-    },
-    {
-      "treat_deferrable_load_as_semi_cont": true
-    }
-  ],
-  "list_set_deferrable_load_single_constant": [
-    {
-      "set_deferrable_load_single_constant": false
-    },
-    {
-      "set_deferrable_load_single_constant": false
-    }
-  ],
-  "list_set_deferrable_startup_penalty": [
-    {
-      "set_deferrable_startup_penalty": 0.0
-    },
-    {
-      "set_deferrable_startup_penalty": 0.0
-    }
-  ],
-  "load_peak_hours_cost": 0.1907,
-  "load_offpeak_hours_cost": 0.1419,
-  "production_price_forecast_method": "constant",
-  "photovoltaic_production_sell_price": 0.1419,
-  "maximum_power_from_grid": 9000,
-  "maximum_power_to_grid": 9000,
-  "list_pv_module_model": [
-    {
-      "pv_module_model": "CSUN_Eurasia_Energy_Systems_Industry_and_Trade_CSUN295_60M"
-    }
-  ],
-  "list_pv_inverter_model": [
-    {
-      "pv_inverter_model": "Fronius_International_GmbH__Fronius_Primo_5_0_1_208_240__240V_"
-    }
-  ],
-  "list_surface_tilt": [
-    {
-      "surface_tilt": 30
-    }
-  ],
-  "list_surface_azimuth": [
-    {
-      "surface_azimuth": 205
-    }
-  ],
-  "list_modules_per_string": [
-    {
-      "modules_per_string": 16
-    }
-  ],
-  "list_strings_per_inverter": [
-    {
-      "strings_per_inverter": 1
-    }
-  ],
-  "inverter_is_hybrid": false,
-  "compute_curtailment": false,
-  "set_use_battery": false,
-  "battery_discharge_power_max": 1000,
-  "battery_charge_power_max": 1000,
-  "battery_discharge_efficiency": 0.95,
-  "battery_charge_efficiency": 0.95,
-  "battery_nominal_energy_capacity": 5000,
-  "battery_minimum_state_of_charge": 0.3,
-  "battery_maximum_state_of_charge": 0.9,
-  "battery_target_state_of_charge": 0.6
-}
+  "Latitude": 0,
+  "Longitude": 0,
+  "Altitude": 0,
+  "data_path": "default",
+  "server_ip": "0.0.0.0"
+}
\ No newline at end of file
diff --git a/scripts/load_clustering.py b/scripts/load_clustering.py
index db92ea2e..f627d8f1 100644
--- a/scripts/load_clustering.py
+++ b/scripts/load_clustering.py
@@ -11,7 +11,7 @@
 
 from emhass.retrieve_hass import RetrieveHass
 from emhass.forecast import Forecast
-from emhass.utils import get_root, get_yaml_parse, get_days_list, get_logger
+from emhass.utils import get_root, get_yaml_parse, get_days_list, get_logger, build_secrets, build_params
 
 from sklearn.cluster import KMeans
 from sklearn.linear_model import LinearRegression
@@ -33,11 +33,13 @@
 
 
 # the root folder
-root = str(get_root(__file__, num_parent=2))
+root = pathlib.Path(str(get_root(__file__, num_parent=2)))
 emhass_conf = {}
-emhass_conf['config_path'] = pathlib.Path(root) / 'config_emhass.yaml'
-emhass_conf['data_path'] = pathlib.Path(root) / 'data/'
-emhass_conf['root_path'] = pathlib.Path(root)
+emhass_conf['data_path'] = root / 'data/'
+emhass_conf['root_path'] = root / 'src/emhass/'
+emhass_conf['config_path'] = root / 'config.json'
+emhass_conf['defaults_path'] = emhass_conf['root_path']  / 'data/config_defaults.json'
+emhass_conf['associations_path'] = emhass_conf['root_path']  / 'data/associations.csv'
 
 # create logger
 logger, ch = get_logger(__name__, emhass_conf, save_to_file=True)
@@ -47,9 +49,11 @@
     days_to_retrieve = 240
     model_type = "load_clustering"
     var_model = "sensor.power_load_positive"
-    
+
+    # Build params with no config and default secrets
     data_path = emhass_conf['data_path'] / str('data_train_'+model_type+'.pkl')
-    params = None
+    _,secrets = build_secrets(emhass_conf,logger,no_response=True)
+    params =  build_params(emhass_conf,secrets,{},logger)
     template = 'presentation'
 
     if data_path.is_file():
@@ -58,9 +62,9 @@
             data, var_model = pickle.load(fid)
     else:
         logger.info("Using EMHASS methods to retrieve the new forecast model train data")
-        retrieve_hass_conf, _, _ = get_yaml_parse(emhass_conf, use_secrets=True)
+        retrieve_hass_conf, _, _ = get_yaml_parse(params,logger)
         rh = RetrieveHass(retrieve_hass_conf['hass_url'], retrieve_hass_conf['long_lived_token'], 
-        retrieve_hass_conf['freq'], retrieve_hass_conf['time_zone'],
+        retrieve_hass_conf['optimization_time_step'], retrieve_hass_conf['time_zone'],
         params, emhass_conf, logger, get_data_from_file=False)
 
         days_list = get_days_list(days_to_retrieve)
diff --git a/scripts/load_forecast_sklearn.py b/scripts/load_forecast_sklearn.py
index 82f5b3b3..8e09215d 100644
--- a/scripts/load_forecast_sklearn.py
+++ b/scripts/load_forecast_sklearn.py
@@ -10,7 +10,7 @@
 
 from emhass.retrieve_hass import RetrieveHass
 from emhass.forecast import Forecast
-from emhass.utils import get_root, get_yaml_parse, get_days_list, get_logger
+from emhass.utils import get_root, get_yaml_parse, get_days_list, get_logger, build_secrets, build_params
 
 from sklearn.linear_model import LinearRegression
 from sklearn.linear_model import ElasticNet
@@ -26,16 +26,18 @@
 
 
 # the root folder
-root = str(get_root(__file__, num_parent=2))
+root = pathlib.Path(str(get_root(__file__, num_parent=2)))
 emhass_conf = {}
-emhass_conf['config_path'] = pathlib.Path(root) / 'config_emhass.yaml'
-emhass_conf['data_path'] = pathlib.Path(root) / 'data/'
-emhass_conf['root_path'] = pathlib.Path(root)
+emhass_conf['data_path'] = root / 'data/'
+emhass_conf['docs_path'] = root / 'docs/'
+emhass_conf['root_path'] = root / 'src/emhass/'
+emhass_conf['config_path'] = root / 'config.json'
+emhass_conf['defaults_path'] = emhass_conf['root_path']  / 'data/config_defaults.json'
+emhass_conf['associations_path'] = emhass_conf['root_path']  / 'data/associations.csv'
 
 # create logger
 logger, ch = get_logger(__name__, emhass_conf, save_to_file=True)
 
-
 def add_date_features(data):
     df = copy.deepcopy(data)
     df['year'] = [i.year for i in df.index]
@@ -57,8 +59,10 @@ def neg_r2_score(y_true, y_pred):
     sklearn_model = "KNeighborsRegressor"
     num_lags = 48
     
+    # Build params with no config and default secrets
     data_path = emhass_conf['data_path'] / str('data_train_'+model_type+'.pkl')
-    params = None
+    _,secrets = build_secrets(emhass_conf,logger,no_response=True)
+    params =  build_params(emhass_conf,secrets,{},logger)
     template = 'presentation'
 
     if data_path.is_file():
@@ -67,9 +71,9 @@ def neg_r2_score(y_true, y_pred):
             data, var_model = pickle.load(fid)
     else:
         logger.info("Using EMHASS methods to retrieve the new forecast model train data")
-        retrieve_hass_conf, _, _ = get_yaml_parse(emhass_conf, use_secrets=True)
+        retrieve_hass_conf, _, _ = get_yaml_parse(params,logger)
         rh = RetrieveHass(retrieve_hass_conf['hass_url'], retrieve_hass_conf['long_lived_token'], 
-        retrieve_hass_conf['freq'], retrieve_hass_conf['time_zone'],
+        retrieve_hass_conf['optimization_time_step'], retrieve_hass_conf['time_zone'],
         params, emhass_conf, logger, get_data_from_file=False)
 
         days_list = get_days_list(days_to_retrieve)
@@ -87,7 +91,7 @@ def neg_r2_score(y_true, y_pred):
     fig.update_yaxes(title_text = "Power (W)")
     fig.update_xaxes(title_text = "Time")
     fig.show()
-    fig.write_image(emhass_conf['root_path'] / "docs/images/inputs_power_load_forecast.svg", width=1080, height=0.8*1080)
+    fig.write_image(emhass_conf['docs_path'] / "images/inputs_power_load_forecast.svg", width=1080, height=0.8*1080)
 
     data.index = pd.to_datetime(data.index)
     data.sort_index(inplace=True)
@@ -140,7 +144,7 @@ def neg_r2_score(y_true, y_pred):
     fig.update_xaxes(title_text = "Time")
     fig.update_xaxes(range=[date_train+pd.Timedelta('10days'), data_exo.index[-1]])
     fig.show()
-    fig.write_image(emhass_conf['root_path'] / "docs/images/load_forecast_knn_bare.svg", width=1080, height=0.8*1080)
+    fig.write_image(emhass_conf['docs_path'] / "images/load_forecast_knn_bare.svg", width=1080, height=0.8*1080)
     
     logger.info("Simple backtesting")
     start_time = time.time()
@@ -166,7 +170,7 @@ def neg_r2_score(y_true, y_pred):
     fig.update_yaxes(title_text = "Power (W)")
     fig.update_xaxes(title_text = "Time")
     fig.show()
-    fig.write_image(emhass_conf['root_path'] / "docs/images/load_forecast_knn_bare_backtest.svg", width=1080, height=0.8*1080)
+    fig.write_image(emhass_conf['docs_path'] / "images/load_forecast_knn_bare_backtest.svg", width=1080, height=0.8*1080)
     
     # Bayesian search hyperparameter and lags with Skopt
     
@@ -224,7 +228,7 @@ def neg_r2_score(y_true, y_pred):
     fig.update_xaxes(title_text = "Time")
     fig.update_xaxes(range=[date_train+pd.Timedelta('10days'), data_exo.index[-1]])
     fig.show()
-    fig.write_image(emhass_conf['root_path'] / "docs/images/load_forecast_knn_optimized.svg", width=1080, height=0.8*1080)
+    fig.write_image(emhass_conf['docs_path'] / "images/load_forecast_knn_optimized.svg", width=1080, height=0.8*1080)
     
     logger.info("######################## Train/Test R2 score comparison ######################## ")
     pred_naive_metric_train = r2_score(df.loc[data_train.index,'train'],df.loc[data_train.index,'pred_naive'])
@@ -251,11 +255,11 @@ def neg_r2_score(y_true, y_pred):
     
     # Then retrieve some data and perform a prediction mocking a production env
     rh = RetrieveHass(retrieve_hass_conf['hass_url'], retrieve_hass_conf['long_lived_token'], 
-                      retrieve_hass_conf['freq'], retrieve_hass_conf['time_zone'],
+                      retrieve_hass_conf['optimization_time_step'], retrieve_hass_conf['time_zone'],
                       params, emhass_conf, logger, get_data_from_file=False)
 
     days_list = get_days_list(days_needed)
-    var_model = retrieve_hass_conf['var_load']
+    var_model = retrieve_hass_conf['sensor_power_load_no_var_loads']
     var_list = [var_model]
     rh.get_data(days_list, var_list)
     data_last_window = copy.deepcopy(rh.df_final)
@@ -275,4 +279,4 @@ def neg_r2_score(y_true, y_pred):
     fig.update_yaxes(title_text = "Power (W)")
     fig.update_xaxes(title_text = "Time")
     fig.show()
-    fig.write_image(emhass_conf['root_path'] / "docs/images/load_forecast_production.svg", width=1080, height=0.8*1080)
\ No newline at end of file
+    fig.write_image(emhass_conf['docs_path'] / "images/load_forecast_production.svg", width=1080, height=0.8*1080)
\ No newline at end of file
diff --git a/scripts/optim_results_analysis.py b/scripts/optim_results_analysis.py
index 173c7045..07909805 100644
--- a/scripts/optim_results_analysis.py
+++ b/scripts/optim_results_analysis.py
@@ -1,4 +1,5 @@
 # -*- coding: utf-8 -*-
+import json
 import pickle
 import numpy as np
 import pandas as pd
@@ -12,20 +13,24 @@
 from emhass.retrieve_hass import RetrieveHass
 from emhass.optimization import Optimization
 from emhass.forecast import Forecast
-from emhass.utils import get_root, get_yaml_parse, get_days_list, get_logger
+from emhass.utils import get_root, get_yaml_parse, get_days_list, get_logger, build_config, build_secrets, build_params
 
 # the root folder
-root = str(get_root(__file__, num_parent=2))
+root = pathlib.Path(str(get_root(__file__, num_parent=2)))
 emhass_conf = {}
-emhass_conf['config_path'] = pathlib.Path(root) / 'config_emhass.yaml'
-emhass_conf['data_path'] = pathlib.Path(root) / 'data/'
-emhass_conf['root_path'] = pathlib.Path(root)
+emhass_conf['data_path'] = root / 'data/'
+emhass_conf['root_path'] = root / 'src/emhass/'
+emhass_conf['docs_path'] = root / 'docs/'
+emhass_conf['config_path'] = root / 'config.json'
+emhass_conf['defaults_path'] = emhass_conf['root_path']  / 'data/config_defaults.json'
+emhass_conf['associations_path'] = emhass_conf['root_path']  / 'data/associations.csv'
 
 # create logger
 logger, ch = get_logger(__name__, emhass_conf, save_to_file=False)
 
 def get_forecast_optim_objects(retrieve_hass_conf, optim_conf, plant_conf,
                                params, get_data_from_file):
+
     fcst = Forecast(retrieve_hass_conf, optim_conf, plant_conf,
                     params, emhass_conf, logger, get_data_from_file=get_data_from_file)
     df_weather = fcst.get_weather_forecast(method=optim_conf['weather_forecast_method'])
@@ -43,32 +48,35 @@ def get_forecast_optim_objects(retrieve_hass_conf, optim_conf, plant_conf,
     save_figures = False
     save_html = False
     get_data_from_file = True
-    params = None
-    if get_data_from_file:
-        retrieve_hass_conf, optim_conf, plant_conf = get_yaml_parse(emhass_conf, use_secrets=False)
-    else:
-        retrieve_hass_conf, optim_conf, plant_conf = get_yaml_parse(emhass_conf)
+    # Build params with default config and default secrets
+    config = build_config(emhass_conf,logger,emhass_conf['defaults_path'])
+    _,secrets = build_secrets(emhass_conf,logger,no_response=True)
+    params =  build_params(emhass_conf,secrets,config,logger)
+    # if get_data_from_file:
+    #     retrieve_hass_conf, optim_conf, plant_conf = get_yaml_parse({},logger)
+    # else:
+    retrieve_hass_conf, optim_conf, plant_conf = get_yaml_parse(params,logger)
     retrieve_hass_conf, optim_conf, plant_conf = \
         retrieve_hass_conf, optim_conf, plant_conf
     rh = RetrieveHass(retrieve_hass_conf['hass_url'], retrieve_hass_conf['long_lived_token'], 
-                      retrieve_hass_conf['freq'], retrieve_hass_conf['time_zone'],
+                      retrieve_hass_conf['optimization_time_step'], retrieve_hass_conf['time_zone'],
                       params, emhass_conf, logger)
     if get_data_from_file:
         with open(pathlib.Path(emhass_conf['data_path'] / 'test_df_final.pkl'), 'rb') as inp:
             rh.df_final, days_list, var_list = pickle.load(inp)
-        retrieve_hass_conf['var_load'] = str(var_list[0])
-        retrieve_hass_conf['var_PV'] = str(var_list[1])
-        retrieve_hass_conf['var_interp'] = [retrieve_hass_conf['var_PV'], retrieve_hass_conf['var_load']]
-        retrieve_hass_conf['var_replace_zero'] = [retrieve_hass_conf['var_PV']]
+        retrieve_hass_conf['sensor_power_load_no_var_loads'] = str(var_list[0])
+        retrieve_hass_conf['sensor_power_photovoltaics'] = str(var_list[1])
+        retrieve_hass_conf['sensor_linear_interp'] = [retrieve_hass_conf['sensor_power_photovoltaics'], retrieve_hass_conf['sensor_power_load_no_var_loads']]
+        retrieve_hass_conf['sensor_replace_zero'] = [retrieve_hass_conf['sensor_power_photovoltaics']]
     else:
-        days_list = get_days_list(retrieve_hass_conf['days_to_retrieve'])
-        var_list = [retrieve_hass_conf['var_load'], retrieve_hass_conf['var_PV']]
+        days_list = get_days_list(retrieve_hass_conf['historic_days_to_retrieve'])
+        var_list = [retrieve_hass_conf['sensor_power_load_no_var_loads'], retrieve_hass_conf['sensor_power_photovoltaics']]
         rh.get_data(days_list, var_list,
                     minimal_response=False, significant_changes_only=False)
-    rh.prepare_data(retrieve_hass_conf['var_load'], load_negative = retrieve_hass_conf['load_negative'],
+    rh.prepare_data(retrieve_hass_conf['sensor_power_load_no_var_loads'], load_negative = retrieve_hass_conf['load_negative'],
                     set_zero_min = retrieve_hass_conf['set_zero_min'], 
-                    var_replace_zero = retrieve_hass_conf['var_replace_zero'], 
-                    var_interp = retrieve_hass_conf['var_interp'])
+                    var_replace_zero = retrieve_hass_conf['sensor_replace_zero'], 
+                    var_interp = retrieve_hass_conf['sensor_linear_interp'])
     df_input_data = rh.df_final.copy()
     
     fcst, P_PV_forecast, P_load_forecast, df_input_data_dayahead, opt = \
@@ -80,15 +88,15 @@ def get_forecast_optim_objects(retrieve_hass_conf, optim_conf, plant_conf,
     template = 'presentation'
     
     # Let's plot the input data
-    fig_inputs1 = df_input_data[[retrieve_hass_conf['var_PV'],
-                                 str(retrieve_hass_conf['var_load'] + '_positive')]].plot()
+    fig_inputs1 = df_input_data[[retrieve_hass_conf['sensor_power_photovoltaics'],
+                                 str(retrieve_hass_conf['sensor_power_load_no_var_loads'] + '_positive')]].plot()
     fig_inputs1.layout.template = template
     fig_inputs1.update_yaxes(title_text = "Powers (W)")
     fig_inputs1.update_xaxes(title_text = "Time")
     if show_figures:
         fig_inputs1.show()
     if save_figures:
-        fig_inputs1.write_image(emhass_conf['root_path'] / "docs/images/inputs_power.svg", 
+        fig_inputs1.write_image(emhass_conf['docs_path'] / "images/inputs_power.svg", 
                                 width=1080, height=0.8*1080)
     
     fig_inputs_dah = df_input_data_dayahead.plot()
@@ -98,14 +106,14 @@ def get_forecast_optim_objects(retrieve_hass_conf, optim_conf, plant_conf,
     if show_figures:
         fig_inputs_dah.show()
     if save_figures:
-        fig_inputs_dah.write_image(emhass_conf['root_path'] /  "docs/images/inputs_dayahead.svg", 
+        fig_inputs_dah.write_image(emhass_conf['docs_path'] /  "images/inputs_dayahead.svg", 
                                    width=1080, height=0.8*1080)
     
     # And then perform a dayahead optimization
     df_input_data_dayahead = fcst.get_load_cost_forecast(df_input_data_dayahead)
     df_input_data_dayahead = fcst.get_prod_price_forecast(df_input_data_dayahead)
-    optim_conf['treat_def_as_semi_cont'] = [True, True]
-    optim_conf['set_def_constant'] = [True, True]
+    optim_conf['treat_deferrable_load_as_semi_cont'] = [True, True]
+    optim_conf['set_deferrable_load_single_constant'] = [True, True]
     unit_load_cost = df_input_data[opt.var_load_cost].values
     unit_prod_price = df_input_data[opt.var_prod_price].values
     opt_res_dah = opt.perform_optimization(df_input_data_dayahead, P_PV_forecast.values.ravel(), 
@@ -122,7 +130,7 @@ def get_forecast_optim_objects(retrieve_hass_conf, optim_conf, plant_conf,
     # if show_figures:
     fig_res_dah.show()
     if save_figures:
-        fig_res_dah.write_image(emhass_conf['root_path'] / "docs/images/optim_results_PV_defLoads_dayaheadOptim.svg", 
+        fig_res_dah.write_image(emhass_conf['docs_path'] / "images/optim_results_PV_defLoads_dayaheadOptim.svg", 
                                 width=1080, height=0.8*1080)
     
     print("System with: PV, two deferrable loads, dayahead optimization, profit >> total cost function sum: "+\
diff --git a/scripts/read_csv_plot_data.py b/scripts/read_csv_plot_data.py
index 2b136bf3..2293725b 100644
--- a/scripts/read_csv_plot_data.py
+++ b/scripts/read_csv_plot_data.py
@@ -21,11 +21,14 @@
 if __name__ == '__main__':
 
     # the root folder
-    root = str(get_root(__file__, num_parent=2))
+    root = pathlib.Path(str(get_root(__file__, num_parent=2)))
     emhass_conf = {}
-    emhass_conf['config_path'] = pathlib.Path(root) / 'config_emhass.yaml'
-    emhass_conf['data_path'] = pathlib.Path(root) / 'data/'
-    emhass_conf['root_path'] = pathlib.Path(root)
+    emhass_conf['data_path'] = root / 'data/'
+    emhass_conf['root_path'] = root / 'src/emhass/'
+    emhass_conf['docs_path'] = root / 'docs/'
+    emhass_conf['config_path'] = root / 'config.json'
+    emhass_conf['defaults_path'] = emhass_conf['root_path']  / 'data/config_defaults.json'
+    emhass_conf['associations_path'] = emhass_conf['root_path']  / 'data/associations.csv'
 
     # create logger
     logger, ch = get_logger(__name__, emhass_conf, save_to_file=False)
@@ -137,7 +140,7 @@
     this_figure.show()
 
     if save_figs:
-        fig_filename = emhass_conf['root_path'] / "docs/images/optim_results"
+        fig_filename = emhass_conf['docs_path'] / "images/optim_results"
         this_figure.write_image(str(fig_filename) + ".png", width=1.5*768, height=1.5*1.5*768)
 
     fig_bar = px.bar(np.arange(len(cf)), x=[c+" (+"+"{:.2f}".format(np.sum(data['gain_'+c])*100/np.sum(
@@ -151,5 +154,5 @@
     fig_bar.show()
 
     if save_figs:
-        fig_filename = emhass_conf['root_path'] / "docs/images/optim_results_bar_plot"
+        fig_filename = emhass_conf['docs_path'] / "images/optim_results_bar_plot"
         fig_bar.write_image(str(fig_filename) + ".png", width=1080, height=0.8*1080)
diff --git a/scripts/requirements.txt b/scripts/requirements.txt
new file mode 100644
index 00000000..d6671a3a
--- /dev/null
+++ b/scripts/requirements.txt
@@ -0,0 +1,3 @@
+kaleido
+tslearn
+tabulate
diff --git a/scripts/save_pvlib_module_inverter_database.py b/scripts/save_pvlib_module_inverter_database.py
index f09a0448..9adfe900 100644
--- a/scripts/save_pvlib_module_inverter_database.py
+++ b/scripts/save_pvlib_module_inverter_database.py
@@ -20,11 +20,14 @@
 from emhass.utils import get_root, get_yaml_parse, get_days_list, get_logger
 
 # the root folder
-root = str(get_root(__file__, num_parent=2))
+root = pathlib.Path(str(get_root(__file__, num_parent=2)))
 emhass_conf = {}
-emhass_conf['config_path'] = pathlib.Path(root) / 'config_emhass.yaml'
-emhass_conf['data_path'] = pathlib.Path(root) / 'data/'
-emhass_conf['root_path'] = pathlib.Path(root)
+emhass_conf['data_path'] = root / 'data/'
+emhass_conf['root_path'] = root / 'src/emhass/'
+emhass_conf['docs_path'] = root / 'docs/'
+emhass_conf['config_path'] = root / 'config.json'
+emhass_conf['defaults_path'] = emhass_conf['root_path']  / 'data/config_defaults.json'
+emhass_conf['associations_path'] = emhass_conf['root_path']  / 'data/associations.csv'
 
 # create logger
 logger, ch = get_logger(__name__, emhass_conf, save_to_file=False)
@@ -52,9 +55,9 @@
     logger.info('Inverters databases')
     print(tabulate(cec_inverters.head(20).iloc[:,:3], headers='keys', tablefmt='psql'))
     if save_new_files:
-        with bz2.BZ2File(emhass_conf['root_path'] + '/src/emhass/data/cec_modules.pbz2', "w") as f: 
+        with bz2.BZ2File(emhass_conf['root_path'] + '/data/cec_modules.pbz2', "w") as f: 
             cPickle.dump(cec_modules, f)    
     if save_new_files:
-        with bz2.BZ2File(emhass_conf['root_path'] + '/src/emhass/data/cec_inverters.pbz2', "w") as f: 
+        with bz2.BZ2File(emhass_conf['root_path'] + '/data/cec_inverters.pbz2', "w") as f: 
             cPickle.dump(cec_inverters, f)
     
\ No newline at end of file
diff --git a/scripts/script_debug_forecasts.py b/scripts/script_debug_forecasts.py
index d2f377da..0525b759 100644
--- a/scripts/script_debug_forecasts.py
+++ b/scripts/script_debug_forecasts.py
@@ -1,4 +1,5 @@
 # -*- coding: utf-8 -*-
+import json
 import pickle
 import numpy as np
 import pandas as pd
@@ -10,14 +11,17 @@
 pd.options.plotting.backend = "plotly"
 
 from emhass.forecast import Forecast
-from emhass.utils import get_root, get_yaml_parse, get_days_list, get_logger
+from emhass.utils import get_root, get_yaml_parse, get_days_list, get_logger, build_config, build_secrets, build_params
 
 # the root folder
-root = str(get_root(__file__, num_parent=2))
+root = pathlib.Path(str(get_root(__file__, num_parent=2)))
 emhass_conf = {}
-emhass_conf['config_path'] = pathlib.Path(root) / 'config_emhass.yaml'
-emhass_conf['data_path'] = pathlib.Path(root) / 'data/'
-emhass_conf['root_path'] = pathlib.Path(root)
+emhass_conf['data_path'] = root / 'data/'
+emhass_conf['root_path'] = root / 'src/emhass/'
+emhass_conf['config_path'] = root / 'config.json'
+emhass_conf['secrets_path'] = root  / 'secrets_emhass.yaml'
+emhass_conf['defaults_path'] = emhass_conf['root_path']  / 'data/config_defaults.json'
+emhass_conf['associations_path'] = emhass_conf['root_path']  / 'data/associations.csv'
 
 # create logger
 logger, ch = get_logger(__name__, emhass_conf, save_to_file=False)
@@ -25,14 +29,19 @@
 if __name__ == '__main__':
 
     get_data_from_file = True
-    params = None
     template = 'presentation'
     
     methods_list = ['solar.forecast', 'solcast', 'scrapper'] # 
     
     for k, method in enumerate(methods_list):
-        retrieve_hass_conf, optim_conf, plant_conf = get_yaml_parse(emhass_conf)
-        optim_conf['delta_forecast'] = pd.Timedelta(days=2)
+        # Build params with default config, weather_forecast_method=method and default secrets
+        config = build_config(emhass_conf,logger,emhass_conf['defaults_path'])
+        config['weather_forecast_method'] = method
+        _,secrets = build_secrets(emhass_conf,logger,secrets_path=emhass_conf['secrets_path'],no_response=True)
+        params =  build_params(emhass_conf,secrets,config,logger)
+        
+        retrieve_hass_conf, optim_conf, plant_conf = get_yaml_parse(params,logger)
+        optim_conf['delta_forecast_daily'] = pd.Timedelta(days=2)
         fcst = Forecast(retrieve_hass_conf, optim_conf, plant_conf,
                         params, emhass_conf, logger, get_data_from_file=get_data_from_file)
         df_weather = fcst.get_weather_forecast(method=method)
diff --git a/scripts/script_debug_optim.py b/scripts/script_debug_optim.py
index 8ac42347..2ea31cd5 100644
--- a/scripts/script_debug_optim.py
+++ b/scripts/script_debug_optim.py
@@ -1,4 +1,5 @@
 # -*- coding: utf-8 -*-
+import json
 import pickle
 import numpy as np
 import pandas as pd
@@ -12,46 +13,50 @@
 from emhass.retrieve_hass import RetrieveHass
 from emhass.optimization import Optimization
 from emhass.forecast import Forecast
-from emhass.utils import get_root, get_yaml_parse, get_days_list, get_logger
+from emhass.utils import get_root, get_yaml_parse, get_days_list, get_logger, build_config, build_params
 
 # the root folder
-root = str(get_root(__file__, num_parent=2))
+root = pathlib.Path(str(get_root(__file__, num_parent=2)))
 emhass_conf = {}
-emhass_conf['config_path'] = pathlib.Path(root) / 'config_emhass.yaml'
-emhass_conf['data_path'] = pathlib.Path(root) / 'data/'
-emhass_conf['root_path'] = pathlib.Path(root)
+emhass_conf['data_path'] = root / 'data/'
+emhass_conf['root_path'] = root / 'src/emhass/'
+emhass_conf['config_path'] = root / 'config.json'
+emhass_conf['defaults_path'] = emhass_conf['root_path']  / 'data/config_defaults.json'
+emhass_conf['associations_path'] = emhass_conf['root_path']  / 'data/associations.csv'
 
 # create logger
 logger, ch = get_logger(__name__, emhass_conf, save_to_file=False)
 
 if __name__ == '__main__':
     get_data_from_file = True
-    params = None
     show_figures = True
     template = 'presentation'
     
-    retrieve_hass_conf, optim_conf, plant_conf = get_yaml_parse(emhass_conf, use_secrets=False)
+    # Build params with default config (no secrets) 
+    config = build_config(emhass_conf,logger,emhass_conf['defaults_path'])
+    params =  build_params(emhass_conf,{},config,logger)
+    retrieve_hass_conf, optim_conf, plant_conf = get_yaml_parse(params, logger)
     retrieve_hass_conf, optim_conf, plant_conf = \
         retrieve_hass_conf, optim_conf, plant_conf
     rh = RetrieveHass(retrieve_hass_conf['hass_url'], retrieve_hass_conf['long_lived_token'], 
-                            retrieve_hass_conf['freq'], retrieve_hass_conf['time_zone'],
+                            retrieve_hass_conf['optimization_time_step'], retrieve_hass_conf['time_zone'],
                             params, emhass_conf, logger)
     if get_data_from_file:
         with open(emhass_conf['data_path'] / 'test_df_final.pkl', 'rb') as inp:
             rh.df_final, days_list, var_list = pickle.load(inp)
-        retrieve_hass_conf['var_load'] = str(var_list[0])
-        retrieve_hass_conf['var_PV'] = str(var_list[1])
-        retrieve_hass_conf['var_interp'] = [retrieve_hass_conf['var_PV'], retrieve_hass_conf['var_load']]
-        retrieve_hass_conf['var_replace_zero'] = [retrieve_hass_conf['var_PV']]
+        retrieve_hass_conf['sensor_power_load_no_var_loads'] = str(var_list[0])
+        retrieve_hass_conf['sensor_power_photovoltaics'] = str(var_list[1])
+        retrieve_hass_conf['sensor_linear_interp'] = [retrieve_hass_conf['sensor_power_photovoltaics'], retrieve_hass_conf['sensor_power_load_no_var_loads']]
+        retrieve_hass_conf['sensor_replace_zero'] = [retrieve_hass_conf['sensor_power_photovoltaics']]
     else:
-        days_list = get_days_list(retrieve_hass_conf['days_to_retrieve'])
-        var_list = [retrieve_hass_conf['var_load'], retrieve_hass_conf['var_PV']]
+        days_list = get_days_list(retrieve_hass_conf['historic_days_to_retrieve'])
+        var_list = [retrieve_hass_conf['sensor_power_load_no_var_loads'], retrieve_hass_conf['sensor_power_photovoltaics']]
         rh.get_data(days_list, var_list,
                         minimal_response=False, significant_changes_only=False)
-    rh.prepare_data(retrieve_hass_conf['var_load'], load_negative = retrieve_hass_conf['load_negative'],
+    rh.prepare_data(retrieve_hass_conf['sensor_power_load_no_var_loads'], load_negative = retrieve_hass_conf['load_negative'],
                             set_zero_min = retrieve_hass_conf['set_zero_min'], 
-                            var_replace_zero = retrieve_hass_conf['var_replace_zero'], 
-                            var_interp = retrieve_hass_conf['var_interp'])
+                            var_replace_zero = retrieve_hass_conf['sensor_replace_zero'], 
+                            var_interp = retrieve_hass_conf['sensor_linear_interp'])
     df_input_data = rh.df_final.copy()
     
     fcst = Forecast(retrieve_hass_conf, optim_conf, plant_conf,
@@ -73,11 +78,11 @@
     optim_conf.update({'lp_solver_path': 'empty'})  # set the path to the LP solver, COIN_CMD default is /usr/bin/cbc
     
     # Semi continuous and constant values
-    optim_conf.update({'treat_def_as_semi_cont': [True, False]})
-    optim_conf.update({'set_def_constant': [True, False]})
+    optim_conf.update({'treat_deferrable_load_as_semi_cont': [True, False]})
+    optim_conf.update({'set_deferrable_load_single_constant': [True, False]})
     
     # A sequence of values
-    # optim_conf.update({'P_deferrable_nom': [[500.0, 100.0, 100.0, 500.0], 750.0]})
+    # optim_conf.update({'nominal_power_of_deferrable_loads': [[500.0, 100.0, 100.0, 500.0], 750.0]})
     
     # Using a battery
     optim_conf.update({'set_use_battery': False})
diff --git a/scripts/script_simple_thermal_model.py b/scripts/script_simple_thermal_model.py
index 2a94bac6..6b95d434 100644
--- a/scripts/script_simple_thermal_model.py
+++ b/scripts/script_simple_thermal_model.py
@@ -1,4 +1,5 @@
 # -*- coding: utf-8 -*-
+import json
 import pickle
 import random
 import numpy as np
@@ -13,46 +14,50 @@
 from emhass.retrieve_hass import RetrieveHass
 from emhass.optimization import Optimization
 from emhass.forecast import Forecast
-from emhass.utils import get_root, get_yaml_parse, get_days_list, get_logger
+from emhass.utils import get_root, get_yaml_parse, get_days_list, get_logger, build_config, build_params
 
 # the root folder
-root = str(get_root(__file__, num_parent=2))
+root = pathlib.Path(str(get_root(__file__, num_parent=2)))
 emhass_conf = {}
-emhass_conf['config_path'] = pathlib.Path(root) / 'config_emhass.yaml'
-emhass_conf['data_path'] = pathlib.Path(root) / 'data/'
-emhass_conf['root_path'] = pathlib.Path(root)
+emhass_conf['data_path'] = root / 'data/'
+emhass_conf['root_path'] = root / 'src/emhass/'
+emhass_conf['config_path'] = root / 'config.json'
+emhass_conf['defaults_path'] = emhass_conf['root_path']  / 'data/config_defaults.json'
+emhass_conf['associations_path'] = emhass_conf['root_path']  / 'data/associations.csv'
 
 # create logger
 logger, ch = get_logger(__name__, emhass_conf, save_to_file=False)
 
 if __name__ == '__main__':
     get_data_from_file = True
-    params = None
     show_figures = True
     template = 'presentation'
     
-    retrieve_hass_conf, optim_conf, plant_conf = get_yaml_parse(emhass_conf, use_secrets=False)
+    # Build params with default config (no secrets) 
+    config = build_config(emhass_conf,logger,emhass_conf['defaults_path'])
+    params =  build_params(emhass_conf,{},config,logger)
+    retrieve_hass_conf, optim_conf, plant_conf = get_yaml_parse(params, logger)
     retrieve_hass_conf, optim_conf, plant_conf = \
         retrieve_hass_conf, optim_conf, plant_conf
     rh = RetrieveHass(retrieve_hass_conf['hass_url'], retrieve_hass_conf['long_lived_token'], 
-                            retrieve_hass_conf['freq'], retrieve_hass_conf['time_zone'],
+                            retrieve_hass_conf['optimization_time_step'], retrieve_hass_conf['time_zone'],
                             params, emhass_conf, logger)
     if get_data_from_file:
         with open(emhass_conf['data_path'] / 'test_df_final.pkl', 'rb') as inp:
             rh.df_final, days_list, var_list = pickle.load(inp)
-        retrieve_hass_conf['var_load'] = str(var_list[0])
-        retrieve_hass_conf['var_PV'] = str(var_list[1])
-        retrieve_hass_conf['var_interp'] = [retrieve_hass_conf['var_PV'], retrieve_hass_conf['var_load']]
-        retrieve_hass_conf['var_replace_zero'] = [retrieve_hass_conf['var_PV']]
+        retrieve_hass_conf['sensor_power_load_no_var_loads'] = str(var_list[0])
+        retrieve_hass_conf['sensor_power_photovoltaics'] = str(var_list[1])
+        retrieve_hass_conf['sensor_linear_interp'] = [retrieve_hass_conf['sensor_power_photovoltaics'], retrieve_hass_conf['sensor_power_load_no_var_loads']]
+        retrieve_hass_conf['sensor_replace_zero'] = [retrieve_hass_conf['sensor_power_photovoltaics']]
     else:
-        days_list = get_days_list(retrieve_hass_conf['days_to_retrieve'])
-        var_list = [retrieve_hass_conf['var_load'], retrieve_hass_conf['var_PV']]
+        days_list = get_days_list(retrieve_hass_conf['historic_days_to_retrieve'])
+        var_list = [retrieve_hass_conf['sensor_power_load_no_var_loads'], retrieve_hass_conf['sensor_power_photovoltaics']]
         rh.get_data(days_list, var_list,
                         minimal_response=False, significant_changes_only=False)
-    rh.prepare_data(retrieve_hass_conf['var_load'], load_negative = retrieve_hass_conf['load_negative'],
+    rh.prepare_data(retrieve_hass_conf['sensor_power_load_no_var_loads'], load_negative = retrieve_hass_conf['load_negative'],
                             set_zero_min = retrieve_hass_conf['set_zero_min'], 
-                            var_replace_zero = retrieve_hass_conf['var_replace_zero'], 
-                            var_interp = retrieve_hass_conf['var_interp'])
+                            var_replace_zero = retrieve_hass_conf['sensor_replace_zero'], 
+                            var_interp = retrieve_hass_conf['sensor_linear_interp'])
     df_input_data = rh.df_final.copy()
     
     fcst = Forecast(retrieve_hass_conf, optim_conf, plant_conf,
@@ -74,14 +79,14 @@
     optim_conf.update({'lp_solver_path': 'empty'})  # set the path to the LP solver, COIN_CMD default is /usr/bin/cbc
     
     # Config for a single thermal model
-    optim_conf.update({'num_def_loads': 1})
-    optim_conf.update({'P_deferrable_nom': [1000.0]})
-    optim_conf.update({'def_total_hours': [0]})
-    optim_conf.update({'def_start_timestep': [0]})
-    optim_conf.update({'def_end_timestep': [0]})
-    optim_conf.update({'treat_def_as_semi_cont': [False]})
-    optim_conf.update({'set_def_constant': [False]})
-    optim_conf.update({'def_start_penalty': [0.0]})
+    optim_conf.update({'number_of_deferrable_loads': 1})
+    optim_conf.update({'nominal_power_of_deferrable_loads': [1000.0]})
+    optim_conf.update({'operating_hours_of_each_deferrable_load': [0]})
+    optim_conf.update({'start_timesteps_of_each_deferrable_load': [0]})
+    optim_conf.update({'end_timesteps_of_each_deferrable_load': [0]})
+    optim_conf.update({'treat_deferrable_load_as_semi_cont': [False]})
+    optim_conf.update({'set_deferrable_load_single_constant': [False]})
+    optim_conf.update({'set_deferrable_startup_penalty': [0.0]})
     
     # Thermal modeling
     df_input_data['outdoor_temperature_forecast'] = [random.normalvariate(10.0, 3.0) for _ in range(48)]
diff --git a/scripts/script_thermal_model_optim.py b/scripts/script_thermal_model_optim.py
index 74440464..49073737 100644
--- a/scripts/script_thermal_model_optim.py
+++ b/scripts/script_thermal_model_optim.py
@@ -1,4 +1,5 @@
 # -*- coding: utf-8 -*-
+import json
 import pickle
 import random
 import numpy as np
@@ -13,14 +14,16 @@
 from emhass.retrieve_hass import RetrieveHass
 from emhass.optimization import Optimization
 from emhass.forecast import Forecast
-from emhass.utils import get_root, get_yaml_parse, get_days_list, get_logger
+from emhass.utils import get_root, get_yaml_parse, get_days_list, get_logger, build_config, build_params
 
 # the root folder
-root = str(get_root(__file__, num_parent=2))
+root = pathlib.Path(str(get_root(__file__, num_parent=2)))
 emhass_conf = {}
-emhass_conf['config_path'] = pathlib.Path(root) / 'config_emhass.yaml'
-emhass_conf['data_path'] = pathlib.Path(root) / 'data/'
-emhass_conf['root_path'] = pathlib.Path(root)
+emhass_conf['data_path'] = root / 'data/'
+emhass_conf['root_path'] = root / 'src/emhass/'
+emhass_conf['config_path'] = root / 'config.json'
+emhass_conf['defaults_path'] = emhass_conf['root_path']  / 'data/config_defaults.json'
+emhass_conf['associations_path'] = emhass_conf['root_path']  / 'data/associations.csv'
 
 # create logger
 logger, ch = get_logger(__name__, emhass_conf, save_to_file=False)
@@ -31,28 +34,31 @@
     show_figures = True
     template = 'presentation'
     
-    retrieve_hass_conf, optim_conf, plant_conf = get_yaml_parse(emhass_conf, use_secrets=False)
+    # Build params with default config (no secrets) 
+    config = build_config(emhass_conf,logger,emhass_conf['defaults_path'])
+    params =  build_params(emhass_conf,{},config,logger)
+    retrieve_hass_conf, optim_conf, plant_conf = get_yaml_parse(params, logger)
     retrieve_hass_conf, optim_conf, plant_conf = \
         retrieve_hass_conf, optim_conf, plant_conf
     rh = RetrieveHass(retrieve_hass_conf['hass_url'], retrieve_hass_conf['long_lived_token'], 
-                            retrieve_hass_conf['freq'], retrieve_hass_conf['time_zone'],
+                            retrieve_hass_conf['optimization_time_step'], retrieve_hass_conf['time_zone'],
                             params, emhass_conf, logger)
     if get_data_from_file:
         with open(emhass_conf['data_path'] / 'test_df_final.pkl', 'rb') as inp:
             rh.df_final, days_list, var_list = pickle.load(inp)
-        retrieve_hass_conf['var_load'] = str(var_list[0])
-        retrieve_hass_conf['var_PV'] = str(var_list[1])
-        retrieve_hass_conf['var_interp'] = [retrieve_hass_conf['var_PV'], retrieve_hass_conf['var_load']]
-        retrieve_hass_conf['var_replace_zero'] = [retrieve_hass_conf['var_PV']]
+        retrieve_hass_conf['sensor_power_load_no_var_loads'] = str(var_list[0])
+        retrieve_hass_conf['sensor_power_photovoltaics'] = str(var_list[1])
+        retrieve_hass_conf['sensor_linear_interp'] = [retrieve_hass_conf['sensor_power_photovoltaics'], retrieve_hass_conf['sensor_power_load_no_var_loads']]
+        retrieve_hass_conf['sensor_replace_zero'] = [retrieve_hass_conf['sensor_power_photovoltaics']]
     else:
-        days_list = get_days_list(retrieve_hass_conf['days_to_retrieve'])
-        var_list = [retrieve_hass_conf['var_load'], retrieve_hass_conf['var_PV']]
+        days_list = get_days_list(retrieve_hass_conf['historic_days_to_retrieve'])
+        var_list = [retrieve_hass_conf['sensor_power_load_no_var_loads'], retrieve_hass_conf['sensor_power_photovoltaics']]
         rh.get_data(days_list, var_list,
                         minimal_response=False, significant_changes_only=False)
-    rh.prepare_data(retrieve_hass_conf['var_load'], load_negative = retrieve_hass_conf['load_negative'],
+    rh.prepare_data(retrieve_hass_conf['sensor_power_load_no_var_loads'], load_negative = retrieve_hass_conf['load_negative'],
                             set_zero_min = retrieve_hass_conf['set_zero_min'], 
-                            var_replace_zero = retrieve_hass_conf['var_replace_zero'], 
-                            var_interp = retrieve_hass_conf['var_interp'])
+                            var_replace_zero = retrieve_hass_conf['sensor_replace_zero'], 
+                            var_interp = retrieve_hass_conf['sensor_linear_interp'])
     df_input_data = rh.df_final.copy()
     
     fcst = Forecast(retrieve_hass_conf, optim_conf, plant_conf,
@@ -74,8 +80,8 @@
     optim_conf.update({'lp_solver_path': 'empty'})  # set the path to the LP solver, COIN_CMD default is /usr/bin/cbc
     
     # Semi continuous and constant values
-    optim_conf.update({'treat_def_as_semi_cont': [True, False]})
-    optim_conf.update({'set_def_constant': [True, False]})
+    optim_conf.update({'treat_deferrable_load_as_semi_cont': [True, False]})
+    optim_conf.update({'set_deferrable_load_single_constant': [True, False]})
     
     # Thermal modeling
     df_input_data['outdoor_temperature_forecast'] = [random.normalvariate(10.0, 3.0) for _ in range(48)]
diff --git a/scripts/special_config_analysis.py b/scripts/special_config_analysis.py
index ace61ab8..ce07fb61 100644
--- a/scripts/special_config_analysis.py
+++ b/scripts/special_config_analysis.py
@@ -22,15 +22,18 @@
 from emhass.retrieve_hass import RetrieveHass
 from emhass.optimization import Optimization
 from emhass.forecast import Forecast
-from emhass.utils import get_root, get_yaml_parse, get_days_list, get_logger, build_params
+from emhass.utils import get_root, get_yaml_parse, get_days_list, get_logger, build_config, build_secrets, build_params
 
 # the root folder
-root = str(get_root(__file__, num_parent=2))
+root = pathlib.Path(str(get_root(__file__, num_parent=2)))
 emhass_conf = {}
-emhass_conf['config_path'] = pathlib.Path(root) / 'config_emhass.yaml'
-emhass_conf['data_path'] = pathlib.Path(root) / 'data/'
-emhass_conf['root_path'] = pathlib.Path(root)
-
+emhass_conf['data_path'] = root / 'data/'
+emhass_conf['root_path'] = root / 'src/emhass/'
+emhass_conf['scripts_path'] = root / 'scripts/'
+emhass_conf['config_path'] = root / 'config.json'
+emhass_conf['secrets_path'] =  root / 'secrets_emhass.yaml'
+emhass_conf['defaults_path'] = emhass_conf['root_path']  / 'data/config_defaults.json'
+emhass_conf['associations_path'] = emhass_conf['root_path']  / 'data/associations.csv'
 # create logger
 logger, ch = get_logger(__name__, emhass_conf, save_to_file=False)
 
@@ -50,30 +53,12 @@ def get_forecast_optim_objects(retrieve_hass_conf, optim_conf, plant_conf,
 
 if __name__ == '__main__':
     get_data_from_file = False
-    
-    with open(emhass_conf['config_path'], 'r') as file:
-        config = yaml.load(file, Loader=yaml.FullLoader)
-    retrieve_hass_conf = config['retrieve_hass_conf']
-    optim_conf = config['optim_conf']
-    plant_conf = config['plant_conf']
-    
-    params = {}
-    params['retrieve_hass_conf'] = retrieve_hass_conf
-    params['optim_conf'] = optim_conf
-    params['plant_conf'] = plant_conf
-    
-    options_json = emhass_conf['root_path'] / 'scripts/special_options.json'
-    with options_json.open('r') as data:
-        options = json.load(data)
-    
-    # params = build_params(params, options)
-    
-    with open(emhass_conf['root_path'] / 'secrets_emhass.yaml', 'r') as file:
-        params_secrets = yaml.load(file, Loader=yaml.FullLoader)
-    
-    params = build_params(params, params_secrets, options, 1, logger)
-    # params['params_secrets'] = input_secrets
-    
+
+    # Build params with defaults, secret file, and added special config and secrets
+    config = build_config(emhass_conf,logger,emhass_conf['defaults_path'],emhass_conf['scripts_path'] / 'special_options.json')
+    emhass_conf,secrets = build_secrets(emhass_conf,logger,options_path=emhass_conf['scripts_path'] / 'special_options.json',secrets_path=emhass_conf['secrets_path'],no_response=True)
+    params = build_params(emhass_conf, secrets, config, logger)
+        
     pv_power_forecast = [0, 8, 27, 42, 47, 41, 25, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 19, 52, 73, 74, 68, 44, 12, 0, 0, 0, 0]
     load_power_forecast = [2850, 3021, 3107, 3582, 2551, 2554, 1856, 2505, 1768, 2540, 1722, 2463, 1670, 1379, 1165, 1000, 1641, 1181, 1861, 1414, 1467, 1344, 1209, 1531]
     load_cost_forecast = [17.836, 19.146, 18.753, 17.838, 17.277, 16.282, 16.736, 16.047, 17.004, 19.982, 17.17, 16.968, 16.556, 16.21, 12.333, 10.937]
@@ -81,44 +66,44 @@ def get_forecast_optim_objects(retrieve_hass_conf, optim_conf, plant_conf,
     prediction_horizon = 16
     soc_init = 0.98
     soc_final = 0.3
-    def_total_hours = [0]
+    operating_hours_of_each_deferrable_load = [0]
     alpha = 1
     beta = 0
     
     params['passed_data'] = {'pv_power_forecast':pv_power_forecast,'load_power_forecast':load_power_forecast,
                              'load_cost_forecast':load_cost_forecast,'prod_price_forecast':prod_price_forecast,
                              'prediction_horizon':prediction_horizon,'soc_init':soc_init,'soc_final':soc_final,
-                             'def_total_hours':def_total_hours,'alpha':alpha,'beta':beta}
+                             'operating_hours_of_each_deferrable_load':operating_hours_of_each_deferrable_load,'alpha':alpha,'beta':beta}
     
-    optim_conf['weather_forecast_method'] = 'list'
-    optim_conf['load_forecast_method'] = 'list'
-    optim_conf['load_cost_forecast_method'] = 'list'
-    optim_conf['prod_price_forecast_method'] = 'list'
+    params['optim_conf']['weather_forecast_method'] = 'list'
+    params['optim_conf']['load_forecast_method'] = 'list'
+    params['optim_conf']['load_cost_forecast_method'] = 'list'
+    params['optim_conf']['production_price_forecast_method'] = 'list'
     
-    data_path = emhass_conf['root_path'] / 'scripts/data_temp.pkl'
+    data_path = emhass_conf['scripts_path'] / 'data_temp.pkl'
+
+    retrieve_hass_conf, optim_conf, plant_conf = get_yaml_parse(params, logger)
     
     if data_path.is_file():
         logger.info("Loading a previous data file")
         with open(data_path, "rb") as fid:
             fcst, P_PV_forecast, P_load_forecast, df_input_data_dayahead, opt, df_input_data = pickle.load(fid)
     else:
-    
-        retrieve_hass_conf, optim_conf, plant_conf = get_yaml_parse(emhass_conf, use_secrets=True, params = json.dumps(params))
         rh = RetrieveHass(retrieve_hass_conf['hass_url'], retrieve_hass_conf['long_lived_token'], 
-                          retrieve_hass_conf['freq'], retrieve_hass_conf['time_zone'],
+                          retrieve_hass_conf['optimization_time_step'], retrieve_hass_conf['time_zone'],
                           params, emhass_conf, logger)
-        days_list = get_days_list(retrieve_hass_conf['days_to_retrieve'])
-        var_list = [retrieve_hass_conf['var_load'], retrieve_hass_conf['var_PV']]
+        days_list = get_days_list(retrieve_hass_conf['historic_days_to_retrieve'])
+        var_list = [retrieve_hass_conf['sensor_power_load_no_var_loads'], retrieve_hass_conf['sensor_power_photovoltaics']]
         rh.get_data(days_list, var_list,
                         minimal_response=False, significant_changes_only=False)
-        rh.prepare_data(retrieve_hass_conf['var_load'], load_negative = retrieve_hass_conf['load_negative'],
+        rh.prepare_data(retrieve_hass_conf['sensor_power_load_no_var_loads'], load_negative = retrieve_hass_conf['load_negative'],
                                 set_zero_min = retrieve_hass_conf['set_zero_min'], 
-                                var_replace_zero = retrieve_hass_conf['var_replace_zero'], 
-                                var_interp = retrieve_hass_conf['var_interp'])
+                                var_replace_zero = retrieve_hass_conf['sensor_replace_zero'], 
+                                var_interp = retrieve_hass_conf['sensor_linear_interp'])
         df_input_data = rh.df_final.copy()
         fcst, P_PV_forecast, P_load_forecast, df_input_data_dayahead, opt = \
             get_forecast_optim_objects(retrieve_hass_conf, optim_conf, plant_conf,
-                                    json.dumps(params), get_data_from_file)
+                                    params, get_data_from_file)
         df_input_data = fcst.get_load_cost_forecast(df_input_data)
         df_input_data = fcst.get_prod_price_forecast(df_input_data)
     
@@ -128,8 +113,8 @@ def get_forecast_optim_objects(retrieve_hass_conf, optim_conf, plant_conf,
     template = 'presentation'
     
     # Let's plot the input data
-    fig_inputs1 = df_input_data[[str(retrieve_hass_conf['var_PV']),
-                                 str(retrieve_hass_conf['var_load'] + '_positive')]].plot()
+    fig_inputs1 = df_input_data[[str(retrieve_hass_conf['sensor_power_photovoltaics']),
+                                 str(retrieve_hass_conf['sensor_power_load_no_var_loads'] + '_positive')]].plot()
     fig_inputs1.layout.template = template
     fig_inputs1.update_yaxes(title_text = "Powers (W)")
     fig_inputs1.update_xaxes(title_text = "Time")
@@ -163,7 +148,7 @@ def get_forecast_optim_objects(retrieve_hass_conf, optim_conf, plant_conf,
         \"prod_price_forecast\":[6.651, 7.743, 7.415, 6.653, 6.185, 5.356, 5.734, 5.16, 5.958, 8.439, 6.096, 5.928, 5.584, 5.296, 4.495, 3.332], 
         \"prediction_horizon\":16, 
         \"pv_power_forecast\": [0, 8, 27, 42, 47, 41, 25, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 19, 52, 73, 74, 68, 44, 12, 0, 0, 0, 0], 
-        \"alpha\": 1, \"beta\": 0, \"soc_init\":0.98, \"soc_final\":0.3, \"def_total_hours\":[0]        
+        \"alpha\": 1, \"beta\": 0, \"soc_init\":0.98, \"soc_final\":0.3, \"operating_hours_of_each_deferrable_load\":[0]        
         }' http://localhost:5000/action/naive-mpc-optim"'''
     
     # Perform a MPC optimization
@@ -180,7 +165,7 @@ def get_forecast_optim_objects(retrieve_hass_conf, optim_conf, plant_conf,
     
     opt_res_dayahead = opt.perform_naive_mpc_optim(
         df_input_data_dayahead, P_PV_forecast, P_load_forecast, prediction_horizon,
-        soc_init=soc_init, soc_final=soc_final, def_total_hours=def_total_hours)
+        soc_init=soc_init, soc_final=soc_final, def_total_hours=operating_hours_of_each_deferrable_load)
     fig_res_mpc = opt_res_dayahead[['P_batt', 'P_grid']].plot()
     fig_res_mpc.layout.template = template
     fig_res_mpc.update_yaxes(title_text = "Powers (W)")
diff --git a/scripts/special_options.json b/scripts/special_options.json
index 4c9e1037..f8ea5b73 100644
--- a/scripts/special_options.json
+++ b/scripts/special_options.json
@@ -28,16 +28,16 @@
       "operating_hours_of_each_deferrable_load": 1                                                                                                                                                              
     }                                                                                                                                                                                                         
   ],                                                                                                                                                                                                             
-  "list_peak_hours_periods_start_hours": [                                                                                                                                                                       
-    {                                                                                                                                                                                                            
-      "peak_hours_periods_start_hours": "05:54"                                                                                                                                                                  
-    }                                                                                                                                                                                                            
-  ],                                                                                                                                                                                                             
-  "list_peak_hours_periods_end_hours": [                                                                                                                                                                                                                                                                                                                                                                                 
-    {                                                                                                                                                                                                            
-      "peak_hours_periods_end_hours": "05:54"                                                                                                                                                                    
-    }                                                                                                                                                                                                            
-  ],  
+  "load_peak_hour_periods": {
+    "period_hp_1": [
+      {
+        "start": "05:54"
+      },
+      {
+        "end": "05:54"
+      }
+    ]
+  },
   "list_treat_deferrable_load_as_semi_cont": [
     {
       "treat_deferrable_load_as_semi_cont": false
diff --git a/scripts/use_cases_analysis.py b/scripts/use_cases_analysis.py
index 0030784e..9ae5234c 100644
--- a/scripts/use_cases_analysis.py
+++ b/scripts/use_cases_analysis.py
@@ -7,6 +7,7 @@
     Before running this script you should perform a perfect optimization for each type of cost function:
     profit, cost and self-consumption 
 '''
+import json
 import numpy as np
 import pandas as pd
 import pathlib
@@ -19,14 +20,18 @@
 from emhass.retrieve_hass import RetrieveHass
 from emhass.optimization import Optimization
 from emhass.forecast import Forecast
-from emhass.utils import get_root, get_yaml_parse, get_days_list, get_logger
+from emhass.utils import get_root, get_yaml_parse, get_days_list, get_logger, build_config, build_secrets, build_params
 
 # the root folder
-root = str(get_root(__file__, num_parent=2))
+root = pathlib.Path(str(get_root(__file__, num_parent=2)))
 emhass_conf = {}
-emhass_conf['config_path'] = pathlib.Path(root) / 'config_emhass.yaml'
-emhass_conf['data_path'] = pathlib.Path(root) / 'data/'
-emhass_conf['root_path'] = pathlib.Path(root)
+emhass_conf['data_path'] = root / 'data/'
+emhass_conf['root_path'] = root / 'src/emhass/'
+emhass_conf['docs_path'] = root / 'docs/'
+emhass_conf['config_path'] = root / 'config.json'
+emhass_conf['secrets_path'] =  root / 'secrets_emhass.yaml'
+emhass_conf['defaults_path'] = emhass_conf['root_path']  / 'data/config_defaults.json'
+emhass_conf['associations_path'] = emhass_conf['root_path']  / 'data/associations.csv'
 
 # create logger
 logger, ch = get_logger(__name__, emhass_conf, save_to_file=False)
@@ -49,18 +54,22 @@ def get_forecast_optim_objects(retrieve_hass_conf, optim_conf, plant_conf,
     get_data_from_file = False
     params = None
     save_figures = False
-    retrieve_hass_conf, optim_conf, plant_conf = get_yaml_parse(emhass_conf, use_secrets=True)
+    # Build params with default config and secrets file
+    config = build_config(emhass_conf,logger,emhass_conf['defaults_path'])
+    _,secrets = build_secrets(emhass_conf,logger,secrets_path=emhass_conf['secrets_path'],no_response=True)
+    params =  build_params(emhass_conf,secrets,config,logger)
+    retrieve_hass_conf, optim_conf, plant_conf = get_yaml_parse(params, logger)
     rh = RetrieveHass(retrieve_hass_conf['hass_url'], retrieve_hass_conf['long_lived_token'], 
-                      retrieve_hass_conf['freq'], retrieve_hass_conf['time_zone'],
+                      retrieve_hass_conf['optimization_time_step'], retrieve_hass_conf['time_zone'],
                       params, emhass_conf, logger)
-    days_list = get_days_list(retrieve_hass_conf['days_to_retrieve'])
-    var_list = [retrieve_hass_conf['var_load'], retrieve_hass_conf['var_PV']]
+    days_list = get_days_list(retrieve_hass_conf['historic_days_to_retrieve'])
+    var_list = [retrieve_hass_conf['sensor_power_load_no_var_loads'], retrieve_hass_conf['sensor_power_photovoltaics']]
     rh.get_data(days_list, var_list,
                     minimal_response=False, significant_changes_only=False)
-    rh.prepare_data(retrieve_hass_conf['var_load'], load_negative = retrieve_hass_conf['load_negative'],
+    rh.prepare_data(retrieve_hass_conf['sensor_power_load_no_var_loads'], load_negative = retrieve_hass_conf['load_negative'],
                             set_zero_min = retrieve_hass_conf['set_zero_min'], 
-                            var_replace_zero = retrieve_hass_conf['var_replace_zero'], 
-                            var_interp = retrieve_hass_conf['var_interp'])
+                            var_replace_zero = retrieve_hass_conf['sensor_replace_zero'], 
+                            var_interp = retrieve_hass_conf['sensor_linear_interp'])
     df_input_data = rh.df_final.copy()
     fcst, P_PV_forecast, P_load_forecast, df_input_data_dayahead, opt = \
         get_forecast_optim_objects(retrieve_hass_conf, optim_conf, plant_conf,
@@ -71,14 +80,14 @@ def get_forecast_optim_objects(retrieve_hass_conf, optim_conf, plant_conf,
     template = 'presentation'
     
     # Let's plot the input data
-    fig_inputs1 = df_input_data[[str(retrieve_hass_conf['var_PV']),
-                                str(retrieve_hass_conf['var_load'] + '_positive')]].plot()
+    fig_inputs1 = df_input_data[[str(retrieve_hass_conf['sensor_power_photovoltaics']),
+                                str(retrieve_hass_conf['sensor_power_load_no_var_loads'] + '_positive')]].plot()
     fig_inputs1.layout.template = template
     fig_inputs1.update_yaxes(title_text = "Powers (W)")
     fig_inputs1.update_xaxes(title_text = "Time")
     fig_inputs1.show()
     if save_figures:
-        fig_inputs1.write_image(emhass_conf['root_path'] / "docs/images/inputs_power.svg", 
+        fig_inputs1.write_image(emhass_conf['docs_path'] / "images/inputs_power.svg", 
                                 width=1080, height=0.8*1080)
     
     fig_inputs2 = df_input_data[['unit_load_cost',
@@ -88,7 +97,7 @@ def get_forecast_optim_objects(retrieve_hass_conf, optim_conf, plant_conf,
     fig_inputs2.update_xaxes(title_text = "Time")
     fig_inputs2.show()
     if save_figures:
-        fig_inputs2.write_image(emhass_conf['root_path'] / "docs/images/inputs_cost_price.svg", 
+        fig_inputs2.write_image(emhass_conf['docs_path'] / "images/inputs_cost_price.svg", 
                                 width=1080, height=0.8*1080)
     
     fig_inputs_dah = df_input_data_dayahead.plot()
@@ -97,7 +106,7 @@ def get_forecast_optim_objects(retrieve_hass_conf, optim_conf, plant_conf,
     fig_inputs_dah.update_xaxes(title_text = "Time")
     fig_inputs_dah.show()
     if save_figures:
-        fig_inputs_dah.write_image(emhass_conf['root_path'] / "docs/images/inputs_dayahead.svg", 
+        fig_inputs_dah.write_image(emhass_conf['docs_path'] / "images/inputs_dayahead.svg", 
                                    width=1080, height=0.8*1080)
     
     # Let's first perform a perfect optimization
@@ -108,7 +117,7 @@ def get_forecast_optim_objects(retrieve_hass_conf, optim_conf, plant_conf,
     fig_res.update_xaxes(title_text = "Time")
     fig_res.show()
     if save_figures:
-        fig_res.write_image(emhass_conf['root_path'] / "docs/images/optim_results_PV_defLoads_perfectOptim.svg", 
+        fig_res.write_image(emhass_conf['docs_path'] / "images/optim_results_PV_defLoads_perfectOptim.svg", 
                             width=1080, height=0.8*1080)
     
     print("System with: PV, two deferrable loads, perfect optimization, profit >> total cost function sum: "+\
@@ -124,7 +133,7 @@ def get_forecast_optim_objects(retrieve_hass_conf, optim_conf, plant_conf,
     fig_res_dah.update_xaxes(title_text = "Time")
     fig_res_dah.show()
     if save_figures:
-        fig_res_dah.write_image(emhass_conf['root_path'] / "docs/images/optim_results_PV_defLoads_dayaheadOptim.svg", 
+        fig_res_dah.write_image(emhass_conf['docs_path'] / "images/optim_results_PV_defLoads_dayaheadOptim.svg", 
                                 width=1080, height=0.8*1080)
     
     print("System with: PV, two deferrable loads, dayahead optimization, profit >> total cost function sum: "+\
@@ -144,7 +153,7 @@ def get_forecast_optim_objects(retrieve_hass_conf, optim_conf, plant_conf,
     fig_res_dah.update_xaxes(title_text = "Time")
     fig_res_dah.show()
     if save_figures:
-        fig_res_dah.write_image(emhass_conf['root_path'] / "docs/images/optim_results_defLoads_dayaheadOptim.svg", 
+        fig_res_dah.write_image(emhass_conf['docs_path'] / "images/optim_results_defLoads_dayaheadOptim.svg", 
                                 width=1080, height=0.8*1080)
     
     print("System with: two deferrable loads, dayahead optimization, profit >> total cost function sum: "+\
@@ -165,7 +174,7 @@ def get_forecast_optim_objects(retrieve_hass_conf, optim_conf, plant_conf,
     fig_res_dah.update_xaxes(title_text = "Time")
     fig_res_dah.show()
     if save_figures:
-        fig_res_dah.write_image(emhass_conf['root_path'] / "docs/images/optim_results_PV_Batt_defLoads_dayaheadOptim.svg", 
+        fig_res_dah.write_image(emhass_conf['docs_path'] / "images/optim_results_PV_Batt_defLoads_dayaheadOptim.svg", 
                                 width=1080, height=0.8*1080)
     fig_res_dah = opt_res_dah[['SOC_opt']].plot()
     fig_res_dah.layout.template = template
@@ -173,7 +182,7 @@ def get_forecast_optim_objects(retrieve_hass_conf, optim_conf, plant_conf,
     fig_res_dah.update_xaxes(title_text = "Time")
     fig_res_dah.show()
     if save_figures:
-        fig_res_dah.write_image(emhass_conf['root_path'] / "docs/images/optim_results_PV_Batt_defLoads_dayaheadOptim_SOC.svg", 
+        fig_res_dah.write_image(emhass_conf['docs_path'] / "images/optim_results_PV_Batt_defLoads_dayaheadOptim_SOC.svg", 
                                 width=1080, height=0.8*1080)
     
     print("System with: PV, Battery, two deferrable loads, dayahead optimization, profit >> total cost function sum: "+\
diff --git a/secrets_emhass(example).yaml b/secrets_emhass(example).yaml
index 312ae7b3..f1c63b51 100644
--- a/secrets_emhass(example).yaml
+++ b/secrets_emhass(example).yaml
@@ -1,12 +1,13 @@
 
 # Use this file to store secrets like usernames and passwords.
 # Learn more at https://home-assistant.io/docs/configuration/secrets/
+server_ip: 0.0.0.0
 hass_url: https://myhass.duckdns.org/
 long_lived_token: thatverylongtokenhere
 time_zone: Europe/Paris
-lat: 45.83
-lon: 6.86
-alt: 4807.8
+Latitude: 45.83
+Longitude: 6.86
+Altitude: 4807.8
 solcast_api_key: yoursecretsolcastapikey
 solcast_rooftop_id: yourrooftopid
 solar_forecast_kwp: 5
\ No newline at end of file
diff --git a/setup.py b/setup.py
index 4482d4c0..60d2f325 100644
--- a/setup.py
+++ b/setup.py
@@ -62,7 +62,7 @@
             'emhass=emhass.command_line:main',
         ],
     },
-    package_data={'emhass': ['templates/index.html','templates/template.html','static/advanced.html','static/basic.html', 'static/script.js',
-    'static/style.css','static/img/emhass_icon.png','static/img/emhass_logo_short.svg', 'static/img/feather-sprite.svg',
-    'data/cec_modules.pbz2', 'data/cec_inverters.pbz2']},
+    package_data={'emhass': ['templates/index.html','templates/template.html','templates/configuration.html','static/advanced.html','static/basic.html', 'static/script.js', 'static/configuration_script.js',
+    'static/style.css','static/configuration_list.html','static/img/emhass_icon.png','static/img/emhass_logo_short.svg', 'static/img/feather-sprite.svg','static/data/param_definitions.json',
+    'data/cec_modules.pbz2', 'data/cec_inverters.pbz2','data/associations.csv','data/config_defaults.json']},
 )
diff --git a/src/emhass/command_line.py b/src/emhass/command_line.py
index 3dee844d..feb6d333 100644
--- a/src/emhass/command_line.py
+++ b/src/emhass/command_line.py
@@ -3,6 +3,7 @@
 
 import argparse
 import os
+import re
 import time
 import pathlib
 import logging
@@ -50,15 +51,25 @@ def set_input_data_dict(emhass_conf: dict, costfun: str,
 
     """
     logger.info("Setting up needed data")
+
+    # check if passed params is a dict
+    if (params != None) and (params != "null"):
+        if type(params) is str:
+            params = json.loads(params)
+    else:
+        params = {}
+
     # Parsing yaml
-    retrieve_hass_conf, optim_conf, plant_conf = utils.get_yaml_parse(
-        emhass_conf, use_secrets=not(get_data_from_file), params=params)
+    retrieve_hass_conf, optim_conf, plant_conf = utils.get_yaml_parse(params,logger)
+    if type(retrieve_hass_conf) is bool:
+        return False
+        
     # Treat runtimeparams
     params, retrieve_hass_conf, optim_conf, plant_conf = utils.treat_runtimeparams(
         runtimeparams, params, retrieve_hass_conf, optim_conf, plant_conf, set_type, logger)
     # Define main objects
     rh = RetrieveHass(retrieve_hass_conf['hass_url'], retrieve_hass_conf['long_lived_token'],
-                      retrieve_hass_conf['freq'], retrieve_hass_conf['time_zone'],
+                      retrieve_hass_conf['optimization_time_step'], retrieve_hass_conf['time_zone'],
                       params, emhass_conf, logger, get_data_from_file=get_data_from_file)
     fcst = Forecast(retrieve_hass_conf, optim_conf, plant_conf,
                     params, emhass_conf, logger, get_data_from_file=get_data_from_file)
@@ -71,24 +82,24 @@ def set_input_data_dict(emhass_conf: dict, costfun: str,
         if get_data_from_file:
             with open(emhass_conf['data_path'] / 'test_df_final.pkl', 'rb') as inp:
                 rh.df_final, days_list, var_list = pickle.load(inp)
-            retrieve_hass_conf['var_load'] = str(var_list[0])
-            retrieve_hass_conf['var_PV'] = str(var_list[1])
-            retrieve_hass_conf['var_interp'] = [
-                retrieve_hass_conf['var_PV'], retrieve_hass_conf['var_load']]
-            retrieve_hass_conf['var_replace_zero'] = [
-                retrieve_hass_conf['var_PV']]
+            retrieve_hass_conf['sensor_power_load_no_var_loads'] = str(var_list[0])
+            retrieve_hass_conf['sensor_power_photovoltaics'] = str(var_list[1])
+            retrieve_hass_conf['sensor_linear_interp'] = [
+                retrieve_hass_conf['sensor_power_photovoltaics'], retrieve_hass_conf['sensor_power_load_no_var_loads']]
+            retrieve_hass_conf['sensor_replace_zero'] = [
+                retrieve_hass_conf['sensor_power_photovoltaics']]
         else:
             days_list = utils.get_days_list(
-                retrieve_hass_conf["days_to_retrieve"])
-            var_list = [retrieve_hass_conf["var_load"],
-                        retrieve_hass_conf["var_PV"]]
+                retrieve_hass_conf['historic_days_to_retrieve'])
+            var_list = [retrieve_hass_conf['sensor_power_load_no_var_loads'],
+                        retrieve_hass_conf['sensor_power_photovoltaics']]
             if not rh.get_data(days_list, var_list, minimal_response=False, significant_changes_only=False):
                 return False
-        if not rh.prepare_data(retrieve_hass_conf["var_load"],
-                               load_negative=retrieve_hass_conf["load_negative"],
-                               set_zero_min=retrieve_hass_conf["set_zero_min"],
-                               var_replace_zero=retrieve_hass_conf["var_replace_zero"],
-                               var_interp=retrieve_hass_conf["var_interp"]):
+        if not rh.prepare_data(retrieve_hass_conf['sensor_power_load_no_var_loads'],
+                               load_negative=retrieve_hass_conf['load_negative'],
+                               set_zero_min=retrieve_hass_conf['set_zero_min'],
+                               var_replace_zero=retrieve_hass_conf['sensor_replace_zero'],
+                               var_interp=retrieve_hass_conf['sensor_linear_interp']):
             return False
         df_input_data = rh.df_final.copy()
         # What we don't need for this type of action
@@ -96,7 +107,7 @@ def set_input_data_dict(emhass_conf: dict, costfun: str,
     elif set_type == "dayahead-optim":
         # Get PV and load forecasts
         df_weather = fcst.get_weather_forecast(
-            method=optim_conf["weather_forecast_method"])
+            method=optim_conf['weather_forecast_method'])
         if isinstance(df_weather, bool) and not df_weather:
             return False
         P_PV_forecast = fcst.get_power_from_weather(df_weather)
@@ -109,9 +120,12 @@ def set_input_data_dict(emhass_conf: dict, costfun: str,
         df_input_data_dayahead = pd.DataFrame(np.transpose(np.vstack(
             [P_PV_forecast.values, P_load_forecast.values])), index=P_PV_forecast.index,
             columns=["P_PV_forecast", "P_load_forecast"])
-        if "freq" in retrieve_hass_conf and retrieve_hass_conf["freq"]:
-            freq = pd.to_timedelta(retrieve_hass_conf["freq"], "minute")
-            df_input_data_dayahead = df_input_data_dayahead.asfreq(freq)
+        if "optimization_time_step" in retrieve_hass_conf and retrieve_hass_conf["optimization_time_step"]:
+            if not isinstance(retrieve_hass_conf["optimization_time_step"], pd._libs.tslibs.timedeltas.Timedelta):
+                optimization_time_step = pd.to_timedelta(retrieve_hass_conf["optimization_time_step"], "minute")
+            else:
+                optimization_time_step = retrieve_hass_conf["optimization_time_step"]
+            df_input_data_dayahead = df_input_data_dayahead.asfreq(optimization_time_step)
         else:
             df_input_data_dayahead = utils.set_df_index_freq(df_input_data_dayahead)
         params = json.loads(params)
@@ -126,23 +140,23 @@ def set_input_data_dict(emhass_conf: dict, costfun: str,
         if get_data_from_file:
             with open(emhass_conf['data_path'] / 'test_df_final.pkl', 'rb') as inp:
                 rh.df_final, days_list, var_list = pickle.load(inp)
-            retrieve_hass_conf['var_load'] = str(var_list[0])
-            retrieve_hass_conf['var_PV'] = str(var_list[1])
-            retrieve_hass_conf['var_interp'] = [
-                retrieve_hass_conf['var_PV'], retrieve_hass_conf['var_load']]
-            retrieve_hass_conf['var_replace_zero'] = [
-                retrieve_hass_conf['var_PV']]
+            retrieve_hass_conf['sensor_power_load_no_var_loads'] = str(var_list[0])
+            retrieve_hass_conf['sensor_power_photovoltaics'] = str(var_list[1])
+            retrieve_hass_conf['sensor_linear_interp'] = [
+                retrieve_hass_conf['sensor_power_photovoltaics'], retrieve_hass_conf['sensor_power_load_no_var_loads']]
+            retrieve_hass_conf['sensor_replace_zero'] = [
+                retrieve_hass_conf['sensor_power_photovoltaics']]
         else:
             days_list = utils.get_days_list(1)
-            var_list = [retrieve_hass_conf["var_load"],
-                        retrieve_hass_conf["var_PV"]]
+            var_list = [retrieve_hass_conf['sensor_power_load_no_var_loads'],
+                        retrieve_hass_conf['sensor_power_photovoltaics']]
             if not rh.get_data(days_list, var_list, minimal_response=False, significant_changes_only=False):
                 return False
-        if not rh.prepare_data(retrieve_hass_conf["var_load"],
-                               load_negative=retrieve_hass_conf["load_negative"],
-                               set_zero_min=retrieve_hass_conf["set_zero_min"],
-                               var_replace_zero=retrieve_hass_conf["var_replace_zero"],
-                               var_interp=retrieve_hass_conf["var_interp"]):
+        if not rh.prepare_data(retrieve_hass_conf['sensor_power_load_no_var_loads'],
+                               load_negative=retrieve_hass_conf['load_negative'],
+                               set_zero_min=retrieve_hass_conf['set_zero_min'],
+                               var_replace_zero=retrieve_hass_conf['sensor_replace_zero'],
+                               var_interp=retrieve_hass_conf['sensor_linear_interp']):
             return False
         df_input_data = rh.df_final.copy()
         # Get PV and load forecasts
@@ -159,9 +173,12 @@ def set_input_data_dict(emhass_conf: dict, costfun: str,
                 "Unable to get sensor power photovoltaics, or sensor power load no var loads. Check HA sensors and their daily data")
             return False
         df_input_data_dayahead = pd.concat([P_PV_forecast, P_load_forecast], axis=1)
-        if "freq" in retrieve_hass_conf and retrieve_hass_conf["freq"]:
-            freq = pd.to_timedelta(retrieve_hass_conf["freq"], "minute")
-            df_input_data_dayahead = df_input_data_dayahead.asfreq(freq)
+        if "optimization_time_step" in retrieve_hass_conf and retrieve_hass_conf["optimization_time_step"]:
+            if not isinstance(retrieve_hass_conf["optimization_time_step"], pd._libs.tslibs.timedeltas.Timedelta):
+                optimization_time_step = pd.to_timedelta(retrieve_hass_conf["optimization_time_step"], "minute")
+            else:
+                optimization_time_step = retrieve_hass_conf["optimization_time_step"]
+            df_input_data_dayahead = df_input_data_dayahead.asfreq(optimization_time_step)
         else:
             df_input_data_dayahead = utils.set_df_index_freq(df_input_data_dayahead)
         df_input_data_dayahead.columns = ["P_PV_forecast", "P_load_forecast"]
@@ -175,7 +192,7 @@ def set_input_data_dict(emhass_conf: dict, costfun: str,
         P_PV_forecast, P_load_forecast = None, None
         params = json.loads(params)
         # Retrieve data from hass
-        days_to_retrieve = params["passed_data"]["days_to_retrieve"]
+        days_to_retrieve = params["passed_data"]['historic_days_to_retrieve']
         model_type = params["passed_data"]["model_type"]
         var_model = params["passed_data"]["var_model"]
         if get_data_from_file:
@@ -274,8 +291,7 @@ def weather_forecast_cache(emhass_conf: dict, params: str,
     """
     
     # Parsing yaml
-    retrieve_hass_conf, optim_conf, plant_conf = utils.get_yaml_parse(
-        emhass_conf, use_secrets=True, params=params)
+    retrieve_hass_conf, optim_conf, plant_conf = utils.get_yaml_parse(params, logger)
     
     # Treat runtimeparams
     params, retrieve_hass_conf, optim_conf, plant_conf = utils.treat_runtimeparams(
@@ -293,7 +309,7 @@ def weather_forecast_cache(emhass_conf: dict, params: str,
     fcst = Forecast(retrieve_hass_conf, optim_conf, plant_conf,
                 params, emhass_conf, logger)
 
-    result = fcst.get_weather_forecast(optim_conf["weather_forecast_method"])
+    result = fcst.get_weather_forecast(optim_conf['weather_forecast_method'])
     if isinstance(result, bool) and not result:
         return False
 
@@ -326,7 +342,7 @@ def perfect_forecast_optim(input_data_dict: dict, logger: logging.Logger,
     if isinstance(df_input_data, bool) and not df_input_data:
         return False
     df_input_data = input_data_dict['fcst'].get_prod_price_forecast(
-        df_input_data, method=input_data_dict['fcst'].optim_conf['prod_price_forecast_method'],
+        df_input_data, method=input_data_dict['fcst'].optim_conf['production_price_forecast_method'],
         list_and_perfect=True)
     if isinstance(df_input_data, bool) and not df_input_data:
         return False
@@ -347,7 +363,7 @@ def perfect_forecast_optim(input_data_dict: dict, logger: logging.Logger,
         params = input_data_dict["params"]
 
     # if continual_publish, save perfect results to data_path/entities json
-    if input_data_dict["retrieve_hass_conf"].get("continual_publish",False) or params["passed_data"].get("entity_save",False):
+    if input_data_dict["retrieve_hass_conf"].get('continual_publish',False) or params["passed_data"].get("entity_save",False):
         #Trigger the publish function, save entity data and not post to HA
         publish_data(input_data_dict, logger, entity_save=True, dont_post=True)   
 
@@ -380,7 +396,7 @@ def dayahead_forecast_optim(input_data_dict: dict, logger: logging.Logger,
         return False
     df_input_data_dayahead = input_data_dict['fcst'].get_prod_price_forecast(
         df_input_data_dayahead,
-        method=input_data_dict['fcst'].optim_conf['prod_price_forecast_method'])
+        method=input_data_dict['fcst'].optim_conf['production_price_forecast_method'])
     if isinstance(df_input_data_dayahead, bool) and not df_input_data_dayahead:
         return False
     if "outdoor_temperature_forecast" in input_data_dict["params"]["passed_data"]:
@@ -406,7 +422,7 @@ def dayahead_forecast_optim(input_data_dict: dict, logger: logging.Logger,
         params = input_data_dict["params"]
     
     # if continual_publish, save day_ahead results to data_path/entities json
-    if input_data_dict["retrieve_hass_conf"].get("continual_publish",False) or params["passed_data"].get("entity_save",False):
+    if input_data_dict["retrieve_hass_conf"].get('continual_publish',False) or params["passed_data"].get("entity_save",False):
         #Trigger the publish function, save entity data and not post to HA
         publish_data(input_data_dict, logger, entity_save=True, dont_post=True)   
         
@@ -438,7 +454,7 @@ def naive_mpc_optim(input_data_dict: dict, logger: logging.Logger,
     if isinstance(df_input_data_dayahead, bool) and not df_input_data_dayahead:
         return False
     df_input_data_dayahead = input_data_dict['fcst'].get_prod_price_forecast(
-        df_input_data_dayahead, method=input_data_dict['fcst'].optim_conf['prod_price_forecast_method'])
+        df_input_data_dayahead, method=input_data_dict['fcst'].optim_conf['production_price_forecast_method'])
     if isinstance(df_input_data_dayahead, bool) and not df_input_data_dayahead:
         return False
     if "outdoor_temperature_forecast" in input_data_dict["params"]["passed_data"]:
@@ -448,9 +464,9 @@ def naive_mpc_optim(input_data_dict: dict, logger: logging.Logger,
     prediction_horizon = input_data_dict["params"]["passed_data"]["prediction_horizon"]
     soc_init = input_data_dict["params"]["passed_data"]["soc_init"]
     soc_final = input_data_dict["params"]["passed_data"]["soc_final"]
-    def_total_hours = input_data_dict["params"]["passed_data"]["def_total_hours"]
-    def_start_timestep = input_data_dict["params"]["passed_data"]["def_start_timestep"]
-    def_end_timestep = input_data_dict["params"]["passed_data"]["def_end_timestep"]
+    def_total_hours = input_data_dict["params"]["passed_data"]['operating_hours_of_each_deferrable_load']
+    def_start_timestep = input_data_dict["params"]["passed_data"]['start_timesteps_of_each_deferrable_load']
+    def_end_timestep = input_data_dict["params"]["passed_data"]['end_timesteps_of_each_deferrable_load']
     opt_res_naive_mpc = input_data_dict["opt"].perform_naive_mpc_optim(
         df_input_data_dayahead, input_data_dict["P_PV_forecast"], input_data_dict["P_load_forecast"],
         prediction_horizon, soc_init, soc_final, def_total_hours,
@@ -473,7 +489,7 @@ def naive_mpc_optim(input_data_dict: dict, logger: logging.Logger,
         params = input_data_dict["params"]
 
     # if continual_publish, save mpc results to data_path/entities json
-    if input_data_dict["retrieve_hass_conf"].get("continual_publish",False) or params["passed_data"].get("entity_save",False):
+    if input_data_dict["retrieve_hass_conf"].get('continual_publish',False) or params["passed_data"].get("entity_save",False):
         #Trigger the publish function, save entity data and not post to HA
         publish_data(input_data_dict, logger, entity_save=True, dont_post=True)   
 
@@ -576,11 +592,11 @@ def forecast_model_predict(input_data_dict: dict, logger: logging.Logger,
         now_precise = datetime.now(
             input_data_dict["retrieve_hass_conf"]["time_zone"]
         ).replace(second=0, microsecond=0)
-        if input_data_dict["retrieve_hass_conf"]["method_ts_round"] == "nearest":
+        if input_data_dict["retrieve_hass_conf"]['method_ts_round'] == "nearest":
             idx_closest = predictions.index.get_indexer([now_precise], method="nearest")[0]
-        elif input_data_dict["retrieve_hass_conf"]["method_ts_round"] == "first":
+        elif input_data_dict["retrieve_hass_conf"]['method_ts_round'] == "first":
             idx_closest = predictions.index.get_indexer([now_precise], method="ffill")[0]
-        elif input_data_dict["retrieve_hass_conf"]["method_ts_round"] == "last":
+        elif input_data_dict["retrieve_hass_conf"]['method_ts_round'] == "last":
             idx_closest = predictions.index.get_indexer([now_precise], method="bfill")[0]
         if idx_closest == -1:
             idx_closest = predictions.index.get_indexer([now_precise], method="nearest")[0]
@@ -676,7 +692,9 @@ def regressor_model_fit(input_data_dict: dict, logger: logging.Logger,
     # The MLRegressor object
     mlr = MLRegressor(data, model_type, regression_model, features, target, timestamp, logger)
     # Fit the ML model
-    mlr.fit(date_features=date_features)
+    fit = mlr.fit(date_features=date_features)
+    if not fit:
+        return False
     # Save model
     if not debug:
         filename = model_type + "_mlr.pkl"
@@ -757,10 +775,13 @@ def publish_data(input_data_dict: dict, logger: logging.Logger,
 
     """
     logger.info("Publishing data to HASS instance")
-    if not isinstance(input_data_dict["params"],dict):
-        params = json.loads(input_data_dict["params"])
-    else:
-        params = input_data_dict["params"]
+    if input_data_dict:
+        if not isinstance(input_data_dict.get("params",{}),dict):
+            params = json.loads(input_data_dict["params"])
+        else:
+            params = input_data_dict.get("params",{})
+
+            
     # Check if a day ahead optimization has been performed (read CSV file)
     if save_data_to_file:
         today = datetime.now(timezone.utc).replace(
@@ -807,17 +828,17 @@ def publish_data(input_data_dict: dict, logger: logging.Logger,
             opt_res_latest = pd.read_csv(
                 input_data_dict['emhass_conf']['data_path'] / filename, index_col='timestamp')
             opt_res_latest.index = pd.to_datetime(opt_res_latest.index)
-            opt_res_latest.index.freq = input_data_dict["retrieve_hass_conf"]["freq"]
+            opt_res_latest.index.freq = input_data_dict["retrieve_hass_conf"]['optimization_time_step']
     # Estimate the current index
     now_precise = datetime.now(
         input_data_dict["retrieve_hass_conf"]["time_zone"]
     ).replace(second=0, microsecond=0)
-    if input_data_dict["retrieve_hass_conf"]["method_ts_round"] == "nearest":
+    if input_data_dict["retrieve_hass_conf"]['method_ts_round'] == "nearest":
         idx_closest = opt_res_latest.index.get_indexer([now_precise], method="nearest")[0]
-    elif input_data_dict["retrieve_hass_conf"]["method_ts_round"] == "first":
+    elif input_data_dict["retrieve_hass_conf"]['method_ts_round'] == "first":
         idx_closest = opt_res_latest.index.get_indexer(
             [now_precise], method="ffill")[0]
-    elif input_data_dict["retrieve_hass_conf"]["method_ts_round"] == "last":
+    elif input_data_dict["retrieve_hass_conf"]['method_ts_round'] == "last":
         idx_closest = opt_res_latest.index.get_indexer(
             [now_precise], method="bfill")[0]
     if idx_closest == -1:
@@ -885,7 +906,7 @@ def publish_data(input_data_dict: dict, logger: logging.Logger,
     custom_deferrable_forecast_id = params["passed_data"][
         "custom_deferrable_forecast_id"
     ]
-    for k in range(input_data_dict["opt"].optim_conf["num_def_loads"]):
+    for k in range(input_data_dict["opt"].optim_conf['number_of_deferrable_loads']):
         if "P_deferrable{}".format(k) not in opt_res_latest.columns:
             logger.error(
                 "P_deferrable{}".format(k)
@@ -908,7 +929,7 @@ def publish_data(input_data_dict: dict, logger: logging.Logger,
     custom_predicted_temperature_id = params["passed_data"][
         "custom_predicted_temperature_id"
     ]
-    for k in range(input_data_dict["opt"].optim_conf["num_def_loads"]):
+    for k in range(input_data_dict["opt"].optim_conf['number_of_deferrable_loads']):
         if "def_load_config" in input_data_dict["opt"].optim_conf.keys():
             if "thermal_config" in input_data_dict["opt"].optim_conf["def_load_config"][k]:
                 input_data_dict["rh"].post_data(
@@ -924,7 +945,7 @@ def publish_data(input_data_dict: dict, logger: logging.Logger,
                 )
                 cols_published = cols_published + ["predicted_temp_heater{}".format(k)]
     # Publish battery power
-    if input_data_dict["opt"].optim_conf["set_use_battery"]:
+    if input_data_dict["opt"].optim_conf['set_use_battery']:
         if "P_batt" not in opt_res_latest.columns:
             logger.error(
                 "P_batt was not found in results DataFrame. Optimization task may need to be relaunched or it did not converge to a solution.",
@@ -1053,7 +1074,7 @@ def continual_publish(input_data_dict: dict, entity_path: pathlib.Path, logger:
 
     """
     logger.info("Continual publish thread service started")
-    freq = input_data_dict['retrieve_hass_conf'].get("freq", pd.to_timedelta(1, "minutes"))
+    freq = input_data_dict['retrieve_hass_conf'].get('optimization_time_step', pd.to_timedelta(1, "minutes"))
     entity_path_contents = []
     while True:
         # Sleep for x seconds (using current time as a reference for time left)
@@ -1064,7 +1085,14 @@ def continual_publish(input_data_dict: dict, entity_path: pathlib.Path, logger:
             for entity in entity_path_contents:
                 if entity != "metadata.json":
                     # Call publish_json with entity file, build entity, and publish                     
-                    publish_json(entity, input_data_dict, entity_path, logger, "continual_publish")
+                    publish_json(entity, input_data_dict, entity_path, logger, 'continual_publish')
+             # Retrieve entity metadata from file
+            if os.path.isfile(entity_path / "metadata.json"):
+                with open(entity_path / "metadata.json", "r") as file:
+                    metadata = json.load(file)
+                    # Check if freq should be shorter
+                    if not metadata.get("lowest_time_step",None) == None:
+                        freq = pd.to_timedelta(metadata["lowest_time_step"], "minutes")
         pass 
     # This function should never return           
     return False 
@@ -1089,9 +1117,7 @@ def publish_json(entity: dict, input_data_dict: dict, entity_path: pathlib.Path,
     # Retrieve entity metadata from file
     if os.path.isfile(entity_path / "metadata.json"):
         with open(entity_path / "metadata.json", "r") as file:
-            metadata = json.load(file) 
-            if not metadata.get("lowest_freq",None) == None:
-                freq = pd.to_timedelta(metadata["lowest_freq"], "minutes")
+            metadata = json.load(file)
     else:
         logger.error("unable to located metadata.json in:" + entity_path)
         return False            
@@ -1105,18 +1131,18 @@ def publish_json(entity: dict, input_data_dict: dict, entity_path: pathlib.Path,
     entity_data.columns = [metadata[entity_id]["name"]]
     entity_data.index.name = "timestamp"
     entity_data.index = pd.to_datetime(entity_data.index).tz_convert(input_data_dict["retrieve_hass_conf"]["time_zone"])
-    entity_data.index.freq = pd.to_timedelta(int(metadata[entity_id]["freq"]), "minutes")    
+    entity_data.index.freq = pd.to_timedelta(int(metadata[entity_id]['optimization_time_step']), "minutes")    
     # Calculate the current state value
-    if input_data_dict["retrieve_hass_conf"]["method_ts_round"] == "nearest":
+    if input_data_dict["retrieve_hass_conf"]['method_ts_round'] == "nearest":
         idx_closest = entity_data.index.get_indexer([now_precise], method="nearest")[0]
-    elif input_data_dict["retrieve_hass_conf"]["method_ts_round"] == "first":
+    elif input_data_dict["retrieve_hass_conf"]['method_ts_round'] == "first":
         idx_closest = entity_data.index.get_indexer([now_precise], method="ffill")[0]
-    elif input_data_dict["retrieve_hass_conf"]["method_ts_round"] == "last":
+    elif input_data_dict["retrieve_hass_conf"]['method_ts_round'] == "last":
         idx_closest = entity_data.index.get_indexer([now_precise], method="bfill")[0]
     if idx_closest == -1:
         idx_closest = entity_data.index.get_indexer([now_precise], method="nearest")[0]
     # Call post data 
-    if reference == "continual_publish":
+    if reference == 'continual_publish':
         logger.debug("Auto Published sensor:")
         logger_levels = "DEBUG"
     else: 
@@ -1161,7 +1187,9 @@ def main():
     parser.add_argument('--action', type=str, help='Set the desired action, options are: perfect-optim, dayahead-optim,\
         naive-mpc-optim, publish-data, forecast-model-fit, forecast-model-predict, forecast-model-tune')
     parser.add_argument('--config', type=str,
-                        help='Define path to the config.yaml file')
+                        help='Define path to the config.json/defaults.json file')
+    parser.add_argument('--params', type=str, default=None,
+                     help='String of configuration parameters passed')
     parser.add_argument('--data', type=str,
                         help='Define path to the Data files (.csv & .pkl)')
     parser.add_argument('--root', type=str, help='Define path emhass root')
@@ -1169,19 +1197,19 @@ def main():
                         help='Define the type of cost function, options are: profit, cost, self-consumption')
     parser.add_argument('--log2file', type=strtobool, default='False',
                         help='Define if we should log to a file or not')
-    parser.add_argument('--params', type=str, default=None,
-                        help='Configuration parameters passed from data/options.json')
+    parser.add_argument('--secrets', type=str, default=None,
+                        help='Define secret parameter file (secrets_emhass.yaml) path')
     parser.add_argument('--runtimeparams', type=str, default=None,
                         help='Pass runtime optimization parameters as dictionnary')
     parser.add_argument('--debug', type=strtobool,
                         default='False', help='Use True for testing purposes')
     args = parser.parse_args()
+   
     # The path to the configuration files
     if args.config is not None:
         config_path = pathlib.Path(args.config)
     else:
-        config_path = pathlib.Path(
-            str(utils.get_root(__file__, num_parent=2) / 'config_emhass.yaml'))
+        config_path = pathlib.Path(str(utils.get_root(__file__, num_parent=3) / 'config.json'))
     if args.data is not None:
         data_path = pathlib.Path(args.data)
     else:
@@ -1189,30 +1217,50 @@ def main():
     if args.root is not None:
         root_path = pathlib.Path(args.root)
     else:
-        root_path = config_path.parent
+        root_path = utils.get_root(__file__, num_parent=1) 
+    if args.secrets is not None:
+        secrets_path = pathlib.Path(args.secrets)
+    else:
+        secrets_path = pathlib.Path(config_path.parent / 'secrets_emhass.yaml')   
+
+    associations_path = root_path / 'data/associations.csv'
+    defaults_path = root_path / 'data/config_defaults.json'
+   
     emhass_conf = {}
     emhass_conf['config_path'] = config_path
     emhass_conf['data_path'] = data_path
     emhass_conf['root_path'] = root_path
+    emhass_conf['associations_path'] = associations_path
+    emhass_conf['defaults_path'] = defaults_path
     # create logger
     logger, ch = utils.get_logger(
         __name__, emhass_conf, save_to_file=bool(args.log2file))
+   
+    # Check paths
     logger.debug("config path: " + str(config_path))
     logger.debug("data path: " + str(data_path))
     logger.debug("root path: " + str(root_path))
-    if not config_path.exists():
+    if not associations_path.exists():   
         logger.error(
-            "Could not find config_emhass.yaml file in: " + str(config_path))
-        logger.error("Try setting config file path with --config")
+            "Could not find associations.csv file in: " + str(associations_path))
+        logger.error("Try setting config file path with --associations")
         return False
+    if not config_path.exists():   
+        logger.warning(
+            "Could not find config.json file in: " + str(config_path))
+        logger.warning("Try setting config file path with --config")
+    if not secrets_path.exists():
+        logger.warning("Could not find secrets file in: " + str(secrets_path))
+        logger.warning("Try setting secrets file path with --secrets")
     if not os.path.isdir(data_path):
-        logger.error("Could not find data foulder in: " + str(data_path))
+        logger.error("Could not find data folder in: " + str(data_path))
         logger.error("Try setting data path with --data")
         return False
-    if not os.path.isdir(root_path / 'src'):
-        logger.error("Could not find emhass/src foulder in: " + str(root_path))
+    if not os.path.isdir(root_path):
+        logger.error("Could not find emhass/src folder in: " + str(root_path))
         logger.error("Try setting emhass root path with --root")
         return False
+    
     # Additional argument
     try:
         parser.add_argument(
@@ -1225,10 +1273,47 @@ def main():
         logger.info(
             "Version not found for emhass package. Or importlib exited with PackageNotFoundError.",
         )
-    # Setup parameters
+
+    # Setup config
+    config = {}
+    # Check if passed config file is yaml of json, build config accordingly
+    if config_path.exists():
+        config_file_ending = re.findall("(?<=\.).*$", str(config_path))
+        if len(config_file_ending) > 0:
+            match(config_file_ending[0]):
+                case "json":
+                    config = utils.build_config(emhass_conf,logger,defaults_path,config_path)
+                case "yaml":
+                    config = utils.build_config(emhass_conf,logger,defaults_path,config_path=config_path)
+                case "yml":
+                    config = utils.build_config(emhass_conf,logger,defaults_path,config_path=config_path)
+    # If unable to find config file, use only defaults_config.json
+    else:
+        logger.warning("Unable to obtain config.json file, building parameters with only defaults")
+        config = utils.build_config(emhass_conf,logger,defaults_path)
+    if type(config) is bool and not config:
+        raise Exception("Failed to find default config")
+    
+
+    # Obtain secrets from secrets_emhass.yaml?
+    params_secrets = {}
+    emhass_conf, built_secrets = utils.build_secrets(emhass_conf,logger,secrets_path=secrets_path)
+    params_secrets.update(built_secrets)
+
+    # Build params
+    params = utils.build_params(emhass_conf, params_secrets, config, logger)
+    if type(params) is bool:
+        raise Exception("A error has occurred while building parameters")  
+    # Add any passed params from args to params
+    if args.params:
+        params.update(json.loads(args.params))
+   
     input_data_dict = set_input_data_dict(emhass_conf,
-                                          args.costfun, args.params, args.runtimeparams, args.action,
+                                          args.costfun, json.dumps(params), args.runtimeparams, args.action,
                                           logger, args.debug)
+    if type(input_data_dict) is bool:
+        raise Exception("A error has occurred while creating action objects")  
+
     # Perform selected action
     if args.action == "perfect-optim":
         opt_res = perfect_forecast_optim(
diff --git a/src/emhass/data/associations.csv b/src/emhass/data/associations.csv
new file mode 100644
index 00000000..e6094557
--- /dev/null
+++ b/src/emhass/data/associations.csv
@@ -0,0 +1,61 @@
+config_categorie,legacy_parameter_name,parameter,list_name 
+retrieve_hass_conf,freq,optimization_time_step
+retrieve_hass_conf,days_to_retrieve,historic_days_to_retrieve
+retrieve_hass_conf,var_PV,sensor_power_photovoltaics
+retrieve_hass_conf,var_load,sensor_power_load_no_var_loads
+retrieve_hass_conf,load_negative,load_negative
+retrieve_hass_conf,set_zero_min,set_zero_min
+retrieve_hass_conf,var_replace_zero,sensor_replace_zero,list_sensor_replace_zero
+retrieve_hass_conf,var_interp,sensor_linear_interp,list_sensor_linear_interp
+retrieve_hass_conf,method_ts_round,method_ts_round
+retrieve_hass_conf,continual_publish,continual_publish
+params_secrets,time_zone,time_zone
+params_secrets,lat,Latitude
+params_secrets,lon,Longitude
+params_secrets,alt,Altitude
+optim_conf,set_use_battery,set_use_battery
+optim_conf,num_def_loads,number_of_deferrable_loads
+optim_conf,P_deferrable_nom,nominal_power_of_deferrable_loads,list_nominal_power_of_deferrable_loads
+optim_conf,def_total_hours,operating_hours_of_each_deferrable_load,list_operating_hours_of_each_deferrable_load
+optim_conf,treat_def_as_semi_cont,treat_deferrable_load_as_semi_cont,list_treat_deferrable_load_as_semi_cont
+optim_conf,set_def_constant,set_deferrable_load_single_constant,list_set_deferrable_load_single_constant
+optim_conf,def_start_penalty,set_deferrable_startup_penalty,list_set_deferrable_startup_penalty
+optim_conf,delta_forecast,delta_forecast_daily
+optim_conf,load_forecast_method,load_forecast_method
+optim_conf,load_cost_forecast_method,load_cost_forecast_method
+optim_conf,load_cost_hp,load_peak_hours_cost
+optim_conf,load_cost_hc,load_offpeak_hours_cost
+optim_conf,prod_price_forecast_method,production_price_forecast_method
+optim_conf,prod_sell_price,photovoltaic_production_sell_price
+optim_conf,set_total_pv_sell,set_total_pv_sell
+optim_conf,lp_solver,lp_solver
+optim_conf,lp_solver_path,lp_solver_path
+optim_conf,set_nocharge_from_grid,set_nocharge_from_grid
+optim_conf,set_nodischarge_to_grid,set_nodischarge_to_grid
+optim_conf,set_battery_dynamic,set_battery_dynamic
+optim_conf,battery_dynamic_max,battery_dynamic_max
+optim_conf,battery_dynamic_min,battery_dynamic_min
+optim_conf,weight_battery_discharge,weight_battery_discharge
+optim_conf,weight_battery_charge,weight_battery_charge
+optim_conf,weather_forecast_method,weather_forecast_method
+optim_conf,def_start_timestep,start_timesteps_of_each_deferrable_load,list_start_timesteps_of_each_deferrable_load
+optim_conf,def_end_timestep,end_timesteps_of_each_deferrable_load,list_end_timesteps_of_each_deferrable_load
+optim_conf,list_hp_periods,load_peak_hour_periods
+plant_conf,P_from_grid_max,maximum_power_from_grid
+plant_conf,P_to_grid_max,maximum_power_to_grid
+plant_conf,module_model,pv_module_model,list_pv_module_model
+plant_conf,inverter_model,pv_inverter_model,list_pv_inverter_model
+plant_conf,surface_tilt,surface_tilt,list_surface_tilt
+plant_conf,surface_azimuth,surface_azimuth,list_surface_azimuth
+plant_conf,modules_per_string,modules_per_string,list_modules_per_string
+plant_conf,strings_per_inverter,strings_per_inverter,list_strings_per_inverter
+plant_conf,inverter_is_hybrid,inverter_is_hybrid
+plant_conf,compute_curtailment,compute_curtailment
+plant_conf,Pd_max,battery_discharge_power_max
+plant_conf,Pc_max,battery_charge_power_max
+plant_conf,eta_disch,battery_discharge_efficiency
+plant_conf,eta_ch,battery_charge_efficiency
+plant_conf,Enom,battery_nominal_energy_capacity
+plant_conf,SOCmin,battery_minimum_state_of_charge
+plant_conf,SOCmax,battery_maximum_state_of_charge
+plant_conf,SOCtarget,battery_target_state_of_charge
\ No newline at end of file
diff --git a/src/emhass/data/config_defaults.json b/src/emhass/data/config_defaults.json
new file mode 100644
index 00000000..d1517e5e
--- /dev/null
+++ b/src/emhass/data/config_defaults.json
@@ -0,0 +1,117 @@
+{
+  "logging_level": "INFO",
+  "costfun": "profit",
+  "optimization_time_step": 30,
+  "historic_days_to_retrieve": 2,
+  "method_ts_round": "nearest",
+  "continual_publish": false,
+  "data_path": "default",
+  "set_total_pv_sell": false,
+  "lp_solver": "default",
+  "lp_solver_path": "empty",
+  "set_nocharge_from_grid": false,
+  "set_nodischarge_to_grid": true,
+  "set_battery_dynamic": false,
+  "battery_dynamic_max": 0.9,
+  "battery_dynamic_min": -0.9,
+  "weight_battery_discharge": 1.0,
+  "weight_battery_charge": 1.0,
+  "sensor_power_photovoltaics": "sensor.power_photovoltaics",
+  "sensor_power_load_no_var_loads": "sensor.power_load_no_var_loads",
+  "sensor_replace_zero": [
+    "sensor.power_photovoltaics",
+    "sensor.power_load_no_var_loads"
+  ],
+  "sensor_linear_interp": [
+    "sensor.power_photovoltaics",
+    "sensor.power_load_no_var_loads"
+  ],
+  "load_negative": false,
+  "set_zero_min": true,
+  "number_of_deferrable_loads": 2,
+  "nominal_power_of_deferrable_loads": [
+    3000.0,
+    750.0
+  ],
+  "operating_hours_of_each_deferrable_load": [
+    4,
+    0
+  ],
+  "weather_forecast_method": "scrapper",
+  "load_forecast_method": "naive",
+  "delta_forecast_daily": 1,
+  "load_cost_forecast_method": "hp_hc_periods",
+  "start_timesteps_of_each_deferrable_load": [
+    0,
+    0
+  ],
+  "end_timesteps_of_each_deferrable_load": [
+    0,
+    0
+  ],
+  "load_peak_hour_periods": {
+    "period_hp_1": [
+      {
+        "start": "02:54"
+      },
+      {
+        "end": "15:24"
+      }
+    ],
+    "period_hp_2": [
+      {
+        "start": "17:24"
+      },
+      {
+        "end": "20:24"
+      }
+    ]
+  },
+  "treat_deferrable_load_as_semi_cont": [
+    true,
+    true
+  ],
+  "set_deferrable_load_single_constant": [
+    false,
+    false
+  ],
+  "set_deferrable_startup_penalty": [
+    0.0,
+    0.0
+  ],
+  "load_peak_hours_cost": 0.1907,
+  "load_offpeak_hours_cost": 0.1419,
+  "production_price_forecast_method": "constant",
+  "photovoltaic_production_sell_price": 0.1419,
+  "maximum_power_from_grid": 9000,
+  "maximum_power_to_grid": 9000,
+  "pv_module_model": [
+    "CSUN_Eurasia_Energy_Systems_Industry_and_Trade_CSUN295_60M"
+  ],
+  "pv_inverter_model": [
+    "Fronius_International_GmbH__Fronius_Primo_5_0_1_208_240__240V_"
+  ],
+  "surface_tilt": [
+    30
+  ],
+  "surface_azimuth": [
+    205
+  ],
+  "modules_per_string": [
+    16
+  ],
+  "strings_per_inverter": [
+    1
+  ],
+  "inverter_is_hybrid": false,
+  "compute_curtailment": false,
+  "set_use_battery": false,
+  "battery_discharge_power_max": 1000,
+  "battery_charge_power_max": 1000,
+  "battery_discharge_efficiency": 0.95,
+  "battery_charge_efficiency": 0.95,
+  "battery_nominal_energy_capacity": 5000,
+  "battery_minimum_state_of_charge": 0.3,
+  "battery_maximum_state_of_charge": 0.9,
+  "battery_target_state_of_charge": 0.6
+}
\ No newline at end of file
diff --git a/src/emhass/forecast.py b/src/emhass/forecast.py
index 7058c220..366c6925 100644
--- a/src/emhass/forecast.py
+++ b/src/emhass/forecast.py
@@ -132,22 +132,24 @@ def __init__(self, retrieve_hass_conf: dict, optim_conf: dict, plant_conf: dict,
         self.retrieve_hass_conf = retrieve_hass_conf
         self.optim_conf = optim_conf
         self.plant_conf = plant_conf
-        self.freq = self.retrieve_hass_conf['freq']
+        self.freq = self.retrieve_hass_conf['optimization_time_step']
         self.time_zone = self.retrieve_hass_conf['time_zone']
         self.method_ts_round = self.retrieve_hass_conf['method_ts_round']
         self.timeStep = self.freq.seconds/3600 # in hours
         self.time_delta = pd.to_timedelta(opt_time_delta, "hours")
-        self.var_PV = self.retrieve_hass_conf['var_PV']
-        self.var_load = self.retrieve_hass_conf['var_load']
+        self.var_PV = self.retrieve_hass_conf['sensor_power_photovoltaics']
+        self.var_load = self.retrieve_hass_conf['sensor_power_load_no_var_loads']
         self.var_load_new = self.var_load+'_positive'
-        self.lat = self.retrieve_hass_conf['lat'] 
-        self.lon = self.retrieve_hass_conf['lon']
+        self.lat = self.retrieve_hass_conf['Latitude'] 
+        self.lon = self.retrieve_hass_conf['Longitude']
         self.emhass_conf = emhass_conf
         self.logger = logger
         self.get_data_from_file = get_data_from_file
         self.var_load_cost = 'unit_load_cost'
         self.var_prod_price = 'unit_prod_price'
-        if params is None:
+        if (params == None) or (params == "null"):
+            self.params = {}
+        elif type(params) is dict:
             self.params = params
         else:
             self.params = json.loads(params)
@@ -159,10 +161,10 @@ def __init__(self, retrieve_hass_conf: dict, optim_conf: dict, plant_conf: dict,
             self.start_forecast = pd.Timestamp(datetime.now(), tz=self.time_zone).replace(microsecond=0).ceil(freq=self.freq)
         else:
             self.logger.error("Wrong method_ts_round passed parameter")
-        self.end_forecast = (self.start_forecast + self.optim_conf['delta_forecast']).replace(microsecond=0)
+        self.end_forecast = (self.start_forecast + self.optim_conf['delta_forecast_daily']).replace(microsecond=0)
         self.forecast_dates = pd.date_range(start=self.start_forecast, 
                                             end=self.end_forecast-self.freq, 
-                                            freq=self.freq).round(self.freq, ambiguous='infer', nonexistent='shift_forward')
+                                            freq=self.freq, tz=self.time_zone).tz_convert('utc').round(self.freq, ambiguous='infer', nonexistent='shift_forward').tz_convert(self.time_zone)
         if params is not None:
             if 'prediction_horizon' in list(self.params['passed_data'].keys()):
                 if self.params['passed_data']['prediction_horizon'] is not None:
@@ -190,7 +192,7 @@ def get_weather_forecast(self, method: Optional[str] = 'scrapper',
             freq_scrap = pd.to_timedelta(60, "minutes") # The scrapping time step is 60min on clearoutside
             forecast_dates_scrap = pd.date_range(start=self.start_forecast,
                                                  end=self.end_forecast-freq_scrap, 
-                                                 freq=freq_scrap).round(freq_scrap, ambiguous='infer', nonexistent='shift_forward')
+                                                 freq=freq_scrap, tz=self.time_zone).tz_convert('utc').round(freq_scrap, ambiguous='infer', nonexistent='shift_forward').tz_convert(self.time_zone)
             # Using the clearoutside webpage
             response = get("https://clearoutside.com/forecast/"+str(round(self.lat, 2))+"/"+str(round(self.lon, 2))+"?desktop=true")
             '''import bz2 # Uncomment to save a serialized data for tests
@@ -226,9 +228,9 @@ def get_weather_forecast(self, method: Optional[str] = 'scrapper',
                 data['temp_air'], data['relative_humidity'])
         elif method == 'solcast': # using Solcast API
             # Check if weather_forecast_cache is true or if forecast_data file does not exist
-            if self.params["passed_data"]["weather_forecast_cache"] or not os.path.isfile(w_forecast_cache_path):
+            if not os.path.isfile(w_forecast_cache_path):
                 # Check if weather_forecast_cache_only is true, if so produce error for not finding cache file
-                if not self.params["passed_data"]["weather_forecast_cache_only"]:
+                if not self.params["passed_data"].get("weather_forecast_cache_only",False):
                     # Retrieve data from the Solcast API
                     if 'solcast_api_key' not in self.retrieve_hass_conf:
                         self.logger.error("The solcast_api_key parameter was not defined")
@@ -243,7 +245,7 @@ def get_weather_forecast(self, method: Optional[str] = 'scrapper',
                         }
                     days_solcast = int(len(self.forecast_dates)*self.freq.seconds/3600)
                     # If weather_forecast_cache, set request days as twice as long to avoid length issues (add a buffer) 
-                    if self.params["passed_data"]["weather_forecast_cache"]:
+                    if self.params["passed_data"].get("weather_forecast_cache",False):
                         days_solcast = min((days_solcast * 2), 336)
                     url = "https://api.solcast.com.au/rooftop_sites/"+self.retrieve_hass_conf['solcast_rooftop_id']+"/forecasts?hours="+str(days_solcast)
                     response = get(url, headers=headers)
@@ -269,7 +271,7 @@ def get_weather_forecast(self, method: Optional[str] = 'scrapper',
                         self.logger.error("Not enough data retried from Solcast service, try increasing the time step or use MPC.")
                     else:
                         # If runtime weather_forecast_cache is true save forecast result to file as cache
-                        if self.params["passed_data"]["weather_forecast_cache"]:
+                        if self.params["passed_data"].get("weather_forecast_cache",False):
                             # Add x2 forecast periods for cached results. This adds a extra delta_forecast amount of days for a buffer
                             cached_forecast_dates =  self.forecast_dates.union(pd.date_range(self.forecast_dates[-1], periods=(len(self.forecast_dates) +1), freq=self.freq)[1:])
                             cache_data_list = data_list[0:len(cached_forecast_dates)]
@@ -289,11 +291,11 @@ def get_weather_forecast(self, method: Optional[str] = 'scrapper',
                         data = pd.DataFrame.from_dict(data_dict)
                         # Define index
                         data.set_index('ts', inplace=True)
-            # Else, notify user to update cache
+                # Else, notify user to update cache
                 else:
                     self.logger.error("Unable to obtain Solcast cache file.")
                     self.logger.error("Try running optimization again with 'weather_forecast_cache_only': false")
-                    self.logger.error("Optionally, obtain new Solcast cache with runtime parameter 'weather_forecast_cache': true in an optimization, or run the `forecast-cache` action, to pull new data from Solcast and cache.")
+                    self.logger.error("Optionally, obtain new Solcast cache with runtime parameter 'weather_forecast_cache': true in an optimization, or run the `weather-forecast-cache` action, to pull new data from Solcast and cache.")
                     return False
             # Else, open stored weather_forecast_data.pkl file for previous forecast data (cached data)
             else:
@@ -301,7 +303,7 @@ def get_weather_forecast(self, method: Optional[str] = 'scrapper',
                     data = cPickle.load(file)
                     if not isinstance(data, pd.DataFrame) or len(data) < len(self.forecast_dates):
                         self.logger.error("There has been a error obtaining cached Solcast forecast data.")
-                        self.logger.error("Try running optimization again with 'weather_forecast_cache': true, or run action `forecast-cache`, to pull new data from Solcast and cache.")
+                        self.logger.error("Try running optimization again with 'weather_forecast_cache': true, or run action `weather-forecast-cache`, to pull new data from Solcast and cache.")
                         self.logger.warning("Removing old Solcast cache file. Next optimization will pull data from Solcast, unless 'weather_forecast_cache_only': true")
                         os.remove(w_forecast_cache_path)
                         return False
@@ -323,17 +325,17 @@ def get_weather_forecast(self, method: Optional[str] = 'scrapper',
             if  self.retrieve_hass_conf['solar_forecast_kwp'] == 0:
                 self.logger.warning("The solar_forecast_kwp parameter is set to zero, setting to default 5")
                 self.retrieve_hass_conf['solar_forecast_kwp'] = 5
-            if self.optim_conf['delta_forecast'].days > 1:
+            if self.optim_conf['delta_forecast_daily'].days > 1:
                 self.logger.warning("The free public tier for solar.forecast only provides one day forecasts")
                 self.logger.warning("Continuing with just the first day of data, the other days are filled with 0.0.")
-                self.logger.warning("Use the other available methods for delta_forecast > 1")
+                self.logger.warning("Use the other available methods for delta_forecast_daily > 1")
             headers = {
                 "Accept": "application/json"
                 }
             data = pd.DataFrame()
-            for i in range(len(self.plant_conf['module_model'])):
+            for i in range(len(self.plant_conf['pv_module_model'])):
                 url = "https://api.forecast.solar/estimate/"+str(round(self.lat, 2))+"/"+str(round(self.lon, 2))+\
-                    "/"+str(self.plant_conf["surface_tilt"][i])+"/"+str(self.plant_conf["surface_azimuth"][i]-180)+\
+                    "/"+str(self.plant_conf['surface_tilt'][i])+"/"+str(self.plant_conf['surface_azimuth'][i]-180)+\
                     "/"+str(self.retrieve_hass_conf["solar_forecast_kwp"])
                 response = get(url, headers=headers)
                 '''import bz2 # Uncomment to save a serialized data for tests
@@ -485,12 +487,12 @@ def get_power_from_weather(self, df_weather: pd.DataFrame,
                 cec_modules = cPickle.load(cec_modules)
                 cec_inverters = bz2.BZ2File(self.emhass_conf['root_path'] / 'data' /  'cec_inverters.pbz2', "rb")
                 cec_inverters = cPickle.load(cec_inverters)
-                if type(self.plant_conf['module_model']) == list:
+                if type(self.plant_conf['pv_module_model']) == list:
                     P_PV_forecast = pd.Series(0, index=df_weather.index)
-                    for i in range(len(self.plant_conf['module_model'])):
+                    for i in range(len(self.plant_conf['pv_module_model'])):
                         # Selecting correct module and inverter
-                        module = cec_modules[self.plant_conf['module_model'][i]]
-                        inverter = cec_inverters[self.plant_conf['inverter_model'][i]]
+                        module = cec_modules[self.plant_conf['pv_module_model'][i]]
+                        inverter = cec_inverters[self.plant_conf['pv_inverter_model'][i]]
                         # Building the PV system in PVLib
                         system = PVSystem(surface_tilt=self.plant_conf['surface_tilt'][i], 
                                         surface_azimuth=self.plant_conf['surface_azimuth'][i],
@@ -506,8 +508,8 @@ def get_power_from_weather(self, df_weather: pd.DataFrame,
                         P_PV_forecast = P_PV_forecast + mc.results.ac
                 else:
                     # Selecting correct module and inverter
-                    module = cec_modules[self.plant_conf['module_model']]
-                    inverter = cec_inverters[self.plant_conf['inverter_model']]
+                    module = cec_modules[self.plant_conf['pv_module_model']]
+                    inverter = cec_inverters[self.plant_conf['pv_inverter_model']]
                     # Building the PV system in PVLib
                     system = PVSystem(surface_tilt=self.plant_conf['surface_tilt'], 
                                     surface_azimuth=self.plant_conf['surface_azimuth'],
@@ -544,10 +546,10 @@ def get_forecast_days_csv(self, timedelta_days: Optional[int] = 1) -> pd.date_ra
             start_forecast_csv = pd.Timestamp(datetime.now(), tz=self.time_zone).replace(microsecond=0).ceil(freq=self.freq)
         else:
             self.logger.error("Wrong method_ts_round passed parameter")
-        end_forecast_csv = (start_forecast_csv + self.optim_conf['delta_forecast']).replace(microsecond=0)
+        end_forecast_csv = (start_forecast_csv + self.optim_conf['delta_forecast_daily']).replace(microsecond=0)
         forecast_dates_csv = pd.date_range(start=start_forecast_csv, 
                                            end=end_forecast_csv+timedelta(days=timedelta_days)-self.freq, 
-                                           freq=self.freq).round(self.freq, ambiguous='infer', nonexistent='shift_forward')
+                                           freq=self.freq, tz=self.time_zone).tz_convert('utc').round(self.freq, ambiguous='infer', nonexistent='shift_forward').tz_convert(self.time_zone)
         if self.params is not None:
             if 'prediction_horizon' in list(self.params['passed_data'].keys()):
                 if self.params['passed_data']['prediction_horizon'] is not None:
@@ -561,7 +563,7 @@ def get_forecast_out_from_csv_or_list(self, df_final: pd.DataFrame, forecast_dat
         Get the forecast data as a DataFrame from a CSV file. 
         
         The data contained in the CSV file should be a 24h forecast with the same frequency as 
-        the main 'freq' parameter in the configuration file. The timestamp will not be used and 
+        the main 'optimization_time_step' parameter in the configuration file. The timestamp will not be used and 
         a new DateTimeIndex is generated to fit the timestamp index of the input data in 'df_final'.
         
         :param df_final: The DataFrame containing the input data.
@@ -695,7 +697,7 @@ def get_load_forecast(self, days_min_load_forecast: Optional[int] = 3, method: O
                 with open(filename_path, 'rb') as inp:
                     rh.df_final, days_list, var_list = pickle.load(inp)
                     self.var_load = var_list[0]
-                    self.retrieve_hass_conf['var_load'] = self.var_load
+                    self.retrieve_hass_conf['sensor_power_load_no_var_loads'] = self.var_load
                     var_interp = [var_list[0]]
                     self.var_list = [var_list[0]]
                     self.var_load_new = self.var_load+'_positive'
@@ -704,13 +706,13 @@ def get_load_forecast(self, days_min_load_forecast: Optional[int] = 3, method: O
                 if not rh.get_data(days_list, var_list):
                     return False
             if  not rh.prepare_data(
-                self.retrieve_hass_conf['var_load'], load_negative = self.retrieve_hass_conf['load_negative'],
+                self.retrieve_hass_conf['sensor_power_load_no_var_loads'], load_negative = self.retrieve_hass_conf['load_negative'],
                 set_zero_min = self.retrieve_hass_conf['set_zero_min'], 
                 var_replace_zero = var_replace_zero, var_interp = var_interp):
                 return False
             df = rh.df_final.copy()[[self.var_load_new]]
         if method == 'naive': # using a naive approach
-            mask_forecast_out = (df.index > days_list[-1] - self.optim_conf['delta_forecast'])
+            mask_forecast_out = (df.index > days_list[-1] - self.optim_conf['delta_forecast_daily'])
             forecast_out = df.copy().loc[mask_forecast_out]
             forecast_out = forecast_out.rename(columns={self.var_load_new: 'yhat'})
             # Force forecast_out length to avoid mismatches
@@ -812,13 +814,13 @@ def get_load_cost_forecast(self, df_final: pd.DataFrame, method: Optional[str] =
         """
         csv_path  = self.emhass_conf['data_path'] / csv_path
         if method == 'hp_hc_periods':
-            df_final[self.var_load_cost] = self.optim_conf['load_cost_hc']
+            df_final[self.var_load_cost] = self.optim_conf['load_offpeak_hours_cost']
             list_df_hp = []
-            for key, period_hp in self.optim_conf['list_hp_periods'].items():
+            for key, period_hp in self.optim_conf['load_peak_hour_periods'].items():
                 list_df_hp.append(df_final[self.var_load_cost].between_time(
                     period_hp[0]['start'], period_hp[1]['end']))
             for df_hp in list_df_hp:
-                df_final.loc[df_hp.index, self.var_load_cost] = self.optim_conf['load_cost_hp']
+                df_final.loc[df_hp.index, self.var_load_cost] = self.optim_conf['load_peak_hours_cost']
         elif method == 'csv':
             forecast_dates_csv = self.get_forecast_days_csv(timedelta_days=0)
             forecast_out = self.get_forecast_out_from_csv_or_list(
@@ -871,7 +873,7 @@ def get_prod_price_forecast(self, df_final: pd.DataFrame, method: Optional[str]
         """
         csv_path  = self.emhass_conf['data_path'] / csv_path
         if method == 'constant':
-            df_final[self.var_prod_price] = self.optim_conf['prod_sell_price']
+            df_final[self.var_prod_price] = self.optim_conf['photovoltaic_production_sell_price']
         elif method == 'csv':
             forecast_dates_csv = self.get_forecast_days_csv(timedelta_days=0)
             forecast_out = self.get_forecast_out_from_csv_or_list(
diff --git a/src/emhass/machine_learning_forecaster.py b/src/emhass/machine_learning_forecaster.py
index ea44a4ee..3691ead6 100644
--- a/src/emhass/machine_learning_forecaster.py
+++ b/src/emhass/machine_learning_forecaster.py
@@ -141,7 +141,8 @@ def fit(self, split_date_delta: Optional[str] = '48h', perform_backtest: Optiona
         elif self.sklearn_model == 'KNeighborsRegressor':
             base_model = KNeighborsRegressor()
         else:
-            self.logger.error("Passed sklearn model "+self.sklearn_model+" is not valid")
+            self.logger.error("Passed sklearn model "+self.sklearn_model+" is not valid. Defaulting to KNeighborsRegressor")
+            base_model = KNeighborsRegressor()
         # Define the forecaster object
         self.forecaster = ForecasterAutoreg(
             regressor = base_model,
diff --git a/src/emhass/machine_learning_regressor.py b/src/emhass/machine_learning_regressor.py
index a44df8db..9e443c6b 100644
--- a/src/emhass/machine_learning_regressor.py
+++ b/src/emhass/machine_learning_regressor.py
@@ -176,15 +176,17 @@ def get_regression_model(self: MLRegressor) -> tuple[str, str]:
                 "Passed model %s is not valid",
                 self.regression_model,
             )
-            return None
+            return None, None
         return base_model, param_grid
 
-    def fit(self: MLRegressor, date_features: list | None = None) -> None:
+    def fit(self: MLRegressor, date_features: list | None = None) -> bool:
         r"""Fit the model using the provided data.
 
         :param date_features: A list of 'date_features' to take into account when \
             fitting the model.
         :type data: list
+        :return: bool if successful
+        :rtype: bool
         """
         self.logger.info("Performing a MLRegressor fit for %s", self.model_type)
         self.data_exo = pd.DataFrame(self.data)
@@ -217,6 +219,8 @@ def fit(self: MLRegressor, date_features: list | None = None) -> None:
         X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
         self.steps = len(X_test)
         base_model, param_grid = self.get_regression_model()
+        if base_model is None:
+            return False
         self.model = make_pipeline(StandardScaler(), base_model)
         # Create a grid search object
         self.grid_search = GridSearchCV(self.model, param_grid, cv=5, scoring="neg_mean_squared_error",
@@ -235,6 +239,7 @@ def fit(self: MLRegressor, date_features: list | None = None) -> None:
             "Prediction R2 score of fitted model on test data: %s",
             pred_metric,
         )
+        return True
 
     def predict(self: MLRegressor, new_values: list) -> np.ndarray:
         """Predict a new value.
diff --git a/src/emhass/optimization.py b/src/emhass/optimization.py
index 3a646fb7..1af1cf00 100644
--- a/src/emhass/optimization.py
+++ b/src/emhass/optimization.py
@@ -66,12 +66,12 @@ def __init__(self, retrieve_hass_conf: dict, optim_conf: dict, plant_conf: dict,
         self.retrieve_hass_conf = retrieve_hass_conf
         self.optim_conf = optim_conf
         self.plant_conf = plant_conf
-        self.freq = self.retrieve_hass_conf['freq']
+        self.freq = self.retrieve_hass_conf['optimization_time_step']
         self.time_zone = self.retrieve_hass_conf['time_zone']
         self.timeStep = self.freq.seconds/3600 # in hours
         self.time_delta = pd.to_timedelta(opt_time_delta, "hours") # The period of optimization
-        self.var_PV = self.retrieve_hass_conf['var_PV']
-        self.var_load = self.retrieve_hass_conf['var_load']
+        self.var_PV = self.retrieve_hass_conf['sensor_power_photovoltaics']
+        self.var_load = self.retrieve_hass_conf['sensor_power_load_no_var_loads']
         self.var_load_new = self.var_load+'_positive'
         self.costfun = costfun
         # self.emhass_conf = emhass_conf
@@ -143,18 +143,18 @@ def perform_optimization(self, data_opt: pd.DataFrame, P_PV: np.array, P_load: n
                 if soc_final is not None:
                     soc_init = soc_final
                 else:
-                    soc_init = self.plant_conf['SOCtarget']
+                    soc_init = self.plant_conf['battery_target_state_of_charge']
             if soc_final is None:
                 if soc_init is not None:
                     soc_final = soc_init
                 else:
-                    soc_final = self.plant_conf['SOCtarget']
+                    soc_final = self.plant_conf['battery_target_state_of_charge']
         if def_total_hours is None:
-            def_total_hours = self.optim_conf['def_total_hours']
+            def_total_hours = self.optim_conf['operating_hours_of_each_deferrable_load']
         if def_start_timestep is None:
-            def_start_timestep = self.optim_conf['def_start_timestep']
+            def_start_timestep = self.optim_conf['start_timesteps_of_each_deferrable_load']
         if def_end_timestep is None:
-            def_end_timestep = self.optim_conf['def_end_timestep']
+            def_end_timestep = self.optim_conf['end_timesteps_of_each_deferrable_load']
         type_self_conso = 'bigm' # maxmin
 
         #### The LP problem using Pulp ####
@@ -166,19 +166,19 @@ def perform_optimization(self, data_opt: pd.DataFrame, P_PV: np.array, P_load: n
 
         ## Add decision variables
         P_grid_neg  = {(i):plp.LpVariable(cat='Continuous',
-                                          lowBound=-self.plant_conf['P_to_grid_max'], upBound=0,
+                                          lowBound=-self.plant_conf['maximum_power_to_grid'], upBound=0,
                                           name="P_grid_neg{}".format(i)) for i in set_I}
         P_grid_pos  = {(i):plp.LpVariable(cat='Continuous',
-                                          lowBound=0, upBound=self.plant_conf['P_from_grid_max'],
+                                          lowBound=0, upBound=self.plant_conf['maximum_power_from_grid'],
                                           name="P_grid_pos{}".format(i)) for i in set_I}
         P_deferrable = []
         P_def_bin1 = []
-        for k in range(self.optim_conf['num_def_loads']):
-            if type(self.optim_conf['P_deferrable_nom'][k]) == list:
-                upBound = np.max(self.optim_conf['P_deferrable_nom'][k])
+        for k in range(self.optim_conf['number_of_deferrable_loads']):
+            if type(self.optim_conf['nominal_power_of_deferrable_loads'][k]) == list:
+                upBound = np.max(self.optim_conf['nominal_power_of_deferrable_loads'][k])
             else:
-                upBound = self.optim_conf['P_deferrable_nom'][k]
-            if self.optim_conf['treat_def_as_semi_cont'][k]:
+                upBound = self.optim_conf['nominal_power_of_deferrable_loads'][k]
+            if self.optim_conf['treat_deferrable_load_as_semi_cont'][k]:
                 P_deferrable.append({(i):plp.LpVariable(cat='Continuous',
                                                         name="P_deferrable{}_{}".format(k, i)) for i in set_I})
             else:
@@ -189,7 +189,7 @@ def perform_optimization(self, data_opt: pd.DataFrame, P_PV: np.array, P_load: n
                                                   name="P_def{}_bin1_{}".format(k, i)) for i in set_I})
         P_def_start = []
         P_def_bin2 = []
-        for k in range(self.optim_conf['num_def_loads']):
+        for k in range(self.optim_conf['number_of_deferrable_loads']):
             P_def_start.append({(i):plp.LpVariable(cat='Binary',
                                                    name="P_def{}_start_{}".format(k, i)) for i in set_I})
             P_def_bin2.append({(i):plp.LpVariable(cat='Binary',
@@ -200,10 +200,10 @@ def perform_optimization(self, data_opt: pd.DataFrame, P_PV: np.array, P_load: n
                                  name="E_{}".format(i)) for i in set_I}
         if self.optim_conf['set_use_battery']:
             P_sto_pos  = {(i):plp.LpVariable(cat='Continuous', 
-                                             lowBound=0, upBound=self.plant_conf['Pd_max'],
+                                             lowBound=0, upBound=self.plant_conf['battery_discharge_power_max'],
                                              name="P_sto_pos_{0}".format(i)) for i in set_I}
             P_sto_neg  = {(i):plp.LpVariable(cat='Continuous', 
-                                             lowBound=-self.plant_conf['Pc_max'], upBound=0,
+                                             lowBound=-self.plant_conf['battery_charge_power_max'], upBound=0,
                                              name="P_sto_neg_{0}".format(i)) for i in set_I}
         else:
             P_sto_pos  = {(i):i*0 for i in set_I}
@@ -221,7 +221,7 @@ def perform_optimization(self, data_opt: pd.DataFrame, P_PV: np.array, P_load: n
         ## Define objective
         P_def_sum= []
         for i in set_I:
-            P_def_sum.append(plp.lpSum(P_deferrable[k][i] for k in range(self.optim_conf['num_def_loads'])))
+            P_def_sum.append(plp.lpSum(P_deferrable[k][i] for k in range(self.optim_conf['number_of_deferrable_loads'])))
         if self.costfun == 'profit':
             if self.optim_conf['set_total_pv_sell']:
                 objective = plp.lpSum(-0.001*self.timeStep*(unit_load_cost[i]*(P_load[i] + P_def_sum[i]) + \
@@ -252,12 +252,12 @@ def perform_optimization(self, data_opt: pd.DataFrame, P_PV: np.array, P_load: n
                     self.optim_conf['weight_battery_charge']*P_sto_neg[i]) for i in set_I)
 
         # Add term penalizing each startup where configured
-        if ("def_start_penalty" in self.optim_conf and self.optim_conf["def_start_penalty"]):
-            for k in range(self.optim_conf["num_def_loads"]):
-                if (len(self.optim_conf["def_start_penalty"]) > k and self.optim_conf["def_start_penalty"][k]):
+        if ('set_deferrable_startup_penalty' in self.optim_conf and self.optim_conf['set_deferrable_startup_penalty']):
+            for k in range(self.optim_conf['number_of_deferrable_loads']):
+                if (len(self.optim_conf['set_deferrable_startup_penalty']) > k and self.optim_conf['set_deferrable_startup_penalty'][k]):
                     objective = objective + plp.lpSum(
-                        -0.001 * self.timeStep * self.optim_conf["def_start_penalty"][k] * P_def_start[k][i] *\
-                            unit_load_cost[i] * self.optim_conf['P_deferrable_nom'][k]
+                        -0.001 * self.timeStep * self.optim_conf['set_deferrable_startup_penalty'][k] * P_def_start[k][i] *\
+                            unit_load_cost[i] * self.optim_conf['nominal_power_of_deferrable_loads'][k]
                         for i in set_I)
 
         opt_model.setObjective(objective)
@@ -288,24 +288,24 @@ def perform_optimization(self, data_opt: pd.DataFrame, P_PV: np.array, P_load: n
                     for i in set_I}
         
         # Constraint for hybrid inverter and curtailment cases
-        if type(self.plant_conf['module_model']) == list:
+        if type(self.plant_conf['pv_module_model']) == list:
             P_nom_inverter = 0.0
-            for i in range(len(self.plant_conf['inverter_model'])):
-                if type(self.plant_conf['inverter_model'][i]) == str:
+            for i in range(len(self.plant_conf['pv_inverter_model'])):
+                if type(self.plant_conf['pv_inverter_model'][i]) == str:
                     cec_inverters = bz2.BZ2File(pathlib.Path(__file__).parent / 'data/cec_inverters.pbz2', "rb")
                     cec_inverters = cPickle.load(cec_inverters)
-                    inverter = cec_inverters[self.plant_conf['inverter_model'][i]]
+                    inverter = cec_inverters[self.plant_conf['pv_inverter_model'][i]]
                     P_nom_inverter += inverter.Paco
                 else:
-                    P_nom_inverter += self.plant_conf['inverter_model'][i]
+                    P_nom_inverter += self.plant_conf['pv_inverter_model'][i]
         else:
-            if type(self.plant_conf['inverter_model'][i]) == str:
+            if type(self.plant_conf['pv_inverter_model'][i]) == str:
                 cec_inverters = bz2.BZ2File(pathlib.Path(__file__).parent / 'data/cec_inverters.pbz2', "rb")
                 cec_inverters = cPickle.load(cec_inverters)
-                inverter = cec_inverters[self.plant_conf['inverter_model']]
+                inverter = cec_inverters[self.plant_conf['pv_inverter_model']]
                 P_nom_inverter = inverter.Paco
             else:
-                P_nom_inverter = self.plant_conf['inverter_model']
+                P_nom_inverter = self.plant_conf['pv_inverter_model']
         if self.plant_conf['inverter_is_hybrid']:
             constraints.update({"constraint_hybrid_inverter1_{}".format(i) :
                 plp.LpConstraint(
@@ -347,26 +347,26 @@ def perform_optimization(self, data_opt: pd.DataFrame, P_PV: np.array, P_load: n
         # Avoid injecting and consuming from grid at the same time
         constraints.update({"constraint_pgridpos_{}".format(i) : 
             plp.LpConstraint(
-                e = P_grid_pos[i] - self.plant_conf['P_from_grid_max']*D[i],
+                e = P_grid_pos[i] - self.plant_conf['maximum_power_from_grid']*D[i],
                 sense = plp.LpConstraintLE,
                 rhs = 0)
             for i in set_I})
         constraints.update({"constraint_pgridneg_{}".format(i) : 
             plp.LpConstraint(
-                e = -P_grid_neg[i] - self.plant_conf['P_to_grid_max']*(1-D[i]),
+                e = -P_grid_neg[i] - self.plant_conf['maximum_power_to_grid']*(1-D[i]),
                 sense = plp.LpConstraintLE,
                 rhs = 0)
             for i in set_I})
 
         # Treat deferrable loads constraints
         predicted_temps = {}
-        for k in range(self.optim_conf['num_def_loads']):
+        for k in range(self.optim_conf['number_of_deferrable_loads']):
             
-            if type(self.optim_conf['P_deferrable_nom'][k]) == list:
+            if type(self.optim_conf['nominal_power_of_deferrable_loads'][k]) == list:
                 # Constraint for sequence of deferrable
                 # WARNING: This is experimental, formulation seems correct but feasibility problems.
                 # Probably uncomptabile with other constraints
-                power_sequence = self.optim_conf['P_deferrable_nom'][k]
+                power_sequence = self.optim_conf['nominal_power_of_deferrable_loads'][k]
                 sequence_length = len(power_sequence)
                 def create_matrix(input_list, n):
                     matrix = []
@@ -421,7 +421,7 @@ def create_matrix(input_list, n):
                                 continue
                             predicted_temp.append(
                                 predicted_temp[I-1]
-                                + (P_deferrable[k][I-1] * (heating_rate * self.timeStep / self.optim_conf['P_deferrable_nom'][k]))
+                                + (P_deferrable[k][I-1] * (heating_rate * self.timeStep / self.optim_conf['nominal_power_of_deferrable_loads'][k]))
                                 - (cooling_constant * (predicted_temp[I-1] - outdoor_temperature_forecast[I-1])))
                             if len(desired_temperatures) > I and desired_temperatures[I]:
                                 constraints.update({"constraint_defload{}_temperature_{}".format(k, I):
@@ -448,7 +448,7 @@ def create_matrix(input_list, n):
                         plp.LpConstraint(
                             e = plp.lpSum(P_deferrable[k][i]*self.timeStep for i in set_I),
                             sense = plp.LpConstraintEQ,
-                            rhs = def_total_hours[k]*self.optim_conf['P_deferrable_nom'][k])
+                            rhs = def_total_hours[k]*self.optim_conf['nominal_power_of_deferrable_loads'][k])
                         })
                 
             # Ensure deferrable loads consume energy between def_start_timestep & def_end_timestep
@@ -516,7 +516,7 @@ def create_matrix(input_list, n):
                 for i in set_I})
             
             # Treat deferrable as a fixed value variable with just one startup
-            if self.optim_conf['set_def_constant'][k]:
+            if self.optim_conf['set_deferrable_load_single_constant'][k]:
                 # P_def_start[i] must be 1 for exactly 1 value of i
                 constraints.update({"constraint_pdef{}_start4".format(k) :
                 plp.LpConstraint(
@@ -533,23 +533,23 @@ def create_matrix(input_list, n):
                 })
         
             # Treat deferrable load as a semi-continuous variable
-            if self.optim_conf['treat_def_as_semi_cont'][k]:
+            if self.optim_conf['treat_deferrable_load_as_semi_cont'][k]:
                 constraints.update({"constraint_pdef{}_semicont1_{}".format(k, i) : 
                     plp.LpConstraint(
-                        e=P_deferrable[k][i] - self.optim_conf['P_deferrable_nom'][k]*P_def_bin1[k][i],
+                        e=P_deferrable[k][i] - self.optim_conf['nominal_power_of_deferrable_loads'][k]*P_def_bin1[k][i],
                         sense=plp.LpConstraintGE,
                         rhs=0)
                     for i in set_I})
                 constraints.update({"constraint_pdef{}_semicont2_{}".format(k, i) :
                     plp.LpConstraint(
-                        e=P_deferrable[k][i] - self.optim_conf['P_deferrable_nom'][k]*P_def_bin1[k][i],
+                        e=P_deferrable[k][i] - self.optim_conf['nominal_power_of_deferrable_loads'][k]*P_def_bin1[k][i],
                         sense=plp.LpConstraintLE,
                         rhs=0)
                     for i in set_I})
         
         
             # Treat the number of starts for a deferrable load (old method, kept here just in case)
-            # if self.optim_conf['set_def_constant'][k]:
+            # if self.optim_conf['set_deferrable_load_single_constant'][k]:
             #     constraints.update({"constraint_pdef{}_start1_{}".format(k, i) : 
             #         plp.LpConstraint(
             #             e=P_deferrable[k][i] - P_def_bin2[k][i]*M,
@@ -592,53 +592,53 @@ def create_matrix(input_list, n):
                 constraints.update({"constraint_pos_batt_dynamic_max_{}".format(i) : 
                     plp.LpConstraint(e = P_sto_pos[i+1] - P_sto_pos[i], 
                                      sense = plp.LpConstraintLE, 
-                                     rhs = self.timeStep*self.optim_conf['battery_dynamic_max']*self.plant_conf['Pd_max']) 
+                                     rhs = self.timeStep*self.optim_conf['battery_dynamic_max']*self.plant_conf['battery_discharge_power_max']) 
                     for i in range(n-1)})
                 constraints.update({"constraint_pos_batt_dynamic_min_{}".format(i) : 
                     plp.LpConstraint(e = P_sto_pos[i+1] - P_sto_pos[i], 
                                      sense = plp.LpConstraintGE, 
-                                     rhs = self.timeStep*self.optim_conf['battery_dynamic_min']*self.plant_conf['Pd_max']) 
+                                     rhs = self.timeStep*self.optim_conf['battery_dynamic_min']*self.plant_conf['battery_discharge_power_max']) 
                     for i in range(n-1)})
                 constraints.update({"constraint_neg_batt_dynamic_max_{}".format(i) : 
                     plp.LpConstraint(e = P_sto_neg[i+1] - P_sto_neg[i], 
                                      sense = plp.LpConstraintLE, 
-                                     rhs = self.timeStep*self.optim_conf['battery_dynamic_max']*self.plant_conf['Pc_max']) 
+                                     rhs = self.timeStep*self.optim_conf['battery_dynamic_max']*self.plant_conf['battery_charge_power_max']) 
                     for i in range(n-1)})
                 constraints.update({"constraint_neg_batt_dynamic_min_{}".format(i) : 
                     plp.LpConstraint(e = P_sto_neg[i+1] - P_sto_neg[i], 
                                      sense = plp.LpConstraintGE, 
-                                     rhs = self.timeStep*self.optim_conf['battery_dynamic_min']*self.plant_conf['Pc_max']) 
+                                     rhs = self.timeStep*self.optim_conf['battery_dynamic_min']*self.plant_conf['battery_charge_power_max']) 
                     for i in range(n-1)})
             # Then the classic battery constraints
             constraints.update({"constraint_pstopos_{}".format(i) : 
                 plp.LpConstraint(
-                    e=P_sto_pos[i] - self.plant_conf['eta_disch']*self.plant_conf['Pd_max']*E[i],
+                    e=P_sto_pos[i] - self.plant_conf['battery_discharge_efficiency']*self.plant_conf['battery_discharge_power_max']*E[i],
                     sense=plp.LpConstraintLE,
                     rhs=0)
                 for i in set_I})
             constraints.update({"constraint_pstoneg_{}".format(i) : 
                 plp.LpConstraint(
-                    e=-P_sto_neg[i] - (1/self.plant_conf['eta_ch'])*self.plant_conf['Pc_max']*(1-E[i]),
+                    e=-P_sto_neg[i] - (1/self.plant_conf['battery_charge_efficiency'])*self.plant_conf['battery_charge_power_max']*(1-E[i]),
                     sense=plp.LpConstraintLE,
                     rhs=0)
                 for i in set_I})
             constraints.update({"constraint_socmax_{}".format(i) : 
                 plp.LpConstraint(
-                    e=-plp.lpSum(P_sto_pos[j]*(1/self.plant_conf['eta_disch']) + self.plant_conf['eta_ch']*P_sto_neg[j] for j in range(i)),
+                    e=-plp.lpSum(P_sto_pos[j]*(1/self.plant_conf['battery_discharge_efficiency']) + self.plant_conf['battery_charge_efficiency']*P_sto_neg[j] for j in range(i)),
                     sense=plp.LpConstraintLE,
-                    rhs=(self.plant_conf['Enom']/self.timeStep)*(self.plant_conf['SOCmax'] - soc_init))
+                    rhs=(self.plant_conf['battery_nominal_energy_capacity']/self.timeStep)*(self.plant_conf['battery_maximum_state_of_charge'] - soc_init))
                 for i in set_I})
             constraints.update({"constraint_socmin_{}".format(i) : 
                 plp.LpConstraint(
-                    e=plp.lpSum(P_sto_pos[j]*(1/self.plant_conf['eta_disch']) + self.plant_conf['eta_ch']*P_sto_neg[j] for j in range(i)),
+                    e=plp.lpSum(P_sto_pos[j]*(1/self.plant_conf['battery_discharge_efficiency']) + self.plant_conf['battery_charge_efficiency']*P_sto_neg[j] for j in range(i)),
                     sense=plp.LpConstraintLE,
-                    rhs=(self.plant_conf['Enom']/self.timeStep)*(soc_init - self.plant_conf['SOCmin']))
+                    rhs=(self.plant_conf['battery_nominal_energy_capacity']/self.timeStep)*(soc_init - self.plant_conf['battery_minimum_state_of_charge']))
                 for i in set_I})
             constraints.update({"constraint_socfinal_{}".format(0) : 
                 plp.LpConstraint(
-                    e=plp.lpSum(P_sto_pos[i]*(1/self.plant_conf['eta_disch']) + self.plant_conf['eta_ch']*P_sto_neg[i] for i in set_I),
+                    e=plp.lpSum(P_sto_pos[i]*(1/self.plant_conf['battery_discharge_efficiency']) + self.plant_conf['battery_charge_efficiency']*P_sto_neg[i] for i in set_I),
                     sense=plp.LpConstraintEQ,
-                    rhs=(soc_init - soc_final)*self.plant_conf['Enom']/self.timeStep)
+                    rhs=(soc_init - soc_final)*self.plant_conf['battery_nominal_energy_capacity']/self.timeStep)
                 })
         opt_model.constraints = constraints
 
@@ -667,16 +667,16 @@ def create_matrix(input_list, n):
         opt_tp = pd.DataFrame()
         opt_tp["P_PV"] = [P_PV[i] for i in set_I]
         opt_tp["P_Load"] = [P_load[i] for i in set_I]
-        for k in range(self.optim_conf['num_def_loads']):
+        for k in range(self.optim_conf['number_of_deferrable_loads']):
             opt_tp["P_deferrable{}".format(k)] = [P_deferrable[k][i].varValue for i in set_I]
         opt_tp["P_grid_pos"] = [P_grid_pos[i].varValue for i in set_I]
         opt_tp["P_grid_neg"] = [P_grid_neg[i].varValue for i in set_I]
         opt_tp["P_grid"] = [P_grid_pos[i].varValue + P_grid_neg[i].varValue for i in set_I]
         if self.optim_conf['set_use_battery']:
             opt_tp["P_batt"] = [P_sto_pos[i].varValue + P_sto_neg[i].varValue for i in set_I]
-            SOC_opt_delta = [(P_sto_pos[i].varValue*(1/self.plant_conf['eta_disch']) + \
-                              self.plant_conf['eta_ch']*P_sto_neg[i].varValue)*(
-                                  self.timeStep/(self.plant_conf['Enom'])) for i in set_I]
+            SOC_opt_delta = [(P_sto_pos[i].varValue*(1/self.plant_conf['battery_discharge_efficiency']) + \
+                              self.plant_conf['battery_charge_efficiency']*P_sto_neg[i].varValue)*(
+                                  self.timeStep/(self.plant_conf['battery_nominal_energy_capacity'])) for i in set_I]
             SOCinit = copy.copy(soc_init)
             SOC_opt = []
             for i in set_I:
@@ -692,7 +692,7 @@ def create_matrix(input_list, n):
         # Lets compute the optimal cost function
         P_def_sum_tp = []
         for i in set_I:
-            P_def_sum_tp.append(sum(P_deferrable[k][i].varValue for k in range(self.optim_conf['num_def_loads'])))
+            P_def_sum_tp.append(sum(P_deferrable[k][i].varValue for k in range(self.optim_conf['number_of_deferrable_loads'])))
         opt_tp["unit_load_cost"] = [unit_load_cost[i] for i in set_I]
         opt_tp["unit_prod_price"] = [unit_prod_price[i] for i in set_I]
         if self.optim_conf['set_total_pv_sell']:
@@ -728,7 +728,7 @@ def create_matrix(input_list, n):
 
         # Debug variables
         if debug:
-            for k in range(self.optim_conf["num_def_loads"]):
+            for k in range(self.optim_conf['number_of_deferrable_loads']):
                 opt_tp[f"P_def_start_{k}"] = [P_def_start[k][i].varValue for i in set_I]
                 opt_tp[f"P_def_bin2_{k}"] = [P_def_bin2[k][i].varValue for i in set_I]
         for i, predicted_temp in predicted_temps.items():
diff --git a/src/emhass/retrieve_hass.py b/src/emhass/retrieve_hass.py
index d86b7512..7b4dcf78 100644
--- a/src/emhass/retrieve_hass.py
+++ b/src/emhass/retrieve_hass.py
@@ -62,7 +62,12 @@ def __init__(self, hass_url: str, long_lived_token: str, freq: pd.Timedelta,
         self.long_lived_token = long_lived_token
         self.freq = freq
         self.time_zone = time_zone
-        self.params = params
+        if (params == None) or (params == "null"):
+            self.params = {}
+        elif type(params) is dict:
+            self.params = params
+        else:
+            self.params = json.loads(params)
         self.emhass_conf = emhass_conf
         self.logger = logger
         self.get_data_from_file = get_data_from_file
@@ -450,11 +455,11 @@ class response:
                     metadata = {}
                 with open(entities_path / "metadata.json", "w") as file:                       
                     # Save entity metadata, key = entity_id 
-                    metadata[entity_id] = {'name': data_df.name, 'unit_of_measurement': unit_of_measurement,'friendly_name': friendly_name,'type_var': type_var, 'freq': int(self.freq.seconds / 60)}
+                    metadata[entity_id] = {'name': data_df.name, 'unit_of_measurement': unit_of_measurement,'friendly_name': friendly_name,'type_var': type_var, 'optimization_time_step': int(self.freq.seconds / 60)}
                     
                     # Find lowest frequency to set for continual loop freq
-                    if metadata.get("lowest_freq",None) == None or metadata["lowest_freq"] > int(self.freq.seconds / 60):
-                        metadata["lowest_freq"] = int(self.freq.seconds / 60)
+                    if metadata.get("lowest_time_step",None) == None or metadata["lowest_time_step"] > int(self.freq.seconds / 60):
+                        metadata["lowest_time_step"] = int(self.freq.seconds / 60)
                     json.dump(metadata,file, indent=4)
 
                     self.logger.debug("Saved " + entity_id + " to json file")   
diff --git a/src/emhass/static/advanced.html b/src/emhass/static/advanced.html
index 6595520c..87172f86 100644
--- a/src/emhass/static/advanced.html
+++ b/src/emhass/static/advanced.html
@@ -1,3 +1,4 @@
+<!-- advance div, dynamically appended inside of index.html -->
 <!-- action button elements section -->
 <div class="loading-div" id="advance">
     <h4>Use the buttons below to manually launch different optimization tasks</h4>
@@ -18,7 +19,7 @@ <h4>Use the buttons below to fit, predict and tune a machine learning model for
 <button type="button" id="regressor-model-fit" class="button button1">ML regressor model fit</button>
 <button type="button" id="regressor-model-predict" class="button button2">ML regressor model predict</button>
 <!-- -->
-<!--dynamic input elements section -->
+<!--dynamic runtime parameter input (list and box) elements section -->
 <h4>Input Runtime Parameters</h4>
 <div class="input-button-container">
     <div class="input-buttons">
diff --git a/src/emhass/static/basic.html b/src/emhass/static/basic.html
index 7bc78114..9a3f0647 100644
--- a/src/emhass/static/basic.html
+++ b/src/emhass/static/basic.html
@@ -1,3 +1,4 @@
+<!-- basic div, dynamically appended inside of index.html -->
 <!-- action button elements section -->
 <div class="loading-div" id="basic">
     <h4>Use the button below to manually launch optimization task</h4>
@@ -7,6 +8,7 @@ <h4>Use the button below to manually launch optimization task</h4>
 <div class="info">
     <p>The day-ahead optimization button will run once, based on the values entered into the configuration page.
         </br>
-        After a few seconds, the charts and table below should be updated to reflect the optimization plan for the next 24 hours.
+        After a few seconds, the charts and table below should be updated to reflect the optimization plan for the next
+        24 hours.
     </p>
-</div>
+</div>
\ No newline at end of file
diff --git a/src/emhass/static/configuration_list.html b/src/emhass/static/configuration_list.html
new file mode 100644
index 00000000..523f34f9
--- /dev/null
+++ b/src/emhass/static/configuration_list.html
@@ -0,0 +1,44 @@
+<!--  configuration list dynamically appended inside of configuration.html  -->
+<!--  configuration list view parameter sections  -->
+<div id="Local" class="section-card">
+  <div class="section-card-header">
+    <h4>Local</h4>
+  </div>
+  <div class="section-body"> </div> <!--  parameters will get generated here -->
+</div>
+<div id="System" class="section-card">
+  <div class="section-card-header">
+    <h4>System</h4>
+  </div>
+  <div class="section-body"> </div> <!--  parameters will get generated here -->
+</div>
+<div id="Tariff" class="section-card">
+  <div class="section-card-header">
+    <h4>Tariff</h4>
+  </div>
+  <div class="section-body"> </div> <!--  parameters will get generated here -->
+</div>
+<div id="Deferrable Loads" class="section-card">
+  <div class="section-card-header">
+    <h4>Deferrable Loads</h4>
+    <input id="number_of_deferrable_loads" min="1" type="number">
+    </label>
+  </div>
+  <div class="section-body"> </div> <!--  parameters will get generated here -->
+</div>
+<div id="Solar System (PV)" class="section-card">
+  <div class="section-card-header">
+    <h4>Solar System (PV)</h4>
+  </div>
+  <div class="section-body"> </div> <!--  parameters will get generated here -->
+</div>
+<div id="Battery" class="section-card">
+  <div class="section-card-header">
+    <h4>Battery</h4>
+    <label class="switch"> <!-- switch connected to set_use_battery  -->
+      <input id="set_use_battery" type="checkbox">
+      <span class="slider"></span>
+    </label>
+  </div>
+  <div class="section-body"> </div> <!--  parameters will get generated here -->
+</div>
\ No newline at end of file
diff --git a/src/emhass/static/configuration_script.js b/src/emhass/static/configuration_script.js
new file mode 100644
index 00000000..0a12a6c0
--- /dev/null
+++ b/src/emhass/static/configuration_script.js
@@ -0,0 +1,871 @@
+//javascript file for dynamically processing configuration page
+
+//used static files
+//param_definitions.json : stores information about parameters (E.g. their defaults, their type, and what parameter section to be in)
+//configuration_list.html : template html to act as a base for the list view. (Params get dynamically added after)
+
+//Div layout
+/* <div configuration-container>
+  <div class="section-card">
+    <div class="section-card-header"> POSSIBLE HEADER INPUT HERE WITH PARAMETER ID</div>
+    <div class="section-body">
+      <div id="PARAMETER-NAME" class="param">
+        <div class="param-input">input/s here</div>
+      </div>
+    </div>
+  </div>
+</div>; */
+
+//on page reload
+window.onload = async function () {
+  ///fetch configuration parameters from definitions json file
+  param_definitions = await getParamDefinitions();
+  //obtain configuration from emhass (pull)
+  config = await obtainConfig();
+  //obtain configuration_list.html html as a template to dynamically to render parameters in a list view (parameters as input items)
+  list_html = await getListHTML();
+  //load list parameter page (default)
+  loadConfigurationListView(param_definitions, config, list_html);
+
+  //add event listener to save button
+  document
+    .getElementById("save")
+    .addEventListener("click", () => saveConfiguration(param_definitions));
+
+  //add event listener to yaml button (convert yaml to json in box view)
+  document.getElementById("yaml").addEventListener("click", () => yamlToJson());
+  //hide yaml button by default (display in box view)
+  document.getElementById("yaml").style.display = "none";
+
+  //add event listener to defaults button
+  document
+    .getElementById("defaults")
+    .addEventListener("click", () =>
+      ToggleView(param_definitions, list_html, true)
+    );
+
+  //add event listener to json-toggle button (toggle between json box and list view)
+  document
+    .getElementById("json-toggle")
+    .addEventListener("click", () =>
+      ToggleView(param_definitions, list_html, false)
+    );
+};
+
+//obtain file containing information about parameters (definitions)
+async function getParamDefinitions() {
+  const response = await fetch(`static/data/param_definitions.json`);
+  if (response.status !== 200 && response.status !== 201) {
+    //alert error in alert box
+    errorAlert("Unable to obtain definitions file");
+    return {};
+  }
+  const param_definitions = await response.json();
+  return await param_definitions;
+}
+
+//obtain emhass config (from saved params extracted/simplified into the config format)
+async function obtainConfig() {
+  config = {};
+  const response = await fetch(`get-config`, {
+    method: "GET",
+  });
+  response_status = await response.status; //return status
+  //if request failed
+  if (response_status !== 200 && response_status !== 201) {
+    showChangeStatus(response_status, await response.json());
+    return {};
+  }
+  //else extract json rom data
+  blob = await response.blob(); //get data blob
+  config = await new Response(blob).json(); //obtain json from blob
+  showChangeStatus(response_status, {});
+  return config;
+}
+
+//obtain emhass default config (to present the default parameters in view)
+async function ObtainDefaultConfig() {
+  config = {};
+  const response = await fetch(`get-config/defaults`, {
+    method: "GET",
+  });
+  //if request failed
+  response_status = await response.status; //return status
+  if (response_status !== 200 && response_status !== 201) {
+    showChangeStatus(response_status, await response.json());
+    return {};
+  }
+  //else extract json rom data
+  blob = await response.blob(); //get data blob
+  config = await new Response(blob).json(); //obtain json from blob
+  showChangeStatus(response_status, {});
+  return config;
+}
+
+//get html data from configuration_list.html (list template)
+async function getListHTML() {
+  const response = await fetch(`static/configuration_list.html`);
+  if (response.status !== 200 && response.status !== 201) {
+    errorAlert("Unable to obtain configuration_list.html file");
+    return {};
+  }
+  blob = await response.blob(); //get data blob
+  htmlTemplateData = await new Response(blob).text(); //obtain html from blob
+  return await htmlTemplateData;
+}
+
+//load list configuration view
+function loadConfigurationListView(param_definitions, config, list_html) {
+  if (list_html == null || config == null || param_definitions == null) {
+    return 1;
+  }
+
+  //list parameters used in the section headers
+  header_input_list = ["set_use_battery", "number_of_deferrable_loads"];
+
+  //get the main container and append list template html
+  document.getElementById("configuration-container").innerHTML = list_html;
+
+  //loop though configuration sections ('Local','System','Tariff','Solar System (PV)') in definitions file
+  for (var section in param_definitions) {
+    // build each section by adding parameters with their corresponding input elements
+    buildParamContainers(
+      section,
+      param_definitions[section],
+      config,
+      header_input_list
+    );
+
+    //after sections have been built, add event listeners for section header inputs
+    //loop though headers
+    for (header_input_param of header_input_list) {
+      if (param_definitions[section].hasOwnProperty(header_input_param)) {
+        //grab default from definitions file
+        value = param_definitions[section][header_input_param]["default_value"];
+        //find input element (using the parameter name as the input element ID)
+        header_input_element = document.getElementById(header_input_param);
+        if (header_input_element !== null) {
+          //add event listener to element (trigger on input change)
+          header_input_element.addEventListener("input", (e) =>
+            headerElement(e.target, param_definitions, config)
+          );
+          //check the EMHASS config to see if it contains a stored param value
+          //else keep default
+          value = checkConfigParam(value, config, header_input_param);
+          //set value of input
+          header_input_element.value = value;
+          //checkboxes (for Booleans) also set value to "checked"
+          if (header_input_element.type == "checkbox") {
+            header_input_element.checked = value;
+          }
+          //manually trigger the header parameter input event listener for setting up initial section state
+          headerElement(header_input_element, param_definitions, config);
+        }
+      }
+    }
+  }
+}
+
+//build sections body, containing parameter/param containers (containing parameter/param inputs)
+function buildParamContainers(
+  section,
+  section_parameters_definitions,
+  config,
+  header_input_list
+) {
+  //get the section container element
+  SectionContainer = document.getElementById(section);
+  //get the body container inside the section (where the parameters will be appended)
+  SectionParamElement = SectionContainer.getElementsByClassName("section-body");
+  if (SectionContainer == null || SectionParamElement.length == 0) {
+    console.error("Unable to find Section container or Section Body");
+    return 0;
+  }
+
+  //loop though the sections parameters in definition file, generate and append param (div) elements for the section
+  for (const [
+    parameter_definition_name,
+    parameter_definition_object,
+  ] of Object.entries(section_parameters_definitions)) {
+    //check parameter definitions have the required key values
+    if (
+      !("friendly_name" in parameter_definition_object) ||
+      !("Description" in parameter_definition_object) ||
+      !("input" in parameter_definition_object) ||
+      !("default_value" in parameter_definition_object)
+    ) {
+      console.log(
+        parameter_definition_name +
+          " is missing some required values in the definitions file"
+      );
+      continue;
+    }
+    if (
+      parameter_definition_object["input"] === "select" &&
+      !("select_options" in parameter_definition_object)
+    ) {
+      console.log(
+        parameter_definition_name +
+          " is missing select_options values in the definitions file"
+      );
+      continue;
+    }
+
+    //check if param is set in the section header, if so skip building param
+    if (header_input_list.includes(parameter_definition_name)) {
+      continue;
+    }
+
+    //if parameter type == array.* and not in "Deferrable Loads" section, append plus and minus buttons in param div
+    array_buttons = "";
+    if (
+      parameter_definition_object["input"].search("array.") > -1 &&
+      section != "Deferrable Loads"
+    ) {
+      array_buttons = `
+                  <button type="button" class="input-plus ${parameter_definition_name}">+</button>
+                  <button type="button" class="input-minus ${parameter_definition_name}">-</button>
+                  <br>
+                  `;
+    }
+
+    //generates and appends param container into section
+    //buildParamElement() builds the parameter input/s and returns html to append in param-input
+    SectionParamElement[0].innerHTML += `
+          <div class="param" id="${parameter_definition_name}">
+             <h5>${
+               parameter_definition_object["friendly_name"]
+             }:</h5> <i>${parameter_definition_name}</i> </br>
+              ${array_buttons}
+             <div class="param-input"> 
+                  ${buildParamElement(
+                    parameter_definition_object,
+                    parameter_definition_name,
+                    config
+                  )}
+             </div>
+              <p>${parameter_definition_object["Description"]}</p>
+          </div>
+          `;
+  }
+
+  //After looping though, build and appending the parameters in the corresponding section:
+  //create add button (array plus) event listeners
+  let plus = SectionContainer.querySelectorAll(".input-plus");
+  plus.forEach(function (answer) {
+    answer.addEventListener("click", () =>
+      plusElements(answer.classList[1], param_definitions, section, {})
+    );
+  });
+
+  //create subtract button (array minus) event listeners
+  let minus = SectionContainer.querySelectorAll(".input-minus");
+  minus.forEach(function (answer) {
+    answer.addEventListener("click", () => minusElements(answer.classList[1]));
+  });
+
+  //check initial checkbox state, check "value" of input and match to "checked" value
+  let checkbox = document.querySelectorAll("input[type='checkbox']");
+  checkbox.forEach(function (answer) {
+    let value = answer.value === "true";
+    answer.checked = value;
+  });
+
+  //loop though sections params again, check if param has a requirement, if so add a event listener to the required param input
+  //if required param gets changed, trigger function to check if that required parameter matches the required value for the param
+  //if false, add css class to param element to shadow it, to show that its unaccessible
+  for (const [
+    parameter_definition_name,
+    parameter_definition_object,
+  ] of Object.entries(section_parameters_definitions)) {
+    //check if param has a requirement from definitions file
+    if ("requires" in parameter_definition_object) {
+      // get param requirement element
+      const requirement_element = document.getElementById(
+        Object.keys(parameter_definition_object["requires"])[0]
+      );
+      if (requirement_element == null) {
+        console.debug(
+          "unable to find " +
+            Object.keys(parameter_definition_object["requires"])[0] +
+            " param div container element"
+        );
+        continue;
+      }
+
+      // get param element that has requirement
+      const param_element = document.getElementById(parameter_definition_name);
+      if (param_element == null) {
+        console.debug(
+          "unable to find " +
+            parameter_definition_name +
+            " param div container element"
+        );
+        continue;
+      }
+
+      //obtain required param inputs, add event listeners
+      requirement_inputs =
+        requirement_element.getElementsByClassName("param_input");
+      //grab required value
+      const requirement_value = Object.values(
+        parameter_definition_object["requires"]
+      )[0];
+
+      //for all required inputs
+      for (const input of requirement_inputs) {
+        //if listener not already attached
+        if (input.getAttribute("listener") !== "true") {
+          //create event listener with arguments referencing the required param. param with requirement and required value
+          input.addEventListener("input", () =>
+            checkRequirements(input, param_element, requirement_value)
+          );
+          //manually run function to gain initial param element initial state
+          checkRequirements(input, param_element, requirement_value);
+        }
+      }
+    }
+  }
+}
+
+//create html input element/s for a param container (called by buildParamContainers)
+function buildParamElement(
+  parameter_definition_object,
+  parameter_definition_name,
+  config
+) {
+  var type = "";
+  var inputs = "";
+  var type_specific_html = "";
+  var type_specific_html_end = "";
+
+  //switch statement to adjust generated html according to the parameter data type (definitions in definitions file)
+  switch (parameter_definition_object["input"]) {
+    case "array.int":
+    //number
+    case "int":
+      type = "number";
+      placeholder = parseInt(parameter_definition_object["default_value"]);
+      break;
+    case "array.float":
+    case "float":
+      type = "number";
+      placeholder = parseFloat(parameter_definition_object["default_value"]);
+      break;
+    //text (string)
+    case "array.string":
+    case "string":
+      type = "text";
+      placeholder = parameter_definition_object["default_value"];
+      break;
+    case "array.time":
+    //time ("00:00")
+    case "time":
+      type = "time";
+      break;
+    //checkbox (boolean)
+    case "array.boolean":
+    case "boolean":
+      type = "checkbox";
+      type_specific_html = `
+              <label class="switch">
+              `;
+      type_specific_html_end = `
+              <span class="slider"></span>
+              </label>
+              `;
+      placeholder = parameter_definition_object["default_value"] === "true";
+      break;
+    //selects (pick)
+    case "select":
+      //format selects later
+      break;
+  }
+
+  //check default values saved in param definitions
+  //definitions default value is used if none is found in the configs, or an array element has been added in the ui (deferrable load number increase or plus button pressed)
+  value = parameter_definition_object["default_value"];
+  //check if a param value is saved in the config file (if so overwrite definition default)
+  value = checkConfigParam(value, config, parameter_definition_name);
+
+  //generate and return param input html,
+  //check if param value is not an object, if so assume its a single value.
+  if (typeof value !== "object") {
+    //if select, generate and return select elements instead of input
+    if (parameter_definition_object["input"] == "select") {
+      let inputs = `<select class="param_input">`;
+      for (const options of parameter_definition_object["select_options"]) {
+        selected = ""
+        //if item in select is the same as the config value, then append "selected" tag
+        if (options==value) {selected = `selected="selected"`}
+        inputs += `<option ${selected}>${options}</option>`;
+      }
+      inputs += `</select>`;
+      return inputs;
+    }
+    // generate param input html and return
+    else {
+      return `
+          ${type_specific_html}
+          <input class="param_input" type="${type}" value=${value} placeholder=${parameter_definition_object["default_value"]}>
+          ${type_specific_html_end}
+          `;
+    }
+  }
+  // else if object, loop though array of values, generate input element per value, and and return
+  else {
+    //for items such as load_peak_hour_periods (object of objects with arrays)
+    if (typeof Object.values(value)[0] === "object") {
+      for (param of Object.values(value)) {
+        for (items of Object.values(param)) {
+          inputs += `<input class="param_input" type="${type}" value=${
+            Object.values(items)[0]
+          } placeholder=${Object.values(items)[0]}>`;
+        }
+        inputs += `</br>`;
+      }
+      return inputs;
+    }
+    // array of values
+    else {
+      let inputs = "";
+      for (param of value) {
+        inputs += `
+          ${type_specific_html}
+          <input class="param_input" type="${type}" value=${param} placeholder=${parameter_definition_object["default_value"]}>
+          ${type_specific_html_end}
+          `;
+      }
+      return inputs;
+    }
+  }
+}
+
+//add param inputs in param div container (for type array)
+function plusElements(
+  parameter_definition_name,
+  param_definitions,
+  section,
+  config
+) {
+  param_element = document.getElementById(parameter_definition_name);
+  if (param_element == null) {
+    console.log(
+      "Unable to find " + parameter_definition_name + " param div container"
+    );
+    return 1;
+  }
+  param_input_container =
+    param_element.getElementsByClassName("param-input")[0];
+  // Add a copy of the param element
+  param_input_container.innerHTML += buildParamElement(
+    param_definitions[section][parameter_definition_name],
+    parameter_definition_name,
+    config
+  );
+}
+
+//Remove param inputs in param div container (minimum 1)
+function minusElements(param) {
+  param_element = document.getElementById(param);
+  if (param_element == null) {
+    console.log(
+      "Unable to find " + parameter_definition_name + " param div container"
+    );
+    return 1;
+  }
+  param_input_list = param_element.getElementsByTagName("input");
+  if (param_input_list.length == 0) {
+    console.log(
+      "Unable to find " + parameter_definition_name + " param input/s"
+    );
+  }
+
+  //verify if input is a boolean (if so remove parent slider/switch element with input)
+  if (
+    param_input_list[param_input_list.length - 1].parentNode.tagName === "LABEL"
+  ) {
+    param_input = param_input_list[param_input_list.length - 1].parentNode;
+  } else {
+    param_input = param_input_list[param_input_list.length - 1];
+  }
+
+  //if param is "load_peak_hour_periods", remove both start and end param inputs as well as the line brake tag separating the inputs
+  if (param == "load_peak_hour_periods") {
+    if (param_input_list.length > 2) {
+      brs = document.getElementById(param).getElementsByTagName("br");
+      param_input_list[param_input_list.length - 1].remove();
+      param_input_list[param_input_list.length - 1].remove();
+      brs[brs.length - 1].remove();
+    }
+  } else if (param_input_list.length > 1) {
+    param_input.remove();
+  }
+}
+
+//check requirement_element inputs,
+//if requirement_element don't match requirement_value, add .requirement-disable class to param_element
+//else remove class
+function checkRequirements(
+  requirement_element,
+  param_element,
+  requirement_value
+) {
+  //get current value of required element
+  if (requirement_element.type == "checkbox") {
+    requirement_element_value = requirement_element.checked;
+  } else {
+    requirement_element_value = requirement_element.value;
+  }
+
+  if (requirement_element_value != requirement_value) {
+    if (!param_element.classList.contains("requirement-disable")) {
+      param_element.classList.add("requirement-disable");
+    }
+  } else {
+    if (param_element.classList.contains("requirement-disable")) {
+      param_element.classList.remove("requirement-disable");
+    }
+  }
+}
+
+//on header input change, execute accordingly
+function headerElement(element, param_definitions, config) {
+  //obtain section body element
+  section_card = element.closest(".section-card");
+  if (section_card == null) {
+    console.log("Unable to obtain section-card");
+    return 1;
+  }
+  param_container = section_card.getElementsByClassName("section-body");
+  if (param_container.length > 0) {
+    param_container = section_card.getElementsByClassName("section-body")[0];
+  } else {
+    console.log("Unable to obtain section-body");
+    return 1;
+  }
+
+  switch (element.id) {
+    //if set_use_battery, add or remove battery section (inc. params)
+    case "set_use_battery":
+      if (element.checked) {
+        buildParamContainers("Battery", param_definitions["Battery"], config, [
+          "set_use_battery",
+        ]);
+        element.checked = true;
+      } else {
+        param_container.innerHTML = "";
+      }
+      break;
+
+    //if number_of_deferrable_loads, the number of inputs in the "Deferrable Loads" section should add up to number_of_deferrable_loads value in header
+    case "number_of_deferrable_loads":
+      //get a list of param in section
+      param_list = param_container.getElementsByClassName("param");
+      if (param_list.length <= 0) {
+        console.log(
+          "There has been an issue counting the amount of params in number_of_deferrable_loads"
+        );
+        return 1;
+      }
+      //calculate how much off the fist parameters input elements amount to is, compering to the number_of_deferrable_loads value
+      difference =
+        parseInt(element.value) -
+        param_container.firstElementChild.querySelectorAll("input").length;
+      //add elements based on how many elements are missing
+      if (difference > 0) {
+        for (let i = difference; i >= 1; i--) {
+          for (const param of param_list) {
+            //append element, do not pass config to obtain default parameter from definitions file
+            plusElements(param.id, param_definitions, "Deferrable Loads", {});
+          }
+        }
+      }
+      //subtract elements based how many elements its over
+      if (difference < 0) {
+        for (let i = difference; i <= -1; i++) {
+          for (const param of param_list) {
+            minusElements(param.id);
+          }
+        }
+      }
+      break;
+  }
+}
+
+//checks parameter value in config, updates value if exists
+function checkConfigParam(value, config, parameter_definition_name) {
+  if (config !== null && config !== undefined) {
+    //check if parameter has a saved value
+    if (parameter_definition_name in config) {
+      value = config[parameter_definition_name];
+    }
+  }
+  return value;
+}
+
+//send all parameter input values to EMHASS, to save to config.json and param.pkl
+async function saveConfiguration(param_definitions) {
+  //start wth none
+  config = {};
+
+  //if section-cards (config sections/list) exists
+  config_card = document.getElementsByClassName("section-card");
+  //check if page is in list or box view
+  config_box_element = document.getElementById("config-box");
+
+  //if true, in list view
+  if (Boolean(config_card.length)) {
+    //retrieve params and their input/s by looping though param_definitions list
+    //loop through the sections
+    for (var [section_name, section_object] of Object.entries(
+      param_definitions
+    )) {
+      //loop through parameters
+      for (var [
+        parameter_definition_name,
+        parameter_definition_object,
+      ] of Object.entries(section_object)) {
+        let param_values = []; //stores the obtained param input values
+        let param_array = false;
+        //get param container
+        param_element = document.getElementById(parameter_definition_name);
+        if (param_element == null) {
+          console.debug(
+            "unable to find " +
+              parameter_definition_name +
+              " param div container element, skipping this param"
+          );
+          continue;
+        }
+        //extract input/s and their value/s from param container div
+        else {
+          if (param_element.tagName !== "INPUT") {
+            param_inputs = param_element.getElementsByClassName("param_input");
+          } else {
+            //check if param_element is also param_input (ex. for header parameters)
+            param_inputs = [param_element];
+          }
+
+          // loop though param_inputs, extract the element/s values
+          for (var input of param_inputs) {
+            switch (input.type) {
+              case "number":
+                param_values.push(parseFloat(input.value));
+                break;
+              case "checkbox":
+                param_values.push(input.checked);
+                break;
+              default:
+                param_values.push(input.value);
+                break;
+            }
+          }
+          //obtain param input type from param_definitions, check if param should be formatted as an array
+          param_array = Boolean(
+            !parameter_definition_object["input"].search("array")
+          );
+
+          //build parameters using values extracted from param_inputs
+
+          // If time with 2 sets (load_peak_hour_periods)
+          if (
+            parameter_definition_object["input"] == "array.time" &&
+            param_values.length % 2 === 0
+          ) {
+            config[parameter_definition_name] = {};
+            for (let i = 0; i < param_values.length; i++) {
+              config[parameter_definition_name][
+                "period_hp_" +
+                  (Object.keys(config[parameter_definition_name]).length + 1)
+              ] = [{ start: param_values[i] }, { end: param_values[++i] }];
+            }
+            continue;
+          }
+
+          //single value
+          if (param_values.length && !param_array) {
+            config[parameter_definition_name] = param_values[0];
+          }
+
+          //array value
+          else if (param_values.length) {
+            config[parameter_definition_name] = param_values;
+          }
+        }
+      }
+    }
+  }
+
+  //if box view, extract json from box view
+  else if (config_box_element !== null) {
+    //try and parse json from box
+    try {
+      config = JSON.parse(config_box_element.value);
+    } catch (error) {
+      //if json error, show in alert box
+      document.getElementById("alert-text").textContent =
+        "\r\n" +
+        error +
+        "\r\n" +
+        "JSON Error: String values may not be wrapped in quotes";
+      document.getElementById("alert").style.display = "block";
+      document.getElementById("alert").style.textAlign = "center";
+      return 0;
+    }
+  }
+  // else, cant find box or list view
+  else {
+    errorAlert("There has been an error verifying box or list view");
+  }
+
+  //finally, send built config to emhass
+  const response = await fetch(`set-config`, {
+    method: "POST",
+    headers: {
+      "Content-Type": "application/json",
+    },
+    body: JSON.stringify(config),
+  });
+  showChangeStatus(response.status, await response.json());
+}
+
+//Toggle between box (json) and list view
+async function ToggleView(param_definitions, list_html, default_reset) {
+  let selected = "";
+  config = {};
+
+  //find out if list or box view is active
+  configuration_container = document.getElementById("configuration-container");
+  if (configuration_container == null) {
+    errorAlert("Unable to find Configuration Container element");
+  }
+  //get yaml button
+  yaml_button = document.getElementById("yaml");
+  if (yaml_button == null) {
+    console.log("Unable to obtain yaml button");
+  }
+
+  // if section-cards (config sections/list) exists
+  config_card = configuration_container.getElementsByClassName("section-card");
+  //selected view (0 = box)
+  selected_view = Boolean(config_card.length);
+
+  //if default_reset is passed do not switch views, instead reinitialize view with default config as values
+  if (default_reset) {
+    selected_view = !selected_view;
+    //obtain default config as config (when pressing the default button)
+    config = await ObtainDefaultConfig();
+  } else {
+    //obtain latest config
+    config = await obtainConfig();
+  }
+
+  //if array is empty assume json box is selected
+  if (selected_view) {
+    selected = "list";
+  } else {
+    selected = "box";
+  }
+  //remove contents of current view
+  configuration_container.innerHTML = "";
+  //build new view
+  switch (selected) {
+    case "box":
+      //load list
+      loadConfigurationListView(param_definitions, config, list_html);
+      yaml_button.style.display = "none";
+      break;
+    case "list":
+      //load box
+      loadConfigurationBoxPage(config);
+      yaml_button.style.display = "block";
+      break;
+  }
+}
+
+//load box (json textarea) view
+async function loadConfigurationBoxPage(config) {
+  //get configuration container element
+  configuration_container = document.getElementById("configuration-container");
+  if (configuration_container == null) {
+    errorAlert("Unable to find Configuration Container element");
+  }
+  //append configuration container with textbox area
+  configuration_container.innerHTML = `
+      <textarea id="config-box" rows="30" placeholder="{}"></textarea>
+      `;
+  //set created textarea box with retrieved config
+  document.getElementById("config-box").innerHTML = JSON.stringify(
+    config,
+    null,
+    2
+  );
+}
+
+//function in control of status icons and alert box from a fetch request
+async function showChangeStatus(status, logJson) {
+  var loading = document.getElementById("loader"); //element showing statuses
+  if (loading === null) {
+    console.log("unable to find loader element");
+    return 1;
+  }
+  if (status === 200 || status === 201) {
+    //if status is 200 or 201, then show a tick
+    loading.innerHTML = `<p class=tick>&#x2713;</p>`;
+  } else {
+    //then show a cross
+    loading.classList.remove("loading");
+    loading.innerHTML = `<p class=cross>&#x292C;</p>`; //show cross icon to indicate an error
+    if (logJson.length != 0 && document.getElementById("alert-text") !== null) {
+      document.getElementById("alert-text").textContent =
+        "\r\n\u2022 " + logJson.join("\r\n\u2022 "); //show received log data in alert box
+      document.getElementById("alert").style.display = "block";
+      document.getElementById("alert").style.textAlign = "left";
+    }
+  }
+  //remove tick/cross after some time
+  setTimeout(() => {
+    loading.innerHTML = "";
+  }, 4000);
+}
+
+//simple function to write text to the alert box
+async function errorAlert(text) {
+  if (
+    document.getElementById("alert-text") !== null &&
+    document.getElementById("alert") !== null
+  ) {
+    document.getElementById("alert-text").textContent = "\r\n" + text + "\r\n";
+    document.getElementById("alert").style.display = "block";
+    document.getElementById("alert").style.textAlign = "left";
+  }
+  return 0;
+}
+
+//convert yaml box into json box
+async function yamlToJson() {
+  //get box element
+  config_box_element = document.getElementById("config-box");
+  if (config_box_element == null) {
+    errorAlert("Unable to obtain config box");
+  } else {
+    const response = await fetch(`get-json`, {
+      method: "POST",
+      headers: {
+        "Content-Type": "application/json",
+      },
+      body: config_box_element.value,
+    });
+    response_status = await response.status; //return status
+    if (response_status == 201) {
+      showChangeStatus(response_status, {});
+      blob = await response.blob(); //get data blob
+      config = await new Response(blob).json(); //obtain json from blob
+      config_box_element.value = JSON.stringify(config, null, 2);
+    } else {
+      showChangeStatus(response_status, await response.json());
+    }
+  }
+  return 0;
+}
diff --git a/src/emhass/static/data/param_definitions.json b/src/emhass/static/data/param_definitions.json
new file mode 100644
index 00000000..aa507bdf
--- /dev/null
+++ b/src/emhass/static/data/param_definitions.json
@@ -0,0 +1,424 @@
+{
+  "Local": {
+    "costfun": {
+      "friendly_name": "Cost function",
+      "Description": "Define the type of cost function.",
+      "input": "select",
+      "select_options": [
+        "profit",
+        "cost",
+        "self-consumption"
+      ],
+      "default_value": "profit"
+    },
+    "sensor_power_photovoltaics": {
+      "friendly_name": "Sensor power sensor",
+      "Description": "This is the name of the photovoltaic power-produced sensor in Watts from Home Assistant. For example: ‘sensor.power_photovoltaics’.",
+      "input": "string",
+      "default_value": "sensor.power_photovoltaics"
+    },
+    "sensor_power_load_no_var_loads": {
+      "friendly_name": "Sensor power loads with no variable loads",
+      "Description": "The name of the household power consumption sensor in Watts from Home Assistant. The deferrable loads that we will want to include in the optimization problem should be subtracted from this sensor in HASS. For example: ‘sensor.power_load_no_var_loads’",
+      "input": "string",
+      "default_value": "sensor.power_load_no_var_loads"
+    },
+    "sensor_replace_zero": {
+      "friendly_name": "Sensor to replace NAN values with 0s",
+      "Description": "The list of retrieved variables that we would want to replace NANs (if they exist) with zeros.",
+      "input": "array.string",
+      "default_value": "sensor.power_photovoltaics"
+    },
+    "sensor_linear_interp": {
+      "friendly_name": "Sensor to replace NAN values with linear interpolation",
+      "Description": "The list of retrieved variables that we would want to interpolate NANs values using linear interpolation",
+      "input": "array.string",
+      "default_value": "sensor.power_photovoltaics"
+    },
+    "continual_publish": {
+      "friendly_name": "Continually publish optimization results",
+      "Description": "set to True to save entities to .json after an optimization run. Then automatically republish the saved entities (with updated current state value) every freq minutes. entity data saved to data_path/entities.",
+      "input": "boolean",
+      "default_value": false
+    },
+    "logging_level": {
+      "friendly_name": "Logging level",
+      "Description": "This is the name of the photovoltaic power-produced sensor in Watts from Home Assistant. For example: ‘sensor.power_photovoltaics’.",
+      "input": "select",
+      "select_options": [
+        "INFO",
+        "DEBUG",  
+        "WARNING",
+        "ERROR"
+      ],
+      "default_value": "INFO"
+    }
+  },
+  "System": {
+    "optimization_time_step": {
+      "friendly_name": "Optimization steps per minute (timesteps)",
+      "Description": "The time step to resample retrieved data from hass. This parameter is given in minutes. It should not be defined too low or you will run into memory problems when defining the Linear Programming optimization. Defaults to 30",
+      "input": "int",
+      "default_value": 30
+    },
+    "historic_days_to_retrieve": {
+      "friendly_name": "Historic days to retrieve",
+      "Description": "We will retrieve data from now to days_to_retrieve days. Defaults to 2",
+      "input": "int",
+      "default_value": 2
+    },
+    "load_negative": {
+      "friendly_name": "Load negative values",
+      "Description": "Set this parameter to True if the retrieved load variable is negative by convention. Defaults to False",
+      "input": "boolean",
+      "default_value": false
+    },
+    "set_zero_min": {
+      "friendly_name": "Remove Negatives",
+      "Description": "Set this parameter to True to give a special treatment for a minimum value saturation to zero for power consumption data. Values below zero are replaced by nans. Defaults to True.",
+      "input": "boolean",
+      "default_value": true
+    },
+    "method_ts_round": {
+      "friendly_name": "Timestamp rounding method",
+      "Description": "Set the method for timestamp rounding, options are: first, last and nearest.",
+      "input": "select",
+      "select_options": [
+        "nearest",
+        "first",
+        "last"
+      ],
+      "default_value": "nearest"
+    },
+    "delta_forecast_daily": {
+      "friendly_name": "Number of forecasted days",
+      "Description": "The number of days for forecasted data. Defaults to 1.",
+      "input": "int",
+      "default_value": 1
+    },
+    "load_forecast_method": {
+      "friendly_name": "Load forecast method",
+      "Description": "The load forecast method that will be used. The options are ‘csv’ to load a CSV file or ‘naive’ for a simple 1-day persistence model.",
+      "input": "select",
+      "select_options": [
+        "naive",
+        "csv"
+      ],
+      "default_value": "naive"
+    },
+    "set_total_pv_sell": {
+      "friendly_name": "PV straight to grid",
+      "Description": "Set this parameter to true to consider that all the PV power produced is injected to the grid. No direct self-consumption. The default is false, for a system with direct self-consumption.",
+      "input": "boolean",
+      "default_value": false
+    },
+    "lp_solver": {
+      "friendly_name": "Linear programming solver",
+      "Description": "Set the name of the linear programming solver that will be used. Defaults to ‘COIN_CMD’. The options are ‘PULP_CBC_CMD’, ‘GLPK_CMD’ and ‘COIN_CMD’.",
+      "input": "select",
+      "select_options": [
+        "default",
+        "COIN_CMD",
+        "PULP_CBC_CMD",
+        "GLPK_CMD"
+      ],
+      "default_value": "COIN_CMD"
+    },
+    "lp_solver_path": {
+      "friendly_name": "Linear programming solver program path",
+      "Description": "Set the path to the LP solver. Defaults to ‘/usr/bin/cbc’.",
+      "input": "text",
+      "default_value": "/usr/bin/cbc"
+    },
+    "weather_forecast_method": {
+      "friendly_name": "Weather forecast method",
+      "Description": "This will define the weather forecast method that will be used. options are 'scrapper' (ClearOutside), 'Solcast', 'solar.forecast' (forecast.solar) and 'csv' to load a CSV file. When loading a CSV file this will be directly considered as the PV power forecast in Watts.",
+      "input": "select",
+      "select_options": [
+        "scrapper",
+        "solcast",
+        "solar.forecast",
+        "csv"
+      ],
+      "default_value": "scrapper"
+    },
+    "maximum_power_from_grid": {
+      "friendly_name": "Max power from grid",
+      "Description": "The maximum power that can be supplied by the utility grid in Watts (consumption). Defaults to 9000.",
+      "input": "int",
+      "default_value": 9000
+    },
+    "maximum_power_to_grid": {
+      "friendly_name": "Max export power to grid",
+      "Description": "The maximum power that can be supplied to the utility grid in Watts (injection). Defaults to 9000.",
+      "input": "int",
+      "default_value": 9000
+    },
+    "inverter_is_hybrid": {
+      "friendly_name": "Inverter is a hybrid",
+      "Description": "Set to True to consider that the installation inverter is hybrid for PV and batteries (Default False)",
+      "input": "boolean",
+      "default_value": false
+    },
+    "compute_curtailment": {
+      "friendly_name": "Set compute curtailment (grid export limit)",
+      "Description": "Set to True to compute a special PV curtailment variable (Default False)",
+      "input": "boolean",
+      "default_value": false
+    }
+  },
+  "Tariff": {
+    "load_cost_forecast_method": {
+      "friendly_name": "Load cost method",
+      "Description": "Define the method that will be used for load cost forecast. The options are ‘hp_hc_periods’ for peak and non-peak hours contracts, and ‘csv’ to load custom cost from CSV file.",
+      "input": "select",
+      "select_options": [
+        "hp_hc_periods",
+        "csv"
+      ],
+      "default_value": "hp_hc_periods"
+    },
+    "load_peak_hour_periods": {
+      "friendly_name": "List peak hour periods",
+      "Description": "A list of peak hour periods for load consumption from the grid. This is useful if you have a contract with peak and non-peak hours.",
+      "input": "array.time",
+      "default_value": {
+        "period_hp_template": [{"start": "02:54"},{"end": "15:24"}]
+      },
+      "requires": {
+        "load_cost_forecast_method": "hp_hc_periods"
+      }
+    },
+    "load_peak_hours_cost": {
+      "friendly_name": "Peak hours electrical energy cost",
+      "Description": "The cost of the electrical energy during peak hours",
+      "input": "float",
+      "requires": {
+        "load_cost_forecast_method": "hp_hc_periods"
+      },
+      "default_value": 0.1907
+    },
+    "load_offpeak_hours_cost": {
+      "friendly_name": "Off-peak hours electrical energy cost",
+      "Description": "The cost of the electrical energy during off-peak hours",
+      "input": "float",
+      "requires": {
+        "load_cost_forecast_method": "hp_hc_periods"
+      },
+      "default_value": 0.1419
+    },
+    "production_price_forecast_method": {
+      "friendly_name": "PV power production price forecast method",
+      "Description": "Define the method that will be used for PV power production price forecast. This is the price that is paid by the utility for energy injected into the grid. The options are ‘constant’ for a constant fixed value or ‘csv’ to load custom price forecasts from a CSV file.",
+      "input": "select",
+      "select_options": [
+        "constant",
+        "csv"
+      ],
+      "default_value": "constant"
+    },
+    "photovoltaic_production_sell_price": {
+      "friendly_name": "Constant PV power production price",
+      "Description": "The paid price for energy injected to the grid from excess PV production in €/kWh.",
+      "input": "float",
+      "default_value": 0.1419,
+      "requires": {
+        "production_price_forecast_method": "constant"
+      }
+    }
+  },
+  "Solar System (PV)": {
+    "pv_module_model": {
+      "friendly_name": "PV module model name",
+      "Description": "The PV module model. This parameter can be a list of items to enable the simulation of mixed orientation systems.",
+      "input": "array.string",
+      "input_attributes": "_'s",
+      "default_value": "CSUN_Eurasia_Energy_Systems_Industry_and_Trade_CSUN295_60M"
+    },
+    "pv_inverter_model": {
+      "friendly_name": "The PV inverter model name",
+      "Description": "The PV inverter model.  This parameter can be a list of items to enable the simulation of mixed orientation systems.",
+      "input": "array.string",
+      "input_attributes": "_'s",
+      "default_value": "Fronius_International_GmbH__Fronius_Primo_5_0_1_208_240__240V_"
+    },
+    "surface_tilt": {
+      "friendly_name": "The PV panel tilt",
+      "Description": "The tilt angle of your solar panels. Defaults to 30. This parameter can be a list of items to enable the simulation of mixed orientation systems.",
+      "input": "array.int",
+      "default_value": 30
+    },
+    "surface_azimuth": {
+      "friendly_name": "The PV azimuth (direction)",
+      "Description": "The azimuth of your PV installation. Defaults to 205. This parameter can be a list of items to enable the simulation of mixed orientation systems.",
+      "input": "array.int",
+      "default_value": 205
+    },
+    "modules_per_string": {
+      "friendly_name": "Number of modules per string",
+      "Description": "The number of modules per string. Defaults to 16. This parameter can be a list of items to enable the simulation of mixed orientation systems.",
+      "input": "array.int",
+      "default_value": 16
+    },
+    "strings_per_inverter": {
+      "friendly_name": "Number of strings per inverter",
+      "Description": "The number of used strings per inverter. Defaults to 1. This parameter can be a list of items to enable the simulation of mixed orientation systems.",
+      "input": "array.int",
+      "default_value": 1
+    }
+  },
+  "Deferrable Loads": {
+    "number_of_deferrable_loads": {
+      "friendly_name": "Number of deferrable loads",
+      "Description": "Define the number of deferrable loads (appliances to shift) to consider. Defaults to 2.",
+      "input": "int",
+      "default_value": 2
+    },
+    "nominal_power_of_deferrable_loads": {
+      "friendly_name": "Deferrable load nominal power",
+      "Description": "The nominal (calculated max) power for each deferrable load in Watts.",
+      "input": "array.float",
+      "default_value": 3000.0
+    },
+    "operating_hours_of_each_deferrable_load": {
+      "friendly_name": "Deferrable load operating hours",
+      "Description": "The total number of hours that each deferrable load should operate",
+      "input": "array.int",
+      "default_value": 0
+    },
+    "treat_deferrable_load_as_semi_cont": {
+      "friendly_name": "Deferrable load as semi-continuous (on/off) variable",
+      "Description": "Semi-continuous variables (True) are variables that must take a value that can be either their maximum or minimum/zero (for example On = Maximum load, Off = 0 W). Non semi-continuous (which means continuous) variables (False) can take any values between their maximum and minimum",
+      "input": "array.boolean",
+      "default_value": true
+    },
+    "set_deferrable_load_single_constant": {
+      "friendly_name": "Deferrable load run single constant per optimization",
+      "Description": "Define if we should set each deferrable load as a constant fixed value variable with just one startup for each optimization task",
+      "input": "array.boolean",
+      "default_value": false
+    },
+    "set_deferrable_startup_penalty": {
+      "friendly_name": "Set deferrable startup penalty",
+      "Description": "For penalty P, each time the deferrable load turns on will incur an additional cost of P * number_of_deferrable_loads * cost_of_electricity at that time",
+      "input": "array.float",
+      "default_value": 0.0
+    },
+    "start_timesteps_of_each_deferrable_load": {
+      "friendly_name": "Deferrable start timestamp",
+      "Description": "The timestep as from which each deferrable load is allowed to operate (if you don’t want the deferrable load to use the whole optimization time window). If you specify a value of 0 (or negative), the deferrable load will be optimized as from the beginning of the complete prediction horizon window.",
+      "input": "array.int",
+      "default_value": 0
+    },
+    "end_timesteps_of_each_deferrable_load": {
+      "friendly_name": "Deferrable end timestamp",
+      "Description": "The timestep before which each deferrable load should operate. The deferrable load is not allowed to operate after the specified time step. If a value of 0 (or negative) is provided, the deferrable load is allowed to operate in the complete optimization window)",
+      "input": "array.int",
+      "default_value": 0
+    }
+  },
+  "Battery": {
+    "set_use_battery": {
+      "friendly_name": "Enable Battery",
+      "Description": "Set to True if we should consider an energy storage device such as a Li-Ion battery. Defaults to False",
+      "input": "boolean",
+      "default_value": false
+    },
+    "set_nocharge_from_grid": {
+      "friendly_name": "Forbid charging battery from grid",
+      "Description": "Set this to true if you want to forbid charging the battery from the grid. The battery will only be charged from excess PV",
+      "input": "boolean",
+      "default_value": false
+    },
+    "set_nodischarge_to_grid": {
+      "friendly_name": "Forbid battery discharge to the grid",
+      "Description": "Set this to true if you want to forbid discharging battery power to the grid.",
+      "input": "boolean",
+      "default_value": true
+    },
+    "set_battery_dynamic": {
+      "friendly_name": "Set Battery dynamic (dis)charge power limiting",
+      "Description": "Set a power dynamic limiting condition to the battery power. This is an additional constraint on the battery dynamic in power per unit of time (timestep), which allows you to set a percentage of the battery’s nominal full power as the maximum power allowed for (dis)charge.",
+      "input": "boolean",
+      "default_value": false
+    },
+    "battery_dynamic_max": {
+      "friendly_name": "Maximum percentage of battery discharge per timestep",
+      "Description": "The maximum positive (for discharge) battery power dynamic. This is the allowed power variation (in percentage) of battery maximum power per unit of timestep",
+      "input": "float",
+      "default_value": 0.9,
+      "requires": {
+        "set_battery_dynamic": true
+      }
+    },
+    "battery_dynamic_min": {
+      "friendly_name": "Maximum percentage of battery charge per timestep",
+      "Description": "The maximum negative (for charge) battery power dynamic. This is the allowed power variation (in percentage) of battery maximum power per timestep.",
+      "input": "float",
+      "default_value": -0.9,
+      "requires": {
+        "set_battery_dynamic": true
+      }
+    },
+    "weight_battery_discharge": {
+      "friendly_name": "Add cost weight for battery discharge",
+      "Description": "An additional weight (currency/ kWh) applied in the cost function to battery usage for discharging",
+      "input": "float",
+      "default_value": 1.0
+    },
+    "weight_battery_charge": {
+      "friendly_name": "Add cost weight for battery charge",
+      "Description": "An additional weight (currency/ kWh) applied in the cost function to battery usage for charging",
+      "input": "float",
+      "default_value": 1.0
+    },
+    "battery_discharge_power_max": {
+      "friendly_name": "Max battery discharge power",
+      "Description": "The maximum discharge power in Watts",
+      "input": "int",
+      "default_value": 1000
+    },
+    "battery_charge_power_max": {
+      "friendly_name": "Max battery charge power",
+      "Description": "The maximum charge power in Watts",
+      "input": "int",
+      "default_value": 1000
+    },
+    "battery_discharge_efficiency": {
+      "friendly_name": "Battery discharge efficiency",
+      "Description": "The discharge efficiency. (percentage/100)",
+      "input": "float",
+      "default_value": 0.95
+    },
+    "battery_charge_efficiency": {
+      "friendly_name": "Battery charge efficiency",
+      "Description": "The charge efficiency. (percentage/100)",
+      "input": "float",
+      "default_value": 0.95
+    },
+    "battery_nominal_energy_capacity": {
+      "friendly_name": "Battery total capacity",
+      "Description": "The total capacity of the battery stack in Wh",
+      "input": "int",
+      "default_value": 5000
+    },
+    "battery_minimum_state_of_charge": {
+      "friendly_name": "Minimum Battery charge percentage",
+      "Description": "The minimum allowable battery state of charge. (percentage/100)",
+      "input": "float",
+      "default_value": 0.3
+    },
+    "battery_maximum_state_of_charge": {
+      "friendly_name": "Maximum Battery charge percentage",
+      "Description": "The maximum allowable battery state of charge. (percentage/100)",
+      "input": "float",
+      "default_value": 0.9
+    },
+    "battery_target_state_of_charge": {
+      "friendly_name": "Battery desired percentage after optimization",
+      "Description": "The desired battery state of charge at the end of each optimization cycle. (percentage/100)",
+      "input": "float",
+      "default_value": 0.6
+    }
+  }
+}
\ No newline at end of file
diff --git a/src/emhass/static/script.js b/src/emhass/static/script.js
index ad02b578..294244ca 100644
--- a/src/emhass/static/script.js
+++ b/src/emhass/static/script.js
@@ -1,419 +1,442 @@
+//configuration for dynamically processing index page
+//loads either the basic or advance html
+
+//used static files
+//advanced.html : template html for displaying all the actions + runtime parameter input
+//basic.html : template html for displaying a minimal view of actions
+
 //on page reload get saved data
 window.onload = async function () {
+  pageSelected = await loadBasicOrAdvanced();
 
-    pageSelected = await loadBasicOrAdvanced();
-
-    //add listener for basic and advanced html switch
-    document.getElementById("basicOrAdvanced").addEventListener("click", () => SwitchBasicOrAdvanced());
+  //add listener for basic and advanced html switch
+  document
+    .getElementById("basicOrAdvanced")
+    .addEventListener("click", () => SwitchBasicOrAdvanced());
 };
 
 //add listeners to buttons (based on page)
 function loadButtons(page) {
-    switch (page) {
-        case "advanced":
-            [
-                "dayahead-optim",
-                "forecast-model-fit",
-                "forecast-model-predict",
-                "forecast-model-tune",
-                "regressor-model-fit",
-                "regressor-model-predict",
-                "perfect-optim",
-                "publish-data",
-                "naive-mpc-optim"
-            ].forEach((id) =>
-                document.getElementById(id).addEventListener("click", () => formAction(id, "advanced"))
-            );
-            ["input-plus", "input-minus"].forEach((id) =>
-                document.getElementById(id).addEventListener("click", () => dictInputs(id))
-            );
-            document.getElementById("input-select").addEventListener("change", () => getSavedData());
-            document.getElementById("input-clear").addEventListener("click", () => ClearInputData());
-            break;
-        case "basic":
-            document.getElementById("dayahead-optim-basic").addEventListener("click", () => formAction("dayahead-optim", "basic"));
-            break;
-    }
+  switch (page) {
+    case "advanced":
+      [
+        "dayahead-optim",
+        "forecast-model-fit",
+        "forecast-model-predict",
+        "forecast-model-tune",
+        "regressor-model-fit",
+        "regressor-model-predict",
+        "perfect-optim",
+        "publish-data",
+        "naive-mpc-optim",
+      ].forEach((id) =>
+        document
+          .getElementById(id)
+          .addEventListener("click", () => formAction(id, "advanced"))
+      );
+      ["input-plus", "input-minus"].forEach((id) =>
+        document
+          .getElementById(id)
+          .addEventListener("click", () => dictInputs(id))
+      );
+      document
+        .getElementById("input-select")
+        .addEventListener("change", () => getSavedData());
+      document
+        .getElementById("input-clear")
+        .addEventListener("click", () => ClearInputData());
+      break;
+    case "basic":
+      document
+        .getElementById("dayahead-optim-basic")
+        .addEventListener("click", () => formAction("dayahead-optim", "basic"));
+      break;
+  }
 }
 
 //on check present basic or advanced html inside form element
 async function loadBasicOrAdvanced(RequestedPage) {
-    let basicFile = "basic.html";
-    let advencedFile = "advanced.html";
-    var formContainer = document.getElementById("TabSelection"); //container element to house basic or advanced data
-    //first check any function  arg
-    if (arguments.length == 1) {
-        switch (RequestedPage) {
-            case "basic":
-                htmlData = await getHTMLData(basicFile);
-                formContainer.innerHTML = htmlData;
-                loadButtons("basic"); //load buttons based on basic or advanced
-                if (testStorage()) { localStorage.setItem("TabSelection", "basic") } //remember mode (save to localStorage)
-                return "basic"; //return basic to get saved data
-            case "advanced":
-                htmlData = await getHTMLData(advencedFile);
-                formContainer.innerHTML = htmlData;
-                loadButtons("advanced");
-                if (testStorage()) { localStorage.setItem("TabSelection", "advanced") }
-                getSavedData();
-                return "advanced";
-            default:
-                htmlData = await getHTMLData(advencedFile);
-                formContainer.innerHTML = htmlData;
-                loadButtons("advanced");
-                getSavedData();
-                return "advanced";
-        }
-    }
-    //then check localStorage
-    if (testStorage()) {
-        if (localStorage.getItem("TabSelection") !== null && localStorage.getItem("TabSelection") === "advanced") { //if advance
-            htmlData = await getHTMLData(advencedFile);
-            formContainer.innerHTML = htmlData;
-            loadButtons("advanced");
-            getSavedData();
-            return "advanced";
-        }
-        else { //else run basic (first time)
-            htmlData = await getHTMLData(basicFile);
-            formContainer.innerHTML = htmlData;
-            loadButtons("basic");
-            return "basic";
+  let basicFile = "basic.html";
+  let advencedFile = "advanced.html";
+  var formContainer = document.getElementById("TabSelection"); //container element to house basic or advanced data
+  //first check any function  arg
+  if (arguments.length == 1) {
+    switch (RequestedPage) {
+      case "basic":
+        htmlData = await getHTMLData(basicFile);
+        formContainer.innerHTML = htmlData;
+        loadButtons("basic"); //load buttons based on basic or advanced
+        if (testStorage()) {
+          localStorage.setItem("TabSelection", "basic");
+        } //remember mode (save to localStorage)
+        return "basic"; //return basic to get saved data
+      case "advanced":
+        htmlData = await getHTMLData(advencedFile);
+        formContainer.innerHTML = htmlData;
+        loadButtons("advanced");
+        if (testStorage()) {
+          localStorage.setItem("TabSelection", "advanced");
         }
-    } else {
-        //if localStorage not supported, set to advanced page
+        getSavedData();
+        return "advanced";
+      default:
         htmlData = await getHTMLData(advencedFile);
         formContainer.innerHTML = htmlData;
         loadButtons("advanced");
+        getSavedData();
         return "advanced";
     }
+  }
+  //then check localStorage
+  if (testStorage()) {
+    if (
+      localStorage.getItem("TabSelection") !== null &&
+      localStorage.getItem("TabSelection") === "advanced"
+    ) {
+      //if advance
+      htmlData = await getHTMLData(advencedFile);
+      formContainer.innerHTML = htmlData;
+      loadButtons("advanced");
+      getSavedData();
+      return "advanced";
+    } else {
+      //else run basic (first time)
+      htmlData = await getHTMLData(basicFile);
+      formContainer.innerHTML = htmlData;
+      loadButtons("basic");
+      return "basic";
+    }
+  } else {
+    //if localStorage not supported, set to advanced page
+    htmlData = await getHTMLData(advencedFile);
+    formContainer.innerHTML = htmlData;
+    loadButtons("advanced");
+    return "advanced";
+  }
 }
 
 //on button press, check current displayed page data and switch
 function SwitchBasicOrAdvanced() {
-    var formContainerChildID = document.getElementById("TabSelection").firstElementChild.id
-    if (formContainerChildID === 'basic') {
-        loadBasicOrAdvanced("advanced")
-    }
-    else {
-        loadBasicOrAdvanced("basic")
-    }
+  var formContainerChildID =
+    document.getElementById("TabSelection").firstElementChild.id;
+  if (formContainerChildID === "basic") {
+    loadBasicOrAdvanced("advanced");
+  } else {
+    loadBasicOrAdvanced("basic");
+  }
 }
 
-
 //get html data from basic.html or advanced.html
 async function getHTMLData(htmlFile) {
-    const response = await fetch(`static/` + htmlFile);
-    blob = await response.blob(); //get data blob
-    htmlTemplateData = await new Response(blob).text(); //obtain html from blob
-    return await htmlTemplateData;
+  const response = await fetch(`static/` + htmlFile);
+  blob = await response.blob(); //get data blob
+  htmlTemplateData = await new Response(blob).text(); //obtain html from blob
+  return await htmlTemplateData;
 }
 
 //function pushing data via post, triggered by button action
 async function formAction(action, page) {
+  if (page !== "basic") {
+    //dont try to get input data in basic mode
+    var data = inputToJson(page);
+  } else {
+    var data = {};
+  } //send no data
 
-    if (page !== "basic") { //dont try to get input data in basic mode
-        var data = inputToJson(page);
-    }
-    else { var data = {} } //send no data
-
-    if (data !== 0) { //don't run if there is an error in the input (box/list) Json data
-        showChangeStatus("loading", {}); // show loading div for status
-        const response = await fetch(`action/` + action, {
-            //fetch data from webserver.py
-            method: "POST",
-            headers: {
-                "Content-Type": "application/json",
-            },
-            body: JSON.stringify(data), //note that post can only send data via strings
-        });
-        if (response.status == 201) {
-            showChangeStatus(response.status, {});
-            if (page !== "basic") {
-                saveStorage(); //save to storage if successful  
-            }
-            return true
-        } //if successful
-        else {
-            showChangeStatus(response.status, await response.json());
-            return false
-        } // else get Log data from response
-    } else {
-        showChangeStatus("remove"); //replace loading, show tick or cross with none
-        return false
-    }
+  if (data !== 0) {
+    //don't run if there is an error in the input (box/list) Json data
+    showChangeStatus("loading", {}); // show loading div for status
+    const response = await fetch(`action/` + action, {
+      //fetch data from webserver.py
+      method: "POST",
+      headers: {
+        "Content-Type": "application/json",
+      },
+      body: JSON.stringify(data), //note that post can only send data via strings
+    });
+    if (response.status == 201) {
+      showChangeStatus(response.status, {});
+      if (page !== "basic") {
+        saveStorage(); //save to storage if successful
+      }
+      return true;
+    } //if successful
+    else {
+      showChangeStatus(response.status, await response.json());
+      return false;
+    } // else get Log data from response
+  } else {
+    showChangeStatus("remove"); //replace loading, show tick or cross with none
+    return false;
+  }
 }
 
 //function in control of status icons of post above
 async function showChangeStatus(status, logJson) {
-    var loading = document.getElementById("loader"); //element showing statuses
-    if (status === "remove") {
-        //remove all
-        loading.innerHTML = "";
-        loading.classList.remove("loading");
-    } else if (status === "loading") {
-        //show loading logo
-        loading.innerHTML = "";
-        loading.classList.add("loading"); //append class with loading animation styling
-    } else if (status === 201) {
-        //if status is 201, then show a tick
-        loading.classList.remove("loading");
-        loading.innerHTML = `<p class=tick>&#x2713;</p>`;
-        getTemplate(); //get updated templates
-    } else {
-        //then show a cross
-        loading.classList.remove("loading");
-        loading.innerHTML = `<p class=cross>&#x292C;</p>`; //show cross icon to indicate an error
-        if (logJson.length != 0) {
-            document.getElementById("alert-text").textContent =
-                "\r\n\u2022 " + logJson.join("\r\n\u2022 "); //show received log data in alert box
-            document.getElementById("alert").style.display = "block";
-            document.getElementById("alert").style.textAlign = "left";
-        }
+  var loading = document.getElementById("loader"); //element showing statuses
+  if (status === "remove") {
+    //remove all
+    loading.innerHTML = "";
+    loading.classList.remove("loading");
+  } else if (status === "loading") {
+    //show loading logo
+    loading.innerHTML = "";
+    loading.classList.add("loading"); //append class with loading animation styling
+  } else if (status === 201) {
+    //if status is 201, then show a tick
+    loading.classList.remove("loading");
+    loading.innerHTML = `<p class=tick>&#x2713;</p>`;
+    getTemplate(); //get updated templates
+  } else {
+    //then show a cross
+    loading.classList.remove("loading");
+    loading.innerHTML = `<p class=cross>&#x292C;</p>`; //show cross icon to indicate an error
+    if (logJson.length != 0 && document.getElementById("alert-text") !== null) {
+      document.getElementById("alert-text").textContent =
+        "\r\n\u2022 " + logJson.join("\r\n\u2022 "); //show received log data in alert box
+      document.getElementById("alert").style.display = "block";
+      document.getElementById("alert").style.textAlign = "left";
     }
+  }
 }
 
 //get rendered html template with containing new table data
 async function getTemplate() {
-    //fetch data from webserver.py
-    let htmlTemplateData = "";
-    response = await fetch(`template/table-template`, {
-        method: "GET",
-    });
-    blob = await response.blob(); //get data blob
-    htmlTemplateData = await new Response(blob).text(); //obtain html from blob
-    templateDiv = document.getElementById("template"); //get template container element to override
-    templateDiv.innerHTML = htmlTemplateData; //override container inner html with new data
-    var scripts = Array.from(templateDiv.getElementsByTagName("script")); //replace script tags manually
-    for (const script of scripts) {
-        var TempScript = document.createElement("script");
-        TempScript.innerHTML = script.innerHTML;
-        script.parentElement.appendChild(TempScript);
-    }
+  //fetch data from webserver.py
+  let htmlTemplateData = "";
+  response = await fetch(`template`, {
+    method: "GET",
+  });
+  blob = await response.blob(); //get data blob
+  htmlTemplateData = await new Response(blob).text(); //obtain html from blob
+  templateDiv = document.getElementById("template"); //get template container element to override
+  templateDiv.innerHTML = htmlTemplateData; //override container inner html with new data
+  var scripts = Array.from(templateDiv.getElementsByTagName("script")); //replace script tags manually
+  for (const script of scripts) {
+    var TempScript = document.createElement("script");
+    TempScript.innerHTML = script.innerHTML;
+    script.parentElement.appendChild(TempScript);
+  }
 }
 
 //test localStorage support
 function testStorage() {
-    try {
-        localStorage.setItem("test", { test: "123" });
-        localStorage.removeItem("test");
-        return true;
-    } catch (error) {
-        return false;
-    }
+  try {
+    localStorage.setItem("test", { test: "123" });
+    localStorage.removeItem("test");
+    return true;
+  } catch (error) {
     return false;
+  }
+  return false;
 }
 
 //function gets saved data (if any)
 function getSavedData() {
-    dictInputs(); //check selected current (List or Box) is correct
-    if (testStorage()) {
-        //if local storage exists and works
-        let selectElement = document.getElementById("input-select"); // select button element
-        var input_container = document.getElementById("input-container"); // container div containing all dynamic input elements (Box/List)
-        if (
-            localStorage.getItem("input_container_content") &&
-            localStorage.getItem("input_container_content") !== "{}"
-        ) {
-            //If items already stored in local storage, then override default
-            if (selectElement.value == "Box") {
-                //if Box is selected, show saved json data into box
-                document.getElementById("text-area").value = localStorage.getItem(
-                    "input_container_content"
-                );
-            }
-            if (selectElement.value == "List") {
-                //if List is selected, show saved json data into box
-                storedJson = JSON.parse(
-                    localStorage.getItem("input_container_content")
-                );
-                if (Object.keys(storedJson).length > 0) {
-                    input_container.innerHTML = "";
-                    i = 1;
-                    for (const ikey in storedJson) {
-                        input_container.appendChild(
-                            createInputListDiv(ikey, JSON.stringify(storedJson[ikey]))
-                        ); //call function to present each key as an list div element (with saved values)
-                    }
-                }
-            }
+  dictInputs(); //check selected current (List or Box) is correct
+  if (testStorage()) {
+    //if local storage exists and works
+    let selectElement = document.getElementById("input-select"); // select button element
+    var input_container = document.getElementById("input-container"); // container div containing all dynamic input elements (Box/List)
+    if (
+      localStorage.getItem("input_container_content") &&
+      localStorage.getItem("input_container_content") !== "{}"
+    ) {
+      //If items already stored in local storage, then override default
+      if (selectElement.value == "Box") {
+        //if Box is selected, show saved json data into box
+        document.getElementById("text-area").value = localStorage.getItem(
+          "input_container_content"
+        );
+      }
+      if (selectElement.value == "List") {
+        //if List is selected, show saved json data into box
+        storedJson = JSON.parse(
+          localStorage.getItem("input_container_content")
+        );
+        if (Object.keys(storedJson).length > 0) {
+          input_container.innerHTML = "";
+          i = 1;
+          for (const ikey in storedJson) {
+            input_container.appendChild(
+              createInputListDiv(ikey, JSON.stringify(storedJson[ikey]))
+            ); //call function to present each key as an list div element (with saved values)
+          }
         }
+      }
     }
+  }
 }
 
 //using localStorage, store json data from input-list(List)/text-area(from input-box) elements for saved state save on page refresh (will save state on successful post)
 function saveStorage() {
-    var data = JSON.stringify(inputToJson());
-    if (testStorage() && data != "{}") {
-        //don't bother saving if empty and/or storage don't exist
-        localStorage.setItem("input_container_content", data);
-    }
+  var data = JSON.stringify(inputToJson());
+  if (testStorage() && data != "{}") {
+    //don't bother saving if empty and/or storage don't exist
+    localStorage.setItem("input_container_content", data);
+  }
 }
 
 //function gets values from input-list/text-area(from input-box) elements and return json dict object
 function inputToJson() {
-    var input_container = document.getElementById("input-container"); //container
-    let inputListArr = document.getElementsByClassName("input-list"); //list
-    let inputTextArea = document.getElementById("text-area"); //box
-    let input_container_child = null;
-    input_container_child = input_container.firstElementChild; //work out which element is first inside container div
-    var jsonReturnData = {};
+  var input_container = document.getElementById("input-container"); //container
+  let inputListArr = document.getElementsByClassName("input-list"); //list
+  let inputTextArea = document.getElementById("text-area"); //box
+  let input_container_child = null;
+  input_container_child = input_container.firstElementChild; //work out which element is first inside container div
+  var jsonReturnData = {};
 
-    if (input_container_child == null) {
-        //if no elements in container then return empty
-        return jsonReturnData;
-    }
-    //if List return box json
-    if (
-        input_container_child.className == "input-list" &&
-        inputListArr.length > 0
-    ) {
-        //if list is first and if list is greater then 0, otherwise give empty dict
+  if (input_container_child == null) {
+    //if no elements in container then return empty
+    return jsonReturnData;
+  }
+  //if List return box json
+  if (
+    input_container_child.className == "input-list" &&
+    inputListArr.length > 0
+  ) {
+    //if list is first and if list is greater then 0, otherwise give empty dict
 
-        let jsonTempData = "{";
-        for (let i = 0; i < inputListArr.length; i++) {
-            let key = inputListArr[i].getElementsByClassName("input-key")[0].value;
-            var value =
-                inputListArr[i].getElementsByClassName("input-value")[0].value;
-            //curate a string with list elements to parse into json later
-            if (key !== "") {
-                //key must not be empty
-                if (i !== 0) {
-                    jsonTempData = jsonTempData.concat(",");
-                } //add comma before every parameter, exuding the first
-                jsonTempData = jsonTempData.concat('"' + key + '":' + value);
-            }
-        }
-        jsonTempData = jsonTempData.concat("}");
-        try {
-            jsonReturnData = JSON.parse(jsonTempData);
-        } catch (error) {
-            //if json error, show in alert box
-            document.getElementById("alert-text").textContent =
-                "\r\n" +
-                error +
-                "\r\n" +
-                "JSON Error: String values may not be wrapped in quotes";
-            document.getElementById("alert").style.display = "block";
-            document.getElementById("alert").style.textAlign = "center";
-            return 0;
-        }
+    let jsonTempData = "{";
+    for (let i = 0; i < inputListArr.length; i++) {
+      let key = inputListArr[i].getElementsByClassName("input-key")[0].value;
+      var value =
+        inputListArr[i].getElementsByClassName("input-value")[0].value;
+      //curate a string with list elements to parse into json later
+      if (key !== "") {
+        //key must not be empty
+        if (i !== 0) {
+          jsonTempData = jsonTempData.concat(",");
+        } //add comma before every parameter, exuding the first
+        jsonTempData = jsonTempData.concat('"' + key + '":' + value);
+      }
     }
-    //if Box return box json
-    if (
-        input_container_child.className == "input-box" &&
-        inputTextArea.value != ""
-    ) {
-        //if Box is first and text is not empty, otherwise give empty dict
-        try {
-            jsonReturnData = JSON.parse(inputTextArea.value);
-        } catch (error) {
-            //if json error, show in alert box
-            document.getElementById("alert-text").textContent = "\r\n" + error;
-            document.getElementById("alert").style.display = "block";
-            return 0;
-        }
+    jsonTempData = jsonTempData.concat("}");
+    try {
+      jsonReturnData = JSON.parse(jsonTempData);
+    } catch (error) {
+      //if json error, show in alert box
+      document.getElementById("alert-text").textContent =
+        "\r\n" +
+        error +
+        "\r\n" +
+        "JSON Error: String values may not be wrapped in quotes";
+      document.getElementById("alert").style.display = "block";
+      document.getElementById("alert").style.textAlign = "center";
+      return 0;
     }
-    return jsonReturnData;
+  }
+  //if Box return box json
+  if (
+    input_container_child.className == "input-box" &&
+    inputTextArea.value != ""
+  ) {
+    //if Box is first and text is not empty, otherwise give empty dict
+    try {
+      jsonReturnData = JSON.parse(inputTextArea.value);
+    } catch (error) {
+      //if json error, show in alert box
+      document.getElementById("alert-text").textContent = "\r\n" + error;
+      document.getElementById("alert").style.display = "block";
+      return 0;
+    }
+  }
+  return jsonReturnData;
 }
 
 //function creates input list div element (and pass it values if given)
 function createInputListDiv(ikey, ivalue) {
-    let div = document.createElement("div");
-    div.className = "input-list";
-    div.innerHTML = `
+  let div = document.createElement("div");
+  div.className = "input-list";
+  div.innerHTML = `
                 <input class="input-key" type="text" placeholder="pv_power_forecast" >
                 <p>:</p>
                 <input class="input-value" type="text" placeholder="[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 70, 141.22, 246.18, 513.5, 753.27, 1049.89, 1797.93, 1697.3, 3078.93, 1164.33, 1046.68, 1559.1, 2091.26, 1556.76, 1166.73, 1516.63, 1391.13, 1720.13, 820.75, 804.41, 251.63, 79.25, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]" >
             `;
 
-    if (ikey && ivalue) {
-        //if value and key is provided (from local storage) then add as elements values
-        div.getElementsByClassName("input-key")[0].value = String(ikey);
-        div.getElementsByClassName("input-value")[0].value = String(ivalue);
-    }
+  if (ikey && ivalue) {
+    //if value and key is provided (from local storage) then add as elements values
+    div.getElementsByClassName("input-key")[0].value = String(ikey);
+    div.getElementsByClassName("input-value")[0].value = String(ivalue);
+  }
 
-    return div;
+  return div;
 }
 
 //function assigned to control (add and remove) input (Box and List) elements
 function dictInputs(action) {
-    var input_container = document.getElementById("input-container"); // container div containing all dynamic input elements
-    let selectElement = document.getElementById("input-select"); // select button
-    let input_container_child = null;
-    let input_container_child_name = null;
-    if (input_container.children.length > 0) {
-        input_container_child = input_container.firstElementChild; // figure out what is the first element inside of container (ie: "text-area" (input-box) or "input-list" (list))
-        input_container_child_name = input_container.firstElementChild.className;
+  var input_container = document.getElementById("input-container"); // container div containing all dynamic input elements
+  let selectElement = document.getElementById("input-select"); // select button
+  let input_container_child = null;
+  let input_container_child_name = null;
+  if (input_container.children.length > 0) {
+    input_container_child = input_container.firstElementChild; // figure out what is the first element inside of container (ie: "text-area" (input-box) or "input-list" (list))
+    input_container_child_name = input_container.firstElementChild.className;
+  }
+  //if list is selected, remove text-area (from Box) element and replace (with input-list)
+  if (selectElement.value == "List") {
+    if (action == "input-plus" || input_container_child_name == "input-box") {
+      //if plus button pressed, or Box element exists
+      if (input_container_child_name == "input-box") {
+        input_container_child.remove();
+      }
+      input_container.appendChild(createInputListDiv(false, false)); //call to createInputListDiv function to craft input-list element (with no values) and append inside container element
     }
-    //if list is selected, remove text-area (from Box) element and replace (with input-list)
-    if (selectElement.value == "List") {
-        if (action == "input-plus" || input_container_child_name == "input-box") {
-            //if plus button pressed, or Box element exists
-            if (input_container_child_name == "input-box") {
-                input_container_child.remove();
-            }
-            input_container.appendChild(createInputListDiv(false, false)); //call to createInputListDiv function to craft input-list element (with no values) and append inside container element
-        }
-        if (action == "input-minus") {
-            //minus button pressed, remove input-list element
-            if (input_container.children.length > 0) {
-                let inputListArr = document.getElementsByClassName("input-list");
-                let obj = inputListArr.item(inputListArr.length - 1);
-                obj.innerHTML = "";
-                obj.remove();
-            }
-        }
+    if (action == "input-minus") {
+      //minus button pressed, remove input-list element
+      if (input_container.children.length > 0) {
+        let inputListArr = document.getElementsByClassName("input-list");
+        let obj = inputListArr.item(inputListArr.length - 1);
+        obj.innerHTML = "";
+        obj.remove();
+      }
     }
-    //if box is selected, remove input-list elements and replace (with text-area)
-    if (selectElement.value == "Box") {
-        if (
-            input_container_child_name == "input-list" ||
-            input_container_child === null
-        ) {
-            // if input list exists or no Box element
-            input_container.innerHTML = ""; //remove input-list list elements via erasing container innerHTML
-            let div = document.createElement("div"); //add input-box element
-            div.className = "input-box";
-            div.innerHTML = `
+  }
+  //if box is selected, remove input-list elements and replace (with text-area)
+  if (selectElement.value == "Box") {
+    if (
+      input_container_child_name == "input-list" ||
+      input_container_child === null
+    ) {
+      // if input list exists or no Box element
+      input_container.innerHTML = ""; //remove input-list list elements via erasing container innerHTML
+      let div = document.createElement("div"); //add input-box element
+      div.className = "input-box";
+      div.innerHTML = `
                 <textarea id="text-area" rows="30" placeholder="{}"></textarea>
                 `;
-            input_container.appendChild(div); //append inside of container element
-        }
+      input_container.appendChild(div); //append inside of container element
     }
+  }
 }
 
 //clear stored input data from localStorage (if any), clear input elements
 async function ClearInputData(id) {
-    if (
-        testStorage() &&
-        localStorage.getItem("input_container_content") !== null
-    ) {
-        localStorage.setItem("input_container_content", "{}");
-    }
-    ClearInputElements();
+  if (
+    testStorage() &&
+    localStorage.getItem("input_container_content") !== null
+  ) {
+    localStorage.setItem("input_container_content", "{}");
+  }
+  ClearInputElements();
 }
 
 //clear input elements
 async function ClearInputElements() {
-    let selectElement = document.getElementById("input-select");
-    var input_container = document.getElementById("input-container");
-    if (selectElement.value == "Box") {
-        document.getElementById("text-area").value = "{}";
-    }
-    if (selectElement.value == "List") {
-        input_container.innerHTML = "";
-    }
+  let selectElement = document.getElementById("input-select");
+  var input_container = document.getElementById("input-container");
+  if (selectElement.value == "Box") {
+    document.getElementById("text-area").value = "{}";
+  }
+  if (selectElement.value == "List") {
+    input_container.innerHTML = "";
+  }
 }
 
 // //Run day ahead, then publish actions
 // async function DayheadOptimPublish() {
 //     response = await formAction("dayahead-optim", "basic")
-//     if (response) { //if successful publish data 
+//     if (response) { //if successful publish data
 //         formAction("publish-data", "basic")
 //     }
 //}
-
-
-
-
diff --git a/src/emhass/static/style.css b/src/emhass/static/style.css
index fd87ba06..3a4a8627 100644
--- a/src/emhass/static/style.css
+++ b/src/emhass/static/style.css
@@ -1,3 +1,4 @@
+/*! EMHASS Style Sheet */
 /*! style.css v1.0.0 (modified version) | ISC License | https://github.com/ungoldman/style.css */
 
 /* color reference css */
@@ -570,6 +571,8 @@ button,
 .mystyle,
 .alert,
 .info,
+.section-card,
+select,
 table {
   border-radius: 7px;
   /* overflow: visible; */
@@ -578,6 +581,7 @@ table {
 button,
 .alert,
 .info,
+.section-card,
 select {
   text-transform: none;
   border-width: 1px;
@@ -599,6 +603,8 @@ select {
 
 h2 {
   margin-bottom: .3em;
+  margin-right: .3em;
+  margin-left: .3em;
 }
 
 .table_div h4 {
@@ -747,12 +753,12 @@ tr:hover td:last-child {
   margin-right: 13px;
 }
 
-#loader {
+/* #loader {
   min-width: 3.5em;
   min-height: 3.5em;
   width: 3.5em;
   height: 3.5em;
-}
+} */
 
 .loading {
   /* loading animation  */
@@ -787,7 +793,7 @@ tr:hover td:last-child {
 
 .tick {
   /* tick symbol  */
-  color: #158b00;
+  color: #bfebbc;
   vertical-align: text-top;
   font-size: 4.0em;
   animation-name: fadeInOpacity;
@@ -798,7 +804,7 @@ tr:hover td:last-child {
 
 .cross {
   /* cross symbol  */
-  color: #a71515;
+  color: #e3b7b7;
   vertical-align: text-top;
   font-size: 4.0em;
   animation-name: fadeInOpacity;
@@ -941,7 +947,8 @@ select,
 
 
 .input-list input,
-.input-box textarea {
+.input-box textarea,
+#config-box {
   border-collapse: collapse;
   border-radius: 7px;
   border-style: solid;
@@ -970,7 +977,229 @@ select,
   margin: 0;
 }
 
-/* */
+/* config page */
+#configuration-container {
+  border: 1px solid;
+  border-width: 1px 0px;
+}
+
+#configuration-container,
+.header-footer {
+  margin-bottom: 5px;
+  padding: 20px 0px;
+  border-radius: 7px;
+  max-width: 90%;
+  margin: auto;
+}
+
+
+.header-footer {
+  background-color: #0000;
+  display: flex;
+  justify-content: space-between;
+}
+
+.header-footer h4,
+.header-footer div,
+.header-footer a {
+  line-height: 0;
+  margin: auto 0;
+
+}
+
+/* loading icons */
+.header-footer p {
+  margin: 20px 0;
+}
+
+
+.header-footer h4 {
+  color: darkblue;
+}
+
+.header-footer .feather {
+  height: 3em !important;
+  stroke-width: 3 !important;
+}
+
+#save,
+#json {
+  min-width: 15%;
+  height: auto;
+  display: block;
+  min-height: fit-content;
+  margin-left: auto;
+}
+
+/* configuration list page */
+
+.section-card {
+  max-width: 99%;
+  margin-left: auto;
+  margin-right: auto;
+  margin-bottom: 1vh;
+}
+
+.section-card h4 {
+  margin-top: 5px;
+  background-color: #0000 !important;
+}
+
+.section-card-header {
+  background-color: #e1e1e15e;
+  display: flex;
+  align-items: center;
+  padding: 0px 10px;
+  border-bottom: 1px solid rgba(0, 0, 0, 0.355);
+  /* justify-content: center; */
+}
+
+.section-body {
+  -webkit-transition: .4s;
+  transition: .4s;
+}
+
+.switch,
+.section-card-header input {
+  position: relative;
+  display: inline-block;
+  width: 52px;
+  height: 27px;
+  margin-left: auto;
+}
+
+.switch input {
+  opacity: 0;
+  width: 0;
+  height: 0;
+}
+
+.slider {
+  position: absolute;
+  cursor: pointer;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background-color: #ccc;
+  -webkit-transition: .4s;
+  transition: .4s;
+}
+
+.slider:before {
+  position: absolute;
+  content: "";
+  height: calc(27px - 7px);
+  width: calc(27px - 7px);
+  left: 4px;
+  bottom: 4px;
+  background-color: white;
+  -webkit-transition: .4s;
+  transition: .4s;
+}
+
+input:checked+.slider {
+  background-color: darkblue;
+}
+
+input:checked+.slider:before {
+  -webkit-transform: translateX(26px);
+  -ms-transform: translateX(26px);
+  transform: translateX(26px);
+}
+
+.slider,
+.slider:before {
+  border-radius: 7px
+}
+
+/* param container and content styling  */
+.param {
+  text-align: left;
+  padding: 5px;
+  border-bottom: 1px solid rgba(0, 0, 0, 0.355);
+  transition: 1s;
+}
+
+.param input,
+.section-card-header input {
+  background-color: #ffffff11;
+  border-radius: 7px;
+  border: solid 1px;
+  color: #181818;
+  min-width: 40%;
+  max-width: 70%;
+}
+
+
+.section-card-header input {
+  min-width: calc(27px - 7px);
+}
+
+.param p,
+.param i {
+  font-size: .7em;
+  margin-top: 4px;
+}
+
+.param i {
+  font-size: .7em;
+  margin-bottom: 5px;
+}
+
+.param p {
+  padding-right: 5px;
+  max-width: 777px;
+}
+
+.param h5 {
+  font-size: 1em;
+}
+
+.param h5,
+p {
+  margin: 5px;
+  margin-left: 0px;
+  margin-bottom: 0px;
+}
+
+.param button {
+  width: 20px;
+  height: 20px;
+  line-height: 0;
+  padding: 1px;
+  box-shadow: none;
+  margin-bottom: 5px;
+}
+
+.param-input {
+  display: block;
+  float: left;
+  min-width: 100%;
+}
+
+.param-input input {
+  min-width: 70%;
+}
+
+.param-input input[type="time"] {
+  min-width: 35%;
+}
+
+/* when requirement param is not met */
+.requirement-disable {
+  pointer-events: none;
+  filter: opacity(25%);
+}
+
+/* configuration box page */
+
+#config-box {
+  min-width: 100%;
+  min-height: 85vh;
+}
+
+
 
 
 /* mobile only changes */
@@ -989,6 +1218,9 @@ select,
   }
 }
 
+
+
+
 /* Dark Theme Support */
 @media (prefers-color-scheme: dark) {
   html.adaptive {
@@ -1031,6 +1263,9 @@ select,
   }
 
   h2,
+  h3,
+  h4,
+  .header-footer h4,
   kbd,
   a {
     background-color: #111111;
@@ -1094,7 +1329,8 @@ select,
   }
 
   .input-list input,
-  .input-box textarea {
+  .input-box textarea,
+  #config-box {
     background-color: #282928;
     border-color: #e1e1e1;
     color: #e1e1e1
@@ -1110,5 +1346,28 @@ select,
     border-top: 16px solid #ccf3ff;
   }
 
+  input:checked+.slider {
+    background-color: #ccf3ff;
+  }
 
-}
+  .param {
+    border-color: rgba(255, 255, 255, 0.355);
+  }
+
+  .param input,
+  .section-card-header input {
+    color: #e1e1e1;
+  }
+
+  .section-card-header {
+    background-color: #ffffff11;
+  }
+
+
+  #configuration-container {
+    background-color: #ffffff07;
+    border: 0;
+  }
+
+
+}
\ No newline at end of file
diff --git a/src/emhass/templates/configuration.html b/src/emhass/templates/configuration.html
new file mode 100644
index 00000000..40c1183d
--- /dev/null
+++ b/src/emhass/templates/configuration.html
@@ -0,0 +1,75 @@
+<!-- Configuration page html -->
+<!DOCTYPE html>
+<html class="adaptive">
+
+<head>
+  <title>EMHASS: Energy Management Optimization for Home Assistant</title>
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <link rel="stylesheet" type="text/css" href="static/style.css?version=1">
+  <link rel="icon" type="image/x-icon" href="static/img/emhass_logo_short.svg">
+  <script src="static/configuration_script.js"></script>
+</head>
+
+<body style="margin: auto; align-items:center; text-align:center;">
+
+  <div>
+    <!-- Icons by feather https://github.com/feathericons/feather -->
+    <div id="top-links">
+      <!-- index page  -->
+      <a href="index" title="Home" style="margin-right: 12px; cursor: pointer; z-index: 1">
+        <svg class="feather">
+          <use class="feather" href="static/img/feather-sprite.svg#home" />
+        </svg>
+      </a>
+      <a href="https://emhass.readthedocs.io/en/latest/">
+        <svg class="feather" title="EMHASS Docs" style="margin-right: 12px;" ;>
+          <use class="feather" href="static/img/feather-sprite.svg#book" />
+        </svg>
+      </a>
+      <a href="https://github.com/davidusb-geek/emhass" target="_blank" rel="noopener noreferrer">
+        <svg class="feather" title="EMHASS Git repo" style="margin-right: 0px;">
+          <use class="feather" href="static/img/feather-sprite.svg#git-branch" />
+        </svg>
+      </a>
+    </div>
+
+    <!-- Title -->
+    <img src="static/img/emhass_icon.png" alt="">
+    <h2>EMHASS: Energy Management Optimization for Home Assistant</h2>
+  </div>
+  <div class="header-footer">
+    <h4>Configuration:</h4>
+    <a id="json-toggle" title="List and Box view toggle">
+      <svg id="json" class="feather" class="header-footer-button">
+        <use class="feather" href="static/img/feather-sprite.svg#code" />
+      </svg>
+    </a>
+  </div>
+
+  <!-- parameter list/box elements section -->
+  <div id=configuration-container>
+  </div>
+  <!-- configuration buttons (defaults, save, yaml) -->
+  <div class="save_button header-footer">
+    <button style="margin-right: 5px;" type="button" id="yaml" title="Convert yaml to json" class="">YAML</button>
+    <button type="button" id="defaults" title="Restore to default parameter values" class="">Defaults</button>
+    <button type="button" id="save" title="Save Config to EMHASS" class="">Save</button>
+    <div class="loading-div"> <!-- dynamic tick/cross element -->
+      <div id=loader></div> 
+    </div>
+  </div>
+  <!-- alert box -->
+  <div style="display: none;" id="alert" class="alert">
+    <div>
+      <span onclick="this.parentElement.parentElement.style.display='none';">&times;</span>
+      <p id="alert-text"></p>
+    </div>
+  </div>
+  <footer class="footer">
+    <p style="margin-top:10px; text-align:center;">&copy; MIT License | Copyright (c) 2021-2023 David
+      HERNANDEZ</p>
+  </footer>
+  </div>
+</body>
+
+</html>
\ No newline at end of file
diff --git a/src/emhass/templates/index.html b/src/emhass/templates/index.html
index 237cd328..5c416e4b 100644
--- a/src/emhass/templates/index.html
+++ b/src/emhass/templates/index.html
@@ -1,10 +1,11 @@
+<!-- Index page html -->
 <!DOCTYPE html>
 <html class="adaptive">
 
 <head>
     <title>EMHASS: Energy Management Optimization for Home Assistant</title>
     <meta name="viewport" content="width=device-width, initial-scale=1.0">
-    <link rel="stylesheet" type="text/css" href="static/style.css?version=1"> <!-- change version on stylesheet changes  -->
+    <link rel="stylesheet" type="text/css" href="static/style.css?version=1">
     <link rel="icon" type="image/x-icon" href="static/img/emhass_logo_short.svg">
     <script src="static/script.js"></script>
 </head>
@@ -14,19 +15,25 @@
     <div>
         <!-- Icons by feather https://github.com/feathericons/feather -->
         <div id="top-links">
+            <!-- configuration page  -->
+            <a href="configuration" title="Configuration Page" style="margin-right: 12px; cursor: pointer; z-index: 1">
+                <svg class="feather">
+                    <use class="feather" href="static/img/feather-sprite.svg#settings" />
+                </svg>
+            </a>
             <!-- advanced or basic page switch -->
-            <a id="basicOrAdvanced" style="margin-right: 24px; cursor: pointer; z-index: 1">
+            <a id="basicOrAdvanced" title="Advanced Toggle" style="margin-right: 24px; cursor: pointer; z-index: 1">
                 <svg class="feather">
                     <use class="feather" href="static/img/feather-sprite.svg#tool" />
                 </svg>
             </a>
             <a href="https://emhass.readthedocs.io/en/latest/">
-                <svg class="feather" style="margin-right: 12px;";>
+                <svg class="feather" title="EMHASS Docs" style="margin-right: 12px;" ;>
                     <use class="feather" href="static/img/feather-sprite.svg#book" />
                 </svg>
             </a>
             <a href="https://github.com/davidusb-geek/emhass" target="_blank" rel="noopener noreferrer">
-                <svg class="feather" style="margin-right: 0px;" >
+                <svg class="feather" title="EMHASS Git repo" style="margin-right: 0px;">
                     <use class="feather" href="static/img/feather-sprite.svg#git-branch" />
                 </svg>
             </a>
@@ -39,18 +46,18 @@ <h2>EMHASS: Energy Management Optimization for Home Assistant</h2>
 
     <div class="center">
         <div id=TabSelection class="form">
-            <!-- Basic and Advance pages will be loaded here  -->
+            <!-- Basic and Advance pages will be dynamically loaded here  -->
         </div>
     </div>
-    <!-- alert box element (for json parse issue) -->
+    <!-- alert box element -->
     <div style="display: none;" id="alert" class="alert">
         <div>
             <span onclick="this.parentElement.parentElement.style.display='none';">&times;</span>
             <p id="alert-text"></p>
         </div>
     </div>
-    <!-- -->
     <br>
+
     <!-- dynamic table/diagram elements section -->
     </div>
     <div id="template"> <!-- table/diagram container element  -->
@@ -60,7 +67,7 @@ <h2>EMHASS: Energy Management Optimization for Home Assistant</h2>
         </div>
         {% endfor %}
         <div>
-            <!-- -->
+
             <footer class="footer">
                 <p style="margin-top:10px; text-align:center;">&copy; MIT License | Copyright (c) 2021-2023 David
                     HERNANDEZ</p>
diff --git a/src/emhass/utils.py b/src/emhass/utils.py
index 3742a7e3..2702ddde 100644
--- a/src/emhass/utils.py
+++ b/src/emhass/utils.py
@@ -1,6 +1,8 @@
 #!/usr/bin/env python3
 # -*- coding: utf-8 -*-
 
+import csv
+import os
 from typing import Tuple, Optional
 from datetime import datetime, timedelta, timezone
 import logging
@@ -9,6 +11,7 @@
 import copy
 import numpy as np
 import pandas as pd
+from requests import get
 import yaml
 import pytz
 import ast
@@ -19,7 +22,6 @@
 
 from emhass.machine_learning_forecaster import MLForecaster
 
-
 def get_root(file: str, num_parent: Optional[int] = 3) -> str:
     """
     Get the root absolute path of the working directory.
@@ -62,7 +64,10 @@ def get_logger(fun_name: str, emhass_conf: dict, save_to_file: Optional[bool] =
     logger.propagate = True
     logger.fileSetting = save_to_file
     if save_to_file:
-        ch = logging.FileHandler(emhass_conf['data_path'] / 'logger_emhass.log')
+        if  os.path.isdir(emhass_conf['data_path']):
+            ch = logging.FileHandler(emhass_conf['data_path'] / 'logger_emhass.log')
+        else:
+            raise Exception("Unable to access data_path: "+emhass_conf['data_path'])  
     else:
         ch = logging.StreamHandler()
     if logging_level == "DEBUG":
@@ -89,7 +94,7 @@ def get_logger(fun_name: str, emhass_conf: dict, save_to_file: Optional[bool] =
     return logger, ch
 
 
-def get_forecast_dates(freq: int, delta_forecast: int, timedelta_days: Optional[int] = 0
+def get_forecast_dates(freq: int, delta_forecast: int, time_zone: datetime.tzinfo, timedelta_days: Optional[int] = 0
                        ) -> pd.core.indexes.datetimes.DatetimeIndex:
     """
     Get the date_range list of the needed future dates using the delta_forecast parameter.
@@ -109,7 +114,7 @@ def get_forecast_dates(freq: int, delta_forecast: int, timedelta_days: Optional[
     end_forecast = (start_forecast + pd.Timedelta(days=delta_forecast)).replace(microsecond=0)
     forecast_dates = pd.date_range(start=start_forecast, 
         end=end_forecast+timedelta(days=timedelta_days)-freq, 
-        freq=freq).round(freq, ambiguous='infer', nonexistent='shift_forward')
+        freq=freq, tz=time_zone).tz_convert('utc').round(freq, ambiguous='infer', nonexistent='shift_forward').tz_convert(time_zone)
     return forecast_dates
 
 
@@ -121,7 +126,7 @@ def treat_runtimeparams(runtimeparams: str, params: str, retrieve_hass_conf: dic
 
     :param runtimeparams: Json string containing the runtime parameters dict.
     :type runtimeparams: str
-    :param params: Configuration parameters passed from data/options.json
+    :param params: Built configuration parameters
     :type params: str
     :param retrieve_hass_conf: Container for data retrieving parameters.
     :type retrieve_hass_conf: dict
@@ -137,14 +142,17 @@ def treat_runtimeparams(runtimeparams: str, params: str, retrieve_hass_conf: dic
     :rtype: Tuple[str, dict]
 
     """
+    # check if passed params is a dict
     if (params != None) and (params != "null"):
-        params = json.loads(params)
+        if type(params) is str:
+            params = json.loads(params)
     else:
         params = {}
+
     # Some default data needed
     custom_deferrable_forecast_id = []
     custom_predicted_temperature_id = []
-    for k in range(optim_conf["num_def_loads"]):
+    for k in range(optim_conf['number_of_deferrable_loads']):
         custom_deferrable_forecast_id.append(
             {
                 "entity_id": "sensor.p_deferrable{}".format(k),
@@ -224,12 +232,20 @@ def treat_runtimeparams(runtimeparams: str, params: str, retrieve_hass_conf: dic
             params["passed_data"][key] = value
     else:
         params["passed_data"] = default_passed_dict
+
     if runtimeparams is not None:
-        runtimeparams = json.loads(runtimeparams)
-        freq = int(retrieve_hass_conf["freq"].seconds / 60.0)
-        delta_forecast = int(optim_conf["delta_forecast"].days)
-        forecast_dates = get_forecast_dates(freq, delta_forecast)
-        if set_type == "regressor-model-fit":            
+        if type(runtimeparams) is str:
+            runtimeparams = json.loads(runtimeparams)
+       # Format required date/time parameters 
+        optimization_time_step = int(
+            retrieve_hass_conf['optimization_time_step'].seconds / 60.0)
+        delta_forecast = int(optim_conf['delta_forecast_daily'].days)
+        time_zone = retrieve_hass_conf["time_zone"]
+        forecast_dates = get_forecast_dates(
+            optimization_time_step, delta_forecast, time_zone)
+       
+       # regressor-model-fit
+        if set_type == "regressor-model-fit":
             if "csv_file" in runtimeparams:
                 csv_file = runtimeparams["csv_file"]
                 params["passed_data"]["csv_file"] = csv_file
@@ -249,6 +265,8 @@ def treat_runtimeparams(runtimeparams: str, params: str, retrieve_hass_conf: dic
             else:
                 date_features = runtimeparams["date_features"]
                 params["passed_data"]["date_features"] = date_features
+        
+        # regressor-model-predict
         if set_type == "regressor-model-predict":
             if "new_values" in runtimeparams:
                 new_values = runtimeparams["new_values"]
@@ -262,6 +280,7 @@ def treat_runtimeparams(runtimeparams: str, params: str, retrieve_hass_conf: dic
             if "target" in runtimeparams:
                 target = runtimeparams["target"]
                 params["passed_data"]["target"] = target
+        
         # Treating special data passed for MPC control case
         if set_type == "naive-mpc-optim":
             if "prediction_horizon" not in runtimeparams.keys():
@@ -270,74 +289,79 @@ def treat_runtimeparams(runtimeparams: str, params: str, retrieve_hass_conf: dic
                 prediction_horizon = runtimeparams["prediction_horizon"]
             params["passed_data"]["prediction_horizon"] = prediction_horizon
             if "soc_init" not in runtimeparams.keys():
-                soc_init = plant_conf["SOCtarget"]
+                soc_init = plant_conf['battery_target_state_of_charge']
             else:
                 soc_init = runtimeparams["soc_init"]
             params["passed_data"]["soc_init"] = soc_init
             if "soc_final" not in runtimeparams.keys():
-                soc_final = plant_conf["SOCtarget"]
+                soc_final = plant_conf['battery_target_state_of_charge']
             else:
                 soc_final = runtimeparams["soc_final"]
             params["passed_data"]["soc_final"] = soc_final
-            if "def_total_hours" not in runtimeparams.keys():
-                def_total_hours = optim_conf["def_total_hours"]
+            if 'operating_hours_of_each_deferrable_load' not in runtimeparams.keys():
+                def_total_hours = optim_conf['operating_hours_of_each_deferrable_load']
             else:
-                def_total_hours = runtimeparams["def_total_hours"]
-            params["passed_data"]["def_total_hours"] = def_total_hours
-            if "def_start_timestep" not in runtimeparams.keys():
-                def_start_timestep = optim_conf["def_start_timestep"]
+                def_total_hours = runtimeparams['operating_hours_of_each_deferrable_load']
+            params["passed_data"]['operating_hours_of_each_deferrable_load'] = def_total_hours
+            if 'start_timesteps_of_each_deferrable_load' in runtimeparams.keys():
+                def_start_timestep = runtimeparams['start_timesteps_of_each_deferrable_load']
             else:
-                def_start_timestep = runtimeparams["def_start_timestep"]
-            params["passed_data"]["def_start_timestep"] = def_start_timestep
-            if "def_end_timestep" not in runtimeparams.keys():
-                def_end_timestep = optim_conf["def_end_timestep"]
+                def_start_timestep = runtimeparams.get(
+                    'def_start_timestep', optim_conf['start_timesteps_of_each_deferrable_load'])
+            params["passed_data"]['start_timesteps_of_each_deferrable_load'] = def_start_timestep
+            if 'end_timesteps_of_each_deferrable_load' in runtimeparams.keys():
+                def_end_timestep = runtimeparams['end_timesteps_of_each_deferrable_load']
             else:
-                def_end_timestep = runtimeparams["def_end_timestep"]
-            params["passed_data"]["def_end_timestep"] = def_end_timestep
-            forecast_dates = copy.deepcopy(forecast_dates)[0:prediction_horizon]
+                def_end_timestep = runtimeparams.get(
+                    'def_end_timestep', optim_conf['end_timesteps_of_each_deferrable_load'])
+            params["passed_data"]['end_timesteps_of_each_deferrable_load'] = def_end_timestep
+            forecast_dates = copy.deepcopy(forecast_dates)[
+                0:prediction_horizon]
         else:
             params["passed_data"]["prediction_horizon"] = None
             params["passed_data"]["soc_init"] = None
             params["passed_data"]["soc_final"] = None
-            params["passed_data"]["def_total_hours"] = None
-            params["passed_data"]["def_start_timestep"] = None
-            params["passed_data"]["def_end_timestep"] = None
+            params["passed_data"]['operating_hours_of_each_deferrable_load'] = None
+            params["passed_data"]['start_timesteps_of_each_deferrable_load'] = None
+            params["passed_data"]['end_timesteps_of_each_deferrable_load'] = None
         # Treat passed forecast data lists
-        list_forecast_key = ['pv_power_forecast', 'load_power_forecast', 'load_cost_forecast', 'prod_price_forecast', 'outdoor_temperature_forecast']
-        forecast_methods = ['weather_forecast_method', 'load_forecast_method', 'load_cost_forecast_method', 'prod_price_forecast_method', 'outdoor_temperature_forecast_method']
-        # Param to save forecast cache (i.e. Solcast)
-        if "weather_forecast_cache" not in runtimeparams.keys():
-            weather_forecast_cache = False
-        else:
-            weather_forecast_cache = runtimeparams["weather_forecast_cache"]
-        params["passed_data"]["weather_forecast_cache"] = weather_forecast_cache  
-        # Param to make sure optimization only uses cached data. (else produce error)
-        if "weather_forecast_cache_only" not in runtimeparams.keys():
-            weather_forecast_cache_only = False
-        else:
-            weather_forecast_cache_only = runtimeparams["weather_forecast_cache_only"]
-        params["passed_data"]["weather_forecast_cache_only"] = weather_forecast_cache_only  
+        list_forecast_key = ['pv_power_forecast', 'load_power_forecast',
+                             'load_cost_forecast', 'prod_price_forecast', 'outdoor_temperature_forecast']
+        forecast_methods = ['weather_forecast_method', 'load_forecast_method', 'load_cost_forecast_method',
+                            'production_price_forecast_method', 'outdoor_temperature_forecast_method']
+        
+        # Loop forecasts, check if value is a list and greater than or equal to forecast_dates
         for method, forecast_key in enumerate(list_forecast_key):
             if forecast_key in runtimeparams.keys():
                 if type(runtimeparams[forecast_key]) == list and len(runtimeparams[forecast_key]) >= len(forecast_dates):
                     params['passed_data'][forecast_key] = runtimeparams[forecast_key]
                     optim_conf[forecast_methods[method]] = 'list'
                 else:
-                    logger.error(f"ERROR: The passed data is either not a list or the length is not correct, length should be {str(len(forecast_dates))}")
-                    logger.error(f"Passed type is {str(type(runtimeparams[forecast_key]))} and length is {str(len(runtimeparams[forecast_key]))}")
-                list_non_digits = [x for x in runtimeparams[forecast_key] if not (isinstance(x, int) or isinstance(x, float))]
+                    logger.error(
+                        f"ERROR: The passed data is either not a list or the length is not correct, length should be {str(len(forecast_dates))}")
+                    logger.error(
+                        f"Passed type is {str(type(runtimeparams[forecast_key]))} and length is {str(len(runtimeparams[forecast_key]))}")
+                # Check if string contains list, if so extract
+                if type(runtimeparams[forecast_key]) == str:
+                    if type(ast.literal_eval(runtimeparams[forecast_key])) == list:
+                        runtimeparams[forecast_key] = ast.literal_eval(runtimeparams[forecast_key])
+                list_non_digits = [x for x in runtimeparams[forecast_key] if not (
+                    isinstance(x, int) or isinstance(x, float))]
                 if len(list_non_digits) > 0:
-                    logger.warning(f"There are non numeric values on the passed data for {forecast_key}, check for missing values (nans, null, etc)")
+                    logger.warning(
+                        f"There are non numeric values on the passed data for {forecast_key}, check for missing values (nans, null, etc)")
                     for x in list_non_digits:
-                        logger.warning(f"This value in {forecast_key} was detected as non digits: {str(x)}")
+                        logger.warning(
+                            f"This value in {forecast_key} was detected as non digits: {str(x)}")
             else:
                 params['passed_data'][forecast_key] = None
+       
         # Treat passed data for forecast model fit/predict/tune at runtime
-        if "days_to_retrieve" not in runtimeparams.keys():
-            days_to_retrieve = 9
+        if 'historic_days_to_retrieve' in runtimeparams.keys():
+            days_to_retrieve = runtimeparams['historic_days_to_retrieve']
         else:
-            days_to_retrieve = runtimeparams["days_to_retrieve"]
-        params["passed_data"]["days_to_retrieve"] = days_to_retrieve
+            days_to_retrieve = runtimeparams.get('days_to_retrieve', 9)
+        params["passed_data"]['historic_days_to_retrieve'] = days_to_retrieve
         if "model_type" not in runtimeparams.keys():
             model_type = "load_forecast"
         else:
@@ -371,12 +395,14 @@ def treat_runtimeparams(runtimeparams: str, params: str, retrieve_hass_conf: dic
         if "perform_backtest" not in runtimeparams.keys():
             perform_backtest = False
         else:
-            perform_backtest = ast.literal_eval(str(runtimeparams["perform_backtest"]).capitalize())
+            perform_backtest = ast.literal_eval(
+                str(runtimeparams["perform_backtest"]).capitalize())
         params["passed_data"]["perform_backtest"] = perform_backtest
         if "model_predict_publish" not in runtimeparams.keys():
             model_predict_publish = False
         else:
-            model_predict_publish = ast.literal_eval(str(runtimeparams["model_predict_publish"]).capitalize())
+            model_predict_publish = ast.literal_eval(
+                str(runtimeparams["model_predict_publish"]).capitalize())
         params["passed_data"]["model_predict_publish"] = model_predict_publish
         if "model_predict_entity_id" not in runtimeparams.keys():
             model_predict_entity_id = "sensor.p_load_forecast_custom_model"
@@ -408,6 +434,7 @@ def treat_runtimeparams(runtimeparams: str, params: str, retrieve_hass_conf: dic
         else:
             mlr_predict_friendly_name = runtimeparams["mlr_predict_friendly_name"]
         params["passed_data"]["mlr_predict_friendly_name"] = mlr_predict_friendly_name
+        
         # Treat passed data for other parameters
         if "alpha" not in runtimeparams.keys():
             alpha = 0.5
@@ -419,68 +446,124 @@ def treat_runtimeparams(runtimeparams: str, params: str, retrieve_hass_conf: dic
         else:
             beta = runtimeparams["beta"]
         params["passed_data"]["beta"] = beta
-        # Treat optimization configuration parameters passed at runtime
-        if "num_def_loads" in runtimeparams.keys():
-            optim_conf["num_def_loads"] = runtimeparams["num_def_loads"]
-        if "P_deferrable_nom" in runtimeparams.keys():
-            optim_conf["P_deferrable_nom"] = runtimeparams["P_deferrable_nom"]
-        if "def_total_hours" in runtimeparams.keys():
-            optim_conf["def_total_hours"] = runtimeparams["def_total_hours"]
-        if "def_start_timestep" in runtimeparams.keys():
-            optim_conf["def_start_timestep"] = runtimeparams["def_start_timestep"]
-        if "def_end_timestep" in runtimeparams.keys():
-            optim_conf["def_end_timestep"] = runtimeparams["def_end_timestep"]
+        # Param to save forecast cache (i.e. Solcast)
+        if "weather_forecast_cache" not in runtimeparams.keys():
+            weather_forecast_cache = False
+        else:
+            weather_forecast_cache = runtimeparams["weather_forecast_cache"]
+        params["passed_data"]["weather_forecast_cache"] = weather_forecast_cache
+        # Param to make sure optimization only uses cached data. (else produce error)
+        if "weather_forecast_cache_only" not in runtimeparams.keys():
+            weather_forecast_cache_only = False
+        else:
+            weather_forecast_cache_only = runtimeparams["weather_forecast_cache_only"]
+        params["passed_data"]["weather_forecast_cache_only"] = weather_forecast_cache_only
+        # A condition to manually save entity data under data_path/entities after optimization
+        if "entity_save" not in runtimeparams.keys():
+            entity_save = ""
+        else:
+            entity_save = runtimeparams["entity_save"]
+        params["passed_data"]["entity_save"] = entity_save
+        # A condition to put a prefix on all published data, or check for saved data under prefix name
+        if "publish_prefix" not in runtimeparams.keys():
+            publish_prefix = ""
+        else:
+            publish_prefix = runtimeparams["publish_prefix"]
+        params["passed_data"]["publish_prefix"] = publish_prefix
+
+        # Treat optimization (optim_conf) configuration parameters passed at runtime
+        if 'number_of_deferrable_loads' in runtimeparams.keys():
+            optim_conf['number_of_deferrable_loads'] = runtimeparams['number_of_deferrable_loads']
+        if 'num_def_loads' in runtimeparams.keys():
+            optim_conf['number_of_deferrable_loads'] = runtimeparams['num_def_loads']
+        if 'nominal_power_of_deferrable_loads' in runtimeparams.keys():
+            optim_conf['nominal_power_of_deferrable_loads'] = runtimeparams['nominal_power_of_deferrable_loads']
+        if 'P_deferrable_nom' in runtimeparams.keys():
+            optim_conf['nominal_power_of_deferrable_loads'] = runtimeparams['P_deferrable_nom']
+        if 'operating_hours_of_each_deferrable_load' in runtimeparams.keys():
+            optim_conf['operating_hours_of_each_deferrable_load'] = runtimeparams['operating_hours_of_each_deferrable_load']
+        if 'def_total_hours' in runtimeparams.keys():
+            optim_conf['operating_hours_of_each_deferrable_load'] = runtimeparams['def_total_hours']
+        if 'start_timesteps_of_each_deferrable_load' in runtimeparams.keys():
+            optim_conf['start_timesteps_of_each_deferrable_load'] = runtimeparams['start_timesteps_of_each_deferrable_load']
+        if 'end_timesteps_of_each_deferrable_load' in runtimeparams.keys():
+            optim_conf['end_timesteps_of_each_deferrable_load'] = runtimeparams['end_timesteps_of_each_deferrable_load']
         if "def_current_state" in runtimeparams.keys():
-            optim_conf["def_current_state"] = [bool(s) for s in runtimeparams["def_current_state"]]
-        if "treat_def_as_semi_cont" in runtimeparams.keys():
-            optim_conf["treat_def_as_semi_cont"] = [
+            optim_conf["def_current_state"] = [
+                bool(s) for s in runtimeparams["def_current_state"]]
+        if 'treat_deferrable_load_as_semi_cont' in runtimeparams.keys():
+            optim_conf['treat_deferrable_load_as_semi_cont'] = [
+                ast.literal_eval(str(k).capitalize())
+                for k in runtimeparams['treat_deferrable_load_as_semi_cont']
+            ]
+        if 'treat_def_as_semi_cont' in runtimeparams.keys():
+            optim_conf['treat_deferrable_load_as_semi_cont'] = [
                 ast.literal_eval(str(k).capitalize())
-                for k in runtimeparams["treat_def_as_semi_cont"]
+                for k in runtimeparams['treat_def_as_semi_cont']
             ]
-        if "set_def_constant" in runtimeparams.keys():
-            optim_conf["set_def_constant"] = [
-                ast.literal_eval(str(k).capitalize()) for k in runtimeparams["set_def_constant"]
+        if 'set_deferrable_load_single_constant' in runtimeparams.keys():
+            optim_conf['set_deferrable_load_single_constant'] = [
+                ast.literal_eval(str(k).capitalize()) for k in runtimeparams['set_deferrable_load_single_constant']
             ]
-        if "def_start_penalty" in runtimeparams.keys():
-            optim_conf["def_start_penalty"] = [
-                ast.literal_eval(str(k).capitalize()) for k in runtimeparams["def_start_penalty"]
+        if 'set_def_constant' in runtimeparams.keys():
+            optim_conf['set_deferrable_load_single_constant'] = [
+                ast.literal_eval(str(k).capitalize()) for k in runtimeparams['set_def_constant']
+            ]
+        if 'set_deferrable_startup_penalty' in runtimeparams.keys():
+            optim_conf['set_deferrable_startup_penalty'] = [
+                ast.literal_eval(str(k).capitalize()) for k in runtimeparams['set_deferrable_startup_penalty']
+            ]
+        if 'def_start_penalty' in runtimeparams.keys():
+            optim_conf['set_deferrable_startup_penalty'] = [
+                ast.literal_eval(str(k).capitalize()) for k in runtimeparams['def_start_penalty']
             ]
         if 'def_load_config' in runtimeparams:
             optim_conf["def_load_config"] = runtimeparams['def_load_config']
+        if 'weight_battery_discharge' in runtimeparams.keys():
+            optim_conf['weight_battery_discharge'] = runtimeparams[
+                'weight_battery_discharge'
+            ]
+        if 'weight_battery_charge' in runtimeparams.keys():
+            optim_conf['weight_battery_charge'] = runtimeparams['weight_battery_charge']
+
+        # Treat retrieve data from Home Assistant (retrieve_hass_conf) configuration parameters passed at runtime
+        if 'optimization_time_step' in runtimeparams.keys():
+            retrieve_hass_conf['optimization_time_step'] = pd.to_timedelta(
+                runtimeparams['optimization_time_step'], "minutes")
+        if 'continual_publish' in runtimeparams.keys():
+            retrieve_hass_conf['continual_publish'] = bool(
+                runtimeparams['continual_publish'])
         if "solcast_api_key" in runtimeparams.keys():
             retrieve_hass_conf["solcast_api_key"] = runtimeparams["solcast_api_key"]
-            optim_conf["weather_forecast_method"] = "solcast"
+            optim_conf['weather_forecast_method'] = "solcast"
         if "solcast_rooftop_id" in runtimeparams.keys():
             retrieve_hass_conf["solcast_rooftop_id"] = runtimeparams[
                 "solcast_rooftop_id"
             ]
-            optim_conf["weather_forecast_method"] = "solcast"
+            optim_conf['weather_forecast_method'] = "solcast"
         if "solar_forecast_kwp" in runtimeparams.keys():
             retrieve_hass_conf["solar_forecast_kwp"] = runtimeparams[
                 "solar_forecast_kwp"
             ]
-            optim_conf["weather_forecast_method"] = "solar.forecast"
-        if "weight_battery_discharge" in runtimeparams.keys():
-            optim_conf["weight_battery_discharge"] = runtimeparams[
-                "weight_battery_discharge"
-            ]
-        if "weight_battery_charge" in runtimeparams.keys():
-            optim_conf["weight_battery_charge"] = runtimeparams["weight_battery_charge"]
-        if 'freq' in runtimeparams.keys():
-            retrieve_hass_conf['freq'] = pd.to_timedelta(runtimeparams['freq'], "minutes")
-        if 'continual_publish' in runtimeparams.keys():
-            retrieve_hass_conf['continual_publish'] = bool(runtimeparams['continual_publish'])
-        # Treat plant configuration parameters passed at runtime
-        if "SOCmin" in runtimeparams.keys():
-            plant_conf["SOCmin"] = runtimeparams["SOCmin"]
-        if "SOCmax" in runtimeparams.keys():
-            plant_conf["SOCmax"] = runtimeparams["SOCmax"]
-        if "SOCtarget" in runtimeparams.keys():
-            plant_conf["SOCtarget"] = runtimeparams["SOCtarget"]
-        if "Pd_max" in runtimeparams.keys():
-            plant_conf["Pd_max"] = runtimeparams["Pd_max"]
-        if "Pc_max" in runtimeparams.keys():
-            plant_conf["Pc_max"] = runtimeparams["Pc_max"]
+            optim_conf['weather_forecast_method'] = "solar.forecast"
+
+        # Treat system model parameters (plant) configuration parameters passed at runtime
+        if 'battery_minimum_state_of_charge' in runtimeparams.keys() or 'SOCmin' in runtimeparams.keys():
+            plant_conf['battery_minimum_state_of_charge'] = runtimeparams.get(
+                'battery_minimum_state_of_charge', runtimeparams.get('SOCmin'))
+        if 'battery_maximum_state_of_charge' in runtimeparams.keys() or 'SOCmax' in runtimeparams.keys():
+            plant_conf['battery_maximum_state_of_charge'] = runtimeparams.get(
+                'battery_maximum_state_of_charge', runtimeparams.get('SOCmax'))
+        if 'battery_target_state_of_charge' in runtimeparams.keys() or 'SOCtarget' in runtimeparams.keys():
+            plant_conf['battery_target_state_of_charge'] = runtimeparams.get(
+                'battery_target_state_of_charge', runtimeparams.get('SOCtarget'))
+        if 'battery_discharge_power_max' in runtimeparams.keys() or 'Pd_max' in runtimeparams.keys():
+            plant_conf['battery_discharge_power_max'] = runtimeparams.get(
+                'battery_discharge_power_max', runtimeparams.get('Pd_max'))
+        if 'battery_charge_power_max' in runtimeparams.keys() or 'Pc_max' in runtimeparams.keys():
+            plant_conf['battery_charge_power_max'] = runtimeparams.get(
+                'battery_charge_power_max', runtimeparams.get('Pc_max'))
+        
         # Treat custom entities id's and friendly names for variables
         if "custom_pv_forecast_id" in runtimeparams.keys():
             params["passed_data"]["custom_pv_forecast_id"] = runtimeparams[
@@ -534,84 +617,47 @@ def treat_runtimeparams(runtimeparams: str, params: str, retrieve_hass_conf: dic
             params["passed_data"]["custom_predicted_temperature_id"] = runtimeparams[
                 "custom_predicted_temperature_id"
             ]
-        # A condition to put a prefix on all published data, or check for saved data under prefix name
-        if "publish_prefix" not in runtimeparams.keys():
-            publish_prefix = ""
-        else:
-            publish_prefix = runtimeparams["publish_prefix"]
-        params["passed_data"]["publish_prefix"] = publish_prefix
-        # A condition to manually save entity data under data_path/entities after optimization
-        if "entity_save" not in runtimeparams.keys():
-            entity_save = ""
-        else:
-            entity_save = runtimeparams["entity_save"]
-        params["passed_data"]["entity_save"] = entity_save
+       
     # Serialize the final params
-    params = json.dumps(params)
+    params = json.dumps(params, default=str)
     return params, retrieve_hass_conf, optim_conf, plant_conf
 
 
-def get_yaml_parse(emhass_conf: dict, use_secrets: Optional[bool] = True,
-                   params: Optional[str] = None) -> Tuple[dict, dict, dict]:
+def get_yaml_parse(params: str, logger: logging.Logger) -> Tuple[dict, dict, dict]:
     """
-    Perform parsing of the config.yaml file.
+    Perform parsing of the params into the configuration catagories 
     
-    :param emhass_conf: Dictionary containing the needed emhass paths
-    :type emhass_conf: dict
-    :param use_secrets: Indicate if we should use a secrets file or not.
-        Set to False for unit tests.
-    :type use_secrets: bool, optional
-    :param params: Configuration parameters passed from data/options.json
+    :param params: Built configuration parameters
     :type params: str
+    :param logger: The logger object
+    :type logger: logging.Logger
     :return: A tuple with the dictionaries containing the parsed data
     :rtype: tuple(dict)
 
     """
-    if params is None:
-        with open(emhass_conf["config_path"], 'r') as file:
-            input_conf = yaml.load(file, Loader=yaml.FullLoader)
-    else:
-        input_conf = json.loads(params)
-    if use_secrets:
-        if params is None:
-            with open(emhass_conf["config_path"].parent / 'secrets_emhass.yaml', 'r') as file: # Assume secrets and config file paths are the same 
-                input_secrets = yaml.load(file, Loader=yaml.FullLoader)
+    if params:
+        if type(params) is str:
+            input_conf = json.loads(params)
         else:
-            input_secrets = input_conf.pop("params_secrets", None)
-
-    if type(input_conf["retrieve_hass_conf"]) == list:  # if using old config version
-        retrieve_hass_conf = dict(
-            {key: d[key] for d in input_conf["retrieve_hass_conf"] for key in d}
-        )
+            input_conf = params
     else:
-        retrieve_hass_conf = input_conf.get("retrieve_hass_conf", {})
+        input_conf = {}
+        logger.error("No params have been detected for get_yaml_parse")
+        return False, False, False
 
-    if use_secrets:
-        retrieve_hass_conf.update(input_secrets)
-    else:
-        retrieve_hass_conf["hass_url"] = "http://supervisor/core/api"
-        retrieve_hass_conf["long_lived_token"] = "${SUPERVISOR_TOKEN}"
-        retrieve_hass_conf["time_zone"] = "Europe/Paris"
-        retrieve_hass_conf["lat"] = 45.83
-        retrieve_hass_conf["lon"] = 6.86
-        retrieve_hass_conf["alt"] = 4807.8
-    retrieve_hass_conf["freq"] = pd.to_timedelta(retrieve_hass_conf["freq"], "minutes")
-    retrieve_hass_conf["time_zone"] = pytz.timezone(retrieve_hass_conf["time_zone"])
-
-    if type(input_conf["optim_conf"]) == list:
-        optim_conf = dict({key: d[key] for d in input_conf["optim_conf"] for key in d})
-    else:
-        optim_conf = input_conf.get("optim_conf", {})
+    optim_conf = input_conf.get("optim_conf", {})
 
-    optim_conf["list_hp_periods"] = dict(
-        (key, d[key]) for d in optim_conf["list_hp_periods"] for key in d
-    )
-    optim_conf["delta_forecast"] = pd.Timedelta(days=optim_conf["delta_forecast"])
+    retrieve_hass_conf = input_conf.get("retrieve_hass_conf", {})
 
-    if type(input_conf["plant_conf"]) == list:
-        plant_conf = dict({key: d[key] for d in input_conf["plant_conf"] for key in d})
-    else:
-        plant_conf = input_conf.get("plant_conf", {})
+    plant_conf = input_conf.get("plant_conf", {})
+
+    # Format time parameters
+    if optim_conf.get('delta_forecast_daily',None) is not None:
+        optim_conf['delta_forecast_daily'] = pd.Timedelta(days=optim_conf['delta_forecast_daily'])
+    if retrieve_hass_conf.get('optimization_time_step',None) is not None:
+        retrieve_hass_conf['optimization_time_step'] = pd.to_timedelta(retrieve_hass_conf['optimization_time_step'], "minutes")
+    if retrieve_hass_conf.get('time_zone',None) is not None:
+        retrieve_hass_conf["time_zone"] = pytz.timezone(retrieve_hass_conf["time_zone"])    
 
     return retrieve_hass_conf, optim_conf, plant_conf
 
@@ -762,156 +808,402 @@ def get_injection_dict_forecast_model_tune(df_pred_optim: pd.DataFrame, mlf: MLF
     injection_dict["figure_0"] = image_path_0
     return injection_dict
 
+def build_config(emhass_conf: dict, logger: logging.Logger, defaults_path: str, config_path: Optional[str] = None, 
+                 legacy_config_path: Optional[str] = None) -> dict:
+    """
+    Retrieve parameters from configuration files. 
+    priority order (low - high) = defaults_path, config_path legacy_config_path
 
-def build_params(params: dict, params_secrets: dict, options: dict, addon: int,
+    :param emhass_conf: Dictionary containing the needed emhass paths
+    :type emhass_conf: dict
+    :param logger: The logger object
+    :type logger: logging.Logger
+    :param defaults_path: path to config file for parameter defaults (config_defaults.json)
+    :type defaults_path: str
+    :param config_path: path to the main configuration file (config.json)
+    :type config_path: str
+    :param legacy_config_path: path to legacy config file (config_emhass.yaml)
+    :type legacy_config_path: str
+    :return: The built config dictionary
+    :rtype: dict
+    """
+
+    # Read default parameters (default root_path/data/config_defaults.json)
+    if defaults_path and pathlib.Path(defaults_path).is_file():
+        with defaults_path.open('r') as data:
+            config = json.load(data)
+    else:
+        logger.error("config_defaults.json. does not exist ")
+        return False
+    
+    # Read user config parameters if provided (default /share/config.json)
+    if config_path and pathlib.Path(config_path).is_file():
+        with config_path.open('r') as data:
+            # Set override default parameters (config_defaults) with user given parameters (config.json)
+            logger.info("Obtaining parameters from config.json:")
+            config.update(json.load(data))
+    else:
+        logger.info("config.json does not exist, or has not been passed")
+        logger.info("you may like to generate the config.json file on the configuration page")
+
+    # Check to see if legacy config_emhass.yaml was provided (default /app/config_emhass.yaml)
+    # Convert legacy parameter definitions/format to match config.json
+    if legacy_config_path and pathlib.Path(legacy_config_path).is_file():
+        with open(legacy_config_path, 'r') as data:
+            legacy_config = yaml.load(data, Loader=yaml.FullLoader)
+            legacy_config_parameters = build_legacy_config_params(emhass_conf,legacy_config,logger)
+            if type(legacy_config_parameters) is not bool:
+                logger.info("Obtaining parameters from config_emhass.yaml:")
+                config.update(legacy_config_parameters)     
+
+    return config
+
+
+def build_legacy_config_params(emhass_conf: dict, legacy_config: dict,
+                 logger: logging.Logger) -> dict:
+    """
+    Build a config dictionary with legacy config_emhass.yaml file. 
+    Uses the associations file to convert parameter naming conventions (to config.json/config_defaults.json).
+    Extracts the parameter values and formats to match config.json.
+
+    :param emhass_conf: Dictionary containing the needed emhass paths
+    :type emhass_conf: dict
+    :param legacy_config: The legacy config dictionary
+    :type legacy_config: dict
+    :param logger: The logger object
+    :type logger: logging.Logger
+    :return: The built config dictionary
+    :rtype: dict
+    """
+
+
+    # Association file key reference
+    # association[0] = config catagories
+    # association[1] = legacy parameter name
+    # association[2] = parameter (config.json/config_defaults.json)
+    # association[3] = parameter list name if exists (not used, from legacy options.json) 
+
+    # Check each config catagories exists, else create blank dict for categories (avoid errors)
+    legacy_config['retrieve_hass_conf'] = legacy_config.get('retrieve_hass_conf',{})
+    legacy_config['optim_conf'] = legacy_config.get('optim_conf',{})
+    legacy_config['plant_conf'] = legacy_config.get('plant_conf',{})
+    config = {}
+
+    # Use associations list to map legacy parameter name with config.json parameter name
+    if emhass_conf['associations_path'].exists():
+            with emhass_conf['associations_path'].open('r') as data:
+                associations = list(csv.reader(data, delimiter=","))
+    else:
+        logger.error("Cant find associations file (associations.csv) in: "  + str(emhass_conf['associations_path']))
+        return False
+    
+    # Loop through all parameters in association file
+    # Append config with existing legacy config parameters (converting alternative parameter naming conventions with associations list)
+    for association in associations:
+        # if legacy config catagories exists and if legacy parameter exists in config catagories
+        if legacy_config.get(association[0],None) is not None and legacy_config[association[0]].get(association[1],None) is not None:
+            config[association[2]] = legacy_config[association[0]][association[1]]
+            
+            # If config now has load_peak_hour_periods, extract from list of dict
+            if association[2] == "load_peak_hour_periods" and type(config[association[2]]) is list:
+                config[association[2]] = dict((key, d[key]) for d in config[association[2]] for key in d)
+                
+    return config
+    # params['associations_dict'] = associations_dict
+
+def param_to_config(param: dict,
                  logger: logging.Logger) -> dict:
     """
-    Build the main params dictionary from the loaded options.json when using the add-on.
+    A function that extracts the parameters from param back to the config.json format.
+    Extracts parameters from config catagories.
+    Attempts to exclude secrets hosed in retrieve_hass_conf.
+    
+    :param params: Built configuration parameters
+    :type param: dict
+    :param logger: The logger object
+    :type logger: logging.Logger
+    :return: The built config dictionary
+    :rtype: dict
+    """ 
+    logger.debug("Converting param to config")
+
+    return_config = {}
+
+    config_catagories = ["retrieve_hass_conf","optim_conf","plant_conf"]
+    secret_params = ["hass_url", "time_zone", "Latitude", "Longitude", "Altitude", "long_lived_token", "solcast_api_key", "solcast_rooftop_id", "solar_forecast_kwp"]
+    
+    # Loop through config catagories that contain config params, and extract
+    for config in config_catagories:
+        for parameter in param[config]:
+                # If parameter is not a secret, append to return_config
+                if parameter not in secret_params:
+                    return_config[str(parameter)] = param[config][parameter]
+    
+    return return_config
+
+def build_secrets(emhass_conf: dict, logger: logging.Logger, argument: Optional[dict] = {}, options_path: Optional[str] = None, 
+                 secrets_path: Optional[str] = None, no_response: Optional[bool] = False) -> Tuple[dict, dict]:    
+    """
+    Retrieve and build parameters from secrets locations (ENV, ARG, Secrets file (secrets_emhass.yaml/options.json) and/or Home Assistant (via API))
+    priority order (lwo to high) = Defaults (written in function), ENV, Options json file, Home Assistant API,  Secrets yaml file, Arguments
+    
+    :param emhass_conf: Dictionary containing the needed emhass paths
+    :type emhass_conf: dict
+    :param logger: The logger object
+    :type logger: logging.Logger
+    :param argument: dictionary of secrets arguments passed (url,key)
+    :type argument: dict
+    :param options_path: path to the options file (options.json) (usually provided bt EMHASS-Add-on)
+    :type options_path: str
+    :param secrets_path: path to secrets file (secrets_emhass.yaml)
+    :type secrets_path: str
+    :param no_response: bypass get request to Home Assistant (json response errors)
+    :type no_response: bool
+    :return: Updated emhass_conf, the built secrets dictionary
+    :rtype: Tuple[dict, dict]:
+    """
+
+    #Set defaults to be overwritten
+    params_secrets = {
+        "hass_url": "https://myhass.duckdns.org/",
+        "long_lived_token": "thatverylongtokenhere",
+        "time_zone": "Europe/Paris",
+        "Latitude": 45.83,
+        "Longitude": 6.86,
+        "Altitude": 4807.8,
+        "solcast_api_key": "yoursecretsolcastapikey",
+        "solcast_rooftop_id": "yourrooftopid",
+        "solar_forecast_kwp": 5
+    }
+
+    # Obtain Secrets from ENV?
+    params_secrets['hass_url'] = os.getenv("EMHASS_URL",params_secrets['hass_url'])
+    params_secrets['long_lived_token'] = os.getenv("SUPERVISOR_TOKEN", params_secrets['long_lived_token'])   
+    params_secrets['time_zone'] = os.getenv("TIME_ZONE", params_secrets['time_zone'])
+    params_secrets['Latitude'] = float(os.getenv("LAT", params_secrets['Latitude']))
+    params_secrets['Longitude'] = float(os.getenv("LON",  params_secrets['Longitude']))
+    params_secrets['Altitude'] = float(os.getenv("ALT", params_secrets['Altitude']))      
+
+    # Obtain secrets from options.json (Generated from EMHASS-Add-on, Home Assistant addon Configuration page) or Home Assistant API (from local Supervisor API)?
+    # Use local supervisor API to obtain secrets from Home Assistant if hass_url in options.json is empty and SUPERVISOR_TOKEN ENV exists (provided by Home Assistant when running the container as addon)
+    options = {}
+    if options_path and pathlib.Path(options_path).is_file():
+        with options_path.open('r') as data:
+            options = json.load(data)
+            
+            # Obtain secrets from Home Assistant?
+            url_from_options = options.get('hass_url', 'empty')
+            key_from_options = options.get('long_lived_token', 'empty') 
+
+            # If data path specified by options.json, overwrite emhass_conf['data_path']
+            if options.get('data_path', None) != None and pathlib.Path(options['data_path']).exists():
+                emhass_conf['data_path'] = pathlib.Path(options['data_path']);  
+            
+            # Check to use Home Assistant local API
+            if not no_response and \
+                (url_from_options == 'empty' or url_from_options == '' or url_from_options == "http://supervisor/core/api") and \
+                os.getenv("SUPERVISOR_TOKEN", None) is not None:
+                
+                params_secrets['long_lived_token'] = os.getenv("SUPERVISOR_TOKEN",None)
+                params_secrets['hass_url'] = "http://supervisor/core/api"
+                headers = {
+                "Authorization": "Bearer " + params_secrets['long_lived_token'],
+                "content-type": "application/json"
+                }
+                # Obtain secrets from Home Assistant via API
+                logger.debug("Obtaining secrets from Home Assistant Supervisor API")
+                response = get((params_secrets['hass_url'] + "/config"), headers=headers)
+                if response.status_code < 400:
+                    config_hass = response.json()
+                    params_secrets = {
+                    'hass_url': params_secrets['hass_url'],
+                    'long_lived_token': params_secrets['long_lived_token'],
+                    'time_zone': config_hass['time_zone'],
+                    'Latitude': config_hass['latitude'],
+                    'Longitude': config_hass['longitude'],
+                    'Altitude': config_hass['elevation']
+                    }
+                else: 
+                    # Obtain the url and key secrets if any from options.json (default /app/options.json)
+                    logger.warning("Error obtaining secrets from Home Assistant Supervisor API")
+                    logger.debug("Obtaining url and key secrets from options.json")
+                    if url_from_options != 'empty' and url_from_options != '':
+                        params_secrets['hass_url'] = url_from_options
+                    if key_from_options != 'empty' and key_from_options != '':
+                        params_secrets['long_lived_token'] = key_from_options
+                    if options.get('time_zone',"empty") != "empty" and options['time_zone'] != '':
+                        params_secrets['time_zone'] = options['time_zone']
+                    if options.get('Latitude',None) is not None and bool(options['Latitude']):
+                        params_secrets['Latitude'] = options['Latitude']
+                    if options.get('Longitude',None) is not None and bool(options['Longitude']):
+                        params_secrets['Longitude'] = options['Longitude']         
+                    if options.get('Altitude',None) is not None and bool(options['Altitude']):
+                        params_secrets['Altitude'] = options['Altitude']
+            else:
+                # Obtain the url and key secrets if any from options.json (default /app/options.json)
+                logger.debug("Obtaining url and key secrets from options.json")
+                if url_from_options != 'empty' and url_from_options != '':
+                    params_secrets['hass_url'] = url_from_options
+                if key_from_options != 'empty' and key_from_options != '':
+                    params_secrets['long_lived_token'] = key_from_options
+                if options.get('time_zone',"empty") != "empty" and options['time_zone'] != '':
+                    params_secrets['time_zone'] = options['time_zone']
+                if options.get('Latitude',None) is not None and bool(options['Latitude']):
+                    params_secrets['Latitude'] = options['Latitude']
+                if options.get('Longitude',None) is not None and bool(options['Longitude']):
+                    params_secrets['Longitude'] = options['Longitude']
+                if options.get('Altitude',None) is not None and bool(options['Altitude']):
+                    params_secrets['Altitude'] = options['Altitude']
+                
+            # Obtain the forecast secrets (if any) from options.json (default /app/options.json)
+            forecast_secrets = ["solcast_api_key","solcast_rooftop_id","solar_forecast_kwp"]
+            if any(x in forecast_secrets for x in list(options.keys())):
+                logger.debug("Obtaining forecast secrets from options.json")
+                if options.get('solcast_api_key',"empty") != "empty" and options['solcast_api_key'] != '':
+                    params_secrets['solcast_api_key'] = options['solcast_api_key']
+                if options.get('solcast_rooftop_id',"empty") != "empty" and options['solcast_rooftop_id'] != '':
+                    params_secrets['solcast_rooftop_id'] = options['solcast_rooftop_id']
+                if options.get('solar_forecast_kwp',None) and bool(options['solar_forecast_kwp']):
+                    params_secrets['solar_forecast_kwp'] = options['solar_forecast_kwp']
+     
+    # Obtain secrets from secrets_emhass.yaml? (default /app/secrets_emhass.yaml)
+    if secrets_path and pathlib.Path(secrets_path).is_file():
+        logger.debug("Obtaining secrets from secrets file")
+        with open(pathlib.Path(secrets_path), 'r') as file:
+            params_secrets.update(yaml.load(file, Loader=yaml.FullLoader))
+
+    # Receive key and url from ARG/arguments? 
+    if argument.get('url',None) is not None:
+       params_secrets['hass_url'] = argument['url']
+       logger.debug("Obtaining url from passed argument")  
+    if argument.get('key',None) is not None:
+        params_secrets['long_lived_token'] = argument['key']  
+        logger.debug("Obtaining long_lived_token from passed argument") 
+ 
+    return emhass_conf, params_secrets 
+ 
+   
 
-    :param params: The main params dictionary
-    :type params: dict
-    :param params_secrets: The dictionary containing the secret protected variables
+def build_params(emhass_conf: dict, params_secrets: dict, config: dict,
+                 logger: logging.Logger) -> dict:
+    """
+    Build the main params dictionary from the config and secrets
+    Appends configuration catagories used by emhass to the parameters. (with use of the associations file as a reference)
+    
+    :param emhass_conf: Dictionary containing the needed emhass paths
+    :type emhass_conf: dict
+    :param params_secrets: The dictionary containing the built secret variables
     :type params_secrets: dict
-    :param options: The load dictionary from options.json
-    :type options: dict
-    :param addon: A "bool" to select if we are using the add-on
-    :type addon: int
+    :param config: The dictionary of built config parameters 
+    :type config: dict
     :param logger: The logger object
     :type logger: logging.Logger
-    :return: The builded dictionary
+    :return: The built param dictionary
     :rtype: dict
     """
-    if addon == 1:
-        # Updating variables in retrieve_hass_conf
-        params["retrieve_hass_conf"]["freq"] = options.get("optimization_time_step", params["retrieve_hass_conf"]["freq"])
-        params["retrieve_hass_conf"]["days_to_retrieve"] = options.get("historic_days_to_retrieve", params["retrieve_hass_conf"]["days_to_retrieve"])
-        params["retrieve_hass_conf"]["var_PV"] = options.get("sensor_power_photovoltaics", params["retrieve_hass_conf"]["var_PV"])
-        params["retrieve_hass_conf"]["var_load"] = options.get("sensor_power_load_no_var_loads", params["retrieve_hass_conf"]["var_load"])
-        params["retrieve_hass_conf"]["load_negative"] = options.get("load_negative", params["retrieve_hass_conf"]["load_negative"])
-        params["retrieve_hass_conf"]["set_zero_min"] = options.get("set_zero_min", params["retrieve_hass_conf"]["set_zero_min"])
-        params["retrieve_hass_conf"]["var_replace_zero"] = [options.get("sensor_power_photovoltaics", params["retrieve_hass_conf"]["var_replace_zero"])]
-        params["retrieve_hass_conf"]["var_interp"] = [
-            options.get("sensor_power_photovoltaics", params["retrieve_hass_conf"]["var_PV"]),
-            options.get("sensor_power_load_no_var_loads", params["retrieve_hass_conf"]["var_load"])
-        ]
-        params["retrieve_hass_conf"]["method_ts_round"] = options.get("method_ts_round", params["retrieve_hass_conf"]["method_ts_round"])
-        params["retrieve_hass_conf"]["continual_publish"] = options.get("continual_publish", params["retrieve_hass_conf"]["continual_publish"])
-        # Update params Secrets if specified
-        params["params_secrets"] = params_secrets
-        params["params_secrets"]["time_zone"] = options.get("time_zone", params_secrets["time_zone"])
-        params["params_secrets"]["lat"] = options.get("Latitude", params_secrets["lat"])
-        params["params_secrets"]["lon"] = options.get("Longitude", params_secrets["lon"])
-        params["params_secrets"]["alt"] = options.get("Altitude", params_secrets["alt"])
-        # Updating variables in optim_conf
-        params["optim_conf"]["set_use_battery"] = options.get("set_use_battery", params["optim_conf"]["set_use_battery"])
-        params["optim_conf"]["num_def_loads"] = options.get("number_of_deferrable_loads", params["optim_conf"]["num_def_loads"])
-        if options.get("list_nominal_power_of_deferrable_loads", None) != None:
-            params["optim_conf"]["P_deferrable_nom"] = [i["nominal_power_of_deferrable_loads"] for i in options.get("list_nominal_power_of_deferrable_loads")]
-        if options.get("list_operating_hours_of_each_deferrable_load", None) != None:
-            params["optim_conf"]["def_total_hours"] = [i["operating_hours_of_each_deferrable_load"] for i in options.get("list_operating_hours_of_each_deferrable_load")]
-        if options.get("list_treat_deferrable_load_as_semi_cont", None) != None:
-            params["optim_conf"]["treat_def_as_semi_cont"] = [i["treat_deferrable_load_as_semi_cont"] for i in options.get("list_treat_deferrable_load_as_semi_cont")]
-        if options.get("list_set_deferrable_load_single_constant", None) != None:
-            params["optim_conf"]["set_def_constant"] = [i["set_deferrable_load_single_constant"] for i in options.get("list_set_deferrable_load_single_constant")]
-        if options.get("list_set_deferrable_startup_penalty", None) != None:
-            params["optim_conf"]["def_start_penalty"] = [i["set_deferrable_startup_penalty"] for i in options.get("list_set_deferrable_startup_penalty")]
-        params["optim_conf"]["weather_forecast_method"] = options.get("weather_forecast_method", params["optim_conf"]["weather_forecast_method"])
-        # Update optional param secrets
-        if params["optim_conf"]["weather_forecast_method"] == "solcast":
-            params["params_secrets"]["solcast_api_key"] = options.get("optional_solcast_api_key", params_secrets.get("solcast_api_key", "123456"))
-            params["params_secrets"]["solcast_rooftop_id"] = options.get("optional_solcast_rooftop_id", params_secrets.get("solcast_rooftop_id", "123456"))
-        elif params["optim_conf"]["weather_forecast_method"] == "solar.forecast":
-            params["params_secrets"]["solar_forecast_kwp"] = options.get("optional_solar_forecast_kwp", params_secrets.get("solar_forecast_kwp", 5))
-        params["optim_conf"]["load_forecast_method"] = options.get("load_forecast_method", params["optim_conf"]["load_forecast_method"])
-        params["optim_conf"]["delta_forecast"] = options.get("delta_forecast_daily", params["optim_conf"]["delta_forecast"])
-        params["optim_conf"]["load_cost_forecast_method"] = options.get("load_cost_forecast_method", params["optim_conf"]["load_cost_forecast_method"])
-        if (options.get("list_peak_hours_periods_start_hours", None) != None and options.get("list_peak_hours_periods_end_hours", None) != None):
-            start_hours_list = [i["peak_hours_periods_start_hours"] for i in options["list_peak_hours_periods_start_hours"]]
-            end_hours_list = [i["peak_hours_periods_end_hours"] for i in options["list_peak_hours_periods_end_hours"]]
+    if type(params_secrets) is not dict:
+        params_secrets = {}
+    
+    params = {}
+    #Start with blank config catagories
+    params['retrieve_hass_conf'] = {}
+    params['params_secrets'] = {}
+    params['optim_conf'] = {}
+    params['plant_conf'] = {}
+
+    # Obtain associations to categorize parameters to their corresponding config catagories 
+    if emhass_conf.get('associations_path', get_root(__file__, num_parent=2) / 'data/associations.csv').exists():
+        with emhass_conf['associations_path'].open('r') as data:
+            associations = list(csv.reader(data, delimiter=","))
+    else:
+        logger.error("Unable to obtain the associations file (associations.csv) in: "  + str(emhass_conf['associations_path']))
+        return False
+
+    # Association file key reference
+    # association[0] = config catagories
+    # association[1] = legacy parameter name
+    # association[2] = parameter (config.json/config_defaults.json)
+    # association[3] = parameter list name if exists (not used, from legacy options.json) 
+
+    # Use association list to append parameters from config into params (with corresponding config catagories)
+    for association in associations:
+        # If parameter has list_ name and parameter in config is presented with its list name 
+        # (ie, config parameter is in legacy options.json format)
+        if len(association) == 4 and config.get(association[3],None) is not None:
+            # Extract lists of dictionaries
+            if config[association[3]] and type(config[association[3]][0]) is dict:
+                params[association[0]][association[2]] = [i[association[2]] for i in config[association[3]]]
+            else:
+                params[association[0]][association[2]] = config[association[3]]
+        # Else, directly set value of config parameter to param  
+        elif config.get(association[2],None) is not None:
+            params[association[0]][association[2]] = config[association[2]]
+
+    # Check if we need to create `list_hp_periods` from config (ie. legacy options.json format)
+    if params.get('optim_conf',None) is not None and config.get("list_peak_hours_periods_start_hours", None) is not None and config.get("list_peak_hours_periods_end_hours", None) is not None:
+            start_hours_list = [i["peak_hours_periods_start_hours"] for i in config["list_peak_hours_periods_start_hours"]]
+            end_hours_list = [i["peak_hours_periods_end_hours"] for i in config["list_peak_hours_periods_end_hours"]]
             num_peak_hours = len(start_hours_list)
-            list_hp_periods_list = [{'period_hp_'+str(i+1):[{'start':start_hours_list[i]},{'end':end_hours_list[i]}]} for i in range(num_peak_hours)]
-            params['optim_conf']['list_hp_periods'] = list_hp_periods_list
-        params['optim_conf']['load_cost_hp'] = options.get('load_peak_hours_cost', params['optim_conf']['load_cost_hp'])
-        params['optim_conf']['load_cost_hc'] = options.get('load_offpeak_hours_cost', params['optim_conf']['load_cost_hc'])
-        params['optim_conf']['prod_price_forecast_method'] = options.get('production_price_forecast_method', params['optim_conf']['prod_price_forecast_method'])
-        params['optim_conf']['prod_sell_price'] = options.get('photovoltaic_production_sell_price', params['optim_conf']['prod_sell_price'])
-        params['optim_conf']['set_total_pv_sell'] = options.get('set_total_pv_sell', params['optim_conf']['set_total_pv_sell'])
-        params['optim_conf']['lp_solver'] = options.get('lp_solver', params['optim_conf']['lp_solver'])
-        params['optim_conf']['lp_solver_path'] = options.get('lp_solver_path', params['optim_conf']['lp_solver_path'])
-        params['optim_conf']['set_nocharge_from_grid'] = options.get('set_nocharge_from_grid', params['optim_conf']['set_nocharge_from_grid'])
-        params['optim_conf']['set_nodischarge_to_grid'] = options.get('set_nodischarge_to_grid', params['optim_conf']['set_nodischarge_to_grid'])
-        params['optim_conf']['set_battery_dynamic'] = options.get('set_battery_dynamic', params['optim_conf']['set_battery_dynamic'])
-        params['optim_conf']['battery_dynamic_max'] = options.get('battery_dynamic_max', params['optim_conf']['battery_dynamic_max'])
-        params['optim_conf']['battery_dynamic_min'] = options.get('battery_dynamic_min', params['optim_conf']['battery_dynamic_min'])
-        params['optim_conf']['weight_battery_discharge'] = options.get('weight_battery_discharge', params['optim_conf']['weight_battery_discharge'])
-        params['optim_conf']['weight_battery_charge'] = options.get('weight_battery_charge', params['optim_conf']['weight_battery_charge'])
-        if options.get('list_start_timesteps_of_each_deferrable_load',None) != None: 
-            params['optim_conf']['def_start_timestep'] = [i['start_timesteps_of_each_deferrable_load'] for i in options.get('list_start_timesteps_of_each_deferrable_load')]
-        if options.get('list_end_timesteps_of_each_deferrable_load',None) != None: 
-            params['optim_conf']['def_end_timestep'] = [i['end_timesteps_of_each_deferrable_load'] for i in options.get('list_end_timesteps_of_each_deferrable_load')]
-        # Updating variables in plant_conf
-        params['plant_conf']['P_from_grid_max'] = options.get('maximum_power_from_grid', params['plant_conf']['P_from_grid_max'])
-        params['plant_conf']['P_to_grid_max'] = options.get('maximum_power_to_grid', params['plant_conf']['P_to_grid_max'])
-        if options.get('list_pv_module_model',None) != None:         
-            params['plant_conf']['module_model'] = [i['pv_module_model'] for i in options.get('list_pv_module_model')]
-        if options.get('list_pv_inverter_model',None) != None:        
-            params['plant_conf']['inverter_model'] = [i['pv_inverter_model'] for i in options.get('list_pv_inverter_model')]
-        if options.get('list_surface_tilt',None) != None:        
-            params['plant_conf']['surface_tilt'] = [i['surface_tilt'] for i in options.get('list_surface_tilt')]
-        if options.get('list_surface_azimuth',None) != None:         
-            params['plant_conf']['surface_azimuth'] = [i['surface_azimuth'] for i in options.get('list_surface_azimuth')]
-        if options.get('list_modules_per_string',None) != None:         
-            params['plant_conf']['modules_per_string'] = [i['modules_per_string'] for i in options.get('list_modules_per_string')]
-        if options.get('list_strings_per_inverter',None) != None: 
-            params['plant_conf']['strings_per_inverter'] = [i['strings_per_inverter'] for i in options.get('list_strings_per_inverter')]
-        params["plant_conf"]["inverter_is_hybrid"] = options.get("inverter_is_hybrid", params["plant_conf"]["inverter_is_hybrid"])
-        params["plant_conf"]["compute_curtailment"] = options.get("compute_curtailment", params["plant_conf"]["compute_curtailment"])
-        params['plant_conf']['Pd_max'] = options.get('battery_discharge_power_max', params['plant_conf']['Pd_max']) 
-        params['plant_conf']['Pc_max'] = options.get('battery_charge_power_max', params['plant_conf']['Pc_max'])
-        params['plant_conf']['eta_disch'] = options.get('battery_discharge_efficiency', params['plant_conf']['eta_disch'])
-        params['plant_conf']['eta_ch'] = options.get('battery_charge_efficiency', params['plant_conf']['eta_ch'])
-        params['plant_conf']['Enom'] = options.get('battery_nominal_energy_capacity', params['plant_conf']['Enom'])
-        params['plant_conf']['SOCmin'] = options.get('battery_minimum_state_of_charge', params['plant_conf']['SOCmin']) 
-        params['plant_conf']['SOCmax'] = options.get('battery_maximum_state_of_charge', params['plant_conf']['SOCmax']) 
-        params['plant_conf']['SOCtarget'] = options.get('battery_target_state_of_charge', params['plant_conf']['SOCtarget'])
-        # Check parameter lists have the same amounts as deferrable loads
-        # If not, set defaults it fill in gaps
-        if params['optim_conf']['num_def_loads'] is not len(params['optim_conf']['def_start_timestep']):
-            logger.warning("def_start_timestep / list_start_timesteps_of_each_deferrable_load does not match number in num_def_loads, adding default values to parameter")
-            for x in range(len(params['optim_conf']['def_start_timestep']), params['optim_conf']['num_def_loads']):
-                params['optim_conf']['def_start_timestep'].append(0)
-        if params['optim_conf']['num_def_loads'] is not len(params['optim_conf']['def_end_timestep']):
-            logger.warning("def_end_timestep / list_end_timesteps_of_each_deferrable_load does not match number in num_def_loads, adding default values to parameter")
-            for x in range(len(params['optim_conf']['def_end_timestep']), params['optim_conf']['num_def_loads']):
-                params['optim_conf']['def_end_timestep'].append(0)
-        if params['optim_conf']['num_def_loads'] is not len(params['optim_conf']['set_def_constant']):
-            logger.warning("set_def_constant / list_set_deferrable_load_single_constant does not match number in num_def_loads, adding default values to parameter")
-            for x in range(len(params['optim_conf']['set_def_constant']), params['optim_conf']['num_def_loads']):
-                params['optim_conf']['set_def_constant'].append(False)
-        if params['optim_conf']['num_def_loads'] is not len(params['optim_conf']['treat_def_as_semi_cont']):
-            logger.warning("treat_def_as_semi_cont / list_treat_deferrable_load_as_semi_cont does not match number in num_def_loads, adding default values to parameter")
-            for x in range(len(params['optim_conf']['treat_def_as_semi_cont']), params['optim_conf']['num_def_loads']):
-                params['optim_conf']['treat_def_as_semi_cont'].append(True)   
-        if params['optim_conf']['num_def_loads'] is not len(params['optim_conf']['def_start_penalty']):
-            logger.warning("def_start_penalty / list_set_deferrable_startup_penalty does not match number in num_def_loads, adding default values to parameter")
-            for x in range(len(params['optim_conf']['def_start_penalty']), params['optim_conf']['num_def_loads']):
-                params['optim_conf']['def_start_penalty'].append(0.0)            
-        # days_to_retrieve should be no less then 2     
-        if params['optim_conf']['num_def_loads'] is not len(params['optim_conf']['def_total_hours']):
-            logger.warning("def_total_hours / list_operating_hours_of_each_deferrable_load does not match number in num_def_loads, adding default values to parameter")
-            for x in range(len(params['optim_conf']['def_total_hours']), params['optim_conf']['num_def_loads']):
-                params['optim_conf']['def_total_hours'].append(0)                   
-        if params['optim_conf']['num_def_loads'] is not len(params['optim_conf']['P_deferrable_nom']):
-            logger.warning("P_deferrable_nom / list_nominal_power_of_deferrable_loads does not match number in num_def_loads, adding default values to parameter")
-            for x in range(len(params['optim_conf']['P_deferrable_nom']), params['optim_conf']['num_def_loads']):
-                params['optim_conf']['P_deferrable_nom'].append(0)   
-        # days_to_retrieve should be no less then 2
-        if params["retrieve_hass_conf"]["days_to_retrieve"] < 2:
-            params["retrieve_hass_conf"]["days_to_retrieve"] = 2
+            list_hp_periods_list = {'period_hp_'+str(i+1):[{'start':start_hours_list[i]},{'end':end_hours_list[i]}] for i in range(num_peak_hours)}
+            params['optim_conf']['load_peak_hour_periods'] = list_hp_periods_list
+    else:
+        # Else, check param already contains load_peak_hour_periods from config
+        if params['optim_conf'].get('load_peak_hour_periods',None) is None:
+            logger.warning("Unable to detect or create load_peak_hour_periods parameter")
+
+    # Format load_peak_hour_periods list to dict if necessary
+    if params['optim_conf'].get('load_peak_hour_periods',None) is not None and isinstance(params['optim_conf']['load_peak_hour_periods'], list):
+        params['optim_conf']['load_peak_hour_periods'] = dict((key, d[key]) for d in params['optim_conf']['load_peak_hour_periods'] for key in d)
+
+    # Call function to check parameter lists that require the same length as deferrable loads
+    # If not, set defaults it fill in gaps
+    if params['optim_conf'].get('number_of_deferrable_loads',None) is not None:
+        num_def_loads = params['optim_conf']['number_of_deferrable_loads']
+        params['optim_conf']['start_timesteps_of_each_deferrable_load'] = check_def_loads(num_def_loads,params['optim_conf'],0,'start_timesteps_of_each_deferrable_load',logger)
+        params['optim_conf']['end_timesteps_of_each_deferrable_load'] = check_def_loads(num_def_loads,params['optim_conf'],0,'end_timesteps_of_each_deferrable_load',logger)
+        params['optim_conf']['set_deferrable_load_single_constant'] = check_def_loads(num_def_loads,params['optim_conf'],False,'set_deferrable_load_single_constant',logger)
+        params['optim_conf']['treat_deferrable_load_as_semi_cont'] = check_def_loads(num_def_loads,params['optim_conf'],True,'treat_deferrable_load_as_semi_cont',logger)
+        params['optim_conf']['set_deferrable_startup_penalty'] = check_def_loads(num_def_loads,params['optim_conf'],0.0,'set_deferrable_startup_penalty',logger)
+        params['optim_conf']['operating_hours_of_each_deferrable_load'] = check_def_loads(num_def_loads,params['optim_conf'],0,'operating_hours_of_each_deferrable_load',logger)
+        params['optim_conf']['nominal_power_of_deferrable_loads'] = check_def_loads(num_def_loads,params['optim_conf'],0,'nominal_power_of_deferrable_loads',logger)
+    else:
+        logger.warning("unable to obtain parameter: number_of_deferrable_loads")
+    # historic_days_to_retrieve should be no less then 2
+    if params["retrieve_hass_conf"].get('historic_days_to_retrieve',None) is not None:
+        if params["retrieve_hass_conf"]['historic_days_to_retrieve'] < 2:
+            params["retrieve_hass_conf"]['historic_days_to_retrieve'] = 2
             logger.warning("days_to_retrieve should not be lower then 2, setting days_to_retrieve to 2. Make sure your sensors also have at least 2 days of history")
     else:
-        params["params_secrets"] = params_secrets
-    # The params dict
+        logger.warning("unable to obtain parameter: historic_days_to_retrieve")
+
+    # Configure secrets, set params to correct config categorie
+    # retrieve_hass_conf
+    params['retrieve_hass_conf']["hass_url"] = params_secrets.get("hass_url",None)
+    params['retrieve_hass_conf']["long_lived_token"] = params_secrets.get("long_lived_token",None)
+    params['retrieve_hass_conf']["time_zone"] = params_secrets.get("time_zone",None)
+    params['retrieve_hass_conf']['Latitude'] = params_secrets.get('Latitude',None)
+    params['retrieve_hass_conf']['Longitude'] = params_secrets.get('Longitude',None)
+    params['retrieve_hass_conf']['Altitude'] = params_secrets.get('Altitude',None)
+    # Update optional param secrets
+    if params["optim_conf"].get('weather_forecast_method',None) is not None:
+        if params["optim_conf"]['weather_forecast_method'] == "solcast":
+            params["retrieve_hass_conf"]["solcast_api_key"] = params_secrets.get("solcast_api_key", "123456")
+            params["params_secrets"]["solcast_api_key"] = params_secrets.get("solcast_api_key", "123456")
+            params["retrieve_hass_conf"]["solcast_rooftop_id"] = params_secrets.get("solcast_rooftop_id", "123456")
+            params["params_secrets"]["solcast_rooftop_id"] = params_secrets.get("solcast_rooftop_id", "123456")
+        elif params["optim_conf"]['weather_forecast_method'] == "solar.forecast":
+            params["retrieve_hass_conf"]["solar_forecast_kwp"] = params_secrets.get("solar_forecast_kwp", 5)
+            params["params_secrets"]["solar_forecast_kwp"] = params_secrets.get("solar_forecast_kwp", 5)
+    else:
+        logger.warning("Unable to detect weather_forecast_method parameter")   
+    #  Check if secrets parameters still defaults values
+    secret_params = ["https://myhass.duckdns.org/","thatverylongtokenhere",45.83,6.86,4807.8]
+    if any(x in secret_params for x in params['retrieve_hass_conf'].values()):
+        logger.warning("Some secret parameters values are still matching their defaults")
+    
+
+    # Set empty dict objects for params passed_data
+    # To be latter populated with runtime parameters (treat_runtimeparams)
     params["passed_data"] = {
         "pv_power_forecast": None,
         "load_power_forecast": None,
@@ -920,14 +1212,39 @@ def build_params(params: dict, params_secrets: dict, options: dict, addon: int,
         "prediction_horizon": None,
         "soc_init": None,
         "soc_final": None,
-        "def_total_hours": None,
-        "def_start_timestep": None,
-        "def_end_timestep": None,
+        'operating_hours_of_each_deferrable_load': None,
+        'start_timesteps_of_each_deferrable_load': None,
+        'end_timesteps_of_each_deferrable_load': None,
         "alpha": None,
         "beta": None,
     }
+
     return params
 
+def check_def_loads(num_def_loads: int, parameter: list[dict], default, parameter_name: str, logger):
+    """
+    Check parameter lists with deferrable loads number, if they do not match, enlarge to fit.
+
+    :param num_def_loads: Total number deferrable loads
+    :type num_def_loads: int
+    :param parameter: parameter config dict containing paramater
+    :type: list[dict]
+    :param default: default value for parameter to pad missing
+    :type: obj
+    :param parameter_name: name of parameter
+    :type logger: str
+    :param logger: The logger object
+    :type logger: logging.Logger
+    return: parameter list 
+    :rtype: list[dict]
+
+    """
+    if parameter.get(parameter_name,None) is not None and type(parameter[parameter_name]) is list and num_def_loads > len(parameter[parameter_name]):
+        logger.warning(parameter_name + " does not match number in num_def_loads, adding default values ("+ str(default) + ") to parameter")
+        for x in range(len(parameter[parameter_name]), num_def_loads):
+            parameter[parameter_name].append(default)
+    return parameter[parameter_name]
+
 
 def get_days_list(days_to_retrieve: int) -> pd.date_range:
     """
diff --git a/src/emhass/web_server.py b/src/emhass/web_server.py
index 82439795..37f3b95b 100644
--- a/src/emhass/web_server.py
+++ b/src/emhass/web_server.py
@@ -16,60 +16,94 @@
 from emhass.command_line import regressor_model_fit, regressor_model_predict
 from emhass.command_line import publish_data, continual_publish
 from emhass.utils import get_injection_dict, get_injection_dict_forecast_model_fit, \
-    get_injection_dict_forecast_model_tune, build_params
+    get_injection_dict_forecast_model_tune,  build_config, build_secrets, build_params, \
+    param_to_config, build_legacy_config_params
 
 # Define the Flask instance
 app = Flask(__name__)
+emhass_conf = {}
 
-# Check logfile for error, anything after string match if provided 
-def checkFileLog(refString=None):
+def checkFileLog(refString=None) -> bool:
+    """
+    Check logfile for error, anything after string match if provided.
+
+    :param refString: String to reduce log area to check for errors. Use to reduce log to check anything after string match (ie. an action). 
+    :type refString: str
+    :return: Boolean return if error was found in logs
+    :rtype: bool
+
+    """
     if (refString is not None): 
-       logArray = grabLog(refString) #grab reduced log array
+       logArray = grabLog(refString) #grab reduced log array (everything after string match)
     else: 
         if ((emhass_conf['data_path'] / 'actionLogs.txt')).exists():
             with open(str(emhass_conf['data_path'] / 'actionLogs.txt'), "r") as fp:
                     logArray = fp.readlines()
+        else:
+            app.logger.debug("Unable to obtain actionLogs.txt")
     for logString in logArray:
             if (logString.split(' ', 1)[0] == "ERROR"):
                 return True     
     return False
 
-# Find string in logs, append all lines after to return
-def grabLog(refString): 
+def grabLog(refString) -> list:
+    """
+    Find string in logs, append all lines after into list to return.
+
+    :param refString: String used to string match log.
+    :type refString: str
+    :return: List of lines in log after string match.
+    :rtype: list
+
+    """
     isFound = []
     output = []
     if ((emhass_conf['data_path'] / 'actionLogs.txt')).exists():
             with open(str(emhass_conf['data_path'] / 'actionLogs.txt'), "r") as fp:
                     logArray = fp.readlines()
-            for x in range(len(logArray)-1): #find all matches and log key in isFound
+            # Find all string matches, log key (line Number) in isFound
+            for x in range(len(logArray)-1):
                 if (re.search(refString,logArray[x])):
                    isFound.append(x)
             if len(isFound) != 0:
-                for x in range(isFound[-1],len(logArray)): #use isFound to extract last related action logs  
+                # Use last item in isFound to extract action logs  
+                for x in range(isFound[-1],len(logArray)): 
                     output.append(logArray[x])
     return output
 
 # Clear the log file
-def clearFileLog(): 
+def clearFileLog():
+    """
+    Clear the contents of the log file (actionLogs.txt)
+
+    """
     if ((emhass_conf['data_path'] / 'actionLogs.txt')).exists():
         with open(str(emhass_conf['data_path'] / 'actionLogs.txt'), "w") as fp:
             fp.truncate()    
 
-
-# Initial index page render
 @app.route('/')
+@app.route('/index')
 def index():
+    """
+    Render initial index page and serve to web server.
+    Appends plot tables saved from previous optimization into index.html, then serves.
+
+    """
     app.logger.info("EMHASS server online, serving index.html...")
     # Load HTML template
     file_loader = PackageLoader('emhass', 'templates')
     env = Environment(loader=file_loader)
+    #check if index.html exists
+    if 'index.html' not in env.list_templates():
+        app.logger.error("Unable to find index.html in emhass module")
+        return make_response(["ERROR: unable to find index.html in emhass module"],404)
     template = env.get_template('index.html')
-    # Load cache dict
+    # Load cached dict (if exists), to present generated plot tables
     if (emhass_conf['data_path'] / 'injection_dict.pkl').exists():
         with open(str(emhass_conf['data_path'] / 'injection_dict.pkl'), "rb") as fid:
             injection_dict = pickle.load(fid)
     else:
-        app.logger.warning("The data container dictionary is empty... Please launch an optimization task")
+        app.logger.info("The data container dictionary is empty... Please launch an optimization task")
         injection_dict={}
 
     # replace {{basename}} in html template html with path root  
@@ -79,35 +113,205 @@ def index():
     return make_response(template.render(injection_dict=injection_dict))
 
 
-#get actions 
-@app.route('/template/<action_name>', methods=['GET'])
-def template_action(action_name):
+@app.route('/configuration')
+def configuration():
+    """
+    Configuration page actions:
+    Render and serve configuration page html
+
+    """
+    app.logger.info("serving configuration.html...")
+    # Load HTML template
+    file_loader = PackageLoader('emhass', 'templates')
+    env = Environment(loader=file_loader)
+    #check if configuration.html exists
+    if 'configuration.html' not in env.list_templates():
+        app.logger.error("Unable to find configuration.html in emhass module")
+        return make_response(["ERROR: unable to find configuration.html in emhass module"],404)
+    template = env.get_template('configuration.html')
+    return make_response(template.render(config=params))
+
+
+@app.route('/template', methods=['GET'])
+def template_action():
+    """
+    template page actions: 
+    Render and serve template html
+
+    """
     app.logger.info(" >> Sending rendered template table data")
-    if action_name == 'table-template':
-        file_loader = PackageLoader('emhass', 'templates')
-        env = Environment(loader=file_loader)
-        template = env.get_template('template.html')
-        if (emhass_conf['data_path'] / 'injection_dict.pkl').exists():
-            with open(str(emhass_conf['data_path'] / 'injection_dict.pkl'), "rb") as fid:
-                injection_dict = pickle.load(fid)
-        else:
-            app.logger.warning("The data container dictionary is empty... Please launch an optimization task")
-            injection_dict={}        
-        return make_response(template.render(injection_dict=injection_dict))
+    file_loader = PackageLoader('emhass', 'templates')
+    env = Environment(loader=file_loader)
+    # Check if template.html exists
+    if 'template.html' not in env.list_templates():
+        app.logger.error("Unable to find template.html in emhass module")
+        return make_response(["WARNING: unable to find template.html in emhass module"],404)
+    template = env.get_template('template.html')
+    if (emhass_conf['data_path'] / 'injection_dict.pkl').exists():
+        with open(str(emhass_conf['data_path'] / 'injection_dict.pkl'), "rb") as fid:
+            injection_dict = pickle.load(fid)
+    else:
+        app.logger.warning("Unable to obtain plot data from injection_dict.pkl")
+        app.logger.warning("Try running an launch an optimization task")
+        injection_dict={}        
+    return make_response(template.render(injection_dict=injection_dict))
+
+@app.route('/get-config', methods=['GET'])
+def parameter_get():
+    """
+    Get request action that builds, formats and sends config as json (config.json format)
+
+    """
+    app.logger.debug("Obtaining current saved parameters as config")
+    # Build config from all possible sources (inc. legacy yaml config)
+    config = build_config(emhass_conf,app.logger,emhass_conf["defaults_path"],emhass_conf["config_path"],emhass_conf["legacy_config_path"])
+    if type(config) is bool and not config:
+        return make_response(["failed to retrieve default config file"],500)
+    # Format parameters in config with params (converting legacy json parameters from options.json if any)
+    params = build_params(emhass_conf,{},config,app.logger)
+    if type(params) is bool and not params:
+        return make_response(["Unable to obtain associations file"],500)
+    # Covert formatted parameters from params back into config.json format
+    return_config = param_to_config(params,app.logger)
+    # Send config
+    return make_response(return_config,201)
+
+
+# Get default Config
+@app.route('/get-config/defaults', methods=['GET'])
+def config_get():
+    """
+    Get request action, retrieves and sends default configuration
+
+    """
+    app.logger.debug("Obtaining default parameters")
+    # Build config, passing only default file
+    config = build_config(emhass_conf,app.logger,emhass_conf["defaults_path"])
+    if type(config) is bool and not config:
+        return make_response(["failed to retrieve default config file"],500)
+    # Format parameters in config with params
+    params = build_params(emhass_conf,{},config,app.logger)
+    if type(params) is bool and not params:
+        return make_response(["Unable to obtain associations file"],500)
+    # Covert formatted parameters from params back into config.json format
+    return_config = param_to_config(params,app.logger)
+    # Send params
+    return make_response(return_config,201)
+
+
+# Get YAML-to-JSON config
+@app.route('/get-json', methods=['POST'])
+def json_convert():
+    """
+    Post request action, receives yaml config (config_emhass.yaml or EMHASS-Add-on config page) and converts to config json format.
+
+    """
+    app.logger.info("Attempting to convert YAML to JSON")
+    data = request.get_data()
+    yaml_config = yaml.safe_load(data)
+
+    # If filed to Parse YAML
+    if yaml_config is None:
+        return make_response(["failed to Parse YAML from data"],400)
+    # Test YAML is legacy config format (from config_emhass.yaml)
+    test_legacy_config = build_legacy_config_params(emhass_conf,yaml_config, app.logger)    
+    if test_legacy_config:
+        yaml_config = test_legacy_config
+    # Format YAML to params (format params. check if params match legacy option.json format)
+    params = build_params(emhass_conf,{},yaml_config,app.logger)
+    if type(params) is bool and not params:
+        return make_response(["Unable to obtain associations file"],500)
+    # Covert formatted parameters from params back into config.json format
+    config = param_to_config(params,app.logger)
+    # convert json to str
+    config = json.dumps(config)
+
+    # Send params
+    return make_response(config,201)
+
+@app.route('/set-config', methods=['POST'])
+def parameter_set():
+    """
+    Receive JSON config, and save config to file (config.json and param.pkl)
+
+    """
+    config = {}
+    if not emhass_conf['defaults_path']:
+        return make_response(["Unable to Obtain defaults_path from emhass_conf"],500)
+    if not emhass_conf['config_path']:
+        return make_response(["Unable to Obtain config_path from emhass_conf"],500)
+    
+    # Load defaults as a reference point (for sorting) and a base to override
+    if os.path.exists(emhass_conf['defaults_path']) and Path(emhass_conf['defaults_path']).is_file():
+        with emhass_conf['defaults_path'].open('r') as data:
+            config = json.load(data)
+    else:
+        app.logger.warning("Unable to obtain default config. only parameters passed from request will be saved to config.json")
+
+    # Retrieve sent config json
+    request_data = request.get_json(force=True)
+
+    # check if data is empty
+    if len(request_data) == 0:
+        return make_response(["failed to retrieve config json"],400)
+    
+    # Format config by converting to params (format params. check if params match legacy option.json format. If so format)
+    params = build_params(emhass_conf,params_secrets,request_data,app.logger)
+    if type(params) is bool and not params:
+        return make_response(["Unable to obtain associations file"],500)
+    
+    # Covert formatted parameters from params back into config.json format.
+    # Overwrite existing default parameters in config
+    config.update(param_to_config(params,app.logger))
+
+    # Save config to config.json
+    if os.path.exists(emhass_conf['config_path'].parent):
+        with emhass_conf['config_path'].open('w') as f:
+            json.dump(config, f, indent=4)
+    else: 
+        return make_response(["Unable to save config file"],500)
+    request_data
+    app.logger.info(params)
+
+    # Save params with updated config 
+    if os.path.exists(emhass_conf['data_path']):
+        with open(str(emhass_conf['data_path'] / 'params.pkl'), "wb") as fid:
+            pickle.dump((config_path, build_params(emhass_conf,params_secrets,config,app.logger)), fid)
+    else: 
+        return make_response(["Unable to save params file, missing data_path"],500)
+    
+    app.logger.info("Saved parameters from webserver")
+    return make_response({},201)
 
-#post actions 
 @app.route('/action/<action_name>', methods=['POST'])
 def action_call(action_name):
+    """
+    Receive Post action, run action according to passed slug(action_name) (e.g. /action/publish-data)
+
+    :param action_name: Slug/Action string corresponding to which action to take
+    :type action_name: String
+    
+    """
     # Setting up parameters
-    with open(str(emhass_conf['data_path'] / 'params.pkl'), "rb") as fid:
-        emhass_conf['config_path'], params = pickle.load(fid)
-    runtimeparams = request.get_json(force=True)
-    params = json.dumps(params)
-    if runtimeparams is not None and runtimeparams != '{}':
-        app.logger.info("Passed runtime parameters: " + str(runtimeparams))
+    # Params
+    if (emhass_conf['data_path'] / 'params.pkl').exists():
+        with open(str(emhass_conf['data_path'] / 'params.pkl'), "rb") as fid:
+            emhass_conf['config_path'], params = pickle.load(fid)
+            params = json.dumps(params)
+    else:
+        app.logger.error("Unable to find params.pkl file")
+        return make_response(grabLog(ActionStr), 400)
+    # Runtime
+    runtimeparams = request.get_json(force=True,silent=True)
+    if runtimeparams is not None:
+        if runtimeparams != '{}':
+            app.logger.info("Passed runtime parameters: " + str(runtimeparams))
+    else:
+        app.logger.warning("Unable to parse runtime parameters")
+        runtimeparams = {} 
     runtimeparams = json.dumps(runtimeparams)
-    
-    # Run action if weather_forecast_cache
+
+    # weather-forecast-cache (check before set_input_data_dict)
     if action_name == 'weather-forecast-cache':
         ActionStr = " >> Performing weather forecast, try to caching result"
         app.logger.info(ActionStr)
@@ -125,13 +329,15 @@ def action_call(action_name):
         return make_response(grabLog(ActionStr), 400)
     
     # If continual_publish is True, start thread with loop function
-    if len(continual_publish_thread) == 0 and input_data_dict['retrieve_hass_conf'].get("continual_publish",False):
+    if len(continual_publish_thread) == 0 and input_data_dict['retrieve_hass_conf'].get('continual_publish',False):
         # Start Thread
-        continualLoop = threading.Thread(name="continual_publish",target=continual_publish,args=[input_data_dict,entity_path,app.logger])
+        continualLoop = threading.Thread(name='continual_publish',target=continual_publish,args=[input_data_dict,entity_path,app.logger])
         continualLoop.start()
         continual_publish_thread.append(continualLoop)      
-        
-    # run action based on POST request
+
+    # Run action based on POST request
+    # If error in log when running action, return actions log (list) as response. (Using ActionStr as a reference of the action start in the log)
+    # publish-data
     if action_name == 'publish-data':
         ActionStr = " >> Publishing data..."
         app.logger.info(ActionStr)
@@ -140,6 +346,7 @@ def action_call(action_name):
         if not checkFileLog(ActionStr):
             return make_response(msg, 201)
         return make_response(grabLog(ActionStr), 400)
+    # perfect-optim
     elif action_name == 'perfect-optim':
         ActionStr = " >> Performing perfect optimization..."
         app.logger.info(ActionStr)
@@ -151,6 +358,7 @@ def action_call(action_name):
         if not checkFileLog(ActionStr):
             return make_response(msg, 201)
         return make_response(grabLog(ActionStr), 400)
+    # dayahead-optim
     elif action_name == 'dayahead-optim':
         ActionStr = " >> Performing dayahead optimization..."
         app.logger.info(ActionStr)
@@ -162,6 +370,7 @@ def action_call(action_name):
         if not checkFileLog(ActionStr):
             return make_response(msg, 201)
         return make_response(grabLog(ActionStr), 400)
+    # naive-mpc-optim
     elif action_name == 'naive-mpc-optim':
         ActionStr = " >> Performing naive MPC optimization..."
         app.logger.info(ActionStr)
@@ -173,6 +382,7 @@ def action_call(action_name):
         if not checkFileLog(ActionStr):
             return make_response(msg, 201)
         return make_response(grabLog(ActionStr), 400)
+    # forecast-model-fit
     elif action_name == 'forecast-model-fit':
         ActionStr = " >> Performing a machine learning forecast model fit..."
         app.logger.info(ActionStr)
@@ -185,6 +395,7 @@ def action_call(action_name):
         if not checkFileLog(ActionStr):
             return make_response(msg, 201)
         return make_response(grabLog(ActionStr), 400)
+    # forecast-model-predict
     elif action_name == 'forecast-model-predict':
         ActionStr = " >> Performing a machine learning forecast model predict..."
         app.logger.info(ActionStr)
@@ -202,6 +413,7 @@ def action_call(action_name):
         if not checkFileLog(ActionStr):
             return make_response(msg, 201)
         return make_response(grabLog(ActionStr), 400)
+    # forecast-model-tune
     elif action_name == 'forecast-model-tune':
         ActionStr = " >> Performing a machine learning forecast model tune..."
         app.logger.info(ActionStr)
@@ -216,6 +428,7 @@ def action_call(action_name):
         if not checkFileLog(ActionStr):
             return make_response(msg, 201)
         return make_response(grabLog(ActionStr), 400)
+    # regressor-model-fit
     elif action_name == 'regressor-model-fit':
         ActionStr = " >> Performing a machine learning regressor fit..."
         app.logger.info(ActionStr)
@@ -224,6 +437,7 @@ def action_call(action_name):
         if not checkFileLog(ActionStr):
             return make_response(msg, 201)
         return make_response(grabLog(ActionStr), 400)
+   # regressor-model-predict
     elif action_name == 'regressor-model-predict':
         ActionStr = " >> Performing a machine learning regressor predict..."
         app.logger.info(ActionStr)
@@ -232,6 +446,7 @@ def action_call(action_name):
         if not checkFileLog(ActionStr):
             return make_response(msg, 201)
         return make_response(grabLog(ActionStr), 400)
+    # Else return error
     else:
         app.logger.error("ERROR: passed action is not valid")
         msg = f'EMHASS >> ERROR: Passed action is not valid... \n'
@@ -242,82 +457,74 @@ def action_call(action_name):
     parser = argparse.ArgumentParser()
     parser.add_argument('--url', type=str, help='The URL to your Home Assistant instance, ex the external_url in your hass configuration')
     parser.add_argument('--key', type=str, help='Your access key. If using EMHASS in standalone this should be a Long-Lived Access Token')
-    parser.add_argument('--addon', type=strtobool, default='False', help='Define if we are usinng EMHASS with the add-on or in standalone mode')
     parser.add_argument('--no_response', type=strtobool, default='False', help='This is set if json response errors occur')
     args = parser.parse_args()
+
+    # Pre formatted config parameters
+    config = {} 
+    # Secrets
+    params_secrets = {}
+    # Built parameters (formatted config + secrets)
+    params = None 
     
-    #Obtain url and key from ENV or ARG (if any)
-    hass_url = os.getenv("EMHASS_URL", default=args.url)
-    key =  os.getenv("SUPERVISOR_TOKEN", default=args.key) 
-    if hass_url != "http://supervisor/core/api":
-        key =  os.getenv("EMHASS_KEY", key)  
-    #If url or key is None, Set as empty string to reduce NoneType errors bellow
-    if key is None: key = ""
-    if hass_url is None: hass_url = ""
-    
-    #find env's, not not set defaults 
-    use_options = os.getenv('USE_OPTIONS', default=False)
-    CONFIG_PATH = os.getenv("CONFIG_PATH", default="/app/config_emhass.yaml")
-    OPTIONS_PATH = os.getenv('OPTIONS_PATH', default="/app/options.json")
+    # Find env's, not not set defaults 
     DATA_PATH = os.getenv("DATA_PATH", default="/app/data/")
     ROOT_PATH = os.getenv("ROOT_PATH", default=str(Path(__file__).parent))
-    
-    #options None by default
-    options = None 
+    CONFIG_PATH = os.getenv('CONFIG_PATH', default="/share/config.json")
+    OPTIONS_PATH = os.getenv('OPTIONS_PATH', default="/data/options.json") 
+    DEFAULTS_PATH = os.getenv('DEFAULTS_PATH', default=ROOT_PATH +"/data/config_defaults.json")
+    ASSOCIATIONS_PATH = os.getenv('ASSOCIATIONS_PATH', default=ROOT_PATH + "/data/associations.csv")
+    LEGACY_CONFIG_PATH = os.getenv("LEGACY_CONFIG_PATH", default="/app/config_emhass.yaml")
 
     # Define the paths
-    if args.addon==1:
-        options_json = Path(OPTIONS_PATH)
-        # Read options info
-        if options_json.exists():
-            with options_json.open('r') as data:
-                options = json.load(data)
-        else:
-            app.logger.error("options.json does not exist")
-            raise Exception("options.json does not exist in path: "+str(options_json)) 
-    else:
-        if use_options:
-            options_json = Path(OPTIONS_PATH)
-            # Read options info
-            if options_json.exists():
-                with options_json.open('r') as data:
-                    options = json.load(data)
-            else:
-                app.logger.error("options.json does not exist")
-                raise Exception("options.json does not exist in path: "+str(options_json)) 
-        else:
-            options = None       
-
-    #if data path specified by options.json
-    if options is not None:
-        if options.get('data_path', None) != None and options.get('data_path', None) != "default":
-            DATA_PATH = options.get('data_path', None);   
-
-    #save paths to dictionary
     config_path = Path(CONFIG_PATH)
+    options_path = Path(OPTIONS_PATH)
+    defaults_path = Path(DEFAULTS_PATH)
+    associations_path = Path(ASSOCIATIONS_PATH)
+    legacy_config_path = Path(LEGACY_CONFIG_PATH)
     data_path = Path(DATA_PATH)
     root_path = Path(ROOT_PATH)
-    emhass_conf = {}
+    # Add paths to emhass_conf
     emhass_conf['config_path'] = config_path
+    emhass_conf['options_path'] = options_path
+    emhass_conf['defaults_path'] = defaults_path
+    emhass_conf['associations_path'] = associations_path
+    emhass_conf['legacy_config_path'] = legacy_config_path 
     emhass_conf['data_path'] = data_path
     emhass_conf['root_path'] = root_path 
-    
-    # Read the example default config file
-    if config_path.exists():
-        with open(config_path, 'r') as file:
-            config = yaml.load(file, Loader=yaml.FullLoader)
-        retrieve_hass_conf = config['retrieve_hass_conf']
-        optim_conf = config['optim_conf']
-        plant_conf = config['plant_conf']
-    else:
-        app.logger.error("Unable to open the default configuration yaml file")
-        raise Exception("Failed to open config file, config_path: "+str(config_path)) 
 
-    params = {}
-    params['retrieve_hass_conf'] = retrieve_hass_conf
-    params['optim_conf'] = optim_conf
-    params['plant_conf'] = plant_conf
-    web_ui_url = '0.0.0.0'
+    # Combine parameters from configuration sources (if exists)
+    config.update(build_config(emhass_conf,app.logger,defaults_path,config_path,legacy_config_path))
+    if type(config) is bool and not config:
+        raise Exception("Failed to find default config")
+
+    costfun = os.getenv('LOCAL_COSTFUN', config.get('costfun', 'profit'))
+    logging_level = os.getenv('LOGGING_LEVEL', config.get('logging_level','INFO'))
+    # Temporary set logging level if debug
+    if logging_level == "DEBUG":
+        app.logger.setLevel(logging.DEBUG)
+        
+    ## Secrets
+    argument = {}
+    if args.url:
+        argument['url'] = args.url
+    if args.key:
+        argument['key'] = args.key
+    # Combine secrets from ENV, Arguments/ARG, Secrets file (secrets_emhass.yaml), options (options.json from addon configuration file) and/or Home Assistant Standalone API (if exist)
+    emhass_conf, secrets = build_secrets(emhass_conf,app.logger,argument,options_path,os.getenv('SECRETS_PATH', default='/app/secrets_emhass.yaml'), bool(args.no_response))
+    params_secrets.update(secrets)
+
+    server_ip = params_secrets.get("server_ip","0.0.0.0")
+
+    # Check if data path exists 
+    if not os.path.isdir(emhass_conf['data_path']):
+        app.logger.warning("Unable to find data_path: " + str(emhass_conf['data_path']))
+        if os.path.isdir(Path("/app/data/")):
+            emhass_conf['data_path'] = Path("/app/data/")
+        else:
+            Path(root_path / "data/").mkdir(parents=True, exist_ok=True) 
+            emhass_conf['data_path'] = root_path / "data/"
+        app.logger.info("data_path has been set to " + str(emhass_conf['data_path']))
 
     # Initialize this global dict
     if (emhass_conf['data_path'] / 'injection_dict.pkl').exists():
@@ -325,123 +532,23 @@ def action_call(action_name):
             injection_dict = pickle.load(fid)
     else:
         injection_dict = None
+
+    # Build params from config and param_secrets (migrate params to correct config catagories), save result to params.pkl
+    params = build_params(emhass_conf, params_secrets, config, app.logger)
+    if type(params) is bool:
+        raise Exception("A error has occurred while building params")   
     
-    if args.addon==1:
-        # The cost function
-        costfun = options.get('costfun', 'profit')
-        # Some data from options
-        logging_level = options.get('logging_level','INFO')
-        url_from_options = options.get('hass_url', 'empty')
-        if url_from_options == 'empty' or url_from_options == '' or url_from_options == "http://supervisor/core/api":
-            url = "http://supervisor/core/api/config"
-        else:
-            hass_url = url_from_options
-            url = hass_url+"api/config"
-        token_from_options = options.get('long_lived_token', 'empty')
-        if token_from_options == 'empty' or token_from_options == '':
-            long_lived_token = key
-        else:
-            long_lived_token = token_from_options
-        headers = {
-            "Authorization": "Bearer " + long_lived_token,
-            "content-type": "application/json"
-        }
-        if not args.no_response==1:
-            response = get(url, headers=headers)
-            config_hass = response.json()
-            params_secrets = {
-            'hass_url': hass_url,
-            'long_lived_token': long_lived_token,
-            'time_zone': config_hass['time_zone'],
-            'lat': config_hass['latitude'],
-            'lon': config_hass['longitude'],
-            'alt': config_hass['elevation']
-            }
-        else: #if no_response is set to true
-            costfun = os.getenv('LOCAL_COSTFUN', default='profit')
-            logging_level = os.getenv('LOGGING_LEVEL', default='INFO')
-            # check if secrets file exists
-            if Path(os.getenv('SECRETS_PATH', default='/app/secrets_emhass.yaml')).is_file(): 
-                with open(os.getenv('SECRETS_PATH', default='/app/secrets_emhass.yaml'), 'r') as file:
-                    params_secrets = yaml.load(file, Loader=yaml.FullLoader)
-                    app.logger.debug("Obtained secrets from secrets file")
-            #If cant find secrets_emhass file, use env
-            else: 
-                app.logger.debug("Failed to find secrets file: "+str(os.getenv('SECRETS_PATH', default='/app/secrets_emhass.yaml')))
-                app.logger.debug("Setting location defaults")
-                params_secrets = {} 
-                #If no secrets file try args, else set some defaults 
-                params_secrets['time_zone'] = os.getenv("TIME_ZONE", default="Europe/Paris")
-                params_secrets['lat'] = float(os.getenv("LAT", default="45.83"))
-                params_secrets['lon'] = float(os.getenv("LON", default="6.86"))
-                params_secrets['alt'] = float(os.getenv("ALT", default="4807.8"))      
-            #If ARG/ENV specify url and key, then override secrets file
-            if hass_url != "":
-                params_secrets['hass_url'] = hass_url
-                app.logger.debug("Using URL obtained from ARG/ENV")
-            else:
-                hass_url = params_secrets.get('hass_url',"http://localhost:8123/")      
-            if long_lived_token != "":
-                params_secrets['long_lived_token'] = long_lived_token
-                app.logger.debug("Using Key obtained from ARG/ENV")       
-    else: #If addon is false
-        costfun = os.getenv('LOCAL_COSTFUN', default='profit')
-        logging_level = os.getenv('LOGGING_LEVEL', default='INFO')
-        if Path(os.getenv('SECRETS_PATH', default='/app/secrets_emhass.yaml')).is_file(): 
-            with open(os.getenv('SECRETS_PATH', default='/app/secrets_emhass.yaml'), 'r') as file:
-                params_secrets = yaml.load(file, Loader=yaml.FullLoader)
-            #Check if URL and KEY are provided by file. If not attempt using values from ARG/ENV
-            if  params_secrets.get("hass_url", "empty") == "empty" or params_secrets['hass_url'] == "":
-                app.logger.info("No specified Home Assistant URL in secrets_emhass.yaml. Attempting to get from ARG/ENV") 
-                if hass_url != "":
-                     params_secrets['hass_url'] = hass_url    
-                else:
-                    app.logger.error("Can not find Home Assistant URL from secrets_emhass.yaml or ARG/ENV")
-                    raise Exception("Can not find Home Assistant URL from secrets_emhass.yaml or ARG/ENV")  
-            else:
-                hass_url = params_secrets['hass_url']
-            if  params_secrets.get("long_lived_token", "empty") == "empty" or params_secrets['long_lived_token'] == "":
-                app.logger.info("No specified Home Assistant KEY in secrets_emhass.yaml. Attempting to get from ARG/ENV") 
-                if key != "":
-                    params_secrets['long_lived_token'] = key
-                else:
-                    app.logger.error("Can not find Home Assistant KEY from secrets_emhass.yaml or ARG/ENV")
-                    raise Exception("Can not find Home Assistant KEY from secrets_emhass.yaml or ARG/ENV")  
-        else: #If no secrets file try args, else set some defaults 
-            app.logger.info("Failed to find secrets_emhass.yaml in directory:" + os.getenv('SECRETS_PATH', default='/app/secrets_emhass.yaml') ) 
-            app.logger.info("Attempting to use secrets from arguments or environment variables")        
-            params_secrets = {} 
-            params_secrets['time_zone'] = os.getenv("TIME_ZONE", default="Europe/Paris")
-            params_secrets['lat'] = float(os.getenv("LAT", default="45.83"))
-            params_secrets['lon'] = float(os.getenv("LON", default="6.86"))
-            params_secrets['alt'] = float(os.getenv("ALT", default="4807.8"))      
-            if hass_url != "":
-                params_secrets['hass_url'] = hass_url
-            else: #If cant find secrets_emhass and passed url ENV/ARG, then send error
-                app.logger.error("No specified Home Assistant URL") 
-                raise Exception("Can not find Home Assistant URL from secrets_emhass.yaml or ARG/ENV") 
-            if key != "":
-                params_secrets['long_lived_token'] = key
-            else: #If cant find secrets_emhass and passed key ENV/ARG, then send error
-                app.logger.error("No specified Home Assistant KEY")     
-                raise Exception("Can not find Home Assistant KEY from secrets_emhass.yaml or ARG/ENV") 
-    # Build params
-    if use_options:
-        params = build_params(params, params_secrets, options, 1, app.logger)
-    else:
-        params = build_params(params, params_secrets, options, args.addon, app.logger)
     if os.path.exists(str(emhass_conf['data_path'])): 
         with open(str(emhass_conf['data_path'] / 'params.pkl'), "wb") as fid:
             pickle.dump((config_path, params), fid)
     else: 
         raise Exception("missing: " + str(emhass_conf['data_path']))   
 
-    # Define logger
-    #stream logger
+    # Define loggers
     ch = logging.StreamHandler() 
     formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
     ch.setFormatter(formatter)
-    #Action File logger
+    # Action file logger
     fileLogger = logging.FileHandler(str(emhass_conf['data_path'] / 'actionLogs.txt')) 
     formatter = logging.Formatter('%(levelname)s - %(name)s - %(message)s')
     fileLogger.setFormatter(formatter) # add format to Handler
@@ -468,7 +575,8 @@ def action_call(action_name):
     app.logger.propagate = False
     app.logger.addHandler(ch)
     app.logger.addHandler(fileLogger)   
-    clearFileLog() #Clear Action File logger file, ready for new instance
+    # Clear Action File logger file, ready for new instance
+    clearFileLog()
 
     # If entity_path exists, remove any entity/metadata files 
     entity_path = emhass_conf['data_path'] / "entities"
@@ -483,11 +591,13 @@ def action_call(action_name):
     
     # Launch server
     port = int(os.environ.get('PORT', 5000))
-    app.logger.info("Launching the emhass webserver at: http://"+web_ui_url+":"+str(port))
-    app.logger.info("Home Assistant data fetch will be performed using url: "+hass_url)
+    app.logger.info("Launching the emhass webserver at: http://"+server_ip+":"+str(port))
+    app.logger.info("Home Assistant data fetch will be performed using url: "+params_secrets['hass_url'])
     app.logger.info("The data path is: "+str(emhass_conf['data_path']))
+    app.logger.info("The logging is: "+str(logging_level))
     try:
         app.logger.info("Using core emhass version: "+version('emhass'))
     except PackageNotFoundError:
         app.logger.info("Using development emhass version")
-    serve(app, host=web_ui_url, port=port, threads=8)
+    serve(app, host=server_ip, port=port, threads=8)
+


Test patch:
diff --git a/.github/workflows/docker-build-test.yaml b/.github/workflows/docker-build-test.yaml
index 1317344a..785b6d2a 100644
--- a/.github/workflows/docker-build-test.yaml
+++ b/.github/workflows/docker-build-test.yaml
@@ -34,10 +34,9 @@ jobs:
           context: .
           platforms: ${{ matrix.platform.buildx }}
           build-args: |
-            build_version=standalone
             TARGETARCH=${{ matrix.platform.target_arch }}
             os_version=${{ matrix.platform.os_version }}
-          tags: emhass/standalone-test
+          tags: emhass
           load: true
-      - name: Test #assume docker fail with FileNotFound secrets_emhass.yaml error
-        run: docker run --rm -it emhass/standalone-test | grep -q secrets_emhass.yaml && echo 0 || echo 1
+      - name: Test # Run docker for 2min, if the output contains waitress   INFO  "Serving on" http://0.0.0.0:5000 assume it worked
+        run: timeout 2 docker run --rm -it emhass | grep -q "Serving on"  && echo 0 || echo 1
diff --git a/.github/workflows/python-test.yml b/.github/workflows/python-test.yml
index fa17858d..4b0e1428 100644
--- a/.github/workflows/python-test.yml
+++ b/.github/workflows/python-test.yml
@@ -35,9 +35,8 @@ jobs:
         
     - name: Install dependencies
       run: |
-        python -m pip install --upgrade pip
-        python -m pip install pytest requests-mock
-        python setup.py install
+        pip install pytest requests-mock
+        pip install .
         
     - name: Test with pytest
       run: |
diff --git a/config_emhass.yaml b/tests/config_emhass.yaml
similarity index 99%
rename from config_emhass.yaml
rename to tests/config_emhass.yaml
index ca3a47d7..1353dd04 100644
--- a/config_emhass.yaml
+++ b/tests/config_emhass.yaml
@@ -1,5 +1,4 @@
-# Configuration file for EMHASS
-
+# Legacy YAML Configuration file for EMHASS Tests
 retrieve_hass_conf:
   freq: 30 # The time step to resample retrieved data from hass in minutes
   days_to_retrieve: 2 # We will retrieve data from now and up to days_to_retrieve days
diff --git a/tests/test_command_line_utils.py b/tests/test_command_line_utils.py
index b779ed49..d8b10a87 100644
--- a/tests/test_command_line_utils.py
+++ b/tests/test_command_line_utils.py
@@ -4,7 +4,7 @@
 import unittest
 from unittest.mock import patch
 import pandas as pd
-import pathlib, json, yaml, copy
+import pathlib, json, copy
 import numpy as np
 
 from emhass.command_line import set_input_data_dict
@@ -24,41 +24,36 @@
 from emhass.command_line import main
 from emhass import utils
 
-# create paths
-
-root = str(utils.get_root(__file__, num_parent=2))
+# The root folder
+root = pathlib.Path(utils.get_root(__file__, num_parent=2))
+# Build emhass_conf paths
 emhass_conf = {}
-emhass_conf['config_path'] = pathlib.Path(root) / 'config_emhass.yaml'
-emhass_conf['data_path'] = pathlib.Path(root) / 'data/'
-emhass_conf['root_path'] = pathlib.Path(root) / 'src/emhass/'
+emhass_conf['data_path'] = root / 'data/'
+emhass_conf['root_path'] = root / 'src/emhass/'
+emhass_conf['config_path'] = root / 'config.json'
+emhass_conf['defaults_path'] = emhass_conf['root_path']  / 'data/config_defaults.json'
+emhass_conf['associations_path'] = emhass_conf['root_path']  / 'data/associations.csv'
+
 
-# create logger
+# create loggerW
 logger, ch = utils.get_logger(__name__, emhass_conf, save_to_file=False)
 
 class TestCommandLineUtils(unittest.TestCase):
     
     @staticmethod
     def get_test_params():
-        with open(emhass_conf['config_path'], 'r') as file:
-            params = yaml.load(file, Loader=yaml.FullLoader)
-        params.update({
-            'params_secrets': {
-                'hass_url': 'http://supervisor/core/api',
-                'long_lived_token': '${SUPERVISOR_TOKEN}',
-                'time_zone': 'Europe/Paris',
-                'lat': 45.83,
-                'lon': 6.86,
-                'alt': 8000.0
-            }})
-        #Force config params for testing
-        params["retrieve_hass_conf"]['var_PV'] = 'sensor.power_photovoltaics'
-        params["retrieve_hass_conf"]['var_load'] = 'sensor.power_load_no_var_loads'
-        params["retrieve_hass_conf"]['var_replace_zero'] = ['sensor.power_photovoltaics']
-        params["retrieve_hass_conf"]['var_interp'] = ['sensor.power_photovoltaics','sensor.power_load_no_var_loads'] 
+        # Build params with default config and secrets
+        if emhass_conf['defaults_path'].exists():
+            config = utils.build_config(emhass_conf,logger,emhass_conf['defaults_path'])
+            _,secrets = utils.build_secrets(emhass_conf,logger,no_response=True)
+            params =  utils.build_params(emhass_conf,secrets,config,logger)
+        else:
+            raise Exception("config_defaults. does not exist in path: "+str(emhass_conf['defaults_path'] )) 
         return params
 
     def setUp(self):
         params = TestCommandLineUtils.get_test_params()
+        # Add runtime parameters for forecast lists
         runtimeparams = {
             'pv_power_forecast':[i+1 for i in range(48)],
             'load_power_forecast':[i+1 for i in range(48)],
@@ -69,8 +64,10 @@ def setUp(self):
         params['passed_data'] = runtimeparams
         self.params_json = json.dumps(params)
         
+    # Test input data for actions (using data from file)
     def test_set_input_data_dict(self):
         costfun = 'profit'
+        # Test dayahead
         action = 'dayahead-optim'
         input_data_dict = set_input_data_dict(emhass_conf, costfun, self.params_json, self.runtimeparams_json, 
                                               action, logger, get_data_from_file=True)
@@ -82,7 +79,8 @@ def test_set_input_data_dict(self):
         self.assertTrue(input_data_dict['fcst'].optim_conf['weather_forecast_method']=='list')
         self.assertTrue(input_data_dict['fcst'].optim_conf['load_forecast_method']=='list')
         self.assertTrue(input_data_dict['fcst'].optim_conf['load_cost_forecast_method']=='list')
-        self.assertTrue(input_data_dict['fcst'].optim_conf['prod_price_forecast_method']=='list')
+        self.assertTrue(input_data_dict['fcst'].optim_conf['production_price_forecast_method']=='list')
+        # Test publish data
         action = 'publish-data'
         input_data_dict = set_input_data_dict(emhass_conf, costfun, self.params_json, self.runtimeparams_json, 
                                               action, logger, get_data_from_file=True)
@@ -90,6 +88,7 @@ def test_set_input_data_dict(self):
         self.assertTrue(input_data_dict['df_input_data_dayahead'] == None)
         self.assertTrue(input_data_dict['P_PV_forecast'] == None)
         self.assertTrue(input_data_dict['P_load_forecast'] == None)
+        # Test naive mpc
         action = 'naive-mpc-optim'
         input_data_dict = set_input_data_dict(emhass_conf, costfun, self.params_json, self.runtimeparams_json, 
                                               action, logger, get_data_from_file=True)
@@ -98,6 +97,7 @@ def test_set_input_data_dict(self):
         self.assertTrue(input_data_dict['df_input_data_dayahead'].index.freq is not None)
         self.assertTrue(input_data_dict['df_input_data_dayahead'].isnull().sum().sum()==0)
         self.assertTrue(len(input_data_dict['df_input_data_dayahead'])==10) # The default value for prediction_horizon
+        # Test Naive mpc with a shorter forecast =
         runtimeparams = {
             'pv_power_forecast':[1,2,3,4,5,6,7,8,9,10],
             'load_power_forecast':[1,2,3,4,5,6,7,8,9,10],
@@ -115,6 +115,7 @@ def test_set_input_data_dict(self):
         self.assertTrue(input_data_dict['df_input_data_dayahead'].index.freq is not None)
         self.assertTrue(input_data_dict['df_input_data_dayahead'].isnull().sum().sum()==0)
         self.assertTrue(len(input_data_dict['df_input_data_dayahead'])==10) # The default value for prediction_horizon
+        # Test naive mpc with a shorter forecast and prediction horizon = 10
         action = 'naive-mpc-optim'
         runtimeparams['prediction_horizon'] = 10
         runtimeparams_json = json.dumps(runtimeparams)
@@ -141,10 +142,10 @@ def test_set_input_data_dict(self):
         input_data_dict = set_input_data_dict(emhass_conf, costfun, params_json, runtimeparams_json, 
                                               action, logger, get_data_from_file=True)
         self.assertTrue(input_data_dict['fcst'].optim_conf['load_cost_forecast_method']=='list')
-        self.assertTrue(input_data_dict['fcst'].optim_conf['prod_price_forecast_method']=='list')
+        self.assertTrue(input_data_dict['fcst'].optim_conf['production_price_forecast_method']=='list')
     
+    # Test day-ahead optimization
     def test_webserver_get_injection_dict(self):
-        # First perform a day-ahead optimization
         costfun = 'profit'
         action = 'dayahead-optim'
         input_data_dict = set_input_data_dict(emhass_conf, costfun, self.params_json, self.runtimeparams_json, 
@@ -155,7 +156,9 @@ def test_webserver_get_injection_dict(self):
         self.assertIsInstance(injection_dict['table1'], str)
         self.assertIsInstance(injection_dict['table2'], str)
     
+    # Test data formatting of dayahead optimization with load cost and prod price as lists
     def test_dayahead_forecast_optim(self):
+        # Test dataframe output of profit dayahead optimization
         costfun = 'profit'
         action = 'dayahead-optim'
         params = copy.deepcopy(json.loads(self.params_json))
@@ -165,7 +168,7 @@ def test_dayahead_forecast_optim(self):
         self.assertIsInstance(opt_res, pd.DataFrame)
         self.assertTrue(opt_res.isnull().sum().sum()==0)
         self.assertTrue(len(opt_res)==len(params['passed_data']['pv_power_forecast']))
-        # Test passing just load cost and prod price as lists
+        # Test dayahead output, passing just load cost and prod price as runtime lists (costfun=profit)
         action = 'dayahead-optim'
         params = TestCommandLineUtils.get_test_params()
         runtimeparams = {
@@ -181,10 +184,11 @@ def test_dayahead_forecast_optim(self):
         self.assertIsInstance(opt_res, pd.DataFrame)
         self.assertTrue(opt_res.isnull().sum().sum()==0)
         self.assertTrue(input_data_dict['fcst'].optim_conf['load_cost_forecast_method']=='list')
-        self.assertTrue(input_data_dict['fcst'].optim_conf['prod_price_forecast_method']=='list')
+        self.assertTrue(input_data_dict['fcst'].optim_conf['production_price_forecast_method']=='list')
         self.assertEqual(opt_res['unit_load_cost'].values.tolist(), runtimeparams['load_cost_forecast'])
         self.assertEqual(opt_res['unit_prod_price'].values.tolist(), runtimeparams['prod_price_forecast'])
         
+    # Test dataframe outpit of perfect forecast optimization
     def test_perfect_forecast_optim(self):
         costfun = 'profit'
         action = 'perfect-optim'
@@ -197,7 +201,9 @@ def test_perfect_forecast_optim(self):
         self.assertIsInstance(opt_res.index.dtype, pd.core.dtypes.dtypes.DatetimeTZDtype)
         self.assertTrue('cost_fun_'+input_data_dict["costfun"] in opt_res.columns)
         
+    # Test naive mpc optimization
     def test_naive_mpc_optim(self):
+        # Test mpc optimization
         costfun = 'profit'
         action = 'naive-mpc-optim'
         params = copy.deepcopy(json.loads(self.params_json))
@@ -207,16 +213,16 @@ def test_naive_mpc_optim(self):
         self.assertIsInstance(opt_res, pd.DataFrame)
         self.assertTrue(opt_res.isnull().sum().sum()==0)
         self.assertTrue(len(opt_res)==10)
-        # A test similar to the docs
+        # Test mpc optimization with runtime parameters similar to the documentation
         runtimeparams = {"pv_power_forecast":
             [1,2,3,4,5,6,7,8,9,10], 
-            "prediction_horizon":10, "soc_init":0.5,"soc_final":0.6,"def_total_hours":[1,3],"def_start_timestep":[-3,0],"def_end_timestep":[8,0]}
+            "prediction_horizon":10, "soc_init":0.5,"soc_final":0.6,'operating_hours_of_each_deferrable_load':[1,3],'start_timesteps_of_each_deferrable_load':[-3,0],'end_timesteps_of_each_deferrable_load':[8,0]}
         runtimeparams_json = json.dumps(runtimeparams)
         params['passed_data'] = runtimeparams
         params['optim_conf']['weather_forecast_method'] = 'list'
         params['optim_conf']['load_forecast_method'] = 'naive'
         params['optim_conf']['load_cost_forecast_method'] = 'hp_hc_periods'
-        params['optim_conf']['prod_price_forecast_method'] = 'constant'
+        params['optim_conf']['production_price_forecast_method'] = 'constant'
         params_json = json.dumps(params)
         input_data_dict = set_input_data_dict(emhass_conf, costfun, params_json, runtimeparams_json, 
                                               action, logger, get_data_from_file=True)
@@ -225,6 +231,7 @@ def test_naive_mpc_optim(self):
         self.assertTrue(opt_res.isnull().sum().sum()==0)
         self.assertTrue(len(opt_res)==10)
         # Test publish after passing the forecast as list
+        # with method_ts_round=first
         costfun = 'profit'
         action = 'naive-mpc-optim'
         params = copy.deepcopy(json.loads(self.params_json))
@@ -238,6 +245,7 @@ def test_naive_mpc_optim(self):
                                               action, logger, get_data_from_file=True)
         opt_res_first = publish_data(input_data_dict, logger, opt_res_latest=opt_res)
         self.assertTrue(len(opt_res_first)==1)
+        # test mpc and publish with method_ts_round=last and set_use_battery=true
         action = 'naive-mpc-optim'
         params = copy.deepcopy(json.loads(self.params_json))
         params['retrieve_hass_conf']['method_ts_round'] = 'last'
@@ -251,12 +259,12 @@ def test_naive_mpc_optim(self):
                                               action, logger, get_data_from_file=True)
         opt_res_last = publish_data(input_data_dict, logger, opt_res_latest=opt_res)
         self.assertTrue(len(opt_res_last)==1)
-        # Reproduce when trying to publish data but params=None and runtimeparams=None
-        action = 'publish-data'
-        input_data_dict = set_input_data_dict(emhass_conf, costfun, None, None, 
-                                              action, logger, get_data_from_file=True)
-        opt_res_last = publish_data(input_data_dict, logger, opt_res_latest=opt_res)
-        self.assertTrue(len(opt_res_last)==1)
+            # Reproduce when trying to publish data params=None and runtimeparams=None
+            # action = 'publish-data'
+            # input_data_dict = set_input_data_dict(emhass_conf, costfun, None, None, 
+            #                                       action, logger, get_data_from_file=True)
+            # opt_res_last = publish_data(input_data_dict, logger, opt_res_latest=opt_res)
+            # self.assertTrue(len(opt_res_last)==1)
         # Check if status is published
         from datetime import datetime
         now_precise = datetime.now(input_data_dict['retrieve_hass_conf']['time_zone']).replace(second=0, microsecond=0)
@@ -272,12 +280,13 @@ def test_naive_mpc_optim(self):
         self.assertTrue(hasattr(response, '__class__'))
         self.assertTrue(data['attributes']['friendly_name'] == 'EMHASS optimization status')
     
+    # Test outputs of fit, predict and tune
     def test_forecast_model_fit_predict_tune(self):
         costfun = 'profit'
         action = 'forecast-model-fit' # fit, predict and tune methods
         params = TestCommandLineUtils.get_test_params()
         runtimeparams = {
-            "days_to_retrieve": 20,
+            'historic_days_to_retrieve': 20,
             "model_type": "load_forecast",
             "var_model": "sensor.power_load_no_var_loads",
             "sklearn_model": "KNeighborsRegressor",
@@ -290,7 +299,6 @@ def test_forecast_model_fit_predict_tune(self):
             "model_predict_friendly_name": "Load Power Forecast KNN regressor"
         }
         runtimeparams_json = json.dumps(runtimeparams)
-        # params['passed_data'] = runtimeparams
         params['optim_conf']['load_forecast_method'] = 'skforecast'
         params_json = json.dumps(params)
         input_data_dict = set_input_data_dict(emhass_conf, costfun, params_json, runtimeparams_json, 
@@ -333,6 +341,7 @@ def test_forecast_model_fit_predict_tune(self):
         self.assertIsInstance(injection_dict, dict)
         self.assertIsInstance(injection_dict["figure_0"], str)
 
+    # Test data formatting of regressor model fit amd predict
     def test_regressor_model_fit_predict(self):
         costfun = "profit"
         action = "regressor-model-fit"  # fit and predict methods
@@ -417,12 +426,14 @@ def test_regressor_model_fit_predict(self):
         regressor_model_predict(input_data_dict, logger, debug=True, mlr=mlr)
 
     
+    # CLI test action that does not exist
     @patch('sys.argv', ['main', '--action', 'test', '--config', str(emhass_conf['config_path']), 
                         '--debug', 'True'])
     def test_main_wrong_action(self):
         opt_res = main()
         self.assertEqual(opt_res, None)
         
+    # CLI test action perfect-optim action 
     @patch('sys.argv', ['main', '--action', 'perfect-optim', '--config', str(emhass_conf['config_path']), 
                         '--debug', 'True', '--params', json.dumps(get_test_params())])
     def test_main_perfect_forecast_optim(self):
@@ -435,6 +446,7 @@ def test_main_perfect_forecast_optim(self):
             pd.core.dtypes.dtypes.DatetimeTZDtype,
         )
 
+    # CLI test dayahead forecast optimzation action
     def test_main_dayahead_forecast_optim(self):
         with patch('sys.argv', ['main', '--action', 'dayahead-optim', '--config', str(emhass_conf['config_path']), 
                                 '--params', self.params_json, '--runtimeparams', self.runtimeparams_json,
@@ -443,6 +455,7 @@ def test_main_dayahead_forecast_optim(self):
         self.assertIsInstance(opt_res, pd.DataFrame)
         self.assertTrue(opt_res.isnull().sum().sum() == 0)
 
+    # CLI test naive mpc optimzation action
     def test_main_naive_mpc_optim(self):
         with patch('sys.argv', ['main', '--action', 'naive-mpc-optim', '--config', str(emhass_conf['config_path']), 
                                 '--params', self.params_json, '--runtimeparams', self.runtimeparams_json,
@@ -452,10 +465,11 @@ def test_main_naive_mpc_optim(self):
         self.assertTrue(opt_res.isnull().sum().sum() == 0)
         self.assertTrue(len(opt_res) == 10)
 
+    # CLI test forecast model fit action
     def test_main_forecast_model_fit(self):
         params = copy.deepcopy(json.loads(self.params_json))
         runtimeparams = {
-            "days_to_retrieve": 20,
+            'historic_days_to_retrieve': 20,
             "model_type": "load_forecast",
             "var_model": "sensor.power_load_no_var_loads",
             "sklearn_model": "KNeighborsRegressor",
@@ -474,10 +488,11 @@ def test_main_forecast_model_fit(self):
         self.assertIsInstance(df_fit_pred, pd.DataFrame)
         self.assertTrue(df_fit_pred_backtest == None)
         
+    # CLI test forecast model predict action
     def test_main_forecast_model_predict(self):
         params = copy.deepcopy(json.loads(self.params_json))
         runtimeparams = {
-            "days_to_retrieve": 20,
+            'historic_days_to_retrieve': 20,
             "model_type": "load_forecast",
             "var_model": "sensor.power_load_no_var_loads",
             "sklearn_model": "KNeighborsRegressor",
@@ -487,7 +502,7 @@ def test_main_forecast_model_predict(self):
         }
         runtimeparams_json = json.dumps(runtimeparams)
         params["passed_data"] = runtimeparams
-        params["optim_conf"]["load_forecast_method"] = "skforecast"
+        params["optim_conf"]['load_forecast_method'] = "skforecast"
         params_json = json.dumps(params)
         with patch('sys.argv', ['main', '--action', 'forecast-model-predict', '--config', str(emhass_conf['config_path']), 
                                 '--params', params_json, '--runtimeparams', runtimeparams_json,
@@ -496,10 +511,11 @@ def test_main_forecast_model_predict(self):
         self.assertIsInstance(df_pred, pd.Series)
         self.assertTrue(df_pred.isnull().sum().sum() == 0)
 
+    # CLI test forecast model tune action
     def test_main_forecast_model_tune(self):
         params = copy.deepcopy(json.loads(self.params_json))
         runtimeparams = {
-            "days_to_retrieve": 20,
+            'historic_days_to_retrieve': 20,
             "model_type": "load_forecast",
             "var_model": "sensor.power_load_no_var_loads",
             "sklearn_model": "KNeighborsRegressor",
@@ -509,7 +525,7 @@ def test_main_forecast_model_tune(self):
         }
         runtimeparams_json = json.dumps(runtimeparams)
         params["passed_data"] = runtimeparams
-        params["optim_conf"]["load_forecast_method"] = "skforecast"
+        params["optim_conf"]['load_forecast_method'] = "skforecast"
         params_json = json.dumps(params)
         with patch('sys.argv', ['main', '--action', 'forecast-model-tune', '--config', str(emhass_conf['config_path']), 
                                 '--params', params_json, '--runtimeparams', runtimeparams_json,
@@ -518,6 +534,7 @@ def test_main_forecast_model_tune(self):
         self.assertIsInstance(df_pred_optim, pd.DataFrame)
         self.assertTrue(mlf.is_tuned == True)
 
+    # CLI test regressor model fit action
     def test_main_regressor_model_fit(self):
         params = copy.deepcopy(json.loads(self.params_json))
         runtimeparams = {
@@ -550,6 +567,7 @@ def test_main_regressor_model_fit(self):
         ):
             mlr = main()
 
+    # CLI test regressor model predict action
     def test_main_regressor_model_predict(self):
         params = copy.deepcopy(json.loads(self.params_json))
         runtimeparams = {
@@ -564,7 +582,7 @@ def test_main_regressor_model_predict(self):
         }
         runtimeparams_json = json.dumps(runtimeparams)
         params["passed_data"] = runtimeparams
-        params["optim_conf"]["load_forecast_method"] = "skforecast"
+        params["optim_conf"]['load_forecast_method'] = "skforecast"
         params_json = json.dumps(params)
         with patch(
             "sys.argv",
@@ -584,8 +602,8 @@ def test_main_regressor_model_predict(self):
         ):
             prediction = main()
         self.assertIsInstance(prediction, np.ndarray)
-
         
+    # CLI test publish data action
     @patch('sys.argv', ['main', '--action', 'publish-data', '--config', str(emhass_conf['config_path']), 
                         '--debug', 'True'])
     def test_main_publish_data(self):
diff --git a/tests/test_forecast.py b/tests/test_forecast.py
index 0b656fc3..2f7b2fd3 100644
--- a/tests/test_forecast.py
+++ b/tests/test_forecast.py
@@ -5,7 +5,7 @@
 import os
 import requests_mock
 import pandas as pd
-import pathlib, pickle, json, copy, yaml
+import pathlib, pickle, json, copy
 import bz2
 import _pickle as cPickle
 
@@ -16,12 +16,15 @@
 from emhass.optimization import Optimization
 from emhass import utils
 
-# the root folder
-root = str(utils.get_root(__file__, num_parent=2))
+# The root folder
+root = pathlib.Path(utils.get_root(__file__, num_parent=2))
+# Build emhass_conf paths
 emhass_conf = {}
-emhass_conf['config_path'] = pathlib.Path(root) / 'config_emhass.yaml'
-emhass_conf['data_path'] = pathlib.Path(root) / 'data/'
-emhass_conf['root_path'] = pathlib.Path(root) / 'src/emhass/'
+emhass_conf['data_path'] = root / 'data/'
+emhass_conf['root_path'] = root / 'src/emhass/'
+emhass_conf['defaults_path'] = emhass_conf['root_path']  / 'data/config_defaults.json'
+emhass_conf['associations_path'] = emhass_conf['root_path']  / 'data/associations.csv'
+
 
 # create logger
 logger, ch = utils.get_logger(__name__, emhass_conf, save_to_file=False)
@@ -30,47 +33,47 @@ class TestForecast(unittest.TestCase):
     
     @staticmethod
     def get_test_params():
-        with open(emhass_conf['config_path'], 'r') as file:
-            params = yaml.load(file, Loader=yaml.FullLoader)
-        params.update({
-            'params_secrets': {
-                'hass_url': 'http://supervisor/core/api',
-                'long_lived_token': '${SUPERVISOR_TOKEN}',
-                'time_zone': 'Europe/Paris',
-                'lat': 45.83,
-                'lon': 6.86,
-                'alt': 8000.0
-            }
-            })
+        params = {}
+        # Build params with default config and secrets
+        if emhass_conf['defaults_path'].exists():
+            config = utils.build_config(emhass_conf,logger,emhass_conf['defaults_path'])
+            _,secrets = utils.build_secrets(emhass_conf,logger,no_response=True)
+            params =  utils.build_params(emhass_conf,secrets,config,logger)
+        else:
+            raise Exception("config_defaults. does not exist in path: "+str(emhass_conf['defaults_path'] ))
         return params
 
     def setUp(self):
         self.get_data_from_file = True
-        params = None
-        retrieve_hass_conf, optim_conf, plant_conf = utils.get_yaml_parse(emhass_conf, use_secrets=False)
+        params = json.dumps(TestForecast.get_test_params())
+        retrieve_hass_conf, optim_conf, plant_conf = utils.get_yaml_parse(params,logger)
         self.retrieve_hass_conf, self.optim_conf, self.plant_conf = \
             retrieve_hass_conf, optim_conf, plant_conf
+        # Create RetrieveHass object
         self.rh = RetrieveHass(self.retrieve_hass_conf['hass_url'], self.retrieve_hass_conf['long_lived_token'], 
-                               self.retrieve_hass_conf['freq'], self.retrieve_hass_conf['time_zone'],
+                               self.retrieve_hass_conf['optimization_time_step'], self.retrieve_hass_conf['time_zone'],
                                params, emhass_conf, logger)
+        # Obtain sensor values from saved file
         if self.get_data_from_file:
             with open(emhass_conf['data_path'] / 'test_df_final.pkl', 'rb') as inp:
                 self.rh.df_final, self.days_list, self.var_list = pickle.load(inp)
-            self.retrieve_hass_conf['var_load'] = str(self.var_list[0])
-            self.retrieve_hass_conf['var_PV'] = str(self.var_list[1])
-            self.retrieve_hass_conf['var_interp'] = [retrieve_hass_conf['var_PV'], retrieve_hass_conf['var_load']]
-            self.retrieve_hass_conf['var_replace_zero'] = [retrieve_hass_conf['var_PV']]
+            self.retrieve_hass_conf['sensor_power_load_no_var_loads'] = str(self.var_list[0])
+            self.retrieve_hass_conf['sensor_power_photovoltaics'] = str(self.var_list[1])
+            self.retrieve_hass_conf['sensor_linear_interp'] = [retrieve_hass_conf['sensor_power_photovoltaics'], retrieve_hass_conf['sensor_power_load_no_var_loads']]
+            self.retrieve_hass_conf['sensor_replace_zero'] = [retrieve_hass_conf['sensor_power_photovoltaics']]
+        # Else obtain sensor values from HA
         else:
-            self.days_list = utils.get_days_list(self.retrieve_hass_conf['days_to_retrieve'])
-            self.var_list = [self.retrieve_hass_conf['var_load'], self.retrieve_hass_conf['var_PV']]
+            self.days_list = utils.get_days_list(self.retrieve_hass_conf['historic_days_to_retrieve'])
+            self.var_list = [self.retrieve_hass_conf['sensor_power_load_no_var_loads'], self.retrieve_hass_conf['sensor_power_photovoltaics']]
             self.rh.get_data(self.days_list, self.var_list,
                             minimal_response=False, significant_changes_only=False)
-        self.rh.prepare_data(self.retrieve_hass_conf['var_load'], load_negative = self.retrieve_hass_conf['load_negative'],
+        # Prepare data for optimization
+        self.rh.prepare_data(self.retrieve_hass_conf['sensor_power_load_no_var_loads'], load_negative = self.retrieve_hass_conf['load_negative'],
                              set_zero_min = self.retrieve_hass_conf['set_zero_min'], 
-                             var_replace_zero = self.retrieve_hass_conf['var_replace_zero'], 
-                             var_interp = self.retrieve_hass_conf['var_interp'])
+                             var_replace_zero = self.retrieve_hass_conf['sensor_replace_zero'], 
+                             var_interp = self.retrieve_hass_conf['sensor_linear_interp'])
         self.df_input_data = self.rh.df_final.copy()
-        
+        # Create forecast Object
         self.fcst = Forecast(self.retrieve_hass_conf, self.optim_conf, self.plant_conf, 
                              params, emhass_conf, logger, get_data_from_file=self.get_data_from_file)
         # The default for test is csv read
@@ -82,6 +85,7 @@ def setUp(self):
         self.opt = Optimization(self.retrieve_hass_conf, self.optim_conf, self.plant_conf, 
                                 self.fcst.var_load_cost, self.fcst.var_prod_price, 
                                 'profit', emhass_conf, logger)
+        # Manually create input data (from formatted parameter) dictionary
         self.input_data_dict = {
             'emhass_conf': emhass_conf,
             'retrieve_hass_conf': self.retrieve_hass_conf,
@@ -95,7 +99,10 @@ def setUp(self):
             'params': params
         }
     
+    
+    # Test weather forecast dataframe output based on saved csv file  
     def test_get_weather_forecast_csv(self):
+        # Test dataframe from get weather forecast
         self.df_weather_csv = self.fcst.get_weather_forecast(method='csv')
         self.assertEqual(self.fcst.weather_forecast_method, 'csv')
         self.assertIsInstance(self.df_weather_csv, type(pd.DataFrame()))
@@ -104,7 +111,8 @@ def test_get_weather_forecast_csv(self):
         self.assertEqual(self.df_weather_csv.index.tz, self.fcst.time_zone)
         self.assertTrue(self.fcst.start_forecast < ts for ts in self.df_weather_csv.index)
         self.assertEqual(len(self.df_weather_csv), 
-                         int(self.optim_conf['delta_forecast'].total_seconds()/3600/self.fcst.timeStep))
+                         int(self.optim_conf['delta_forecast_daily'].total_seconds()/3600/self.fcst.timeStep))
+        # Test dataframe from get power from weather
         P_PV_forecast = self.fcst.get_power_from_weather(self.df_weather_csv)
         self.assertIsInstance(P_PV_forecast, pd.core.series.Series)
         self.assertIsInstance(P_PV_forecast.index, pd.core.indexes.datetimes.DatetimeIndex)
@@ -114,12 +122,14 @@ def test_get_weather_forecast_csv(self):
         df_weather_none = self.fcst.get_weather_forecast(method='none')
         self.assertTrue(df_weather_none == None)
     
+    # Test output weather forecast using scrapper with mock get request data
     def test_get_weather_forecast_scrapper_method_mock(self):
         with requests_mock.mock() as m:
             data = bz2.BZ2File(str(emhass_conf['data_path'] / 'test_response_scrapper_get_method.pbz2'), "rb")
             data = cPickle.load(data)
             get_url = "https://clearoutside.com/forecast/"+str(round(self.fcst.lat, 2))+"/"+str(round(self.fcst.lon, 2))+"?desktop=true"
             m.get(get_url, content=data)
+            # Test dataframe output from get weather forecast
             df_weather_scrap = self.fcst.get_weather_forecast(method='scrapper')
             self.assertIsInstance(df_weather_scrap, type(pd.DataFrame()))
             self.assertIsInstance(df_weather_scrap.index, pd.core.indexes.datetimes.DatetimeIndex)
@@ -127,15 +137,17 @@ def test_get_weather_forecast_scrapper_method_mock(self):
             self.assertEqual(df_weather_scrap.index.tz, self.fcst.time_zone)
             self.assertTrue(self.fcst.start_forecast < ts for ts in df_weather_scrap.index)
             self.assertEqual(len(df_weather_scrap), 
-                            int(self.optim_conf['delta_forecast'].total_seconds()/3600/self.fcst.timeStep))
+                            int(self.optim_conf['delta_forecast_daily'].total_seconds()/3600/self.fcst.timeStep))
+            # Test dataframe output from get power from weather forecast
             P_PV_forecast = self.fcst.get_power_from_weather(df_weather_scrap)
             self.assertIsInstance(P_PV_forecast, pd.core.series.Series)
             self.assertIsInstance(P_PV_forecast.index, pd.core.indexes.datetimes.DatetimeIndex)
             self.assertIsInstance(P_PV_forecast.index.dtype, pd.core.dtypes.dtypes.DatetimeTZDtype)
             self.assertEqual(P_PV_forecast.index.tz, self.fcst.time_zone)
             self.assertEqual(len(df_weather_scrap), len(P_PV_forecast))
-            self.plant_conf['module_model'] = [self.plant_conf['module_model'][0], self.plant_conf['module_model'][0]]
-            self.plant_conf['inverter_model'] = [self.plant_conf['inverter_model'][0], self.plant_conf['inverter_model'][0]]
+            # Test dataframe output from get power from weather forecast (with 2 PV plant's)
+            self.plant_conf['pv_module_model'] = [self.plant_conf['pv_module_model'][0], self.plant_conf['pv_module_model'][0]]
+            self.plant_conf['pv_inverter_model'] = [self.plant_conf['pv_inverter_model'][0], self.plant_conf['pv_inverter_model'][0]]
             self.plant_conf['surface_tilt'] = [30, 45]
             self.plant_conf['surface_azimuth'] = [270, 90]
             self.plant_conf['modules_per_string'] = [8, 8]
@@ -146,7 +158,8 @@ def test_get_weather_forecast_scrapper_method_mock(self):
             self.assertIsInstance(P_PV_forecast.index.dtype, pd.core.dtypes.dtypes.DatetimeTZDtype)
             self.assertEqual(P_PV_forecast.index.tz, self.fcst.time_zone)
             self.assertEqual(len(df_weather_scrap), len(P_PV_forecast))
-
+    
+    # Test output weather forecast using Solcast with mock get request data   
     def test_get_weather_forecast_solcast_method_mock(self):
         self.fcst.params = {'passed_data': {'weather_forecast_cache': False, 'weather_forecast_cache_only': False}}
         self.fcst.retrieve_hass_conf['solcast_api_key'] = "123456"
@@ -165,17 +178,18 @@ def test_get_weather_forecast_solcast_method_mock(self):
             self.assertEqual(df_weather_scrap.index.tz, self.fcst.time_zone)
             self.assertTrue(self.fcst.start_forecast < ts for ts in df_weather_scrap.index)
             self.assertEqual(len(df_weather_scrap), 
-                            int(self.optim_conf['delta_forecast'].total_seconds()/3600/self.fcst.timeStep))
+                            int(self.optim_conf['delta_forecast_daily'].total_seconds()/3600/self.fcst.timeStep))
             if os.path.isfile(emhass_conf['data_path'] / "temp_weather_forecast_data.pkl"): 
                 os.rename(emhass_conf['data_path'] / "temp_weather_forecast_data.pkl", emhass_conf['data_path'] / "weather_forecast_data.pkl")
         
+    # Test output weather forecast using Forecast.Solar with mock get request data
     def test_get_weather_forecast_solarforecast_method_mock(self):
         with requests_mock.mock() as m:
             data = bz2.BZ2File(str(emhass_conf['data_path'] / 'test_response_solarforecast_get_method.pbz2'), "rb")
             data = cPickle.load(data)
-            for i in range(len(self.plant_conf['module_model'])):
+            for i in range(len(self.plant_conf['pv_module_model'])):
                 get_url = "https://api.forecast.solar/estimate/"+str(round(self.fcst.lat, 2))+"/"+str(round(self.fcst.lon, 2))+\
-                    "/"+str(self.plant_conf["surface_tilt"][i])+"/"+str(self.plant_conf["surface_azimuth"][i]-180)+\
+                    "/"+str(self.plant_conf['surface_tilt'][i])+"/"+str(self.plant_conf['surface_azimuth'][i]-180)+\
                     "/"+str(5)
                 m.get(get_url, json=data)
                 df_weather_solarforecast = self.fcst.get_weather_forecast(method='solar.forecast')
@@ -185,21 +199,21 @@ def test_get_weather_forecast_solarforecast_method_mock(self):
                 self.assertEqual(df_weather_solarforecast.index.tz, self.fcst.time_zone)
                 self.assertTrue(self.fcst.start_forecast < ts for ts in df_weather_solarforecast.index)
                 self.assertEqual(len(df_weather_solarforecast), 
-                                int(self.optim_conf['delta_forecast'].total_seconds()/3600/self.fcst.timeStep))
+                                int(self.optim_conf['delta_forecast_daily'].total_seconds()/3600/self.fcst.timeStep))
 
+    #  Test output weather forecast using passed runtime lists   
     def test_get_forecasts_with_lists(self):
-        with open(emhass_conf['config_path'], 'r') as file:
-            params = yaml.load(file, Loader=yaml.FullLoader)
-        params.update({
-            'params_secrets': {
-                'hass_url': 'http://supervisor/core/api',
-                'long_lived_token': '${SUPERVISOR_TOKEN}',
-                'time_zone': 'Europe/Paris',
-                'lat': 45.83,
-                'lon': 6.86,
-                'alt': 4807.8
-            }
-            })
+        # Load default params
+        params = {}
+        if emhass_conf['defaults_path'].exists():
+            with emhass_conf['defaults_path'].open('r') as data:
+                defaults = json.load(data)
+                updated_emhass_conf, built_secrets = utils.build_secrets(emhass_conf,logger)
+                emhass_conf.update(updated_emhass_conf)
+                params.update(utils.build_params(emhass_conf, built_secrets, defaults, logger))
+        else:
+            raise Exception("config_defaults. does not exist in path: "+str(emhass_conf['defaults_path'] ))
+        # Create 48 (1 day of data) long lists runtime forecasts parameters
         runtimeparams = {
             'pv_power_forecast':[i+1 for i in range(48)],
             'load_power_forecast':[i+1 for i in range(48)],
@@ -209,35 +223,41 @@ def test_get_forecasts_with_lists(self):
         runtimeparams_json = json.dumps(runtimeparams)
         params['passed_data'] = runtimeparams
         params_json = json.dumps(params)
-        retrieve_hass_conf, optim_conf, plant_conf = utils.get_yaml_parse(emhass_conf, 
-                                                                    use_secrets=False, params=params_json)
+        retrieve_hass_conf, optim_conf, plant_conf = utils.get_yaml_parse(params_json,logger)
         set_type = "dayahead-optim"
         params, retrieve_hass_conf, optim_conf, plant_conf = utils.treat_runtimeparams(
             runtimeparams_json, params_json, retrieve_hass_conf, 
             optim_conf, plant_conf, set_type, logger)
+        # Build RetrieveHass Object
         rh = RetrieveHass(retrieve_hass_conf['hass_url'], retrieve_hass_conf['long_lived_token'], 
-                          retrieve_hass_conf['freq'], retrieve_hass_conf['time_zone'],
+                          retrieve_hass_conf['optimization_time_step'], retrieve_hass_conf['time_zone'],
                           params, emhass_conf, logger)
+        # Obtain sensor values from saved file
         if self.get_data_from_file:
             with open((emhass_conf['data_path'] / 'test_df_final.pkl'), 'rb') as inp:
                 rh.df_final, days_list, var_list = pickle.load(inp)
-            retrieve_hass_conf['var_load'] = str(self.var_list[0])
-            retrieve_hass_conf['var_PV'] = str(self.var_list[1])
-            retrieve_hass_conf['var_interp'] = [retrieve_hass_conf['var_PV'], retrieve_hass_conf['var_load']]
-            retrieve_hass_conf['var_replace_zero'] = [retrieve_hass_conf['var_PV']]
+            retrieve_hass_conf['sensor_power_load_no_var_loads'] = str(self.var_list[0])
+            retrieve_hass_conf['sensor_power_photovoltaics'] = str(self.var_list[1])
+            retrieve_hass_conf['sensor_linear_interp'] = [retrieve_hass_conf['sensor_power_photovoltaics'], retrieve_hass_conf['sensor_power_load_no_var_loads']]
+            retrieve_hass_conf['sensor_replace_zero'] = [retrieve_hass_conf['sensor_power_photovoltaics']]
+        # Else obtain sensor values from HA
         else:
-            days_list = utils.get_days_list(retrieve_hass_conf['days_to_retrieve'])
-            var_list = [retrieve_hass_conf['var_load'], retrieve_hass_conf['var_PV']]
+            days_list = utils.get_days_list(retrieve_hass_conf['historic_days_to_retrieve'])
+            var_list = [retrieve_hass_conf['sensor_power_load_no_var_loads'], retrieve_hass_conf['sensor_power_photovoltaics']]
             rh.get_data(days_list, var_list,
                         minimal_response=False, significant_changes_only=False)
-        rh.prepare_data(retrieve_hass_conf['var_load'], load_negative = retrieve_hass_conf['load_negative'],
+        # Prepare data for optimization
+        rh.prepare_data(retrieve_hass_conf['sensor_power_load_no_var_loads'], load_negative = retrieve_hass_conf['load_negative'],
                         set_zero_min = retrieve_hass_conf['set_zero_min'], 
-                        var_replace_zero = retrieve_hass_conf['var_replace_zero'], 
-                        var_interp = retrieve_hass_conf['var_interp'])
+                        var_replace_zero = retrieve_hass_conf['sensor_replace_zero'], 
+                        var_interp = retrieve_hass_conf['sensor_linear_interp'])
         df_input_data = rh.df_final.copy()
+        # Build Forecast Object
         fcst = Forecast(retrieve_hass_conf, optim_conf, plant_conf, 
-                        params_json, emhass_conf, logger, get_data_from_file=True)
+                        params, emhass_conf, logger, get_data_from_file=True)
+        # Obtain only 48 rows of data and remove last column for input
         df_input_data = copy.deepcopy(df_input_data).iloc[-49:-1]
+        # Get Weather forecast with list, check dataframe output
         P_PV_forecast = fcst.get_weather_forecast(method='list')
         df_input_data.index = P_PV_forecast.index
         df_input_data.index.freq = rh.df_final.index.freq
@@ -248,6 +268,7 @@ def test_get_forecasts_with_lists(self):
         self.assertTrue(fcst.start_forecast < ts for ts in P_PV_forecast.index)
         self.assertTrue(P_PV_forecast.values[0][0] == 1)
         self.assertTrue(P_PV_forecast.values[-1][0] == 48)
+        # Get load forecast with list, check dataframe output
         P_load_forecast = fcst.get_load_forecast(method='list')
         self.assertIsInstance(P_load_forecast, pd.core.series.Series)
         self.assertIsInstance(P_load_forecast.index, pd.core.indexes.datetimes.DatetimeIndex)
@@ -256,29 +277,33 @@ def test_get_forecasts_with_lists(self):
         self.assertEqual(len(P_PV_forecast), len(P_load_forecast))
         self.assertTrue(P_load_forecast.values[0] == 1)
         self.assertTrue(P_load_forecast.values[-1] == 48)
+        # Get load cost forecast with list, check dataframe output
         df_input_data = fcst.get_load_cost_forecast(df_input_data, method='list')
         self.assertTrue(fcst.var_load_cost in df_input_data.columns)
         self.assertTrue(df_input_data.isnull().sum().sum()==0)
         self.assertTrue(df_input_data['unit_load_cost'].values[0] == 1)
         self.assertTrue(df_input_data['unit_load_cost'].values[-1] == 48)
+        # Get production price forecast with list, check dataframe output
         df_input_data = fcst.get_prod_price_forecast(df_input_data, method='list')
         self.assertTrue(fcst.var_prod_price in df_input_data.columns)
         self.assertTrue(df_input_data.isnull().sum().sum()==0)
         self.assertTrue(df_input_data['unit_prod_price'].values[0] == 1)
         self.assertTrue(df_input_data['unit_prod_price'].values[-1] == 48)
-        # Test with longer lists
-        with open(emhass_conf['config_path'], 'r') as file:
-            params = yaml.load(file, Loader=yaml.FullLoader)
-        params.update({
-            'params_secrets': {
-                'hass_url': 'http://supervisor/core/api',
-                'long_lived_token': '${SUPERVISOR_TOKEN}',
-                'time_zone': 'Europe/Paris',
-                'lat': 45.83,
-                'lon': 6.86,
-                'alt': 4807.8
-            }
-            })
+    
+    # Test output weather forecast using longer passed runtime lists   
+    def test_get_forecasts_with_longer_lists(self):
+        # Load default params
+        params = {}
+        set_type = "dayahead-optim"
+        if emhass_conf['defaults_path'].exists():
+            with emhass_conf['defaults_path'].open('r') as data:
+                defaults = json.load(data)
+                updated_emhass_conf, built_secrets = utils.build_secrets(emhass_conf,logger)
+                emhass_conf.update(updated_emhass_conf)
+                params.update(utils.build_params(emhass_conf, built_secrets, defaults, logger))
+        else:
+            raise Exception("config_defaults. does not exist in path: "+str(emhass_conf['defaults_path'] ))
+        # Create 3*48 (3 days of data) long lists runtime forecasts parameters
         runtimeparams = {
             'pv_power_forecast':[i+1 for i in range(3*48)],
             'load_power_forecast':[i+1 for i in range(3*48)],
@@ -288,14 +313,15 @@ def test_get_forecasts_with_lists(self):
         runtimeparams_json = json.dumps(runtimeparams)
         params['passed_data'] = runtimeparams
         params_json = json.dumps(params)
-        retrieve_hass_conf, optim_conf, plant_conf = utils.get_yaml_parse(emhass_conf, 
-                                                                    use_secrets=False, params=params_json)
-        optim_conf['delta_forecast'] = pd.Timedelta(days=3)
+        retrieve_hass_conf, optim_conf, plant_conf = utils.get_yaml_parse(params_json,logger)
+        optim_conf['delta_forecast_daily'] = pd.Timedelta(days=3)
         params, retrieve_hass_conf, optim_conf, plant_conf = utils.treat_runtimeparams(
             runtimeparams_json, params_json, retrieve_hass_conf, 
             optim_conf, plant_conf, set_type, logger)
+        # Create Forecast Object
         fcst = Forecast(retrieve_hass_conf, optim_conf, plant_conf, 
-                        params_json, emhass_conf, logger, get_data_from_file=True)
+                        params, emhass_conf, logger, get_data_from_file=True)
+        # Get weather forecast with list, check dataframe output
         P_PV_forecast = fcst.get_weather_forecast(method='list')
         self.assertIsInstance(P_PV_forecast, type(pd.DataFrame()))
         self.assertIsInstance(P_PV_forecast.index, pd.core.indexes.datetimes.DatetimeIndex)
@@ -304,6 +330,7 @@ def test_get_forecasts_with_lists(self):
         self.assertTrue(fcst.start_forecast < ts for ts in P_PV_forecast.index)
         self.assertTrue(P_PV_forecast.values[0][0] == 1)
         self.assertTrue(P_PV_forecast.values[-1][0] == 3*48)
+        # Get load forecast with list, check dataframe output
         P_load_forecast = fcst.get_load_forecast(method='list')
         self.assertIsInstance(P_load_forecast, pd.core.series.Series)
         self.assertIsInstance(P_load_forecast.index, pd.core.indexes.datetimes.DatetimeIndex)
@@ -315,30 +342,30 @@ def test_get_forecasts_with_lists(self):
         df_input_data_dayahead = pd.concat([P_PV_forecast, P_load_forecast], axis=1)
         df_input_data_dayahead = utils.set_df_index_freq(df_input_data_dayahead)
         df_input_data_dayahead.columns = ['P_PV_forecast', 'P_load_forecast']
+        # Get load cost forecast with list, check dataframe output
         df_input_data_dayahead = fcst.get_load_cost_forecast(df_input_data_dayahead, method='list')
         self.assertTrue(fcst.var_load_cost in df_input_data_dayahead.columns)
         self.assertTrue(df_input_data_dayahead.isnull().sum().sum()==0)
         self.assertTrue(df_input_data_dayahead[fcst.var_load_cost].iloc[0] == 1)
         self.assertTrue(df_input_data_dayahead[fcst.var_load_cost].iloc[-1] == 3*48)
+        # Get production price forecast with list, check dataframe output
         df_input_data_dayahead = fcst.get_prod_price_forecast(df_input_data_dayahead, method='list')
         self.assertTrue(fcst.var_prod_price in df_input_data_dayahead.columns)
         self.assertTrue(df_input_data_dayahead.isnull().sum().sum()==0)
         self.assertTrue(df_input_data_dayahead[fcst.var_prod_price].iloc[0] == 1)
         self.assertTrue(df_input_data_dayahead[fcst.var_prod_price].iloc[-1] == 3*48)
-        
+
+    # Test output values of weather forecast using passed runtime lists and saved sensor data
     def test_get_forecasts_with_lists_special_case(self):
-        with open(emhass_conf['config_path'], 'r') as file:
-            params = yaml.load(file, Loader=yaml.FullLoader)
-        params.update({
-            'params_secrets': {
-                'hass_url': 'http://supervisor/core/api',
-                'long_lived_token': '${SUPERVISOR_TOKEN}',
-                'time_zone': 'Europe/Paris',
-                'lat': 45.83,
-                'lon': 6.86,
-                'alt': 4807.8
-            }
-            })
+        # Load default params
+        params = {}
+        if emhass_conf['defaults_path'].exists():
+            config = utils.build_config(emhass_conf,logger,emhass_conf['defaults_path'])
+            _,secrets = utils.build_secrets(emhass_conf,logger,no_response=True)
+            params =  utils.build_params(emhass_conf,secrets,config,logger)
+        else:
+            raise Exception("config_defaults. does not exist in path: "+str(emhass_conf['defaults_path'] ))
+        # Create 48 (1 day of data) long lists runtime forecasts parameters
         runtimeparams = {
             'load_cost_forecast':[i+1 for i in range(48)],
             'prod_price_forecast':[i+1 for i in range(48)]
@@ -346,44 +373,52 @@ def test_get_forecasts_with_lists_special_case(self):
         runtimeparams_json = json.dumps(runtimeparams)
         params['passed_data'] = runtimeparams
         params_json = json.dumps(params)
-        retrieve_hass_conf, optim_conf, plant_conf = utils.get_yaml_parse(emhass_conf, 
-                                                                    use_secrets=False, params=params_json)
+        retrieve_hass_conf, optim_conf, plant_conf = utils.get_yaml_parse(params_json,logger)
         set_type = "dayahead-optim"
         params, retrieve_hass_conf, optim_conf, plant_conf = utils.treat_runtimeparams(
             runtimeparams_json, params_json, retrieve_hass_conf, 
             optim_conf, plant_conf, set_type, logger)
+        # Create RetrieveHass Object
         rh = RetrieveHass(retrieve_hass_conf['hass_url'], retrieve_hass_conf['long_lived_token'], 
-                          retrieve_hass_conf['freq'], retrieve_hass_conf['time_zone'],
+                          retrieve_hass_conf['optimization_time_step'], retrieve_hass_conf['time_zone'],
                           params, emhass_conf, logger)
+        # Obtain sensor values from saved file
         if self.get_data_from_file:
             with open(emhass_conf['data_path'] / 'test_df_final.pkl', 'rb') as inp:
                 rh.df_final, days_list, var_list = pickle.load(inp)
-            retrieve_hass_conf['var_load'] = str(self.var_list[0])
-            retrieve_hass_conf['var_PV'] = str(self.var_list[1])
-            retrieve_hass_conf['var_interp'] = [retrieve_hass_conf['var_PV'], retrieve_hass_conf['var_load']]
-            retrieve_hass_conf['var_replace_zero'] = [retrieve_hass_conf['var_PV']]
+            retrieve_hass_conf['sensor_power_load_no_var_loads'] = str(self.var_list[0])
+            retrieve_hass_conf['sensor_power_photovoltaics'] = str(self.var_list[1])
+            retrieve_hass_conf['sensor_linear_interp'] = [retrieve_hass_conf['sensor_power_photovoltaics'], retrieve_hass_conf['sensor_power_load_no_var_loads']]
+            retrieve_hass_conf['sensor_replace_zero'] = [retrieve_hass_conf['sensor_power_photovoltaics']]
+        # Else obtain sensor values from HA
         else:
-            days_list = utils.get_days_list(retrieve_hass_conf['days_to_retrieve'])
-            var_list = [retrieve_hass_conf['var_load'], retrieve_hass_conf['var_PV']]
+            days_list = utils.get_days_list(retrieve_hass_conf['historic_days_to_retrieve'])
+            var_list = [retrieve_hass_conf['sensor_power_load_no_var_loads'], retrieve_hass_conf['sensor_power_photovoltaics']]
             rh.get_data(days_list, var_list,
                         minimal_response=False, significant_changes_only=False)
-        rh.prepare_data(retrieve_hass_conf['var_load'], load_negative = retrieve_hass_conf['load_negative'],
+        # Prepare data for optimization
+        rh.prepare_data(retrieve_hass_conf['sensor_power_load_no_var_loads'], load_negative = retrieve_hass_conf['load_negative'],
                         set_zero_min = retrieve_hass_conf['set_zero_min'], 
-                        var_replace_zero = retrieve_hass_conf['var_replace_zero'], 
-                        var_interp = retrieve_hass_conf['var_interp'])
+                        var_replace_zero = retrieve_hass_conf['sensor_replace_zero'], 
+                        var_interp = retrieve_hass_conf['sensor_linear_interp'])
         df_input_data = rh.df_final.copy()
+        # Create forecast object
         fcst = Forecast(retrieve_hass_conf, optim_conf, plant_conf, 
-                        params_json, emhass_conf, logger, get_data_from_file=True)
+                        params, emhass_conf, logger, get_data_from_file=True)
+        # Obtain only 48 rows of data and remove last column for input
         df_input_data = copy.deepcopy(df_input_data).iloc[-49:-1]
+        # Get weather forecast with list 
         P_PV_forecast = fcst.get_weather_forecast()
         df_input_data.index = P_PV_forecast.index
         df_input_data.index.freq = rh.df_final.index.freq
+        # Get load cost forecast with list, check values from output
         df_input_data = fcst.get_load_cost_forecast(
             df_input_data, method='list')
         self.assertTrue(fcst.var_load_cost in df_input_data.columns)
         self.assertTrue(df_input_data.isnull().sum().sum()==0)
         self.assertTrue(df_input_data['unit_load_cost'].values[0] == 1)
         self.assertTrue(df_input_data['unit_load_cost'].values[-1] == 48)
+        # Get production price forecast with list, check values from output
         df_input_data = fcst.get_prod_price_forecast(
             df_input_data, method='list')
         self.assertTrue(fcst.var_prod_price in df_input_data.columns)
@@ -397,9 +432,9 @@ def test_get_power_from_weather(self):
         self.assertIsInstance(self.P_PV_forecast.index.dtype, pd.core.dtypes.dtypes.DatetimeTZDtype)
         self.assertEqual(self.P_PV_forecast.index.tz, self.fcst.time_zone)
         self.assertEqual(len(self.df_weather_scrap), len(self.P_PV_forecast))
-        # Lets test passing a lists of PV params
-        self.plant_conf['module_model'] = [self.plant_conf['module_model'], self.plant_conf['module_model']]
-        self.plant_conf['inverter_model'] = [self.plant_conf['inverter_model'], self.plant_conf['inverter_model']]
+        # Test passing a lists of PV params
+        self.plant_conf['pv_module_model'] = [self.plant_conf['pv_module_model'], self.plant_conf['pv_module_model']]
+        self.plant_conf['pv_inverter_model'] = [self.plant_conf['pv_inverter_model'], self.plant_conf['pv_inverter_model']]
         self.plant_conf['surface_tilt'] = [30, 45]
         self.plant_conf['surface_azimuth'] = [270, 90]
         self.plant_conf['modules_per_string'] = [8, 8]
@@ -426,6 +461,7 @@ def test_get_power_from_weather(self):
         self.assertEqual(P_PV_forecast.index.tz, self.fcst.time_zone)
         self.assertEqual(len(self.df_weather_scrap), len(P_PV_forecast))
     
+    # Test dataframe output of load forecast
     def test_get_load_forecast(self):
         P_load_forecast = self.fcst.get_load_forecast()
         self.assertIsInstance(P_load_forecast, pd.core.series.Series)
@@ -453,14 +489,16 @@ def test_get_load_forecast(self):
         self.assertEqual(P_load_forecast.index.tz, self.fcst.time_zone)
         self.assertEqual(len(self.P_PV_forecast), len(P_load_forecast))
         
+    # Test dataframe output of ml load forecast
     def test_get_load_forecast_mlforecaster(self):
         params = TestForecast.get_test_params()
         params_json = json.dumps(params)
         costfun = 'profit'
         action = 'forecast-model-fit' # fit, predict and tune methods
         params = copy.deepcopy(json.loads(params_json))
+        # pass custom runtime parameters
         runtimeparams = {
-            "days_to_retrieve": 20,
+            'historic_days_to_retrieve': 20,
             "model_type": "load_forecast",
             "var_model": "sensor.power_load_no_var_loads",
             "sklearn_model": "KNeighborsRegressor",
@@ -473,12 +511,14 @@ def test_get_load_forecast_mlforecaster(self):
         input_data_dict = set_input_data_dict(emhass_conf, costfun, params_json, runtimeparams_json, 
                                               action, logger, get_data_from_file=True)
         data = copy.deepcopy(input_data_dict['df_input_data'])
+        # Create MLForecaster Object
         model_type = input_data_dict['params']['passed_data']['model_type']
         var_model = input_data_dict['params']['passed_data']['var_model']
         sklearn_model = input_data_dict['params']['passed_data']['sklearn_model']
         num_lags = input_data_dict['params']['passed_data']['num_lags']
         mlf = MLForecaster(data, model_type, var_model, sklearn_model, num_lags, emhass_conf, logger)
         mlf.fit()
+        # Get load forecast using mlforecaster 
         P_load_forecast = input_data_dict['fcst'].get_load_forecast(method="mlforecaster", use_last_window=False, 
                                                                     debug=True, mlf=mlf)
         self.assertIsInstance(P_load_forecast, pd.core.series.Series)
@@ -488,6 +528,7 @@ def test_get_load_forecast_mlforecaster(self):
         self.assertTrue((P_load_forecast.index == self.fcst.forecast_dates).all())
         self.assertEqual(len(self.P_PV_forecast), len(P_load_forecast))
         
+    # Test load cost forecast dataframe output using saved csv referece file
     def test_get_load_cost_forecast(self):
         df_input_data = self.fcst.get_load_cost_forecast(self.df_input_data)
         self.assertTrue(self.fcst.var_load_cost in df_input_data.columns)
@@ -497,6 +538,7 @@ def test_get_load_cost_forecast(self):
         self.assertTrue(self.fcst.var_load_cost in df_input_data.columns)
         self.assertTrue(df_input_data.isnull().sum().sum()==0)
         
+    # Test production price forecast dataframe output using saved csv referece file
     def test_get_prod_price_forecast(self):
         df_input_data = self.fcst.get_prod_price_forecast(self.df_input_data)
         self.assertTrue(self.fcst.var_prod_price in df_input_data.columns)
diff --git a/tests/test_machine_learning_forecaster.py b/tests/test_machine_learning_forecaster.py
index 5fd504cf..78e7a3d0 100644
--- a/tests/test_machine_learning_forecaster.py
+++ b/tests/test_machine_learning_forecaster.py
@@ -5,7 +5,6 @@
 from unittest.mock import patch
 import pathlib
 import json
-import yaml
 import copy
 import pickle
 import pandas as pd
@@ -18,12 +17,15 @@
 from emhass.machine_learning_forecaster import MLForecaster
 from emhass import utils
 
-# the root folder
-root = str(utils.get_root(__file__, num_parent=2))
+# The root folder
+root = pathlib.Path(utils.get_root(__file__, num_parent=2))
+# Build emhass_conf paths
 emhass_conf = {}
-emhass_conf['config_path'] = pathlib.Path(root) / 'config_emhass.yaml'
-emhass_conf['data_path'] = pathlib.Path(root) / 'data/'
-emhass_conf['root_path'] = pathlib.Path(root) / 'src/emhass/'
+emhass_conf['data_path'] = root / 'data/'
+emhass_conf['root_path'] = root / 'src/emhass/'
+emhass_conf['defaults_path'] = emhass_conf['root_path']  / 'data/config_defaults.json'
+emhass_conf['associations_path'] = emhass_conf['root_path']  / 'data/associations.csv'
+
 
 # create logger
 logger, ch = utils.get_logger(__name__, emhass_conf, save_to_file=False)
@@ -32,28 +34,22 @@ class TestMLForecaster(unittest.TestCase):
     
     @staticmethod
     def get_test_params():
-        with open(emhass_conf['config_path'], 'r') as file:
-            params = yaml.load(file, Loader=yaml.FullLoader)
-        params.update({
-            'params_secrets': {
-                'hass_url': 'http://supervisor/core/api',
-                'long_lived_token': '${SUPERVISOR_TOKEN}',
-                'time_zone': 'Europe/Paris',
-                'lat': 45.83,
-                'lon': 6.86,
-                'alt': 8000.0
-            }
-            })
+        # Build params with default config and secrets
+        if emhass_conf['defaults_path'].exists():
+            config = utils.build_config(emhass_conf,logger,emhass_conf['defaults_path'])
+            _,secrets = utils.build_secrets(emhass_conf,logger,no_response=True)
+            params =  utils.build_params(emhass_conf,secrets,config,logger)
+        else:
+            raise Exception("config_defaults. does not exist in path: "+str(emhass_conf['defaults_path'] ))
         return params
 
     def setUp(self):
         params = TestMLForecaster.get_test_params()
-        params_json = json.dumps(params)
         costfun = 'profit'
         action = 'forecast-model-fit' # fit, predict and tune methods
-        params = copy.deepcopy(json.loads(params_json))
+        # Create runtime parameters
         runtimeparams = {
-            "days_to_retrieve": 20,
+            'historic_days_to_retrieve': 20,
             "model_type": "load_forecast",
             "var_model": "sensor.power_load_no_var_loads",
             "sklearn_model": "KNeighborsRegressor",
@@ -62,22 +58,25 @@ def setUp(self):
         runtimeparams_json = json.dumps(runtimeparams)
         params['passed_data'] = runtimeparams
         params['optim_conf']['load_forecast_method'] = 'skforecast'
+        #Create input dictionary
         params_json = json.dumps(params)
         self.input_data_dict = set_input_data_dict(emhass_conf, costfun, params_json, runtimeparams_json, 
                                                    action, logger, get_data_from_file=True)
+        # Create MLForcaster object
         data = copy.deepcopy(self.input_data_dict['df_input_data'])
         model_type = self.input_data_dict['params']['passed_data']['model_type']
         var_model = self.input_data_dict['params']['passed_data']['var_model']
         sklearn_model = self.input_data_dict['params']['passed_data']['sklearn_model']
         num_lags = self.input_data_dict['params']['passed_data']['num_lags']
         self.mlf = MLForecaster(data, model_type, var_model, sklearn_model, num_lags, emhass_conf, logger)
-        
+        # Create RetrieveHass Object
         get_data_from_file = True
         params = None
-        self.retrieve_hass_conf, self.optim_conf, _ = utils.get_yaml_parse(emhass_conf, use_secrets=False)
+        self.retrieve_hass_conf, self.optim_conf, _ = utils.get_yaml_parse(params_json,logger)
         self.rh = RetrieveHass(self.retrieve_hass_conf['hass_url'], self.retrieve_hass_conf['long_lived_token'], 
-                               self.retrieve_hass_conf['freq'], self.retrieve_hass_conf['time_zone'],
-                               params, emhass_conf, logger, get_data_from_file=get_data_from_file)
+                               self.retrieve_hass_conf['optimization_time_step'], self.retrieve_hass_conf['time_zone'],
+                               params_json, emhass_conf, logger, get_data_from_file=get_data_from_file)
+        # Open and extract saved sensor data to test against
         with open(emhass_conf['data_path'] / 'test_df_final.pkl', 'rb') as inp:
             self.rh.df_final, self.days_list, self.var_list = pickle.load(inp)
         
diff --git a/tests/test_machine_learning_regressor.py b/tests/test_machine_learning_regressor.py
index 6cea4701..ab6d1de9 100644
--- a/tests/test_machine_learning_regressor.py
+++ b/tests/test_machine_learning_regressor.py
@@ -7,18 +7,20 @@
 
 import numpy as np
 import pandas as pd
-import yaml
 from emhass import utils
 from emhass.command_line import set_input_data_dict
 from emhass.machine_learning_regressor import MLRegressor
 from sklearn.pipeline import Pipeline
 
-# the root folder
-root = str(utils.get_root(__file__, num_parent=2))
+# The root folder
+root = pathlib.Path(utils.get_root(__file__, num_parent=2))
+# Build emhass_conf paths
 emhass_conf = {}
-emhass_conf["config_path"] = pathlib.Path(root) / "config_emhass.yaml"
-emhass_conf["data_path"] = pathlib.Path(root) / "data/"
-emhass_conf['root_path'] = pathlib.Path(root) / 'src/emhass/'
+emhass_conf['data_path'] = root / 'data/'
+emhass_conf['root_path'] = root / 'src/emhass/'
+emhass_conf['defaults_path'] = emhass_conf['root_path']  / 'data/config_defaults.json'
+emhass_conf['associations_path'] = emhass_conf['root_path']  / 'data/associations.csv'
+
 
 # create logger
 logger, ch = utils.get_logger(__name__, emhass_conf, save_to_file=False)
@@ -27,28 +29,22 @@
 class TestMLRegressor(unittest.TestCase):
     @staticmethod
     def get_test_params():
-        with open(emhass_conf["config_path"]) as file:
-            params = yaml.safe_load(file)
-        params.update(
-            {
-                "params_secrets": {
-                    "hass_url": "http://supervisor/core/api",
-                    "long_lived_token": "${SUPERVISOR_TOKEN}",
-                    "time_zone": "Europe/Paris",
-                    "lat": 45.83,
-                    "lon": 6.86,
-                    "alt": 8000.0,
-                },
-            },
-        )
+        # Build params with default config and secrets
+        if emhass_conf['defaults_path'].exists():
+            config = utils.build_config(emhass_conf,logger,emhass_conf['defaults_path'])
+            _,secrets = utils.build_secrets(emhass_conf,logger,no_response=True)
+            params =  utils.build_params(emhass_conf,secrets,config,logger)
+        else:
+            raise Exception("config_defaults. does not exist in path: "+str(emhass_conf['defaults_path'] ))
         return params
 
     def setUp(self):
+        # parameters
         params = TestMLRegressor.get_test_params()
-        params_json = json.dumps(params)
         costfun = "profit"
         action = "regressor-model-fit"  # fit and predict methods
-        params = copy.deepcopy(json.loads(params_json))
+        params["optim_conf"]['load_forecast_method'] = "skforecast"
+        # runtime parameters
         runtimeparams = {
             "csv_file": "heating_prediction.csv",
             "features": ["degreeday", "solar"],
@@ -59,10 +55,10 @@ def setUp(self):
             "date_features": ["month", "day_of_week"],
             "new_values": [12.79, 4.766, 1, 2]
         }
-        runtimeparams_json = json.dumps(runtimeparams)
         params["passed_data"] = runtimeparams
-        params["optim_conf"]["load_forecast_method"] = "skforecast"
+        runtimeparams_json = json.dumps(runtimeparams)
         params_json = json.dumps(params)
+        # build data dictionary
         self.input_data_dict = set_input_data_dict(
             emhass_conf,
             costfun,
@@ -74,6 +70,7 @@ def setUp(self):
         )
         data = copy.deepcopy(self.input_data_dict["df_input_data"])
         self.assertIsInstance(data, pd.DataFrame)
+        # create MLRegressor object
         self.csv_file = self.input_data_dict["params"]["passed_data"]["csv_file"]
         features = self.input_data_dict["params"]["passed_data"]["features"]
         target = self.input_data_dict["params"]["passed_data"]["target"]
@@ -96,10 +93,12 @@ def setUp(self):
             logger,
         )
 
+    # Test Regressor fit
     def test_fit(self):
         self.mlr.fit(self.date_features)
         self.assertIsInstance(self.mlr.model, Pipeline)
 
+    # Test Regressor tune
     def test_predict(self):
         self.mlr.fit(self.date_features)
         predictions = self.mlr.predict(self.new_values)
diff --git a/tests/test_optimization.py b/tests/test_optimization.py
index a2a1bf7e..0be0dca4 100644
--- a/tests/test_optimization.py
+++ b/tests/test_optimization.py
@@ -1,6 +1,7 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 
+import json
 import os
 import unittest
 import pandas as pd
@@ -13,15 +14,17 @@
 from emhass.retrieve_hass import RetrieveHass
 from emhass.optimization import Optimization
 from emhass.forecast import Forecast
-from emhass.utils import get_root, get_yaml_parse, get_days_list, get_logger
+from emhass.utils import get_root, get_yaml_parse, get_days_list, get_logger,build_config, build_params, build_secrets
 from pandas.testing import assert_series_equal
 
-# the root folder
-root = str(get_root(__file__, num_parent=2))
+# The root folder
+root = pathlib.Path(get_root(__file__, num_parent=2))
+# Build emhass_conf paths
 emhass_conf = {}
-emhass_conf['config_path'] = pathlib.Path(root) / 'config_emhass.yaml'
-emhass_conf['data_path'] = pathlib.Path(root) / 'data/'
-emhass_conf['root_path'] = pathlib.Path(root) / 'src/emhass/'
+emhass_conf['data_path'] = root / 'data/'
+emhass_conf['root_path'] = root / 'src/emhass/'
+emhass_conf['defaults_path'] = emhass_conf['root_path']  / 'data/config_defaults.json'
+emhass_conf['associations_path'] = emhass_conf['root_path']  / 'data/associations.csv'
 
 # create logger
 logger, ch = get_logger(__name__, emhass_conf, save_to_file=False)
@@ -30,31 +33,42 @@ class TestOptimization(unittest.TestCase):
 
     def setUp(self):
         get_data_from_file = True
-        params = '{"passed_data": {}}'
-        retrieve_hass_conf, optim_conf, plant_conf = get_yaml_parse(emhass_conf, use_secrets=False)
+        params = {}
+        # Build params with default config and secrets
+        if emhass_conf['defaults_path'].exists():
+            config = build_config(emhass_conf,logger,emhass_conf['defaults_path'])
+            _,secrets = build_secrets(emhass_conf,logger,no_response=True)
+            params =  build_params(emhass_conf,secrets,config,logger)
+        else:
+            raise Exception("config_defaults. does not exist in path: "+str(emhass_conf['defaults_path'] ))
+        retrieve_hass_conf, optim_conf, plant_conf = get_yaml_parse(json.dumps(params),logger)
         self.retrieve_hass_conf, self.optim_conf, self.plant_conf = \
             retrieve_hass_conf, optim_conf, plant_conf
+        #Build RetrieveHass object
         self.rh = RetrieveHass(self.retrieve_hass_conf['hass_url'], self.retrieve_hass_conf['long_lived_token'], 
-                               self.retrieve_hass_conf['freq'], self.retrieve_hass_conf['time_zone'],
+                               self.retrieve_hass_conf['optimization_time_step'], self.retrieve_hass_conf['time_zone'],
                                params, emhass_conf, logger)
+        # Obtain sensor values from saved file
         if get_data_from_file:
             with open(emhass_conf['data_path'] / 'test_df_final.pkl', 'rb') as inp:
                 self.rh.df_final, self.days_list, self.var_list = pickle.load(inp)
-            self.retrieve_hass_conf['var_load'] = str(self.var_list[0])
-            self.retrieve_hass_conf['var_PV'] = str(self.var_list[1])
-            self.retrieve_hass_conf['var_interp'] = [retrieve_hass_conf['var_PV'], retrieve_hass_conf['var_load']]
-            self.retrieve_hass_conf['var_replace_zero'] = [retrieve_hass_conf['var_PV']]
+            self.retrieve_hass_conf['sensor_power_load_no_var_loads'] = str(self.var_list[0])
+            self.retrieve_hass_conf['sensor_power_photovoltaics'] = str(self.var_list[1])
+            self.retrieve_hass_conf['sensor_linear_interp'] = [retrieve_hass_conf['sensor_power_photovoltaics'], retrieve_hass_conf['sensor_power_load_no_var_loads']]
+            self.retrieve_hass_conf['sensor_replace_zero'] = [retrieve_hass_conf['sensor_power_photovoltaics']]
+        # Else obtain sensor values from HA
         else:
-            self.days_list = get_days_list(self.retrieve_hass_conf['days_to_retrieve'])
-            self.var_list = [self.retrieve_hass_conf['var_load'], self.retrieve_hass_conf['var_PV']]
+            self.days_list = get_days_list(self.retrieve_hass_conf['historic_days_to_retrieve'])
+            self.var_list = [self.retrieve_hass_conf['sensor_power_load_no_var_loads'], self.retrieve_hass_conf['sensor_power_photovoltaics']]
             self.rh.get_data(self.days_list, self.var_list,
                             minimal_response=False, significant_changes_only=False)
-        self.rh.prepare_data(self.retrieve_hass_conf['var_load'], load_negative = self.retrieve_hass_conf['load_negative'],
+        # Prepare data for optimization
+        self.rh.prepare_data(self.retrieve_hass_conf['sensor_power_load_no_var_loads'], load_negative = self.retrieve_hass_conf['load_negative'],
                              set_zero_min = self.retrieve_hass_conf['set_zero_min'], 
-                             var_replace_zero = self.retrieve_hass_conf['var_replace_zero'], 
-                             var_interp = self.retrieve_hass_conf['var_interp'])
+                             var_replace_zero = self.retrieve_hass_conf['sensor_replace_zero'], 
+                             var_interp = self.retrieve_hass_conf['sensor_linear_interp'])
         self.df_input_data = self.rh.df_final.copy()
-        
+        #Build Forecast object
         self.fcst = Forecast(self.retrieve_hass_conf, self.optim_conf, self.plant_conf,
                              params, emhass_conf, logger, get_data_from_file=get_data_from_file)
         self.df_weather = self.fcst.get_weather_forecast(method='csv')
@@ -62,7 +76,7 @@ def setUp(self):
         self.P_load_forecast = self.fcst.get_load_forecast(method=optim_conf['load_forecast_method'])
         self.df_input_data_dayahead = pd.concat([self.P_PV_forecast, self.P_load_forecast], axis=1)
         self.df_input_data_dayahead.columns = ['P_PV_forecast', 'P_load_forecast']
-        
+        #Build Optimization object
         self.costfun = 'profit'
         self.opt = Optimization(self.retrieve_hass_conf, self.optim_conf, self.plant_conf, 
                                 self.fcst.var_load_cost, self.fcst.var_prod_price,  
@@ -73,6 +87,7 @@ def setUp(self):
             'retrieve_hass_conf': retrieve_hass_conf,
         }
         
+    # Check formatting of output from perfect optimization
     def test_perform_perfect_forecast_optim(self):
         self.opt_res = self.opt.perform_perfect_forecast_optim(self.df_input_data, self.days_list)
         self.assertIsInstance(self.opt_res, type(pd.DataFrame()))
@@ -80,7 +95,9 @@ def test_perform_perfect_forecast_optim(self):
         self.assertIsInstance(self.opt_res.index.dtype, pd.core.dtypes.dtypes.DatetimeTZDtype)
         self.assertTrue('cost_fun_'+self.costfun in self.opt_res.columns)
         
+    
     def test_perform_dayahead_forecast_optim(self):
+        # Check formatting of output from dayahead optimization
         self.df_input_data_dayahead = self.fcst.get_load_cost_forecast(self.df_input_data_dayahead)
         self.df_input_data_dayahead = self.fcst.get_prod_price_forecast(self.df_input_data_dayahead)
         self.opt_res_dayahead = self.opt.perform_dayahead_forecast_optim(
@@ -90,7 +107,7 @@ def test_perform_dayahead_forecast_optim(self):
         self.assertIsInstance(self.opt_res_dayahead.index.dtype, pd.core.dtypes.dtypes.DatetimeTZDtype)
         self.assertTrue('cost_fun_'+self.costfun in self.opt_res_dayahead.columns)
         self.assertTrue(self.opt_res_dayahead['P_deferrable0'].sum()*(
-            self.retrieve_hass_conf['freq'].seconds/3600) == self.optim_conf['P_deferrable_nom'][0]*self.optim_conf['def_total_hours'][0])
+            self.retrieve_hass_conf['optimization_time_step'].seconds/3600) == self.optim_conf['nominal_power_of_deferrable_loads'][0]*self.optim_conf['operating_hours_of_each_deferrable_load'][0])
         # Test the battery, dynamics and grid exchange contraints
         self.optim_conf.update({'set_use_battery': True})
         self.optim_conf.update({'set_nocharge_from_grid': True})
@@ -104,7 +121,7 @@ def test_perform_dayahead_forecast_optim(self):
         self.assertIsInstance(self.opt_res_dayahead, type(pd.DataFrame()))
         self.assertTrue('P_batt' in self.opt_res_dayahead.columns)
         self.assertTrue('SOC_opt' in self.opt_res_dayahead.columns)
-        self.assertAlmostEqual(self.opt_res_dayahead.loc[self.opt_res_dayahead.index[-1],'SOC_opt'], self.plant_conf['SOCtarget'])
+        self.assertAlmostEqual(self.opt_res_dayahead.loc[self.opt_res_dayahead.index[-1],'SOC_opt'], self.plant_conf['battery_target_state_of_charge'])
         # Test table conversion
         opt_res = pd.read_csv(emhass_conf['data_path'] / 'opt_res_latest.csv', index_col='timestamp')
         cost_cols = [i for i in opt_res.columns if 'cost_' in i]
@@ -114,40 +131,40 @@ def test_perform_dayahead_forecast_optim(self):
         # Check status
         self.assertTrue('optim_status' in self.opt_res_dayahead.columns)
         # Test treat_def_as_semi_cont and set_def_constant constraints
-        self.optim_conf.update({'treat_def_as_semi_cont': [True, True]})
-        self.optim_conf.update({'set_def_constant': [True, True]})
+        self.optim_conf.update({'treat_deferrable_load_as_semi_cont': [True, True]})
+        self.optim_conf.update({'set_deferrable_load_single_constant': [True, True]})
         self.opt = Optimization(self.retrieve_hass_conf, self.optim_conf, self.plant_conf, 
                                 self.fcst.var_load_cost, self.fcst.var_prod_price,  
                                 self.costfun, emhass_conf, logger)
         self.opt_res_dayahead = self.opt.perform_dayahead_forecast_optim(
             self.df_input_data_dayahead, self.P_PV_forecast, self.P_load_forecast)
         self.assertTrue(self.opt.optim_status == 'Optimal')
-        self.optim_conf.update({'treat_def_as_semi_cont': [False, True]})
-        self.optim_conf.update({'set_def_constant': [True, True]})
+        self.optim_conf.update({'treat_deferrable_load_as_semi_cont': [False, True]})
+        self.optim_conf.update({'set_deferrable_load_single_constant': [True, True]})
         self.opt = Optimization(self.retrieve_hass_conf, self.optim_conf, self.plant_conf, 
                                 self.fcst.var_load_cost, self.fcst.var_prod_price,  
                                 self.costfun, emhass_conf, logger)
         self.opt_res_dayahead = self.opt.perform_dayahead_forecast_optim(
             self.df_input_data_dayahead, self.P_PV_forecast, self.P_load_forecast)
         self.assertTrue(self.opt.optim_status == 'Optimal')
-        self.optim_conf.update({'treat_def_as_semi_cont': [False, True]})
-        self.optim_conf.update({'set_def_constant': [False, True]})
+        self.optim_conf.update({'treat_deferrable_load_as_semi_cont': [False, True]})
+        self.optim_conf.update({'set_deferrable_load_single_constant': [False, True]})
         self.opt = Optimization(self.retrieve_hass_conf, self.optim_conf, self.plant_conf, 
                                 self.fcst.var_load_cost, self.fcst.var_prod_price,  
                                 self.costfun, emhass_conf, logger)
         self.opt_res_dayahead = self.opt.perform_dayahead_forecast_optim(
             self.df_input_data_dayahead, self.P_PV_forecast, self.P_load_forecast)
         self.assertTrue(self.opt.optim_status == 'Optimal')
-        self.optim_conf.update({'treat_def_as_semi_cont': [False, False]})
-        self.optim_conf.update({'set_def_constant': [False, True]})
+        self.optim_conf.update({'treat_deferrable_load_as_semi_cont': [False, False]})
+        self.optim_conf.update({'set_deferrable_load_single_constant': [False, True]})
         self.opt = Optimization(self.retrieve_hass_conf, self.optim_conf, self.plant_conf, 
                                 self.fcst.var_load_cost, self.fcst.var_prod_price,  
                                 self.costfun, emhass_conf, logger)
         self.opt_res_dayahead = self.opt.perform_dayahead_forecast_optim(
             self.df_input_data_dayahead, self.P_PV_forecast, self.P_load_forecast)
         self.assertTrue(self.opt.optim_status == 'Optimal')
-        self.optim_conf.update({'treat_def_as_semi_cont': [False, False]})
-        self.optim_conf.update({'set_def_constant': [False, False]})
+        self.optim_conf.update({'treat_deferrable_load_as_semi_cont': [False, False]})
+        self.optim_conf.update({'set_deferrable_load_single_constant': [False, False]})
         self.opt = Optimization(self.retrieve_hass_conf, self.optim_conf, self.plant_conf, 
                                 self.fcst.var_load_cost, self.fcst.var_prod_price,  
                                 self.costfun, emhass_conf, logger)
@@ -177,7 +194,9 @@ def test_perform_dayahead_forecast_optim(self):
         self.assertTrue('cost_fun_'+self.costfun in self.opt_res_dayahead.columns)
         self.assertTrue(self.opt.optim_status == 'Optimal')
         
-    def test_perform_dayahead_forecast_optim_costfun_selfconso(self):
+    
+    # Check formatting of output from dayahead optimization in self-consumption
+    def test_perform_dayahead_forecast_optim_costfun_selfconsumption(self):
         costfun = 'self-consumption'
         self.opt = Optimization(self.retrieve_hass_conf, self.optim_conf, self.plant_conf, 
                                 self.fcst.var_load_cost, self.fcst.var_prod_price,  
@@ -191,6 +210,7 @@ def test_perform_dayahead_forecast_optim_costfun_selfconso(self):
         self.assertIsInstance(self.opt_res_dayahead.index.dtype, pd.core.dtypes.dtypes.DatetimeTZDtype)
         self.assertTrue('cost_fun_selfcons' in self.opt_res_dayahead.columns)
         
+    # Check formatting of output from dayahead optimization in cost
     def test_perform_dayahead_forecast_optim_costfun_cost(self):
         costfun = 'cost'
         self.opt = Optimization(self.retrieve_hass_conf, self.optim_conf, self.plant_conf, 
@@ -205,10 +225,11 @@ def test_perform_dayahead_forecast_optim_costfun_cost(self):
         self.assertIsInstance(self.opt_res_dayahead.index.dtype, pd.core.dtypes.dtypes.DatetimeTZDtype)
         self.assertTrue('cost_fun_cost' in self.opt_res_dayahead.columns)
         
+    #
     def test_perform_dayahead_forecast_optim_aux(self):
-        self.optim_conf['treat_def_as_semi_cont'] = [False, False]
+        self.optim_conf['treat_deferrable_load_as_semi_cont'] = [False, False]
         self.optim_conf['set_total_pv_sell'] = True
-        self.optim_conf['set_def_constant'] = [True, True]
+        self.optim_conf['set_deferrable_load_single_constant'] = [True, True]
         self.opt = Optimization(self.retrieve_hass_conf, self.optim_conf, self.plant_conf, 
                                 self.fcst.var_load_cost, self.fcst.var_prod_price,  
                                 self.costfun, emhass_conf, logger)
@@ -219,12 +240,13 @@ def test_perform_dayahead_forecast_optim_aux(self):
         self.assertIsInstance(self.opt_res_dayahead, type(pd.DataFrame()))
         self.assertIsInstance(self.opt_res_dayahead.index, pd.core.indexes.datetimes.DatetimeIndex)
         self.assertIsInstance(self.opt_res_dayahead.index.dtype, pd.core.dtypes.dtypes.DatetimeTZDtype)
+        # Test dayahead optimization using different solvers
         import pulp as pl
         solver_list = pl.listSolvers(onlyAvailable=True)
         for solver in solver_list:
             self.optim_conf['lp_solver'] = solver
-            if os.getenv("LP_SOLVER_PATH", default=None) == None:
-                self.optim_conf['lp_solver_path'] = os.getenv("LP_SOLVER_PATH", default=None)            
+            if os.getenv('lp_solver_path', default=None) == None:
+                self.optim_conf['lp_solver_path'] = os.getenv('lp_solver_path', default=None)            
             self.opt = Optimization(self.retrieve_hass_conf, self.optim_conf, self.plant_conf, 
                                     self.fcst.var_load_cost, self.fcst.var_prod_price,  
                                     self.costfun, emhass_conf, logger)
@@ -257,9 +279,10 @@ def test_perform_naive_mpc_optim(self):
         self.assertTrue('P_batt' in self.opt_res_dayahead.columns)
         self.assertTrue('SOC_opt' in self.opt_res_dayahead.columns)
         self.assertTrue(np.abs(self.opt_res_dayahead.loc[self.opt_res_dayahead.index[-1],'SOC_opt']-soc_final)<1e-3)
-        term1 = self.optim_conf['P_deferrable_nom'][0]*def_total_hours[0]
-        term2 = self.opt_res_dayahead['P_deferrable0'].sum()*(self.retrieve_hass_conf['freq'].seconds/3600)
+        term1 = self.optim_conf['nominal_power_of_deferrable_loads'][0]*def_total_hours[0]
+        term2 = self.opt_res_dayahead['P_deferrable0'].sum()*(self.retrieve_hass_conf['optimization_time_step'].seconds/3600)
         self.assertTrue(np.abs(term1-term2)<1e-3)
+        #
         soc_init = 0.8
         soc_final = 0.5
         self.opt_res_dayahead = self.opt.perform_naive_mpc_optim(
@@ -267,6 +290,7 @@ def test_perform_naive_mpc_optim(self):
             soc_init=soc_init, soc_final=soc_final, def_total_hours=def_total_hours, def_start_timestep=def_start_timestep, def_end_timestep=def_end_timestep)
         self.assertAlmostEqual(self.opt_res_dayahead.loc[self.opt_res_dayahead.index[-1],'SOC_opt'], soc_final)
     
+    # Test format output of dayahead optimization with a thermal deferrable load
     def test_thermal_load_optim(self):
         self.df_input_data_dayahead = self.fcst.get_load_cost_forecast(self.df_input_data_dayahead)
         self.df_input_data_dayahead = self.fcst.get_prod_price_forecast(self.df_input_data_dayahead)
@@ -300,15 +324,16 @@ def test_thermal_load_optim(self):
         self.assertTrue('cost_fun_'+self.costfun in self.opt_res_dayahead.columns)
         self.assertTrue(self.opt.optim_status == 'Optimal')
         
+    # Setup function to run dayahead optimization for the following tests
     def run_penalty_test_forecast(self):
         self.opt = Optimization(self.retrieve_hass_conf, self.optim_conf, self.plant_conf,
                                 self.fcst.var_load_cost, self.fcst.var_prod_price,
                                 self.costfun, emhass_conf, logger)
-        def_total_hours = [5 * self.retrieve_hass_conf["freq"].seconds / 3600.0]
+        def_total_hours = [5 * self.retrieve_hass_conf['optimization_time_step'].seconds / 3600.0]
         def_start_timestep = [0]
         def_end_timestep = [0]
         prediction_horizon = 10
-        self.optim_conf.update({"num_def_loads": 1})
+        self.optim_conf.update({'number_of_deferrable_loads': 1})
 
         self.fcst.params["passed_data"]["prod_price_forecast"] = [0 for i in range(prediction_horizon)]
         self.fcst.params["passed_data"]["solar_forecast_kwp"] = [
@@ -333,71 +358,76 @@ def run_penalty_test_forecast(self):
             def_end_timestep=def_end_timestep
         )
 
+    # Test load is constant
     def test_constant_load(self):
         self.fcst.params["passed_data"]["load_cost_forecast"] = [2,1,1,1,1,1.5,1.1,2,2,2]
-        self.optim_conf.update({"set_def_constant": [True]})
+        self.optim_conf.update({'set_deferrable_load_single_constant': [True]})
 
         self.run_penalty_test_forecast()
 
         assert_series_equal(
             self.opt_res_dayahead["P_deferrable0"],
-            self.optim_conf["P_deferrable_nom"][0]
+            self.optim_conf['nominal_power_of_deferrable_loads'][0]
             * pd.Series(
                 [0, 1, 1, 1, 1, 1, 0, 0, 0, 0], index=self.opt_res_dayahead.index
             ),
             check_names=False,
         )
 
+    # Test no startup penalty when bump is small
     def test_startup_penalty_continuous_with_small_bump(self):
         self.fcst.params["passed_data"]["load_cost_forecast"] = [2,1,1,1,1,1.5,1.1,2,2,2]
-        self.optim_conf.update({"def_start_penalty": [100.0]})
+        self.optim_conf.update({'set_deferrable_startup_penalty': [100.0]})
 
         self.run_penalty_test_forecast()
 
         assert_series_equal(
             self.opt_res_dayahead["P_deferrable0"],
-            self.optim_conf["P_deferrable_nom"][0]
+            self.optim_conf['nominal_power_of_deferrable_loads'][0]
             * pd.Series(
                 [0, 1, 1, 1, 1, 1, 0, 0, 0, 0], index=self.opt_res_dayahead.index
             ),
             check_names=False,
         )
 
+    # Test startup penalty
     def test_startup_penalty_discontinuity_when_justified(self):
         self.fcst.params["passed_data"]["load_cost_forecast"] = [2,1,1,1,1,1.5,1.1,2,2,2]
 
-        self.optim_conf.update({"def_start_penalty": [0.1]})
+        self.optim_conf.update({'set_deferrable_startup_penalty': [0.1]})
 
         self.run_penalty_test_forecast()
 
         assert_series_equal(self.opt_res_dayahead["P_deferrable0"],
-                        self.optim_conf["P_deferrable_nom"][0] *
+                        self.optim_conf['nominal_power_of_deferrable_loads'][0] *
                         pd.Series([0, 1, 1, 1, 1, 0, 1, 0, 0, 0],
                                   index=self.opt_res_dayahead.index),
                         check_names=False)
 
+    # Test penalty continuity when deferrable load is already on
     def test_startup_penalty_no_discontinuity_at_start(self):
         self.fcst.params["passed_data"]["load_cost_forecast"] = [1.2,1,1,1,1,1.1,2,2,2,2]
 
         self.optim_conf.update({
-            "def_start_penalty": [100.0],
+            'set_deferrable_startup_penalty': [100.0],
             "def_current_state": [True],
         })
 
         self.run_penalty_test_forecast()
 
         assert_series_equal(self.opt_res_dayahead["P_deferrable0"],
-                        self.optim_conf["P_deferrable_nom"][0] *
+                        self.optim_conf['nominal_power_of_deferrable_loads'][0] *
                         pd.Series([1, 1, 1, 1, 1, 0, 0, 0, 0, 0],
                                   index=self.opt_res_dayahead.index),
                         check_names=False)
 
+    # Test delay start
     def test_startup_penalty_delayed_start(self):
         self.fcst.params["passed_data"]["load_cost_forecast"] = [1.2,1,1,1,1,1.1,2,2,2,2]
 
         self.optim_conf.update(
             {
-                "def_start_penalty": [100.0],
+                'set_deferrable_startup_penalty': [100.0],
                 "def_current_state": [False],
             }
         )
@@ -406,7 +436,7 @@ def test_startup_penalty_delayed_start(self):
 
         assert_series_equal(
             self.opt_res_dayahead["P_deferrable0"],
-            self.optim_conf["P_deferrable_nom"][0]
+            self.optim_conf['nominal_power_of_deferrable_loads'][0]
             * pd.Series(
                 [0, 1, 1, 1, 1, 1, 0, 0, 0, 0], index=self.opt_res_dayahead.index
             ),
diff --git a/tests/test_retrieve_hass.py b/tests/test_retrieve_hass.py
index 3526334c..7980698c 100644
--- a/tests/test_retrieve_hass.py
+++ b/tests/test_retrieve_hass.py
@@ -3,21 +3,26 @@
 
 import datetime
 import unittest
-import requests_mock
+import requests_mock 
 import numpy as np, pandas as pd
-import pytz, pathlib, pickle, json, yaml, copy
+import pytz, pathlib, pickle, json, copy
 import bz2
 import pickle as cPickle
 
+from emhass import utils
 from emhass.retrieve_hass import RetrieveHass
-from emhass.utils import get_root, get_yaml_parse, get_days_list, get_logger
+from emhass.utils import get_yaml_parse, get_days_list, get_logger
 
-# the root folder
-root = str(get_root(__file__, num_parent=2))
+# The root folder
+root = pathlib.Path(utils.get_root(__file__, num_parent=2))
+# Build emhass_conf paths
 emhass_conf = {}
-emhass_conf['config_path'] = pathlib.Path(root) / 'config_emhass.yaml'
-emhass_conf['data_path'] = pathlib.Path(root) / 'data/'
-emhass_conf['root_path'] = pathlib.Path(root) / 'src/emhass/'
+emhass_conf['data_path'] = root / 'data/'
+emhass_conf['root_path'] = root / 'src/emhass/'
+emhass_conf['options_path'] = root / 'options.json'
+emhass_conf['secrets_path'] = root / 'secrets_emhass(example).yaml'
+emhass_conf['defaults_path'] = emhass_conf['root_path']  / 'data/config_defaults.json'
+emhass_conf['associations_path'] = emhass_conf['root_path']  / 'data/associations.csv'
 
 # create logger
 logger, ch = get_logger(__name__, emhass_conf, save_to_file=False)
@@ -27,81 +32,77 @@ class TestRetrieveHass(unittest.TestCase):
     def setUp(self):
         get_data_from_file = True
         save_data_to_file = False
-        params = None
-        retrieve_hass_conf, _, _ = get_yaml_parse(emhass_conf, use_secrets=False)
-        
-        #Force config params for testing
-        retrieve_hass_conf['var_PV'] = 'sensor.power_photovoltaics'
-        retrieve_hass_conf['var_load'] = 'sensor.power_load_no_var_loads'
-        retrieve_hass_conf['var_replace_zero'] = ['sensor.power_photovoltaics']
-        retrieve_hass_conf['var_interp'] = ['sensor.power_photovoltaics','sensor.power_load_no_var_loads'] 
-        
+
+        # Build params with default secrets (no config)
+        if emhass_conf['defaults_path'].exists():   
+            _,secrets = utils.build_secrets(emhass_conf,logger,no_response=True)
+            params =  utils.build_params(emhass_conf,secrets,{},logger)
+            retrieve_hass_conf, _, _ = get_yaml_parse(params,logger)
+        else:
+            raise Exception("config_defaults. does not exist in path: "+str(emhass_conf['defaults_path'] ))
+
+        # Force config params for testing
+        retrieve_hass_conf["optimization_time_step"] = pd.to_timedelta(30, "minutes")
+        retrieve_hass_conf['sensor_power_photovoltaics'] = 'sensor.power_photovoltaics'
+        retrieve_hass_conf['sensor_power_load_no_var_loads'] = 'sensor.power_load_no_var_loads'
+        retrieve_hass_conf['sensor_replace_zero'] = ['sensor.power_photovoltaics']
+        retrieve_hass_conf['sensor_linear_interp'] = ['sensor.power_photovoltaics','sensor.power_load_no_var_loads'] 
+        retrieve_hass_conf['set_zero_min'] = True
+        retrieve_hass_conf['load_negative'] = True
+
         self.retrieve_hass_conf = retrieve_hass_conf
         self.rh = RetrieveHass(self.retrieve_hass_conf['hass_url'], self.retrieve_hass_conf['long_lived_token'], 
-                               self.retrieve_hass_conf['freq'], self.retrieve_hass_conf['time_zone'],
+                               self.retrieve_hass_conf['optimization_time_step'], self.retrieve_hass_conf['time_zone'],
                                params, emhass_conf, logger, get_data_from_file=get_data_from_file)
+        # Obtain sensor values from saved file
         if get_data_from_file:
             with open(emhass_conf['data_path'] / 'test_df_final.pkl', 'rb') as inp:
                 self.rh.df_final, self.days_list, self.var_list = pickle.load(inp)
+        # Else obtain sensor values from HA
         else:
-            self.days_list = get_days_list(self.retrieve_hass_conf['days_to_retrieve'])
-            self.var_list = [self.retrieve_hass_conf['var_load'], self.retrieve_hass_conf['var_PV']]
+            self.days_list = get_days_list(self.retrieve_hass_conf['historic_days_to_retrieve'])
+            self.var_list = [self.retrieve_hass_conf['sensor_power_load_no_var_loads'], self.retrieve_hass_conf['sensor_power_photovoltaics']]
             self.rh.get_data(self.days_list, self.var_list,
                              minimal_response=False, significant_changes_only=False)
+            # Check to save updated data to file 
             if save_data_to_file:
                 with open(emhass_conf['data_path'] / 'test_df_final.pkl', 'wb') as outp:
                     pickle.dump((self.rh.df_final, self.days_list, self.var_list), 
                                 outp, pickle.HIGHEST_PROTOCOL)
         self.df_raw = self.rh.df_final.copy()
         
+    # Check yaml parse in setUp worked
     def test_get_yaml_parse(self):
-        with open(emhass_conf['config_path'], 'r') as file:
-            params = yaml.load(file, Loader=yaml.FullLoader)
-        params.update({
-            'params_secrets': {
-                'hass_url': 'http://supervisor/core/api',
-                'long_lived_token': '${SUPERVISOR_TOKEN}',
-                'time_zone': 'Europe/Paris',
-                'lat': 45.83,
-                'lon': 6.86,
-                'alt': 4807.8
-            }
-            })
-        params = json.dumps(params)
-        retrieve_hass_conf, optim_conf, plant_conf = get_yaml_parse(emhass_conf, 
-                                                                    use_secrets=True, params=params)
-        self.assertIsInstance(retrieve_hass_conf, dict)
-        self.assertTrue('hass_url' in retrieve_hass_conf.keys())
-        self.assertTrue(retrieve_hass_conf['hass_url'] == 'http://supervisor/core/api')
-        self.assertIsInstance(optim_conf, dict)
-        self.assertIsInstance(plant_conf, dict)
+        self.assertIsInstance(self.retrieve_hass_conf, dict)
+        self.assertTrue('hass_url' in self.retrieve_hass_conf.keys())
+        self.assertTrue(self.retrieve_hass_conf['hass_url'] == 'https://myhass.duckdns.org/')
     
-    def test_yaml_parse_wab_server(self):
-        with open(emhass_conf['config_path'], 'r') as file:
-            config = yaml.load(file, Loader=yaml.FullLoader)
-        retrieve_hass_conf = config['retrieve_hass_conf']
-        optim_conf = config['optim_conf']
-        plant_conf = config['plant_conf']
+    # Check yaml parse worked   
+    def test_yaml_parse_web_server(self):
         params = {}
-        params['retrieve_hass_conf'] = retrieve_hass_conf
-        params['optim_conf'] = optim_conf
-        params['plant_conf'] = plant_conf
+        if emhass_conf['defaults_path'].exists():
+                with emhass_conf['defaults_path'].open('r') as data:
+                    defaults = json.load(data)
+                    params.update(utils.build_params(emhass_conf, {}, defaults, logger))
+        _, optim_conf, _ = get_yaml_parse(params,logger)
         # Just check forecast methods
-        self.assertFalse(params['optim_conf'].get('weather_forecast_method') == None)
-        self.assertFalse(params['optim_conf'].get('load_forecast_method') == None)
-        self.assertFalse(params['optim_conf'].get('load_cost_forecast_method') == None)
-        self.assertFalse(params['optim_conf'].get('prod_price_forecast_method') == None)
+        self.assertFalse(optim_conf.get('weather_forecast_method') == None)
+        self.assertFalse(optim_conf.get('load_forecast_method') == None)
+        self.assertFalse(optim_conf.get('load_cost_forecast_method') == None)
+        self.assertFalse(optim_conf.get('production_price_forecast_method') == None)
 
+    # Assume get_data to HA fails
     def test_get_data_failed(self):
         days_list = get_days_list(1)
-        var_list = [self.retrieve_hass_conf['var_load']]
+        var_list = [self.retrieve_hass_conf['sensor_power_load_no_var_loads']]
         response = self.rh.get_data(days_list, var_list)
         self.assertFalse(response)
 
+    # Test with html mock response
     def test_get_data_mock(self):
         with requests_mock.mock() as m:
             days_list = get_days_list(1)
-            var_list = [self.retrieve_hass_conf['var_load']]
+            var_list = [self.retrieve_hass_conf['sensor_power_load_no_var_loads']]
             data = bz2.BZ2File(str(emhass_conf['data_path'] / 'test_response_get_data_get_method.pbz2'), "rb")
             data = cPickle.load(data)
             m.get(self.retrieve_hass_conf['hass_url'], json=data.json())
@@ -112,41 +113,45 @@ def test_get_data_mock(self):
             self.assertIsInstance(self.rh.df_final.index, pd.core.indexes.datetimes.DatetimeIndex)
             self.assertIsInstance(self.rh.df_final.index.dtype, pd.core.dtypes.dtypes.DatetimeTZDtype)
             self.assertEqual(len(self.rh.df_final.columns), len(var_list))
-            self.assertEqual(self.rh.df_final.index.freq, self.retrieve_hass_conf['freq'])
+            self.assertEqual(self.rh.df_final.index.freq, self.retrieve_hass_conf['optimization_time_step'])
             self.assertEqual(self.rh.df_final.index.tz, datetime.timezone.utc)
         
+    
+    # Check the dataframe was formatted correctly
     def test_prepare_data(self):
         self.assertIsInstance(self.rh.df_final, type(pd.DataFrame()))
         self.assertIsInstance(self.rh.df_final.index, pd.core.indexes.datetimes.DatetimeIndex)
         self.assertIsInstance(self.rh.df_final.index.dtype, pd.core.dtypes.dtypes.DatetimeTZDtype)
         self.assertEqual(len(self.rh.df_final.columns), len(self.var_list))
         self.assertEqual(self.rh.df_final.index.isin(self.days_list).sum(), len(self.days_list))
-        self.assertEqual(self.rh.df_final.index.freq, self.retrieve_hass_conf['freq'])
+        self.assertEqual(self.rh.df_final.index.freq, self.retrieve_hass_conf['optimization_time_step'])
         self.assertEqual(self.rh.df_final.index.tz, datetime.timezone.utc)
-        self.rh.prepare_data(self.retrieve_hass_conf['var_load'], 
+        self.rh.prepare_data(self.retrieve_hass_conf['sensor_power_load_no_var_loads'], 
                              load_negative = self.retrieve_hass_conf['load_negative'],
                              set_zero_min = self.retrieve_hass_conf['set_zero_min'], 
-                             var_replace_zero = self.retrieve_hass_conf['var_replace_zero'], 
-                             var_interp = self.retrieve_hass_conf['var_interp'])
+                             var_replace_zero = self.retrieve_hass_conf['sensor_replace_zero'], 
+                             var_interp = self.retrieve_hass_conf['sensor_linear_interp'])
         self.assertIsInstance(self.rh.df_final, type(pd.DataFrame()))
         self.assertEqual(self.rh.df_final.index.isin(self.days_list).sum(), self.df_raw.index.isin(self.days_list).sum())
         self.assertEqual(len(self.rh.df_final.columns), len(self.df_raw.columns))
-        self.assertEqual(self.rh.df_final.index.freq, self.retrieve_hass_conf['freq'])
+        self.assertEqual(self.rh.df_final.index.freq, self.retrieve_hass_conf['optimization_time_step'])
         self.assertEqual(self.rh.df_final.index.tz, self.retrieve_hass_conf['time_zone'])
         
+    # Test negative load 
     def test_prepare_data_negative_load(self):
-        self.rh.df_final[self.retrieve_hass_conf['var_load']] = -self.rh.df_final[self.retrieve_hass_conf['var_load']]
-        self.rh.prepare_data(self.retrieve_hass_conf['var_load'], 
+        self.rh.df_final[self.retrieve_hass_conf['sensor_power_load_no_var_loads']] = -self.rh.df_final[self.retrieve_hass_conf['sensor_power_load_no_var_loads']]
+        self.rh.prepare_data(self.retrieve_hass_conf['sensor_power_load_no_var_loads'], 
                              load_negative = True,
                              set_zero_min = self.retrieve_hass_conf['set_zero_min'], 
-                             var_replace_zero = self.retrieve_hass_conf['var_replace_zero'], 
+                             var_replace_zero = self.retrieve_hass_conf['sensor_replace_zero'], 
                              var_interp = None)
         self.assertIsInstance(self.rh.df_final, type(pd.DataFrame()))
         self.assertEqual(self.rh.df_final.index.isin(self.days_list).sum(), self.df_raw.index.isin(self.days_list).sum())
         self.assertEqual(len(self.rh.df_final.columns), len(self.df_raw.columns))
-        self.assertEqual(self.rh.df_final.index.freq, self.retrieve_hass_conf['freq'])
+        self.assertEqual(self.rh.df_final.index.freq, self.retrieve_hass_conf['optimization_time_step'])
         self.assertEqual(self.rh.df_final.index.tz, self.retrieve_hass_conf['time_zone'])
         
+    # Test publish data
     def test_publish_data(self):
         response, data = self.rh.post_data(self.df_raw[self.df_raw.columns[0]], 
                                            25, 'sensor.p_pv_forecast', "Unit", "Variable",
diff --git a/tests/test_utils.py b/tests/test_utils.py
index bb1ce018..52ba0da5 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -3,73 +3,111 @@
 
 import unittest
 import pandas as pd
-import pathlib, json, yaml, copy
+import pathlib, json
 
 from emhass import utils
 
-# the root folder
-root = str(utils.get_root(__file__, num_parent=2))
+# The root folder
+root = pathlib.Path(utils.get_root(__file__, num_parent=2))
+# Build emhass_conf paths
 emhass_conf = {}
-emhass_conf['config_path'] = pathlib.Path(root) / 'config_emhass.yaml'
-emhass_conf['data_path'] = pathlib.Path(root) / 'data/'
-emhass_conf['root_path'] = pathlib.Path(root) / 'src/emhass/'
+emhass_conf['data_path'] = root / 'data/'
+emhass_conf['root_path'] = root / 'src/emhass/'
+emhass_conf['options_path'] = root / 'options.json'
+emhass_conf['config_path'] = root / 'config.json'
+emhass_conf['secrets_path'] = root / 'secrets_emhass(example).yaml'
+emhass_conf['legacy_config_path'] = pathlib.Path(utils.get_root(__file__, num_parent=1)) / 'config_emhass.yaml' 
+emhass_conf['defaults_path'] = emhass_conf['root_path']  / 'data/config_defaults.json'
+emhass_conf['associations_path'] = emhass_conf['root_path']  / 'data/associations.csv'
 
-# create logger
+# Create logger
 logger, ch = utils.get_logger(__name__, emhass_conf, save_to_file=False)
 
 class TestCommandLineUtils(unittest.TestCase):
     
     @staticmethod
     def get_test_params():
-        with open(emhass_conf['config_path'], 'r') as file:
-            params = yaml.load(file, Loader=yaml.FullLoader)
-        params.update({
-            'params_secrets': {
-                'hass_url': 'http://supervisor/core/api',
-                'long_lived_token': '${SUPERVISOR_TOKEN}',
-                'time_zone': 'Europe/Paris',
-                'lat': 45.83,
-                'lon': 6.86,
-                'alt': 8000.0
-            }
-            })
+        print(emhass_conf['legacy_config_path'])
+        # Build params with default config and secrets
+        if emhass_conf['defaults_path'].exists():
+            config = utils.build_config(emhass_conf,logger,emhass_conf['defaults_path'])
+            _,secrets = utils.build_secrets(emhass_conf,logger,no_response=True)
+            # Add Altitude secret manually for testing get_yaml_parse
+            secrets['Altitude'] = 8000.0
+            params =  utils.build_params(emhass_conf,secrets,config,logger)
+        else:
+            raise Exception("config_defaults. does not exist in path: "+str(emhass_conf['defaults_path'] ))
+                
         return params
 
     def setUp(self):
         params = TestCommandLineUtils.get_test_params()
+        # Add runtime parameters for forecast lists
         runtimeparams = {
-            'pv_power_forecast':[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48],
-            'load_power_forecast':[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48],
-            'load_cost_forecast':[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48],
-            'prod_price_forecast':[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48]
+            'pv_power_forecast':[i+1 for i in range(48)],
+            'load_power_forecast':[i+1 for i in range(48)],
+            'load_cost_forecast':[i+1 for i in range(48)],
+            'prod_price_forecast':[i+1 for i in range(48)]
         }
         self.runtimeparams_json = json.dumps(runtimeparams)
         params['passed_data'] = runtimeparams
         params['optim_conf']['weather_forecast_method'] = 'list'
         params['optim_conf']['load_forecast_method'] = 'list'
         params['optim_conf']['load_cost_forecast_method'] = 'list'
-        params['optim_conf']['prod_price_forecast_method'] = 'list'
+        params['optim_conf']['production_price_forecast_method'] = 'list'
         self.params_json = json.dumps(params)
+
+    def test_build_config(self):
+        # Test building with the different config methods
+        config = {}
+        params = {}
+        # Test with defaults 
+        config = utils.build_config(emhass_conf,logger,emhass_conf['defaults_path'])
+        params = utils.build_params(emhass_conf,{},config,logger)
+        self.assertTrue(params['optim_conf']['lp_solver'] == "default")
+        self.assertTrue(params['optim_conf']['lp_solver_path'] == "empty")
+        self.assertTrue(config['load_peak_hour_periods'] == {'period_hp_1': [{'start': '02:54'}, {'end': '15:24'}], 'period_hp_2': [{'start': '17:24'}, {'end': '20:24'}]})
+        self.assertTrue(params['retrieve_hass_conf']['sensor_replace_zero'] == ['sensor.power_photovoltaics','sensor.power_load_no_var_loads'])
+        # Test with config.json 
+        config = utils.build_config(emhass_conf,logger,emhass_conf['defaults_path'],emhass_conf['config_path'])
+        params = utils.build_params(emhass_conf,{},config,logger)
+        self.assertTrue(params['optim_conf']['lp_solver'] == "default")
+        self.assertTrue(params['optim_conf']['lp_solver_path'] == "empty")
+        # Test with legacy config_emhass yaml
+        config = utils.build_config(emhass_conf,logger,emhass_conf['defaults_path'],legacy_config_path=emhass_conf['legacy_config_path'])
+        params = utils.build_params(emhass_conf,{},config,logger)
+        self.assertTrue(params['retrieve_hass_conf']['sensor_replace_zero'] == ['sensor.power_photovoltaics'])
+        self.assertTrue(config['load_peak_hour_periods'] == {'period_hp_1': [{'start': '02:54'}, {'end': '15:24'}], 'period_hp_2': [{'start': '17:24'}, {'end': '20:24'}]})
+        self.assertTrue(params['plant_conf']['battery_charge_efficiency'] == 0.95)
+
         
     def test_get_yaml_parse(self):
-        retrieve_hass_conf, optim_conf, plant_conf = utils.get_yaml_parse(emhass_conf, use_secrets=False)
+        # Test get_yaml_parse with only secrets
+        params = {}
+        updated_emhass_conf, secrets = utils.build_secrets(emhass_conf,logger)
+        emhass_conf.update(updated_emhass_conf)
+        params.update(utils.build_params(emhass_conf, secrets, {}, logger))
+        retrieve_hass_conf, optim_conf, plant_conf = utils.get_yaml_parse(json.dumps(params),logger)
         self.assertIsInstance(retrieve_hass_conf, dict)
         self.assertIsInstance(optim_conf, dict)
         self.assertIsInstance(plant_conf, dict)
-        self.assertTrue(retrieve_hass_conf['alt'] == 4807.8)
-        retrieve_hass_conf, optim_conf, plant_conf = utils.get_yaml_parse(emhass_conf, use_secrets=True, params=self.params_json)
-        self.assertTrue(retrieve_hass_conf['alt'] == 8000.0)
+        self.assertTrue(retrieve_hass_conf['Altitude'] == 4807.8)
+        # Test get_yaml_parse with built params in get_test_params
+        retrieve_hass_conf, optim_conf, plant_conf = utils.get_yaml_parse(self.params_json,logger)
+        self.assertTrue(retrieve_hass_conf['Altitude'] == 8000.0)
         
     def test_get_forecast_dates(self):
-        retrieve_hass_conf, optim_conf, plant_conf = utils.get_yaml_parse(emhass_conf, use_secrets=True, params=self.params_json)
-        freq = int(retrieve_hass_conf['freq'].seconds/60.0)
-        delta_forecast = int(optim_conf['delta_forecast'].days)
-        forecast_dates = utils.get_forecast_dates(freq, delta_forecast)
+        retrieve_hass_conf, optim_conf, _ = utils.get_yaml_parse(self.params_json,logger)
+        freq = int(retrieve_hass_conf['optimization_time_step'].seconds/60.0)
+        delta_forecast = int(optim_conf['delta_forecast_daily'].days)
+        time_zone = retrieve_hass_conf['time_zone']
+        forecast_dates = utils.get_forecast_dates(freq, delta_forecast, time_zone)
         self.assertIsInstance(forecast_dates, pd.core.indexes.datetimes.DatetimeIndex)
         self.assertTrue(len(forecast_dates)==int(delta_forecast*60*24/freq))
         
     def test_treat_runtimeparams(self):
-        retrieve_hass_conf, optim_conf, plant_conf = utils.get_yaml_parse(emhass_conf, use_secrets=True, params=self.params_json)
+        # Test dayahead runtime params
+        retrieve_hass_conf, optim_conf, plant_conf = utils.get_yaml_parse(self.params_json,logger)
         set_type = 'dayahead-optim'
         params, retrieve_hass_conf, optim_conf, plant_conf = utils.treat_runtimeparams(
             self.runtimeparams_json, self.params_json,
@@ -83,7 +121,8 @@ def test_treat_runtimeparams(self):
         self.assertTrue(optim_conf['weather_forecast_method'] == 'list')
         self.assertTrue(optim_conf['load_forecast_method'] == 'list')
         self.assertTrue(optim_conf['load_cost_forecast_method'] == 'list')
-        self.assertTrue(optim_conf['prod_price_forecast_method'] == 'list')
+        self.assertTrue(optim_conf['production_price_forecast_method'] == 'list')
+        # Test naive MPC runtime params
         set_type = 'naive-mpc-optim'
         params, retrieve_hass_conf, optim_conf, plant_conf = utils.treat_runtimeparams(
             self.runtimeparams_json, self.params_json,
@@ -91,38 +130,22 @@ def test_treat_runtimeparams(self):
         self.assertIsInstance(params, str)
         params = json.loads(params)
         self.assertTrue(params['passed_data']['prediction_horizon'] == 10)
-        self.assertTrue(params['passed_data']['soc_init'] == plant_conf['SOCtarget'])
-        self.assertTrue(params['passed_data']['soc_final'] == plant_conf['SOCtarget'])
-        self.assertTrue(params['passed_data']['def_total_hours'] == optim_conf['def_total_hours'])
-        # This will be the case when using emhass in standalone mode
-        retrieve_hass_conf, optim_conf, plant_conf = utils.get_yaml_parse(emhass_conf, use_secrets=True, params=self.params_json)
-        params = json.dumps(None)
-        params, retrieve_hass_conf, optim_conf, plant_conf = utils.treat_runtimeparams(
-            self.runtimeparams_json, params,
-            retrieve_hass_conf, optim_conf, plant_conf, set_type, logger)
-        self.assertIsInstance(params, str)
-        params = json.loads(params)
-        self.assertIsInstance(params['passed_data']['pv_power_forecast'], list)
-        self.assertIsInstance(params['passed_data']['load_power_forecast'], list)
-        self.assertIsInstance(params['passed_data']['load_cost_forecast'], list)
-        self.assertIsInstance(params['passed_data']['prod_price_forecast'], list)
-        self.assertTrue(optim_conf['weather_forecast_method'] == 'list')
-        self.assertTrue(optim_conf['load_forecast_method'] == 'list')
-        self.assertTrue(optim_conf['load_cost_forecast_method'] == 'list')
-        self.assertTrue(optim_conf['prod_price_forecast_method'] == 'list')
-        # Test passing optimization and plant configuration parameters at runtime 
+        self.assertTrue(params['passed_data']['soc_init'] == plant_conf['battery_target_state_of_charge'])
+        self.assertTrue(params['passed_data']['soc_final'] == plant_conf['battery_target_state_of_charge'])
+        self.assertTrue(params['passed_data']['operating_hours_of_each_deferrable_load'] == optim_conf['operating_hours_of_each_deferrable_load'])     
+        # Test passing optimization and plant configuration parameters at runtime        
         runtimeparams = json.loads(self.runtimeparams_json)
-        runtimeparams.update({'num_def_loads':3})
-        runtimeparams.update({'P_deferrable_nom':[3000.0, 750.0, 2500.0]})
-        runtimeparams.update({'def_total_hours':[5, 8, 10]})
-        runtimeparams.update({'treat_def_as_semi_cont':[True, True, True]})
-        runtimeparams.update({'set_def_constant':[False, False, False]})
+        runtimeparams.update({'number_of_deferrable_loads':3})
+        runtimeparams.update({'nominal_power_of_deferrable_loads':[3000.0, 750.0, 2500.0]})
+        runtimeparams.update({'operating_hours_of_each_deferrable_load':[5, 8, 10]})
+        runtimeparams.update({'treat_deferrable_load_as_semi_cont':[True, True, True]})
+        runtimeparams.update({'set_deferrable_load_single_constant':[False, False, False]})
         runtimeparams.update({'weight_battery_discharge':2.0})
         runtimeparams.update({'weight_battery_charge':2.0})
         runtimeparams.update({'solcast_api_key':'yoursecretsolcastapikey'})
         runtimeparams.update({'solcast_rooftop_id':'yourrooftopid'})
         runtimeparams.update({'solar_forecast_kwp':5.0})
-        runtimeparams.update({'SOCtarget':0.4})
+        runtimeparams.update({'battery_target_state_of_charge':0.4})
         runtimeparams.update({'publish_prefix':'emhass_'})
         runtimeparams.update({'custom_pv_forecast_id':'my_custom_pv_forecast_id'})
         runtimeparams.update({'custom_load_forecast_id':'my_custom_load_forecast_id'})
@@ -134,11 +157,10 @@ def test_treat_runtimeparams(self):
         runtimeparams.update({'custom_unit_load_cost_id':'my_custom_unit_load_cost_id'})
         runtimeparams.update({'custom_unit_prod_price_id':'my_custom_unit_prod_price_id'})
         runtimeparams.update({'custom_deferrable_forecast_id':'my_custom_deferrable_forecast_id'})
-        runtimeparams_json = json.dumps(runtimeparams)
-        retrieve_hass_conf, optim_conf, plant_conf = utils.get_yaml_parse(emhass_conf, use_secrets=True, params=self.params_json)
+        retrieve_hass_conf, optim_conf, plant_conf = utils.get_yaml_parse(self.params_json,logger)
         set_type = 'dayahead-optim'
         params, retrieve_hass_conf, optim_conf, plant_conf = utils.treat_runtimeparams(
-            runtimeparams_json, self.params_json,
+            runtimeparams, self.params_json,
             retrieve_hass_conf, optim_conf, plant_conf, set_type, logger)
         self.assertIsInstance(params, str)
         params = json.loads(params)
@@ -146,17 +168,17 @@ def test_treat_runtimeparams(self):
         self.assertIsInstance(params['passed_data']['load_power_forecast'], list)
         self.assertIsInstance(params['passed_data']['load_cost_forecast'], list)
         self.assertIsInstance(params['passed_data']['prod_price_forecast'], list)
-        self.assertTrue(optim_conf['num_def_loads'] == 3)
-        self.assertTrue(optim_conf['P_deferrable_nom'] == [3000.0, 750.0, 2500.0])
-        self.assertTrue(optim_conf['def_total_hours'] == [5, 8, 10])
-        self.assertTrue(optim_conf['treat_def_as_semi_cont'] == [True, True, True])
-        self.assertTrue(optim_conf['set_def_constant'] == [False, False, False])
+        self.assertTrue(optim_conf['number_of_deferrable_loads'] == 3)
+        self.assertTrue(optim_conf['nominal_power_of_deferrable_loads'] == [3000.0, 750.0, 2500.0])
+        self.assertTrue(optim_conf['operating_hours_of_each_deferrable_load'] == [5, 8, 10])
+        self.assertTrue(optim_conf['treat_deferrable_load_as_semi_cont'] == [True, True, True])
+        self.assertTrue(optim_conf['set_deferrable_load_single_constant'] == [False, False, False])
         self.assertTrue(optim_conf['weight_battery_discharge'] == 2.0)
         self.assertTrue(optim_conf['weight_battery_charge'] == 2.0)
         self.assertTrue(retrieve_hass_conf['solcast_api_key'] == 'yoursecretsolcastapikey')
         self.assertTrue(retrieve_hass_conf['solcast_rooftop_id'] == 'yourrooftopid')
         self.assertTrue(retrieve_hass_conf['solar_forecast_kwp'] == 5.0)
-        self.assertTrue(plant_conf['SOCtarget'] == 0.4)
+        self.assertTrue(plant_conf['battery_target_state_of_charge'] == 0.4)
         self.assertTrue(params['passed_data']['publish_prefix'] == 'emhass_')
         self.assertTrue(params['passed_data']['custom_pv_forecast_id'] == 'my_custom_pv_forecast_id')
         self.assertTrue(params['passed_data']['custom_load_forecast_id'] == 'my_custom_load_forecast_id')
@@ -170,6 +192,7 @@ def test_treat_runtimeparams(self):
         self.assertTrue(params['passed_data']['custom_deferrable_forecast_id'] == 'my_custom_deferrable_forecast_id')
     
     def test_treat_runtimeparams_failed(self):
+        # Test treatment of nan values
         params = TestCommandLineUtils.get_test_params()
         runtimeparams = {
             'pv_power_forecast':[1,2,3,4,5,'nan',7,8,9,10],
@@ -177,24 +200,22 @@ def test_treat_runtimeparams_failed(self):
             'load_cost_forecast':[1,2,3,4,5,6,7,8,'nan',10],
             'prod_price_forecast':[1,2,3,4,'nan',6,7,8,9,10]
         }
-        runtimeparams_json = json.dumps(runtimeparams)
         params['passed_data'] = runtimeparams
         params['optim_conf']['weather_forecast_method'] = 'list'
         params['optim_conf']['load_forecast_method'] = 'list'
         params['optim_conf']['load_cost_forecast_method'] = 'list'
-        params['optim_conf']['prod_price_forecast_method'] = 'list'
-        params_json = json.dumps(params)
-        retrieve_hass_conf, optim_conf, plant_conf = utils.get_yaml_parse(emhass_conf, use_secrets=True, params=params_json)
+        params['optim_conf']['production_price_forecast_method'] = 'list'
+        retrieve_hass_conf, optim_conf, plant_conf = utils.get_yaml_parse(params,logger)
         set_type = 'dayahead-optim'
         params, retrieve_hass_conf, optim_conf, plant_conf = utils.treat_runtimeparams(
-            runtimeparams_json, params_json,
+            runtimeparams, params,
             retrieve_hass_conf, optim_conf, plant_conf, set_type, logger)
-        params = json.loads(params)
-        runtimeparams = json.loads(runtimeparams_json)
+        
         self.assertTrue(len([x for x in runtimeparams['pv_power_forecast'] if not str(x).isdigit()])>0)
         self.assertTrue(len([x for x in runtimeparams['load_power_forecast'] if not str(x).isdigit()])>0)
         self.assertTrue(len([x for x in runtimeparams['load_cost_forecast'] if not str(x).isdigit()])>0)
         self.assertTrue(len([x for x in runtimeparams['prod_price_forecast'] if not str(x).isdigit()])>0)
+        # Test list embedded into a string
         params = TestCommandLineUtils.get_test_params()
         runtimeparams = {
             'pv_power_forecast':'[1,2,3,4,5,6,7,8,9,10]',
@@ -202,56 +223,84 @@ def test_treat_runtimeparams_failed(self):
             'load_cost_forecast':'[1,2,3,4,5,6,7,8,9,10]',
             'prod_price_forecast':'[1,2,3,4,5,6,7,8,9,10]'
         }
-        runtimeparams_json = json.dumps(runtimeparams)
         params['passed_data'] = runtimeparams
         params['optim_conf']['weather_forecast_method'] = 'list'
         params['optim_conf']['load_forecast_method'] = 'list'
         params['optim_conf']['load_cost_forecast_method'] = 'list'
-        params['optim_conf']['prod_price_forecast_method'] = 'list'
-        params_json = json.dumps(params)
-        retrieve_hass_conf, optim_conf, plant_conf = utils.get_yaml_parse(emhass_conf, use_secrets=True, params=params_json)
+        params['optim_conf']['production_price_forecast_method'] = 'list'
+        retrieve_hass_conf, optim_conf, plant_conf = utils.get_yaml_parse(params,logger)
         set_type = 'dayahead-optim'
         params, retrieve_hass_conf, optim_conf, plant_conf = utils.treat_runtimeparams(
-            runtimeparams_json, params_json,
+            runtimeparams, params,
+            retrieve_hass_conf, optim_conf, plant_conf, set_type, logger)
+        self.assertIsInstance(runtimeparams['pv_power_forecast'], list)
+        self.assertIsInstance(runtimeparams['load_power_forecast'], list)
+        self.assertIsInstance(runtimeparams['load_cost_forecast'], list)
+        self.assertIsInstance(runtimeparams['prod_price_forecast'], list)
+        # Test string of numbers
+        params = TestCommandLineUtils.get_test_params()
+        runtimeparams = {
+            'pv_power_forecast':'1,2,3,4,5,6,7,8,9,10',
+            'load_power_forecast':'1,2,3,4,5,6,7,8,9,10',
+            'load_cost_forecast':'1,2,3,4,5,6,7,8,9,10',
+            'prod_price_forecast':'1,2,3,4,5,6,7,8,9,10'
+        }
+        params['passed_data'] = runtimeparams
+        params['optim_conf']['weather_forecast_method'] = 'list'
+        params['optim_conf']['load_forecast_method'] = 'list'
+        params['optim_conf']['load_cost_forecast_method'] = 'list'
+        params['optim_conf']['production_price_forecast_method'] = 'list'
+        retrieve_hass_conf, optim_conf, plant_conf = utils.get_yaml_parse(params,logger)
+        set_type = 'dayahead-optim'
+        params, retrieve_hass_conf, optim_conf, plant_conf = utils.treat_runtimeparams(
+            runtimeparams, params,
             retrieve_hass_conf, optim_conf, plant_conf, set_type, logger)
-        params = json.loads(params)
-        runtimeparams = json.loads(runtimeparams_json)
         self.assertIsInstance(runtimeparams['pv_power_forecast'], str)
         self.assertIsInstance(runtimeparams['load_power_forecast'], str)
         self.assertIsInstance(runtimeparams['load_cost_forecast'], str)
         self.assertIsInstance(runtimeparams['prod_price_forecast'], str)
     
-    def test_build_params(self):
-        with open(emhass_conf['config_path'], 'r') as file:
-            config = yaml.load(file, Loader=yaml.FullLoader)
-        retrieve_hass_conf = config['retrieve_hass_conf']
-        optim_conf = config['optim_conf']
-        plant_conf = config['plant_conf']
-        params = {}
-        params['retrieve_hass_conf'] = retrieve_hass_conf
-        params['optim_conf'] = optim_conf
-        params['plant_conf'] = plant_conf
-        options_json = emhass_conf['config_path'].parent / "options.json"
-        # Read options info
-        with options_json.open('r') as data:
-            options = json.load(data)
-        with open(emhass_conf['config_path'].parent / "secrets_emhass(example).yaml", 'r') as file:
-            params_secrets = yaml.load(file, Loader=yaml.FullLoader)
-        addon = 1
-        params = utils.build_params(params, params_secrets, options, addon, logger)
+    def test_build_secrets(self):
+        # Test the build_secrets defaults from get_test_params()
+        params = TestCommandLineUtils.get_test_params()
         expected_keys = ['retrieve_hass_conf', 'params_secrets', 'optim_conf', 'plant_conf', 'passed_data']
         for key in expected_keys:
             self.assertTrue(key in params.keys())
-        self.assertTrue(params['params_secrets']['time_zone'] == "Europe/Paris")
+        self.assertTrue(params['retrieve_hass_conf']['time_zone'] == "Europe/Paris") 
+        self.assertTrue(params['retrieve_hass_conf']['hass_url'] == "https://myhass.duckdns.org/")
+        self.assertTrue(params['retrieve_hass_conf']['long_lived_token'] == "thatverylongtokenhere")      
+        # Test Secrets from options.json
+        params = {}
+        secrets = {}
+        _, secrets = utils.build_secrets(emhass_conf,logger,options_path=emhass_conf["options_path"],secrets_path="",no_response=True)
+        params = utils.build_params(emhass_conf, secrets, {}, logger)
+        for key in expected_keys:
+            self.assertTrue(key in params.keys())
+        self.assertTrue(params['retrieve_hass_conf']['time_zone'] == "Europe/Paris")
+        self.assertTrue(params['retrieve_hass_conf']['hass_url'] == "https://myhass.duckdns.org/")
+        self.assertTrue(params['retrieve_hass_conf']['long_lived_token'] == "thatverylongtokenhere")
+        # Test Secrets from secrets_emhass(example).yaml
         params = {}
-        params['retrieve_hass_conf'] = retrieve_hass_conf
-        params['optim_conf'] = optim_conf
-        params['plant_conf'] = plant_conf
-        addon = 0
-        params = utils.build_params(params, params_secrets, options, addon, logger)
+        secrets = {}
+        _, secrets = utils.build_secrets(emhass_conf,logger,secrets_path=emhass_conf["secrets_path"])
+        params = utils.build_params(emhass_conf, secrets, {}, logger)
         for key in expected_keys:
             self.assertTrue(key in params.keys())
-        self.assertTrue(params['params_secrets']['time_zone'] == "Europe/Paris")
+        self.assertTrue(params['retrieve_hass_conf']['time_zone'] == "Europe/Paris")
+        self.assertTrue(params['retrieve_hass_conf']['hass_url'] == "https://myhass.duckdns.org/")
+        self.assertTrue(params['retrieve_hass_conf']['long_lived_token'] == "thatverylongtokenhere")
+        # Test Secrets from arguments (command_line cli)
+        params = {}
+        secrets = {}
+        _, secrets = utils.build_secrets(emhass_conf,logger,{"url":"test.url", "key":"test.key" },secrets_path="")
+        logger.debug("Obtaining long_lived_token from passed argument") 
+        params = utils.build_params(emhass_conf, secrets, {}, logger)
+        for key in expected_keys:
+            self.assertTrue(key in params.keys())
+        self.assertTrue(params['retrieve_hass_conf']['time_zone'] == "Europe/Paris")
+        self.assertTrue(params['retrieve_hass_conf']['hass_url'] == "test.url")
+        self.assertTrue(params['retrieve_hass_conf']['long_lived_token'] == "test.key")
+        
         
 if __name__ == '__main__':
     unittest.main()