diff --git a/tasmota/xsns_45_vl53l0x.ino b/tasmota/xsns_45_vl53l0x.ino index 1d377bd6d..3996f1c8e 100644 --- a/tasmota/xsns_45_vl53l0x.ino +++ b/tasmota/xsns_45_vl53l0x.ino @@ -1,7 +1,7 @@ /* - xsns_45_vl53l0x.ino - VL53L0X time of flight sensor support for Tasmota + xsns_45_vl53l0x.ino - VL53L0X time of flight multiple sensors support for Tasmota - Copyright (C) 2021 Theo Arends and Gerhard Mutz + Copyright (C) 2021 Theo Arends, Gerhard Mutz and Adrian Scillato This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -23,105 +23,216 @@ * VL53L0x time of flight sensor * * I2C Addres: 0x29 + ********************************************************************************************* + * + * Note: When using multiple VL53L0X, it is required to also wire the XSHUT pin of all those sensors + * in order to let Tasmota change by software the I2C address of those and give them an unique address + * for operation. The sensor don't save its address, so this procedure of changing its address is needed + * to be performed every restart. The Addresses used for this are 120 (0x78) to 127 (0x7F). In the I2c + * Standard (https://i2cdevices.org/addresses) those addresses are used by the PCA9685, so take into + * account they won't work together. + * + * The default value of VL53L0X_MAX_SENSORS is set in the file tasmota.h + * Changing that is backwards incompatible - Max supported devices by this driver are 8 + * + ********************************************************************************************** + * + * How to install this sensor: https://www.st.com/resource/en/datasheet/vl53l0x.pdf + * + * If you are going to use long I2C wires read this: + * https://hackaday.com/2017/02/08/taking-the-leap-off-board-an-introduction-to-i2c-over-long-wires/ + * \*********************************************************************************************/ #define XSNS_45 45 #define XI2C_31 31 // See I2CDEVICES.md +// Uncomment this line to use long range mode. This +// increases the sensitivity of the sensor and extends its +// potential range, but increases the likelihood of getting +// an inaccurate reading because of reflections from objects +// other than the intended target. It works best in dark +// conditions. + +//#define VL53L0X_LONG_RANGE + +// Uncomment ONE of these two lines to get +// - higher speed at the cost of lower accuracy OR +// - higher accuracy at the cost of lower speed + +//#define VL53L0X_HIGH_SPEED +//#define VL53L0X_HIGH_ACCURACY + #define USE_VL_MEDIAN #define USE_VL_MEDIAN_SIZE 5 // Odd number of samples median detection #include #include "VL53L0X.h" -VL53L0X sensor; +VL53L0X sensor[VL53L0X_MAX_SENSORS]; struct { uint16_t distance; uint16_t distance_prev; uint16_t buffer[5]; uint8_t ready = 0; uint8_t index; -} Vl53l0x; +} Vl53l0x[VL53L0X_MAX_SENSORS]; + +bool xshut = false; +bool VL53L0X_detected = false; /********************************************************************************************/ void Vl53l0Detect(void) { - if (!I2cSetDevice(0x29)) { return; } - if (!sensor.init()) { return; } - I2cSetActiveFound(sensor.getAddress(), "VL53L0X"); + for (uint32_t i = 0; i < VL53L0X_MAX_SENSORS; i++) { + if (PinUsed(GPIO_VL53L0X_XSHUT1, i)) { + pinMode(Pin(GPIO_VL53L0X_XSHUT1, i), OUTPUT); + digitalWrite(Pin(GPIO_VL53L0X_XSHUT1, i), i==0 ? 1 : 0); + xshut = true; + } + } - sensor.setTimeout(500); + for (uint32_t i = 0; i < VL53L0X_MAX_SENSORS; i++) { + if (PinUsed(GPIO_VL53L0X_XSHUT1, i) || (!xshut)) { + if (xshut) { pinMode(Pin(GPIO_VL53L0X_XSHUT1, i), INPUT); delay(1); } + if (!I2cSetDevice(0x29) && !I2cSetDevice((uint8_t)(120+i))) { return; } // Detection for unconfigured OR configured sensor + if (sensor[i].init()) { + if (xshut) { sensor[i].setAddress((uint8_t)(120+i)); } + uint8_t addr = sensor[i].getAddress(); + if (xshut) { + I2cSetActive(addr); + AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_I2C D_SENSOR " VL53L0X %d " D_SENSOR_DETECTED " - " D_NEW_ADDRESS " 0x%02X"), i+1, addr); + } else { + I2cSetActiveFound(addr, "VL53L0X"); + } + sensor[i].setTimeout(500); - // Start continuous back-to-back mode (take readings as - // fast as possible). To use continuous timed mode - // instead, provide a desired inter-measurement period in - // ms (e.g. sensor.startContinuous(100)). - sensor.startContinuous(); - Vl53l0x.ready = 1; +#if defined VL53L0X_LONG_RANGE + // lower the return signal rate limit (default is 0.25 MCPS) + sensor[i].setSignalRateLimit(0.1); + // increase laser pulse periods (defaults are 14 and 10 PCLKs) + sensor[i].setVcselPulsePeriod(VL53L0X::VcselPeriodPreRange, 18); + sensor[i].setVcselPulsePeriod(VL53L0X::VcselPeriodFinalRange, 14); +#endif +#if defined VL53L0X_HIGH_SPEED + // reduce timing budget to 20 ms (default is about 33 ms) + sensor[i].setMeasurementTimingBudget(20000); +#elif defined VL53L0X_HIGH_ACCURACY + // increase timing budget to 200 ms + sensor[i].setMeasurementTimingBudget(200000); +#endif + // Start continuous back-to-back mode (take readings as + // fast as possible). To use continuous timed mode + // instead, provide a desired inter-measurement period in + // ms (e.g. sensor.startContinuous(100)). + sensor[i].startContinuous(); - Vl53l0x.index = 0; + Vl53l0x[i].ready = 1; + Vl53l0x[i].index = 0; + VL53L0X_detected = true; + if (!xshut) { break; } + } else { + AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_I2C D_SENSOR " VL53L0X %d - " D_FAILED_TO_START), i+1); + } + } + } } void Vl53l0Every_250MSecond(void) { - uint16_t dist = sensor.readRangeContinuousMillimeters(); - if ((0 == dist) || (dist > 2000)) { - dist = 9999; - } + for (uint32_t i = 0; i < VL53L0X_MAX_SENSORS; i++) { + if (PinUsed(GPIO_VL53L0X_XSHUT1, i) || (!xshut)) { + uint16_t dist = sensor[i].readRangeContinuousMillimeters(); + if ((0 == dist) || (dist > 2200)) { + dist = 9999; + } #ifdef USE_VL_MEDIAN - // store in ring buffer - Vl53l0x.buffer[Vl53l0x.index] = dist; - Vl53l0x.index++; - if (Vl53l0x.index >= USE_VL_MEDIAN_SIZE) { - Vl53l0x.index = 0; - } + // store in ring buffer + Vl53l0x[i].buffer[Vl53l0x[i].index] = dist; + Vl53l0x[i].index++; + if (Vl53l0x[i].index >= USE_VL_MEDIAN_SIZE) { + Vl53l0x[i].index = 0; + } - // sort list and take median - uint16_t tbuff[USE_VL_MEDIAN_SIZE]; - memmove(tbuff, Vl53l0x.buffer, sizeof(tbuff)); - uint16_t tmp; - uint8_t flag; - for (uint32_t ocnt = 0; ocnt < USE_VL_MEDIAN_SIZE; ocnt++) { - flag = 0; - for (uint32_t count = 0; count < USE_VL_MEDIAN_SIZE -1; count++) { - if (tbuff[count] > tbuff[count +1]) { - tmp = tbuff[count]; - tbuff[count] = tbuff[count +1]; - tbuff[count +1] = tmp; - flag = 1; - } - } - if (!flag) { break; } - } - Vl53l0x.distance = tbuff[(USE_VL_MEDIAN_SIZE -1) / 2]; + // sort list and take median + uint16_t tbuff[USE_VL_MEDIAN_SIZE]; + memmove(tbuff, Vl53l0x[i].buffer, sizeof(tbuff)); + uint16_t tmp; + uint8_t flag; + for (uint32_t ocnt = 0; ocnt < USE_VL_MEDIAN_SIZE; ocnt++) { + flag = 0; + for (uint32_t count = 0; count < USE_VL_MEDIAN_SIZE -1; count++) { + if (tbuff[count] > tbuff[count +1]) { + tmp = tbuff[count]; + tbuff[count] = tbuff[count +1]; + tbuff[count +1] = tmp; + flag = 1; + } + } + if (!flag) { break; } + } + Vl53l0x[i].distance = tbuff[(USE_VL_MEDIAN_SIZE -1) / 2]; #else - Vl53l0x.distance = dist; + Vl53l0x[i].distance = dist; #endif + } + if (!xshut) { break; } + } } #ifdef USE_DOMOTICZ void Vl53l0Every_Second(void) { - if (abs(Vl53l0x.distance - Vl53l0x.distance_prev) > 8) { - Vl53l0x.distance_prev = Vl53l0x.distance; - DomoticzSensor(DZ_ILLUMINANCE, Vl53l0x.distance); + if (abs(Vl53l0x[0].distance - Vl53l0x[0].distance_prev) > 8) { + Vl53l0x[0].distance_prev = Vl53l0x[0].distance; + DomoticzSensor(DZ_ILLUMINANCE, Vl53l0x[0].distance); } } #endif // USE_DOMOTICZ void Vl53l0Show(boolean json) { - if (json) { - ResponseAppend_P(PSTR(",\"VL53L0X\":{\"" D_JSON_DISTANCE "\":%d}"), Vl53l0x.distance); + for (uint32_t i = 0; i < VL53L0X_MAX_SENSORS; i++) { + if (PinUsed(GPIO_VL53L0X_XSHUT1, i) || (!xshut)) { + if (json) { + if (Vl53l0x[i].distance == 9999) { + if (xshut) { + ResponseAppend_P(PSTR(",\"VL53L0X_%d\":{\"" D_JSON_DISTANCE "\":null}"), i+1); + } else { + ResponseAppend_P(PSTR(",\"VL53L0X\":{\"" D_JSON_DISTANCE "\":null}")); // For backwards compatibility when not using XSHUT GPIOs + } + } else { + if (xshut) { + ResponseAppend_P(PSTR(",\"VL53L0X_%d\":{\"" D_JSON_DISTANCE "\":%d}"), i+1, Vl53l0x[i].distance); + } else { + ResponseAppend_P(PSTR(",\"VL53L0X\":{\"" D_JSON_DISTANCE "\":%d}"), Vl53l0x[i].distance); // For backwards compatibility when not using XSHUT GPIOs + } + } +#ifdef USE_WEBSERVER + } else { + if (Vl53l0x[i].distance == 9999) { + if (xshut) { + WSContentSend_PD("{s}%s_%d " D_DISTANCE "{m}%s {e}", PSTR("VL53L0X"), i+1, PSTR(D_OUT_OF_RANGE)); + } else { + WSContentSend_PD("{s}%s " D_DISTANCE "{m}%s {e}", PSTR("VL53L0X"), PSTR(D_OUT_OF_RANGE)); // For backwards compatibility when not using XSHUT GPIOs + } + } else { + if (xshut) { + WSContentSend_PD("{s}%s_%d " D_DISTANCE "{m}%d " D_UNIT_MILLIMETER "{e}", PSTR("VL53L0X"), i+1, Vl53l0x[i].distance); + } else { + WSContentSend_PD(HTTP_SNS_DISTANCE, PSTR("VL53L0X"), Vl53l0x[i].distance); // For backwards compatibility when not using XSHUT GPIOs + } + } +#endif + } + } + if (sensor[i].timeoutOccurred()) { AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_I2C D_TIMEOUT_WAITING_FOR D_SENSOR " VL53L0X %d"), i+1); } + if (!xshut) { break; } + } #ifdef USE_DOMOTICZ - if (0 == TasmotaGlobal.tele_period) { - DomoticzSensor(DZ_ILLUMINANCE, Vl53l0x.distance); + if ((json) && (0 == TasmotaGlobal.tele_period)){ + DomoticzSensor(DZ_ILLUMINANCE, Vl53l0x[0].distance); } #endif // USE_DOMOTICZ -#ifdef USE_WEBSERVER - } else { - WSContentSend_PD(HTTP_SNS_DISTANCE, PSTR("VL53L0X"), Vl53l0x.distance); -#endif - } } /*********************************************************************************************\ @@ -136,7 +247,7 @@ bool Xsns45(byte function) { if (FUNC_INIT == function) { Vl53l0Detect(); } - else if (Vl53l0x.ready) { + else if (VL53L0X_detected) { switch (function) { case FUNC_EVERY_250_MSECOND: Vl53l0Every_250MSecond();