Youshiko Weather Station - ESP32 + CC1101 - Working Integration Blueprint

I bought a cheap(ish) weather sensor from Amazon ( Official 2026 UK Version, with 3 x Wireless Sensors Weather Station, Radio Controlled Clock Indoor Outdoor Temperature Thermometer, Humidity, Barometric Pressure : Amazon.co.uk: Garden ) and have used the following hardware to integrate it into HomeAssistant.

I then use this code is ESPHOME to capture all the radio traffic so I could get the SensorID and model id for all 3 sensors

Sorry for the basic info - but I hope it will get someone started ( as it took a fair amount or trial and error to get it working )

#######  Use this to get the platform / Sensor ID  ##########

esphome:
  name: esphome-web-c4a1a8
  friendly_name: Weather Station
  name_add_mac_suffix: false
  comment: "ESP32 + CC1101 receiver for Youshiko YC9443 wireless weather sensors"

esp32:
  board: esp32dev
  framework:
    type: arduino
#    type: esp-idf
    advanced:
      minimum_chip_revision: "3.1"
      sram1_as_iram: true

logger:
  level: INFO
  #level: VERY_VERBOSE
  logs:
    # Keep RF decoding visible but avoid making Wi-Fi/API too noisy.
    rtl_433: INFO
    remote_receiver: INFO
    cc1101: INFO

    # rtl_433: VERY_VERBOSE
    # remote_receiver: VERY_VERBOSE
    # cc1101: VERY_VERBOSE

api:

ota:
  - platform: esphome

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  min_auth_mode: WPA2
  # Prevent reboot loops if Wi-Fi fails
  reboot_timeout: 0s

web_server:
  port: 80
  version: 3
  local: true

substitutions:
  device_name: youshiko_yc9443_rf
  friendly_name: "Youshiko YC9443 RF"

  # ESP32 board type. Common values:
  # esp32dev, nodemcu-32s, esp32doit-devkit-v1, lolin_s2_mini, etc.
  esp32_board: esp32dev

  # CC1101 SPI pins.
  # These are common ESP32 VSPI defaults, but use YOUR actual wiring.
  pin_sck: GPIO18
  pin_mosi: GPIO23
  pin_miso: GPIO19
  pin_cs: GPIO5

  # CC1101 data output pin.
  # For receive-only, connect CC1101 GDO2 to this GPIO if available.
  # If your module only has GDO0 wired, set this to your GDO0 GPIO.
  # pin_cc1101_rx: GPIO32
  pin_cc1101_rx: GPIO4

  # Normal UK/EU weather sensors are usually 433.92 MHz.
  # If your raw logs are strongest elsewhere, tune this slightly.
  rf_frequency: 433.92MHz

# Community rtl_433 decoder for ESPHome.
# It receives pulses from ESPHome remote_receiver and tries rtl_433 protocol decoders.
packages:
  rtl_433: github://juanboro/esphome-rtl_433-decoder/rtl_433.yaml

# SPI bus used by the CC1101.
spi:
  clk_pin: ${pin_sck}
  mosi_pin: ${pin_mosi}
  miso_pin: ${pin_miso}

# Native ESPHome CC1101 driver.
# ASK/OOK is the usual modulation for simple 433 MHz weather sensors.
cc1101:
  id: cc1101_radio
  cs_pin: ${pin_cs}
  frequency: ${rf_frequency}
  modulation_type: ASK/OOK

  # Conservative starting values. Adjust only if reception is unreliable.
  symbol_rate: 5000
  filter_bandwidth: 203kHz
  rx_attenuation: 0dB

# Pulse receiver fed from the CC1101 GDO output.
# This creates the pulse stream consumed by rtl_433.
remote_receiver:
  id: rf_receiver
  pin:
    number: ${pin_cc1101_rx}
    mode:
      input: true
      pullup: false

  # Weather sensors transmit short bursts every 30-90 seconds.
  # idle controls where one RF burst is considered complete.
  idle: 10ms

  # Ignore very short noise pulses.
  filter: 20us

  # Large enough for weather-station messages.
  buffer_size: 4096

  # During discovery this is useful. After things work, change to:
  #dump: []
  dump: all

# rtl_433 decoder connected to the ESPHome remote_receiver above.
rtl_433:
  id: rtl433_decoder
  receiver_id: rf_receiver

  on_json_message:
    then:
      - lambda: |-
          // Discovery logger: print every decoded rtl_433 message.

          std::string msg;
          serializeJson(x, msg);
          ESP_LOGI("rtl_433", "decoded: %s", msg.c_str());

          const char *model = x["model"] | "";
          int sensor_id = x["id"] | -1;
          int channel = x["channel"] | -1;

          bool has_temp = x["temperature_C"].is<float>() || x["temperature_C"].is<int>();
          bool has_humidity = x["humidity"].is<float>() || x["humidity"].is<int>();

          ESP_LOGI(
            "rtl_433",
            "summary: model=%s id=%d channel=%d has_temp=%s has_humidity=%s",
            model,
            sensor_id,
            channel,
            has_temp ? "yes" : "no",
            has_humidity ? "yes" : "no"
          );

time:
  - platform: sntp
    id: sntp_time
    timezone: Europe/London

sensor:
  # Channel 1
  - platform: template
    name: "Youshiko Channel 1 Temperature"
    id: youshiko_ch1_temperature
    unit_of_measurement: "°C"
    device_class: temperature
    state_class: measurement
    accuracy_decimals: 1

  - platform: template
    name: "Youshiko Channel 1 Humidity"
    id: youshiko_ch1_humidity
    unit_of_measurement: "%"
    device_class: humidity
    state_class: measurement
    accuracy_decimals: 0

  # Channel 2
  - platform: template
    name: "Youshiko Channel 2 Temperature"
    id: youshiko_ch2_temperature
    unit_of_measurement: "°C"
    device_class: temperature
    state_class: measurement
    accuracy_decimals: 1

  - platform: template
    name: "Youshiko Channel 2 Humidity"
    id: youshiko_ch2_humidity
    unit_of_measurement: "%"
    device_class: humidity
    state_class: measurement
    accuracy_decimals: 0

  # Channel 3
  - platform: template
    name: "Youshiko Channel 3 Temperature"
    id: youshiko_ch3_temperature
    unit_of_measurement: "°C"
    device_class: temperature
    state_class: measurement
    accuracy_decimals: 1

  - platform: template
    name: "Youshiko Channel 3 Humidity"
    id: youshiko_ch3_humidity
    unit_of_measurement: "%"
    device_class: humidity
    state_class: measurement
    accuracy_decimals: 0

binary_sensor:
  - platform: template
    name: "Youshiko Channel 1 Battery OK"
    id: youshiko_ch1_battery_ok
    device_class: battery

  - platform: template
    name: "Youshiko Channel 2 Battery OK"
    id: youshiko_ch2_battery_ok
    device_class: battery

  - platform: template
    name: "Youshiko Channel 3 Battery OK"
    id: youshiko_ch3_battery_ok
    device_class: battery

text_sensor:
  - platform: template
    name: "Youshiko Channel 1 Last Seen"
    id: youshiko_ch1_last_seen

  - platform: template
    name: "Youshiko Channel 2 Last Seen"
    id: youshiko_ch2_last_seen

  - platform: template
    name: "Youshiko Channel 3 Last Seen"
    id: youshiko_ch3_last_seen

Then once I had the correct Sensor and Device ID I updated the rtl_433 section in the Final Code below and that fully integrated it into Home Assistant.

esphome:
  name: esphome-web-c4a1a8
  friendly_name: Weather Station
  name_add_mac_suffix: false
  comment: "ESP32 + CC1101 receiver for Youshiko YC9443 wireless weather sensors"

esp32:
  board: esp32dev
  framework:
    type: arduino
#    type: esp-idf
    advanced:
      minimum_chip_revision: "3.1"
      sram1_as_iram: true

logger:
  level: INFO
  #level: VERY_VERBOSE
  logs:
    # Keep RF decoding visible but avoid making Wi-Fi/API too noisy.
    rtl_433: INFO
    remote_receiver: WARN
    cc1101: WARN

    # rtl_433: VERY_VERBOSE
    # remote_receiver: VERY_VERBOSE
    # cc1101: VERY_VERBOSE

api:

ota:
  - platform: esphome

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  min_auth_mode: WPA2
  # Prevent reboot loops if Wi-Fi fails
  reboot_timeout: 0s

web_server:
  port: 80
  version: 3
  local: true

substitutions:
  device_name: youshiko_yc9443_rf
  friendly_name: "Youshiko YC9443 RF"

  # CC1101 SPI pins.
  # These are common ESP32 VSPI defaults, but use YOUR actual wiring.
  pin_sck: GPIO18
  pin_mosi: GPIO23
  pin_miso: GPIO19
  pin_cs: GPIO5

  # CC1101 data output pin.
  # For receive-only, connect CC1101 GDO2 to this GPIO if available.
  # If your module only has GDO0 wired, set this to your GDO0 GPIO.
  # pin_cc1101_rx: GPIO32
  pin_cc1101_rx: GPIO4

  # Normal UK/EU weather sensors are usually 433.92 MHz.
  # If your raw logs are strongest elsewhere, tune this slightly.
  rf_frequency: 433.92MHz

# Community rtl_433 decoder for ESPHome.
# It receives pulses from ESPHome remote_receiver and tries rtl_433 protocol decoders.
packages:
  rtl_433: github://juanboro/esphome-rtl_433-decoder/rtl_433.yaml

# SPI bus used by the CC1101.
spi:
  clk_pin: ${pin_sck}
  mosi_pin: ${pin_mosi}
  miso_pin: ${pin_miso}

# Native ESPHome CC1101 driver.
# ASK/OOK is the usual modulation for simple 433 MHz weather sensors.
cc1101:
  id: cc1101_radio
  cs_pin: ${pin_cs}
  frequency: ${rf_frequency}
  modulation_type: ASK/OOK

  # Conservative starting values. Adjust only if reception is unreliable.
  symbol_rate: 5000
  filter_bandwidth: 203kHz
  rx_attenuation: 0dB

# Pulse receiver fed from the CC1101 GDO output.
# This creates the pulse stream consumed by rtl_433.
remote_receiver:
  id: rf_receiver
  pin:
    number: ${pin_cc1101_rx}
    mode:
      input: true
      pullup: false

  # Weather sensors transmit short bursts every 30-90 seconds.
  # idle controls where one RF burst is considered complete.
  idle: 10ms

  # Ignore very short noise pulses.
  filter: 20us

  # Large enough for weather-station messages.
  buffer_size: 4096

  # During discovery this is useful. After things work, change to:
  dump: []
  #dump: all

# rtl_433 decoder connected to the ESPHome remote_receiver above.
rtl_433:
  id: rtl433_decoder
  receiver_id: rf_receiver

  on_json_message:
    then:
      - lambda: |-
          const char *model = x["model"] | "";
          int sensor_id = x["id"] | -1;
          int channel = x["channel"] | -1;

          // Ignore anything that is not one of the Youshiko / inFactory-compatible sensors.
          if (strcmp(model, "inFactory-TH") != 0) {
            return;
          }

          bool has_temp = x["temperature_C"].is<float>() || x["temperature_C"].is<int>();
          bool has_humidity = x["humidity"].is<float>() || x["humidity"].is<int>();
          bool has_battery = x["battery_ok"].is<int>() || x["battery_ok"].is<bool>();

          // Channel 1: id 227
          if (sensor_id == 227 && channel == 1) {
            if (has_temp) {
              id(youshiko_ch1_temperature).publish_state(x["temperature_C"].as<float>());
            }

            if (has_humidity) {
              id(youshiko_ch1_humidity).publish_state(x["humidity"].as<float>());
            }

            if (has_battery) {
              id(youshiko_ch1_battery_ok).publish_state(x["battery_ok"].as<int>() > 0);
            }

            if (id(sntp_time).now().is_valid()) {
              id(youshiko_ch1_last_seen).publish_state(
                id(sntp_time).now().strftime("%Y-%m-%d %H:%M:%S")
              );
            }

            ESP_LOGI("rtl_433", "Youshiko CH1 updated: id=227");
          }

          // Channel 2: id 183
          else if (sensor_id == 183 && channel == 2) {
            if (has_temp) {
              id(youshiko_ch2_temperature).publish_state(x["temperature_C"].as<float>());
            }

            if (has_humidity) {
              id(youshiko_ch2_humidity).publish_state(x["humidity"].as<float>());
            }

            if (has_battery) {
              id(youshiko_ch2_battery_ok).publish_state(x["battery_ok"].as<int>() > 0);
            }

            if (id(sntp_time).now().is_valid()) {
              id(youshiko_ch2_last_seen).publish_state(
                id(sntp_time).now().strftime("%Y-%m-%d %H:%M:%S")
              );
            }

            ESP_LOGI("rtl_433", "Youshiko CH2 updated: id=183");
          }

          // Channel 3: id 152
          else if (sensor_id == 152 && channel == 3) {
            if (has_temp) {
              id(youshiko_ch3_temperature).publish_state(x["temperature_C"].as<float>());
            }

            if (has_humidity) {
              id(youshiko_ch3_humidity).publish_state(x["humidity"].as<float>());
            }

            if (has_battery) {
              id(youshiko_ch3_battery_ok).publish_state(x["battery_ok"].as<int>() > 0);
            }

            if (id(sntp_time).now().is_valid()) {
              id(youshiko_ch3_last_seen).publish_state(
                id(sntp_time).now().strftime("%Y-%m-%d %H:%M:%S")
              );
            }

            ESP_LOGI("rtl_433", "Youshiko CH3 updated: id=152");
          }

time:
  - platform: sntp
    id: sntp_time
    timezone: Europe/London

sensor:
  # Channel 1
  - platform: template
    name: "Youshiko Channel 1 Temperature"
    id: youshiko_ch1_temperature
    unit_of_measurement: "°C"
    device_class: temperature
    state_class: measurement
    accuracy_decimals: 1

  - platform: template
    name: "Youshiko Channel 1 Humidity"
    id: youshiko_ch1_humidity
    unit_of_measurement: "%"
    device_class: humidity
    state_class: measurement
    accuracy_decimals: 0

  # Channel 2
  - platform: template
    name: "Youshiko Channel 2 Temperature"
    id: youshiko_ch2_temperature
    unit_of_measurement: "°C"
    device_class: temperature
    state_class: measurement
    accuracy_decimals: 1

  - platform: template
    name: "Youshiko Channel 2 Humidity"
    id: youshiko_ch2_humidity
    unit_of_measurement: "%"
    device_class: humidity
    state_class: measurement
    accuracy_decimals: 0

  # Channel 3
  - platform: template
    name: "Youshiko Channel 3 Temperature"
    id: youshiko_ch3_temperature
    unit_of_measurement: "°C"
    device_class: temperature
    state_class: measurement
    accuracy_decimals: 1

  - platform: template
    name: "Youshiko Channel 3 Humidity"
    id: youshiko_ch3_humidity
    unit_of_measurement: "%"
    device_class: humidity
    state_class: measurement
    accuracy_decimals: 0

binary_sensor:
  - platform: template
    name: "Youshiko Channel 1 Battery OK"
    id: youshiko_ch1_battery_ok
    device_class: battery

  - platform: template
    name: "Youshiko Channel 2 Battery OK"
    id: youshiko_ch2_battery_ok
    device_class: battery

  - platform: template
    name: "Youshiko Channel 3 Battery OK"
    id: youshiko_ch3_battery_ok
    device_class: battery

text_sensor:
  - platform: template
    name: "Youshiko Channel 1 Last Seen"
    id: youshiko_ch1_last_seen

  - platform: template
    name: "Youshiko Channel 2 Last Seen"
    id: youshiko_ch2_last_seen

  - platform: template
    name: "Youshiko Channel 3 Last Seen"
    id: youshiko_ch3_last_seen

Slight updated if needed.... if you change the batteries in the remote sensors it could change the device ID !!!

This code just looks at the channel number ( just hope no-one else has the same unit near you )

rtl_433:
  id: rtl433_decoder
  receiver_id: rf_receiver

  on_json_message:
    then:
      - lambda: |-
          const char *model = x["model"] | "";
          int sensor_id = x["id"] | -1;
          int channel = x["channel"] | -1;

          // Ignore anything that is not one of the Youshiko / inFactory-compatible sensors.
          if (strcmp(model, "inFactory-TH") != 0) {
            return;
          }

          bool has_temp = x["temperature_C"].is<float>() || x["temperature_C"].is<int>();
          bool has_humidity = x["humidity"].is<float>() || x["humidity"].is<int>();
          bool has_battery = x["battery_ok"].is<int>() || x["battery_ok"].is<bool>();

          // Channel 1 (Checks only channel, ignores the volatile sensor_id)
          if (channel == 1) {
            if (has_temp) id(youshiko_ch1_temperature).publish_state(x["temperature_C"].as<float>());
            if (has_humidity) id(youshiko_ch1_humidity).publish_state(x["humidity"].as<float>());
            if (has_battery) id(youshiko_ch1_battery_ok).publish_state(x["battery_ok"].as<int>() > 0);
            if (id(sntp_time).now().is_valid()) {
              id(youshiko_ch1_last_seen).publish_state(id(sntp_time).now().strftime("%Y-%m-%d %H:%M:%S"));
            }
            ESP_LOGI("rtl_433", "Youshiko CH1 updated");
          }

          // Channel 2
          else if (channel == 2) {
            if (has_temp) id(youshiko_ch2_temperature).publish_state(x["temperature_C"].as<float>());
            if (has_humidity) id(youshiko_ch2_humidity).publish_state(x["humidity"].as<float>());
            if (has_battery) id(youshiko_ch2_battery_ok).publish_state(x["battery_ok"].as<int>() > 0);
            if (id(sntp_time).now().is_valid()) {
              id(youshiko_ch2_last_seen).publish_state(id(sntp_time).now().strftime("%Y-%m-%d %H:%M:%S"));
            }
            ESP_LOGI("rtl_433", "Youshiko CH2 updated");
          }

          // Channel 3
          else if (channel == 3) {
            if (has_temp) id(youshiko_ch3_temperature).publish_state(x["temperature_C"].as<float>());
            if (has_humidity) id(youshiko_ch3_humidity).publish_state(x["humidity"].as<float>());
            if (has_battery) id(youshiko_ch3_battery_ok).publish_state(x["battery_ok"].as<int>() > 0);
            if (id(sntp_time).now().is_valid()) {
              id(youshiko_ch3_last_seen).publish_state(id(sntp_time).now().strftime("%Y-%m-%d %H:%M:%S"));
            }
            ESP_LOGI("rtl_433", "Youshiko CH3 updated");
          }

Interesting find.
I got some openmqtt gateways running but like the idea to switch everything to esphome.
i will try if i find the time.