# Credit where due.... # I put a certain amount of work into this, but a lot of ESPHome integration is # "look for other examples and see what they do" programming-by-example. Here are # things that helped me along with this: # # - I mined the existing tsl2561 integration for basic structural framing for both # the code and documentation. # # - I looked at the existing bme280 integration as an example of a single device # with multiple sensors. # # - Comments and code in this thread got me going with the Adafruit TSL2591 library # and prompted my desired to have tsl2591 as a standard component instead of a # custom/external component. # # - And, of course, the handy and available Adafruit TSL2591 library was very # helpful in understanding what the device is actually talking about. # # Here is the project that started me down the TSL2591 device trail in the first # place: https://hackaday.io/project/176690-the-water-watcher import esphome.codegen as cg from esphome.components import i2c, sensor import esphome.config_validation as cv from esphome.const import ( CONF_ACTUAL_GAIN, CONF_CALCULATED_LUX, CONF_DEVICE_FACTOR, CONF_FULL_SPECTRUM, CONF_GAIN, CONF_GLASS_ATTENUATION_FACTOR, CONF_ID, CONF_INFRARED, CONF_INTEGRATION_TIME, CONF_NAME, CONF_POWER_SAVE_MODE, CONF_VISIBLE, DEVICE_CLASS_ILLUMINANCE, ICON_BRIGHTNESS_6, STATE_CLASS_MEASUREMENT, UNIT_LUX, ) DEPENDENCIES = ["i2c"] tsl2591_ns = cg.esphome_ns.namespace("tsl2591") TSL2591IntegrationTime = tsl2591_ns.enum("TSL2591IntegrationTime") INTEGRATION_TIMES = { 100: TSL2591IntegrationTime.TSL2591_INTEGRATION_TIME_100MS, 200: TSL2591IntegrationTime.TSL2591_INTEGRATION_TIME_200MS, 300: TSL2591IntegrationTime.TSL2591_INTEGRATION_TIME_300MS, 400: TSL2591IntegrationTime.TSL2591_INTEGRATION_TIME_400MS, 500: TSL2591IntegrationTime.TSL2591_INTEGRATION_TIME_500MS, 600: TSL2591IntegrationTime.TSL2591_INTEGRATION_TIME_600MS, } TSL2591ComponentGain = tsl2591_ns.enum("TSL2591ComponentGain") GAINS = { "1X": TSL2591ComponentGain.TSL2591_CGAIN_LOW, "LOW": TSL2591ComponentGain.TSL2591_CGAIN_LOW, "25X": TSL2591ComponentGain.TSL2591_CGAIN_MED, "MED": TSL2591ComponentGain.TSL2591_CGAIN_MED, "MEDIUM": TSL2591ComponentGain.TSL2591_CGAIN_MED, "400X": TSL2591ComponentGain.TSL2591_CGAIN_HIGH, "HIGH": TSL2591ComponentGain.TSL2591_CGAIN_HIGH, "9500X": TSL2591ComponentGain.TSL2591_CGAIN_MAX, "MAX": TSL2591ComponentGain.TSL2591_CGAIN_MAX, "MAXIMUM": TSL2591ComponentGain.TSL2591_CGAIN_MAX, "AUTO": TSL2591ComponentGain.TSL2591_CGAIN_AUTO, } def validate_integration_time(value): value = cv.positive_time_period_milliseconds(value).total_milliseconds return cv.enum(INTEGRATION_TIMES, int=True)(value) TSL2591Component = tsl2591_ns.class_( "TSL2591Component", cg.PollingComponent, i2c.I2CDevice ) CONFIG_SCHEMA = ( cv.Schema( { cv.GenerateID(): cv.declare_id(TSL2591Component), cv.Optional(CONF_INFRARED): sensor.sensor_schema( icon=ICON_BRIGHTNESS_6, accuracy_decimals=0, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_VISIBLE): sensor.sensor_schema( icon=ICON_BRIGHTNESS_6, accuracy_decimals=0, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_FULL_SPECTRUM): sensor.sensor_schema( icon=ICON_BRIGHTNESS_6, accuracy_decimals=0, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_CALCULATED_LUX): sensor.sensor_schema( unit_of_measurement=UNIT_LUX, icon=ICON_BRIGHTNESS_6, accuracy_decimals=4, device_class=DEVICE_CLASS_ILLUMINANCE, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_ACTUAL_GAIN): sensor.sensor_schema( icon=ICON_BRIGHTNESS_6, accuracy_decimals=0, device_class=DEVICE_CLASS_ILLUMINANCE, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional( CONF_INTEGRATION_TIME, default="100ms" ): validate_integration_time, cv.Optional(CONF_NAME, default="TLS2591"): cv.string, cv.Optional(CONF_GAIN, default="AUTO"): cv.enum(GAINS, upper=True), cv.Optional(CONF_POWER_SAVE_MODE, default=True): cv.boolean, cv.Optional(CONF_DEVICE_FACTOR, default=53.0): cv.float_with_unit( "device_factor", "", True ), cv.Optional(CONF_GLASS_ATTENUATION_FACTOR, default=7.7): cv.float_with_unit( "glass_attenuation_factor", "", True ), } ) .extend(cv.polling_component_schema("60s")) .extend(i2c.i2c_device_schema(0x29)) ) async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await i2c.register_i2c_device(var, config) if CONF_FULL_SPECTRUM in config: conf = config[CONF_FULL_SPECTRUM] sens = await sensor.new_sensor(conf) cg.add(var.set_full_spectrum_sensor(sens)) if CONF_INFRARED in config: conf = config[CONF_INFRARED] sens = await sensor.new_sensor(conf) cg.add(var.set_infrared_sensor(sens)) if CONF_VISIBLE in config: conf = config[CONF_VISIBLE] sens = await sensor.new_sensor(conf) cg.add(var.set_visible_sensor(sens)) if CONF_CALCULATED_LUX in config: conf = config[CONF_CALCULATED_LUX] sens = await sensor.new_sensor(conf) cg.add(var.set_calculated_lux_sensor(sens)) if CONF_ACTUAL_GAIN in config: conf = config[CONF_ACTUAL_GAIN] sens = await sensor.new_sensor(conf) cg.add(var.set_actual_gain_sensor(sens)) cg.add(var.set_name(config[CONF_NAME])) cg.add(var.set_power_save_mode(config[CONF_POWER_SAVE_MODE])) cg.add(var.set_integration_time(config[CONF_INTEGRATION_TIME])) cg.add(var.set_gain(config[CONF_GAIN])) cg.add( var.set_device_and_glass_attenuation_factors( config[CONF_DEVICE_FACTOR], config[CONF_GLASS_ATTENUATION_FACTOR] ) )