|
| 1 | +# Credit where due.... |
| 2 | +# I put a certain amount of work into this, but a lot of ESPHome integration is |
| 3 | +# "look for other examples and see what they do" programming-by-example. Here are |
| 4 | +# things that helped me along with this: |
| 5 | +# |
| 6 | +# - I mined the existing tsl2561 integration for basic structural framing for both |
| 7 | +# the code and documentation. |
| 8 | +# |
| 9 | +# - I looked at the existing bme280 integration as an example of a single device |
| 10 | +# with multiple sensors. |
| 11 | +# |
| 12 | +# - Comments and code in this thread got me going with the Adafruit TSL2591 library |
| 13 | +# and prompted my desired to have tsl2591 as a standard component instead of a |
| 14 | +# custom/external component. |
| 15 | +# |
| 16 | +# - And, of course, the handy and available Adafruit TSL2591 library was very |
| 17 | +# helpful in understanding what the device is actually talking about. |
| 18 | +# |
| 19 | +# Here is the project that started me down the TSL2591 device trail in the first |
| 20 | +# place: https://hackaday.io/project/176690-the-water-watcher |
| 21 | + |
| 22 | +import esphome.codegen as cg |
| 23 | +from esphome.components import i2c, sensor |
| 24 | +import esphome.config_validation as cv |
| 25 | +from esphome.const import ( |
| 26 | + CONF_ACTUAL_GAIN, |
| 27 | + CONF_CALCULATED_LUX, |
| 28 | + CONF_DEVICE_FACTOR, |
| 29 | + CONF_FULL_SPECTRUM, |
| 30 | + CONF_GAIN, |
| 31 | + CONF_GLASS_ATTENUATION_FACTOR, |
| 32 | + CONF_ID, |
| 33 | + CONF_INFRARED, |
| 34 | + CONF_INTEGRATION_TIME, |
| 35 | + CONF_NAME, |
| 36 | + CONF_POWER_SAVE_MODE, |
| 37 | + CONF_VISIBLE, |
| 38 | + DEVICE_CLASS_ILLUMINANCE, |
| 39 | + ICON_BRIGHTNESS_6, |
| 40 | + STATE_CLASS_MEASUREMENT, |
| 41 | + UNIT_LUX, |
| 42 | +) |
| 43 | + |
| 44 | +DEPENDENCIES = ["i2c"] |
| 45 | + |
| 46 | +tsl2591_ns = cg.esphome_ns.namespace("tsl2591") |
| 47 | + |
| 48 | +TSL2591IntegrationTime = tsl2591_ns.enum("TSL2591IntegrationTime") |
| 49 | +INTEGRATION_TIMES = { |
| 50 | + 100: TSL2591IntegrationTime.TSL2591_INTEGRATION_TIME_100MS, |
| 51 | + 200: TSL2591IntegrationTime.TSL2591_INTEGRATION_TIME_200MS, |
| 52 | + 300: TSL2591IntegrationTime.TSL2591_INTEGRATION_TIME_300MS, |
| 53 | + 400: TSL2591IntegrationTime.TSL2591_INTEGRATION_TIME_400MS, |
| 54 | + 500: TSL2591IntegrationTime.TSL2591_INTEGRATION_TIME_500MS, |
| 55 | + 600: TSL2591IntegrationTime.TSL2591_INTEGRATION_TIME_600MS, |
| 56 | +} |
| 57 | + |
| 58 | +TSL2591ComponentGain = tsl2591_ns.enum("TSL2591ComponentGain") |
| 59 | +GAINS = { |
| 60 | + "1X": TSL2591ComponentGain.TSL2591_CGAIN_LOW, |
| 61 | + "LOW": TSL2591ComponentGain.TSL2591_CGAIN_LOW, |
| 62 | + "25X": TSL2591ComponentGain.TSL2591_CGAIN_MED, |
| 63 | + "MED": TSL2591ComponentGain.TSL2591_CGAIN_MED, |
| 64 | + "MEDIUM": TSL2591ComponentGain.TSL2591_CGAIN_MED, |
| 65 | + "400X": TSL2591ComponentGain.TSL2591_CGAIN_HIGH, |
| 66 | + "HIGH": TSL2591ComponentGain.TSL2591_CGAIN_HIGH, |
| 67 | + "9500X": TSL2591ComponentGain.TSL2591_CGAIN_MAX, |
| 68 | + "MAX": TSL2591ComponentGain.TSL2591_CGAIN_MAX, |
| 69 | + "MAXIMUM": TSL2591ComponentGain.TSL2591_CGAIN_MAX, |
| 70 | + "AUTO": TSL2591ComponentGain.TSL2591_CGAIN_AUTO, |
| 71 | +} |
| 72 | + |
| 73 | + |
| 74 | +def validate_integration_time(value): |
| 75 | + value = cv.positive_time_period_milliseconds(value).total_milliseconds |
| 76 | + return cv.enum(INTEGRATION_TIMES, int=True)(value) |
| 77 | + |
| 78 | + |
| 79 | +TSL2591Component = tsl2591_ns.class_( |
| 80 | + "TSL2591Component", cg.PollingComponent, i2c.I2CDevice |
| 81 | +) |
| 82 | + |
| 83 | +CONFIG_SCHEMA = ( |
| 84 | + cv.Schema( |
| 85 | + { |
| 86 | + cv.GenerateID(): cv.declare_id(TSL2591Component), |
| 87 | + cv.Optional(CONF_INFRARED): sensor.sensor_schema( |
| 88 | + icon=ICON_BRIGHTNESS_6, |
| 89 | + accuracy_decimals=0, |
| 90 | + state_class=STATE_CLASS_MEASUREMENT, |
| 91 | + ), |
| 92 | + cv.Optional(CONF_VISIBLE): sensor.sensor_schema( |
| 93 | + icon=ICON_BRIGHTNESS_6, |
| 94 | + accuracy_decimals=0, |
| 95 | + state_class=STATE_CLASS_MEASUREMENT, |
| 96 | + ), |
| 97 | + cv.Optional(CONF_FULL_SPECTRUM): sensor.sensor_schema( |
| 98 | + icon=ICON_BRIGHTNESS_6, |
| 99 | + accuracy_decimals=0, |
| 100 | + state_class=STATE_CLASS_MEASUREMENT, |
| 101 | + ), |
| 102 | + cv.Optional(CONF_CALCULATED_LUX): sensor.sensor_schema( |
| 103 | + unit_of_measurement=UNIT_LUX, |
| 104 | + icon=ICON_BRIGHTNESS_6, |
| 105 | + accuracy_decimals=4, |
| 106 | + device_class=DEVICE_CLASS_ILLUMINANCE, |
| 107 | + state_class=STATE_CLASS_MEASUREMENT, |
| 108 | + ), |
| 109 | + cv.Optional(CONF_ACTUAL_GAIN): sensor.sensor_schema( |
| 110 | + icon=ICON_BRIGHTNESS_6, |
| 111 | + accuracy_decimals=0, |
| 112 | + device_class=DEVICE_CLASS_ILLUMINANCE, |
| 113 | + state_class=STATE_CLASS_MEASUREMENT, |
| 114 | + ), |
| 115 | + cv.Optional( |
| 116 | + CONF_INTEGRATION_TIME, default="100ms" |
| 117 | + ): validate_integration_time, |
| 118 | + cv.Optional(CONF_NAME, default="TLS2591"): cv.string, |
| 119 | + cv.Optional(CONF_GAIN, default="AUTO"): cv.enum(GAINS, upper=True), |
| 120 | + cv.Optional(CONF_POWER_SAVE_MODE, default=True): cv.boolean, |
| 121 | + cv.Optional(CONF_DEVICE_FACTOR, default=53.0): cv.float_with_unit( |
| 122 | + "device_factor", "", True |
| 123 | + ), |
| 124 | + cv.Optional(CONF_GLASS_ATTENUATION_FACTOR, default=7.7): cv.float_with_unit( |
| 125 | + "glass_attenuation_factor", "", True |
| 126 | + ), |
| 127 | + } |
| 128 | + ) |
| 129 | + .extend(cv.polling_component_schema("60s")) |
| 130 | + .extend(i2c.i2c_device_schema(0x29)) |
| 131 | +) |
| 132 | + |
| 133 | + |
| 134 | +async def to_code(config): |
| 135 | + var = cg.new_Pvariable(config[CONF_ID]) |
| 136 | + await cg.register_component(var, config) |
| 137 | + await i2c.register_i2c_device(var, config) |
| 138 | + |
| 139 | + if CONF_FULL_SPECTRUM in config: |
| 140 | + conf = config[CONF_FULL_SPECTRUM] |
| 141 | + sens = await sensor.new_sensor(conf) |
| 142 | + cg.add(var.set_full_spectrum_sensor(sens)) |
| 143 | + |
| 144 | + if CONF_INFRARED in config: |
| 145 | + conf = config[CONF_INFRARED] |
| 146 | + sens = await sensor.new_sensor(conf) |
| 147 | + cg.add(var.set_infrared_sensor(sens)) |
| 148 | + |
| 149 | + if CONF_VISIBLE in config: |
| 150 | + conf = config[CONF_VISIBLE] |
| 151 | + sens = await sensor.new_sensor(conf) |
| 152 | + cg.add(var.set_visible_sensor(sens)) |
| 153 | + |
| 154 | + if CONF_CALCULATED_LUX in config: |
| 155 | + conf = config[CONF_CALCULATED_LUX] |
| 156 | + sens = await sensor.new_sensor(conf) |
| 157 | + cg.add(var.set_calculated_lux_sensor(sens)) |
| 158 | + |
| 159 | + if CONF_ACTUAL_GAIN in config: |
| 160 | + conf = config[CONF_ACTUAL_GAIN] |
| 161 | + sens = await sensor.new_sensor(conf) |
| 162 | + cg.add(var.set_actual_gain_sensor(sens)) |
| 163 | + |
| 164 | + cg.add(var.set_name(config[CONF_NAME])) |
| 165 | + cg.add(var.set_power_save_mode(config[CONF_POWER_SAVE_MODE])) |
| 166 | + cg.add(var.set_integration_time(config[CONF_INTEGRATION_TIME])) |
| 167 | + cg.add(var.set_gain(config[CONF_GAIN])) |
| 168 | + cg.add( |
| 169 | + var.set_device_and_glass_attenuation_factors( |
| 170 | + config[CONF_DEVICE_FACTOR], config[CONF_GLASS_ATTENUATION_FACTOR] |
| 171 | + ) |
| 172 | + ) |
0 commit comments