Add ChType_Enum and enable SetChannelEnum. (#1830)

* create a ChType_Enum to go with SetChannelEnum

* resolve build errors for ChType_Enum PR

* fixing build errors for simulator and others for cmd_enums.c

* added ChType_ReadOnlyEnum and assocaited enum selftests

* ChType_Enum simulation and memory error corrections

* ChType_Enum documentation updates

---------

Co-authored-by: root <root@stonacek.nz>
This commit is contained in:
KC Stonacek 2025-10-14 20:21:18 +13:00 committed by GitHub
parent 993a46a19d
commit 4ad9a85a08
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
27 changed files with 564 additions and 112 deletions

View File

@ -1355,3 +1355,81 @@ return
```
### Example use of Channel Enum Types in combination with TuyaMCU dpids
Example uses of `SetChannelType [ch] Enum` and `SetChannelType [ch] ReadOnlyEnum` combined `SetChannelEnum [ch] [option:label]`
From [CB2S - Tongou MCB Temperature and Humidity relay](https://www.elektroda.com/rtvforum/viewtopic.php?p=21125206#21125206)
<br>
```c++
// tuyaMCU store RAW data in /cm?cmnd=Dp must be turned off on this device..
setflag 46 0
ntp_setServer 132.163.97.4
ntp_timeZoneOfs 12:00
startDriver TuyaMCU
tuyaMcu_setBaudRate 115200
// always report paired
tuyaMcu_defWiFiState 4
// update states any time the temperature changes
addEventHandler OnChannelChange 27 tuyaMcu_sendQueryState
// 2 switch 1 relay bool - 121 device control must be 2 or 3 (remote mode)
setChannelType 21 Toggle
setChannelLabel 21 "Switch 1"
linkTuyaMCUOutputToChannel 2 bool 21
// 101 switch 2 relay bool - 121 device control must be 2 or 3 (remote mode)
setChannelType 22 Toggle
setChannelLabel 22 "Switch 2"
linkTuyaMCUOutputToChannel 101 bool 22
// 27 current temperature /10 - dpId 20 changes C/F
setChannelType 1 Temperature_div10
setChannelLabel 1 temperature
linkTuyaMCUOutputToChannel 27 val 1
// 46 current humidity
setChannelType 2 Humidity
setChannelLabel 2 Humidity
linkTuyaMCUOutputToChannel 46 val 2
// 118 event RO
setChannelType 3 ReadOnlyEnum
setChannelLabel 3 "Event Status"
SetChannelEnum 3 0:Normal "9:Buttons Locked" "10:Local Mode" "11:Remote Control" "12:Any Control"
linkTuyaMCUOutputToChannel 118 enum 3
//102 online state enum; 0 online, 1 offline
setChannelType 4 ReadOnlyEnum
setChannelLabel 4 "Online State"
setChannelEnum 4 0:Online 1:Offline
linkTuyaMCUOutputToChannel 102 enum 4
// 121 device control mode enum; 0 local_lock, 1 MCU control, 2 OBK control, 3 MCU and Tuya control
setChannelType 5 Enum
setChannelLabel 5 "Device Control"
SetChannelEnum 5 "0:Buttons Locked" "1:Device Control" "2:Remote Control" "3:Any Control"
linkTuyaMCUOutputToChannel 121 enum 5
// 106 device Power-On Relay behaviour
setChannelType 6 Enum
setChannelLabel 6 "Power-on Behaviour"
SetChannelEnum 6 0:off 1:on 2:memory
linkTuyaMCUOutputToChannel 106 enum 6
// 107 Switch 1 Automatic Control Mode
setChannelType 7 Enum
setChannelLabel 7 "Switch 1 Control Mode"
setChannelEnum 7 0:Temp 1:Humidity
linkTuyaMCUOutputToChannel 107 enum 7
//trunacted for example
// refresh tuyaMCU after definitions
tuyaMcu_sendQueryState
```

View File

@ -66,4 +66,6 @@ Do not add anything here, as it will overwritten with next rebuild.
| Frequency_div1000 | For TuyaMCU power metering. Not used for BL09** and CSE** sensors. Divider is used by TuyaMCU, because TuyaMCU sends always values as integers so we have to divide them before displaying on UI |
| OpenStopClose | TODO |
| Percent | TODO |
| Enum | This channel type allows creating custom Enum types in combination with SetChannelEnum command. Ideal for defining TuyaMCU enum mappings. |
| ReadOnlyEnum | Read Only Enum Channel type for use in combination with SetChannelEnum command. Ideal for defining TuyaMCU enum mappings. |
| Max | This is the current total number of available channel types. |

View File

@ -232,7 +232,7 @@ Do not add anything here, as it will overwritten with next rebuild.
| setButtonLabel | [ButtonIndex][Label] | Sets the label of custom scriptable HTTP page button.<br/><br/>See also [setButtonLabel on forum](https://www.elektroda.com/rtvforum/find.php?q=setButtonLabel). | File: driver/drv_httpButtons.c<br/>Function: CMD_setButtonLabel |
| setButtonTimes | [ValLongPress][ValShortPress][ValRepeat] | Each value is times 100ms, so: SetButtonTimes 2 1 1 means 200ms long press, 100ms short and 100ms repeat.<br/><br/>See also [setButtonTimes on forum](https://www.elektroda.com/rtvforum/find.php?q=setButtonTimes). | File: new_pins.c<br/>Function: CMD_SetButtonTimes |
| SetChannel | [ChannelIndex][ChannelValue] | Sets a raw channel to given value. Relay channels are using 1 and 0 values. PWM channels are within [0,100] range. Do not use this for LED control, because there is a better and more advanced LED driver with dimming and configuration memory (remembers setting after on/off), LED driver commands has 'led_' prefix.<br/><br/>See also [SetChannel on forum](https://www.elektroda.com/rtvforum/find.php?q=SetChannel). | File: cmnds/cmd_channels.c<br/>Function: CMD_SetChannel |
| SetChannelEnum | [ChannelIndex][Value,Title][Value,Title] | Creates a custom channel enumeration.<br/><br/>See also [SetChannelEnum on forum](https://www.elektroda.com/rtvforum/find.php?q=SetChannelEnum). | File: cmnds/cmd_channels.c<br/>Function: SetChannelEnum |
| SetChannelEnum | [ChannelIndex][Value:Title][Value:Title] | Creates a channel enumeration type. Channel type must be set to Enum. e.g. SetChannelEnum 1:One "2:Enum Two" 5:Five.<br/><br/>See also [SetChannelEnum on forum](https://www.elektroda.com/rtvforum/find.php?q=SetChannelEnum). | File: cmnds/cmd_channels.c<br/>Function: SetChannelEnum |
| SetChannelFloat | [ChannelIndex][ChannelValue] | Sets a raw channel to given float value. Currently only used for LED PWM channels.<br/><br/>See also [SetChannelFloat on forum](https://www.elektroda.com/rtvforum/find.php?q=SetChannelFloat). | File: cmnds/cmd_channels.c<br/>Function: CMD_SetChannelFloat |
| SetChannelLabel | [ChannelIndex][Str][bHideTogglePrefix] | Sets a channel label for UI and default entity name for Home Assistant discovery. If you use 1 for bHideTogglePrefix, then the 'Toggle ' prefix from UI button will be omitted.<br/><br/>See also [SetChannelLabel on forum](https://www.elektroda.com/rtvforum/find.php?q=SetChannelLabel). | File: cmnds/cmd_channels.c<br/>Function: CMD_SetChannelLabel |
| SetChannelPrivate | [ChannelIndex][bPrivate] | Channels marked as private are NEVER published via MQTT and excluded from Home Assistant discovery.<br/><br/>See also [SetChannelPrivate on forum](https://www.elektroda.com/rtvforum/find.php?q=SetChannelPrivate). | File: cmnds/cmd_channels.c<br/>Function: NULL); |

View File

@ -235,7 +235,7 @@ Do not add anything here, as it will overwritten with next rebuild.
| setButtonLabel | [ButtonIndex][Label] | Sets the label of custom scriptable HTTP page button.<br/><br/>See also [setButtonLabel on forum](https://www.elektroda.com/rtvforum/find.php?q=setButtonLabel). |
| setButtonTimes | [ValLongPress][ValShortPress][ValRepeat] | Each value is times 100ms, so: SetButtonTimes 2 1 1 means 200ms long press, 100ms short and 100ms repeat.<br/><br/>See also [setButtonTimes on forum](https://www.elektroda.com/rtvforum/find.php?q=setButtonTimes). |
| SetChannel | [ChannelIndex][ChannelValue] | Sets a raw channel to given value. Relay channels are using 1 and 0 values. PWM channels are within [0,100] range. Do not use this for LED control, because there is a better and more advanced LED driver with dimming and configuration memory (remembers setting after on/off), LED driver commands has 'led_' prefix.<br/><br/>See also [SetChannel on forum](https://www.elektroda.com/rtvforum/find.php?q=SetChannel). |
| SetChannelEnum | [ChannelIndex][Value,Title][Value,Title] | Creates a custom channel enumeration.<br/><br/>See also [SetChannelEnum on forum](https://www.elektroda.com/rtvforum/find.php?q=SetChannelEnum). |
| SetChannelEnum | [ChannelIndex][Value:Title][Value:Title] | Creates a channel enumeration type. Channel type must be set to Enum or ReadOnlyEnum. e.g. SetChannelEnum 1:One "2:Enum Two" 5:Five.<br/><br/>See also [SetChannelEnum on forum](https://www.elektroda.com/rtvforum/find.php?q=SetChannelEnum). |
| SetChannelFloat | [ChannelIndex][ChannelValue] | Sets a raw channel to given float value. Currently only used for LED PWM channels.<br/><br/>See also [SetChannelFloat on forum](https://www.elektroda.com/rtvforum/find.php?q=SetChannelFloat). |
| SetChannelLabel | [ChannelIndex][Str][bHideTogglePrefix] | Sets a channel label for UI and default entity name for Home Assistant discovery. If you use 1 for bHideTogglePrefix, then the 'Toggle ' prefix from UI button will be omitted.<br/><br/>See also [SetChannelLabel on forum](https://www.elektroda.com/rtvforum/find.php?q=SetChannelLabel). |
| SetChannelPrivate | [ChannelIndex][bPrivate] | Channels marked as private are NEVER published via MQTT and excluded from Home Assistant discovery.<br/><br/>See also [SetChannelPrivate on forum](https://www.elektroda.com/rtvforum/find.php?q=SetChannelPrivate). |

View File

@ -495,6 +495,22 @@
"file": "new_pins.h",
"driver": ""
},
{
"name": "Enum",
"title": "Enum",
"descr": "This channel type allows creating custom Enum types in combination with SetChannelEnum command. Ideal for defining TuyaMCU enum mappings.",
"enum": "ChType_Enum",
"file": "new_pins.h",
"driver": ""
},
{
"name": "ReadOnlyEnum",
"title": "ReadOnlyEnum",
"descr": "Read Only Enum Channel type for use in combination with SetChannelEnum command. Ideal for defining TuyaMCU enum mappings.",
"enum": "ChType_ReadOnlyEnum",
"file": "new_pins.h",
"driver": ""
},
{
"name": "Max",
"title": "TODO",
@ -503,4 +519,4 @@
"file": "new_pins.h",
"driver": ""
}
]
]

View File

@ -2044,8 +2044,8 @@
},
{
"name": "SetChannelEnum",
"args": "[ChannelIndex][Value,Title][Value,Title]",
"descr": "Creates a custom channel enumeration.",
"args": "[ChannelIndex][Value:Title][Value:Title]",
"descr": "Creates a channel enumeration type. Channel type must be set to Enum or ReadOnlyEnum. e.g. SetChannelEnum 1:One \"2:Enum Two\" 5:Five",
"fn": "SetChannelEnum",
"file": "cmnds/cmd_channels.c",
"requires": "",
@ -3257,4 +3257,4 @@
"requires": "",
"examples": ""
}
]
]

View File

@ -179,6 +179,7 @@
<ClCompile Include="src\cJSON\cJSON.c" />
<ClCompile Include="src\cmnds\cmd_berry.c" />
<ClCompile Include="src\cmnds\cmd_channels.c" />
<ClCompile Include="src\cmnds\cmd_enums.c" />
<ClCompile Include="src\cmnds\cmd_eventHandlers.c" />
<ClCompile Include="src\cmnds\cmd_if.c" />
<ClCompile Include="src\cmnds\cmd_main.c" />
@ -783,6 +784,7 @@
<ClCompile Include="src\selftest\selftest_demo_signAndValue.c" />
<ClCompile Include="src\selftest\selftest_doorSensor.c" />
<ClCompile Include="src\selftest\selftest_flashSearch.c" />
<ClCompile Include="src\selftest\selftest_enums.c" />
<ClCompile Include="src\selftest\selftest_hass_discovery_base.c" />
<ClCompile Include="src\selftest\selftest_hass_discovery_ext.c" />
<ClCompile Include="src\selftest\selftest_http_led.c" />
@ -1305,4 +1307,4 @@
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>
</Project>

View File

@ -14,6 +14,7 @@
<ClCompile Include="src\bitmessage\bitmessage_write.c" />
<ClCompile Include="src\cJSON\cJSON.c" />
<ClCompile Include="src\cmnds\cmd_channels.c" />
<ClCompile Include="src\selftest\selftest_enums.c" />
<ClCompile Include="src\cmnds\cmd_eventHandlers.c" />
<ClCompile Include="src\cmnds\cmd_if.c" />
<ClCompile Include="src\cmnds\cmd_main.c" />
@ -655,4 +656,4 @@
<CustomBuild Include="src\rgb2hsv.h" />
<CustomBuild Include="..\..\platforms\bk7231t\bk7231t_os\application.mk" />
</ItemGroup>
</Project>
</Project>

View File

@ -6,6 +6,7 @@ set(OBKM_SRC
${OBK_SRCS}bitmessage/bitmessage_write.c
${OBK_SRCS}cmnds/cmd_berry.c
${OBK_SRCS}cmnds/cmd_channels.c
${OBK_SRCS}cmnds/cmd_enums.c
${OBK_SRCS}cmnds/cmd_eventHandlers.c
${OBK_SRCS}cmnds/cmd_if.c
${OBK_SRCS}cmnds/cmd_main.c

View File

@ -21,6 +21,7 @@ OBKM_SRC += $(OBK_SRCS)bitmessage/bitmessage_write.c
OBKM_SRC += $(OBK_SRCS)cJSON/cJSON.c
OBKM_SRC += $(OBK_SRCS)cmnds/cmd_berry.c
OBKM_SRC += $(OBK_SRCS)cmnds/cmd_channels.c
OBKM_SRC += $(OBK_SRCS)cmnds/cmd_enums.c
OBKM_SRC += $(OBK_SRCS)cmnds/cmd_eventHandlers.c
OBKM_SRC += $(OBK_SRCS)cmnds/cmd_if.c
OBKM_SRC += $(OBK_SRCS)cmnds/cmd_main.c

View File

@ -18,6 +18,7 @@ int g_doNotPublishChannels = 0;
void CHANNEL_FreeLabels() {
for (int ch = 0; ch < CHANNEL_MAX; ch++) {
CMD_FreeLabels(); // free any enum labels
if (g_channelLabels[ch]) {
free(g_channelLabels[ch]);
g_channelLabels[ch] = 0;
@ -592,13 +593,11 @@ void CMD_InitChannelCommands(){
//cmddetail:"fn":"CMD_FullBootTime","file":"cmnds/cmd_channels.c","requires":"",
//cmddetail:"examples":""}
CMD_RegisterCommand("FullBootTime", CMD_FullBootTime, NULL);
//cmddetail:{"name":"SetChannelEnum","args":"[ChannelIndex][Value,Title][Value,Title]",
//cmddetail:"descr":"Creates a custom channel enumeration.",
//cmddetail:{"name":"SetChannelEnum","args":"[ChannelIndex][Value:Title][Value:Title]",
//cmddetail:"descr":"Creates a channel enumeration type. Channel type must be set to Enum or ReadOnlyEnum. e.g. SetChannelEnum 1:One \"2:Enum Two\" 5:Five",
//cmddetail:"fn":"SetChannelEnum","file":"cmnds/cmd_channels.c","requires":"",
//cmddetail:"examples":""}
#if WINDOWS
//CMD_RegisterCommand("SetChannelEnum", CMD_SetChannelEnum, NULL);
#endif
CMD_RegisterCommand("SetChannelEnum", CMD_SetChannelEnum, NULL);
//cmddetail:{"name":"SetChannelLabel","args":"[ChannelIndex][Str][bHideTogglePrefix]",
//cmddetail:"descr":"Sets a channel label for UI and default entity name for Home Assistant discovery. If you use 1 for bHideTogglePrefix, then the 'Toggle ' prefix from UI button will be omitted",
//cmddetail:"fn":"CMD_SetChannelLabel","file":"cmnds/cmd_channels.c","requires":"",

148
src/cmnds/cmd_enums.c Normal file
View File

@ -0,0 +1,148 @@
#include "../logging/logging.h"
#include "../new_pins.h"
#include "../new_cfg.h"
#include "../obk_config.h"
#include "../driver/drv_public.h"
#include <ctype.h>
#include "cmd_local.h"
#include "cmd_enums.h"
#define CMD_ENUM_MAX_LABEL_SIZE 32
channelEnum_t **g_enums = 0;
void CMD_FreeChannelEnumOptions(int ch) {
if (g_enums && g_enums[ch] && g_enums[ch]->numOptions != 0) {
for (int i = 0; i < g_enums[ch]->numOptions; i++) {
os_free(g_enums[ch]->options[i].label);
}
os_free(g_enums[ch]->options);
g_enums[ch]->numOptions=0;
os_free(g_enums[ch]);
g_enums[ch] = 0;
}
}
// method to clean up on shutdown
void CMD_FreeLabels() {
for (int ch = 0; ch < CHANNEL_MAX; ch++) {
// if (g_enums && g_enums[ch] && g_enums[ch]->numOptions != 0) {
CMD_FreeChannelEnumOptions(ch);
//os_free(g_enums[ch]);
}
//os_free(g_enums);
//g_enums = 0;
}
// helpers for preparing a homeassistant select template for enums
void CMD_GenEnumValueTemplate(channelEnum_t *e, char *out, int outSize) {
CMD_FormatEnumTemplate(e, out, outSize, false);
}
void CMD_GenEnumCommandTemplate(channelEnum_t *e, char *out, int outSize) {
CMD_FormatEnumTemplate(e, out, outSize, true);
}
// this is quite similar to hass.c generate_command_template,
// except for enum types are not ordered and don't align to an array index
// we also want a default for undefined enums
// in the future this could be merge with those in hass.c
void CMD_FormatEnumTemplate(channelEnum_t *e, char *out,
int outSize, bool isCommand) {
char tmp[CMD_ENUM_MAX_LABEL_SIZE+24];
int numOptions = 0;
*out = 0;
strcat_safe(out, "{{ {", outSize);
if (e != NULL && e != 0)
numOptions = e->numOptions;
for (int i = 0; i < numOptions; i++) {
if (!isCommand)
sprintf(tmp, "%i:'%s', ", e->options[i].value,e->options[i].label);
else
sprintf(tmp, "'%s':'%i', ", e->options[i].label,e->options[i].value);
strcat_safe(out, tmp, outSize);
}
if (!isCommand) {
strcat_safe(out, "99999:'Undefined'}", outSize);
strcat_safe(out,"[(value | int(99999))] | default(\"Undefined Enum [\"~value~\"]\") }}",outSize);
} else {
strcat_safe(out, "'Undefined':'99999'}", outSize);
strcat_safe(out,"[value] | default(99999) }}",outSize);
}
#if WINDOWS
FILE *f = fopen("lastEnumTemplate.txt", "w");
fprintf(f, out);
fclose(f);
#endif
}
commandResult_t CMD_SetChannelEnum(const void *context, const char *cmd,
const char *args, int cmdFlags) {
int ch;
const char *s;
char *label;
channelEnum_t *en;
Tokenizer_TokenizeString(args, TOKENIZER_ALLOW_QUOTES);
// following check must be done after 'Tokenizer_TokenizeString',
// so we know arguments count in Tokenizer. 'cmd' argument is
// only for warning display
if (Tokenizer_CheckArgsCountAndPrintWarning(cmd, 2)) {
return CMD_RES_NOT_ENOUGH_ARGUMENTS;
}
ch = Tokenizer_GetArgInteger(0);
if (g_enums == 0) {
g_enums = malloc(sizeof(channelEnum_t*)*CHANNEL_MAX);
memset(g_enums,0, sizeof(channelEnum_t*)*CHANNEL_MAX);
}
if (g_enums[ch] != 0 && g_enums[ch]->numOptions != 0) {
// free any previously defined channel enums
CMD_FreeChannelEnumOptions(ch);
}
en = malloc(sizeof(channelEnum_t));
g_enums[ch] = en;
en->numOptions = Tokenizer_GetArgsCount()-1;
en->options = malloc(sizeof(channelEnumOption_t)*en->numOptions);
for (int i = 0; i < en->numOptions; i++) {
s = Tokenizer_GetArg(1+i);
en->options[i].value = atoi(s);
while (*s) {
if (*s == ':') {
s++;
break;
}
s++;
}
//en->options[i].label = strdup(s);
int llen = strlen(s) > CMD_ENUM_MAX_LABEL_SIZE ? CMD_ENUM_MAX_LABEL_SIZE : strlen(s);
label = (char *)malloc(llen+1);
strncpy(label,s,llen);
label[llen]='\0';
en->options[i].label = label;
}
return CMD_RES_OK;
}
// helper function to map enum values to labels
char* CMD_FindChannelEnumLabel(channelEnum_t *channelEnum, int value) {
static char notfound[5]; // assuming INT_MAX 32k
snprintf(notfound, 5, "%i", value); // if no label defined, use the value
if (channelEnum == NULL || channelEnum == 0 || channelEnum->numOptions == 0 ) { // g_enum zeroed on init
return notfound;
}
for (int i = 0; i < channelEnum->numOptions; i++) {
if (channelEnum->options[i].value == value)
return channelEnum->options[i].label;
}
return notfound;
}

View File

@ -16,9 +16,11 @@ typedef struct channelEnum_s {
extern channelEnum_t **g_enums;
void CMD_GenEnumValueTemplate(channelEnum_t *e, char *out, int outSize);
void CMD_GenEnumCommandTemplate(channelEnum_t *e, char *out, int outSize);
void CMD_FormatEnumTemplate(channelEnum_t *e, char *out, int outSize, bool isCommand);
void CMD_FormatEnumTemplate(channelEnum_t *e, char *out, int outSize);
char* CMD_FindChannelEnumLabel(channelEnum_t *channelEnum, int value);

View File

@ -25,6 +25,7 @@ commandResult_t CMD_If(const void *context, const char *cmd, const char *args, i
void CMD_ExpandConstantsWithinString(const char *in, char *out, int outLen);
void CMD_Script_ProcessWaitersForEvent(byte eventCode, int argument);
bool CheckEventCondition(eventWait_t *w, byte eventCode, int argument);
void CMD_FreeLabels();
#endif // __CMD_LOCAL_H__

View File

@ -1,79 +0,0 @@
#include "../logging/logging.h"
#include "../new_pins.h"
#include "../new_cfg.h"
#include "../obk_config.h"
#include "../driver/drv_public.h"
#include <ctype.h>
#include "cmd_local.h"
#include "cmd_newEnums.h"
channelEnum_t **g_enums = 0;
void CMD_FormatEnumTemplate(channelEnum_t *e, char *out, int outSize) {
char tmp[8];
*out = 0;
for (int i = 0; i < e->numOptions; i++) {
strcat_safe(out, "{% ", outSize);
if (i == 0) {
strcat_safe(out, "if", outSize);
}
else {
strcat_safe(out, "elif", outSize);
}
strcat_safe(out, " value == '", outSize);
sprintf(tmp, "%i", e->options[i].value);
strcat_safe(out, tmp, outSize);
strcat_safe(out, "' %}\n", outSize);
strcat_safe(out, " ", outSize);
strcat_safe(out, e->options[i].label, outSize);
strcat_safe(out, "\n", outSize);
}
strcat_safe(out, "{% else %}\n", outSize);
strcat_safe(out, " Unknown\n", outSize);
strcat_safe(out, "{% endif %}", outSize);
#if WINDOWS
FILE *f = fopen("lastEnumTemplate.txt", "w");
fprintf(f, out);
fclose(f);
#endif
}
commandResult_t CMD_SetChannelEnum(const void *context, const char *cmd,
const char *args, int cmdFlags) {
int ch;
const char *s;
Tokenizer_TokenizeString(args, TOKENIZER_ALLOW_QUOTES);
// following check must be done after 'Tokenizer_TokenizeString',
// so we know arguments count in Tokenizer. 'cmd' argument is
// only for warning display
if (Tokenizer_CheckArgsCountAndPrintWarning(cmd, 2)) {
return CMD_RES_NOT_ENOUGH_ARGUMENTS;
}
ch = Tokenizer_GetArgInteger(0);
if (g_enums == 0) {
g_enums = malloc(sizeof(channelEnum_t*)*CHANNEL_MAX);
memset(g_enums,0, sizeof(channelEnum_t*)*CHANNEL_MAX);
}
channelEnum_t *en = malloc(sizeof(channelEnum_t));
g_enums[ch] = en;
en->numOptions = Tokenizer_GetArgsCount()-1;
en->options = malloc(sizeof(channelEnumOption_t)*en->numOptions);
for (int i = 0; i < en->numOptions; i++) {
s = Tokenizer_GetArg(1+i);
en->options[i].value = atoi(s);
while (*s) {
if (*s == ':') {
s++;
break;
}
s++;
}
en->options[i].label = strdup(s);
}
return CMD_RES_OK;
}

View File

@ -5,6 +5,7 @@
#include "../hal/hal_wifi.h"
#include "../driver/drv_public.h"
#include "../new_pins.h"
#include "../cmnds/cmd_enums.h"
#if ENABLE_HA_DISCOVERY
@ -134,6 +135,9 @@ void hass_populate_unique_id(ENTITY_TYPE type, int index, char* uniq_id, int ase
case HASS_BUTTON:
sprintf(uniq_id, "%s_%s", longDeviceName, "button");
break;
case HASS_SELECT:
sprintf(uniq_id, "%s_%s", longDeviceName, "select");
break;
default:
// TODO: USE type here as well?
// If type is not set, and we use "sensor" naming, we can easily make collision
@ -322,6 +326,19 @@ static void generate_command_template(int numoptions, const char* options[], cha
HassDeviceInfo* hass_createSelectEntityIndexed(const char* state_topic, const char* command_topic, int numoptions,
const char* options[], const char* title) {
char value_template[512];
generate_value_template(numoptions, options, value_template, sizeof(value_template));
char command_template[512];
generate_command_template(numoptions, options, command_template, sizeof(command_template));
return hass_createSelectEntityIndexedCustom(state_topic, command_topic, numoptions, options, title,
value_template, command_template);
}
HassDeviceInfo* hass_createSelectEntityIndexedCustom(const char* state_topic, const char* command_topic, int numoptions,
const char* options[], const char* title, char* value_template, char* command_template) {
HassDeviceInfo* info = hass_init_device_info(HASS_SELECT, 0, NULL, NULL, 0, title);
cJSON_AddStringToObject(info->root, "name", title);
@ -335,17 +352,14 @@ HassDeviceInfo* hass_createSelectEntityIndexed(const char* state_topic, const ch
}
cJSON_AddItemToObject(info->root, "options", select_options);
char value_template[512];
generate_value_template(numoptions, options, value_template, sizeof(value_template));
cJSON_AddStringToObject(info->root, "value_template", value_template);
char command_template[512];
generate_command_template(numoptions, options, command_template, sizeof(command_template));
cJSON_AddStringToObject(info->root, "command_template", command_template);
cJSON_AddStringToObject(info->root, "availability_topic", "~/status");
cJSON_AddStringToObject(info->root, "payload_available", "online");
cJSON_AddStringToObject(info->root, "payload_not_available", "offline");
if (!CFG_HasFlag(OBK_FLAG_NOT_PUBLISH_AVAILABILITY)) {
cJSON_AddStringToObject(info->root, "availability_topic", "~/connected");
cJSON_AddStringToObject(info->root, "payload_available", "online");
cJSON_AddStringToObject(info->root, "payload_not_available", "offline");
}
sprintf(info->channel, "select/%s/config", info->unique_id);
@ -572,7 +586,7 @@ HassDeviceInfo* hass_init_device_info(ENTITY_TYPE type, int index, const char* p
case HASS_BUTTON:
sprintf(g_hassBuffer, "%s" , "");
break;
case HASS_READONLYENUM:
default:
sprintf(g_hassBuffer, "%s", CHANNEL_GetLabel(index));
break;
@ -607,6 +621,12 @@ HassDeviceInfo* hass_init_device_info(ENTITY_TYPE type, int index, const char* p
}
}
if (type == HASS_READONLYENUM) {
char value_template[1024];
CMD_GenEnumValueTemplate(g_enums[index], value_template, sizeof(value_template));
cJSON_AddStringToObject(info->root, "value_template", value_template);
}
cJSON_AddStringToObject(info->root, "uniq_id", info->unique_id); //unique_id
cJSON_AddNumberToObject(info->root, "qos", 1);
@ -1012,6 +1032,11 @@ HassDeviceInfo* hass_init_sensor_device_info(ENTITY_TYPE type, int channel, int
sprintf(g_hassBuffer, "~/%d/get", channel);
cJSON_AddStringToObject(info->root, "stat_t", g_hassBuffer);
break;
case HASS_READONLYENUM:
sprintf(g_hassBuffer, "~/%d/get", channel);
cJSON_AddStringToObject(info->root, "stat_t", g_hassBuffer);
// str sensor can't have state_class, so return before it gets set
return info;
case CUSTOM_SENSOR:
sprintf(g_hassBuffer, "~/%d/get", channel);
cJSON_AddStringToObject(info->root, "stat_t", g_hassBuffer);

View File

@ -101,6 +101,8 @@ typedef enum {
HASS_PERCENT,
HASS_TEXTFIELD,
HASS_BUTTON,
// @Brief ChType_ReadOnlyEnum, readonly with value_template
HASS_READONLYENUM,
} ENTITY_TYPE;
typedef enum {
@ -145,6 +147,8 @@ HassDeviceInfo* hass_createSelectEntity(const char* state_topic, const char* com
const char* options[], const char* title);
HassDeviceInfo* hass_createSelectEntityIndexed(const char* state_topic, const char* command_topic, int numoptions,
const char* options[], const char* title);
HassDeviceInfo* hass_createSelectEntityIndexedCustom(const char* state_topic, const char* command_topic, int numoptions,
const char* options[], const char* title, char* value_template, char* command_template);
HassDeviceInfo* hass_createToggle(const char *label, const char *stateTopic, const char *commandTopic);
HassDeviceInfo* hass_init_textField_info(int index);

View File

@ -5,6 +5,7 @@
#include "../hal/hal_ota.h"
// Commands register, execution API and cmd tokenizer
#include "../cmnds/cmd_public.h"
#include "../cmnds/cmd_enums.h"
#include "../driver/drv_tuyaMCU.h"
#include "../driver/drv_public.h"
#include "../driver/drv_bl_shared.h"
@ -495,6 +496,56 @@ int http_fn_index(http_request_t* request) {
hprintf255(request, "Channel %s = %i", CHANNEL_GetLabel(i), iValue);
}
poststr(request, "</td></tr>");
} else if (channelType == ChType_Enum) {
iValue = CHANNEL_Get(i);
channelEnum_t *en;
// if setChannelEnum has not been defined, treat ChType_Enum as a textfield
if (g_enums == NULL || g_enums[i]->numOptions == 0 ) {
//en = g_enums[i];
poststr(request, "<tr><td>");
hprintf255(request, "<p>Change channel %s enum:</p><form action=\"index\">", CHANNEL_GetLabel(i));
hprintf255(request, "<input type=\"hidden\" name=\"setIndex\" value=\"%i\">", i);
hprintf255(request, "<input type=\"number\" name=\"set\" value=\"%i\" onblur=\"this.form.submit()\">", iValue);
hprintf255(request, "<input type=\"submit\" value=\"Set!\"/></form>");
hprintf255(request, "</form>");
poststr(request, "</td></tr>");
} else {
en = g_enums[i];
poststr(request, "<tr><td>");
hprintf255(request, "<form action=\"index\"><label for=\"select%i\">Channel %s Enum:</label>", i, CHANNEL_GetLabel(i));
hprintf255(request, "<input type=\"hidden\" name=\"setIndex\" value=\"%i\">", i);
hprintf255(request, "<select id=\"select%i\" name=\"set\" onchange=\"this.form.submit()\">", i);
bool found = false;
for (int o = 0; o < en->numOptions; o++) {
const char* selected;
if (en->options[o].value == iValue) {
selected = "selected";
found = true;
} else
selected = "";
hprintf255(request, "<option value=\"%i\" %s>%s [%i]</option>", en->options[o].value, selected, en->options[o].label,en->options[o].value);
}
if (!found) // create an item if no label is found
hprintf255(request, "<option value=\"%i\" selected>undefined enum [%i]</option>", iValue,iValue);
hprintf255(request, "</select></form>");
poststr(request, "</td></tr>");
}
}
else if (channelType == ChType_ReadOnlyEnum) {
iValue = CHANNEL_Get(i);
const char* oLabel;
if (g_enums == NULL || g_enums[i]->numOptions == 0)
oLabel = CHANNEL_GetLabel(i);
else
oLabel = CMD_FindChannelEnumLabel(g_enums[i], iValue);
poststr(request, "<tr><td>");
hprintf255(request, "Channel %s = %s [%i]", CHANNEL_GetLabel(i), oLabel, iValue);
poststr(request, "</td></tr>");
}
else if ((types = Channel_GetOptionsForChannelType(channelType, &numTypes)) != 0) {
const char *what;
@ -2236,6 +2287,54 @@ void doHomeAssistantDiscovery(const char* topic, http_request_t* request) {
dev_info = hass_init_textField_info(i);
}
break;
case ChType_ReadOnlyEnum:
{
dev_info = hass_init_sensor_device_info(HASS_READONLYENUM, i, -1, -1, -1);
}
break;
case ChType_Enum:
{
channelEnum_t *en;
if (g_enums != NULL && g_enums[i]->numOptions != 0) {
en = g_enums[i];
} else {
// revert to textfield if no enums are defined
dev_info = hass_init_textField_info(i);
break;
}
char **options=(char**)malloc(en->numOptions * sizeof(char *));
for (int o = 0; o < en->numOptions; o++) {
options[o] = en->options[o].label;
}
if (en->options != NULL && en->numOptions >0) {
// backlog setChannelType 1 Enum; setChannelEnum 0:red 2:blue 3:green; scheduleHADiscovery 1
char stateTopic[32];
char cmdTopic[32];
char title[64];
char value_tmp[1024];
char command_tmp[1024];
CMD_GenEnumValueTemplate(en, value_tmp, sizeof(value_tmp));
CMD_GenEnumCommandTemplate(en, command_tmp, sizeof(command_tmp));
strcpy(title, CHANNEL_GetLabel(i));
sprintf(stateTopic, "~/%i/get", i);
sprintf(cmdTopic, "~/%i/set", i);
dev_info = hass_createSelectEntityIndexedCustom(
stateTopic,
cmdTopic,
en->numOptions,
(const char**)options,
title,
value_tmp,
command_tmp
);
}
os_free(options);
}
break;
default:
{
int numOptions;

View File

@ -325,7 +325,7 @@ void http_html_start(http_request_t* request, const char* pagename) {
}
const char pageScriptPart1[] = "<script type='text/javascript'>var firstTime,lastTime,onlineFor,req=null,onlineForEl=null,getElement=e=>document.getElementById(e);function showState(){clearTimeout(firstTime),clearTimeout(lastTime),null!=req&&req.abort(),(e=getElement(\"state\"))&&((req=new XMLHttpRequest).onreadystatechange=()=>{4==req.readyState&&\"OK\"==req.statusText&&((\"INPUT\"!=document.activeElement.tagName||\"number\"!=document.activeElement.type&&\"color\"!=document.activeElement.type)&&(e.innerHTML=req.responseText),clearTimeout(firstTime),clearTimeout(lastTime),lastTime=setTimeout(showState,";
const char pageScriptPart1[] = "<script type='text/javascript'>var firstTime,lastTime,onlineFor,req=null,onlineForEl=null,getElement=e=>document.getElementById(e);function showState(){clearTimeout(firstTime),clearTimeout(lastTime),null!=req&&req.abort(),(e=getElement(\"state\"))&&((req=new XMLHttpRequest).onreadystatechange=()=>{4==req.readyState&&\"OK\"==req.statusText&&(\"SELECT\"!=document.activeElement.tagName&&(\"INPUT\"!=document.activeElement.tagName||\"number\"!=document.activeElement.type&&\"color\"!=document.activeElement.type)&&(e.innerHTML=req.responseText),clearTimeout(firstTime),clearTimeout(lastTime),lastTime=setTimeout(showState,";
const char pageScriptPart2[] = "))},req.open(\"GET\",\"index?state=1\",!0),req.send()),firstTime=setTimeout(showState,";
const char pageScriptPart3[] = ")}function fmtUpTime(e){var t,n,o=Math.floor(e/86400);return e%=86400,t=Math.floor(e/3600),e%=3600,n=Math.floor(e/60),e=e%60,0<o?o+` days, ${t} hours, ${n} minutes and ${e} seconds`:0<t?t+` hours, ${n} minutes and ${e} seconds`:0<n?n+` minutes and ${e} seconds`:`just ${e} seconds`}function updateOnlineFor(){onlineForEl.textContent=fmtUpTime(++onlineFor)}function onLoad(){(onlineForEl=getElement(\"onlineFor\"))&&(onlineFor=parseInt(onlineForEl.dataset.initial,10))&&setInterval(updateOnlineFor,1e3),showState()}function submitTemperature(e){var t=getElement(\"form132\");getElement(\"kelvin132\").value=Math.round(1e6/parseInt(e.value)),t.submit()}window.addEventListener(\"load\",onLoad),history.pushState(null,\"\",window.location.pathname.slice(1)),setTimeout(()=>{var e=getElement(\"changed\");e&&(e.innerHTML=\"\")},5e3);</script>";

View File

@ -21,8 +21,9 @@ function showState() {
if (req.readyState == 4 && req.statusText == "OK") {
if (
!(
document.activeElement.tagName == "INPUT" &&
(document.activeElement.type == "number" || document.activeElement.type == "color")
document.activeElement.tagName == "SELECT" &&
(document.activeElement.tagName == "INPUT" &&
(document.activeElement.type == "number" || document.activeElement.type == "color"))
)
) {
var stateEl = getElement("state");

View File

@ -2392,6 +2392,8 @@ const char* g_channelTypeNames[] = {
"Percent",
"StopUpDown",
"EnergyImport_kWh_div1000",
"Enum",
"ReadOnlyEnum",
"error",
"error",
};

View File

@ -1071,6 +1071,20 @@ typedef enum channelType_e {
//chandetail:"file":"new_pins.h",
//chandetail:"driver":""}
ChType_EnergyImport_kWh_div1000,
//chandetail:{"name":"Enum",
//chandetail:"title":"Enum",
//chandetail:"descr":"This channel type allows creating custom Enum types in combination with SetChannelEnum command. Ideal for defining TuyaMCU enum mappings.",
//chandetail:"enum":"ChType_Enum",
//chandetail:"file":"new_pins.h",
//chandetail:"driver":""}
ChType_Enum,
//chandetail:{"name":"ReadOnlyEnum",
//chandetail:"title":"ReadOnlyEnum",
//chandetail:"descr":"Read Only Enum Channel type for use in combination with SetChannelEnum command. Ideal for defining TuyaMCU enum mappings.",
//chandetail:"enum":"ChType_ReadOnlyEnum",
//chandetail:"file":"new_pins.h",
//chandetail:"driver":""}
ChType_ReadOnlyEnum,
//chandetail:{"name":"Max",
//chandetail:"title":"TODO",
//chandetail:"descr":"This is the current total number of available channel types.",

View File

@ -1,7 +1,7 @@
#ifdef WINDOWS
#include "selftest_local.h"
#include "../cmnds/cmd_newEnums.h"
#include "../cmnds/cmd_enums.h"
void Test_Enums() {
@ -12,8 +12,17 @@ void Test_Enums() {
SELFTEST_ASSERT(g_enums == 0);
CMD_ExecuteCommand("SetChannelEnum 4 1:Ok 0:Bad", 0);
CMD_ExecuteCommand("SetChannelType 4 Enum", 0);
CMD_ExecuteCommand("SetChannelEnum 4 1:Bad 0:Ok", 0);
CMD_ExecuteCommand("SetChannelEnum 4 1:Ok 0:Bad", 0); // check options overwrite
// null checks
SELFTEST_ASSERT(g_enums);
SELFTEST_ASSERT(!strcmp(CMD_FindChannelEnumLabel(g_enums[14],1), "1"));
CMD_GenEnumValueTemplate(g_enums[14], tmp, sizeof(tmp));
// actual discovery template content assertions are in selftst_hass_discovery_ext.c
SELFTEST_ASSERT(!strcmp(tmp,"{{ {99999:'Undefined'}[(value | int(99999))] | default(\"Undefined Enum [\"~value~\"]\") }}"));
for (int i = 0; i < CHANNEL_MAX; i++) {
if (i != 4) {
SELFTEST_ASSERT(!g_enums[i]);
@ -27,9 +36,10 @@ void Test_Enums() {
SELFTEST_ASSERT(g_enums[4]->options[1].value == 0);
SELFTEST_ASSERT(!strcmp(g_enums[4]->options[1].label, "Bad"));
CMD_FormatEnumTemplate(g_enums[4], tmp, sizeof(tmp));
CMD_FormatEnumTemplate(g_enums[4], tmp, sizeof(tmp),false);
CMD_FormatEnumTemplate(g_enums[4], tmp, sizeof(tmp),true);
CMD_ExecuteCommand("SetChannelEnum 14 1:One 0:Zero 3:Three", 0);
CMD_ExecuteCommand("SetChannelEnum 14 \"1:One Switch\" 0:Zero 3:Three", 0);
SELFTEST_ASSERT(g_enums);
SELFTEST_ASSERT(g_enums[14]);
SELFTEST_ASSERT(!g_enums[13]);
@ -37,11 +47,17 @@ void Test_Enums() {
SELFTEST_ASSERT(g_enums[14]->numOptions == 3);
SELFTEST_ASSERT(g_enums[14]->options[0].value == 1);
SELFTEST_ASSERT(!strcmp(g_enums[14]->options[0].label, "One"));
SELFTEST_ASSERT(!strcmp(g_enums[14]->options[0].label, "One Switch"));
SELFTEST_ASSERT(g_enums[14]->options[1].value == 0);
SELFTEST_ASSERT(!strcmp(g_enums[14]->options[1].label, "Zero"));
SELFTEST_ASSERT(g_enums[14]->options[2].value == 3);
SELFTEST_ASSERT(!strcmp(g_enums[14]->options[2].label, "Three"));
SELFTEST_ASSERT(!strcmp(CMD_FindChannelEnumLabel(g_enums[14],1), "One Switch"));
SELFTEST_ASSERT(!strcmp(CMD_FindChannelEnumLabel(g_enums[14],0), "Zero"));
SELFTEST_ASSERT(!strcmp(CMD_FindChannelEnumLabel(g_enums[14],3), "Three"));
}

View File

@ -681,6 +681,107 @@ void Test_HassDiscovery_Channel_Toggle_2x() {
SELFTEST_ASSERT_HAS_MQTT_JSON_SENT_ANY("homeassistant", true, 0, 0, "stat_t", "~/5/get");
SELFTEST_ASSERT_HAS_MQTT_JSON_SENT_ANY("homeassistant", true, 0, 0, "cmd_t", "~/5/set");
}
void Test_HassDiscovery_Enum() {
const char *shortName = "Enumtest";
const char *fullName = "Windows Fake Enum";
const char *mqttName = "testEnum";
SIM_ClearOBK(shortName);
SIM_ClearAndPrepareForMQTTTesting(mqttName, "bekens");
CFG_SetShortDeviceName(shortName);
CFG_SetDeviceName(fullName);
SIM_ClearMQTTHistory();
CMD_ExecuteCommand("SetChannelType 4 Enum", 0);
CMD_ExecuteCommand("SetChannelLabel 4 EnumFour", 0);
CMD_ExecuteCommand("SetChannelEnum 4 1:Ok 0:Bad", 0);
CMD_ExecuteCommand("SetChannelType 14 Enum", 0);
CMD_ExecuteCommand("SetChannelLabel 14 Enum14", 0);
CMD_ExecuteCommand("SetChannelEnum 14 \"1:One Switch\" 0:Zero 3:Three", 0);
CMD_ExecuteCommand("scheduleHADiscovery 1", 0);
Sim_RunSeconds(5, false);
// OBK device should publish JSON on MQTT topic "homeassistant/select"
SELFTEST_ASSERT_HAS_MQTT_JSON_SENT("homeassistant/select", true);
SELFTEST_ASSERT_JSON_VALUE_STRING("dev", "name", shortName);
SELFTEST_ASSERT_JSON_VALUE_STRING("dev", "sw", USER_SW_VER);
SELFTEST_ASSERT_JSON_VALUE_STRING("dev", "mf", MANUFACTURER);
SELFTEST_ASSERT_JSON_VALUE_STRING("dev", "mdl", PLATFORM_MCU_NAME);
SELFTEST_ASSERT_JSON_VALUE_STRING(0, "~", mqttName);
// channel 4
SELFTEST_ASSERT_HAS_MQTT_JSON_SENT_ANY_3KEY("homeassistant/select", true, 0, 0,
"unique_id", "EnumFour",
"state_topic", "~/4/get",
"command_topic", "~/4/set");
SELFTEST_ASSERT_JSON_VALUE_STRING_NESTED_ARRAY(0,"options",0,"Ok");
SELFTEST_ASSERT_JSON_VALUE_STRING_NESTED_ARRAY(0,"options",1,"Bad");
SELFTEST_ASSERT_HAS_MQTT_JSON_SENT_ANY("homeassistant/select", true, 0, 0, "command_template", "{{ {'Ok':'1', 'Bad':'0', 'Undefined':'99999'}[value] | default(99999) }}");
SELFTEST_ASSERT_HAS_MQTT_JSON_SENT_ANY("homeassistant/select", true, 0, 0, "value_template", "{{ {1:'Ok', 0:'Bad', 99999:'Undefined'}[(value | int(99999))] | default(\"Undefined Enum [\"~value~\"]\") }}");
// channel 14
SELFTEST_ASSERT_HAS_MQTT_JSON_SENT_ANY_3KEY("homeassistant/select", true, 0, 0,
"unique_id", "Enum14",
"state_topic", "~/14/get",
"command_topic", "~/14/set");
SELFTEST_ASSERT_HAS_MQTT_JSON_SENT_ANY("homeassistant/select", true, 0, 0, "value_template", "{{ {1:'One Switch', 0:'Zero', 3:'Three', 99999:'Undefined'}[(value | int(99999))] | default(\"Undefined Enum [\"~value~\"]\") }}");
SELFTEST_ASSERT_HAS_MQTT_JSON_SENT_ANY("homeassistant/select", true, 0, 0, "command_template", "{{ {'One Switch':'1', 'Zero':'0', 'Three':'3', 'Undefined':'99999'}[value] | default(99999) }}");
}
void Test_HassDiscovery_ReadOnlyEnum() {
const char *shortName = "ReadOnlyEnumtest";
const char *fullName = "Windows Fake ReadOnlyEnum";
const char *mqttName = "testReadOnlyEnum";
SIM_ClearOBK(shortName);
SIM_ClearAndPrepareForMQTTTesting(mqttName, "bekens");
CFG_SetShortDeviceName(shortName);
CFG_SetDeviceName(fullName);
SIM_ClearMQTTHistory();
CMD_ExecuteCommand("SetChannelType 4 ReadOnlyEnum", 0);
CMD_ExecuteCommand("SetChannelLabel 4 ReadOnlyEnumFour", 0);
CMD_ExecuteCommand("SetChannelEnum 4 1:Ok 0:Bad", 0);
//CHANNEL_SetType(14, ChType_ReadOnlyEnum);
CMD_ExecuteCommand("SetChannelType 14 ReadOnlyEnum", 0);
CMD_ExecuteCommand("SetChannelLabel 14 ReadOnlyEnum14", 0);
CMD_ExecuteCommand("SetChannelEnum 14 \"1:One Switch\" 0:Zero 3:Three", 0);
CMD_ExecuteCommand("scheduleHADiscovery 1", 0);
Sim_RunSeconds(5, false);
// MQTT Config should be sensor for ReadOnlyEnum
SELFTEST_ASSERT_HAS_MQTT_JSON_SENT("homeassistant/sensor", true);
SELFTEST_ASSERT_JSON_VALUE_STRING("dev", "name", shortName);
SELFTEST_ASSERT_JSON_VALUE_STRING("dev", "sw", USER_SW_VER);
SELFTEST_ASSERT_JSON_VALUE_STRING("dev", "mf", MANUFACTURER);
SELFTEST_ASSERT_JSON_VALUE_STRING("dev", "mdl", PLATFORM_MCU_NAME);
SELFTEST_ASSERT_JSON_VALUE_STRING(0, "~", mqttName);
// channel 4
SELFTEST_ASSERT_HAS_MQTT_JSON_SENT_ANY("homeassistant/sensor", true, 0, 0, "name", "ReadOnlyEnumFour");
SELFTEST_ASSERT_HAS_MQTT_JSON_SENT_ANY("homeassistant/sensor", true, 0, 0, "stat_t", "~/4/get");
SELFTEST_ASSERT_HAS_MQTT_JSON_SENT_ANY("homeassistant/sensor", true, 0, 0, "value_template", "{{ {1:'Ok', 0:'Bad', 99999:'Undefined'}[(value | int(99999))] | default(\"Undefined Enum [\"~value~\"]\") }}");
// state_class should not be defined
SELFTEST_ASSERT_HAS_NOT_MQTT_JSON_SENT_ANY("homeassistant/sensor/Windows_Fake_ReadOnlyEnum_sensor_4", true, 0, 0, "stat_cla", "temperature");
SELFTEST_ASSERT_HAS_NOT_MQTT_JSON_SENT_ANY("homeassistant/sensor/Windows_Fake_ReadOnlyEnum_sensor_4", true, 0, 0, "stat_cla", "measurement");
// channel 14
SELFTEST_ASSERT_HAS_MQTT_JSON_SENT_ANY("homeassistant/sensor", true, 0, 0, "name", "ReadOnlyEnum14");
SELFTEST_ASSERT_HAS_MQTT_JSON_SENT_ANY("homeassistant/sensor", true, 0, 0, "stat_t", "~/14/get");
SELFTEST_ASSERT_HAS_MQTT_JSON_SENT_ANY("homeassistant/sensor", true, 0, 0, "value_template", "{{ {1:'One Switch', 0:'Zero', 3:'Three', 99999:'Undefined'}[(value | int(99999))] | default(\"Undefined Enum [\"~value~\"]\") }}");
}
void Test_HassDiscovery_Ext() {
Test_HassDiscovery_TuyaMCU_VoltageCurrentPower();
Test_HassDiscovery_TuyaMCU_Power10();
@ -705,6 +806,8 @@ void Test_HassDiscovery_Ext() {
Test_HassDiscovery_Channel_Smoke();
Test_HassDiscovery_Channel_Ph();
Test_HassDiscovery_Enum();
Test_HassDiscovery_ReadOnlyEnum();
}

View File

@ -250,6 +250,19 @@ const char *Test_GetJSONValue_StrFromArray(int index, const char *key) {
return "";
return tmp->valuestring;
}
const char *Test_GetJSONValue_StrFromNestedArray(const char *par, const char *key, int index) {
cJSON *tmp;
cJSON *parent;
parent = Test_GetJSONValue_Generic(key, par);
if (parent == 0)
return "";
tmp = cJSON_GetArrayItem(parent, index);
if (tmp == 0)
return "";
printf("Test_GetJSONValue_StrFromNestedArray DEBUG will return %s for %s[%i]\n", tmp->valuestring, key, index);
return tmp->valuestring;
}
const char *Test_GetJSONValue_String(const char *keyword, const char *obj) {
cJSON *tmp;

View File

@ -38,6 +38,7 @@ void SelfTest_Failed(const char *file, const char *function, int line, const cha
#define SELFTEST_ASSERT_JSON_VALUE_STRING_NESTED2(par1, par2, varName, res) SELFTEST_ASSERT((!strcmp(Test_GetJSONValue_String_Nested2(par1, par2,varName),res)));
#define SELFTEST_ASSERT_HAS_MQTT_ARRAY_ITEM_INT(index, key, valInt) SELFTEST_ASSERT((Test_GetJSONValue_IntFromArray(index,key)==valInt));
#define SELFTEST_ASSERT_HAS_MQTT_ARRAY_ITEM_STR(index, key, valInt) SELFTEST_ASSERT((!strcmp(Test_GetJSONValue_StrFromArray(index,key),valInt)));
#define SELFTEST_ASSERT_JSON_VALUE_STRING_NESTED_ARRAY(par, varName, index, res) SELFTEST_ASSERT((!strcmp(Test_GetJSONValue_StrFromNestedArray(par, varName, index),res)));
#define SELFTEST_ASSERT_HTTP_HAS_LED_DIMMER(bHas) SELFTEST_ASSERT((bHas) == SIM_HasHTTPDimmer());
#define SELFTEST_ASSERT_HTTP_HAS_LED_TEMPERATURE(bHas) SELFTEST_ASSERT((bHas) == SIM_HasHTTPTemperature());
#define SELFTEST_ASSERT_HTTP_HAS_LED_RGB(bHas) SELFTEST_ASSERT((bHas) == SIM_HasHTTPRGB());
@ -62,6 +63,7 @@ void SelfTest_Failed(const char *file, const char *function, int line, const cha
#define SELFTEST_ASSERT_FLAG(flag, value) SELFTEST_ASSERT(CFG_HasFlag(flag)==value);
#define SELFTEST_ASSERT_HAS_MQTT_JSON_SENT(topic, bPrefixMode) SELFTEST_ASSERT(!SIM_BeginParsingMQTTJSON(topic, bPrefixMode));
#define SELFTEST_ASSERT_HAS_MQTT_JSON_SENT_ANY(topic, bPrefixMode, object1, object2, key, value) SELFTEST_ASSERT(SIM_HasMQTTHistoryStringWithJSONPayload(topic, bPrefixMode, object1, object2, key, value, 0, 0, 0, 0, 0, 0));
#define SELFTEST_ASSERT_HAS_NOT_MQTT_JSON_SENT_ANY(topic, bPrefixMode, object1, object2, key, value) SELFTEST_ASSERT(!SIM_HasMQTTHistoryStringWithJSONPayload(topic, bPrefixMode, object1, object2, key, value, 0, 0, 0, 0, 0, 0));
#define SELFTEST_ASSERT_HAS_MQTT_JSON_SENT_ANY_TWOKEY(topic, bPrefixMode, object1, object2, key, value, key2, value2) SELFTEST_ASSERT(SIM_HasMQTTHistoryStringWithJSONPayload(topic, bPrefixMode, object1, object2, key, value, key2, value2, 0, 0, 0, 0));
#define SELFTEST_ASSERT_HAS_MQTT_JSON_SENT_ANY_3KEY(topic, bPrefixMode, object1, object2, key, value, key2, value2, key3, value3) SELFTEST_ASSERT(SIM_HasMQTTHistoryStringWithJSONPayload(topic, bPrefixMode, object1, object2, key, value, key2, value2, key3, value3, 0, 0));
#define SELFTEST_ASSERT_HAS_MQTT_JSON_SENT_ANY_4KEY(topic, bPrefixMode, object1, object2, key, value, key2, value2, key3, value3, key4, value4) SELFTEST_ASSERT(SIM_HasMQTTHistoryStringWithJSONPayload(topic, bPrefixMode, object1, object2, key, value, key2, value2, key3, value3, key4, value4));
@ -177,6 +179,7 @@ const char *Test_GetJSONValue_String_Nested(const char *par1, const char *keywor
const char *Test_GetJSONValue_String_Nested2(const char *par1, const char *par2, const char *keyword);
int Test_GetJSONValue_IntFromArray(int index, const char *obj);
const char *Test_GetJSONValue_StrFromArray(int index, const char *obj);
const char *Test_GetJSONValue_StrFromNestedArray(const char *par, const char *key, int index);
void SIM_SendFakeMQTT(const char *text, const char *arguments);
void SIM_SendFakeMQTTAndRunSimFrame_CMND(const char *command, const char *arguments);

View File

@ -193,7 +193,7 @@ void Win_DoUnitTests() {
Test_Demo_ConditionalRelay();
Test_Expressions_RunTests_Braces();
Test_Expressions_RunTests_Basic();
//Test_Enums();
Test_Enums();
Test_Backlog();
Test_DoorSensor();
Test_LEDstrips();