diff --git a/CMakeLists.txt b/CMakeLists.txt index 19706ad..ba3fb22 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,6 +18,7 @@ idf_component_register( "src/HTTPAPISystem.c" "src/HTTPPrintSystem.c" "src/HTTPPostSystem.c" + "src/CommandProcSys.c" "src/Helpers.c" "src/NetTransport.c" "src/WiFiTransport.c" @@ -52,6 +53,7 @@ idf_component_register( esp_wifi esp_http_server esp_eth + esp_cron esp_modem ttn-esp32 diff --git a/include/CommandProcSys.h b/include/CommandProcSys.h new file mode 100644 index 0000000..692b3db --- /dev/null +++ b/include/CommandProcSys.h @@ -0,0 +1,39 @@ + /* Copyright 2023 Bogdan Pilyugin + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * File name: CommandProcSys.h + * Project: WebguiappTemplate + * Created on: 2023-10-09 + * Author: bogdan + * Description: + */ + +#ifndef COMPONENTS_WEBGUIAPP_INCLUDE_COMMANDPROCSYS_H_ +#define COMPONENTS_WEBGUIAPP_INCLUDE_COMMANDPROCSYS_H_ + + +#include "esp_err.h" + +#define EXEC_COMMAND_MAX_LENGTH (64) +#define EXEC_OBJECT_NAME_MAX_LENGTH (EXEC_COMMAND_MAX_LENGTH/4) +#define EXEC_ACTION_NAME_MAX_LENGTH (EXEC_COMMAND_MAX_LENGTH/4) +#define EXEC_ARGUMENT_MAX_LENGTH (EXEC_COMMAND_MAX_LENGTH/2) + + + +int ExecCommand(char *cmd); +void GetSysObjectsInfo(char *data); + + +#endif /* COMPONENTS_WEBGUIAPP_INCLUDE_COMMANDPROCSYS_H_ */ diff --git a/include/CronTimers.h b/include/CronTimers.h new file mode 100644 index 0000000..b2c0575 --- /dev/null +++ b/include/CronTimers.h @@ -0,0 +1,76 @@ +/* Copyright 2023 Bogdan Pilyugin + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * File name: CronTimers.h + * Project: webguiapp_ref_implement + * Created on: 2023-04-15 + * Author: bogdan + * Description: + */ + +#ifndef COMPONENTS_WEBGUIAPP_INCLUDE_CRONTIMERS_H_ +#define COMPONENTS_WEBGUIAPP_INCLUDE_CRONTIMERS_H_ +#include "esp_err.h" +#include "cron.h" +#include "jobs.h" +#include "ccronexpr.h" + +#define CRON_TIMERS_NUMBER (16) +#define TIMER_NAME_LENGTH (16) +#define TIMER_CRONSTRING_LENGTH (32) +#define CRON_EXPRESS_MAX_LENGTH (128) + +#define CRON_OBJECTS_NUMBER (16) +#define CRON_OBJECT_NAME_LENGTH (16) + +typedef struct +{ + int idx; + char objname[CRON_OBJECT_NAME_LENGTH]; +} cron_obj_t; + +/** + * Cron scheduler configuration structure + */ +typedef struct +{ + int num; /*!< Index of sheduler */ + bool del; /*!< Flag of non valid record, free for future overwrite */ + bool enab; /*!< Enable scheduler */ + bool prev; /*!< Enable to execute nearest in the past sheduled action */ + char name[TIMER_NAME_LENGTH]; /*!< Human readable name of scheduler */ + int obj; /*!< Index of object scheduler affected on */ + int act; /*!< Index of action with the object*/ + char cron[TIMER_CRONSTRING_LENGTH]; /*!< Cron expression */ + +} cron_timer_t; + +esp_err_t InitCronSheduler(); +esp_err_t ReloadCronSheduler(); +char* GetCronError(); +void DebugTimer(); + +char* GetCronObjectNameDef(int idx); +char* GetCronObjectName(int idx); +char* GetCronActionName(int idx); +char* GetCronActAvail(int idx); + +/** + * \brief Handle all actions under all objects + * \param obj Index of the object + * \param act Index of the action + */ +void custom_cron_execute(int obj, int act); + +#endif /* COMPONENTS_WEBGUIAPP_INCLUDE_CRONTIMERS_H_ */ diff --git a/include/UserCallbacks.h b/include/UserCallbacks.h index 86d3a80..1197f5c 100644 --- a/include/UserCallbacks.h +++ b/include/UserCallbacks.h @@ -41,7 +41,11 @@ void regAfterPostHandlerCustom(HTTP_IO_RESULT (*post_handler)(httpd_req_t *req, //User handler for various payload types void regCustomPayloadTypeHandler(sys_error_code (*payload_handler)(data_message_t *MSG)); + //User handler for save App configuration void regCustomSaveConf(void (*custom_saveconf)(void)); +//User handler for execute command +void regCustomExecCommand(int(*custom_exec)(char *cmd)); + #endif /* COMPONENTS_WEBGUIAPP_INCLUDE_USERCALLBACKS_H_ */ diff --git a/include/webguiapp.h b/include/webguiapp.h index 8f0fc55..0ce31fc 100644 --- a/include/webguiapp.h +++ b/include/webguiapp.h @@ -34,6 +34,7 @@ #include "SystemApplication.h" #include "UserCallbacks.h" +#include "CommandProcSys.h" esp_err_t spi_device_polling_transmit_synchronized(spi_device_handle_t handle, spi_transaction_t *trans_desc); diff --git a/src/CommandProcSys.c b/src/CommandProcSys.c new file mode 100644 index 0000000..875685a --- /dev/null +++ b/src/CommandProcSys.c @@ -0,0 +1,160 @@ +/* Copyright 2023 Bogdan Pilyugin + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * File name: CommandProcSys.c + * Project: WebguiappTemplate + * Created on: 2023-10-09 + * Author: bogdan + * Description: + */ + +#include "CommandProcSys.h" +#include "webguiapp.h" + +#define TAG "COMMAND_PROC_SYS" + +#define OBJECTS_NUMBER_SYS (1) +#define EXEC_ACTIONS_MAX_NUMBER_SYS (2) + +const char *exec_errors[] = { + "executed OK", + "exec string exceeded maximum length", + "param OBJECT not found", + "param COMMAND not found", + "object not exists", + "command not exists", + "handler not set" +}; + +static int ExecCommandParse(char *cmd); +static int ExecSysCommand(char *cmd); + +int (*CustomExecCommand)(char *cmd); +void regCustomExecCommand(int (*custom_exec)(char *cmd)) +{ + CustomExecCommand = custom_exec; +} + +static void SYSTEM_TEST_handle(char *obj, char *com, char *arg) +{ + ESP_LOGI(TAG, "INPUTS handler command %s with argument %s", com, arg); +} +static void SYSTEM_REBOOT_handle(char *obj, char *com, char *arg) +{ + ESP_LOGI(TAG, "SYSTEM handler command %s with argument %s", com, arg); +} + +typedef struct +{ + int index; + char object_name[EXEC_OBJECT_NAME_MAX_LENGTH]; + char allowed_actions[EXEC_ACTIONS_MAX_NUMBER_SYS][EXEC_ACTION_NAME_MAX_LENGTH]; + void (*command_handlers[EXEC_ACTIONS_MAX_NUMBER_SYS])(char *obj, char *com, char *arg); +} obj_struct_t; + +const obj_struct_t com_obj_arr[] = { + { + .index = 0, + .object_name = "SYSTEM", + .allowed_actions = { "TEST", "REBOOT" }, + .command_handlers = { &SYSTEM_TEST_handle, &SYSTEM_REBOOT_handle } + } +}; + +void GetSysObjectsInfo(char *data) +{ + struct jWriteControl jwc; + jwOpen(&jwc, data, VAR_MAX_VALUE_LENGTH, JW_ARRAY, JW_COMPACT); + for (int idx = 0; idx < OBJECTS_NUMBER_SYS; idx++) + { + jwArr_object(&jwc); + jwObj_string(&jwc, "object", com_obj_arr[idx].object_name); + jwObj_array(&jwc, "actions"); + for (int i = 0; i < EXEC_ACTIONS_MAX_NUMBER_SYS; i++) + { + if ((com_obj_arr[idx].allowed_actions[i])[0] != NULL) + jwArr_string(&jwc, com_obj_arr[idx].allowed_actions[i]); + } + jwEnd(&jwc); + jwEnd(&jwc); + } + jwClose(&jwc); +} + +int ExecCommand(char *cmd) +{ + int err = ExecSysCommand(cmd); + if(err != 4) + { + if (err > 0) + ESP_LOGW(TAG, "Command execution ERROR: %s",exec_errors[err]); + return err; + } + + if (CustomExecCommand != 0) + err = CustomExecCommand(cmd); + + if (err > 0) + ESP_LOGW(TAG, "Command execution ERROR: %s",exec_errors[err]); + return err; +} + +static int ExecSysCommand(char *cmd) +{ + return ExecCommandParse(cmd); +} + +static int ExecCommandParse(char *cmd) +{ + char *obj = NULL, *com = NULL, *arg = NULL; + int err = 0; + int commlen = strlen(cmd); + if (commlen > EXEC_COMMAND_MAX_LENGTH) + return 1; + char comm[EXEC_COMMAND_MAX_LENGTH + 1]; + const char del1 = ','; + const char del2 = 0x00; + strcpy(comm, cmd); + obj = strtok(comm, &del1); + com = strtok(NULL, &del1); + arg = strtok(NULL, &del2); + if (!obj) + return 2; + if (!com) + return 3; + + err = 4; + for (int idx = 0; idx < OBJECTS_NUMBER_SYS; idx++) + { + if (!strcmp(obj, com_obj_arr[idx].object_name)) + { + err = 5; + for (int i = 0; i < EXEC_ACTIONS_MAX_NUMBER_SYS; i++) + { + if (!strcmp(com, com_obj_arr[idx].allowed_actions[i])) + { + if (com_obj_arr[idx].command_handlers[i] != NULL) + { + com_obj_arr[idx].command_handlers[i](obj, com, arg); + err = 0; + } + else + err = 6; + } + } + } + } + return err; +} + diff --git a/src/CronTimers.c b/src/CronTimers.c new file mode 100644 index 0000000..7e80978 --- /dev/null +++ b/src/CronTimers.c @@ -0,0 +1,213 @@ +/* Copyright 2023 Bogdan Pilyugin + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * File name: CronTimers.c + * Project: webguiapp_ref_implement + * Created on: 2023-04-15 + * Author: bogdan + * Description: + */ + +#include +#include "esp_log.h" +#include "webguiapp.h" + +#define TAG "CRON_TIMER" + + +const char *cron_actions[] = { "ON", "REBOOT", "TOGGLE", "OFF","VERYLONG_OPERATION" }; +const char *cron_objects[] = { + "RELAY1", + "RELAY2", + "RELAY3", + "RELAY4", + "RELAY5", + "RELAY6", + "RELAY7", + "RELAY8", + "SYSTEM" }; +const char *cron_act_avail[] = { + "[0,2,3]", + "[0,2,3]", + "[0,2,3]", + "[0,2,3]", + "[0,2,3]", + "[0,2,3]", + "[0,2,3]", + "[0,2,3]", + "[1,4]" }; + +char* GetCronObjectNameDef(int idx) +{ + if(idx < 0 || idx >= sizeof(cron_objects)/sizeof(char*)) + return ""; + return (char*)cron_objects[idx]; +} + +char* GetCronObjectName(int idx) +{ + if(idx < 0 || idx >= sizeof(cron_objects)/sizeof(char*)) + return ""; + return GetAppConf()->CronObjects[idx].objname; +} + +char* GetCronActionName(int idx) +{ + if(idx < 0 || idx >= sizeof(cron_actions)/sizeof(char*)) + return ""; + return (char*)cron_actions[idx]; +} + +char* GetCronActAvail(int idx) +{ + if(idx < 0 || idx >= sizeof(cron_act_avail)/sizeof(char*)) + return "[]"; + return (char*)cron_act_avail[idx]; +} + + +static cron_job *JobsList[CRON_TIMERS_NUMBER]; +static char cron_express_error[CRON_EXPRESS_MAX_LENGTH]; + +char* GetCronError() +{ + return cron_express_error; +} + +/** + * \brief Handle all actions under all objects + * \param obj Index of the object + * \param act Index of the action + */ +void custom_cron_execute(int obj, int act) +{ + + + +} + +void custom_cron_job_callback(cron_job *job) +{ + int act = ((cron_timer_t*) job->data)->act; + int obj = ((cron_timer_t*) job->data)->obj; + char *name = ((cron_timer_t*) job->data)->name; + //here call all timers jobs depends on object and action + time_t now; + time(&now); + ESP_LOGI(TAG, "Execute scheduler '%s' action %d under object %d at time %d", name, act, obj, (unsigned int )now); + LogFile("cron.log", "Executed sheduler with action %u under object %u", act, obj); + custom_cron_execute(obj, act); + return; +} + +esp_err_t InitCronSheduler() +{ + esp_err_t res = ESP_OK; + return res; +} + +const char* check_expr(const char *expr) +{ + const char *err = NULL; + cron_expr test; + memset(&test, 0, sizeof(test)); + cron_parse_expr(expr, &test, &err); + return err; +} + + +static void ExecuteLastAction() +{ + int obj; + for (obj = 0; obj < sizeof(cron_objects); obj++) + { + int shdl; + time_t now; + time(&now); + time_t delta = now; + int act = -1; + + for (shdl = 0; shdl < CRON_TIMERS_NUMBER; shdl++) + { + if (GetAppConf()->Timers[shdl].enab && + !GetAppConf()->Timers[shdl].del && + GetAppConf()->Timers[shdl].prev && + GetAppConf()->Timers[shdl].obj == obj) + { + cron_expr cron_exp = { 0 }; + cron_parse_expr(GetAppConf()->Timers[shdl].cron, &cron_exp, NULL); + time_t prev = cron_prev(&cron_exp, now); + if ((now - prev) < delta) + { + delta = (now - prev); + act = GetAppConf()->Timers[shdl].act; + } + } + } + + if(act != -1) + { + ESP_LOGW(TAG, "Execute last action %d with object %d", act, obj); + LogFile("cron.log", "Execute last action %d under object %d", act, obj); + custom_cron_execute(obj, act); + } + } +} + +void TimeObtainHandler(struct timeval *tm) +{ + ESP_LOGW(TAG, "Current time received with value %d", (unsigned int )tm->tv_sec); + ReloadCronSheduler(); + ExecuteLastAction(); + LogFile("cron.log", "Cron service started"); +} + +void DebugTimer() +{ + ExecuteLastAction(); +} + +esp_err_t ReloadCronSheduler() +{ + //remove all jobs + ESP_LOGI(TAG, "Cron stop call result %d", cron_stop()); + cron_job_clear_all(); + //check if we have jobs to run + bool isExpressError = false; + for (int i = 0; i < CRON_TIMERS_NUMBER; i++) + { + const char *err = check_expr(GetAppConf()->Timers[i].cron); + if (err) + { + snprintf(cron_express_error, CRON_EXPRESS_MAX_LENGTH - 1, "In timer %d expression error:%s", i + 1, err); + ESP_LOGE(TAG, "%s", cron_express_error); + isExpressError = true; + continue; + } + else if (!GetAppConf()->Timers[i].del && GetAppConf()->Timers[i].enab) + { + JobsList[i] = cron_job_create(GetAppConf()->Timers[i].cron, custom_cron_job_callback, + (void*) &GetAppConf()->Timers[i]); + } + } + if (!isExpressError) + cron_express_error[0] = 0x00; //clear last cron expression parse + int jobs_num = cron_job_node_count(); + ESP_LOGI(TAG, "In config presents %d jobs", jobs_num); + + if (jobs_num > 0) + ESP_LOGI(TAG, "Cron start call result %d", cron_start()); + return ESP_OK; +} +