Lucky_G
Published © GPL3+

Converting ceiling lights from halogene lamps to LED

Converting my Artemide Mercury ceiling lights from R7s halogen lamps to LED, adding ESP32 to enable dimming, remote control, etc.

AdvancedShowcase (no instructions)137
Converting ceiling lights from halogene lamps to LED

Things used in this project

Hardware components

Espressif ESP32 Development Board - Developer Edition
Espressif ESP32 Development Board - Developer Edition
×1
Infineon IRF4905PbF
×2

Software apps and online services

Home Assistant
Home Assistant

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)
Drill / Driver, Cordless
Drill / Driver, Cordless

Story

Read more

Schematics

MOSFET LED Dimmer Module

The IRF9Z34N MOSFET is replaced by IRF4905PbF in version 2.0

230VAC Dimmer / Power Sensing Module

Block diagram

Code

ESPhome device YAML

YAML
esphome:
  name: esp32-lamp-dimmer-r
  friendly_name: ESP32 Lamp Dimmer R

  includes:
    - functions.h
 
esp32:
  board: esp32dev
  framework:
    type: arduino

substitutions:
  devicename: Lamp_R


# Enable logging
logger:

# Enable Home Assistant API
api:
  encryption:
    key: "-- replace with your own api key --"

ota:
  - platform: esphome
    password: "-- replace with your own password --"

wifi:
  networks:
  - ssid: !secret wifi_ssid
    password: !secret wifi_password

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Esp32-Lamp-Dimmer-R-Dining-Room"
    password: "YAgJ3Cdta6an"

captive_portal:
    

globals:
  # current power drawn by ledc_output_1 - LED strip
  - id: power_value_1
    type: float
    restore_value: no
    initial_value: "0.0"

  # current power drawn by ledc_output_2 - COB lamps
  - id: power_value_2
    type: float
    restore_value: no
    initial_value: "0.0"

  - id: energy_hourly
    type: float
    restore_value: no
    initial_value: "0.0"
  - id: energy_daily
    type: float
    restore_value: no
    initial_value: "0.0"
  - id: STANDBY_POWER
    type: float
    restore_value: no
    initial_value: '0.5'  #0.5W


# GPIO Pins used:
# D13 - power sense from wall switch
# D25, D26 - PWC outputs
# D23 - One wire
# D2 - builtin LED


binary_sensor:
  - platform: gpio
    pin:
      number: GPIO13
      mode:
        input: true
        pullup: true
      inverted: True
    name: "Power sense 1"
    id: power_sense_1
    filters:
      - delayed_off: 500ms
    on_state:
      then:
        - if:
            condition:
              - binary_sensor.is_on: power_sense_1
            then:
              - script.stop: timer_off
              - light.turn_on:
                  id: light_1
                  brightness: !lambda return (id(light_strip_init_val).state / 100.0); # instead of 100%
              - light.turn_on:
                  id: light_2
                  brightness: !lambda return (id(light_cob_init_val).state / 100.0);
            else:
              - script.execute: timer_off


output:
  - platform: ledc
    pin: GPIO25
    id: ledc_output_1
    frequency: 19531Hz
    #frequency: 9765Hz
  - platform: ledc
    pin: GPIO26
    id: ledc_output_2
    frequency: 19531Hz
    # frequency: 9765Hz
  - platform: template
    id: template_output_1
    type: float
    min_power: 0%
    max_power: 100%
    write_action:
      - number.set:
          id: light_strip_val
          value: !lambda return (state * 100.0);
  - platform: template
    id: template_output_2
    type: float
    min_power: 0%
    max_power: 100%
    write_action:
      - number.set:
          id: light_lamp_val
          value: !lambda return (state * 100.0);


number:
  - platform: template
    name: 'LED strip'
    id: light_strip_val
    max_value: 100.0
    min_value: 0.0
    step: 1
    mode: slider
    internal: True
    initial_value: 0
    optimistic: true
    update_interval: 1s
    #restore_value: true
    on_value:
      #- lambda: id(power_value_1) = x * (id(STRIP_MAX_POWER) / 100.0);   #means that 100% = 38W (100 x (38.0 / 100.0))
      - lambda: id(power_value_1) = led_strip_power(x);
      - sensor.template.publish:
          id: current_power
          state: !lambda 'return (id(power_value_1) + id(power_value_2) + id(STANDBY_POWER));'
      - output.set_level:
          id: ledc_output_1
          level: !lambda "return ledc_val(x);"  # custom function from functions.h

  
  - platform: template
    name: 'LED COB lights'
    id: light_lamp_val
    max_value: 100
    min_value: 0
    step: 1
    mode: slider
    internal: True
    initial_value: 0
    optimistic: true
    #restore_value: false
    on_value:
      #- lambda: id(power_value_2) = x * (id(COB_MAX_POWER) / 100.0);   #means that 100% = 30W
      - lambda: id(power_value_2) = led_cob_power(x);
      - output.set_level:
          id: ledc_output_2
          level: !lambda "return ledc_val(x);"  # custom function from functions.h
      - sensor.template.publish:
          id: current_power
          state: !lambda 'return (id(power_value_1) + id(power_value_2) + id(STANDBY_POWER));'

  - platform: template
    name: 'LED strip power-on value'
    id: light_strip_init_val
    max_value: 100.0
    min_value: 0.0
    step: 1
    unit_of_measurement: "%"
    mode: slider
    optimistic: true
    update_interval: 1s
    restore_value: True
  - platform: template
    name: 'LED COB lights power-on value'
    id: light_cob_init_val
    max_value: 100.0
    min_value: 0.0
    step: 1
    unit_of_measurement: "%"
    mode: slider
    optimistic: true
    update_interval: 1s
    restore_value: True


light:
  - platform: monochromatic
    id: light_1
    name: "LED strip"
    output: template_output_1
    gamma_correct: 1.0
    default_transition_length: 2s
    effects:
      - strobe:
          name: Strobe slow
          colors:
            - state: true
              brightness: 100%
              duration: 20ms
            - state: false
              brightness: 0%
              duration: 250ms
      - strobe:
          name: Strobe fast
          colors:
            - state: true
              brightness: 100%
              duration: 20ms
            - state: false
              brightness: 0%
              duration: 50ms

  - platform: monochromatic
    id: light_2
    name: "COB lights"
    output: template_output_2
    gamma_correct: 1.0
    default_transition_length: 2s
    effects:
      - strobe:
          name: Strobe slow
          colors:
            - state: true
              brightness: 100%
              duration: 20ms
            - state: false
              brightness: 0%
              duration: 250ms
      - strobe:
          name: Strobe fast
          colors:
            - state: true
              brightness: 100%
              duration: 20ms
            - state: false
              brightness: 0%
              duration: 50ms
  

# Use the blue LED on the ESP32 module as a status LED, which will flash if there are warnings (slow) or errors (fast)
status_led:
  pin:
    number: GPIO2

select:
  - platform: template
    name: "Effect"
    options:
      - No effect
      - Strobe slow
      - Strobe fast
    initial_option: No effect
    set_action: 
      then:
        - if:
            condition:
              - lambda: return (x == "No effect");
            then:
              - light.turn_on:
                  id: light_1
                  effect: none
              - light.turn_on:
                  id: light_2
                  effect: none
            else:
              - light.turn_on:
                  id: light_1
                  effect: !lambda return x;
              - light.turn_on:
                  id: light_2
                  effect: !lambda return x;


button:
  - platform: restart
    name: "Restart"
  - platform: template
    name: "L1+"
    id: button_l1_plus
    on_press:
      - light.dim_relative:
          id: light_1
          relative_brightness: 1%
  - platform: template
    name: "L1-"
    id: button_l1_minus
    on_press:
      - light.dim_relative:
          id: light_1
          relative_brightness: -1%
  
  - platform: template
    name: "L2+"
    id: button_l2_plus
    on_press:
      - light.dim_relative:
          id: light_2
          relative_brightness: 1%
  - platform: template
    name: "L2-"
    id: button_l2_minus
    on_press:
      - light.dim_relative:
          id: light_2
          relative_brightness: -1%


one_wire:
  - platform: gpio
    pin: GPIO15

interval:
  - interval: 1min
    then:
      lambda: |-
        id(energy_hourly) += (id(power_value_1) + id(power_value_2) + id(STANDBY_POWER)) / 60.0; 
        id(energy_daily) += (id(power_value_1) + id(power_value_2) + id(STANDBY_POWER)) / 60.0;

        



sensor:
  - platform: template
    id: current_power
    name: "Current Power"
    device_class: power
    update_interval: 30s
    icon: "mdi:flash"
    state_class: "measurement"
    accuracy_decimals: 1
    unit_of_measurement: "W"
    lambda: |-
      return (id(power_value_1) + id(power_value_2) + id(STANDBY_POWER));

  - platform: template
    id: energy_hh
    name: "Energy hourly"
    device_class: energy
    update_interval: 60s
    icon: "mdi:flash"
    state_class: "total_increasing"
    accuracy_decimals: 5
    unit_of_measurement: "kWh"
    lambda: |-
      return (id(energy_hourly) / 1000.0);

  - platform: template
    id: energy_dd
    name: "Energy daily"
    device_class: energy
    update_interval: 60s
    icon: "mdi:flash"
    state_class: "TOTAL_INCREASING"
    accuracy_decimals: 5
    unit_of_measurement: "kWh"
    lambda: |-
      return (id(energy_daily) / 1000.0);

  - platform: dallas_temp
    address: 0x39151cb20164ff28 # replace with address of your sensor
    name: temperature
    update_interval: 60s

  - platform: wifi_signal
    name: ${devicename} wifi signal
    update_interval: 600s

  # human readable uptime sensor output to the text sensor above
  - platform: uptime
    name: ${devicename} Uptime in Days
    id: uptime_sensor_days
    update_interval: 60s
    on_raw_value:
      then:
        - text_sensor.template.publish:
            id: uptime_human
            state: !lambda |-
              int seconds = round(id(uptime_sensor_days).raw_state);
              int days = seconds / (24 * 3600);
              seconds = seconds % (24 * 3600);
              int hours = seconds / 3600;
              seconds = seconds % 3600;
              int minutes = seconds /  60;
              seconds = seconds % 60;
              return (
                (days ? String(days) + "d " : "") +
                (hours ? String(hours) + "h " : "") +
                (minutes ? String(minutes) + "m " : "") +
                (String(seconds) + "s")
              ).c_str();

time:
  - platform: homeassistant
    id: homeassistant_time
    on_time:
    # Every hour at HH:00:00
    - seconds: 0
      minutes: 0
      then:
        lambda: |-
          id(energy_hourly) = 0;
    # Every midnight
    - seconds: 0
      minutes: 0
      hours: 0
      then:
        lambda: |-
          id(energy_daily) = 0;


# Text sensors with general information.
text_sensor:
  # Expose ESPHome version as sensor.
  - platform: version
    name: $devicename Version
  # Expose WiFi information as sensors.
  - platform: wifi_info
    ip_address:
      name: $devicename IP
    bssid:
      name: $devicename BSSID

  # human readable update text sensor from sensor:uptime
  - platform: template
    name: Uptime Human Readable
    id: uptime_human
    icon: mdi:clock-start
  
# script for delayed power off
# when the wall switch is put to OFF position,
# the COBS are turned off immediately,
# the LED strip is dimmed to 20% brightness for 15 seconds,
# then to 5% brightness for 5 seconds,
# and then finally turns off completely
# this allows to leave the room safely with some light
script:
  - id: timer_off
    then:
      - light.turn_off: light_2
      - light.turn_on:
          id: light_1
          brightness: 20%
      - delay: 15s
      - light.turn_on:
          id: light_1
          brightness: 5%
      - delay: 5s
      - light.turn_off: light_1

Custom functions

C/C++
Functions to calculate calibrated LED dimmer output value and power consumption based on current slider value. All the values are based on measurement of a unique device, they will be different for any other device.
float ledc_val(int x) {
	float retval = 0;
	//converts 0-100% to 0.0-1.0 with corrections for low light levels
    switch (x) {
		case 100:
			retval = 1.0;
			break;
		case 99:
			retval = (float) x / 104.0;
			break;
		case 98:
			retval = (float) x / 111.0;
			break;
		case 97:
			retval = (float) x / 120.0;
			break;
		case 96:
			retval = (float) x / 130.0;
			break;
		case 95:
			retval = (float) x / 141.0;
			break;
		case 94:
			retval = (float) x / 151.0;
			break;
		case 93:
			retval = (float) x / 162.0;
			break;
		case 92:
			retval = (float) x / 172.0;
			break;
		case 91:
			retval = (float) x / 182.0;
			break;
		case 68 ... 90:
			retval = (float) x / (374.0 - ((float) (x - 67) * 8.0));
			break;
		case 63 ... 67:
			retval = (float) x / (420.0 - ((float) (x - 62) * 9.0));
			break;
		case 59 ... 62:
			retval = (float) x / (461.0 - ((float) (x - 58) * 10.0));
			break;
		case 50 ... 58:
			retval = (float) x / (561.0 - ((float) (x - 49) * 11.0));
			break;
		case 47 ... 49:
			retval = (float) x / (598.0 - ((float) (x - 46) * 12.0));
			break;
		case 44 ... 46:
			retval = (float) x / (638.0 - ((float) (x - 43) * 13.0));
			break;
		case 42 ... 43:
			retval = (float) x / (667.0 - ((float) (x - 41) * 14.0));
			break;
		case 41:
			retval = (float) x / 668.0;
			break;
		case 40:
			retval = (float) x / 684.0;
			break;
		case 39:
			retval = (float) x / 702.0;
			break;
		case 38:
			retval = (float) x / 722.0;
			break;
		case 37:
			retval = (float) x / 744.0;
			break;
		case 36:
			retval = (float) x / 769.0;
			break;
		case 35:
			retval = (float) x / 797.0;
			break;
		case 1 ... 34:
			retval = (float) x / (float) (1882 - (x * 31));
			break;
		default:
			retval = 0;
			break;
	}
	if (retval > 0.0) {
		//retval += 0.017796832;	//minimal level offset for LEDs to begin shine = 0,018337154 @ 10kHz
		retval += 0.035424837;	//minimal level offset for LEDs to begin shine = 0,018337154 @ 19.531kHz
		//if (x > 1) {
			//retval = retval / 1.017796832;	// @ 10kHz
			retval = retval / 1.035424837; 		// @ 19.531kHz
		//}
	}
	if (retval > 1.0) {
		retval = 1.0;	//minimal level offset for LEDs to begin shine = 0,018337154
	}
	return retval;
}	





int led_strip_power(int x) {
	int retval = 0;
	//converts 0-100% to 0-60W
    switch (x) {
		case 90 ... 100:
			retval = 55;
			break;
		case 80 ... 89:
			retval = ((90 -  x) / 2) + 55;		//=(90-D3)/2+55
			break;
		case 55 ... 79:
			retval = (((x - 55) / 5) * 6) + 34;			//=((D81-55)/5)*6+34
			break;
		case 27 ... 54:
			retval = x - 21;
			break;
		case 26:
			retval = 6;
			break;
		case 23 ... 25:
			retval = 5;
			break;
		case 19 ... 22:
			retval = 4;
			break;
		case 15 ... 18:
			retval = 3;
			break;
		case 10 ... 12:
			retval = 2;
			break;
		case 1 ... 9:
			retval = 1;
			break;
		default:
			retval = 0;
			break;
	}
	return retval;
}


int led_cob_power(int x) {
	int retval = 0;
	//converts 0-100% to 0-60W
    switch (x) {
		case 100:
			retval = 34;
			break;
		case 96 ... 99:
			retval = x - 65;
			break;
		case 94 ... 95:
			retval = x - 64;
			break;
		case 92 ... 93:
			retval = x - 65;
			break;
		case 88 ... 91:
			retval = x - 66;
			break;
		case 85 ... 87:
			retval = x - 65;
			break;
		case 60 ... 84:
			retval = (((x - 60) / 5) * 2) + 9;		//=((D86-60)/5)*2+9
			break;
		case 55 ... 59:
			retval = 8;
			break;
		case 50 ... 54:
			retval = x - 7;
			break;
		case 45 ... 49:
			retval = 6;
			break;
		case 30 ... 44:
			retval = 5;
			break;
		case 20 ... 29:
			retval = 4;
			break;
		case 15 ... 19:
			retval = 3;
			break;
		case 7 ... 14:
			retval = 2;
			break;
		case 1 ... 6:
			retval = 1;
			break;
		default:
			retval = 0;
			break;
	}
	return retval;
}

Credits

Lucky_G
1 project • 3 followers
Contact

Comments

Please log in or sign up to comment.