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);