diff --git a/parts/esp32-components/lifesensor_common/include/logger.h b/parts/esp32-components/lifesensor_common/include/logger.h new file mode 100644 index 0000000..220704f --- /dev/null +++ b/parts/esp32-components/lifesensor_common/include/logger.h @@ -0,0 +1,70 @@ +#ifndef LIFESENSOR_COMMON_LOGGER_H +#define LIFESENSOR_COMMON_LOGGER_H + +#include + +enum lifesensor_logger_scope_type { + LIFESENSOR_LOGGER_SCOPE_NONE, + LIFESENSOR_LOGGER_SCOPE_LIST, + LIFESENSOR_LOGGER_SCOPE_MAP +}; + +struct lifesensor_logger_scope { + int (*printf)(const char *fmt, ...); + enum lifesensor_logger_scope_type type; + size_t entries; + size_t level; +}; +typedef struct lifesensor_logger_scope Lifesensor_logger_scope; + +struct lifesensor_logger { + void (*enter_map)( + struct lifesensor_logger_scope* scope, + struct lifesensor_logger_scope* subscope, + char *name + ); + void (*enter_list)( + struct lifesensor_logger_scope* scope, + struct lifesensor_logger_scope* subscope, + char *name + ); + void (*exit)( + struct lifesensor_logger_scope* scope, + struct lifesensor_logger_scope* subscope + ); + + void (*log_uint)( + struct lifesensor_logger_scope* scope, + unsigned long int value, + char *name + ); + void (*log_int)( + struct lifesensor_logger_scope* scope, + long int value, + char *name + ); + void (*log_float)( + struct lifesensor_logger_scope* scope, + float value, + char *name + ); + void (*log_char)( + struct lifesensor_logger_scope* scope, + char value, + char *name + ); + void (*log_str)( + struct lifesensor_logger_scope* scope, + char *value, + char *name + ); + void (*log_ptr)( + struct lifesensor_logger_scope* scope, + void *value, + char *name + ); +}; +typedef struct lifesensor_logger Lifesensor_logger; + + +#endif diff --git a/parts/esp32-components/lifesensor_common/include/logger_json.h b/parts/esp32-components/lifesensor_common/include/logger_json.h new file mode 100644 index 0000000..68d1549 --- /dev/null +++ b/parts/esp32-components/lifesensor_common/include/logger_json.h @@ -0,0 +1,12 @@ +#ifndef LIFESENSOR_COMMON_LOGGER_JSON_H +#define LIFESENSOR_COMMON_LOGGER_JSON_H + +#include "logger.h" + +void +lifesensor_logger_json_init( + Lifesensor_logger *logger, + Lifesensor_logger_scope *scope, + int (*printf)(const char *fmt, ...)); + +#endif \ No newline at end of file diff --git a/parts/esp32-components/lifesensor_common/include/logger_yaml.h b/parts/esp32-components/lifesensor_common/include/logger_yaml.h new file mode 100644 index 0000000..1dd0b98 --- /dev/null +++ b/parts/esp32-components/lifesensor_common/include/logger_yaml.h @@ -0,0 +1,12 @@ +#ifndef LIFESENSOR_COMMON_LOGGER_YAML_H +#define LIFESENSOR_COMMON_LOGGER_YAML_H + +#include "logger.h" + +void +lifesensor_logger_yaml_init( + Lifesensor_logger *logger, + Lifesensor_logger_scope *scope, + int (*printf)(const char *fmt, ...)); + +#endif \ No newline at end of file diff --git a/parts/esp32-components/lifesensor_common/include/macro/map.h b/parts/esp32-components/lifesensor_common/include/macro/map.h new file mode 100644 index 0000000..0100d23 --- /dev/null +++ b/parts/esp32-components/lifesensor_common/include/macro/map.h @@ -0,0 +1,177 @@ +#ifndef LIFESENSOR_COMMON_MAP_H +#define LIFESENSOR_COMMON_MAP_H + +/** + * macro _MAP_EVAL*(...) + * these macros let the preprocessor expand the inital arguments multiple times, + * which results in multiple evaluation runs of the initial arguments. + */ +#define _MAP_EVAL1(...) __VA_ARGS__ +#define _MAP_EVAL2(...) _MAP_EVAL1(_MAP_EVAL1(__VA_ARGS__)) +#define _MAP_EVAL4(...) _MAP_EVAL2(_MAP_EVAL2(__VA_ARGS__)) +#define _MAP_EVAL16(...) _MAP_EVAL4(_MAP_EVAL4(__VA_ARGS__)) +#define _MAP_EVAL256(...) _MAP_EVAL16(_MAP_EVAL16(__VA_ARGS__)) + +/** + * macro _MAP_NOP + * this macro expands to nothing and can be used to break up another macro from + * its arguments to stop the preprocessor from expanding the macro. + */ +#define _MAP_NOP /*empty*/ + +/** + * macros _MAP_POP{0,1}(F, X, ...) + * these macros are called as _MAP_POP{0,1}(F, ...) and take the first argument + * of the variadic arguments and apply the function F to it. + * If any variadic arguments remain they call their sibling macro which does + * the same. The use of the sibling and _MAP_NOP macro ensures that the + * preprocessor does not detect a recursion call and stops processing. + */ +#define _MAP_POP0(F, X, ...) \ + F(X) \ + __VA_OPT__(, _MAP_POP1 _MAP_NOP(F, __VA_ARGS__)) +#define _MAP_POP1(F, X, ...) \ + F(X) \ + __VA_OPT__(, _MAP_POP0 _MAP_NOP(F, __VA_ARGS__)) + +/** + * macro MAP(F, ...) + * Maps the function F to each of the variadic arguments and uses a comma as + * delimiter. + * I.e. MAP(F,1,2,3) results in F(1), F(2), F(3) + */ +#define MAP(F, ...) __VA_OPT__(_MAP_EVAL256(_MAP_POP0(F, __VA_ARGS__))) + +/** + * see _MAP_POP{0,1} + */ +#define _MAP1_POP0(F, P0, X, ...) \ + F(P0, X) \ + __VA_OPT__(, _MAP1_POP1 _MAP_NOP(F, P0, __VA_ARGS__)) +#define _MAP1_POP1(F, P0, X, ...) \ + F(P0, X) \ + __VA_OPT__(, _MAP1_POP0 _MAP_NOP(F, P0, __VA_ARGS__)) + +/** + * macro MAP1(F, P0, ...) + * Maps the parameterized function F to each of the variadic arguments + * and uses a comma as delimiter. + * I.e. MAP1(F,A,1,2,3) results in F(A,1), F(A,2), F(A,3) + */ +#define MAP1(F, P0, ...) \ + __VA_OPT__(_MAP_EVAL256(_MAP1_POP0(F, P0, __VA_ARGS__))) + +/** + * see _MAP_POP{0,1} + */ +#define _MAP2_POP0(F, P0, P1, X, ...) \ + F(P0, P1, X) \ + __VA_OPT__(, _MAP2_POP1 _MAP_NOP(F, P0, P1, __VA_ARGS__)) +#define _MAP2_POP1(F, P0, P1, X, ...) \ + F(P0, P1, X) \ + __VA_OPT__(, _MAP2_POP0 _MAP_NOP(F, P0, P1, __VA_ARGS__)) + +/** + * macro MAP2(F, P0, P1, ...) + * Maps the parameterized function F to each of the variadic arguments + * and uses a comma as delimiter. + * I.e. MAP2(F,A,B,1,2,3) results in F(A,B,1), F(A,B,2), F(A,B,3) + */ +#define MAP2(F, P0, P1, ...) \ + __VA_OPT__(_MAP_EVAL256(_MAP2_POP0(F, P0, P1, __VA_ARGS__))) + +/** + * see _MAP_POP{0,1} + */ +#define _MAP3_POP0(F, P0, P1, P2, X, ...) \ + F(P0, P1, P2, X) \ + __VA_OPT__(, _MAP3_POP1 _MAP_NOP(F, P0, P1, P2, __VA_ARGS__)) +#define _MAP3_POP1(F, P0, P1, P2, X, ...) \ + F(P0, P1, P2, X) \ + __VA_OPT__(, _MAP3_POP0 _MAP_NOP(F, P0, P1, P2, __VA_ARGS__)) + +/** + * macro MAP3(F, P0, P1, P2, ...) + * Maps the parameterized function F to each of the variadic arguments + * and uses a comma as delimiter. + * I.e. MAP3(F,A,B,C,1,2,3) results in F(A,B,C,1), F(A,B,C,2), F(A,B,C,3) + */ +#define MAP3(F, P0, P1, P2, ...) \ + __VA_OPT__(_MAP_EVAL256(_MAP3_POP0(F, P0, P1, P2, __VA_ARGS__))) + +/** + * see _MAP_POP{0,1} + */ +#define _MAPD_POP0(D, F, X, ...) \ + F(X) \ + __VA_OPT__(D _MAPD_POP1 _MAP_NOP(D, F, __VA_ARGS__)) +#define _MAPD_POP1(D, F, X, ...) \ + F(X) \ + __VA_OPT__(D _MAPD_POP0 _MAP_NOP(D, F, __VA_ARGS__)) + +/** + * macro MAP(D, F, ...) + * Maps the function F to each of the variadic arguments and uses D as + * delimiter. + * I.e. MAP(+,F,1,2,3) results in F(1) + F(2) + F(3) + */ +#define MAPD(D, F, ...) __VA_OPT__(_MAP_EVAL256(_MAPD_POP0(D, F, __VA_ARGS__))) + +/** + * see _MAP_POP{0,1} + */ +#define _MAPD1_POP0(D, F, P0, X, ...) \ + F(P0, X) \ + __VA_OPT__(D _MAPD1_POP1 _MAP_NOP(D, F, P0, __VA_ARGS__)) +#define _MAPD1_POP1(D, F, P0, X, ...) \ + F(P0, X) \ + __VA_OPT__(D _MAPD1_POP0 _MAP_NOP(D, F, P0, __VA_ARGS__)) + +/** + * macro MAPD1(D, F, P0, ...) + * Maps the parameterized function F to each of the variadic arguments + * and uses D as delimiter. + * I.e. MAPD1(+,F,A,1,2,3) results in F(A,1) + F(A,2) + F(A,3) + */ +#define MAPD1(D, F, P0, ...) \ + __VA_OPT__(_MAP_EVAL256(_MAPD1_POP0(D, F, P0, __VA_ARGS__))) + +/** + * see _MAP_POP{0,1} + */ +#define _MAPD2_POP0(D, F, P0, P1, X, ...) \ + F(P0, P1, X) \ + __VA_OPT__(D _MAPD2_POP1 _MAP_NOP(D, F, P0, P1, __VA_ARGS__)) +#define _MAPD2_POP1(D, F, P0, P1, X, ...) \ + F(P0, P1, X) \ + __VA_OPT__(D _MAPD2_POP0 _MAP_NOP(D, F, P0, P1, __VA_ARGS__)) + +/** + * macro MAPD2(D, F, P0, P1, ...) + * Maps the parameterized function F to each of the variadic arguments + * and uses D as delimiter. + * I.e. MAPD2(+,F,A,B,1,2,3) results in F(A,B,1) + F(A,B,2) + F(A,B,3) + */ +#define MAPD2(D, F, P0, P1, ...) \ + __VA_OPT__(_MAP_EVAL256(_MAPD2_POP0(D, F, P0, P1, __VA_ARGS__))) + +/** + * see _MAP_POP{0,1} + */ +#define _MAPD3_POP0(D, F, P0, P1, P2, X, ...) \ + F(P0, P1, P2, X) \ + __VA_OPT__(D _MAPD3_POP1 _MAP_NOP(D, F, P0, P1, P2, __VA_ARGS__)) +#define _MAPD3_POP1(D, F, P0, P1, P2, X, ...) \ + F(P0, P1, P2, X) \ + __VA_OPT__(D _MAPD3_POP0 _MAP_NOP(D, F, P0, P1, P2, __VA_ARGS__)) + +/** + * macro MAPD3(D, F, P0, P1, P2, ...) + * Maps the parameterized function F to each of the variadic arguments + * and uses D as delimiter. + * I.e. MAPD3(+,F,A,B,C,1,2,3) results in F(A,B,C,1) + F(A,B,C,2) + F(A,B,C,3) + */ +#define MAPD3(D, F, P0, P1, P2, ...) \ + __VA_OPT__(_MAP_EVAL256(_MAPD3_POP0(D, F, P0, P1, P2, __VA_ARGS__))) + +#endif \ No newline at end of file diff --git a/parts/esp32-components/lifesensor_common/include/macro/static_alloc.h b/parts/esp32-components/lifesensor_common/include/macro/static_alloc.h new file mode 100644 index 0000000..c9cde40 --- /dev/null +++ b/parts/esp32-components/lifesensor_common/include/macro/static_alloc.h @@ -0,0 +1,6 @@ +#ifndef LIFESENSOR_COMMON_STATIC_ALLOC_H +#define LIFESENSOR_COMMON_STATIC_ALLOC_H + +#define STATIC_ALLOC(TYPE, SIZE, ...) (TYPE[SIZE]){__VA_ARGS__} + +#endif \ No newline at end of file diff --git a/parts/esp32-components/lifesensor_common/include/module.h b/parts/esp32-components/lifesensor_common/include/module.h new file mode 100644 index 0000000..37590d8 --- /dev/null +++ b/parts/esp32-components/lifesensor_common/include/module.h @@ -0,0 +1,35 @@ +#ifndef LIFESENSOR_COMMON_MODULE_H +#define LIFESENSOR_COMMON_MODULE_H + +#include "logger.h" +#include "macro/map.h" +#include + +#define INIT_LIFESENSOR_MODULE(TYPE, NAME, INIT, DUMP, /* SUBMODULES */...) \ + { \ + .parent = NULL, \ + .type = #TYPE, \ + .name = (NAME), \ + .init = (INIT), \ + .dump = (DUMP), \ + .submodule_offsets = (ptrdiff_t[]){__VA_OPT__(MAP1(offsetof, TYPE, __VA_ARGS__), ) 0}, \ + } + +struct lifesensor_module +{ + struct lifesensor_module *parent; + char *name; + char *type; + void (*dump)( + struct lifesensor_module *module, + Lifesensor_logger *logger, + Lifesensor_logger_scope *scope); + void (*init)( + struct lifesensor_module *module); + ptrdiff_t *submodule_offsets; +}; +typedef struct lifesensor_module Lifesensor_module; + +void lifesensor_module_init(Lifesensor_module *module); +void lifesensor_module_dump(Lifesensor_module *module, Lifesensor_logger *logger, Lifesensor_logger_scope *scope); +#endif \ No newline at end of file diff --git a/parts/esp32-components/lifesensor_common/src/logger_json.c b/parts/esp32-components/lifesensor_common/src/logger_json.c new file mode 100644 index 0000000..96e6fbc --- /dev/null +++ b/parts/esp32-components/lifesensor_common/src/logger_json.c @@ -0,0 +1,164 @@ + +#include "logger.h" + +static inline void +_lifesensor_logger_json_comma( + Lifesensor_logger_scope *scope) +{ + if (scope->entries++) + { + scope->printf(", "); + } +} + +static inline void +_lifesensor_logger_json_name( + Lifesensor_logger_scope *scope, + char *name) +{ + if (name) + { + scope->printf("\"%s\": "); + } +} + +static void +lifesensor_logger_json_enter_map( + Lifesensor_logger_scope *scope, + Lifesensor_logger_scope *subscope, + char *name) +{ + if (scope) { + subscope->entries = 0; + subscope->level = scope->level + 1; + subscope->printf = scope->printf; + + _lifesensor_logger_json_comma(scope); + _lifesensor_logger_json_name(scope, name); + } + + subscope->type = LIFESENSOR_LOGGER_SCOPE_MAP; + subscope->printf("{ "); +} + +static void +lifesensor_logger_json_enter_list( + Lifesensor_logger_scope *scope, + Lifesensor_logger_scope *subscope, + char *name) +{ + if (scope) { + subscope->entries = 0; + subscope->level = scope->level + 1; + subscope->printf = scope->printf; + + _lifesensor_logger_json_comma(scope); + _lifesensor_logger_json_name(scope, name); + } + + subscope->type = LIFESENSOR_LOGGER_SCOPE_LIST; + subscope->printf("[ "); +} + +static void +lifesensor_logger_json_exit( + Lifesensor_logger_scope *scope, + Lifesensor_logger_scope *subscope) +{ + switch (subscope->type) + { + case LIFESENSOR_LOGGER_SCOPE_MAP: + subscope->printf(" }"); + break; + case LIFESENSOR_LOGGER_SCOPE_LIST: + subscope->printf(" ]"); + break; + } +} + +static void +lifesensor_logger_json_log_uint( + Lifesensor_logger_scope *scope, + unsigned long int value, + char *name) +{ + _lifesensor_logger_json_comma(scope); + _lifesensor_logger_json_name(scope, name); + scope->printf("%+lu", value); +} + +static void +lifesensor_logger_json_log_int( + Lifesensor_logger_scope *scope, + long int value, + char *name) +{ + _lifesensor_logger_json_comma(scope); + _lifesensor_logger_json_name(scope, name); + scope->printf("%+ld", value); +} + +static void +lifesensor_logger_json_log_float( + Lifesensor_logger_scope *scope, + float value, + char *name) +{ + _lifesensor_logger_json_comma(scope); + _lifesensor_logger_json_name(scope, name); + scope->printf("%+G", value); +} + +static void +lifesensor_logger_json_log_char( + Lifesensor_logger_scope *scope, + char value, + char *name) +{ + _lifesensor_logger_json_comma(scope); + _lifesensor_logger_json_name(scope, name); + scope->printf("\"%c\"", value); +} + +static void +lifesensor_logger_json_log_str( + Lifesensor_logger_scope *scope, + char *value, + char *name) +{ + _lifesensor_logger_json_comma(scope); + _lifesensor_logger_json_name(scope, name); + scope->printf("\"%s\"", value); +} + +static void +lifesensor_logger_json_log_ptr( + Lifesensor_logger_scope *scope, + void *value, + char *name) +{ + _lifesensor_logger_json_comma(scope); + _lifesensor_logger_json_name(scope, name); + scope->printf("\"0x%x\"", value); +} + +void lifesensor_logger_json_init( + Lifesensor_logger *logger, + Lifesensor_logger_scope *scope, + int (*printf)(const char *fmt, ...)) +{ + logger->enter_map = &lifesensor_logger_json_enter_map; + logger->enter_list = &lifesensor_logger_json_enter_list; + logger->exit = &lifesensor_logger_json_exit; + logger->log_uint = &lifesensor_logger_json_log_uint; + logger->log_int = &lifesensor_logger_json_log_int; + logger->log_float = &lifesensor_logger_json_log_float; + logger->log_char = &lifesensor_logger_json_log_char; + logger->log_str = &lifesensor_logger_json_log_str; + logger->log_ptr = &lifesensor_logger_json_log_ptr; + + scope->type = LIFESENSOR_LOGGER_SCOPE_NONE; + scope->entries = 0; + scope->level = 0; + scope->printf = printf; +} diff --git a/parts/esp32-components/lifesensor_common/src/logger_yaml.c b/parts/esp32-components/lifesensor_common/src/logger_yaml.c new file mode 100644 index 0000000..73fdd2d --- /dev/null +++ b/parts/esp32-components/lifesensor_common/src/logger_yaml.c @@ -0,0 +1,161 @@ + +#include "logger.h" + +static inline void +_lifesensor_logger_yaml_prefix( + Lifesensor_logger_scope *scope, + char *name) +{ + scope->printf("\n%*s", 4 * scope->level, ""); + scope->entries++; + switch (scope->type) + { + case LIFESENSOR_LOGGER_SCOPE_LIST: + scope->printf("- "); + break; + case LIFESENSOR_LOGGER_SCOPE_MAP: + if (name) + { + scope->printf("%s: ", name); + } + break; + } +} + +static void +lifesensor_logger_yaml_enter_map( + Lifesensor_logger_scope *scope, + Lifesensor_logger_scope *subscope, + char *name) +{ + subscope->type = LIFESENSOR_LOGGER_SCOPE_MAP; + + if (!scope) { + return; + } + + subscope->entries = 0; + subscope->level = scope->level + 1; + subscope->printf = scope->printf; + + _lifesensor_logger_yaml_prefix(scope, name); +} + +static void +lifesensor_logger_yaml_enter_list( + Lifesensor_logger_scope *scope, + Lifesensor_logger_scope *subscope, + char *name) +{ + subscope->type = LIFESENSOR_LOGGER_SCOPE_LIST; + + if (!scope) { + return; + } + + subscope->entries = 0; + subscope->level = scope->level + 1; + subscope->printf = scope->printf; + + _lifesensor_logger_yaml_prefix(scope, name); +} + +static void +lifesensor_logger_yaml_exit( + Lifesensor_logger_scope *scope, + Lifesensor_logger_scope *subscope) +{ + if (!subscope->entries) + { + switch (subscope->type) + { + case LIFESENSOR_LOGGER_SCOPE_MAP: + subscope->printf("{}"); + break; + case LIFESENSOR_LOGGER_SCOPE_LIST: + subscope->printf("[]"); + break; + } + } +} + +static void +lifesensor_logger_yaml_log_uint( + Lifesensor_logger_scope *scope, + unsigned long int value, + char *name) +{ + _lifesensor_logger_yaml_prefix(scope, name); + scope->printf("%+lu", value); +} + +static void +lifesensor_logger_yaml_log_int( + Lifesensor_logger_scope *scope, + long int value, + char *name) +{ + _lifesensor_logger_yaml_prefix(scope, name); + scope->printf("%+ld", value); +} + +static void +lifesensor_logger_yaml_log_float( + Lifesensor_logger_scope *scope, + float value, + char *name) +{ + _lifesensor_logger_yaml_prefix(scope, name); + scope->printf("%+G", value); +} + +static void +lifesensor_logger_yaml_log_char( + Lifesensor_logger_scope *scope, + char value, + char *name) +{ + _lifesensor_logger_yaml_prefix(scope, name); + scope->printf("\"%c\"", value); +} + +static void +lifesensor_logger_yaml_log_str( + Lifesensor_logger_scope *scope, + char *value, + char *name) +{ + _lifesensor_logger_yaml_prefix(scope, name); + scope->printf("\"%s\"", value); +} + +static void +lifesensor_logger_yaml_log_ptr( + Lifesensor_logger_scope *scope, + void *value, + char *name) +{ + _lifesensor_logger_yaml_prefix(scope, name); + scope->printf("0x%x", value); +} + +void lifesensor_logger_yaml_init( + Lifesensor_logger *logger, + Lifesensor_logger_scope *scope, + int (*printf)(const char *fmt, ...)) +{ + logger->enter_map = &lifesensor_logger_yaml_enter_map; + logger->enter_list = &lifesensor_logger_yaml_enter_list; + logger->exit = &lifesensor_logger_yaml_exit; + logger->log_uint = &lifesensor_logger_yaml_log_uint; + logger->log_int = &lifesensor_logger_yaml_log_int; + logger->log_float = &lifesensor_logger_yaml_log_float; + logger->log_char = &lifesensor_logger_yaml_log_char; + logger->log_str = &lifesensor_logger_yaml_log_str; + logger->log_ptr = &lifesensor_logger_yaml_log_ptr; + + scope->type = LIFESENSOR_LOGGER_SCOPE_NONE; + scope->entries = 0; + scope->level = 0; + scope->printf = printf; +} diff --git a/parts/esp32-components/lifesensor_common/src/module.c b/parts/esp32-components/lifesensor_common/src/module.c new file mode 100644 index 0000000..de19382 --- /dev/null +++ b/parts/esp32-components/lifesensor_common/src/module.c @@ -0,0 +1,57 @@ + +#include "module.h" +#include "logger.h" + +void lifesensor_module_init(Lifesensor_module *module) +{ + for (ptrdiff_t *offset = module->submodule_offsets; *offset; offset++) + { + Lifesensor_module *submodule = ((void *)module) + *offset; + submodule->parent = module; + if (submodule->init) + { + submodule->init(submodule); + } + } + if (module->init) + { + module->init(module); + } +} + +void lifesensor_module_dump(Lifesensor_module *module, Lifesensor_logger *logger, Lifesensor_logger_scope *scope) +{ + Lifesensor_logger_scope subscope; + Lifesensor_logger_scope subsubscope; + + logger->log_ptr(scope, module, "address"); + logger->log_ptr(scope, module->parent, "parent"); + logger->log_str(scope, module->type, "type"); + logger->log_str(scope, module->name, "name"); + logger->log_ptr(scope, module->init, "init"); + logger->log_ptr(scope, module->dump, "dump"); + + logger->enter_list(scope, &subscope, "submodule_offsets"); + for (ptrdiff_t *offset = module->submodule_offsets; *offset; offset++) + { + logger->log_int(&subscope, *offset, NULL); + } + logger->exit(scope, &subscope); + + logger->enter_map(scope, &subscope, "locals"); + if (module->dump) + { + module->dump(module, logger, &subscope); + } + logger->exit(scope, &subscope); + + logger->enter_map(scope, &subscope, "submodules"); + for (ptrdiff_t *offset = module->submodule_offsets; *offset; offset++) + { + Lifesensor_module *submodule = ((void *)module) + *offset; + logger->enter_map(&subscope, &subsubscope, submodule->name); + lifesensor_module_dump(submodule, logger, &subsubscope); + logger->exit(&subscope, &subsubscope); + } + logger->exit(scope, &subscope); +}