/* xsns_86_tfminiplus.ino - TFmini Plus interface for Tasmota Copyright (C) 2021 Raphael Breiting and Theo Arends 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 the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifdef USE_TFMINIPLUS /*****************************************************************************\ * TFmini, TFmini Plus, TFmini Plus (Indoor Version), TFmini-S - LiDAR Module * Manufacturer: Benewake (Beijing) Co. Ltd. * * Code for Time-Of-Flight (TOF) family single-point short-range LiDAR sensor * modules with UART interface. * * Connection Description for GH1.25-4P (Molex51021-0400) connector: * No Color Corresponding PIN Function Comment * 1 RED PIN-1 +5V Power Supply * 2 WHITE PIN-2 RxD/SDA Receiving/Data * 3 GREEN PIN-3 TxD/SCL Transmitting/Clock * 4 BLACK PIN-4 GND Ground * * Before connecting module to Tasmota, please prepare module with * the following sequence through terminal program (e.g. Termite): * 1. Connect sensor to USB-to-UART (TTL 3.3V) * 2. Set terminal to 115200bps and 8N1 * 3. Set frame rate to 0 (zero): * send -->: 5A0603000000 * receive <-- (OK): 5A0603000000 * 4. Set baud rate to 9600bps * send -->: 5A0806802500000D * receive <-- (OK): 5A0806802500000D * 5. Set terminal to 9600bps and 8N1 * 6. Save settings * send -->: 5A04116F * receive <-- (OK): 5A05110070 * receive <-- (FAIL): 5A05110071 * 7. When point 6 returned OK, than ready to connect to Tasmota! :) * When point 6 returned FAIL, start with item 1 and * - check connection of RX and TX pin are not exchanged * - check power supply * - 5V +-0,5V * - 110mA average * - 140mA max peak * * References: * - http://en.benewake.com/product * - https://de.aliexpress.com/item/32852024277.html?spm=a2g0s.9042311.0.0.27424c4d5Edizk * - https://de.aliexpress.com/item/4001076614996.html?spm=a2g0s.9042311.0.0.27424c4d5Edizk \*****************************************************************************/ #define XSNS_86 86 // Default baudrate #define TFMP_SPEED 9600 // Serial buffer length for incoming data #define TFMP_MAX_DATA_LEN 9 #include char Tfmp_buffer[TFMP_MAX_DATA_LEN + 1]; struct xsns_86_tfminiplus { bool ready = false; uint16_t distance = 0; uint16_t sigstrength = 0; uint16_t chiptemp = 0; } tfminiplus_sensor; // Software and hardware serial pointers TasmotaSerial *TfmpSerial = nullptr; void TfmpInit(void) { if (!tfminiplus_sensor.ready) { if (PinUsed(GPIO_TFMINIPLUS_RX) && PinUsed(GPIO_TFMINIPLUS_TX)) { TfmpSerial = new TasmotaSerial(Pin(GPIO_TFMINIPLUS_RX), Pin(GPIO_TFMINIPLUS_TX), 1); if (TfmpSerial->begin(TFMP_SPEED)) { if (TfmpSerial->hardwareSerial()) { ClaimSerial(); } #ifdef ESP32 AddLog(LOG_LEVEL_DEBUG, PSTR("TFM: Serial UART%d"), TfmpSerial->getUart()); #endif tfminiplus_sensor.ready = true; TfmpSerial->flush(); } } } } void TfmpTrigger(void) { if (TfmpSerial && tfminiplus_sensor.ready) { TfmpSerial->write(0x5A); TfmpSerial->write(0x04); TfmpSerial->write(0x04); TfmpSerial->write(0x62); } } void TfmpProcessData(void) { // check crc sum uint16_t crc = 0; for (int i = 0; i < TFMP_MAX_DATA_LEN - 1; ++i) { crc += (uint16_t)Tfmp_buffer[i]; } if ((char)(crc & 0xff) == Tfmp_buffer[TFMP_MAX_DATA_LEN-1]) { // distance to object (default in cm) tfminiplus_sensor.distance = (Tfmp_buffer[3] << 8) + Tfmp_buffer[2]; // signal strength (between 100 and 65535 fine, otherwise 0 due to out of range of non-reflective surface) tfminiplus_sensor.sigstrength = (Tfmp_buffer[5] << 8) + Tfmp_buffer[4]; // chip temperature tfminiplus_sensor.chiptemp = (((Tfmp_buffer[7] << 8) + Tfmp_buffer[6]) >> 3) - 256; DEBUG_SENSOR_LOG(PSTR("TFmini Plus: Distance: %d"), tfminiplus_sensor.distance); DEBUG_SENSOR_LOG(PSTR("TFmini Plus: Signal: %d"), tfminiplus_sensor.sigstrength); DEBUG_SENSOR_LOG(PSTR("TFmini Plus: Chip Temp: %d"), tfminiplus_sensor.chiptemp); } else { DEBUG_SENSOR_LOG(PSTR("TFmini Plus: crc error")); } TfmpSerial->flush(); } void TfmpProcessSerialData (void) { uint8_t data; bool dataReady; if (TfmpSerial && tfminiplus_sensor.ready) { while (TfmpSerial->available() > 0) { data = TfmpSerial->read(); dataReady = TfmpAddData((char)data); if (dataReady) { TfmpProcessData(); } } } } bool TfmpAddData(char nextChar) { // Buffer position static uint8_t currentIndex = 0; // Store data into buffer at position if ((currentIndex >0) && (0x59 == Tfmp_buffer[currentIndex-1]) && (0x59 == nextChar)) { currentIndex = 1; } Tfmp_buffer[currentIndex] = nextChar; currentIndex++; // Check for too many data if (currentIndex > TFMP_MAX_DATA_LEN) { // Terminate buffer and reset position Tfmp_buffer[TFMP_MAX_DATA_LEN] = '\0'; currentIndex = 0; return true; } return false; } #ifdef USE_WEBSERVER // {s} = , {m} = , {e} = const char HTTP_SNS_SIGNALSTRENGTH[] PROGMEM = "{s}%s " D_SIGNALSTRENGTH "{m}%d{e}"; const char HTTP_SNS_CHIPTEMPERATURE[] PROGMEM = "{s}%s " D_CHIPTEMPERATURE "{m}%d " D_UNIT_DEGREE "%c{e}"; #endif // USE_WEBSERVER void TfmpShow(bool json) { char sensor_name[12]; strcpy_P(sensor_name, "TFminiPlus"); float distance = (float)tfminiplus_sensor.distance; // cm if (json) { ResponseAppend_P(PSTR(",\"%s\":{\"" D_JSON_DISTANCE "\":%1_f,\"" D_JSON_SIGNALSTRENGTH "\":%d,\"" D_JSON_CHIPTEMPERATURE "\":%d}"), sensor_name, &distance, tfminiplus_sensor.sigstrength, tfminiplus_sensor.chiptemp); #ifdef USE_DOMOTICZ if (0 == TasmotaGlobal.tele_period) { DomoticzFloatSensor(DZ_COUNT, distance); } #endif // USE_DOMOTICZ #ifdef USE_WEBSERVER } else { WSContentSend_P(HTTP_SNS_F_DISTANCE_CM, sensor_name, &distance); WSContentSend_P(HTTP_SNS_SIGNALSTRENGTH, sensor_name, tfminiplus_sensor.sigstrength); WSContentSend_P(HTTP_SNS_CHIPTEMPERATURE, sensor_name, tfminiplus_sensor.chiptemp, TempUnit()); #endif // USE_WEBSERVER } } /*********************************************************************************************\ * Interface \*********************************************************************************************/ bool Xsns86(uint32_t callback_id) { bool result = false; if (FUNC_INIT == callback_id) { TfmpInit(); } else if (TfmpSerial && tfminiplus_sensor.ready) { switch (callback_id) { case FUNC_EVERY_SECOND: TfmpTrigger(); TfmpProcessSerialData(); result = true; break; case FUNC_JSON_APPEND: TfmpShow(1); break; #ifdef USE_WEBSERVER case FUNC_WEB_SENSOR: TfmpShow(0); break; #endif // USE_WEBSERVER case FUNC_SAVE_BEFORE_RESTART: break; case FUNC_COMMAND: break; } } return result; } #endif // USE_TFMINIPLUS