diff --git a/BUILDS.md b/BUILDS.md
index cb6e3de96..32aa08d78 100644
--- a/BUILDS.md
+++ b/BUILDS.md
@@ -215,6 +215,7 @@ Note: `minimal` variant is not listed as it shouldn't be used outside of the [up
| USE_PROJECTOR_CTRL | - | - / - | - | - | - | - |
| USE_AS608 | - | - / - | - | - | - | - |
| USE_LD2410 | - | - / - | - | - | - | - |
+| USE_GM861 | - | - / - | - | - | - | - |
| USE_TCP_BRIDGE | - | - / - | - | - | - | - | zbbridge / zbbrdgpro
| | | | | | | |
| USE_NRF24 | - | - / - | - | - | - | - |
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d2c63a54b..6532b2f1c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,7 +3,25 @@ All notable changes to this project will be documented in this file.
## [Unreleased] - Development
-## [12.5.0.1]
+## [12.5.0.2]
+### Added
+- Matter support for Shutters with Tilt
+- Matter POC for remote Relay
+- Support for Zero-Cross Dimmer on ESP32, changed calculation on EPS8266, high resolution control e.g. Solar: `ZCDimmerSet`
+- ESP32 Enhanced Shutterbuttons functionality to control tilt position, additionally incr/decr possible to position and tilt.
+- ESP32 `Shuttersetup` for "Shelly 2.5 pro" automatic calibration and setup (experimental)
+- Berry `tcpclientasync` class for non-blocking TCP client
+- Support for GM861 1D and 2D bar code reader (#18399)
+
+### Breaking Changed
+
+### Changed
+
+### Fixed
+
+### Removed
+
+## [12.5.0.1] 20230505
### Added
- Matter sensors Humidity, Pressure, Illuminance; optimize memory (#18441)
- Command ``SetOption152 0/1`` to select two (0 = default) pin bistable or one (1) pin latching relay control (#18386)
@@ -11,14 +29,6 @@ All notable changes to this project will be documented in this file.
- Matter UI to change endpoints configuration (#18498)
- Matter support for Shutters (without Tilt) (#18509)
- Support for TC74 temperature sensor by Michael Loftis (#18042)
-- Matter support for Shutters with Tilt
-- Matter POC for remote Relay
-- Added support for Zero-Cross Dimmer on ESP32, changed calculation on EPS8266, high resolution control e.g. Solar: `ZCDimmerSet`
-- ESP32: Enhanced Shutterbuttons functionality to control tilt position, additionally incr/decr possible to position and tilt.
-- ESP32: `Shuttersetup` for "Shelly 2.5 pro" automatic calibration and setup (experimental)
-- Berry add `tcpclientasync` class for non-blocking TCP client
-
-### Breaking Changed
### Changed
- ESP32 Framework (Core) from v2.0.7 to v2.0.8
@@ -31,8 +41,6 @@ All notable changes to this project will be documented in this file.
- Berry fix rules for string comparisons (#18464)
- Shutter: GarageMode does not stop on console commands, `ShutterSetOpen` and `ShutterSetClose` does not reset direction (#18539)
-### Removed
-
## [Released]
## [12.5.0] 20230417
diff --git a/RELEASENOTES.md b/RELEASENOTES.md
index df5c0d388..4d651e25a 100644
--- a/RELEASENOTES.md
+++ b/RELEASENOTES.md
@@ -110,14 +110,18 @@ The latter links can be used for OTA upgrades too like ``OtaUrl https://ota.tasm
[Complete list](BUILDS.md) of available feature and sensors.
-## Changelog v12.5.0.1
+## Changelog v12.5.0.2
### Added
- Command ``SetOption152 0/1`` to select two (0 = default) pin bistable or one (1) pin latching relay control [#18386](https://github.com/arendst/Tasmota/issues/18386)
- Support for TC74 temperature sensor by Michael Loftis [#18042](https://github.com/arendst/Tasmota/issues/18042)
+- Support for GM861 1D and 2D bar code reader [#18399](https://github.com/arendst/Tasmota/issues/18399)
- Matter sensors Humidity, Pressure, Illuminance [#18441](https://github.com/arendst/Tasmota/issues/18441)
- Matter allow `Matter#Initialized` rule once the device is configured [#18451](https://github.com/arendst/Tasmota/issues/18451)
- Matter UI to change endpoints configuration [#18498](https://github.com/arendst/Tasmota/issues/18498)
-- Matter support for Shutters (without Tilt) [#18509](https://github.com/arendst/Tasmota/issues/18509)
+- Matter support for Shutters with Tilt [#18509](https://github.com/arendst/Tasmota/issues/18509)
+- ESP32 Enhanced Shutterbuttons functionality to control tilt position, additionally incr/decr possible to position and tilt.
+- ESP32 `Shuttersetup` for "Shelly 2.5 pro" automatic calibration and setup (experimental)
+- Berry `tcpclientasync` class for non-blocking TCP client
### Breaking Changed
diff --git a/tasmota/include/tasmota_version.h b/tasmota/include/tasmota_version.h
index 5d31fe5bd..a98d9e299 100644
--- a/tasmota/include/tasmota_version.h
+++ b/tasmota/include/tasmota_version.h
@@ -20,6 +20,6 @@
#ifndef _TASMOTA_VERSION_H_
#define _TASMOTA_VERSION_H_
-const uint32_t VERSION = 0x0C050001; // 12.5.0.1
+const uint32_t VERSION = 0x0C050002; // 12.5.0.2
#endif // _TASMOTA_VERSION_H_
diff --git a/tasmota/my_user_config.h b/tasmota/my_user_config.h
index 0d4b8088a..3559b6303 100644
--- a/tasmota/my_user_config.h
+++ b/tasmota/my_user_config.h
@@ -831,7 +831,8 @@
// #define VINDRIKTNING_SHOW_PM1 // Display undocumented/supposed PM1.0 values
// #define VINDRIKTNING_SHOW_PM10 // Display undocumented/supposed PM10 values
//#define USE_LD2410 // Add support for HLK-LD2410 24GHz smart wave motion sensor (+2k8 code)
-// #define USE_LOX_O2 // Add support for LuminOx LOX O2 Sensor (+0k8 code)
+//#define USE_LOX_O2 // Add support for LuminOx LOX O2 Sensor (+0k8 code)
+//#define USE_GM861 // Add support for GM861 1D and 2D Bar Code Reader (+1k3 code)
// -- Power monitoring sensors --------------------
#define USE_ENERGY_SENSOR // Add support for Energy Monitors (+14k code)
diff --git a/tasmota/tasmota_xsns_sensor/xsns_107_gm861.ino b/tasmota/tasmota_xsns_sensor/xsns_107_gm861.ino
new file mode 100644
index 000000000..23c0b37e2
--- /dev/null
+++ b/tasmota/tasmota_xsns_sensor/xsns_107_gm861.ino
@@ -0,0 +1,329 @@
+/*
+ xsns_107_gm861.ino - Support for GM861 Bar Code Reader for Tasmota
+
+ Copyright (C) 2023 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_GM861
+/*********************************************************************************************\
+ * GM861 1D and 2D Bar Code Reader
+ *
+ * For background information see https://github.com/arendst/Tasmota/discussions/18399
+\*********************************************************************************************/
+
+#define XSNS_107 107
+
+/*
+#define GPIO_GM861_TX 304
+#define D_SENSOR_GM861_TX "GM861 Tx"
+#define GPIO_GM861_RX 305
+#define D_SENSOR_GM861_RX "GM861 Rx"
+
+Command ===================== Response ============== Description =========================================================
+Headr Ty Ln Addrs Data Check Headr Ty Ln Data Check
+----- -- -- ----- ----- ----- ----- -- -- ----- -----
+7E 00 07 01 00 2A 02 D8 0F 02 00 00 02 39 01 C1 4C Get baudrate (9600)
+7E 00 08 02 00 2A 39 01 A7 EA 02 00 00 02 39 01 SS SS Set baudrate to 9600
+7E 00 08 01 00 00 D6 AB CD LED on, Mute off, Normal lighting, Normal brightness, Continuous mode
+7E 00 08 01 00 02 01 AB CD 02 00 00 01 00 33 31 Command trigger Mode (Set bit0 of zone byte 0x0002)
+7E 00 08 01 00 02 01 AB CD Trigger mode
+7E 00 08 01 00 2C 02 AB CD Full area, Allow read all bar codes
+7E 00 08 01 00 36 01 AB CD Allow read Code39
+7E 00 08 01 00 37 00 AB CD Code39 Min length
+7E 00 08 01 00 38 FF AB CD Code39 Max length
+7E 00 08 01 00 B0 01 AB CD Cut out data:Output Start part
+7E 00 08 01 00 B1 FF AB CD Cut out M bytes from start
+7E 00 08 01 00 B0 02 AB CD Output End part
+7E 00 08 01 00 B2 FF AB CD Cut out N bytes from end
+7E 00 08 01 00 B0 03 AB CD Output center part
+7E 00 08 01 00 B1 03 AB CD Cut out N bytes from start (Eg: three characters)
+7E 00 08 01 00 B2 02 AB CD Cut out N bytes from end (Eg: two characters)
+7E 00 08 01 00 D9 50 81 D3 02 00 00 01 00 33 31 Zone bytes reset to defaults
+7E 00 08 01 00 D9 55 D1 76 02 00 00 01 00 33 31 Restore user-defined factory settings
+7E 00 08 01 00 D9 56 E1 15 02 00 00 01 00 33 31 Save as user-defined factory settings
+7E 00 08 01 00 D9 A5 3E 69 02 00 00 01 00 33 31 Deep sleep, can be awakened by serial port interrupt, this serial port command is invalid
+7E 00 08 01 00 D9 00 DB 26 02 00 00 01 00 33 31 Wake up from sleep
+7E 00 08 01 00 E7 00 AB CD OUT1 Output low level
+7E 00 08 01 00 E7 01 AB CD OUT1 Output high level
+7E 00 09 01 00 00 00 DE C8 02 00 00 01 00 33 31 Save settings to flash
+7E 00 0A 01 00 00 00 30 1A 03 00 00 01 00 33 31 Send heartbeat every 10 seconds
+*/
+
+enum Gm861States {
+ GM861_STATE_DONE,
+ GM861_STATE_DUMP,
+ GM861_STATE_SERIAL_OUTPUT,
+ GM861_STATE_OUTPUT,
+ GM861_STATE_SETUP_CODE_ON,
+ GM861_STATE_INIT_OFFSET = 16, // Init after (GM861_STATE_INIT_OFFSET - GM861_STATE_SETUP_CODE_ON) * 0.25 seconds restart
+ GM861_STATE_RESET
+};
+
+#include
+TasmotaSerial *Gm861Serial = nullptr;
+
+struct GDK {
+ char barcode[30];
+ uint8_t index;
+ uint8_t state;
+ bool heartbeat;
+ bool read;
+} Gm861;
+
+/*********************************************************************************************/
+
+uint32_t Gm861Crc(uint8_t* ptr, uint32_t len) {
+ // When no need for checking CRC, CRC byte can be filled in 0xAB 0xCD
+ uint32_t crc = 0;
+ while (len-- != 0) {
+ for (uint8_t i = 0x80; i != 0; i /= 2) {
+ crc *= 2;
+ if ((crc & 0x10000) !=0) { // After multiplying the last bit of CRC by 2, if the first bit is 1, then divide by 0x11021
+ crc ^= 0x11021;
+ }
+ if ((*ptr & i) != 0) { // If this bit is 1, then CRC = previous CRC + this bit/CRC_CCITT
+ crc ^= 0x1021;
+ }
+ }
+ ptr++;
+ }
+ return crc;
+}
+
+void Gm861Send(uint32_t type, uint32_t len, uint32_t address, uint32_t data) {
+ uint8_t buffer[10];
+
+ buffer[0] = 0x7E;
+ buffer[1] = 0;
+ buffer[2] = type;
+ buffer[3] = len;
+ buffer[4] = 0;
+ buffer[5] = address;
+ buffer[6] = data;
+ uint8_t index = 7;
+ if (len > 1) {
+ buffer[7] = data >> 8;
+ index++;
+ }
+ uint32_t crc = Gm861Crc(buffer+2, len + 4);
+ buffer[index] = crc >> 8;
+ index++;
+ buffer[index] = crc;
+ index++;
+
+ AddLogBuffer(LOG_LEVEL_DEBUG_MORE, (uint8_t*)buffer, index);
+
+ Gm861Serial->write(buffer, index);
+}
+
+void Gm861SetZone(uint32_t address, uint32_t data) {
+ Gm861.read = false;
+ uint32_t len = 1;
+ if (0x2A == address) { len = 2; } // Baudrate
+ Gm861Send(8, len, address, data);
+}
+
+void Gm861GetZone(uint32_t address) {
+ Gm861.read = true;
+ Gm861.index = address;
+ uint32_t data = 1;
+ if (0x2A == address) { data = 2; } // Baudrate
+ Gm861Send(7, 1, address, data);
+}
+
+void Gm861SerialInput(void) {
+ if (!Gm861Serial->available()) { return; }
+
+ char buffer[256];
+ uint32_t byte_counter = 0;
+
+ // Use timeout to allow large serial reads within a window
+ uint32_t timeout = millis();
+ while (millis() - 10 < timeout) {
+ if (Gm861Serial->available()) {
+ timeout = millis();
+ buffer[byte_counter++] = Gm861Serial->read();
+ if (byte_counter >= sizeof(buffer) -1) { break; }
+ }
+ }
+ buffer[byte_counter] = 0; // Add string terminating zero
+
+ AddLogBuffer(LOG_LEVEL_DEBUG_MORE, (uint8_t*)buffer, byte_counter);
+
+ if (0 == buffer[1]) { // Command result or Heartbeat
+ if (2 == buffer[0]) { // Command result
+ // 02 00 00 01 00 33 31 - Command acknowledge
+ // 02 00 00 01 01 23 10 - Command result (Zonebyte 96 - 0x60)
+ // 02 00 00 02 39 01 C1 4C - Command result (Zonebytes 42/43 - 0x2A)
+ if (Gm861.read) {
+ uint32_t result = buffer[4];
+ if (2 == buffer[3]) { // Length
+ result += (buffer[5] << 8);
+ }
+ Gm861.read = false;
+ Response_P(S_JSON_COMMAND_INDEX_NVALUE, PSTR("GM861Zone"), Gm861.index, result);
+ MqttPublishPrefixTopicRulesProcess_P(RESULT_OR_STAT, PSTR("GM861Zone"));
+ }
+ }
+ else if (3 == buffer[0]) { // Heartbeat
+ // 03 00 00 01 00 33 31 - Heartbeat response
+ // 03 00 0A 30 33 32 31 35 36 33 38 34 30 - Scan response with zone byte 96 = 0x81
+ Gm861.heartbeat = true;
+ }
+ } else { // Bar code
+ // 38 37 31 31 32 31 38 39 37 32 38 37 35 0D - Barcode 8711218972875
+ RemoveControlCharacter(buffer); // Remove control character (0x00 .. 0x1F and 0x7F)
+ snprintf_P(Gm861.barcode, sizeof(Gm861.barcode) -3, PSTR("%s"), buffer);
+ if (strlen(buffer) > sizeof(Gm861.barcode) -3) {
+ strcat(Gm861.barcode, "...");
+ }
+ ResponseTime_P(PSTR(",\"GM861\":\"%s\"}"), buffer);
+ MqttPublishTeleSensor();
+ }
+}
+
+/********************************************************************************************/
+
+void Gm861Init(void) {
+ if (PinUsed(GPIO_GM861_RX) && PinUsed(GPIO_GM861_TX)) {
+ Gm861Serial = new TasmotaSerial(Pin(GPIO_GM861_RX), Pin(GPIO_GM861_TX), 1);
+ if (Gm861Serial->begin(9600)) {
+ if (Gm861Serial->hardwareSerial()) {
+ ClaimSerial();
+ }
+ Gm861.barcode[0] = '0'; // No barcode yet
+ Gm861.state = GM861_STATE_INIT_OFFSET;
+// AddLog(LOG_LEVEL_INFO, PSTR("GM8: Connected"));
+ }
+ }
+}
+
+void Gm861StateMachine(void) {
+ /*
+ Power on
+ 14:25:04.219-025 DMP: 02 00 00 62 D6 00 20 00 00 0A 32 01 2C 00 87 3C 01 A1 1C 32 03 00 80 00 06 00 00 00 00 00 00 00 00 00 00 00 00 02 80 3C 00 00 00 06 00 00 39 01 05 64 0D 0D 0D 01 0D 01 04 80 09 04 80 05 04 80 01 04 80 01 08 04 80 08 04 80 08 04 80 08 04 80 08 01 80 00 00 00 04 80 01 01 00 00 00 00 04 80 00 00 03 00 01 00 30 FE
+ Default: setup code on
+ 12:12:18.672-024 DMP: 02 00 00 61 D6 00 20 00 00 0A 32 01 2C 00 87 3C 01 A1 1C 32 03 00 80 00 06 00 00 00 00 00 00 00 00 00 00 00 00 02 80 3C 00 00 00 06 00 00 39 01 05 64 0D 0D 0D 01 0D 01 04 80 09 04 80 05 04 80 01 04 80 01 08 04 80 08 04 80 08 04 80 08 04 80 08 01 80 00 00 00 04 80 01 01 00 00 00 00 04 80 00 00 03 00 01 00 30 FE
+ Output:
+ 14:37:45.129-027 DMP: 02 00 00 62 D6 00 20 01 00 0A 32 01 2C 00 87 3C 01 A1 1C 32 03 00 80 00 06 00 00 00 00 00 00 00 00 00 00 00 00 02 80 3C 00 00 00 06 00 00 39 01 05 64 0D 0D 0D 01 0D 01 04 80 09 04 80 05 04 80 01 04 80 01 08 04 80 08 04 80 08 04 80 08 04 80 08 01 80 00 00 00 04 80 01 01 00 00 00 00 04 80 00 00 03 00 01 00 20 91
+ Serial output:
+ 14:39:04.887-027 DMP: 02 00 00 62 D6 00 20 01 00 0A 32 01 2C 00 87 3C 01 A0 1C 32 03 00 80 00 06 00 00 00 00 00 00 00 00 00 00 00 00 02 80 3C 00 00 00 06 00 00 39 01 05 64 0D 0D 0D 01 0D 01 04 80 09 04 80 05 04 80 01 04 80 01 08 04 80 08 04 80 08 04 80 08 04 80 08 01 80 00 00 00 04 80 01 01 00 00 00 00 04 80 00 00 03 00 01 00 2D 9E
+ */
+ if (!Gm861.state) { return; }
+
+ switch (Gm861.state) {
+ case GM861_STATE_RESET:
+ Gm861SetZone(0xD9, 0x50); // Factory reset
+ Gm861.state = GM861_STATE_SETUP_CODE_ON +7; // Add time for reset to complete
+ break;
+ case GM861_STATE_SETUP_CODE_ON:
+ Gm861SetZone(0x00, 0xD6); // Set LED on, Mute off, Normal lighting, Normal brightness, Continuous mode (Default: setup code on)
+ break;
+ case GM861_STATE_OUTPUT:
+ Gm861SetZone(0x03, 0x01); // Enable output (Output)
+ break;
+ case GM861_STATE_SERIAL_OUTPUT:
+ Gm861SetZone(0x0D, 0xA0); // Enable serial port output (Serial Output)
+ break;
+ case GM861_STATE_DUMP:
+ Gm861Send(7, 1, 0, 0x62); // Dump zone bytes 0 to 97
+ AddLog(LOG_LEVEL_INFO, PSTR("GM8: Initialized"));
+ break;
+ }
+ Gm861.state--;
+}
+
+/*********************************************************************************************\
+ * Commands
+\*********************************************************************************************/
+
+const char kGm861Commands[] PROGMEM = "GM861|" // Prefix
+ "Zone|Reset|Dump";
+
+void (* const Gm861Command[])(void) PROGMEM = {
+ &CmndGm816Zone, &CmndGm816Reset, &CmndGm816Dump };
+
+void CmndGm816Zone(void) {
+ // GM861Zone42 - Read baudrate
+ // GM861Zone0 0xD6 - Set LED on, Mute off, Normal lighting, Normal brightness, Continuous mode
+ if ((XdrvMailbox.index >= 0x00) && (XdrvMailbox.index <= 0xD9)) {
+ if ((XdrvMailbox.payload >= 0x00) && (XdrvMailbox.payload <= 0x09C4)) {
+ Gm861SetZone(XdrvMailbox.index, XdrvMailbox.payload);
+ ResponseCmndIdxNumber(XdrvMailbox.payload);
+ } else {
+ Gm861GetZone(XdrvMailbox.index);
+ ResponseClear();
+ }
+ }
+}
+
+void CmndGm816Reset(void) {
+ // GM861Reset 1 - Do factory reset and inititalize for serial comms
+ if (1 == XdrvMailbox.payload) {
+ Gm861.state = GM861_STATE_RESET;
+ ResponseCmndDone();
+ }
+}
+
+void CmndGm816Dump(void) {
+ // GM861Dump - Dump zone bytes 0 to 97. Needs logging level 4
+ Gm861Send(7, 1, 0, 0x62); // Dump zone bytes 0 to 97
+ ResponseCmndDone();
+}
+
+/*********************************************************************************************\
+ * Presentation
+\*********************************************************************************************/
+
+#ifdef USE_WEBSERVER
+void Gm861Show(void) {
+ WSContentSend_PD(PSTR("{s}GM816{m}%s{e}"), Gm861.barcode);
+}
+#endif // USE_WEBSERVER
+
+/*********************************************************************************************\
+ Interface
+\*********************************************************************************************/
+
+bool Xsns107(uint32_t function) {
+ bool result = false;
+
+ if (FUNC_INIT == function) {
+ Gm861Init();
+ }
+ else if (Gm861Serial) {
+ switch (function) {
+ case FUNC_LOOP:
+ case FUNC_SLEEP_LOOP:
+ Gm861SerialInput();
+ break;
+ case FUNC_EVERY_250_MSECOND:
+ Gm861StateMachine();
+ break;
+#ifdef USE_WEBSERVER
+ case FUNC_WEB_SENSOR:
+ Gm861Show();
+ break;
+#endif // USE_WEBSERVER
+ case FUNC_COMMAND:
+ result = DecodeCommand(kGm861Commands, Gm861Command);
+ break;
+ }
+ }
+ return result;
+}
+
+#endif // USE_GM861