# Imports `jescore` is a **C/C++** library built on top of **FreeRTOS**. It is developed in the **PlatformIO** development environment and can be easily integrated into other projects based on that framework: ```ini ; platformio.ini: [env:my_board] ... lib_deps = https://github.com/jesdev-io/jescore ; as repo ; or jescore ; as PlatformIO registry library ``` Right now, `jescore` supports the ESP32 platform with the Arduino FW and the STM32 platform with the STM32Cube HAL. The following devices were tested: - ESP32-WROOM - ESP32-C3 - ESP32-S3 - ESP32-WROVER - STM32 Nucleo-L476RG - STM32 Nucleo-L432KC - STM32 Nucleo-G431KB - STM32 Nucleo-H753ZI - STM32U585 (Arduino Uno Q) Theoretically, all STM and ESP boards are supported, but some of them need hardware specific macros in place. Please refer to the guide on adding [board support](https://github.com/jesdev-io/jescore/wiki/Backend-Documentation#board-support). You can now import `jescore`: ```c++ // your_project/src/main.cpp ... #include void setup(){ } void loop(){ } ``` ```c // your_project/src/main.c ... #include int main(){ } ``` For other IDEs, you can clone the repo with `git clone https://github.com/jesdev-io/jescore` and append the libraries to your Makefile like you would with any other source files. # Functions #### `void your_function(void* p)` `jescore` is all about the user, but you still have to adhere to its rules if you want to use it. For this reason, all functions you intend to run with it have to have the mentioned signature. No return, a single `void*` as parameter, similar to FreeRTOS (you can guess why). However, this parameter `p` is always loaded with the handle to the job which belongs to the function, as it may be useful from within the function, if you know what you are doing, see [Backend documentation](https://github.com/jesdev-io/jescore/wiki/Backend-Documentation). But for most cases, especially for simple jobs, this can be ignored. *** ## Core Functions #### `jes_err_t jes_init()` > **Brief:** Start the core and all of its abilities. **Returns:** Status. Returns `e_err_no_err` in case of successful launch. Use `jes_init()` in your `setup()` or any other initialization code. `jescore` needs dynamic memory and will tell you if not enough is available. Be sure to **always** check the return value of `jes_err_t`! The error types are explained in [`jes_err_t`](#jes_err_t). #### `void jes_dispatch(void)` **(only for STM32 projects!)** > **Brief:** Let the core take over your program flow. This activates the task scheduler of FreeRTOS **and will never return**! Call this at the end of your `main()` function. This is needed because on the STM32, the program entry is `main()`, which is not a task. On the ESP32 with the Arduino FW, `setup()` and `loop()` already operate with a running scheduler, similar to the ESP-IDF `app_main()`. ## Job Management #### `jes_err_t jes_register_job(const char* name, uint32_t mem_size, uint8_t priority, void (*function)(void* p), uint8_t is_loop, uint8_t is_singleton)` > **Brief:** Add a job (function block) to the list of all known jobs. `name`: Name of job. Can't be longer than `MAX_JOB_NAME_LEN_BYTE`. `mem_size`: Dynamic memory size for job. `priority`: Priority of the job (1 is highest). `function`: Function to run when the job is called. Has to be of signature `void my_func(void* p)`. `is_loop`: Flag which describes the lifetime of the job. `is_singleton`: Flag which describes if only one instance of this job can run at a time (1 = singleton, 0 = multiple instances allowed). **Returns:** Status. Returns `e_err_no_err` in case of successful registration. Use `jes_register_job()` to make one of your functions known to `jescore`. By registering it, you append it to the core's list of known jobs and it gets passed to the task scheduler of FreeRTOS. You can give your function a name, which can later be called by the CLI, see [CLI documentation](https://github.com/jesdev-io/jescore/wiki/CLI-Documentation), if you want that. Be sure that your name does not contain whitespaces, because `jescore` uses those to determine additional arguments in your CLI calls. It also should not be longer than [`MAX_JOB_NAME_LEN_BYTE`](#max_job_name_len_byte). Start with a generous memory size, as FreeRTOS keeps a limited stack for each of your registered functions. The size is determined by the amount of local or static variables in your function and the depth of its calls, so estimating a good memory size is quite hard. Start larger than you might initially think, and then lower it over time if everything runs as expected. You also have to specify whether your function has an infinite loop or ends at some point. Additionally, you can specify if the job should be a singleton (only one instance at a time) or can have multiple concurrent instances. Be sure to set these truthfully, as it may affect job behavior. #### `jes_err_t jes_unregister_job(const char* name)` > **Brief:** Remove a job from the job list and free its resources. `name`: Name of job to unregister. **Returns:** Status. Returns `e_err_no_err` if OK. **Note:** Will not unregister a job that has active instances running. Frees the job struct, its queue, and semaphore. #### `jes_err_t jes_launch_job(const char* name)` > **Brief:** Start a registered job. `name`: String name of job as set in `jes_register_job()`. **Returns:** Status. Returns `e_err_no_err` in case of successful launch. To launch a job, use `jes_launch_job()` and give it the name of the job you previously registered. If you find this step redundant, see [`jes_register_and_launch_job()`](#jes_err_t-jes_register_and_launch_jobconst-char-name-uint32_t-mem_size-uint8_t-priority-void-functionvoid-p-uint8_t-is_loop-uint8_t-is_singleton). #### `jes_err_t jes_launch_job_args(const char* name, const char* args)` > **Brief:** Start a registered job with arguments. `name`: String name of job as set in `jes_register_job()`. `args`: String of arguments delimited by whitespaces. Can't be longer than `MAX_JOB_ARGS_LEN_BYTE`. **Returns:** Status. Returns `e_err_no_err` in case of successful launch. Launch a job with arguments. This is useful if you have runtime-dependent launch sequences or want to mimic a CLI call. This makes `jescore` powerful: during development, the CLI can be used to trigger actions on the MCU. If hardware is added, its interrupt or callback can trigger the exact same job with arguments that the CLI would perform before the input hardware was added. #### `jes_err_t jes_register_and_launch_job(const char* name, uint32_t mem_size, uint8_t priority, void (*function)(void* p), uint8_t is_loop, uint8_t is_singleton)` > **Brief:** Add a job (function block) to the list of all known jobs and launch it. `name`: Name of job. Can't be longer than `MAX_JOB_NAME_LEN_BYTE`. `mem_size`: Dynamic memory size for job. `priority`: Priority of the job (1 is highest). `function`: Function to run when the job is called. has to be of signature `void my_func(void* p)`. `is_loop`: flag which describes the lifetime of the job. `is_singleton`: flag which describes if only one instance of this job can run at a time. **Returns:** Status. Returns `e_err_no_err` in case of successful launch. Use this function to combine the two above. This makes sense for all jobs which get started during the boot process of your project. ## Job Argument Handling #### `jes_err_t jes_job_set_args(const char* s)` > **Brief:** Set the field `args` of the calling job. `s`: String to insert into `args` field. **Returns:** status, `e_err_no_err` if OK. Each job contains a field of fixed memory size, called [`args`](https://github.com/jesdev-io/jescore/wiki/Backend-Documentation#jobs). Its size depends on [`MAX_JOB_ARGS_LEN_BYTE`](#max_job_args_len_byte). It is normally used to store the CLI arguments passed to that particular job, but there might be cases in which this array may be loaded by a program snippet rather than a user handling the CLI. Use this function to put an arbitrary C-string into that buffer. If the string is too long, the error return will tell you that. #### `char* jes_job_get_args(void)` > **Brief:** Get the field `args` of the calling job. **Returns:** Pointer to `args` field of the job. This function returns a pointer to the `args` buffer of the calling job. Since this function is intended to be called from within a job, the memory of the job struct persists, which makes a copy of the arg string redundant. If, however, something goes wrong, this function will return NULL. Use this function to retrieve anything that has been put into the `args` buffer by either [`jes_job_set_args()`](#jes_err_t-jes_job_set_argschar-s) or the CLI. #### `char* jes_job_arg_next(void)` > **Brief:** Get the next arg from the args field. **Return:** Next arg delimited by a whitespace. **Note:** Use this in an arg-parsing loop. This is a practical way of parsing args in a loop. It returns args until none are left, then returning `NULL`. #### `uint8_t jes_job_is_arg(const char* arg, const char* name)` > **Brief:** Check if two args are the same. **Param:** arg Input arg from [`jes_job_get_args()`](#char-jes_job_get_argsvoid) or [`jes_job_arg_next()`](#char-jes_job_arg_nextvoid) **Param:** name Name of arg to compare. **Return:** 1 if matching, 0 if not. **Note:** Use this in an arg-parsing loop. This function exists to check args against each other. ## Job Parameter Handling #### `jes_err_t jes_job_set_param(const void* p)` > **Brief:** Set the field `optional` of the job. `p`: Arbitrary reference to parameter. **Returns:** status, `e_err_no_err` if OK. For any arbitrary argument you might pass to a job, the field `param` can be used. It is declared as `void*`, so you can pass anything to it. Keep in mind that you 1. need to have persistent storage for the content of the pointer, because `jescore` does not copy it 2. make sure that you cast it back into the original type correctly. In most use-cases, you might pass the reference to a struct to this parameter, because a struct has addressable fields as opposed to an "arbitrary" array length, which means that a single pointer is sufficient to describe it completely. #### `void* jes_job_get_param(void)` > **Brief:** Get the field `param` of the calling job. **Returns:** Pointer to `param` field of the job. Use this function to retrieve whatever you put into the `param` field in your job with [`jes_job_set_param()`](#jes_err_t-jes_job_set_paramvoid-p). Be careful, this value might be `NULL` if you never called `jes_job_set_param()` with anything other than `NULL`. ## Error Handling #### `jes_err_t jes_error_get(const char* job_name)` > **Brief:** Get the field `error` of a given job. **Param:** job_name Name of the job. **Return:** Stored error. Call this function to get the last error that occured in a job. An error can be set by an illegal run-time operation that the core caught or a custom error caused by [`jes_throw_error()`](#void-jes_throw_errorjes_err_t-e). #### `jes_err_t jes_error_get_any(void)` > **Brief:** Get the first error that of all jobs. **Return:** Error of first job that has one. **Note:** Returns `e_err_no_err` in case that every job is error-free. **Note:** Use this function to quickly spot if the program is error free. This function will iterate over all registered jobs and return on the first associated error it finds. This function is useful for checking if all processes are running error free at a given point in time. #### `void jes_throw_error(jes_err_t e)` > **Brief:** Throw an error that is registered in the core. **Param:** e Error to throw. **Note:** This is useful to let other jobs or the core know when a job exits based on a user condition. With this function an error can be thrown manually in a user program if a given condition is met. This will then set the [`error`](Backend-Documentation.md#error) field of the evoking job. ## Inter-Job Communication #### `jes_err_t jes_notify_job(const char* name, const void* notification)` > **Brief:** Notify a job with an optional message. **Param:** `name`: Name of job which should be notified. **Param:** `notification`: Optional pointer to notification value. **Returns:** status, `e_err_no_err` if OK. Call this function to notify a different job. For a job to accept notifications, it has to be waiting for one, see [`jes_wait_for_notification()`](#void-jes_wait_for_notificationvoid). If you don't want to send a notification value, pass `NULL` to `notification`. #### `jes_err_t jes_notify_job_ISR(const char* name, const void* notification)` > **Brief:** Notify a job with an optional message. **Param:** `name`: Name of job which should be notified. **Param:** `notification`: Optional pointer to notification value. **Returns:** status, `e_err_no_err` if OK. Same as [`jes_notify_job()`](#jes_err_t-jes_notify_jobconst-char-name-const-void-notification) but for interrupt service routines. Call this from within an interrupt. #### `void* jes_wait_for_notification(void)` > **Brief:** Wait for an incoming message. **Returns:** The optional notification value. This stalls the calling task but is not blocking. The task is simply suspended from execution until a notification arrives. From there on, the task will resume, regardless of the contents of the optional return notification. However, it can be useful to tell the waiting task what to do next. ## Timing #### `void jes_delay_job_ms(uint32_t ms)` > **Brief:** Delay a job in milliseconds. **Param:** ms Milliseconds of delay. **Note:** Timing is handled by FreeRTOS. This function should be called from within a job context. ## Printing #### `jes_print(format, ...)` > **Brief:** Print a formatted string with job identification. **Note:** This macro automatically prefixes the output with the calling job's name. Use it like `printf()`. **Example:** `jes_print("Hello from job!\n")` will print `[job_name]: Hello from job!` #### `jes_print_pj(pj, format, ...)` > **Brief:** Print a formatted string with a specific job's name as prefix. **Param:** pj Pointer to a job_struct_t. **Param:** format Format string (like printf). **Note:** This macro prefixes the output with the specified job's name. **Example:** `jes_print_pj(my_job, "Status: %d\n", status)` # Types ### `jes_err_t` This is the standard return type of most `jescore` functions. They describe if a function call was successful or failed in one or more ways. The errors are shown below: ```c typedef enum jes_err{ e_err_no_err, e_err_mem_null, e_err_is_zero, e_err_param, e_err_peripheral_block, e_err_core_fail, e_err_duplicate, e_err_too_long, e_err_unknown_job, e_err_leading_whitespace, e_err_prohibited, e_err_driver_fail, e_err_NUM_ERR }jes_err_t; ``` > `e_err_no_err`: No error, everything worked. `e_err_mem_null`: Dynamic memory operation returned `NULL`, out of memory. `e_err_is_zero`: Given value is 0 or `NULL` or a value that should never be 0 is 0 regardless. `e_err_param`: An incorrect parameter was given. `e_err_peripheral_block`: Calling a hardware peripheral failed because it is busy. `e_err_core_fail`: The core dealt with a hard fault. `e_err_duplicate`: A job has been registered twice by accident or a job which can only have one launch at a time has been launched twice. `e_err_too_long`: A given string argument is too long. Can be caused by both API and CLI usage. `e_err_unknown_job`: An unregistered job tried to launch. This is typical for typing errors. `e_err_leading_whitespace`: CLI only. A command with a leading whitespace was sent. Whitespaces are used to separate arguments, so this can cause issues. `e_err_prohibited`: You tried to call a core job by the API. This is forbidden, as it may crash the system. `e_err_driver_fail`: A (hardware) driver failure from an underlying HAL function. `e_err_NUM_ERR`: Number of error types. Used for error counting and validation. # Macros ### `MAX_JOB_NAME_LEN_BYTE` This macro is used to allocate a character buffer in every job struct. The names of your jobs can never be longer than this, and [`jes_register_job()`](#jes_err_t-jes_register_jobconst-char-name-uint32_t-mem_size-uint8_t-priority-void-functionvoid-p-uint8_t-is_loop-uint8_t-is_singleton) will return `e_err_too_long` in such a case. You can overwrite the default value of 32 bytes by defining `MAX_JOB_NAME_LEN_BYTE` yourself: ```c #define MAX_JOB_NAME_LEN_BYTE 64 void setup(){ } void loop(){ } ``` This will of course cause every `job_struct_t` to become bigger. ### `MAX_JOB_ARGS_LEN_BYTE` Similar to `MAX_JOB_NAME_LEN_BYTE` you can change `MAX_JOB_ARGS_LEN_BYTE`. This is also a character buffer which holds arguments in string format. Both the [`jes_job_set_args()`](#jes_err_t-jes_job_set_argschar-s) and [`jes_job_get_args()`](#char-jes_job_get_argsvoid) use this buffer as well as any usage of the CLI. ### `JES_LOG_LEN` `jescore` logs core interactions during runtime. The log exists in RAM and is as long as `JES_LOG_LEN`, which by default is set to 8. You can override this macro by passing `-DJES_LOG_LEN=16` to your build flags. Keep in mind that this will use extra memory. If you set `JES_LOG_LEN` to 0, logging will be disabled entirely.