Merge branch 'development' into ina3221

This commit is contained in:
Barbudor 2022-10-09 20:02:27 +02:00
commit 5f6d0f8310
32 changed files with 738 additions and 312 deletions

View File

@ -6,11 +6,15 @@ All notable changes to this project will be documented in this file.
## [12.1.1.4]
### Added
- Support for Shelly Plus 2PM using template ``{"NAME":"Shelly Plus 2PM PCB v0.1.9","GPIO":[320,0,0,0,32,192,0,0,225,224,0,0,0,0,193,0,0,0,0,0,0,608,640,3458,0,0,0,0,0,9472,0,4736,0,0,0,0],"FLAG":0,"BASE":1,"CMND":"AdcParam1 2,10000,10000,3350"}``
- Zigbee Alexa/Hue emulation, support multiple switches on separate endpoints
- Zigbee Alexa/Hue emulation, support multiple switches on separate endpoints (#16718)
- Support for QMC5883L magnetic induction sensor by Helge Scheunemann (#16714)
- LVGL/HASPmota add tiny "pixel perfect" fonts for small screens (#16758)
- HASPmota support for TTF fonts (#16759)
- Support for Modbus Energy Monitoring devices using a rule file. See ``xnrg_29_modbus.ino`` for more information
### Changed
- ESP32 LVGL library from v8.3.0 to v8.3.2
- Increase serial console fixed input buffer size from 520 to 800
### Fixed

View File

@ -124,6 +124,7 @@ The latter links can be used for OTA upgrades too like ``OtaUrl http://ota.tasmo
- Zigbee device plugin mechanism with commands ``ZbLoad``, ``ZbUnload`` and ``ZbLoadDump`` [#16252](https://github.com/arendst/Tasmota/issues/16252)
- Zigbee basic support for Green Power [#16407](https://github.com/arendst/Tasmota/issues/16407)
- Zigbee friendly names per endpoint
- Zigbee Alexa/Hue emulation, support multiple switches on separate endpoints [#16718](https://github.com/arendst/Tasmota/issues/16718)
- Flowrate meter flow amount/duration, show values in table format [#16385](https://github.com/arendst/Tasmota/issues/16385)
- Support of optional file calib.dat on ADE7953 based energy monitors like Shelly EM [#16486](https://github.com/arendst/Tasmota/issues/16486)
- Support for Ethernet in ESP32 safeboot firmware [#16388](https://github.com/arendst/Tasmota/issues/16388)
@ -131,14 +132,17 @@ The latter links can be used for OTA upgrades too like ``OtaUrl http://ota.tasmo
- ESP32-S2 and ESP32-S3 touch button support
- Berry has persistent MQTT subscriptions: auto-subscribe at (re)connection
- Berry automated solidification of code
- LVGL/HASPmota add tiny "pixel perfect" fonts for small screens [#16758](https://github.com/arendst/Tasmota/issues/16758)
- HASPmota support for TTF fonts [#16759](https://github.com/arendst/Tasmota/issues/16759)
### Breaking Changed
### Changed
- ESP32 NimBLE library from v1.3.6 to v1.4.0
- IRremoteESP8266 library from v2.8.2 to v2.8.4
- Tasmota Core32 from 2.0.4.1 to 2.0.5
- TasmotaModbus library from v3.5.0 to v3.6.0 [#16351](https://github.com/arendst/Tasmota/issues/16351)
- ESP32 NimBLE library from v1.3.6 to v1.4.0
- ESP32 LVGL library from v8.3.0 to v8.3.2
- ESP32 Tasmota Core32 from 2.0.4.1 to 2.0.5
- Button debouncing V3 by adopting switch debounce code [#16339](https://github.com/arendst/Tasmota/issues/16339)
- Thermostat max allowed temperature from 100 to 200C [#16363](https://github.com/arendst/Tasmota/issues/16363)
- Using command ``SerialBuffer`` raise max allowed buffer size to 2048 characters [#16374](https://github.com/arendst/Tasmota/issues/16374)

View File

@ -37,6 +37,7 @@ static int m_dump(bvm *vm)
time_insert(vm, "min", t->tm_min);
time_insert(vm, "sec", t->tm_sec);
time_insert(vm, "weekday", t->tm_wday);
time_insert(vm, "epoch", ts);
be_pop(vm, 1);
be_return(vm);
}

View File

@ -1719,7 +1719,7 @@ be_local_closure(lvh_obj_get_pad_left, /* name */
********************************************************************/
be_local_closure(lvh_obj_set_text_font, /* name */
be_nested_proto(
12, /* nstack */
16, /* nstack */
2, /* argc */
2, /* varg */
0, /* has upvals */
@ -1727,28 +1727,32 @@ be_local_closure(lvh_obj_set_text_font, /* name */
0, /* has sup protos */
NULL, /* no sub protos */
1, /* has constants */
( &(const bvalue[17]) { /* constants */
( &(const bvalue[21]) { /* constants */
/* K0 */ be_nested_str_weak(int),
/* K1 */ be_nested_str_weak(lv),
/* K2 */ be_nested_str_weak(font_embedded),
/* K3 */ be_nested_str_weak(robotocondensed),
/* K4 */ be_nested_str_weak(montserrat),
/* K5 */ be_nested_str_weak(string),
/* K6 */ be_nested_str_weak(split),
/* K7 */ be_nested_str_weak(_X3A),
/* K8 */ be_const_int(1),
/* K9 */ be_nested_str_weak(_X2D),
/* K10 */ be_const_int(0),
/* K11 */ be_nested_str_weak(load_font),
/* K6 */ be_nested_str_weak(re),
/* K7 */ be_nested_str_weak(split),
/* K8 */ be_nested_str_weak(_X3A),
/* K9 */ be_const_int(1),
/* K10 */ be_nested_str_weak(_X2D),
/* K11 */ be_const_int(0),
/* K12 */ be_const_int(2),
/* K13 */ be_nested_str_weak(concat),
/* K14 */ be_nested_str_weak(_lv_obj),
/* K15 */ be_nested_str_weak(set_style_text_font),
/* K16 */ be_nested_str_weak(HSP_X3A_X20Unsupported_X20font_X3A),
/* K14 */ be_nested_str_weak(match),
/* K15 */ be_nested_str_weak(_X2E_X2A_X5C_X2Ettf_X24),
/* K16 */ be_nested_str_weak(load_freetype_font),
/* K17 */ be_nested_str_weak(load_font),
/* K18 */ be_nested_str_weak(_lv_obj),
/* K19 */ be_nested_str_weak(set_style_text_font),
/* K20 */ be_nested_str_weak(HSP_X3A_X20Unsupported_X20font_X3A),
}),
be_str_weak(set_text_font),
&be_const_str_solidified,
( &(const binstruction[114]) { /* code */
( &(const binstruction[143]) { /* code */
0x4C080000, // 0000 LDNIL R2
0x600C0004, // 0001 GETGBL R3 G4
0x5C100200, // 0002 MOVE R4 R1
@ -1782,87 +1786,116 @@ be_local_closure(lvh_obj_set_text_font, /* name */
0xB0080000, // 001E RAISE 2 R0 R0
0x70020000, // 001F JMP #0021
0xB0080000, // 0020 RAISE 2 R0 R0
0x70020041, // 0021 JMP #0064
0x7002005E, // 0021 JMP #0081
0x600C0004, // 0022 GETGBL R3 G4
0x5C100200, // 0023 MOVE R4 R1
0x7C0C0200, // 0024 CALL R3 1
0x1C0C0705, // 0025 EQ R3 R3 K5
0x780E003C, // 0026 JMPF R3 #0064
0x780E0059, // 0026 JMPF R3 #0081
0xA40E0A00, // 0027 IMPORT R3 K5
0x8C100706, // 0028 GETMET R4 R3 K6
0x5C180200, // 0029 MOVE R6 R1
0x581C0007, // 002A LDCONST R7 K7
0xA4120C00, // 0028 IMPORT R4 K6
0x8C140707, // 0029 GETMET R5 R3 K7
0x5C1C0200, // 002A MOVE R7 R1
0x58200008, // 002B LDCONST R8 K8
0x7C100800, // 002C CALL R4 4
0x8C140706, // 002D GETMET R5 R3 K6
0x5C1C0200, // 002E MOVE R7 R1
0x58200009, // 002F LDCONST R8 K9
0x7C140600, // 0030 CALL R5 3
0x6018000C, // 0031 GETGBL R6 G12
0x5C1C0800, // 0032 MOVE R7 R4
0x7C180200, // 0033 CALL R6 1
0x24180D08, // 0034 GT R6 R6 K8
0x781A000A, // 0035 JMPF R6 #0041
0x6018000C, // 0036 GETGBL R6 G12
0x941C090A, // 0037 GETIDX R7 R4 K10
0x7C180200, // 0038 CALL R6 1
0x1C180D08, // 0039 EQ R6 R6 K8
0x781A0005, // 003A JMPF R6 #0041
0xB81A0200, // 003B GETNGBL R6 K1
0x8C180D0B, // 003C GETMET R6 R6 K11
0x5C200200, // 003D MOVE R8 R1
0x7C180400, // 003E CALL R6 2
0x5C080C00, // 003F MOVE R2 R6
0x70020022, // 0040 JMP #0064
0x6018000C, // 0041 GETGBL R6 G12
0x5C1C0A00, // 0042 MOVE R7 R5
0x7C180200, // 0043 CALL R6 1
0x28180D0C, // 0044 GE R6 R6 K12
0x781A001D, // 0045 JMPF R6 #0064
0x60180009, // 0046 GETGBL R6 G9
0x541DFFFE, // 0047 LDINT R7 -1
0x941C0A07, // 0048 GETIDX R7 R5 R7
0x7C180200, // 0049 CALL R6 1
0x541DFFFD, // 004A LDINT R7 -2
0x401E1407, // 004B CONNECT R7 K10 R7
0x941C0A07, // 004C GETIDX R7 R5 R7
0x8C1C0F0D, // 004D GETMET R7 R7 K13
0x58240009, // 004E LDCONST R9 K9
0x7C1C0400, // 004F CALL R7 2
0x24200D0A, // 0050 GT R8 R6 K10
0x78220011, // 0051 JMPF R8 #0064
0x6020000C, // 0052 GETGBL R8 G12
0x5C240E00, // 0053 MOVE R9 R7
0x7C200200, // 0054 CALL R8 1
0x2420110A, // 0055 GT R8 R8 K10
0x7822000C, // 0056 JMPF R8 #0064
0xA8020007, // 0057 EXBLK 0 #0060
0xB8220200, // 0058 GETNGBL R8 K1
0x8C201102, // 0059 GETMET R8 R8 K2
0x5C280E00, // 005A MOVE R10 R7
0x5C2C0C00, // 005B MOVE R11 R6
0x7C200600, // 005C CALL R8 3
0x5C081000, // 005D MOVE R2 R8
0xA8040001, // 005E EXBLK 1 1
0x70020003, // 005F JMP #0064
0xAC200000, // 0060 CATCH R8 0 0
0x70020000, // 0061 JMP #0063
0x70020000, // 0062 JMP #0064
0xB0080000, // 0063 RAISE 2 R0 R0
0x4C0C0000, // 0064 LDNIL R3
0x200C0403, // 0065 NE R3 R2 R3
0x780E0005, // 0066 JMPF R3 #006D
0x880C010E, // 0067 GETMBR R3 R0 K14
0x8C0C070F, // 0068 GETMET R3 R3 K15
0x5C140400, // 0069 MOVE R5 R2
0x5818000A, // 006A LDCONST R6 K10
0x7C0C0600, // 006B CALL R3 3
0x70020003, // 006C JMP #0071
0x600C0001, // 006D GETGBL R3 G1
0x58100010, // 006E LDCONST R4 K16
0x5C140200, // 006F MOVE R5 R1
0x7C0C0400, // 0070 CALL R3 2
0x80000000, // 0071 RET 0
0x58240009, // 002C LDCONST R9 K9
0x7C140800, // 002D CALL R5 4
0x8C180707, // 002E GETMET R6 R3 K7
0x5C200200, // 002F MOVE R8 R1
0x5824000A, // 0030 LDCONST R9 K10
0x7C180600, // 0031 CALL R6 3
0x5C1C0200, // 0032 MOVE R7 R1
0x5820000B, // 0033 LDCONST R8 K11
0x50240000, // 0034 LDBOOL R9 0 0
0x6028000C, // 0035 GETGBL R10 G12
0x5C2C0A00, // 0036 MOVE R11 R5
0x7C280200, // 0037 CALL R10 1
0x24281509, // 0038 GT R10 R10 K9
0x782A0003, // 0039 JMPF R10 #003E
0x6028000C, // 003A GETGBL R10 G12
0x942C0B0B, // 003B GETIDX R11 R5 K11
0x7C280200, // 003C CALL R10 1
0x742A0000, // 003D JMPT R10 #003F
0x50280001, // 003E LDBOOL R10 0 1
0x50280200, // 003F LDBOOL R10 1 0
0x602C000C, // 0040 GETGBL R11 G12
0x5C300C00, // 0041 MOVE R12 R6
0x7C2C0200, // 0042 CALL R11 1
0x282C170C, // 0043 GE R11 R11 K12
0x782E000B, // 0044 JMPF R11 #0051
0x602C0009, // 0045 GETGBL R11 G9
0x5431FFFE, // 0046 LDINT R12 -1
0x94300C0C, // 0047 GETIDX R12 R6 R12
0x7C2C0200, // 0048 CALL R11 1
0x5C201600, // 0049 MOVE R8 R11
0x542DFFFD, // 004A LDINT R11 -2
0x402E160B, // 004B CONNECT R11 K11 R11
0x942C0C0B, // 004C GETIDX R11 R6 R11
0x8C2C170D, // 004D GETMET R11 R11 K13
0x5834000A, // 004E LDCONST R13 K10
0x7C2C0400, // 004F CALL R11 2
0x5C1C1600, // 0050 MOVE R7 R11
0x8C2C090E, // 0051 GETMET R11 R4 K14
0x5834000F, // 0052 LDCONST R13 K15
0x5C380E00, // 0053 MOVE R14 R7
0x7C2C0600, // 0054 CALL R11 3
0x782E0006, // 0055 JMPF R11 #005D
0x8C2C0707, // 0056 GETMET R11 R3 K7
0x5C340E00, // 0057 MOVE R13 R7
0x58380008, // 0058 LDCONST R14 K8
0x7C2C0600, // 0059 CALL R11 3
0x5431FFFE, // 005A LDINT R12 -1
0x941C160C, // 005B GETIDX R7 R11 R12
0x50240200, // 005C LDBOOL R9 1 0
0x78260007, // 005D JMPF R9 #0066
0xB82E0200, // 005E GETNGBL R11 K1
0x8C2C1710, // 005F GETMET R11 R11 K16
0x5C340E00, // 0060 MOVE R13 R7
0x5C381000, // 0061 MOVE R14 R8
0x583C000B, // 0062 LDCONST R15 K11
0x7C2C0800, // 0063 CALL R11 4
0x5C081600, // 0064 MOVE R2 R11
0x7002001A, // 0065 JMP #0081
0x782A0005, // 0066 JMPF R10 #006D
0xB82E0200, // 0067 GETNGBL R11 K1
0x8C2C1711, // 0068 GETMET R11 R11 K17
0x5C340200, // 0069 MOVE R13 R1
0x7C2C0400, // 006A CALL R11 2
0x5C081600, // 006B MOVE R2 R11
0x70020013, // 006C JMP #0081
0x242C110B, // 006D GT R11 R8 K11
0x782E0011, // 006E JMPF R11 #0081
0x602C000C, // 006F GETGBL R11 G12
0x5C300E00, // 0070 MOVE R12 R7
0x7C2C0200, // 0071 CALL R11 1
0x242C170B, // 0072 GT R11 R11 K11
0x782E000C, // 0073 JMPF R11 #0081
0xA8020007, // 0074 EXBLK 0 #007D
0xB82E0200, // 0075 GETNGBL R11 K1
0x8C2C1702, // 0076 GETMET R11 R11 K2
0x5C340E00, // 0077 MOVE R13 R7
0x5C381000, // 0078 MOVE R14 R8
0x7C2C0600, // 0079 CALL R11 3
0x5C081600, // 007A MOVE R2 R11
0xA8040001, // 007B EXBLK 1 1
0x70020003, // 007C JMP #0081
0xAC2C0000, // 007D CATCH R11 0 0
0x70020000, // 007E JMP #0080
0x70020000, // 007F JMP #0081
0xB0080000, // 0080 RAISE 2 R0 R0
0x4C0C0000, // 0081 LDNIL R3
0x200C0403, // 0082 NE R3 R2 R3
0x780E0005, // 0083 JMPF R3 #008A
0x880C0112, // 0084 GETMBR R3 R0 K18
0x8C0C0713, // 0085 GETMET R3 R3 K19
0x5C140400, // 0086 MOVE R5 R2
0x5818000B, // 0087 LDCONST R6 K11
0x7C0C0600, // 0088 CALL R3 3
0x70020003, // 0089 JMP #008E
0x600C0001, // 008A GETGBL R3 G1
0x58100014, // 008B LDCONST R4 K20
0x5C140200, // 008C MOVE R5 R1
0x7C0C0400, // 008D CALL R3 2
0x80000000, // 008E RET 0
})
)
);

View File

@ -535,20 +535,35 @@ class lvh_obj
end
elif type(t) == 'string'
import string
import re
# look for 'A:name.font' style font file name
var drive_split = string.split(t, ':', 1)
var fn_split = string.split(t, '-')
if size(drive_split) > 1 && size(drive_split[0]) == 1
var name = t
var sz = 0
var is_ttf = false
var is_binary = (size(drive_split) > 1 && size(drive_split[0]))
if size(fn_split) >= 2
sz = int(fn_split[-1])
name = fn_split[0..-2].concat('-') # rebuild the font name
end
if re.match(".*\\.ttf$", name)
# ttf font
name = string.split(name, ':')[-1] # remove A: if any
is_ttf = true
end
if is_ttf
font = lv.load_freetype_font(name, sz, 0)
elif is_binary
# font is from disk
font = lv.load_font(t)
elif size(fn_split) >= 2 # it does contain '-'
var sz = int(fn_split[-1])
var name = fn_split[0..-2].concat('-') # rebuild the font name
if sz > 0 && size(name) > 0 # looks good, let's have a try
try
font = lv.font_embedded(name, sz)
except ..
end
elif sz > 0 && size(name) > 0 # looks good, let's have a try
try
font = lv.font_embedded(name, sz)
except ..
end
end
end

View File

@ -196,8 +196,9 @@ const uint8_t OTA_ATTEMPTS = 10; // Number of times to try fetching t
const uint8_t OTA_ATTEMPTS = 5; // Number of times to try fetching the new firmware
#endif // ESP8266
const uint16_t INPUT_BUFFER_SIZE = 520; // Max number of characters in Tasmota serial command buffer
const uint16_t MIN_INPUT_BUFFER_SIZE = 256; // Max number of characters in Tasmota serial command buffer
//const uint16_t INPUT_BUFFER_SIZE = 520; // Max number of characters in Tasmota serial command buffer
const uint16_t INPUT_BUFFER_SIZE = 800; // Max number of characters in Tasmota serial command buffer
const uint16_t MIN_INPUT_BUFFER_SIZE = 256; // Max number of characters in Tasmota serial command buffer
const uint16_t MAX_INPUT_BUFFER_SIZE = 2048; // Max number of characters in Arduino serial command buffer
const uint16_t FLOATSZ = 16; // Max number of characters in float result from dtostrfd (max 32)
const uint16_t CMDSZ = 24; // Max number of characters in command

View File

@ -230,6 +230,7 @@ struct mi_sensor_t{
uint8_t lastCnt; //device generated counter of the packet
uint8_t shallSendMQTT;
uint8_t MAC[6];
uint16_t PID;
uint8_t *key;
uint32_t lastTimeSeen;
union {
@ -335,6 +336,7 @@ const char kMI32_Commands[] PROGMEM = D_CMND_MI32 "|Key|Cfg|Option";
void (*const MI32_Commands[])(void) PROGMEM = {&CmndMi32Key, &CmndMi32Cfg, &CmndMi32Option };
#define UNKNOWN_MI 0
#define FLORA 1
#define MJ_HT_V1 2
#define LYWSD02 3
@ -351,8 +353,9 @@ void (*const MI32_Commands[])(void) PROGMEM = {&CmndMi32Key, &CmndMi32Cfg, &Cmnd
#define SJWS01L 14
#define PVVX 15
#define YLKG08 16
#define YLAI003 17
#define MI32_TYPES 16 //count this manually
#define MI32_TYPES 17 //count this manually
const uint16_t kMI32DeviceID[MI32_TYPES]={ 0x0098, // Flora
0x01aa, // MJ_HT_V1
@ -369,29 +372,11 @@ const uint16_t kMI32DeviceID[MI32_TYPES]={ 0x0098, // Flora
0x098b, // MCCGQ02
0x0863, // SJWS01L
0x944a, // PVVX -> this is a fake ID
0x03b6 // YLKG08 and YLKG07 - version w/wo mains
0x03b6, // YLKG08 and YLKG07 - version w/wo mains
0x07bf, // YLAI003
};
const char kMI32DeviceType1[] PROGMEM = "Flora";
const char kMI32DeviceType2[] PROGMEM = "MJ_HT_V1";
const char kMI32DeviceType3[] PROGMEM = "LYWSD02";
const char kMI32DeviceType4[] PROGMEM = "LYWSD03";
const char kMI32DeviceType5[] PROGMEM = "CGG1";
const char kMI32DeviceType6[] PROGMEM = "CGD1";
const char kMI32DeviceType7[] PROGMEM = "NLIGHT";
const char kMI32DeviceType8[] PROGMEM = "MJYD2S";
const char kMI32DeviceType9[] PROGMEM = "YLYK01"; //old name yeerc
const char kMI32DeviceType10[] PROGMEM ="MHOC401";
const char kMI32DeviceType11[] PROGMEM ="MHOC303";
const char kMI32DeviceType12[] PROGMEM ="ATC";
const char kMI32DeviceType13[] PROGMEM ="MCCGQ02";
const char kMI32DeviceType14[] PROGMEM ="SJWS01L";
const char kMI32DeviceType15[] PROGMEM ="PVVX";
const char kMI32DeviceType16[] PROGMEM ="YLKG08";
const char * kMI32DeviceType[] PROGMEM = {kMI32DeviceType1,kMI32DeviceType2,kMI32DeviceType3,kMI32DeviceType4,
kMI32DeviceType5,kMI32DeviceType6,kMI32DeviceType7,kMI32DeviceType8,
kMI32DeviceType9,kMI32DeviceType10,kMI32DeviceType11,kMI32DeviceType12,
kMI32DeviceType13,kMI32DeviceType14,kMI32DeviceType15,kMI32DeviceType16};
const char kMI32DeviceType[] PROGMEM = {"Flora|MJ_HT_V1|LYWSD02|LYWSD03|CGG1|CGD1|NLIGHT|MJYD2S|YLYK01|MHOC401|MHOC303|ATC|MCCGQ02|SJWS01L|PVVX|YLKG08|YLAI003"};
const char kMI32_ConnErrorMsg[] PROGMEM = "no Error|could not connect|did disconnect|got no service|got no characteristic|can not read|can not notify|can not write|did not write|notify time out";

View File

@ -0,0 +1,20 @@
KREATIVE SOFTWARE RELAY FONTS FREE USE LICENSE
version 1.2f
Permission is hereby granted, free of charge, to any person or entity (the "User") obtaining a copy of the included font files (the "Software") produced by Kreative Software, to utilize, display, embed, or redistribute the Software, subject to the following conditions:
1. The User may not sell copies of the Software for a fee.
1a. The User may give away copies of the Software free of charge provided this license and any documentation is included verbatim and credit is given to Kreative Korporation or Kreative Software.
2. The User may not modify, reverse-engineer, or create any derivative works of the Software.
3. Any Software carrying the following font names or variations thereof is not covered by this license and may not be used under the terms of this license: Jewel Hill, Miss Diode n Friends, This is Beckie's font!
3a. Any Software carrying a font name ending with the string "Pro CE" is not covered by this license and may not be used under the terms of this license.
4. This license becomes null and void if any of the above conditions are not met.
5. Kreative Software reserves the right to change this license at any time without notice.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE SOFTWARE OR FROM OTHER DEALINGS IN THE SOFTWARE.

View File

@ -0,0 +1,20 @@
KREATIVE SOFTWARE RELAY FONTS FREE USE LICENSE
version 1.2f
Permission is hereby granted, free of charge, to any person or entity (the "User") obtaining a copy of the included font files (the "Software") produced by Kreative Software, to utilize, display, embed, or redistribute the Software, subject to the following conditions:
1. The User may not sell copies of the Software for a fee.
1a. The User may give away copies of the Software free of charge provided this license and any documentation is included verbatim and credit is given to Kreative Korporation or Kreative Software.
2. The User may not modify, reverse-engineer, or create any derivative works of the Software.
3. Any Software carrying the following font names or variations thereof is not covered by this license and may not be used under the terms of this license: Jewel Hill, Miss Diode n Friends, This is Beckie's font!
3a. Any Software carrying a font name ending with the string "Pro CE" is not covered by this license and may not be used under the terms of this license.
4. This license becomes null and void if any of the above conditions are not met.
5. Kreative Software reserves the right to change this license at any time without notice.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE SOFTWARE OR FROM OTHER DEALINGS IN THE SOFTWARE.

View File

@ -0,0 +1,20 @@
KREATIVE SOFTWARE RELAY FONTS FREE USE LICENSE
version 1.2f
Permission is hereby granted, free of charge, to any person or entity (the "User") obtaining a copy of the included font files (the "Software") produced by Kreative Software, to utilize, display, embed, or redistribute the Software, subject to the following conditions:
1. The User may not sell copies of the Software for a fee.
1a. The User may give away copies of the Software free of charge provided this license and any documentation is included verbatim and credit is given to Kreative Korporation or Kreative Software.
2. The User may not modify, reverse-engineer, or create any derivative works of the Software.
3. Any Software carrying the following font names or variations thereof is not covered by this license and may not be used under the terms of this license: Jewel Hill, Miss Diode n Friends, This is Beckie's font!
3a. Any Software carrying a font name ending with the string "Pro CE" is not covered by this license and may not be used under the terms of this license.
4. This license becomes null and void if any of the above conditions are not met.
5. Kreative Software reserves the right to change this license at any time without notice.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE SOFTWARE OR FROM OTHER DEALINGS IN THE SOFTWARE.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,15 @@
{"page":0,"comment":"---------- Upper stat line ----------"}
{"id":0,"text_color":"#FFFFFF"}
{"id":11,"obj":"label","x":0,"y":0,"w":320,"pad_right":90,"h":22,"bg_color":"#D00000","bg_opa":255,"radius":0,"border_side":0,"text":"Tasmota","text_font":"montserrat-20"}
{"id":15,"obj":"lv_wifi_arcs","x":291,"y":0,"w":29,"h":22,"radius":0,"border_side":0,"bg_color":"#000000","line_color":"#FFFFFF"}
{"id":16,"obj":"lv_clock","x":232,"y":3,"w":55,"h":16,"radius":0,"border_side":0}
{"page":1,"comment":"---------- Page 1 ----------"}
{"id":5,"obj":"label","x":2,"y":30,"w":316,"text":"Berkalium 192.168.x.x ABCDEF\nThe quick brown fox jumps over the lazy dog","text_font":"A:Berkelium64.lvfont"}
{"id":6,"obj":"label","x":2,"y":55,"w":316,"text":"PrintChar21 192.168.x.x ABCDEF\nThe quick brown fox jumps over the lazy dog","text_font":"A:PrintChar21.lvfont"}
{"id":7,"obj":"label","x":2,"y":80,"w":316,"text":"Shaston320 192.168.x.x ABCDEF\nThe quick brown fox jumps over the lazy dog","text_font":"A:Shaston320.lvfont"}
{"id":8,"obj":"label","x":2,"y":105,"w":316,"text":"m5x7 192.168.x.x ABCDEF\nThe quick brown fox jumps over the lazy dog","text_font":"A:m5x7.lvfont"}
{"id":9,"obj":"label","x":2,"y":130,"w":316,"text":"m3x6 192.168.x.x ABCDEF\nThe quick brown fox jumps over the lazy dog","text_font":"A:m3x6.lvfont"}
{"id":99,"obj":"label","x":2,"y":170,"w":316,"text":"unscii-8 192.168.x.x ABCDEF\nThe quick brown fox jumps over the lazy dog","text_font":"unscii-8"}

View File

@ -837,7 +837,9 @@ void ResponseAppendFeatures(void)
#if defined(USE_I2C) && defined(USE_QMC5883L)
feature9 |= 0x00000008; // xsns_33_qmc5882l.ino
#endif
// feature9 |= 0x00000010;
#if defined(USE_ENERGY_SENSOR) && defined(USE_MODBUS_ENERGY)
feature9 |= 0x00000010; // xnrg_29_modbus.ino
#endif
// feature9 |= 0x00000020;
// feature9 |= 0x00000040;
// feature9 |= 0x00000080;

View File

@ -342,18 +342,43 @@ public:
manufacturer = (const char*)_manuf;
}
// check if a matches b, return true if so
//
// Special behavior:
// - return true if `a` is empty or null
// - return false if `b` is null
// - matches start of `a` if `a` ends with `'*'`
// - exact match otherwise
static bool matchStar(const char *_a, const char *_b) {
if (_a == nullptr || *_a == '\0') { return true; }
if (_b == nullptr) { return false; }
const char *a = _a;
const char *b = _b;
while (1) {
if (a[0] == '*' && a[1] == '\0') { // pattern ends with '*'
return true; // matches worked until now, accept match
}
if (*a != *b) {
return false;
}
if (*a == '\0') {
return true;
}
a++;
b++;
}
}
bool match(const char *match_model, const char *match_manuf) const {
bool match = true;
if (model.length() > 0) {
if (!model.equals(match_model)) {
match = false;
}
if (!matchStar(model.c_str(), match_model)) {
match = false;
}
if (manufacturer.length() > 0) {
if (!manufacturer.equals(match_manuf)) {
if (!matchStar(manufacturer.c_str(), match_manuf)) {
match = false;
}
}
// AddLog(LOG_LEVEL_DEBUG, ">match device(%s, %s) compared to (%s, %s) = %i", match_model, match_manuf, model.c_str(), manufacturer.c_str(), match);
return match;
}

View File

@ -36,7 +36,7 @@ extern "C" {
********************************************************************/
extern uint32_t MI32numberOfDevices();
extern const char * MI32getDeviceName(uint32_t slot);
extern char * MI32getDeviceName(uint32_t slot);
extern void MI32setBatteryForSlot(uint32_t slot, uint8_t value);
extern void MI32setHumidityForSlot(uint32_t slot, float value);
extern void MI32setTemperatureForSlot(uint32_t slot, float value);

View File

@ -514,15 +514,14 @@ extern "C" {
// write BMP header
static const uint8_t bmp_sign[] = { 0x42, 0x4d }; // BM = Windows
f.write(bmp_sign, sizeof(bmp_sign));
size_t bmp_size = bmp_width * bmp_height * LV_COLOR_DEPTH / 8 + 0x44;
size_t bmp_size = bmp_width * bmp_height * LV_COLOR_DEPTH / 8 + 0x42;
f.write((uint8_t*)&bmp_size, sizeof(bmp_size));
uint32_t zero = 0;
f.write((uint8_t*) &zero, sizeof(zero)); // reserved 4-bytes
uint32_t bmp_offset_to_pixels = 0x44; // TODO
uint32_t bmp_offset_to_pixels = 0x42; // TODO
f.write((uint8_t*) &bmp_offset_to_pixels, sizeof(bmp_offset_to_pixels));
// DIB Header BITMAPINFOHEADER
size_t bmp_dib_header_size = 52; // BITMAPV2INFOHEADER
size_t bmp_dib_header_size = 0x28;
f.write((uint8_t*) &bmp_dib_header_size, sizeof(bmp_dib_header_size));
f.write((uint8_t*) &bmp_width, sizeof(bmp_width));
@ -530,13 +529,15 @@ extern "C" {
// rest of header
// BITMAPV2INFOHEADER = 52 bytes header, 40 bytes sub-header
static const uint8_t bmp_dib_header[] = {
static const uint8_t bmp_dib_header1[] = {
0x01, 0x00, // planes
16, 0x00, // bits per pixel = 16
0x03, 0x00, 0x00, 0x00, // compression = BI_BITFIELDS uncrompressed
0x00, 0x00, 0x00, 0x00, // Image size, 0 is valid for BI_RGB (uncompressed) TODO
0x00, 0x00, 0x00, 0x00, // X pixels per meter
0x00, 0x00, 0x00, 0x00, // Y pixels per meter
};
static const uint8_t bmp_dib_header2[] = {
0xC4, 0xE0, 0x00, 0x00, // X pixels per meter
0xC4, 0xE0, 0x00, 0x00, // Y pixels per meter
0x00, 0x00, 0x00, 0x00, // Colors in table
0x00, 0x00, 0x00, 0x00, // Important color count
@ -544,10 +545,10 @@ extern "C" {
0x00, 0xF8, 0x00, 0x00, // Red channel mask
0xE0, 0x07, 0x00, 0x00, // Green channel mask
0x1F, 0x00, 0x00, 0x00, // Blue channel mask
0x00, 0x00, // Padding to align on 4 bytes boundary
};
f.write(bmp_dib_header, sizeof(bmp_dib_header));
f.write(bmp_dib_header1, sizeof(bmp_dib_header1));
f.write((uint8_t*)&bmp_size, sizeof(bmp_size));
f.write(bmp_dib_header2, sizeof(bmp_dib_header2));
// now we can write the pixels array
// redraw screen

View File

@ -265,6 +265,7 @@ extern "C" {
be_map_insert_int(vm, "min", t->tm_min);
be_map_insert_int(vm, "sec", t->tm_sec);
be_map_insert_int(vm, "weekday", t->tm_wday);
be_map_insert_int(vm, "epoch", mktime(t));
if (unparsed) be_map_insert_str(vm, "unparsed", unparsed);
be_pop(vm, 1);
}

View File

@ -20,32 +20,104 @@
#ifdef USE_ENERGY_SENSOR
#ifdef USE_MODBUS_ENERGY
/*********************************************************************************************\
* Generic Modbus energy meter - experimental (but works on my SDM230)
* Generic Modbus energy meter
*
* Using a rule file called modbus allows to easy configure modbus energy monitor devices.
* Using a rule file called modbus allows to easy configure modbus energy monitor devices up to three phases.
*
* Works:
* rule3 on file#modbus do {"Name":"SDM230","Baud":2400,"Config":8N1","Address":1,"Function":4,"Voltage":0,"Current":6,"Power":12,"ApparentPower":18,"ReactivePower":24,"Factor":30,"Frequency":70,"ImportActive":342,"ExportActive":0x004A} endon
* rule3 on file#modbus do {"Name":"SDM230","Baud":2400,"Config":8N1","Address":1,"Function":4,"Voltage":0x0000,"Current":0x0006,"Power":0x000C,"ApparentPower":0x0012,"ReactivePower":0x0018,"Factor":0x001E,"Frequency":0x0046,"ImportActive":0x0156,"ExportActive":0x004A} endon
* Value pair description:
* {"Name":"SDM230","Baud":2400,"Config":8N1","Address":1,"Function":4,"Voltage":0,"Current":6,"Power":12,"ApparentPower":18,"ReactivePower":24,"Factor":30,"Frequency":70,"Total":342,"ExportActive":0x004A}
* Modbus config parameters:
* Name - Name of energy monitoring device
* Baud - Baudrate of device modbus interface
* Config - Serial config parameters like 8N1 - 8 databits, No parity, 1 stop bit
* Address - Modbus device address entered as decimal (1) or hexadecimal (0x01))
* Function - Modbus function code to access two registers
* Tasmota default embedded register names:
* Voltage - Voltage register entered as decimal or hexadecimal for one phase (0x0000) or up to three phases ([0x0000,0x0002,0x0004])
* Current - Current register entered as decimal or hexadecimal for one phase (0x0006) or up to three phases ([0x0006,0x0008,0x000A])
* Power - Active power register entered as decimal or hexadecimal for one phase (0x000C) or up to three phases ([0x000C,0x000E,0x0010])
* ApparentPower - Apparent power register entered as decimal or hexadecimal for one phase (0x000C) or up to three phases ([0x000C,0x000E,0x0010])
* ReactivePower - Reactive power register entered as decimal or hexadecimal for one phase (0x0018) or up to three phases ([0x0018,0x001A,0x001C])
* Factor - Power factor register entered as decimal or hexadecimal for one phase (0x001E) or up to three phases ([0x001E,0x0020,0x0022])
* Frequency - Frequency register entered as decimal or hexadecimal for one phase (0x0046) or up to three phases ([0x0046,0x0048,0x004A])
* Total - Total active energy register entered as decimal or hexadecimal for one phase (0x0156) or up to three phases ([0x015A,0x015C,0x015E])
* ExportActive - Export active energy register entered as decimal or hexadecimal for one phase (0x0160) or up to three phases ([0x0160,0x0162,0x0164])
* Optional user defined registers:
* User - Additional user defined registers
* Value pair description:
* "User":{"R":0x0024,"J":"PhaseAngle","G":"Phase Angle","U":"Deg","D":2}
* R - Modbus register entered as decimal or hexadecimal for one phase (0x0160) or up to three phases ([0x0160,0x0162,0x0164])
* J - JSON register name (preferrably without spaces like "PhaseAngle")
* G - GUI register name
* U - GUI unit name
* D - Number of decimals for floating point presentation or a code correspondig to Tasmota resolution command settings:
* 21 - VoltRes (V)
* 22 - AmpRes (A)
* 23 - WattRes (W, VA, VAr)
* 24 - EnergyRes (kWh, kVAh, kVArh)
* 25 - FreqRes (Hz)
* 26 - TempRes (C, F)
* 27 - HumRes (%)
* 28 - PressRes (hPa, mmHg)
* 29 - WeightRes (Kg)
*
* Example using default Energy registers:
* rule3 on file#modbus do {"Name":"SDM230","Baud":2400,"Config":8N1","Address":1,"Function":4,"Voltage":0,"Current":6,"Power":12,"ApparentPower":18,"ReactivePower":24,"Factor":30,"Frequency":70,"Total":342,"ExportActive":0x004A} endon
* rule3 on file#modbus do {"Name":"SDM230 with hex registers","Baud":2400,"Config":8N1","Address":1,"Function":4,"Voltage":0x0000,"Current":0x0006,"Power":0x000C,"ApparentPower":0x0012,"ReactivePower":0x0018,"Factor":0x001E,"Frequency":0x0046,"Total":0x0156,"ExportActive":0x004A} endon
* rule3 on file#modbus do {"Name":"DDSU666","Baud":9600,"Config":8N1","Address":1,"Function":4,"Voltage":0x2000,"Current":0x2002,"Power":0x2004,"ReactivePower":0x2006,"Factor":0x200A,"Frequency":0x200E,"Total":0x4000,"ExportActive":0x400A} endon
*
* Example using default Energy registers and some user defined registers:
* rule3 on file#modbus do {"Name":"SDM72","Baud":9600,"Config":8N1","Address":0x01,"Function":0x04,"Power":0x0034,"Total":0x0156,"ExportActive":0x004A,"User":[{"R":0x0502,"J":"ImportActive","G":"Import Active","U":"kWh","D":24},{"R":0x0502,"J":"ExportPower","G":"Export Power","U":"W","D":23},{"R":0x0500,"J":"ImportPower","G":"Import Power","U":"W","D":23}]} endon
* rule3 on file#modbus do {"Name":"SDM120","Baud":2400,"Config":8N1","Address":1,"Function":4,"Voltage":0,"Current":6,"Power":12,"ApparentPower":18,"ReactivePower":24,"Factor":30,"Frequency":70,"Total":342,"ExportActive":0x004A,"User":[{"R":0x0048,"J":"ImportActive","G":"Import Active","U":"kWh","D":24},{"R":0x004E,"J":"ExportReactive","G":"Export Reactive","U":"kVArh","D":24},{"R":0x004C,"J":"ImportReactive","G":"Import Reactive","U":"kVArh","D":24},{"R":0x0024,"J":"PhaseAngle","G":"Phase Angle","U":"Deg","D":2}]} endon
* rule3 on file#modbus do {"Name":"SDM230 with two user registers","Baud":2400,"Config":8N1","Address":1,"Function":4,"Voltage":0,"Current":6,"Power":12,"ApparentPower":18,"ReactivePower":24,"Factor":30,"Frequency":70,"Total":342,"ExportActive":0x004A,"User":[{"R":0x004E,"J":"ExportReactive","G":"Export Reactive","U":"kVArh","D":3},{"R":0x0024,"J":"PhaseAngle","G":"Phase Angle","U":"Deg","D":2}]} endon
* rule3 on file#modbus do {"Name":"SDM630","Baud":9600,"Config":8N1","Address":1,"Function":4,"Voltage":[0,2,4],"Current":[6,8,10],"Power":[12,14,16],"ApparentPower":[18,20,22],"ReactivePower":[24,26,28],"Factor":[30,32,34],"Frequency":70,"Total":342,"ExportActive":[352,354,356],"User":{"R":[346,348,350],"J":"ImportActive","G":"Import Active","U":"kWh","D":24}} endon
*
* Note:
* - To enter long rules using the serial console and solve error "Serial buffer overrun" you might need to enlarge the serial input buffer with command serialbuffer 800
* - Changes to rule file are only executed on restart
*
* Restrictions:
* - Supports Modbus floating point registers
* - Max number of uer defined registers is defined by one rule buffer (511 characters uncompressed, around 800 characters compressed)
*
* To do:
* - Support all three rule slots
* - Support other modbus register like integers
*
* Test set:
* rule3 on file#modbus do {"Name":"SDM230 test1","Baud":2400,"Config":8N1","Address":1,"Function":4,"Voltage":[0,0,0],"Current":[6,6,6],"Power":[12,12,12],"ApparentPower":[18,18,18],"ReactivePower":[24,24,24],"Factor":[30,30,30],"Frequency":[70,70,70],"ImportActive":[342,342,342]} endon
* rule3 on file#modbus do {"Name":"SDM230 test2","Baud":2400,"Config":8N1","Address":1,"Function":4,"Voltage":[0,0,0],"Current":[6,6,6],"Power":[12,12,12],"ApparentPower":[18,18,18],"ReactivePower":[24,24,24],"Factor":[30,30,30],"Frequency":70,"ImportActive":[342,342,342]} endon
* rule3 on file#modbus do {"Name":"SDM230 test3","Baud":2400,"Config":8N1","Address":1,"Function":4,"Voltage":0,"Current":[6,6,6],"Power":[12,12,12],"ApparentPower":[18,18,18],"ReactivePower":[24,24,24],"Factor":[30,30,30],"Frequency":70,"ImportActive":[342,342,342]} endon
* rule3 on file#modbus do {"Name":"SDM230 test1","Baud":2400,"Config":8N1","Address":1,"Function":4,"Voltage":[0,0,0],"Current":[6,6,6],"Power":[12,12,12],"ApparentPower":[18,18,18],"ReactivePower":[24,24,24],"Factor":[30,30,30],"Frequency":[70,70,70],"Total":[342,342,342]} endon
* rule3 on file#modbus do {"Name":"SDM230 test2","Baud":2400,"Config":8N1","Address":1,"Function":4,"Voltage":[0,0,0],"Current":[6,6,6],"Power":[12,12,12],"ApparentPower":[18,18,18],"ReactivePower":[24,24,24],"Factor":[30,30,30],"Frequency":70,"Total":[342,342,342]} endon
* rule3 on file#modbus do {"Name":"SDM230 test3","Baud":2400,"Config":8N1","Address":1,"Function":4,"Voltage":0,"Current":[6,6,6],"Power":[12,12,12],"ApparentPower":[18,18,18],"ReactivePower":[24,24,24],"Factor":[30,30,30],"Frequency":70,"Total":[342,342,342]} endon
* rule3 on file#modbus do {"Name":"SDM230 test4","Baud":2400,"Config":8N1","Address":1,"Function":4,"Voltage":0,"Current":6,"Power":12,"ApparentPower":18,"ReactivePower":24,"Factor":30,"Frequency":70,"Total":342,"ExportActive":0x004A,"User":[{"R":0x004E,"J":"ExportReactive","G":"Export Reactive","U":"kVArh","D":24},{"R":0x0024,"J":"PhaseAngle","G":"Phase Angle","U":"Deg","D":2}]} endon
* rule3 on file#modbus do {"Name":"SDM230 test5","Baud":2400,"Config":8N1","Address":1,"Function":4,"Voltage":[0,0,0],"Current":6,"Power":12,"ApparentPower":18,"ReactivePower":24,"Factor":30,"Frequency":70,"Total":342,"ExportActive":0x004A,"User":[{"R":[0x004E,0x004E,0x004E],"J":"ExportReactive","G":"Export Reactive","U":"kVArh","D":3},{"R":0x0024,"J":"PhaseAngle","G":"Phase Angle","U":"Deg","D":2}]} endon
* rule3 on file#modbus do {"Name":"SDM120 test1","Baud":2400,"Config":8N1","Address":1,"Function":4,"Voltage":0,"Current":6,"Power":12,"ApparentPower":18,"ReactivePower":24,"Factor":30,"Frequency":70,"Total":342,"ExportActive":0x004A,"User":[{"R":0x0048,"J":"ImportActive","G":"Import Active","U":"kWh","D":24},{"R":0x004E,"J":"ExportReactive","G":"Export Reactive","U":"kVArh","D":24},{"R":0x004C,"J":"ImportReactive","G":"Import Reactive","U":"kVArh","D":24},{"R":0x0024,"J":"PhaseAngle","G":"Phase Angle","U":"Deg","D":2}]} endon
\*********************************************************************************************/
#define XNRG_29 29
#define ENERGY_MODBUS_SPEED 9600 // default Modbus baudrate
#define ENERGY_MODBUS_CONFIG TS_SERIAL_8N1
#define ENERGY_MODBUS_ADDR 1 // default Modbus device_address
#define ENERGY_MODBUS_FUNC 0x04 // default Modbus function code
#define ENERGY_MODBUS_SPEED 9600 // Default Modbus baudrate
#define ENERGY_MODBUS_CONFIG TS_SERIAL_8N1 // Default Modbus serial configuration
#define ENERGY_MODBUS_ADDR 1 // Default Modbus device_address
#define ENERGY_MODBUS_FUNC 0x04 // Default Modbus function code
#define ENERGY_MODBUS_UNITS "" // Default user GUI unit
#define ENERGY_MODBUS_DECIMALS 0 // Default user decimal resolution
//#define ENERGY_MODBUS_DEBUG
//#define ENERGY_MODBUS_DEBUG_SHOW
const uint16_t nrg_mbs_reg_not_used = 1; // Odd number 1 is unused register
enum EnergyModbusResolutions { NRG_RES_VOLTAGE = 21, // V
NRG_RES_CURRENT, // A
NRG_RES_POWER, // W, VA, VAr
NRG_RES_ENERGY, // kWh, kVAh, kVArh
NRG_RES_FREQUENCY, // Hz
NRG_RES_TEMPERATURE, // C, F
NRG_RES_HUMIDITY, // %
NRG_RES_PRESSURE, // hPa, mmHg
NRG_RES_WEIGHT }; // Kg
enum EnergyModbusRegisters { NRG_MBS_VOLTAGE,
NRG_MBS_CURRENT,
NRG_MBS_ACTIVE_POWER,
@ -53,7 +125,7 @@ enum EnergyModbusRegisters { NRG_MBS_VOLTAGE,
NRG_MBS_REACTIVE_POWER,
NRG_MBS_POWER_FACTOR,
NRG_MBS_FREQUENCY,
NRG_MBS_IMPORT_ACTIVE_ENERGY,
NRG_MBS_TOTAL_ENERGY,
NRG_MBS_EXPORT_ACTIVE_ENERGY,
NRG_MBS_MAX_REGS };
@ -64,12 +136,14 @@ const char kEnergyModbusValues[] PROGMEM = D_JSON_VOLTAGE "|" // Vo
D_JSON_REACTIVE_POWERUSAGE "|" // ReactivePower
D_JSON_POWERFACTOR "|" // Factor
D_JSON_FREQUENCY "|" // Frequency
D_JSON_IMPORT_ACTIVE "|" // ImportActive
D_JSON_TOTAL "|" // Total
D_JSON_EXPORT_ACTIVE "|" // ExportActive
;
#include <TasmotaModbus.h>
TasmotaModbus *EnergyModbus;
#include <Ticker.h>
Ticker ticker_energy_modbus;
struct NRGMODBUS {
uint32_t serial_bps;
@ -77,19 +151,37 @@ struct NRGMODBUS {
uint16_t register_address[NRG_MBS_MAX_REGS][ENERGY_MAX_PHASES];
uint8_t device_address;
uint8_t function;
uint8_t user_adds;
uint8_t phase;
uint8_t state;
uint8_t retry;
bool mutex;
} *NrgModbus = nullptr;
typedef struct NRGMODBUSUSER {
float register_data[ENERGY_MAX_PHASES];
uint16_t register_address[ENERGY_MAX_PHASES];
uint8_t resolution;
String json_name;
String gui_name;
String gui_unit;
} NrgModbusUser_t;
NrgModbusUser_t* NrgModbusUser = nullptr;
/*********************************************************************************************/
void EnergyModbusLoop(void) {
if (NrgModbus->mutex) { return; }
// AddLog(LOG_LEVEL_DEBUG, PSTR("DBG: EnergyModbusLoop() entry"));
NrgModbus->mutex = 1;
uint16_t register_address;
bool data_ready = EnergyModbus->ReceiveReady();
if (data_ready) {
uint8_t buffer[14]; // At least 5 + (2 * 2) = 9
uint8_t buffer[9]; // At least 5 + (2 * 2) = 9
uint32_t error = EnergyModbus->ReceiveBuffer(buffer, 2);
AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("NRG: Modbus register %d, phase %d, rcvd %*_H"),
@ -148,38 +240,118 @@ void EnergyModbusLoop(void) {
case NRG_MBS_FREQUENCY:
Energy.frequency[NrgModbus->phase] = value; // 50.0 Hz
break;
case NRG_MBS_IMPORT_ACTIVE_ENERGY:
case NRG_MBS_TOTAL_ENERGY:
Energy.import_active[NrgModbus->phase] = value; // 6.216 kWh => used in EnergyUpdateTotal()
break;
case NRG_MBS_EXPORT_ACTIVE_ENERGY:
Energy.export_active[NrgModbus->phase] = value; // 478.492 kWh
break;
default:
if (NrgModbusUser) {
NrgModbusUser[NrgModbus->state - NRG_MBS_MAX_REGS].register_data[NrgModbus->phase] = value;
}
}
do {
NrgModbus->phase++;
if (NrgModbus->phase == Energy.phase_count) {
if (NrgModbus->phase >= Energy.phase_count) {
NrgModbus->phase = 0;
NrgModbus->state++;
if (NrgModbus->state == NRG_MBS_MAX_REGS) {
if (NrgModbus->state >= NRG_MBS_MAX_REGS + NrgModbus->user_adds) {
NrgModbus->state = 0;
NrgModbus->phase = 0;
EnergyUpdateTotal(); // update every cycle after all registers have been read
break;
}
}
} while (NrgModbus->register_address[NrgModbus->state][NrgModbus->phase] == nrg_mbs_reg_not_used);
delay(0);
register_address = (NrgModbus->state < NRG_MBS_MAX_REGS) ? NrgModbus->register_address[NrgModbus->state][NrgModbus->phase] :
NrgModbusUser[NrgModbus->state - NRG_MBS_MAX_REGS].register_address[NrgModbus->phase];
} while (register_address == nrg_mbs_reg_not_used);
}
} // end data ready
if (0 == NrgModbus->retry || data_ready) {
NrgModbus->retry = 5;
EnergyModbus->Send(NrgModbus->device_address, NrgModbus->function, NrgModbus->register_address[NrgModbus->state][NrgModbus->phase], 2);
NrgModbus->retry = 1;
register_address = (NrgModbus->state < NRG_MBS_MAX_REGS) ? NrgModbus->register_address[NrgModbus->state][NrgModbus->phase] :
NrgModbusUser[NrgModbus->state - NRG_MBS_MAX_REGS].register_address[NrgModbus->phase];
EnergyModbus->Send(NrgModbus->device_address, NrgModbus->function, register_address, 2);
} else {
NrgModbus->retry--;
#ifdef ENERGY_MODBUS_DEBUG
AddLog(LOG_LEVEL_DEBUG, PSTR("NRG: Modbus state %d retry %d"), NrgModbus->state, NrgModbus->retry);
#endif
}
delay(0);
NrgModbus->mutex = 0;
// AddLog(LOG_LEVEL_DEBUG, PSTR("DBG: EnergyModbusLoop() exit"));
}
#ifdef USE_RULES
bool EnergyModbusReadUserRegisters(JsonParserObject user_add_value, uint32_t add_index) {
// {"R":0x004E,"J":"ExportReactive","G":"Export Reactive","U":"kVArh","D":3}
JsonParserToken val;
val = user_add_value[PSTR("R")]; // Register address
uint32_t phase = 0;
if (val.isArray()) {
JsonParserArray address_arr = val.getArray();
for (auto value : address_arr) {
NrgModbusUser[add_index].register_address[phase] = value.getUInt();
phase++;
if (phase >= ENERGY_MAX_PHASES) { break; }
}
} else if (val) {
NrgModbusUser[add_index].register_address[0] = val.getUInt();
phase++;
} else {
return false;
}
if (phase > Energy.phase_count) {
Energy.phase_count = phase;
}
val = user_add_value[PSTR("J")]; // JSON value name
if (val) {
NrgModbusUser[add_index].json_name = val.getStr();
} else {
return false;
}
val = user_add_value[PSTR("G")]; // GUI value name
if (val) {
NrgModbusUser[add_index].gui_name = val.getStr();
} else {
return false;
}
NrgModbusUser[add_index].gui_unit = ENERGY_MODBUS_UNITS;
val = user_add_value[PSTR("U")]; // GUI value Unit
if (val) {
NrgModbusUser[add_index].gui_unit = val.getStr();
}
NrgModbusUser[add_index].resolution = ENERGY_MODBUS_DECIMALS;
val = user_add_value[PSTR("D")]; // Decimal resolution
if (val) {
NrgModbusUser[add_index].resolution = val.getUInt();
}
#ifdef ENERGY_MODBUS_DEBUG
AddLog(LOG_LEVEL_DEBUG, PSTR("NRG: Idx %d, R [%04X,%04X,%04X], J '%s', G '%s', U '%s', D %d"),
add_index,
NrgModbusUser[add_index].register_address[0],
NrgModbusUser[add_index].register_address[1],
NrgModbusUser[add_index].register_address[2],
NrgModbusUser[add_index].json_name.c_str(),
NrgModbusUser[add_index].gui_name.c_str(),
NrgModbusUser[add_index].gui_unit.c_str(),
NrgModbusUser[add_index].resolution);
#endif
return true;
}
#endif // USE_RULES
bool EnergyModbusReadRegisters(void) {
#ifdef USE_RULES
String modbus = RuleLoadFile("MODBUS");
@ -196,8 +368,8 @@ bool EnergyModbusReadRegisters(void) {
JsonParserObject root = parser.getRootObject();
if (!root) { return false; } // Invalid JSON
NrgModbus = (NRGMODBUS *)calloc(sizeof(struct NRGMODBUS), 1);
if (NrgModbus == nullptr) { return false; } // Unable to allocate variabvles on heap
NrgModbus = (NRGMODBUS *)calloc(1, sizeof(struct NRGMODBUS));
if (NrgModbus == nullptr) { return false; } // Unable to allocate variables on heap
// Init defaults
NrgModbus->serial_bps = ENERGY_MODBUS_SPEED;
@ -222,32 +394,33 @@ bool EnergyModbusReadRegisters(void) {
}
val = root[PSTR("Address")];
if (val) {
NrgModbus->device_address = val.getInt(); // 1
NrgModbus->device_address = val.getUInt(); // 1
}
val = root[PSTR("Function")];
if (val) {
NrgModbus->function = val.getInt(); // 4
NrgModbus->function = val.getUInt(); // 4
}
char register_name[32];
uint32_t phase;
Energy.voltage_available = false; // Disable voltage is measured
Energy.current_available = false; // Disable current is measured
for (uint32_t names = 0; names < NRG_MBS_MAX_REGS; names++) {
phase = 0;
val = root[GetTextIndexed(register_name, sizeof(register_name), names, kEnergyModbusValues)];
if (val.isArray()) {
JsonParserArray arr = val.getArray();
for (auto value : arr) {
NrgModbus->register_address[names][phase] = value.getUInt();
if (val) {
// "Voltage":0
// "Voltage":[0,0,0]
uint32_t phase = 0;
if (val.isArray()) {
JsonParserArray arr = val.getArray();
for (auto value : arr) {
NrgModbus->register_address[names][phase] = value.getUInt();
phase++;
if (phase >= ENERGY_MAX_PHASES) { break; }
}
} else if (val) {
NrgModbus->register_address[names][0] = val.getUInt();
phase++;
if (phase == ENERGY_MAX_PHASES) { break; }
}
} else if (val) {
NrgModbus->register_address[names][phase] = val.getUInt();
phase++;
}
if (phase) {
if (phase > Energy.phase_count) {
Energy.phase_count = phase;
}
@ -266,14 +439,68 @@ bool EnergyModbusReadRegisters(void) {
Energy.frequency_common = true; // Use common frequency
}
break;
case NRG_MBS_IMPORT_ACTIVE_ENERGY:
case NRG_MBS_TOTAL_ENERGY:
Settings->flag3.hardware_energy_total = 1; // SetOption72 - Enable hardware energy total counter as reference (#6561)
break;
}
#ifdef ENERGY_MODBUS_DEBUG
AddLog(LOG_LEVEL_DEBUG, PSTR("NRG: Idx %d, R [%04X,%04X,%04X]"),
names,
NrgModbus->register_address[names][0],
NrgModbus->register_address[names][1],
NrgModbus->register_address[names][2]);
#endif
}
}
NrgModbus->user_adds = 0;
// "User":{"R":0x004E,"J":"ExportReactive","G":"Export Reactive","U":"kVArh","D":3}
// "User":[{"R":0x004E,"J":"ExportReactive","G":"Export Reactive","U":"kVArh","D":3},{"R":0x0024,"J":"PhaseAngle","G":"Phase Angle","U":"Deg","D":2}]
val = root[PSTR("User")];
if (val) {
NrgModbus->user_adds = 1;
if (val.isArray()) {
NrgModbus->user_adds = val.size();
}
NrgModbusUser = (NrgModbusUser_t*)calloc(NrgModbus->user_adds, sizeof(NrgModbusUser_t));
if (NrgModbusUser) {
// Init defaults
for (uint32_t i = 0; i < NrgModbus->user_adds; i++) {
for (uint32_t j = 0; j < ENERGY_MAX_PHASES; j++) {
NrgModbusUser[i].register_address[j] = nrg_mbs_reg_not_used;
NrgModbusUser[i].register_data[j] = NAN;
}
}
if (val.isArray()) {
JsonParserArray user_adds_arr = val.getArray();
uint32_t add_index = 0;
for (auto user_add_values : user_adds_arr) {
if (!user_add_values.isObject()) { break; }
if (EnergyModbusReadUserRegisters(user_add_values.getObject(), add_index)) {
add_index++;
} else {
AddLog(LOG_LEVEL_INFO, PSTR("NRG: Dropped JSON user input %d"), add_index +1);
NrgModbus->user_adds--;
}
}
} else if (val) {
if (val.isObject()) {
if (!EnergyModbusReadUserRegisters(val.getObject(), 0)) {
AddLog(LOG_LEVEL_INFO, PSTR("NRG: Dropped JSON user input"));
NrgModbus->user_adds--;
}
}
}
} else {
// Unable to allocate variables on heap
NrgModbus->user_adds = 0;
}
}
#ifdef ENERGY_MODBUS_DEBUG
AddLog(LOG_LEVEL_DEBUG, PSTR("NRG: Registers %*_H"), sizeof(NrgModbus->register_address), NrgModbus->register_address);
AddLog(LOG_LEVEL_DEBUG, PSTR("NRG: RAM usage %d + %d"), sizeof(struct NRGMODBUS), NrgModbus->user_adds * sizeof(NrgModbusUser_t));
#endif
// NrgModbus->state = 0; // Set by calloc()
@ -298,6 +525,7 @@ void EnergyModbusSnsInit(void) {
uint8_t result = EnergyModbus->Begin(NrgModbus->serial_bps, NrgModbus->serial_config);
if (result) {
if (2 == result) { ClaimSerial(); }
ticker_energy_modbus.attach_ms(150, EnergyModbusLoop);
return;
}
}
@ -310,6 +538,90 @@ void EnergyModbusDrvInit(void) {
}
}
/*********************************************************************************************\
* Additional presentation
\*********************************************************************************************/
void EnergyModbusReset(void) {
for (uint32_t i = 0; i < NrgModbus->user_adds; i++) {
for (uint32_t j = 0; j < ENERGY_MAX_PHASES; j++) {
if (NrgModbusUser[i].register_address[0] != nrg_mbs_reg_not_used) {
NrgModbusUser[i].register_data[j] = 0;
}
}
}
}
uint32_t EnergyModbusResolution(uint32_t resolution) {
if (resolution >= NRG_RES_VOLTAGE) {
switch (resolution) {
case NRG_RES_VOLTAGE:
return Settings->flag2.voltage_resolution;
case NRG_RES_CURRENT:
return Settings->flag2.current_resolution;
case NRG_RES_POWER:
return Settings->flag2.wattage_resolution;
case NRG_RES_ENERGY:
return Settings->flag2.energy_resolution;
case NRG_RES_FREQUENCY:
return Settings->flag2.frequency_resolution;
case NRG_RES_TEMPERATURE:
return Settings->flag2.temperature_resolution;
case NRG_RES_HUMIDITY:
return Settings->flag2.humidity_resolution;
case NRG_RES_PRESSURE:
return Settings->flag2.pressure_resolution;
case NRG_RES_WEIGHT:
return Settings->flag2.weight_resolution;
}
}
return resolution;
}
void EnergyModbusShow(bool json) {
char value_chr[TOPSZ];
for (uint32_t i = 0; i < NrgModbus->user_adds; i++) {
#ifdef ENERGY_MODBUS_DEBUG_SHOW
AddLog(LOG_LEVEL_DEBUG, PSTR("NRG: Idx %d, R [%04X,%04X,%04X], J '%s', G '%s', U '%s', D %d, V [%3_f,%3_f,%3_f]"),
i,
NrgModbusUser[i].register_address[0],
NrgModbusUser[i].register_address[1],
NrgModbusUser[i].register_address[2],
NrgModbusUser[i].json_name.c_str(),
NrgModbusUser[i].gui_name.c_str(),
NrgModbusUser[i].gui_unit.c_str(),
NrgModbusUser[i].resolution,
&NrgModbusUser[i].register_data[0],
&NrgModbusUser[i].register_data[1],
&NrgModbusUser[i].register_data[2]);
#endif
if ((NrgModbusUser[i].register_address[0] != nrg_mbs_reg_not_used) && !isnan(NrgModbusUser[i].register_data[0])) {
float values[ENERGY_MAX_PHASES];
for (uint32_t j = 0; j < ENERGY_MAX_PHASES; j++) {
values[j] = NrgModbusUser[i].register_data[j];
}
uint32_t resolution = EnergyModbusResolution(NrgModbusUser[i].resolution);
#ifdef ENERGY_MODBUS_DEBUG_SHOW
AddLog(LOG_LEVEL_DEBUG, PSTR("NRG: resolution %d -> %d"), NrgModbusUser[i].resolution, resolution);
#endif
if (json) {
ResponseAppend_P(PSTR(",\"%s\":%s"), NrgModbusUser[i].json_name.c_str(), EnergyFormat(value_chr, values, resolution));
#ifdef USE_WEBSERVER
} else {
WSContentSend_PD(PSTR("{s}%s{m}%s %s{e}"),
NrgModbusUser[i].gui_name.c_str(),
WebEnergyFormat(value_chr, values, resolution),
NrgModbusUser[i].gui_unit.c_str());
#endif // USE_WEBSERVER
}
}
}
}
/*********************************************************************************************\
* Interface
\*********************************************************************************************/
@ -318,12 +630,26 @@ bool Xnrg29(uint8_t function) {
bool result = false;
switch (function) {
// case FUNC_EVERY_250_MSECOND:
case FUNC_EVERY_200_MSECOND:
/*
case FUNC_EVERY_200_MSECOND: // Energy ticker interrupt
// case FUNC_EVERY_250_MSECOND: // Tasmota dispatcher
EnergyModbusLoop();
break;
*/
case FUNC_JSON_APPEND:
EnergyModbusShow(1);
break;
#ifdef USE_WEBSERVER
#ifdef USE_ENERGY_COLUMN_GUI
case FUNC_WEB_COL_SENSOR:
#else // not USE_ENERGY_COLUMN_GUI
case FUNC_WEB_SENSOR:
#endif // USE_ENERGY_COLUMN_GUI
EnergyModbusShow(0);
break;
#endif // USE_WEBSERVER
case FUNC_ENERGY_RESET:
// EnergyModbusReset();
EnergyModbusReset();
break;
case FUNC_INIT:
EnergyModbusSnsInit();

View File

@ -22,6 +22,8 @@
--------------------------------------------------------------------------------------------
Version yyyymmdd Action Description
--------------------------------------------------------------------------------------------
0.9.5.6 20221006 changed - remove old HASS code, allow adding unknown sensors, prepare YLAI003
-------
0.9.5.5 20220326 changed - refactored connection task for asynchronous op, add response option,
fixed MI32Key command
-------
@ -91,7 +93,7 @@ static BLEScan* MI32Scan;
class MI32SensorCallback : public NimBLEClientCallbacks {
void onConnect(NimBLEClient* pclient) {
// AddLog(LOG_LEVEL_DEBUG,PSTR("connected %s"), kMI32DeviceType[(MIBLEsensors[MI32.conCtx->slot].type)-1]);
// AddLog(LOG_LEVEL_DEBUG,PSTR("connected %s"), MI32getDeviceName(MI32.conCtx->slot));
MI32.infoMsg = MI32_DID_CONNECT;
MI32.mode.willConnect = 0;
MI32.mode.connected = 1;
@ -251,14 +253,16 @@ void MI32_ReverseMAC(uint8_t _mac[]){
void MI32AddKey(mi_bindKey_t keyMAC){
bool unknownMAC = true;
uint32_t _slot = 0;
for(auto &_sensor : MIBLEsensors){
if(memcmp(keyMAC.MAC,_sensor.MAC,sizeof(keyMAC.MAC))==0){
_sensor.key = new uint8_t[16];
memcpy(_sensor.key,keyMAC.key,16);
unknownMAC=false;
_sensor.status.hasWrongKey = 0;
AddLog(LOG_LEVEL_INFO,PSTR("add key to %s"),kMI32DeviceType[_sensor.type-1]);
AddLog(LOG_LEVEL_INFO,PSTR("add key to %s"),MI32getDeviceName(_slot));
}
_slot++;
}
if(unknownMAC){
AddLog(LOG_LEVEL_ERROR,PSTR("M32: unknown MAC"));
@ -291,7 +295,7 @@ int MI32_decryptPacket(char * _buf, uint16_t _bufSize, uint8_t * _payload, uint3
}
uint32_t _version = (uint32_t)_beacon->frame.version;
// AddLog(LOG_LEVEL_DEBUG,PSTR("M32: encrypted msg from %s with version:%u"),kMI32DeviceType[MIBLEsensors[_slot].type-1],_version);
// AddLog(LOG_LEVEL_DEBUG,PSTR("M32: encrypted msg from %s with version:%u"),MI32getDeviceName(_slot),_version);
if(_version == 5){
if(_beacon->frame.includesMAC){
@ -361,12 +365,13 @@ int MI32_decryptPacket(char * _buf, uint16_t _bufSize, uint8_t * _payload, uint3
/**
* @brief Return the slot number of a known sensor or return create new sensor slot
*
* @param _MAC BLE address of the sensor
* @param _MAC BLE address of the sensor
* @param _type Type number of the sensor
* @return uint32_t Known or new slot in the sensors-vector
*/
uint32_t MIBLEgetSensorSlot(uint8_t (&_MAC)[6], uint16_t _type, uint8_t counter){
DEBUG_SENSOR_LOG(PSTR("%s: will test ID-type: %x"),D_CMND_MI32, _type);
uint16_t _pid = _type; // save for unknown types
bool _success = false;
for (uint32_t i=0;i<MI32_TYPES;i++){ // i < sizeof(kMI32DeviceID) gives compiler warning
if(_type == kMI32DeviceID[i]){
@ -374,11 +379,8 @@ uint32_t MIBLEgetSensorSlot(uint8_t (&_MAC)[6], uint16_t _type, uint8_t counter)
_type = i+1;
_success = true;
}
else {
DEBUG_SENSOR_LOG(PSTR("%s: ID-type is not: %x"),D_CMND_MI32,kMI32DeviceID[i]);
}
}
if(!_success) return 0xff;
if(!_success) _type = UNKNOWN_MI;
DEBUG_SENSOR_LOG(PSTR("%s: vector size %u"),D_CMND_MI32, MIBLEsensors.size());
for(uint32_t i=0; i<MIBLEsensors.size(); i++){
@ -401,6 +403,7 @@ uint32_t MIBLEgetSensorSlot(uint8_t (&_MAC)[6], uint16_t _type, uint8_t counter)
DEBUG_SENSOR_LOG(PSTR("%s: found new sensor"),D_CMND_MI32);
mi_sensor_t _newSensor;
memcpy(_newSensor.MAC,_MAC, sizeof(_MAC));
_newSensor.PID = _pid;
_newSensor.type = _type;
_newSensor.eventType.raw = 0;
_newSensor.feature.raw = 0;
@ -414,6 +417,9 @@ uint32_t MIBLEgetSensorSlot(uint8_t (&_MAC)[6], uint16_t _type, uint8_t counter)
_newSensor.key = nullptr;
switch (_type)
{
case UNKNOWN_MI:
_newSensor.hum_history = (uint8_t*) calloc(24,1);
break;
case FLORA:
_newSensor.moisture =0xff;
_newSensor.fertility =0xffff;
@ -450,7 +456,7 @@ uint32_t MIBLEgetSensorSlot(uint8_t (&_MAC)[6], uint16_t _type, uint8_t counter)
_newSensor.feature.bat=1;
_newSensor.NMT=0;
break;
case YLYK01: case YLKG08:
case YLYK01: case YLKG08: case YLAI003:
_newSensor.feature.Btn = 1;
_newSensor.Btn = 99;
if(_type == YLKG08){
@ -496,7 +502,7 @@ uint32_t MIBLEgetSensorSlot(uint8_t (&_MAC)[6], uint16_t _type, uint8_t counter)
break;
}
MIBLEsensors.push_back(_newSensor);
AddLog(LOG_LEVEL_DEBUG,PSTR("M32: new %s at slot: %u"),kMI32DeviceType[_type-1],MIBLEsensors.size()-1);
AddLog(LOG_LEVEL_DEBUG,PSTR("M32: new %s at slot: %u"),MI32getDeviceName(MIBLEsensors.size()-1),MIBLEsensors.size()-1);
MI32.mode.shallShowStatusInfo = 1;
return MIBLEsensors.size()-1;
};
@ -796,9 +802,15 @@ extern "C" {
return MIBLEsensors[slot].MAC;
}
const char * MI32getDeviceName(uint32_t slot){
if(slot>MIBLEsensors.size()-1) return "";
return kMI32DeviceType[MIBLEsensors[slot].type-1];
char * MI32getDeviceName(uint32_t slot){
static char _name[12];
if( MIBLEsensors[slot].type == UNKNOWN_MI){
snprintf_P(_name,8,PSTR("MI_%04X"),MIBLEsensors[slot].PID);
}
else{
GetTextIndexed(_name, sizeof(_name), MIBLEsensors[slot].type-1, kMI32DeviceType);
}
return _name;
}
} //extern "C"
@ -1337,10 +1349,11 @@ if(decryptRet!=0){
return;
}
// AddLog(LOG_LEVEL_DEBUG,PSTR("%s at slot %u with payload type: %02x"), kMI32DeviceType[MIBLEsensors[_slot].type-1],_slot,_payload.type);
// AddLog(LOG_LEVEL_DEBUG,PSTR("%s at slot %u with payload type: %02x"), MI32getDeviceName(_slot),_slot,_payload.type);
MIBLEsensors[_slot].lastTime = millis();
switch(_payload.type){
case 0x01:
MIBLEsensors[_slot].feature.Btn = 1;
if(_payload.Btn.type == 4){ //dimmer knob rotation
MIBLEsensors[_slot].eventType.knob = 1;
if(_payload.Btn.num == 0){
@ -1385,6 +1398,7 @@ if(decryptRet!=0){
// AddLog(LOG_LEVEL_DEBUG,PSTR("Mode 1: U16: %u Button"), MIBLEsensors[_slot].Btn );
break;
case 0x04:
MIBLEsensors[_slot].feature.temp = 1;
_tempFloat=(float)(_payload.temp)/10.0f;
if(_tempFloat<60){
MIBLEsensors[_slot].temp=_tempFloat;
@ -1400,6 +1414,7 @@ if(decryptRet!=0){
// AddLog(LOG_LEVEL_DEBUG,PSTR("Mode 4: U16: %u Temp"), _payload.temp );
break;
case 0x06:
MIBLEsensors[_slot].feature.hum = 1;
_tempFloat=(float)(_payload.hum)/10.0f;
if(_tempFloat<101){
MIBLEsensors[_slot].hum=_tempFloat;
@ -1415,6 +1430,7 @@ if(decryptRet!=0){
// AddLog(LOG_LEVEL_DEBUG,PSTR("Mode 6: U16: %u Hum"), _payload.hum);
break;
case 0x07:
MIBLEsensors[_slot].feature.lux = 1;
MIBLEsensors[_slot].lux=_payload.lux & 0x00ffffff;
if(MIBLEsensors[_slot].type==MJYD2S){
MIBLEsensors[_slot].eventType.noMotion = 1;
@ -1429,18 +1445,21 @@ if(decryptRet!=0){
// AddLog(LOG_LEVEL_DEBUG,PSTR("Mode 7: U24: %u Lux"), _payload.lux & 0x00ffffff);
break;
case 0x08:
MIBLEsensors[_slot].feature.moist = 1;
MIBLEsensors[_slot].moisture=_payload.moist;
MIBLEsensors[_slot].eventType.moist = 1;
DEBUG_SENSOR_LOG(PSTR("Mode 8: moisture updated"));
// AddLog(LOG_LEVEL_DEBUG,PSTR("Mode 8: U8: %u Moisture"), _payload.moist);
break;
case 0x09:
MIBLEsensors[_slot].fertility=_payload.fert;
MIBLEsensors[_slot].eventType.fert = 1;
DEBUG_SENSOR_LOG(PSTR("Mode 9: fertility updated"));
MIBLEsensors[_slot].feature.fert = 1;
MIBLEsensors[_slot].fertility=_payload.fert;
MIBLEsensors[_slot].eventType.fert = 1;
DEBUG_SENSOR_LOG(PSTR("Mode 9: fertility updated"));
// AddLog(LOG_LEVEL_DEBUG,PSTR("Mode 9: U16: %u Fertility"), _payload.fert);
break;
case 0x0a:
MIBLEsensors[_slot].feature.bat = 1;
if(MI32.option.ignoreBogusBattery){
if(MIBLEsensors[_slot].type==LYWSD03MMC || MIBLEsensors[_slot].type==MHOC401){
break;
@ -1473,6 +1492,8 @@ if(decryptRet!=0){
case 0x0f:
if (_payload.ten!=0) break;
MIBLEsensors[_slot].feature.motion = 1;
MIBLEsensors[_slot].feature.NMT = 1; //only driver based
MIBLEsensors[_slot].eventType.motion = 1;
MIBLEsensors[_slot].events++;
MIBLEsensors[_slot].lux = _payload.lux & 0x00ffffff;
@ -1489,6 +1510,7 @@ if(decryptRet!=0){
// AddLog(LOG_LEVEL_DEBUG,PSTR("motion: primary"),MIBLEsensors[_slot].lux );
break;
case 0x14:
MIBLEsensors[_slot].feature.leak = 1;
MIBLEsensors[_slot].leak = _payload.leak;
MIBLEsensors[_slot].eventType.leak = 1;
if(_payload.leak>0) MI32.mode.shallTriggerTele = 1;
@ -1497,12 +1519,14 @@ if(decryptRet!=0){
#endif //USE_MI_HOMEKIT
break;
case 0x17:
MIBLEsensors[_slot].feature.NMT = 1;
MIBLEsensors[_slot].NMT = _payload.NMT;
MIBLEsensors[_slot].eventType.NMT = 1;
MI32.mode.shallTriggerTele = 1;
// AddLog(LOG_LEVEL_DEBUG,PSTR("Mode 17: NMT: %u seconds"), _payload.NMT);
break;
case 0x19:
MIBLEsensors[_slot].feature.door = 1;
MIBLEsensors[_slot].door = _payload.door;
MIBLEsensors[_slot].eventType.door = 1;
MIBLEsensors[_slot].events++;
@ -1544,7 +1568,7 @@ void MI32ParseATCPacket(char * _buf, uint32_t length, uint8_t addr[6], int RSSI)
_slot = MIBLEgetSensorSlot(_packet->MAC, 0x944a, _packet->P.frameCnt); // ... and again
}
if(_slot==0xff) return;
// AddLog(LOG_LEVEL_DEBUG,PSTR("%s at slot %u"), kMI32DeviceType[MIBLEsensors[_slot].type-1],_slot);
// AddLog(LOG_LEVEL_DEBUG,PSTR("%s at slot %u"), MI32getDeviceName(_slot),_slot);
MIBLEsensors[_slot].RSSI=RSSI;
MIBLEsensors[_slot].lastTime = millis();
@ -1581,7 +1605,7 @@ void MI32parseCGD1Packet(char * _buf, uint32_t length, uint8_t addr[6], int RSSI
memcpy(_addr,addr,6);
uint32_t _slot = MIBLEgetSensorSlot(_addr, 0x0576, 0); // This must be hard-coded, no object-id in Cleargrass-packet, we have no packet counter too
if(_slot==0xff) return;
// AddLog(LOG_LEVEL_DEBUG,PSTR("%s at slot %u"), kMI32DeviceType[MIBLEsensors[_slot].type-1],_slot);
// AddLog(LOG_LEVEL_DEBUG,PSTR("%s at slot %u"), MI32getDeviceName(_slot),_slot);
MIBLEsensors[_slot].RSSI=RSSI;
MIBLEsensors[_slot].lastTime = millis();
cg_packet_t _packet;
@ -1918,9 +1942,9 @@ void MI32sendWidget(uint32_t slot){
snprintf_P(_bat,24,PSTR("&#128267;%u%%"), _sensor.bat);
if(!_sensor.feature.bat) _bat[0] = 0;
if (_sensor.bat == 0) _bat[9] = 0;
WSContentSend_P(HTTP_MI32_WIDGET,slot+1,_opacity,_MAC,_sensor.RSSI,_bat,_key,kMI32DeviceType[_sensor.type-1]);
WSContentSend_P(HTTP_MI32_WIDGET,slot+1,_opacity,_MAC,_sensor.RSSI,_bat,_key,MI32getDeviceName(slot));
if(_sensor.feature.tempHum){
if(_sensor.feature.temp && _sensor.feature.hum){
if(!isnan(_sensor.temp)){
char _polyline[176];
MI32createPolyline(_polyline,_sensor.temp_history);
@ -2058,15 +2082,6 @@ void MI32ShowContinuation(bool *commaflg) {
void MI32Show(bool json)
{
if (json) {
#ifdef USE_HOME_ASSISTANT
bool _noSummarySave = MI32.option.noSummary;
bool _minimalSummarySave = MI32.option.minimalSummary;
if(hass_mode==2){
MI32.option.noSummary = false;
MI32.option.minimalSummary = false;
}
#endif //USE_HOME_ASSISTANT
if(!MI32.mode.triggeredTele){
if(MI32.option.noSummary) return; // no message at TELEPERIOD
}
@ -2077,18 +2092,14 @@ void MI32Show(bool json)
bool commaflg = false;
ResponseAppend_P(PSTR(",\"%s-%02x%02x%02x\":{"),
kMI32DeviceType[MIBLEsensors[i].type-1],
MI32getDeviceName(i),
MIBLEsensors[i].MAC[3], MIBLEsensors[i].MAC[4], MIBLEsensors[i].MAC[5]);
if((!MI32.mode.triggeredTele && !MI32.option.minimalSummary)||MI32.mode.triggeredTele){
bool tempHumSended = false;
if(MIBLEsensors[i].feature.tempHum){
if(MIBLEsensors[i].eventType.tempHum || !MI32.mode.triggeredTele || MI32.option.allwaysAggregate){
if (!isnan(MIBLEsensors[i].hum) && !isnan(MIBLEsensors[i].temp)
#ifdef USE_HOME_ASSISTANT
||(hass_mode!=-1)
#endif //USE_HOME_ASSISTANT
) {
if (!isnan(MIBLEsensors[i].hum) && !isnan(MIBLEsensors[i].temp)) {
MI32ShowContinuation(&commaflg);
ResponseAppendTHD(MIBLEsensors[i].temp, MIBLEsensors[i].hum);
tempHumSended = true;
@ -2097,11 +2108,7 @@ void MI32Show(bool json)
}
if(MIBLEsensors[i].feature.temp && !tempHumSended){
if(MIBLEsensors[i].eventType.temp || !MI32.mode.triggeredTele || MI32.option.allwaysAggregate) {
if (!isnan(MIBLEsensors[i].temp)
#ifdef USE_HOME_ASSISTANT
||(hass_mode!=-1)
#endif //USE_HOME_ASSISTANT
) {
if (!isnan(MIBLEsensors[i].temp)) {
MI32ShowContinuation(&commaflg);
ResponseAppend_P(PSTR("\"" D_JSON_TEMPERATURE "\":%*_f"),
Settings->flag2.temperature_resolution, &MIBLEsensors[i].temp);
@ -2110,11 +2117,7 @@ void MI32Show(bool json)
}
if(MIBLEsensors[i].feature.hum && !tempHumSended){
if(MIBLEsensors[i].eventType.hum || !MI32.mode.triggeredTele || MI32.option.allwaysAggregate) {
if (!isnan(MIBLEsensors[i].hum)
#ifdef USE_HOME_ASSISTANT
||(hass_mode!=-1)
#endif //USE_HOME_ASSISTANT
) {
if (!isnan(MIBLEsensors[i].hum)) {
char hum[FLOATSZ];
dtostrfd(MIBLEsensors[i].hum, Settings->flag2.humidity_resolution, hum);
MI32ShowContinuation(&commaflg);
@ -2124,17 +2127,7 @@ void MI32Show(bool json)
}
if (MIBLEsensors[i].feature.lux){
if(MIBLEsensors[i].eventType.lux || !MI32.mode.triggeredTele || MI32.option.allwaysAggregate){
#ifdef USE_HOME_ASSISTANT
if ((hass_mode != -1) && (MIBLEsensors[i].lux == 0x0ffffff)) {
MI32ShowContinuation(&commaflg);
ResponseAppend_P(PSTR("\"" D_JSON_ILLUMINANCE "\":null"));
} else
#endif //USE_HOME_ASSISTANT
if ((MIBLEsensors[i].lux != 0x0ffffff)
#ifdef USE_HOME_ASSISTANT
|| (hass_mode != -1)
#endif //USE_HOME_ASSISTANT
) { // this is the error code -> no lux
if ((MIBLEsensors[i].lux != 0x0ffffff)) { // this is the error code -> no lux
MI32ShowContinuation(&commaflg);
ResponseAppend_P(PSTR("\"" D_JSON_ILLUMINANCE "\":%u"), MIBLEsensors[i].lux);
}
@ -2142,17 +2135,7 @@ void MI32Show(bool json)
}
if (MIBLEsensors[i].feature.moist){
if(MIBLEsensors[i].eventType.moist || !MI32.mode.triggeredTele || MI32.option.allwaysAggregate){
#ifdef USE_HOME_ASSISTANT
if ((hass_mode != -1) && (MIBLEsensors[i].moisture == 0xff)) {
MI32ShowContinuation(&commaflg);
ResponseAppend_P(PSTR("\"" D_JSON_MOISTURE "\":null"));
} else
#endif //USE_HOME_ASSISTANT
if ((MIBLEsensors[i].moisture != 0xff)
#ifdef USE_HOME_ASSISTANT
|| (hass_mode != -1)
#endif //USE_HOME_ASSISTANT
) {
if ((MIBLEsensors[i].moisture != 0xff)) {
MI32ShowContinuation(&commaflg);
ResponseAppend_P(PSTR("\"" D_JSON_MOISTURE "\":%u"), MIBLEsensors[i].moisture);
}
@ -2160,38 +2143,20 @@ void MI32Show(bool json)
}
if (MIBLEsensors[i].feature.fert){
if(MIBLEsensors[i].eventType.fert || !MI32.mode.triggeredTele || MI32.option.allwaysAggregate){
#ifdef USE_HOME_ASSISTANT
if ((hass_mode != -1) && (MIBLEsensors[i].fertility == 0xffff)) {
MI32ShowContinuation(&commaflg);
ResponseAppend_P(PSTR("\"Fertility\":null"));
} else
#endif //USE_HOME_ASSISTANT
if ((MIBLEsensors[i].fertility != 0xffff)
#ifdef USE_HOME_ASSISTANT
|| (hass_mode != -1)
#endif //USE_HOME_ASSISTANT
) {
if ((MIBLEsensors[i].fertility != 0xffff)) {
MI32ShowContinuation(&commaflg);
ResponseAppend_P(PSTR("\"Fertility\":%u"), MIBLEsensors[i].fertility);
}
}
}
if (MIBLEsensors[i].feature.Btn){
if(MIBLEsensors[i].eventType.Btn
#ifdef USE_HOME_ASSISTANT
||(hass_mode==2)
#endif //USE_HOME_ASSISTANT
){
if(MIBLEsensors[i].eventType.Btn){
MI32ShowContinuation(&commaflg);
ResponseAppend_P(PSTR("\"Button%u\":%u"),MIBLEsensors[i].Btn,MIBLEsensors[i].BtnType + 1); //internal type is Xiaomi/Homekit 0,1,2 -> Tasmota 1,2,3
}
}
if (MIBLEsensors[i].feature.knob){
if(MIBLEsensors[i].eventType.knob
#ifdef USE_HOME_ASSISTANT
||(hass_mode==2)
#endif //USE_HOME_ASSISTANT
){
if(MIBLEsensors[i].eventType.knob){
MI32ShowContinuation(&commaflg);
char _pressed[3] = {'_','P',0};
if (MIBLEsensors[i].pressed == 0){
@ -2199,11 +2164,7 @@ void MI32Show(bool json)
}
ResponseAppend_P(PSTR("\"Dimmer%s\":%d"),_pressed, MIBLEsensors[i].dimmer);
}
if(MIBLEsensors[i].eventType.longpress
#ifdef USE_HOME_ASSISTANT
||(hass_mode==2)
#endif //USE_HOME_ASSISTANT
){
if(MIBLEsensors[i].eventType.longpress){
MI32ShowContinuation(&commaflg);
ResponseAppend_P(PSTR("\"Hold\":%d"), MIBLEsensors[i].longpress);
}
@ -2250,17 +2211,7 @@ void MI32Show(bool json)
}
if (MIBLEsensors[i].feature.bat){
if(MIBLEsensors[i].eventType.bat || !MI32.mode.triggeredTele || MI32.option.allwaysAggregate){
#ifdef USE_HOME_ASSISTANT
if ((hass_mode != -1) && (MIBLEsensors[i].bat == 0x00)) {
MI32ShowContinuation(&commaflg);
ResponseAppend_P(PSTR("\"Battery\":null"));
} else
#endif //USE_HOME_ASSISTANT
if ((MIBLEsensors[i].bat != 0x00)
#ifdef USE_HOME_ASSISTANT
|| (hass_mode != -1)
#endif //USE_HOME_ASSISTANT
) {
if ((MIBLEsensors[i].bat != 0x00)) {
MI32ShowContinuation(&commaflg);
ResponseAppend_P(PSTR("\"Battery\":%u"), MIBLEsensors[i].bat);
}
@ -2279,12 +2230,6 @@ void MI32Show(bool json)
}
}
MI32.mode.triggeredTele = 0;
#ifdef USE_HOME_ASSISTANT
if(hass_mode==2){
MI32.option.noSummary = _noSummarySave;
MI32.option.minimalSummary = _minimalSummarySave;
}
#endif //USE_HOME_ASSISTANT
#ifdef USE_MI_EXT_GUI
Mi32invalidateOldHistory();
#ifdef USE_MI_ESP32_ENERGY
@ -2307,22 +2252,23 @@ void MI32Show(bool json)
WSContentSend_PD(HTTP_MI32_HL);
char _MAC[18];
ToHex_P(MIBLEsensors[i].MAC,6,_MAC,18,':');
WSContentSend_PD(HTTP_MI32_MAC, kMI32DeviceType[MIBLEsensors[i].type-1], D_MAC_ADDRESS, _MAC);
WSContentSend_PD(HTTP_RSSI, kMI32DeviceType[MIBLEsensors[i].type-1], MIBLEsensors[i].RSSI);
const char * _sensorName = MI32getDeviceName(i);
WSContentSend_PD(HTTP_MI32_MAC, _sensorName, D_MAC_ADDRESS, _MAC);
WSContentSend_PD(HTTP_RSSI, _sensorName, MIBLEsensors[i].RSSI);
if (MIBLEsensors[i].type==FLORA) {
if (!isnan(MIBLEsensors[i].temp)) {
WSContentSend_Temp(kMI32DeviceType[MIBLEsensors[i].type-1], MIBLEsensors[i].temp);
WSContentSend_Temp(_sensorName, MIBLEsensors[i].temp);
}
if (MIBLEsensors[i].moisture!=0xff) {
WSContentSend_PD(HTTP_SNS_MOISTURE, kMI32DeviceType[MIBLEsensors[i].type-1], MIBLEsensors[i].moisture);
WSContentSend_PD(HTTP_SNS_MOISTURE, _sensorName, MIBLEsensors[i].moisture);
}
if (MIBLEsensors[i].fertility!=0xffff) {
WSContentSend_PD(HTTP_MI32_FLORA_DATA, kMI32DeviceType[MIBLEsensors[i].type-1], MIBLEsensors[i].fertility);
WSContentSend_PD(HTTP_MI32_FLORA_DATA, _sensorName, MIBLEsensors[i].fertility);
}
}
if (MIBLEsensors[i].type>FLORA) { // everything "above" Flora
if (!isnan(MIBLEsensors[i].hum) && !isnan(MIBLEsensors[i].temp)) {
WSContentSend_THD(kMI32DeviceType[MIBLEsensors[i].type-1], MIBLEsensors[i].temp, MIBLEsensors[i].hum);
WSContentSend_THD(_sensorName, MIBLEsensors[i].temp, MIBLEsensors[i].hum);
}
}
if (MIBLEsensors[i].feature.needsKey) {
@ -2334,20 +2280,20 @@ void MI32Show(bool json)
}
}
if (MIBLEsensors[i].type==NLIGHT || MIBLEsensors[i].type==MJYD2S) {
WSContentSend_PD(HTTP_EVENTS, kMI32DeviceType[MIBLEsensors[i].type-1], MIBLEsensors[i].events);
if(MIBLEsensors[i].NMT>0) WSContentSend_PD(HTTP_NMT, kMI32DeviceType[MIBLEsensors[i].type-1], MIBLEsensors[i].NMT);
WSContentSend_PD(HTTP_EVENTS, _sensorName, MIBLEsensors[i].events);
if(MIBLEsensors[i].NMT>0) WSContentSend_PD(HTTP_NMT, _sensorName, MIBLEsensors[i].NMT);
}
if(MIBLEsensors[i].door != 255 && MIBLEsensors[i].type==MCCGQ02){
WSContentSend_PD(HTTP_DOOR, kMI32DeviceType[MIBLEsensors[i].type-1], MIBLEsensors[i].door);
WSContentSend_PD(HTTP_DOOR, _sensorName, MIBLEsensors[i].door);
}
if (MIBLEsensors[i].lux!=0x00ffffff) { // this is the error code -> no valid value
WSContentSend_PD(HTTP_SNS_ILLUMINANCE, kMI32DeviceType[MIBLEsensors[i].type-1], MIBLEsensors[i].lux);
WSContentSend_PD(HTTP_SNS_ILLUMINANCE, _sensorName, MIBLEsensors[i].lux);
}
if(MIBLEsensors[i].bat!=0x00){
WSContentSend_PD(HTTP_BATTERY, kMI32DeviceType[MIBLEsensors[i].type-1], MIBLEsensors[i].bat);
WSContentSend_PD(HTTP_BATTERY, _sensorName, MIBLEsensors[i].bat);
}
if (MIBLEsensors[i].type==YLYK01){
WSContentSend_PD(HTTP_LASTBUTTON, kMI32DeviceType[MIBLEsensors[i].type-1], MIBLEsensors[i].Btn);
WSContentSend_PD(HTTP_LASTBUTTON, _sensorName, MIBLEsensors[i].Btn);
}
}
#endif //USE_MI_EXT_GUI

View File

@ -18,7 +18,7 @@ static int MI32_accessory_identify(hap_acc_t *ha);
static void MI32_bridge_thread_entry(void *p);
extern uint32_t MI32numberOfDevices();
extern const char *MI32getDeviceName(uint32_t slot);
extern char *MI32getDeviceName(uint32_t slot);
extern uint32_t MI32getDeviceType(uint32_t slot);
extern void MI32saveHAPhandles(uint32_t slot, uint32_t type, void* handle);
extern void MI32passHapEvent(uint32_t event);
@ -32,6 +32,7 @@ static bool MIBridgeWasNeverConnected = true;
#define CONFIG_EXAMPLE_SETUP_ID "MI32"
#define UNKNOWN_MI 0
#define FLORA 1
#define MJ_HT_V1 2
#define LYWSD02 3
@ -48,6 +49,7 @@ static bool MIBridgeWasNeverConnected = true;
#define SJWS01L 14
#define PVVX 15
#define YLKG08 16
#define YLAI003 17
/*********************************************************************************************\
* Homekit
@ -139,7 +141,8 @@ static void MI32_bridge_thread_entry(void *p)
/* Create and add the Accessory to the Bridge object*/
uint32_t _numDevices = MI32numberOfDevices();
for (uint32_t i = 0; i < _numDevices; i++) {
char *accessory_name = (char*)MI32getDeviceName(i);
if(MI32getDeviceType(i) == UNKNOWN_MI) continue;
char *accessory_name = MI32getDeviceName(i);
char _serialNum[4] = {0};
snprintf(_serialNum,sizeof(_serialNum),"%u", i);

View File

@ -0,0 +1,4 @@
#Z2Tv1
# TuYa generic switch https://www.zigbee2mqtt.io/devices/TS0001_switch_module.html
:TS0001,_TZ3000_*
0006/8002%30,StartUpOnOff

View File

@ -287,7 +287,7 @@ a_features = [[
"USE_BP5758D","USE_HYT","USE_SM2335","USE_DISPLAY_TM1621_SONOFF"
],[
"USE_SGP40","USE_LUXV30B","USE_CANSNIFFER","USE_QMC5883L",
"","","","",
"USE_MODBUS_ENERGY","","","",
"","","","",
"","","","",
"","","","",