Mirabella Genio Wi-Fi Nebula and Star Projector (Tuya, BK7231T/W3BS) - ESPHome config

Hi all, I’ve gotten this 95% complete so thought I’d share with the community in case it’s helpful for others. Use it if is, no need to credit me. I’m a new user, I can’t put too many links in the post, apologies for the lack of hyperlinks.

We were gifted one of these star/nebula projectors after my daughter was born to help with sleep (it didn’t help, but nothing did, however she really likes the light now). I recently decided to see if I could flash it with something open source.

Initially I flashed with OpenBeken, following this guide from joelstevens1101 and p.kaczmarek2: elektroda.com. This worked just fine, however I wasn’t happy with the config interface in OBK (I’m a tech enthusiast, not an expert, and OBK is not intuitive or user-friendly - not intended as a criticism, for most open source projects user-friendliness can’t and shouldn’t be put above functionality). So, I instead flashed the device with ESPHome. Thanks to one very helpful redditor, I did this without needing to break out the soldering iron again, instead taking advantage of the OBK API and curl (link to reddit comment: reddit.com)

Using OBK first did give me the advantage of learning the GPIO pin config, as the BK7231 GUI flash tool (on GitHub) can export the Tuya configuration and extract GPIO. Between that and the joelstevens1101 guide I had the info I needed to configure ESPHome.

Some important notes:

  • The laser light and lens motor seem to be powered in series, so that if the motor is off, the laser will light but very dimly. However if the motor is set to about 10% or 20%, the projected lights won’t spin at a noticeable rate while the laser can be powered at full brightness. So with this in mind, I’ve configured ESPHome to drive the motor at minimum speed whenever the laser is enabled. Disabling both the laser and RGB (nebula) light will also disable the motor, as you can hear it spinning and it uses a small amount of power to run. Similarly the motor can’t be enabled while both lights are disabled. Feel free to change this behaviour if you want to.
  • The side button is currently configured to toggle the device on and off. In the future I’d like a long press to toggle the lights and motor, with a short press cycling through some preset scenes. I’ll probably do this in ESPHome even though it’s probably easier in Home Assistant, since I’d prefer the device be functional without a wifi connection. But my daughter wanted her “stars” back, so this will have to wait.
  • By default the RGB, laser, and motor will be active when the device boots. I did this for family/wife approval so the device will “just work” if it was unplugged for some reason. Again, change it if you want.
  • I’m not a software developer or engineer, just an enthusiast. This is probably not bug-free. I welcome feedback and suggestions for improvements.

The .yaml code, not including any of the main config:

### Device Configuration ###

## Substitutions ## 
#  Define GPIO pins for PWM controls and the button LED ring here.
#  Default configuration is based on pinout for W3BS/BK7231T from a Mirabella 
#  Genio Wi-Fi Nebula and Star Projector (purchased 2021). Pinout for your 
#  device may be different.
substitutions:
  PinPWM_R: 'GPIO9'
  PinPWM_G: "GPIO24"
  PinPWM_B: "GPIO26"
  PinPWM_LASER: "GPIO8"
  PinPWM_MOTOR: "GPIO6"
  PinGPIO_BTN_LED: "GPIO7"

## WiFi & network connection diagnostic information ##
#  Generally useful, not always desired. Delete if not.
text_sensor:
  - platform: wifi_info
    ip_address: 
      name: "IP Address"
    ssid:
      name: "SSID"
    mac_address: 
      name: "MAC Address"
sensor:
  - platform: wifi_signal
    name: "WiFi Signal dB"
    id: wifi_signal_db
    update_interval: 60s
    entity_category: "diagnostic"

## Light components ##
#  Note: the motor and laser light are connected in series. If the motor 
#  is disabled, the laser light will be very dim. Enabling the motor  
#  (even at very low power so that the rotation of the projected lights 
#  is too slow to notice) will allow the laser to light at full brightness.
#  The device is configured to ensure the motor is enabled whenever the
#  laser is enabled, and to disabled the motor when both the RGB light and
#  laser are disabled.

light:
## Case button - blue LED ring ##
#  Enabled only when RGB/laser/motor are enabled, this behaviour is controlled 
#  below. By default this light is not exposed to Home Assistant.
- platform: binary
  output: LED_BTN
  id: BTN_LED
  name: "Button LED"
  internal: True # Delete to allow control in Home Assistant

## Galaxy projector - main RGB light ##
- platform: rgb
  id: RGB_GALAXY
  name: "Galaxy Projector"
  red: PWM_RED
  green: PWM_GREEN
  blue: PWM_BLUE
  # When disabling the RGB light, IF laser is disabled, then disable the motor
  on_turn_off: 
    then:
      - if: 
          condition: 
            light.is_off: LIGHT_LASER
          then: 
            - light.turn_off: MOTOR
            - light.turn_off: BTN_LED
          else: 
            - light.turn_off: RGB_GALAXY
  # Enable the button LED when light is enabled
  on_turn_on: 
    then:
      - light.turn_on: BTN_LED

## Galaxy projector - laser light ##
- platform: monochromatic
  id: LIGHT_LASER
  name: "Galaxy Projector Laser"
  output: PWM_LASER
  # When disabling the laser, if RGB is also disabled, then disable the motor
  on_turn_off: 
    then:
      - if: 
          condition: 
            light.is_off: RGB_GALAXY
          then: 
            - light.turn_off: MOTOR
            - light.turn_off: BTN_LED
          else: 
            - light.turn_off: LIGHT_LASER
  # When enabling the laser, enable the motor if it is disabled
  on_turn_on: 
    then:
      - if: 
          condition: 
            - light.is_off: MOTOR
          then:
            - light.control: 
                id: MOTOR
                state: True
                brightness: 40%               
          else:
            - light.turn_on: LIGHT_LASER
      - light.turn_on: BTN_LED


## Galaxy projector - motor control
#  Monochromatic light component is used for ease of control in Home Assistant.
#  I tested Servo Component but found it to be inconsistent and not intuitive
#  with how the motor is intended to be used. Additionally the motor cannot run
#  in reverse, so that functionality isn't required.

- platform: monochromatic
  id: MOTOR
  output: PWM_MOTOR
  name: "Galaxy Projector Motor"
  # When disabling the motor, if the laser is disabled, then disable the motor. 
  # Else, set the motor PWM output power to 0.1.
  # Laser and motor are powered in series. If the motor is disabled, laser output is very dim.
  on_turn_off: 
    then:
      - if: 
          condition:
            light.is_off: LIGHT_LASER
          then: 
            light.turn_off: MOTOR
          else:
            - light.control: 
                id: MOTOR
                state: True
                brightness: 40%
  # When enabling, if both RGB and laser are currently disabled, do not enable the motor.
  # The motor makes an audible noise when running and consumes power, I prefer this doesn't
  # happen. Feel free to disable if you like.
  on_turn_on: 
    then:
      - if: 
          condition:
            and:
              - light.is_off: RGB_GALAXY
              - light.is_off: LIGHT_LASER
          then: 
            - light.turn_off: MOTOR
          else:
            - light.turn_on: MOTOR

## Case Button Configuration ##
#  The button is currently configured to toggle all lights and the motor when pressed. If 
#  Home Assistant is used, the lights will resume their previous state when toggled back 
#  on. 
#  To Do:
#   - Change power toggle to long press
#   - Enable preset scenes when button is short pressed
binary_sensor:
  - platform: gpio
    pin: 
      number: GPIO14
      mode:
        input: True
        #pullup: True
      inverted: True
    id: BTN_SIDE
    internal: True
    on_click: 
      then:
        - if:
            condition:
              or:
                - light.is_on: LIGHT_LASER
                - light.is_on: RGB_GALAXY
            then:
              - light.turn_off: LIGHT_LASER
              - light.turn_off: RGB_GALAXY
              - light.turn_off: MOTOR
              - light.turn_off: BTN_LED
            else:
              Null
        - if:
            condition:
              and:
                - light.is_off: LIGHT_LASER
                - light.is_off: RGB_GALAXY
            then:
              - light.turn_on: MOTOR
              - light.turn_on: RGB_GALAXY
              - light.turn_on: LIGHT_LASER
              - light.turn_on: BTN_LED
            else:
              Null
            
## PWM/GPIO configuration ##
#  Change pin values in Substitutions, above (not here unless you really want to).
output:
- platform: libretiny_pwm
  pin: $PinPWM_R
  id: PWM_RED
  inverted: True

- platform: libretiny_pwm
  pin: $PinPWM_G
  id: PWM_GREEN
  inverted: True

- platform: libretiny_pwm
  pin: $PinPWM_B
  id: PWM_BLUE
  inverted: True

- platform: libretiny_pwm
  pin: $PinPWM_MOTOR
  id: PWM_MOTOR
  min_power: 0.2 # Tuned to ensure Laser gets 100% power, motor should not turn.
  zero_means_zero: True # So that motor can be disabled

- platform: libretiny_pwm
  pin: $PinPWM_LASER
  id: PWM_LASER
  inverted: True
  min_power: 0.3 # Some flickering below this level
  zero_means_zero: True # So that laser can be disabled

- platform: gpio
  pin: $PinGPIO_BTN_LED
  id: LED_BTN
  inverted: True
1 Like