Control Nibe heat pump using Shelly and SG ready based on Nordpool price

You could use the other aux to utilize the:

Kontakt för aktivering av “extern justering”
En extern kontaktfunktion kan kopplas till F1126 för
ändring av framledningstemperaturen och därmed
ändring av rumstemperaturen.
Då kontakten är sluten ändras temperaturen i °C (om
rumsgivare är ansluten och aktiverad). Om rumsgivare
inte är ansluten eller inte aktiverad ställs önskad föränd-
ring av “temperatur” (förskjutning av värmekurva) med
det antal steg som väljs. Värdet är inställbart mellan -10
och +10 .
■ klimatsystem 1
Kontakten ska vara potentialfri och kopplas in på vald
ingång (meny 5.4, se sida 37) på kopplingsplint X1.
Inställning av värdet på förändringen görs i meny 1.9.2,
“extern justering”

That would give you a bit control.
Or you could disconnect the indoor sensor and just control via a proper set heatcurve and use the sg control

1 Like

Removed the thermometer sensor and started to use SG ready now, since the price went crazy in Sweden I had to fix it fast. Here is my esphome-config if someone is interested, not tested more than a couple of hours. I used an esp32 and a relay.

substitutions:
  name: esp32-sg-ready-bt-proxy-ce2d2c
  friendly_name: SG Ready and Bluetooth Proxy ce2d2c
packages:
  esphome.bluetooth-proxy: github://esphome/bluetooth-proxies/esp32-generic/esp32-generic.yaml@main
esphome:
  name: ${name}
  name_add_mac_suffix: false
  friendly_name: ${friendly_name}
api:
  encryption:
    key: !secret api_encryption_key_esp32-sg-ready-bt-proxy

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  use_address: !secret wifi_use_address_esp32-sg-ready-bt-proxy

sensor:
  - platform: homeassistant
    name: "Electricity price level from Home Assistant"
    entity_id: sensor.electricity_price_level_median_today
    id: electricityPrice
    accuracy_decimals: 0

  - platform: homeassistant
    name: "Temperature from Home Assistant"
    entity_id: sensor.hall_temperatur
    id: averageTemperature

  - platform: duty_time
    id: sg_mode_1_active_time
    name: "Blocked operation active time"
    restore: False

  - platform: dht
    pin: GPIO14
    temperature:
      name: "Hall Temperatur"
      id: current_temperature
    humidity:
      name: "Hall Luftfuktighet"
      id: current_humidity
    update_interval: 60s
    model: AM2302

  - platform: template
    id: sg_mode
    name: "Mode"
    accuracy_decimals: 0
    on_value:
      then:
        - logger.log: "Price: id(electricityPrice), temperature: id(current_temperature)"
        - if:
            condition:
              - lambda: "return x != id(sg_mode_current).state;"
            then:
              - logger.log: "Current mode id(sg_mode_current), change to: id(sg_mode)"
              # If current is Mode 1 blocking and the new is not, then one active times today is done
              - if:
                  condition:
                    - lambda: "return id(sg_mode_current).state == 1;"
                  then:
                    - number.increment: sg_mode_1_active_times_today

              - if:
                  condition:
                    - lambda: "return x == 0;"
                  then:
                    - text_sensor.template.publish:
                        id: sg_mode_text
                        state: "Disabled - Normal operation (0:0)"
                    - switch.turn_off: relaya
                    - switch.turn_off: relayb
                    - sensor.template.publish:
                        id: sg_mode_current
                        state: 0
              - if:
                  condition:
                    - lambda: "return x == 1;"
                  then:
                    - text_sensor.template.publish:
                        id: sg_mode_text
                        state: "Mode 1 - Blocked operation (1:0)"
                    - switch.turn_on: relaya
                    - switch.turn_off: relayb
                    - sensor.template.publish:
                        id: sg_mode_current
                        state: 1
              - if:
                  condition:
                    - lambda: "return x == 2;"
                  then:
                    - text_sensor.template.publish:
                        id: sg_mode_text
                        state: "Mode 2 - Normal operation (0:0)"
                    - switch.turn_off: relaya
                    - switch.turn_off: relayb
                    - sensor.template.publish:
                        id: sg_mode_current
                        state: 2
              - if:
                  condition:
                    - lambda: "return x == 3;"
                  then:
                    - text_sensor.template.publish:
                        id: sg_mode_text
                        state: "Mode 3 - Encouraged operation (0:1)"
                    - switch.turn_off: relaya
                    - switch.turn_on: relayb
                    - sensor.template.publish:
                        id: sg_mode_current
                        state: 3
              - if:
                  condition:
                    - lambda: "return x == 4;"
                  then:
                    - text_sensor.template.publish:
                        id: sg_mode_text
                        state: "Mode 4 - Ordered operation (1:1)"
                    - switch.turn_on: relaya
                    - switch.turn_on: relayb
                    - sensor.template.publish:
                        id: sg_mode_current
                        state: 4

  - platform: template
    name: "Mode Current"
    id: sg_mode_current
    accuracy_decimals: 0
    on_value: 
      then:
        - sensor.duty_time.stop: sg_mode_1_active_time
        - sensor.duty_time.reset: sg_mode_1_active_time
        - if:
            condition:
              - lambda: "return x == 1;"
            then:
              - sensor.duty_time.start: sg_mode_1_active_time


switch:
  - platform: gpio
    pin: GPIO27
    id: relaya
    name: "SG Ready A"
    icon: "mdi:alpha-a-circle"
    restore_mode: ALWAYS_OFF
    inverted: True

  - platform: gpio
    pin: GPIO21
    id: relayb
    name: "SG Ready B"
    icon: "mdi:alpha-b-circle"
    restore_mode: ALWAYS_OFF
    inverted: True

  - platform: template
    name: "Activate"
    id: activate_sg
    optimistic: True
    restore_mode: RESTORE_DEFAULT_OFF
    on_turn_off:
      then:
        - sensor.template.publish:
            id: sg_mode
            state: 0
    on_turn_on:
      then:
        - sensor.template.publish:
            id: sg_mode
            state: 2

  - platform: template
    name: "Override. Blocked operation active time."
    id: sg_mode_1_active_time_override
    optimistic: True
    restore_mode: RESTORE_DEFAULT_OFF

  - platform: template
    name: "Override. Blocked operation active times today."
    id: sg_mode_1_active_times_today_override
    optimistic: True
    restore_mode: RESTORE_DEFAULT_OFF

  - platform: template
    name: "Disable: Mode 3 - Encouraged operation (0:1)"
    id: disable_sg_mode_3
    optimistic: True
    restore_mode: RESTORE_DEFAULT_ON

  - platform: template
    name: "Disable: Mode 4 - Ordered operation (1:1)"
    id: disable_sg_mode_4
    optimistic: True
    restore_mode: RESTORE_DEFAULT_ON

text_sensor:
  - platform: template
    name: "Current SG Mode Sensor"
    id: sg_mode_text
    update_interval: never

number:
  - platform: template
    name: "Minimum Temperature"
    id: minimumTemperature
    initial_value: 18.5
    min_value: 15.0
    max_value: 30.0
    step: 0.5
    optimistic: True
    mode: BOX
    restore_value: True

  - platform: template
    name: "Blocked operation active times today."
    id: sg_mode_1_active_times_today
    initial_value: 0
    min_value: 0
    max_value: 8 # set a higher value to be able to use overrive
    step: 1
    optimistic: True
    mode: SLIDER

time:
  - platform: sntp
    id: sntp_time
    on_time:
      # Every minutes
      - seconds: 0
        minutes: 0,10,20,30,40,50
        then:
          - logger.log: "Price: id(electricityPrice), temperature: id(current_temperature)"
          - lambda: |-
              auto time = id(sntp_time).now();
              auto is_night = (time.hour >= 22 || time.hour < 6);
              auto temperature_threshold = (id(current_temperature).state > id(minimumTemperature).state);
              auto active_time_threshold = (id(sg_mode_1_active_time).state <= 7200.0 || id(sg_mode_1_active_time_override).state);
              auto active_times_today_threshold = (id(sg_mode_1_active_times_today).state < 3 || id(sg_mode_1_active_times_today_override).state);

              if (id(activate_sg).state) {
                switch (int(id(electricityPrice).state)) {
                  case 1:
                    if (!id(disable_sg_mode_4).state && !id(disable_sg_mode_3).state && is_night) {
                        id(sg_mode).publish_state(4);
                    } else if (!id(disable_sg_mode_3).state && is_night) {
                      id(sg_mode).publish_state(3);
                    } else {
                      id(sg_mode).publish_state(2);
                    }
                    break;
                  case 2:
                    if (!id(disable_sg_mode_3).state && is_night) {
                      id(sg_mode).publish_state(3);
                    } else {
                      id(sg_mode).publish_state(2);
                    }
                    break;
                  case 3:
                    id(sg_mode).publish_state(2);
                    break;
                  default:
                    if (temperature_threshold && active_time_threshold && active_times_today_threshold) {
                      id(sg_mode).publish_state(1);
                    } else {
                      id(sg_mode).publish_state(2);
                    }
                }
              } else {
                id(sg_mode).publish_state(0);
              }
      - hours: 5
        minutes: 30
        seconds: 0
        then:
          - logger.log: "Reset SG Mode 1 Active times today"
          - number.set:
              id: sg_mode_1_active_times_today
              value: 0

button:
  - platform: template
    name: "Mode 1 - Blocked operation (1:0)"
    id: sg_mode_1
    on_press:
      then:
        - sensor.template.publish:
            id: sg_mode
            state: 1

  - platform: template
    name: "Mode 2 - Normal operation (0:0)"
    id: sg_mode_2
    on_press:
      then:
        - sensor.template.publish:
            id: sg_mode
            state: 2

  - platform: template
    name: "Mode 3 - Encouraged operation (0:1)"
    id: sg_mode_3
    on_press:
      then:
        - sensor.template.publish:
            id: sg_mode
            state: 3

  - platform: template
    name: "Mode 4 - Ordered operation (1:1)"
    id: sg_mode_4
    on_press:
      then:
        - sensor.template.publish:
            id: sg_mode
            state: 4
# https://www.gridx.ai/knowledge/sg-ready
# AB
# 10 - Mode 1 – Blocked operation (1:0): The operation for the heat pump is blocked for a maximum of two hours per day.
# 00 - Mode 2 – Normal operation (0:0): The heat pump runs in energy-efficient normal mode.
# 01 - Mode 3 – Encouraged operation (0:1): The operation of the heat pump is encouraged to increase electricity consumption for heating and warm water.
# 11 - Mode 4 – Ordered operation (1:1): The heat pump is ordered to run. This state supports two variants which must be adjustable on the controller for different tariff and utilization models:
#           i) the heat pump is switched on
#           ii) the heat pump is switched on AND the warm water temperature is raised
#
#
# The blocking signal (mode 1) is active for at least 10 minutes and can only be reactivated 10 minutes after it was last active.
# The blocking signal (mode 1) is only applied for a maximum of 2 hours.
# The blocking signal (mode 1) is switched no more than 3 times a day.
# As soon as the signal for mode 3 or 4 is set, it remains active for at least 10 minutes and can only be reactivated 10 minutes after it was last active.
2 Likes

Is it possible that the SG ready settings is missing on my Nibe? I can see it at the AUX setting (as a choice), but the settings is missing. My Nibe is F470 and it should be under Heatpump (4.1.5).

Great to fin this thread also. I just fixing my ESP-12F_Relay_X8 | devices.esphome.io board so I can easily set the AUX inputs from HASS on my Nibe F1145 Heatpump.

I currently use the Smart Price Adaption feature of Nibe but is not optimal, for example it doesn’t make use of own solar-PV. So I wanted to test the SG ready feature.

The first step is when to define de SG Ready mode:

  • 1 = Overcapacity
  • 2 = Low price mode
  • 3 = Normal mode
  • 4 = High price mode

I was wondering, what 5 prices levels of Tibber are your referring to @vilhelm.carlsson ? I also have tibber.

@tvds , I was wondering if you have progressed on your findings of the effect of SG ready? You should be able to set the influence of SG ready in the Nibe Menu.

The price levels are defined in tibbers development docs Tibber Developer

Tibber uses last 3 days data, i’m using only todays values and using the median value as reference.

Thanks @vilhelm.carlsson . So you calculate the 5 price levels.

You compare the current nordpool price with what data/prices?

I see that Tibber calculates the price level based on trailing price average (3 days for hourly values and 30 days for daily values).

Would it be possible to get the Tibber PriceLevel and PriceRatingLevel into HASS as en entity? I don’t see them in the integration as entity but when I look at: Tibber - Home Assistant it’s mentioned in the response data of the the ACTION TIBBER.GET_PRICES.

Sorry, I didn’t read you reaction well. I understand it now. You determin the median price of the day and based on that determine the price level.

Managed to get easily the Tibber PriceLevel in by using this tip: Tibber integration - PriceLevel and PriceRatingLevel - #2 by matrover

@Orrpan, I was wondering are you stil using the same code for steering de SG Ready modes 1 to 4 on your Nibe Heatpump?

How is your experience so far?

best regards and thanks

Exactly. The idea by using a daily median was to avoid having a day with no runtime due to it being expensive after a couple of cheep days.