diff --git a/src/driver/drv_tuyaMCU.c b/src/driver/drv_tuyaMCU.c index 88e1a8679..dbca695cc 100644 --- a/src/driver/drv_tuyaMCU.c +++ b/src/driver/drv_tuyaMCU.c @@ -101,6 +101,10 @@ typedef struct tuyaMCUMapping_s { int fnId; // target channel int channel; + // data point type (one of the DP_TYPE_xxx defines) + int dpType; + // store last channel value to avoid sending it again + int prevValue; // TODO //int mode; // list @@ -109,6 +113,17 @@ typedef struct tuyaMCUMapping_s { tuyaMCUMapping_t *g_tuyaMappings = 0; +/** + * Dimmer range + * + * Map OpenBK7231T_App's dimmer range of 0..100 to the dimmer range used by TuyMCU. + * Use tuyaMCU_setDimmerrange command to set range used by TuyaMCU. + */ +// minimum dimmer value as reported by TuyaMCU dimmer +int g_dimmerRangeMin = 0; +// maximum dimmer value as reported by TuyaMCU dimmer +int g_dimmerRangeMax = 100; + tuyaMCUMapping_t *TuyaMCU_FindDefForID(int fnId) { tuyaMCUMapping_t *cur; @@ -120,7 +135,20 @@ tuyaMCUMapping_t *TuyaMCU_FindDefForID(int fnId) { } return 0; } -void TuyaMCU_MapIDToChannel(int fnId, int channel) { + +tuyaMCUMapping_t *TuyaMCU_FindDefForChannel(int channel) { + tuyaMCUMapping_t *cur; + + cur = g_tuyaMappings; + while(cur) { + if(cur->channel == channel) + return cur; + cur = cur->next; + } + return 0; +} + +void TuyaMCU_MapIDToChannel(int fnId, int dpType, int channel) { tuyaMCUMapping_t *cur; cur = TuyaMCU_FindDefForID(fnId); @@ -128,6 +156,8 @@ void TuyaMCU_MapIDToChannel(int fnId, int channel) { if(cur == 0) { cur = (tuyaMCUMapping_t*)malloc(sizeof(tuyaMCUMapping_t)); cur->fnId = fnId; + cur->dpType = dpType; + cur->prevValue = 0; cur->next = g_tuyaMappings; g_tuyaMappings = cur; } @@ -215,6 +245,124 @@ void TuyaMCU_SendCommandWithData(byte cmdType, byte *data, int payload_len) { UART_SendByte(check_sum); } +void TuyaMCU_SendState(uint8_t id, uint8_t type, uint8_t* value) +{ + uint16_t payload_len = 4; + uint8_t payload_buffer[8]; + payload_buffer[0] = id; + payload_buffer[1] = type; + switch (type) { + case DP_TYPE_BOOL: + case DP_TYPE_ENUM: + payload_len += 1; + payload_buffer[2] = 0x00; + payload_buffer[3] = 0x01; + payload_buffer[4] = value[0]; + break; + case DP_TYPE_VALUE: + payload_len += 4; + payload_buffer[2] = 0x00; + payload_buffer[3] = 0x04; + payload_buffer[4] = value[3]; + payload_buffer[5] = value[2]; + payload_buffer[6] = value[1]; + payload_buffer[7] = value[0]; + break; + + } + + TuyaMCU_SendCommandWithData(TUYA_CMD_SET_DP, payload_buffer, payload_len); +} + +void TuyaMCU_SendBool(uint8_t id, bool value) +{ + TuyaMCU_SendState(id, DP_TYPE_BOOL, (uint8_t*)&value); +} + +void TuyaMCU_SendValue(uint8_t id, uint32_t value) +{ + TuyaMCU_SendState(id, DP_TYPE_VALUE, (uint8_t*)(&value)); +} + +void TuyaMCU_SendEnum(uint8_t id, uint32_t value) +{ + TuyaMCU_SendState(id, DP_TYPE_ENUM, (uint8_t*)(&value)); +} + +static uint16_t convertHexStringtoBytes (uint8_t * dest, char src[], uint16_t src_len){ + if (NULL == dest || NULL == src || 0 == src_len){ + return 0; + } + + char hexbyte[3]; + hexbyte[2] = 0; + uint16_t i; + + for (i = 0; i < src_len; i++) { + hexbyte[0] = src[2*i]; + hexbyte[1] = src[2*i+1]; + dest[i] = strtol(hexbyte, NULL, 16); + } + + return i; +} + +void TuyaMCU_SendHexString(uint8_t id, char data[]) { + + uint16_t len = strlen(data)/2; + uint16_t payload_len = 4 + len; + uint8_t payload_buffer[payload_len]; + payload_buffer[0] = id; + payload_buffer[1] = DP_TYPE_STRING; + payload_buffer[2] = len >> 8; + payload_buffer[3] = len & 0xFF; + + (void) convertHexStringtoBytes(&payload_buffer[4], data, len); + + TuyaMCU_SendCommandWithData(TUYA_CMD_SET_DP, payload_buffer, payload_len); +} + +void TuyaMCU_SendString(uint8_t id, char data[]) { + + uint16_t len = strlen(data); + uint16_t payload_len = 4 + len; + uint8_t payload_buffer[payload_len]; + payload_buffer[0] = id; + payload_buffer[1] = DP_TYPE_STRING; + payload_buffer[2] = len >> 8; + payload_buffer[3] = len & 0xFF; + + for (uint16_t i = 0; i < len; i++) { + payload_buffer[4+i] = data[i]; + } + + TuyaMCU_SendCommandWithData(TUYA_CMD_SET_DP, payload_buffer, payload_len); +} + +void TuyaMCU_SendRaw(uint8_t id, char data[]) { + char* beginPos = strchr(data, 'x'); + if(!beginPos) { + beginPos = strchr(data, 'X'); + } + if(!beginPos) { + beginPos = data; + } else { + beginPos += 1; + } + uint16_t strSize = strlen(beginPos); + uint16_t len = strSize/2; + uint16_t payload_len = 4 + len; + uint8_t payload_buffer[payload_len]; + payload_buffer[0] = id; + payload_buffer[1] = DP_TYPE_RAW; + payload_buffer[2] = len >> 8; + payload_buffer[3] = len & 0xFF; + + (void) convertHexStringtoBytes(&payload_buffer[4], beginPos, len); + + TuyaMCU_SendCommandWithData(TUYA_CMD_SET_DP, payload_buffer, payload_len); +} + void TuyaMCU_Send_SetTime(struct tm *pTime) { byte payload_buffer[8]; byte tuya_day_of_week; @@ -266,20 +414,22 @@ int TuyaMCU_Send_Hex(const void *context, const char *cmd, const char *args) { } int TuyaMCU_LinkTuyaMCUOutputToChannel(const void *context, const char *cmd, const char *args) { - int dpID; + int dpId; + int dpType; int channelID; - // linkTuyaMCUOutputToChannel dpID channelID [varType] + // linkTuyaMCUOutputToChannel dpId channelID [varType] Tokenizer_TokenizeString(args); - if(Tokenizer_GetArgsCount() < 2) { - addLogAdv(LOG_INFO, LOG_FEATURE_TUYAMCU,"TuyaMCU_LinkTuyaMCUOutputToChannel: requires 2 arguments (dpID, channelIndex)\n"); + if(Tokenizer_GetArgsCount() < 3) { + addLogAdv(LOG_INFO, LOG_FEATURE_TUYAMCU,"TuyaMCU_LinkTuyaMCUOutputToChannel: requires 3 arguments (dpId, dpType, channelIndex)\n"); return -1; } - dpID = Tokenizer_GetArgInteger(0); - channelID = Tokenizer_GetArgInteger(1); + dpId = Tokenizer_GetArgInteger(0); + dpType = Tokenizer_GetArgInteger(1); + channelID = Tokenizer_GetArgInteger(2); - TuyaMCU_MapIDToChannel(dpID,channelID); + TuyaMCU_MapIDToChannel(dpId, dpType, channelID); return 1; } @@ -319,6 +469,60 @@ void TuyaMCU_Send(byte *data, int size) { addLogAdv(LOG_INFO, LOG_FEATURE_TUYAMCU,"\nWe sent %i bytes to Tuya MCU\n",size+1); } + +int TuyaMCU_SetDimmerRange(const void *context, const char *cmd, const char *args) { + Tokenizer_TokenizeString(args); + + if(Tokenizer_GetArgsCount() < 2) { + addLogAdv(LOG_INFO, LOG_FEATURE_TUYAMCU,"tuyaMcu_setDimmerRange: requires 2 arguments (dimmerRangeMin, dimmerRangeMax)\n"); + return -1; + } + + g_dimmerRangeMin = Tokenizer_GetArgInteger(0); + g_dimmerRangeMax = Tokenizer_GetArgInteger(1); + + return 1; +} + +int TuyaMCU_SendHeartbeat(const void *context, const char *cmd, const char *args) { + TuyaMCU_SendCommandWithData(TUYA_CMD_HEARTBEAT, NULL, 0); + + return 1; +} + +int TuyaMCU_SendQueryState(const void *context, const char *cmd, const char *args) { + TuyaMCU_SendCommandWithData(TUYA_CMD_QUERY_STATE, NULL, 0); + + return 1; +} + +int TuyaMCU_SendStateCmd(const void *context, const char *cmd, const char *args) { + int dpId; + int dpType; + int value; + + Tokenizer_TokenizeString(args); + + if(Tokenizer_GetArgsCount() < 3) { + addLogAdv(LOG_INFO, LOG_FEATURE_TUYAMCU,"tuyaMcu_sendState: requires 3 arguments (dpId, dpType, value)\n"); + return -1; + } + + dpId = Tokenizer_GetArgInteger(0); + dpType = Tokenizer_GetArgInteger(1); + value = Tokenizer_GetArgInteger(2); + + TuyaMCU_SendState(dpId, dpType, (uint8_t *)&value); + + return 1; +} + +int TuyaMCU_SendMCUConf(const void *context, const char *cmd, const char *args) { + TuyaMCU_SendCommandWithData(TUYA_CMD_MCU_CONF, NULL, 0); + + return 1; +} + void TuyaMCU_Init() { UART_InitUART(9600); @@ -329,7 +533,11 @@ void TuyaMCU_Init() CMD_RegisterCommand("uartSendHex","",TuyaMCU_Send_Hex, "Sends raw data by TuyaMCU UART, you must write whole packet with checksum yourself", NULL); ///CMD_RegisterCommand("tuyaMcu_sendSimple","",TuyaMCU_Send_Simple, "Appends a 0x55 0xAA header to a data, append a checksum at end and send"); CMD_RegisterCommand("linkTuyaMCUOutputToChannel","",TuyaMCU_LinkTuyaMCUOutputToChannel, "Map value send from TuyaMCU (eg. humidity or temperature) to channel", NULL); - + CMD_RegisterCommand("tuyaMcu_setDimmerRange","",TuyaMCU_SetDimmerRange, "Set dimmer range used by TuyaMCU", NULL); + CMD_RegisterCommand("tuyaMcu_sendHeartbeat","",TuyaMCU_SendHeartbeat, "Send heartbeat to TuyaMCU", NULL); + CMD_RegisterCommand("tuyaMcu_sendQueryState","",TuyaMCU_SendQueryState, "Send query state command", NULL); + CMD_RegisterCommand("tuyaMcu_sendState","",TuyaMCU_SendStateCmd, "Send set state command", NULL); + CMD_RegisterCommand("tuyaMcu_sendMCUConf","",TuyaMCU_SendMCUConf, "Send MCU conf command", NULL); } // ntp_timeZoneOfs 2 // addRepeatingEvent 10 uartSendHex 55AA0008000007 @@ -344,6 +552,7 @@ void TuyaMCU_Init() // void TuyaMCU_ApplyMapping(int fnID, int value) { tuyaMCUMapping_t *mapping; + int mappedValue = value; // find mapping (where to save received data) mapping = TuyaMCU_FindDefForID(fnID); @@ -351,8 +560,80 @@ void TuyaMCU_ApplyMapping(int fnID, int value) { if(mapping == 0){ return; } - CHANNEL_Set(mapping->channel,value,0); + + // map value depending on channel type + switch(CHANNEL_GetType(mapping->channel)) + { + case ChType_Dimmer: + // map TuyaMCU's dimmer range to OpenBK7231T_App's dimmer range 0..100 + mappedValue = ((value - g_dimmerRangeMin) * 100) / (g_dimmerRangeMax - g_dimmerRangeMin); + break; + default: + break; + } + + if (value != mappedValue) { + addLogAdv(LOG_DEBUG, LOG_FEATURE_TUYAMCU,"TuyaMCU_ApplyMapping: mapped value %d (TuyaMCU range) to %d (OpenBK7321T_App range)\n", value, mappedValue); + } + + mapping->prevValue = mappedValue; + + CHANNEL_Set(mapping->channel,mappedValue,false); } + +void TuyaMCU_OnChannelChanged(int channel, int iVal) { + tuyaMCUMapping_t *mapping; + int mappediVal = iVal; + + // find mapping + mapping = TuyaMCU_FindDefForChannel(channel); + + if(mapping == 0){ + return; + } + + // this might be a callback from CHANNEL_Set in TuyaMCU_ApplyMapping. If we should set exactly the + // same value, skip it + if (mapping->prevValue == iVal) { + return; + } + + // map value depending on channel type + switch(CHANNEL_GetType(mapping->channel)) + { + case ChType_Dimmer: + // map OpenBK7231T_App's dimmer range 0..100 to TuyaMCU's dimmer range + mappediVal = (((g_dimmerRangeMax - g_dimmerRangeMin) * iVal) / 100) + g_dimmerRangeMin; + break; + default: + break; + } + + if (iVal != mappediVal) { + addLogAdv(LOG_DEBUG, LOG_FEATURE_TUYAMCU,"TuyaMCU_OnChannelChanged: mapped value %d (OpenBK7321T_App range) to %d (TuyaMCU range)\n", iVal, mappediVal); + } + + // send value to TuyaMCU + switch(mapping->dpType) + { + case DP_TYPE_BOOL: + TuyaMCU_SendBool(mapping->fnId, mappediVal != 0); + break; + + case DP_TYPE_ENUM: + TuyaMCU_SendEnum(mapping->fnId, mappediVal); + break; + + case DP_TYPE_VALUE: + TuyaMCU_SendValue(mapping->fnId, mappediVal); + break; + + default: + addLogAdv(LOG_INFO, LOG_FEATURE_TUYAMCU,"TuyaMCU_OnChannelChanged: channel %d: unsupported data point type %d-%s\n", channel, mapping->dpType, TuyaMCU_GetDataTypeString(mapping->dpType)); + break; + } +} + void TuyaMCU_ParseStateMessage(const byte *data, int len) { int ofs; int sectorLen; @@ -365,7 +646,7 @@ void TuyaMCU_ParseStateMessage(const byte *data, int len) { sectorLen = data[ofs + 2] << 8 | data[ofs + 3]; fnId = data[ofs]; dataType = data[ofs+1]; - addLogAdv(LOG_INFO, LOG_FEATURE_TUYAMCU,"TuyaMCU_ParseStateMessage: processing command %i, dataType %i-%s and %i data bytes\n", + addLogAdv(LOG_INFO, LOG_FEATURE_TUYAMCU,"TuyaMCU_ParseStateMessage: processing dpId %i, dataType %i-%s and %i data bytes\n", fnId, dataType, TuyaMCU_GetDataTypeString(dataType),sectorLen); diff --git a/src/driver/drv_tuyaMCU.h b/src/driver/drv_tuyaMCU.h index eae68a952..eecd7f2e2 100644 --- a/src/driver/drv_tuyaMCU.h +++ b/src/driver/drv_tuyaMCU.h @@ -2,4 +2,5 @@ void TuyaMCU_Init(); void TuyaMCU_RunFrame(); -void TuyaMCU_Send(byte *data, int size); \ No newline at end of file +void TuyaMCU_Send(byte *data, int size); +void TuyaMCU_OnChannelChanged(int channel,int iVal); diff --git a/src/httpserver/http_fns.c b/src/httpserver/http_fns.c index 351d5eff9..db35eb2da 100644 --- a/src/httpserver/http_fns.c +++ b/src/httpserver/http_fns.c @@ -136,6 +136,13 @@ int http_fn_index(http_request_t *request) { hprintf128(request,"