diff --git a/openBeken_win32_mvsc2017.vcxproj b/openBeken_win32_mvsc2017.vcxproj index 14eaaa8fb..921d86b57 100644 --- a/openBeken_win32_mvsc2017.vcxproj +++ b/openBeken_win32_mvsc2017.vcxproj @@ -240,6 +240,7 @@ + diff --git a/openBeken_win32_mvsc2017.vcxproj.filters b/openBeken_win32_mvsc2017.vcxproj.filters index 45d068fd3..5fcf16424 100644 --- a/openBeken_win32_mvsc2017.vcxproj.filters +++ b/openBeken_win32_mvsc2017.vcxproj.filters @@ -401,6 +401,7 @@ + diff --git a/src/cmnds/cmd_repeatingEvents.c b/src/cmnds/cmd_repeatingEvents.c index c725e5254..851243dab 100644 --- a/src/cmnds/cmd_repeatingEvents.c +++ b/src/cmnds/cmd_repeatingEvents.c @@ -178,7 +178,8 @@ commandResult_t RepeatingEvents_Cmd_AddRepeatingEvent(const void *context, const } interval = Tokenizer_GetArgFloat(0); times = Tokenizer_GetArgInteger(1); - if(!stricmp(cmd,"addRepeatingEventID")) { + bool bUID = !stricmp(cmd, "addRepeatingEventUID"); + if(!stricmp(cmd,"addRepeatingEventID") || bUID) { userID = Tokenizer_GetArgInteger(2); cmdToRepeat = Tokenizer_GetArgFrom(3); } else { @@ -189,6 +190,9 @@ commandResult_t RepeatingEvents_Cmd_AddRepeatingEvent(const void *context, const interval = MIN_REPEATING_INTERVAL; addLogAdv(LOG_INFO, LOG_FEATURE_CMD, "Interval was too small!"); } + if (bUID) { + RepeatingEvents_CancelRepeatingEvents(userID); + } addLogAdv(LOG_INFO, LOG_FEATURE_CMD,"addRepeatingEvent: interval %f, repeats %i, command [%s]",interval,times,cmdToRepeat); @@ -260,7 +264,12 @@ void RepeatingEvents_Init() { //cmddetail:"descr":"as addRepeatingEvent, but with a given ID. You can later cancel it with cancelRepeatingEvent.", //cmddetail:"fn":"RepeatingEvents_Cmd_AddRepeatingEvent","file":"cmnds/cmd_repeatingEvents.c","requires":"", //cmddetail:"examples":"addRepeatingEventID 2 -1 123 Power0 Toggle"} - CMD_RegisterCommand("addRepeatingEventID",RepeatingEvents_Cmd_AddRepeatingEvent, NULL); + CMD_RegisterCommand("addRepeatingEventID",RepeatingEvents_Cmd_AddRepeatingEvent, NULL); + //cmddetail:{"name":"addRepeatingEventUID","args":"[IntervalSeconds][RepeatsOr-1][UserID][CommandToRun]", + //cmddetail:"descr":"as addRepeatingEventID, but also automatically cancels previous events with same ID.", + //cmddetail:"fn":"RepeatingEvents_Cmd_AddRepeatingEvent","file":"cmnds/cmd_repeatingEvents.c","requires":"", + //cmddetail:"examples":"addRepeatingEventUID 2 -1 123 Power0 Toggle"} + CMD_RegisterCommand("addRepeatingEventUID", RepeatingEvents_Cmd_AddRepeatingEvent, NULL); //cmddetail:{"name":"cancelRepeatingEvent","args":"[UserIDInteger]", //cmddetail:"descr":"Stops a given repeating event with a specified ID", //cmddetail:"fn":"RepeatingEvents_Cmd_CancelRepeatingEvent","file":"cmnds/cmd_repeatingEvents.c","requires":"", diff --git a/src/driver/drv_local.h b/src/driver/drv_local.h index 0e635a0bb..773740875 100644 --- a/src/driver/drv_local.h +++ b/src/driver/drv_local.h @@ -55,6 +55,11 @@ void SM15155E_Init(); void DRV_GosundSW2_Init(); void DRV_GosundSW2_RunFrame(); + +void DRV_PinMutex_Init(); +void DRV_PinMutex_RunFrame(); + + void SM16703P_Init(); void SM16703P_Shutdown(); // set RGBCW values - Cold and Warm White are optional and might be ignored if hardware does not support them, or if diff --git a/src/driver/drv_main.c b/src/driver/drv_main.c index ef7ca63d3..ba7c8b724 100644 --- a/src/driver/drv_main.c +++ b/src/driver/drv_main.c @@ -91,6 +91,14 @@ static driver_t g_drivers[] = { +#if ENABLE_DRIVER_PINMUTEX + //drvdetail:{"name":"PinMutex", + //drvdetail:"title":"TODO", + //drvdetail:"descr":"PinMutex.", + //drvdetail:"requires":""} + { "PinMutex", DRV_PinMutex_Init, NULL, NULL, DRV_PinMutex_RunFrame, NULL, NULL, false }, +#endif + #if ENABLE_DRIVER_GOSUNDSW2 //drvdetail:{"name":"GosundSW", //drvdetail:"title":"TODO", diff --git a/src/driver/drv_pinMutex.c b/src/driver/drv_pinMutex.c new file mode 100644 index 000000000..bdc95ee01 --- /dev/null +++ b/src/driver/drv_pinMutex.c @@ -0,0 +1,146 @@ + + +#include "../obk_config.h" + +#if ENABLE_DRIVER_PINMUTEX + +#include +#include "drv_local.h" +#include "../logging/logging.h" +#include "../new_cfg.h" +#include "../new_pins.h" +#include "../quicktick.h" +#include "../cmnds/cmd_public.h" + +/* +startDriver PinMutex +// setMutex MutexIndex ChannelIndex PinUp PinDown DelayMs +setMutex 0 1 10 11 50 +// now, if you set channel 1 to 0, both pins are low. +// If you set to 1, Up goes 1, if you set to 2, down goes 1 +// but there is guaranted 50ms dead time +*/ + +// desired pin state: off, up, or down +typedef enum { + PM_DESIRED_OFF = 0, + PM_DESIRED_DOWN = 1, + PM_DESIRED_UP = 2, +} pmDesired_t; + +// pinMutex structure +typedef struct pinMutex_s { + int channel; // which CHANNEL_Get index to watch + int pinUp; // gpio pin number for "up" + int pinDown; // gpio pin number for "down" + int deadTimeMs; // minimum off-time when switching + pmDesired_t lastDesired; // previous desired state + int timerMs; // remaining dead-time countdown +} pinMutex_t; + +#define MAX_PINMUTEX 4 +static pinMutex_t pms[MAX_PINMUTEX]; + +// this must be called from the main loop at ~1 kHz (once per millisecond) +void DRV_PinMutex_RunFrame() { + for (int i = 0; i < MAX_PINMUTEX; i++) { + pinMutex_t *pm = &pms[i]; + + // skip unused slots (both pins zero) + if (pm->pinUp == 0 && pm->pinDown == 0) continue; + + // read desired state: 0=off, 1=up, 2=down + int desired = CHANNEL_Get(pm->channel); + + // state changed? start dead-time + if ((pmDesired_t)desired != pm->lastDesired) { + // immediately turn both pins off + HAL_PIN_SetOutputValue(pm->pinUp, 0); + HAL_PIN_SetOutputValue(pm->pinDown, 0); + // start delay before new direction + if (pm->lastDesired != 0) { + pm->timerMs = pm->deadTimeMs; + } + else { + pm->timerMs = 0; + } + pm->lastDesired = (pmDesired_t)desired; + } + else if (pm->timerMs > 0) { + // still in dead-time: count down + pm->timerMs -= g_deltaTimeMS; + // keep both outputs off + } + else { + // dead-time expired → apply new state + switch (pm->lastDesired) { + case PM_DESIRED_OFF: + HAL_PIN_SetOutputValue(pm->pinUp, 0); + HAL_PIN_SetOutputValue(pm->pinDown, 0); + break; + case PM_DESIRED_UP: + HAL_PIN_SetOutputValue(pm->pinUp, 1); + HAL_PIN_SetOutputValue(pm->pinDown, 0); + break; + case PM_DESIRED_DOWN: + HAL_PIN_SetOutputValue(pm->pinUp, 0); + HAL_PIN_SetOutputValue(pm->pinDown, 1); + break; + } + } + } +} + +// setMutex +static commandResult_t CMD_setMutex(const void *context, const char *cmd, const char *args, int cmdFlags) { + Tokenizer_TokenizeString(args, 0); + + // expect 5 arguments + if (Tokenizer_CheckArgsCountAndPrintWarning(cmd, 5) || Tokenizer_GetArgsCount() < 5) { + return CMD_RES_NOT_ENOUGH_ARGUMENTS; + } + + int idx = Tokenizer_GetArgInteger(0); + int channel = Tokenizer_GetArgInteger(1); + int delayMs = Tokenizer_GetArgInteger(2); + int pinDown = Tokenizer_GetArgInteger(3); + int pinUp = Tokenizer_GetArgInteger(4); + + if (idx < 0 || idx >= MAX_PINMUTEX) { + addLogAdv(LOG_ERROR, LOG_FEATURE_GENERAL, "setMutex: index %d out of range (0..%d)", idx, MAX_PINMUTEX - 1); + return CMD_RES_BAD_ARGUMENT; + } + if (delayMs < 0) { + addLogAdv(LOG_ERROR, LOG_FEATURE_GENERAL, "setMutex: delay must be >= 0"); + return CMD_RES_BAD_ARGUMENT; + } + + // initialize slot + pms[idx].channel = channel; + pms[idx].pinUp = pinUp; + pms[idx].pinDown = pinDown; + pms[idx].deadTimeMs = delayMs; + pms[idx].lastDesired = PM_DESIRED_OFF; + pms[idx].timerMs = 0; + + // configure gpio as outputs, start low + HAL_PIN_Setup_Output(pinUp); + HAL_PIN_Setup_Output(pinDown); + HAL_PIN_SetOutputValue(pinUp, 0); + HAL_PIN_SetOutputValue(pinDown, 0); + + addLogAdv(LOG_ERROR, LOG_FEATURE_GENERAL, "PinMutex[%d] = ch=%d, up=%d, down=%d, t=%dms", + idx, channel, pinUp, pinDown, delayMs); + return CMD_RES_OK; +} + +void DRV_PinMutex_Init() { + // mark all slots unused (both pins zero) + for (int i = 0; i < MAX_PINMUTEX; i++) { + pms[i].pinUp = 0; + pms[i].pinDown = 0; + } + CMD_RegisterCommand("setMutex", CMD_setMutex, NULL); +} + +#endif // ENABLE_DRIVER_PINMUTEX diff --git a/src/httpserver/http_fns.c b/src/httpserver/http_fns.c index 47d96b8e4..d9c868fd9 100644 --- a/src/httpserver/http_fns.c +++ b/src/httpserver/http_fns.c @@ -80,6 +80,7 @@ const char* g_typesOffOnRemember[] = { "Off", "On", "Remember" }; const char* g_typeLowMidHigh[] = { "Low","Mid","High" }; const char* g_typesLowestLowMidHighHighest[] = { "Lowest", "Low", "Mid", "High", "Highest" };; const char* g_typeOpenStopClose[] = { "Open","Stop","Close" }; +const char* g_typeStopUpDown[] = { "Stop","Up","Down" }; #define ADD_OPTION(t,a) if(type == t) { *numTypes = sizeof(a)/sizeof(a[0]); return a; } @@ -92,6 +93,7 @@ const char **Channel_GetOptionsForChannelType(int type, int *numTypes) { ADD_OPTION(ChType_OffOnRemember, g_typesOffOnRemember); ADD_OPTION(ChType_LowMidHigh, g_typeLowMidHigh); ADD_OPTION(ChType_OpenStopClose, g_typeOpenStopClose); + ADD_OPTION(ChType_StopUpDown, g_typeStopUpDown); *numTypes = 0; return 0; @@ -498,7 +500,7 @@ int http_fn_index(http_request_t* request) { if (channelType == ChType_OffOnRemember) { what = "memory"; } - else if (channelType == ChType_OpenStopClose) { + else if (channelType == ChType_OpenStopClose || channelType == ChType_StopUpDown) { what = "mode"; } else { diff --git a/src/new_pins.c b/src/new_pins.c index eeceba576..306055595 100644 --- a/src/new_pins.c +++ b/src/new_pins.c @@ -2294,7 +2294,7 @@ const char* g_channelTypeNames[] = { "Frequency_div1000", "OpenStopClose", "Percent", - "error", + "StopUpDown", "error", "error", }; diff --git a/src/new_pins.h b/src/new_pins.h index ef3eca8e8..19bd51b2d 100644 --- a/src/new_pins.h +++ b/src/new_pins.h @@ -1004,6 +1004,8 @@ typedef enum channelType_e { ChType_OpenStopClose, ChType_Percent, + ChType_StopUpDown, + //chandetail:{"name":"Max", //chandetail:"title":"TODO", //chandetail:"descr":"This is the current total number of available channel types.", diff --git a/src/obk_config.h b/src/obk_config.h index 4a8109e1b..da5c7ff03 100644 --- a/src/obk_config.h +++ b/src/obk_config.h @@ -118,6 +118,7 @@ #define ENABLE_DRIVER_TESTSPIFLASH 1 #define ENABLE_HTTP_OVERRIDE 1 +#define ENABLE_DRIVER_PINMUTEX 1 #define ENABLE_DRIVER_TCL 1 #define ENABLE_DRIVER_PIR 1 #define ENABLE_HA_DISCOVERY 1 diff --git a/src/selftest/selftest_cmd_generic.c b/src/selftest/selftest_cmd_generic.c index 83abb6d93..457d46b5f 100644 --- a/src/selftest/selftest_cmd_generic.c +++ b/src/selftest/selftest_cmd_generic.c @@ -178,9 +178,63 @@ void Test_UART() { } } +void Test_PinMutex() { + // reset whole device + SIM_ClearOBK(0); + CMD_ExecuteCommand("startDriver PinMutex", 0); + // setMutex + CMD_ExecuteCommand("setMutex 0 0 100 10 11", 0); + + SELFTEST_ASSERT(SIM_GetSimulatedPinValue(10) == 0); + SELFTEST_ASSERT(SIM_GetSimulatedPinValue(11) == 0); + Sim_RunMiliseconds(100, false); + SELFTEST_ASSERT(SIM_GetSimulatedPinValue(10) == 0); + SELFTEST_ASSERT(SIM_GetSimulatedPinValue(11) == 0); + CMD_ExecuteCommand("setChannel 0 1", 0); + // from 0 0 to 1 0 set is quick + Sim_RunMiliseconds(25, false); + SELFTEST_ASSERT(SIM_GetSimulatedPinValue(10) == 1); + SELFTEST_ASSERT(SIM_GetSimulatedPinValue(11) == 0); + CMD_ExecuteCommand("setChannel 0 2", 0); + Sim_RunMiliseconds(25, false); + // dead time + SELFTEST_ASSERT(SIM_GetSimulatedPinValue(10) == 0); + SELFTEST_ASSERT(SIM_GetSimulatedPinValue(11) == 0); + Sim_RunMiliseconds(25, false); + SELFTEST_ASSERT(SIM_GetSimulatedPinValue(10) == 0); + SELFTEST_ASSERT(SIM_GetSimulatedPinValue(11) == 0); + Sim_RunMiliseconds(25, false); + SELFTEST_ASSERT(SIM_GetSimulatedPinValue(10) == 0); + SELFTEST_ASSERT(SIM_GetSimulatedPinValue(11) == 0); + Sim_RunMiliseconds(50, false); + // set + SELFTEST_ASSERT(SIM_GetSimulatedPinValue(10) == 0); + SELFTEST_ASSERT(SIM_GetSimulatedPinValue(11) == 1); + CMD_ExecuteCommand("setChannel 0 1", 0); + Sim_RunMiliseconds(25, false); + // dead time + SELFTEST_ASSERT(SIM_GetSimulatedPinValue(10) == 0); + SELFTEST_ASSERT(SIM_GetSimulatedPinValue(11) == 0); + Sim_RunMiliseconds(25, false); + SELFTEST_ASSERT(SIM_GetSimulatedPinValue(10) == 0); + SELFTEST_ASSERT(SIM_GetSimulatedPinValue(11) == 0); + Sim_RunMiliseconds(25, false); + SELFTEST_ASSERT(SIM_GetSimulatedPinValue(10) == 0); + SELFTEST_ASSERT(SIM_GetSimulatedPinValue(11) == 0); + Sim_RunMiliseconds(50, false); + // set + SELFTEST_ASSERT(SIM_GetSimulatedPinValue(10) == 1); + SELFTEST_ASSERT(SIM_GetSimulatedPinValue(11) == 0); + Sim_RunMiliseconds(25, false); + SELFTEST_ASSERT(SIM_GetSimulatedPinValue(10) == 1); + SELFTEST_ASSERT(SIM_GetSimulatedPinValue(11) == 0); + + +} void Test_Commands_Generic() { Test_UART(); Test_Events(); + Test_PinMutex(); // reset whole device SIM_ClearOBK(0);