Valor G4 Controller with ESPHome

Total newbie at this stuff but I did a thing and I thought I’d share it with everyone.

I recently had an old fireplace redone and installed a Valor G4 gas powered insert. While the fireplace came with a 433mhz remote control, I wanted to control the fireplace into Home Assistant so I can tie it to automation and sensor monitoring (like put the thermostat in Eco mode when the fireplace is on, or rough calculate gas usage).

I read a number of different articles online about how people made their fireplace smart, going as far to clone their remote using RF receivers and but I wanted something simple and straight forward. I took a ESP8266 and ESPhome and created a configuration that allows me to directly control the fireplace.

This fireplace uses a Mertik Maxitrol fireplace starter that has a 5-pin port on the side of the fireplace that’s used by the wall switch you can buy separately. The wires here are (back to front): common, fire up, on/off, fire down, and a 5th pin with an unknown function (seemingly no voltage). I had hoped the 5th wire would signal if the fireplace was on or off, but that didn’t seem to be the case.


(left side of insert, fireplace control unit, 5pin control port center)

Closing the circuit on these wires has the following functions:

  • On/Off - While the fireplace is off, closing the circuit for longer than 1 second starts the ignition sequence. While the unit is on, doing so will initiate a shutdown sequence. The ignition sequence is 30 seconds long, with 10 seconds of ‘beeps’, followed by 20 seconds where the pilot warms up and the valve fully opens. The shutdown immediate closes the input valve and closes the step motor.
  • Up / Down - Closing the circuit causes the valve step motor to rotate open or close depending on which wire. On this unit, it takes about 9 seconds to go from completely closed to completely open (or vice versa).

By using a 5VDC 4 channel relay and a ESP8266, and a length of security wire, I was able to wire up a controller that I can control through Lovelace.


(Controller unpacked. This is now stuffed into a project box mounted inside a cabinet next to the fireplace.)

For the ESPhome configuration, I tried to keep keep as much of the tracking on the unit itself. Apologies for the spaghetti but it works.

esphome:
  name: fireplace-control
  comment: A controller for the fireplace.
  platform: ESP8266
  board: esp01_1m
  on_boot:
    then:
      # explicitly set the control slider to 0
      - number.set:
          id: flame_control
          value: 0
      # set the status to reboot
      - text_sensor.template.publish:
          id: fireplace_status
          state: 'reboot'
          
# Enable logging
logger:

# Enable Home Assistant API
api:
  services: 
    # this is a debug service in case a state desync between the controller
    # and the fireplace occurs. Doing this resets the controller state
    # back to 'off' without a hard reset.
    - service: controller_reset
      then:
        - switch.turn_off: fire_onoff
        - switch.template.publish:
            id: fireplace_ignition_switch
            state: OFF
        - text_sensor.template.publish:
            id: fireplace_status
            state: 'reseting'
        - lambda: |-
            id(fire_size) = 0;
            id(fire_target) = id(fire_size);
        - number.set:
            id: flame_control
            value: 0
        - logger.log: 
            format: "Control Reset. Size: %i, Target: %i, Control: %i"
            args: ['id(fire_size)', 'id(fire_target)',  'id(flame_control)']
        - text_sensor.template.publish:
            id: fireplace_status
            state: 'reset'
    
    # this service simply turns the fireplace on.
    - service: fireplace_on
      then:
        - switch.turn_on: fireplace_ignition_switch
        
    # this service simply turns the fireplace off.    
    - service: fireplace_off
      then:
        - switch.turn_off: fireplace_ignition_switch
            
    # this service turns the fireplace down to idle.
    - service: fireplace_idle
      then:
        - number.set:
            id: flame_control
            value: 0
            
    # this service turns the fireplace all the way up.
    - service: fireplace_max
      then:
        - number.set:
            id: flame_control
            value: 10
            
ota:
# your info here

wifi:
# your wifi info here

captive_portal:

globals:
  # this variable holds the internally recognized flame height
  - id: fire_size
    type: int
    restore_value: no
    initial_value: "0"
  # this variable holds the requested flame height
  - id: fire_target
    type: int
    restore_value: no
    initial_value: "0"

number:
  # this creates a slider entity to control the flame height,
  # and manages comparisons between current and requested height.
  - platform: template
    name: "Flame Control"
    id: flame_control
    step: 1
    min_value: 0
    max_value: 10
    mode: slider
    optimistic: true
    on_value:
      then:
        - if: #the fireplace is on and the requested height is lower than current height
            condition:
              lambda: 'return (id(fireplace_status).state == "on") && ((id(fire_size) < x));'
            then: # hold the switch while it turns down for the notch difference times 9/8's of a second
              - lambda: 'id(fire_target) = x;'
              - switch.turn_on: fire_up
              - text_sensor.template.publish:
                  id: fireplace_status
                  state: 'increasing'
              - logger.log: 
                  format: "Flame Up Triggered. Target: %i Current: %i"
                  args: [ 'id(fire_target)', 'id(fire_size)' ]
              - delay: !lambda "return abs(id(fire_size) - id(fire_target)) * 9 / 8 * 1000;"
              - lambda: 'id(fire_size) = id(fire_target);'
              - lambda: 'id(flame_control).state = id(fire_size);'
              - switch.turn_off: fire_up
              - text_sensor.template.publish:
                  id: fireplace_status
                  state: 'on'
        - if: #the fireplace is on and the requested height is height than current height
            condition:
              lambda: 'return (id(fireplace_status).state == "on") && ((id(fire_size) > x));'
            then: # hold the switch while it turns up for the notch difference times 9/8's of a second
              - lambda: 'id(fire_target) = x;'
              - switch.turn_on: fire_down
              - text_sensor.template.publish:
                  id: fireplace_status
                  state: 'decreasing'
              - logger.log: 
                  format: "Flame Down Triggered. Target: %i Current: %i"
                  args: [ 'id(fire_target)', 'id(fire_size)' ]
              - delay: !lambda "return abs(id(fire_size) - id(fire_target)) * 10 / 9 * 1000;"
              - lambda: 'id(fire_size) = id(fire_target);'
              - lambda: 'id(flame_control).state = id(fire_size);'
              - switch.turn_off: fire_down
              - text_sensor.template.publish:
                  id: fireplace_status
                  state: 'on'
                  
switch:
  - platform: gpio
    pin: GPIO5   #D1, relay 1
    name: "Fire Up"
    restore_mode: ALWAYS_OFF
    id: fire_up
    internal: true
  - platform: gpio 
    pin: GPIO4  #D2, relay 2
    name: "Fire ON/OFF"
    restore_mode: ALWAYS_OFF
    id: fire_onoff
    internal: true
  - platform: gpio 
    pin: GPIO0  #D3, relay 3
    restore_mode: ALWAYS_OFF
    name: "Fire Down" 
    id: fire_down
    internal: true
    
  - platform: template
    name: "Fireplace Ignition"
    id: fireplace_ignition_switch
    turn_on_action: # when toggled, perform the ignition sequence
      - then:
        - switch.turn_on: fire_onoff
        - text_sensor.template.publish:
            id: fireplace_status
            state: 'initiating'
        - delay: 2s #valor fireplace debounces 2s before reacting
        - switch.turn_off: fire_onoff
        - switch.template.publish:
            id: fireplace_ignition_switch
            state: ON
        - text_sensor.template.publish:
            id: fireplace_status
            state: 'waiting on beeps' 
        - delay: 10s #waiting for beeps
        - text_sensor.template.publish:
            id: fireplace_status
            state: 'pilot on'
        - delay: 20s #waiting for full ignition
        - lambda: |-
            id(fire_size) = 10;
            id(fire_target) = id(fire_size);
        - number.set:
            id: flame_control
            value: 10
        - text_sensor.template.publish:
            id: fireplace_status
            state: 'on'
        - logger.log: 
            format: "Ignited. Current: %i"
            args: ['id(fire_size)' ]
    turn_off_action:
      - then:
        - switch.turn_on: fire_onoff
        - delay: 2s
        - switch.turn_off: fire_onoff
        - text_sensor.template.publish:
            id: fireplace_status
            state: 'turning off'
        - delay: 10s #waiting for turn down
        - switch.template.publish:
            id: fireplace_ignition_switch
            state: OFF
        - lambda: |-
            id(fire_size) = 0;
            id(fire_target) = id(fire_size);
        - number.set:
            id: flame_control
            value: 0
        - logger.log: 
            format: "Extinguished. Current: %i"
            args: ['id(fire_size)' ]
        - text_sensor.template.publish:
            id: fireplace_status
            state: 'off'
            
text_sensor: # this creates a text status to help track the controller state
  - platform: template
    name: "Fireplace Status"
    id: fireplace_status

After hooking it up into HASS, I have three UI items I use to control my fireplace:

Limitations:

  • If you turn on the fireplace using another means (ie the remote control), this controller won’t know that and will desync the states.
  • Doesn’t current work with the wall switch (future planned support).
  • The step motor math isn’t 100% accurate (but it’s close enough).
  • Wish I had a better slider for the flame control.

This was a fun little project based on my experience making a custom sprinkler controller. Hope someone finds it useful!

3 Likes

Hehe… You have a strange definition of simple and straight :slight_smile:
Nice work!

This is great. Thanks so much. My manufacturer is Ortal, and it has the same 5-pin control as you describe (including the unknown last pin).

Have you figured out a way to monitor the state directly from the control unit itself? Have you changed anything since your initial post?

No, I have to assume the state of the fireplace is what the controller thinks it is. No changes since then and it worked through the season without issue, but my wife requested a simpler hardwired button instead, so I switched back to a dumb solution. The only thing I feel like I’m missing is the realtime gas usage estimations.

1 Like

Awesome! I have nearly an identical Valor fireplace insert, ESP board and relay module. However, due to my lack of skill I did not implement the ability to use a slider to control the flame height. My automation simply turns it on to 100% or off. If I want to have the flame height less than 100%, I exposed the switches so I could manually dial it down, which is finicky, to say the least. I may try out your code.

However, I was wondering, did you do anything to account for the fact that the fireplace controller will automatically turn off the flame after 8 hours of inactivity, and the pilot will turn off completely after 7 days of inactivity?

Currently, I have a set of home assistant automations and timers that track inactivity and reset states based on them. Just curious what you are doing.

switch:
  - platform: gpio
    pin: 
      number: D1   #D1
      inverted: true
    name: "Flame Up" 
    id: IN1
    restore_mode: ALWAYS_OFF
    on_turn_on:
     - delay: 12s
     - switch.turn_off: IN1
  - platform: gpio 
    pin: 
      number: D2  #D2
      inverted: true
    name: "IN2"
    id: IN2
    restore_mode: ALWAYS_OFF
    internal: true

  - platform: gpio 
    pin: 
      number: D5  #D5
      inverted: true
    name: "Flame Down" 
    id: IN3
    restore_mode: ALWAYS_OFF
    on_turn_on:
     - delay: 12s
     - switch.turn_off: IN3
    
  - platform: template
    name: "Fireplace Ignition On"
    id: Fireplace_ignition_on
    turn_on_action:
      - then:
        - switch.turn_on: IN1
        - switch.turn_on: IN3
        - switch.template.publish:
            id: Fireplace_ignition_on
            state: ON
        - delay: 2s
        - switch.turn_off: IN1
        - switch.turn_off: IN3
        - switch.template.publish:
            id: Fireplace_ignition_on
            state: OFF

  - platform: template
    name: "Fireplace Ignition Off"
    id: Fireplace_ignition_off
    turn_on_action:
      - then:
        - switch.turn_on: IN1
        - switch.turn_on: IN2
        - switch.turn_on: IN3
        - switch.template.publish:
            id: Fireplace_ignition_off
            state: ON
        - delay: 1.2s
        - switch.turn_off: IN1
        - switch.turn_off: IN2
        - switch.turn_off: IN3
        - switch.template.publish:
            id: Fireplace_ignition_off
            state: OFF