Brennenstuhl Floodlight WF2050P PIR and ambient light sensor - others?

Hey I wanted to share my ESPhome configuration for floodlight.

To begin with previously I was not able to find any floodlight that is sealable after playing around with its guts but now I have found one. It is the Brennenstuhl WF2050P, and it requires removing 4 rubbber cover and 4 screws from top and you are good to go (but You do not have to if you manage to “cloud cut” it) with ESPhome (since libreTiny branch was merged).

This one is not cheapest one but …
Anyway next problem was to template chip. I believe there is space for improvement, so feel free to adjust my code.
My aim was to provide same capabilities as regular app assuming it should be still functional without connection to HA … and I have added few switches to ease adjusting for You use-case.

So:
Action mode: self explanatory
Action mode fallback: time after which manual mode will switch to auto - You probably will forget abut that after party .
Ambient light threshold: level of ADC input used by ambient sensor to determine light conditions - you can tweak voltage levels on code level
Luminance: RAW value of ADC voltage corresponding to current light conditions - i do not know which sensor is used so i did not bother to convert it to lux since it would require me to guess.
Duration after movement: how long led will be on after detecting movement - during being on if PIR sensor will be triggered duration time will reset to this value.
Motion delayed PIR: this is variable responsible directly for switching light
Motion sensor: this is sensor connected directly to Your PIR - You can use it to adjust PIR sensivity
PIR sensitivity: this is PWM output that determines how sensitive PIR is - you can tweak voltage levels on code level
Set Automatic manual mode: if this is on it will switch manual mode if you manually enable light without PIR being True
Set PIR based fallback to auto mode: if this is True it light will switch it mode from manual to auto if PIR is triggered
Set Time based fallback to auto mode: if this is True It will automatically switch to auto mode after being in manual mode for time stated in Action mode fallback, this option is set in code to default as it will be probably most commonly used.

WF2050P Floodlight - light itself
WF2050P WiFi Signal Sensor - I left this because i may have to add external antenna at one point.

If you want to you can dig into code I have added ## adjust.. comment in potentially every place that regular users may be interested.
Probably you will want to turn off loggers.

It should be easy adapting this to any other tuya light only differences probably will be light type and PWM output for PIR adjustment.

Edit:

  1. If You are all new to this my post is explaining how to handle #placeyours marked values. Also it contains guide on how to flash it by wire but instead of using ESPhome web flasher refer to next point.

  2. Also as @amphibitus pointed out in his post to be able to flash esphome for the first time by wire, you will have to use Itchiptool.

esphome:
  name: brennenstuhl-floodlight-wf2050p
  friendly_name: Brennenstuhl Floodlight WF2050P

bk72xx:
  board: wb3s
  framework:
    version: latest
# Enable logging
logger:

# Enable Home Assistant API
api:
  encryption:
    key: #placeyours

ota:
  password: #placeyours

web_server:
  port: 80
  version: 2
  include_internal: true
  ota: true

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

  # Enable fallback hotspot in case wifi connection fails
  ap:
    ssid: "Brennenstuhl-Floodlight-Wf2050P"
    password: "6OcBkZ0R8NqH"

globals:
  - id: ambientmapper
    type: float
    restore_value: yes
    initial_value: "1.8" ## adjust default fallback time should be same as initial selector 

  - id: autofallbackglob
    type: int
    restore_value: yes
    initial_value: "3600" ## adjust default fallback time to automode

  - id: leddurationglob
    type: int
    restore_value: yes
    initial_value: "60"    ## adjust default led duration in automode

button:
  - platform: restart
    name: "WF2050P restart"


number:
  - platform: template
    name: Action mode fallback
    id: autofallback
    unit_of_measurement: s
    min_value: 0
    max_value: 86400
    step: 1
    icon: "mdi:timer-refresh-outline"
    optimistic: true
    restore_value: true
    on_value:
      then:
        - globals.set:
            id: autofallbackglob
            value: !lambda 'return x;'

  - platform: template
    name: Duration after movement
    id: ledduration
    unit_of_measurement: s
    icon: "mdi:timer-play-outline"
    min_value: 0
    max_value: 3600
    step: 1
    restore_value: true
    optimistic: true
    on_value:
      then:
        - globals.set:
            id: leddurationglob
            value: !lambda 'return x;'
    
output:
  - platform: libretiny_pwm
    id: lightbri
    pin: P8
    frequency: 1000 Hz

  - platform: libretiny_pwm
    id: lightct
    pin: P9
    frequency: 1000 Hz
    inverted: true

  - platform: libretiny_pwm
    id: PIRsen
    frequency: 1000 Hz
    pin: P26

select:
  - platform: template
    name: PIR sensitivity
    id: PIRsenMode
    options:
      - "Far"
      - "Medium"
      - "Close"
    initial_option: "Medium"
    optimistic: true
    icon: "mdi:signal-distance-variant"
    restore_value: True
    ## adjust below values of PIR sensitivity
    on_value:
      then:
        - lambda: |-
            if (id(PIRsenMode).state == "Far") {
              id(PIRsen).set_level(0.00); 
            } else if (id(PIRsenMode).state == "Medium") {
              id(PIRsen).set_level(0.50);
            } else if (id(PIRsenMode).state == "Close") {
              id(PIRsen).set_level(1.00);
            }

  - platform: template
    name: Ambient light threshold
    id: ambientLight
    options:
      - "Pitch Black"
      - "Semi Dark"
      - "Moderate"
      - "Semi Bright"
      - "Day"
    initial_option: "Moderate"
    optimistic: true
    icon: "mdi:theme-light-dark"
    restore_value: True
    ## adjust below values ambient light trheshold
    on_value:
      then:
        - if:
            condition:
                lambda: 'return id(ambientLight).state == "Pitch Black";'
            then:
              - globals.set:
                  id: ambientmapper
                  value: '3.6'
              - logger.log:
                  format: "Ambient value set to %.2f"
                  args: [ 'id(ambientmapper)']
        - if:
            condition:
                lambda: 'return id(ambientLight).state == "Semi Dark";'
            then:
              - globals.set:
                  id: ambientmapper
                  value: '2.7'
              - logger.log:
                  format: "Ambient value set to %.2f"
                  args: [ 'id(ambientmapper)']
        - if:
            condition:
                lambda: 'return id(ambientLight).state == "Moderate";'
            then:
              - globals.set:
                  id: ambientmapper
                  value: '1.8'
              - logger.log:
                  format: "Ambient value set to %.2f"
                  args: [ 'id(ambientmapper)']
        - if:
            condition:
                lambda: 'return id(ambientLight).state == "Semi Bright";'
            then:
              - globals.set:
                  id: ambientmapper
                  value: '0.9'
              - logger.log:
                  format: "Ambient value set to %.2f"
                  args: [ 'id(ambientmapper)']
        - if:
            condition:
                lambda: 'return id(ambientLight).state == "Day";'
            then:
              - globals.set:
                  id: ambientmapper
                  value: '0.01'
              - logger.log:
                  format: "Ambient value set to %.2f"
                  args: [ 'id(ambientmapper)']

  - platform: template
    name: Action mode
    id: actionmode
    options:
      - "manual"
      - "auto"
    initial_option: "auto"
    optimistic: true
    restore_value: True
    on_value:
      then:
        - if:
            condition:
              lambda: |-
                if ( id(actionmode).state == "manual" & id(timeautofallback).state ) {
                  return true;
                } else {
                  return false;
                }                
            then:
              - logger.log:
                  format: "Manual mode set, will get into auto mode after %i s"
                  args: [ 'id(autofallbackglob)']
              - delay: !lambda 'return id(autofallbackglob) * 1000;'  # how long it will take befoore falling back to auto mode works only from HA
              - select.set:
                  id: actionmode
                  option: "auto"
              - logger.log:
                  format: "Autmoaticaly got back to auto mode after time set"

sensor:
  - platform: adc
    id: luval
    pin: ADC3
    name: "Luminance"
    update_interval: 10s
    internal: true
    accuracy_decimals: 2

  - platform: wifi_signal
    name: "WF2050P WiFi Signal Sensor"
    update_interval: 60s
    icon: "md:signal-variant"

binary_sensor:
  - platform: gpio
    name: Motion sensor
    pin: P6
    internal: True
    id: motionsensor
    on_press:
      then:
        - if:
            condition:
              lambda: |-
                if ( id(pirautofallback).state & id(actionmode).state == "manual") {
                  return true;
                } else {
                  return false;
                }
            then:
              - logger.log:
                  format: "Automatically switched back to autom mode according to setting"
              - select.set:
                  id: actionmode
                  option: "auto"



  - platform: template
    internal: true
    name: Motion delayed PIR
    id: motiondelayed
    lambda: |-
      return id(motionsensor).state;
    filters:
      - delayed_off: !lambda 'return id(leddurationglob)*1000;' # Sensor goes to OFF state when this time has passed after last true ON
    on_press:
      then:
        - if:
            condition:
              lambda: |-
                if (id(actionmode).state == "auto" & id(ambientmapper) <=  id(luval).state) {
                  return true;
                } else {
                  return false;
                }
            then:
              - light.turn_on:
                  id: wf2050p
              - logger.log:
                  format: "AmbientLightThreshold %.2f was lower than current ADC illumination %.2f"
                  args: [ 'id(ambientmapper)', 'id(luval).state']
            else:
              - logger.log:
                  format: "AmbientLightThreshold %.2f was higher than current ADC illumination %.2f "
                  args: [ 'id(ambientmapper)', 'id(luval).state']
              
    on_release:
      then:
        - light.turn_off:
            id: wf2050p



light:
  - platform: color_temperature
    name: "WF2050P"
    icon: "mdi:light-flood-down"
    id: wf2050p
    color_temperature: lightct
    brightness: lightbri
    cold_white_color_temperature: 6500 K
    warm_white_color_temperature: 3000 K

## Comment below section if You want to use Light with outside automations. 

    on_turn_on:
      then: 
        - if:
            condition:
              lambda: |-
                if (id(actionmode).state == "auto" & not id(motionsensor).state & id(automanual).state ) {
                  return true;
                } else {
                  return false;
                }
            then:
              - select.set:
                  id: actionmode
                  option: "manual"
              - logger.log:
                  format: "Switched to manual mode since PIR was not active during enablling LED"


switch: 
# Chooser time based fallback to auto mode
  - platform: template
    name: "Set Time based fallback to auto mode"
    id: timeautofallback
    optimistic: true
    restore_mode: RESTORE_DEFAULT_ON # adjust default state 


# Chooser PIR based fallback to auto mode
  - platform: template
    name: "Set PIR based fallback to auto mode"
    optimistic: true
    id: pirautofallback
   # restore_mode: RESTORE_DEFAULT_ON # adjust default state 
  

# Chooser Automatic manual mode
  - platform: template
    name: "Set Automatic manual mode"
    optimistic: true
    id: automanual
   # restore_mode: RESTORE_DEFAULT_ON ## adjust default state 
2 Likes

Hi, Great project, but can you explain to us how you flashed the WBs3 or share a suitable link

Ok. Hope You don`t mind if I will write something obvious for You.

  1. I have not checked if it can be done in any wireless way.
  2. It is not that hard if you have basic soldering skills and You have flashed anything by wire. You can follow these steps:
    Preparing firmware:
    • create new project in ESPHome dashboard;
    • copy yaml content to notepad;
    • paste my code;
    • mind swapping values marked #placeyours whit these generated by ESP home dashboard that You have just placed in notepad;
  • click install and choose by wire;
  • at this point ESP will recognize that it will not be compiling FW for regular ESP board so it will have slightly different menu than usually.
  • use https://web.esphome.io/ ltchiptool to install generated file.

I may have not mention something since my path was different - I have initially installed OpenBeken and then migrated wireless to ESPhome.

Mechanical part:

  • remove back mount plate;
  • release white connector with L and N terminals;
  • remove rubber screw covers;
  • unscrew all of them (total of 4);
  • open case (pay attention to rubber sealing);
  • pulling of white connector enables you to put apart top and bottom part a little further;
  • pull two white JST connectors visible on next photo;
  • solder pins as shown in photo (remember to cross TX and RX wires) i have used numbering from datasheet (“Pin No.”).
  • connect it to programmer (leave CEN wire loose for now);
  • I find it rare but for me to work I had to supply other power source (that provided by programmer was not enough) 3V3.
  • to enable programming you will have to short CEN wire to GND while you click connect in Your browser;
  • de-solder put it back together;

Note: if you are worried about losing warranty by soldering use BDM/pogo probes (be advised this is really failure prone solution), and swapping FW probably voids warranty anyway.

Feel free to ask questions, but here are few helpful links:
WB3S Module Datasheet
ESPhome LibreTiny Platform

The hardware part works perfectly!

But the software part doesn’t work for me. After hours of experience I tried “ltchiptool”. And now it was easy! I converted four spotlights and the last one only took me five minutes to fully install.

Here the Way

grafik

→ Manual Download

grafik

→ UF2 package

Download the ltchiptool here:

grafik

Start the Program Select your Com and the “UF2” File and klick to start

Don’t forget the bridge CEN to GND after start

And here is my Hardware.

I still had a suitable plug for 3V and GND lying around, so I only needed to operate TX and RX. I managed the bridge connection with a loose cable (or press the Resetbutton on back).

Thanks, for your work!

So I was right about me not mentioning something caused by taking different path.
I should have figured out it will require using the ltchiptool since I have used it to flash different BK chip on another project.
I am happy that you have managed to make it work and that my trial and error path was useful for someone else.
If You will find any need for code change feel free to point it out.
I will refer to your post in OP.

Thanks for posting about this conversion.
I ordered the same floodlight but I really want to try the cloud cut approach when it arrives since my soldiering skills are subpar.

1 Like

I’d also like to say thanks for this setup. These floodlights aren’t sold here in Australia but I got one from Amazon UK, and flashed it serially with no dramas. It’s working just fine and I’m pleased with the PIR performance - seems adequately sensitive but less prone to false triggering than some I’ve tried before.

1 Like

I am happy to hear that !
Lately I have discovered that after upgrading ESPhome Duration after movement falls back to 0. Did not have time to debug this.

Short update on my cloudcutter approach
I managed to use cloud cutter with the ume-motion-security-light profile.
It showed a kickstarter UI afterwards.


This only allowed me to set my WLAN and do an OTA upate. I needed multiple tries to upload an ota image to get to more or less blank esp home ui.
I flashed it again and now I have a UI that shows this:


There are still some problems:

  • I can only activate the light via the ON switch
  • It still does not show up on my esp home page in home assistant

I’ll keep on investigating.

1 Like

It works :smiley:

I renamed the name to floodlight and flashed it again. It now shows up and after fiddeling with the settings it works.

Many thanks to @Kosy for the initial posting

1 Like

I have a WFD3050P Brennenstuhl Connect WiFi LED Duo floodlight and can confirm that the it works with the cloudcutter approach (I followed this tutorial: UPDATED How To Guide - Tuya CloudCutter with ESPHome LibreTiny - No soldering | digiblurDIY ) and used the ESPHome config at the top.

I can also confirm that it works with Cloudflatter. I followed the guide provided by @Mikael and the ESPHome code from @Kosy!

Many thanks to @Kosy!

One more question:
Is there a way to display sensor status in Home Assistant for other automations?

Sensor → Movement - no movement
You can see this in the web interface.

Just set this

binary_sensor:
  - platform: gpio
    name: Motion sensor
    pin: P6
    internal: True

To False

It works! Thank you!