From 21b59266ed521f0bc2cc14a446eb81a84a18cb4e Mon Sep 17 00:00:00 2001 From: Anders Blomdell <anders.blomdell@control.lth.se> Date: Thu, 28 Feb 2019 18:02:40 +0100 Subject: [PATCH] AnalogIn works for comedi --- Makefile | 1 + moberg.c | 83 ++++++++++----- moberg.h | 58 ++++++----- moberg_channel.h | 25 +---- moberg_device.c | 5 +- moberg_device.h | 6 +- moberg_module.h | 1 + moberg_parser.c | 20 ++-- moberg_parser.h | 4 +- modules/comedi/Makefile | 3 +- modules/comedi/comedi.c | 164 ++++++++++++++++++++++++++---- modules/serial2002/serial2002.c | 11 +- test/.config/moberg.d/moberg.conf | 11 ++ test/Makefile | 22 ++-- test/test_io.c | 14 +++ 15 files changed, 312 insertions(+), 116 deletions(-) create mode 100644 test/test_io.c diff --git a/Makefile b/Makefile index c0c47ed..1eab439 100644 --- a/Makefile +++ b/Makefile @@ -44,6 +44,7 @@ test: all clean: find build -type f -delete rm -f *~ + make -C test clean build/libmoberg.so: build/lib/moberg_config.o build/libmoberg.so: build/lib/moberg_device.o diff --git a/moberg.c b/moberg.c index ad8de2e..32437cd 100644 --- a/moberg.c +++ b/moberg.c @@ -13,6 +13,7 @@ #include <moberg.h> #include <moberg_config.h> #include <moberg_parser.h> +#include <moberg_module.h> struct moberg { struct moberg_config *config; @@ -20,19 +21,19 @@ struct moberg { int capacity; struct moberg_channel **value; } analog_in, analog_out, digital_in, digital_out, encoder_in; -}; + struct deferred_action { + struct deferred_action *next; + int (*action)(void *param); + void *param; + } *deferred_action; -static struct deferred_action { - struct deferred_action *next; - int (*action)(void *param); - void *param; -} *deferred_action = NULL; +}; -static void run_deferred_actions() +static void run_deferred_actions(struct moberg *moberg) { - while (deferred_action) { - struct deferred_action *deferred = deferred_action; - deferred_action = deferred_action->next; + while (moberg->deferred_action) { + struct deferred_action *deferred = moberg->deferred_action; + moberg->deferred_action = deferred->next; deferred->action(deferred->param); free(deferred); } @@ -100,7 +101,7 @@ static void parse_config_at( buf[statbuf.st_size] = 0; } printf("Parsing... %s %d %d\n", pathname, dirfd, fd); - struct moberg_config *config = moberg_parse(buf); + struct moberg_config *config = moberg_parse(moberg, buf); printf("-> %p\n", config); if (config) { if (! moberg->config) { @@ -236,6 +237,7 @@ struct moberg *moberg_new( result->digital_out.value = NULL; result->encoder_in.capacity = 0; result->encoder_in.value = NULL; + result->deferred_action = NULL; if (config) { result->config = config; } else { @@ -263,7 +265,7 @@ struct moberg *moberg_new( /* Parse environment overrides */ } install_config(result); - run_deferred_actions(); + run_deferred_actions(result); err: return result; @@ -278,37 +280,72 @@ void moberg_free(struct moberg *moberg) channel_list_free(&moberg->digital_in); channel_list_free(&moberg->digital_out); channel_list_free(&moberg->encoder_in); + run_deferred_actions(moberg); free(moberg); } - run_deferred_actions(); } -enum moberg_status moberg_start( +/* Input/output */ + +int moberg_analog_in_open(struct moberg *moberg, + int index, + struct moberg_analog_in *analog_in) +{ + struct moberg_channel *channel = NULL; + channel_list_get(&moberg->analog_in, index, &channel); + if (channel) { + printf("Call open\n"); + channel->open(channel); + channel->up(channel); + *analog_in = channel->action.analog_in; + return 1; + } + return 0; +} + +int moberg_analog_in_close(struct moberg *moberg, + int index, + struct moberg_analog_in analog_in) +{ + struct moberg_channel *channel = NULL; + channel_list_get(&moberg->analog_in, index, &channel); + if (channel && channel->action.analog_in.context == analog_in.context) { + printf("Call close\n"); + channel->close(channel); + channel->down(channel); + } + return 1; +} + + + +/* System init functionality (systemd/init/...) */ + +int moberg_start( struct moberg *moberg, FILE *f) { return moberg_config_start(moberg->config, f); - return moberg_OK; } - -enum moberg_status moberg_stop( +int moberg_stop( struct moberg *moberg, FILE *f) { return moberg_config_stop(moberg->config, f); - return moberg_OK; } -void moberg_deferred_action( - int (*action)(void *param), - void *param) +/* Intended for final cleanup actions (dlclose so far...) */ + +void moberg_deferred_action(struct moberg *moberg, + int (*action)(void *param), + void *param) { struct deferred_action *deferred = malloc(sizeof(*deferred)); if (deferred) { - deferred->next = deferred_action; + deferred->next = moberg->deferred_action; deferred->action = action; deferred->param = param; - deferred_action = deferred; + moberg->deferred_action = deferred; } } diff --git a/moberg.h b/moberg.h index 23e49c4..062892a 100644 --- a/moberg.h +++ b/moberg.h @@ -6,46 +6,54 @@ struct moberg; struct moberg_config; -enum moberg_status { moberg_OK }; +/* Creation & free */ struct moberg *moberg_new(struct moberg_config *config); void moberg_free(struct moberg *moberg); -/* Input/output functions */ +/* Input/output */ -enum moberg_status moberg_analog_in( - double *value, - struct moberg *moberg, - int channel); +struct moberg_analog_in { + struct moberg_channel_analog_in *context; + int (*read)(struct moberg_channel_analog_in *, double *value); +}; -enum moberg_status moberg_analog_out( - double value, - struct moberg *moberg, - int channel); +struct moberg_analog_out { + struct moberg_channel_analog_out *context; + int (*write)(struct moberg_channel_analog_in *, double value); +}; -enum moberg_status moberg_digital_in( - int *value, - struct moberg *moberg, - int channel); +struct moberg_digital_in { + struct moberg_channel_digital_in *context; + int (*read)(struct moberg_channel_digital_in *, int *value); +}; -enum moberg_status moberg_digital_out( - int value, - struct moberg *moberg, - int channel); +struct moberg_digital_out { + struct moberg_channel_digital_out *context; + int (*write)(struct moberg_channel_digital_out *, int value); +}; -enum moberg_status moberg_encoder_in( - long *value, - struct moberg *moberg, - int channel); +struct moberg_encoder_in { + struct moberg_channel_encoder_in *context; + int (*read)(struct moberg_channel_encoder_in *, long *value); +}; + +int moberg_analog_in_open(struct moberg *moberg, + int index, + struct moberg_analog_in *analog_in); + +int moberg_analog_in_close(struct moberg *moberg, + int index, + struct moberg_analog_in analog_in); -/* Install functionality */ +/* System init functionality (systemd/init/...) */ -enum moberg_status moberg_start( +int moberg_start( struct moberg *moberg, FILE *f); -enum moberg_status moberg_stop( +int moberg_stop( struct moberg *moberg, FILE *f); diff --git a/moberg_channel.h b/moberg_channel.h index 2045b21..dab6355 100644 --- a/moberg_channel.h +++ b/moberg_channel.h @@ -25,26 +25,11 @@ struct moberg_channel { /* I/O operations */ enum moberg_channel_kind kind; union moberg_channel_action { - struct { - struct moberg_channel_analog_in *context; - int (*read)(struct moberg_channel_analog_in *, double *value); - } analog_in; - struct { - struct moberg_channel_analog_out *context; - int (*write)(struct moberg_channel_context *, double value); - } analog_out; - struct { - struct moberg_channel_digital_in *context; - int (*read)(struct moberg_channel_context *, int *value); - } digital_in; - struct { - struct moberg_channel_digital_out *context; - int (*write)(struct moberg_channel_context *, int value); - } digital_out; - struct { - struct moberg_channel_encoder_in *context; - int (*read)(struct moberg_channel_context *, long *value); - } encoder_in; + struct moberg_analog_in analog_in; + struct moberg_analog_out analog_out; + struct moberg_digital_in digital_in; + struct moberg_digital_out digital_out; + struct moberg_encoder_in encoder_in; } action; }; diff --git a/moberg_device.c b/moberg_device.c index 638179b..e4f339b 100644 --- a/moberg_device.c +++ b/moberg_device.c @@ -30,7 +30,8 @@ struct moberg_device { } *range; }; -struct moberg_device *moberg_device_new(const char *driver) +struct moberg_device *moberg_device_new(struct moberg *moberg, + const char *driver) { struct moberg_device *result = NULL; @@ -54,7 +55,7 @@ struct moberg_device *moberg_device_new(const char *driver) goto dlclose_driver; } result->driver = *device_driver; - result->device_context = result->driver.new(dlclose, handle); + result->device_context = result->driver.new(moberg, dlclose, handle); if (result->device_context) { result->driver.up(result->device_context); } else { diff --git a/moberg_device.h b/moberg_device.h index 0acafb2..449e243 100644 --- a/moberg_device.h +++ b/moberg_device.h @@ -13,7 +13,8 @@ struct moberg_parser_context; struct moberg_device_driver { /* Create new device context */ - struct moberg_device_context *(*new)(int (*dlclose)(void *dlhandle), + struct moberg_device_context *(*new)(struct moberg *moberg, + int (*dlclose)(void *dlhandle), void *dlhandle); /* Use-count of device, when it reaches zero, device will be free'd */ int (*up)(struct moberg_device_context *context); @@ -41,7 +42,8 @@ struct moberg_device_driver { struct moberg_device; -struct moberg_device *moberg_device_new(const char *driver); +struct moberg_device *moberg_device_new(struct moberg *moberg, + const char *driver); void moberg_device_free(struct moberg_device *device); diff --git a/moberg_module.h b/moberg_module.h index 434f2c3..e3bf1b1 100644 --- a/moberg_module.h +++ b/moberg_module.h @@ -55,6 +55,7 @@ void moberg_parser_failed( FILE *f); void moberg_deferred_action( + struct moberg *moberg, int (*action)(void *param), void *param); diff --git a/moberg_parser.c b/moberg_parser.c index ac7bdbd..9ce96cc 100644 --- a/moberg_parser.c +++ b/moberg_parser.c @@ -361,7 +361,8 @@ err: return 0; } -static int parse(context_t *c) +static int parse(struct moberg *moberg, + context_t *c) { for (;;) { if (acceptsym(c, tok_EOF, NULL)) { @@ -381,18 +382,20 @@ static int parse(context_t *c) t.u.idstr.length, t.u.idstr.value); goto err; } - device = moberg_device_new(name); + device = moberg_device_new(moberg, name); free(name); if (! device) { goto err; } if (! parse_device(c, device)) { - moberg_device_free(device); - goto err; + goto device_free; } if (! moberg_config_add_device(c->config, device)) { - moberg_device_free(device); - goto err; + goto device_free; } + continue; + device_free: + moberg_device_free(device); + goto err; } } return 1; @@ -403,7 +406,8 @@ err: return 0; } -struct moberg_config *moberg_parse(const char *buf) +struct moberg_config *moberg_parse(struct moberg *moberg, + const char *buf) { context_t context; @@ -413,7 +417,7 @@ struct moberg_config *moberg_parse(const char *buf) context.buf = buf; context.p = context.buf; nextsym(&context); - if (! parse(&context)) { + if (! parse(moberg, &context)) { moberg_config_free(context.config); context.config = NULL; } diff --git a/moberg_parser.h b/moberg_parser.h index 6bfcebe..575f845 100644 --- a/moberg_parser.h +++ b/moberg_parser.h @@ -1,10 +1,12 @@ #ifndef __MOBERG_PARSER_H__ #define __MOBERG_PARSER_H__ +#include <moberg.h> #include <moberg_config.h> struct moberg_parser_context; -struct moberg_config *moberg_parse(const char *buf); +struct moberg_config *moberg_parse(struct moberg* moberg, + const char *buf); #endif diff --git a/modules/comedi/Makefile b/modules/comedi/Makefile index ca1e0e2..543d534 100644 --- a/modules/comedi/Makefile +++ b/modules/comedi/Makefile @@ -1,11 +1,10 @@ LIBRARIES=libmoberg_comedi.so CCFLAGS+=-Wall -Werror -I../.. -g -LDFLAGS+=-Lbuild/ -lmoberg MODULES=$(wildcard modules/*) all: $(LIBRARIES:%=../../build/%) ../../build/libmoberg_comedi.so: comedi.c Makefile - $(CC) -o $@ $(CCFLAGS) -shared -fPIC $< + $(CC) -o $@ $(CCFLAGS) -shared -fPIC -lcomedi $< ../../build/libmoberg_comedi.so: ../../moberg_module.h diff --git a/modules/comedi/comedi.c b/modules/comedi/comedi.c index d0ddead..32853eb 100644 --- a/modules/comedi/comedi.c +++ b/modules/comedi/comedi.c @@ -5,6 +5,7 @@ #include <stdio.h> #include <stdlib.h> #include <string.h> +#include <comedilib.h> typedef enum moberg_parser_token_kind kind_t; typedef struct moberg_parser_token token_t; @@ -12,10 +13,15 @@ typedef struct moberg_parser_ident ident_t; typedef struct moberg_parser_context context_t; struct moberg_device_context { + struct moberg *moberg; int (*dlclose)(void *dlhandle); void *dlhandle; int use_count; char *name; + struct { + int count; + comedi_t *handle; + } comedi; struct idstr { struct idstr *next; struct idstr *prev; @@ -28,6 +34,15 @@ struct moberg_channel_context { void *to_free; /* To be free'd when use_count goes to zero */ struct moberg_device_context *device; int use_count; + struct channel_descriptor { + int subdevice; + int subchannel; + int route; + lsampl_t maxdata; + double min; + double max; + double delta; + } descriptor; }; struct moberg_channel_analog_in { @@ -55,13 +70,29 @@ struct moberg_channel_encoder_in { struct moberg_channel_context channel_context; }; -static struct moberg_device_context *new_context( - int (*dlclose)(void *dlhandle), - void *dlhandle) +static int analog_in_read(struct moberg_channel_analog_in *analog_in, + double *value) +{ + struct channel_descriptor descriptor = analog_in->channel_context.descriptor; + lsampl_t data; + comedi_data_read(analog_in->channel_context.device->comedi.handle, + descriptor.subdevice, + descriptor.subchannel, + 0, 0, &data); + + *value = descriptor.min + data * descriptor.delta; + fprintf(stderr, "Data: %d %f\n", data, *value); + return 1; +} + +static struct moberg_device_context *new_context(struct moberg *moberg, + int (*dlclose)(void *dlhandle), + void *dlhandle) { struct moberg_device_context *result = malloc(sizeof(*result)); if (result) { memset(result, 0, sizeof(*result)); + result->moberg = moberg; result->dlclose = dlclose; result->dlhandle = dlhandle; result->modprobe_list.next = &result->modprobe_list; @@ -72,41 +103,67 @@ static struct moberg_device_context *new_context( return result; } -static int device_up(struct moberg_device_context *context) +static int device_up(struct moberg_device_context *device) { - context->use_count++; - return context->use_count; + device->use_count++; + return device->use_count; } -static int device_down(struct moberg_device_context *context) +static int device_down(struct moberg_device_context *device) { - context->use_count--; - int result = context->use_count; - if (context->use_count <= 0) { - moberg_deferred_action(context->dlclose, context->dlhandle); - free(context->name); + device->use_count--; + int result = device->use_count; + if (device->use_count <= 0) { + moberg_deferred_action(device->moberg, + device->dlclose, device->dlhandle); + free(device->name); struct idstr *e; - e = context->modprobe_list.next; - while (e != &context->modprobe_list) { + e = device->modprobe_list.next; + while (e != &device->modprobe_list) { struct idstr *next = e->next; free(e->value); free(e); e = next; } - e = context->config_list.next; - while (e != &context->config_list) { + e = device->config_list.next; + while (e != &device->config_list) { struct idstr *next = e->next; free(e->value); free(e); e = next; } - free(context); + free(device); return 0; } return result; } +static int device_open(struct moberg_device_context *device) +{ + if (device->comedi.count == 0) { + device->comedi.handle = comedi_open(device->name); + if (device->comedi.handle == NULL) { + goto err; + } + } + device->comedi.count++; + return 1; +err: + fprintf(stderr, "Failed to open %s\n", device->name); + return 0; +} + +static int device_close(struct moberg_device_context *device) +{ + device->comedi.count--; + if (device->comedi.count == 0) { + comedi_close(device->comedi.handle); + } + + return 1; +} + static int channel_up(struct moberg_channel *channel) { device_up(channel->context->device); @@ -125,22 +182,73 @@ static int channel_down(struct moberg_channel *channel) return channel->context->use_count; } +static int channel_open(struct moberg_channel *channel) +{ + channel_up(channel); + if (! device_open(channel->context->device)) { goto err; } + fprintf(stderr, "Open %s[%d][%d]\n", + channel->context->device->name, + channel->context->descriptor.subdevice, + channel->context->descriptor.subchannel); + lsampl_t maxdata; + comedi_range *range; + + maxdata = comedi_get_maxdata(channel->context->device->comedi.handle, + channel->context->descriptor.subdevice, + channel->context->descriptor.subchannel); + range = comedi_get_range(channel->context->device->comedi.handle, + channel->context->descriptor.subdevice, + channel->context->descriptor.subchannel, + 0); + + if (! maxdata) { + fprintf(stderr, "Failed to get maxdata for %s[%d][%d]\n", + channel->context->device->name, + channel->context->descriptor.subdevice, + channel->context->descriptor.subchannel); + goto err; + } + if (! range) { + fprintf(stderr, "Failed to get range for %s[%d][%d]\n", + channel->context->device->name, + channel->context->descriptor.subdevice, + channel->context->descriptor.subchannel); + goto err; + } + channel->context->descriptor.maxdata = maxdata; + channel->context->descriptor.min = range->min; + channel->context->descriptor.max = range->max; + channel->context->descriptor.delta = (range->max - range->min) / maxdata; + return 1; +err: + return 0; +} + +static int channel_close(struct moberg_channel *channel) +{ + device_close(channel->context->device); + channel_down(channel); + return 1; +} + static void init_channel(struct moberg_channel *channel, void *to_free, struct moberg_channel_context *context, struct moberg_device_context *device, + struct channel_descriptor descriptor, enum moberg_channel_kind kind, union moberg_channel_action action) { context->to_free = to_free; context->device = device; context->use_count = 0; + context->descriptor = descriptor; channel->context = context; channel->up = channel_up; channel->down = channel_down; - channel->open = NULL; - channel->close = NULL; + channel->open = channel_open; + channel->close = channel_close; channel->kind = kind; channel->action = action; }; @@ -252,7 +360,7 @@ static int parse_map(struct moberg_device_context *device, enum moberg_channel_kind kind, struct moberg_channel_map *map) { - token_t min, max; + token_t min, max, route={ .u.integer.value=0 }; if (! acceptsym(c, tok_LBRACE, NULL)) { goto err; } for (;;) { @@ -262,7 +370,6 @@ static int parse_map(struct moberg_device_context *device, if (! acceptsym(c, tok_INTEGER, &subdevice)) { goto err; } if (! acceptsym(c, tok_RBRACKET, NULL)) { goto err; } if (acceptkeyword(c, "route")) { - token_t route; if (! acceptsym(c, tok_INTEGER, &route)) { goto err; } } if (! acceptsym(c, tok_LBRACKET, NULL)) { goto err; } @@ -273,6 +380,14 @@ static int parse_map(struct moberg_device_context *device, max = min; } for (int i = min.u.integer.value ; i <= max.u.integer.value ; i++) { + struct channel_descriptor descriptor = { + .subdevice=subdevice.u.integer.value, + .subchannel=i, + .route=route.u.integer.value, + .maxdata=0, + .min=0.0, + .max=0.0 + }; switch (kind) { case chan_ANALOGIN: { struct moberg_channel_analog_in *channel = @@ -283,10 +398,11 @@ static int parse_map(struct moberg_device_context *device, channel, &channel->channel_context, device, + descriptor, kind, (union moberg_channel_action) { .analog_in.context=channel, - .analog_in.read=NULL }); + .analog_in.read=analog_in_read }); map->map(map->device, &channel->channel); } } break; @@ -299,6 +415,7 @@ static int parse_map(struct moberg_device_context *device, channel, &channel->channel_context, device, + descriptor, kind, (union moberg_channel_action) { .analog_out.context=channel, @@ -315,6 +432,7 @@ static int parse_map(struct moberg_device_context *device, channel, &channel->channel_context, device, + descriptor, kind, (union moberg_channel_action) { .digital_in.context=channel, @@ -331,6 +449,7 @@ static int parse_map(struct moberg_device_context *device, channel, &channel->channel_context, device, + descriptor, kind, (union moberg_channel_action) { .digital_out.context=channel, @@ -347,6 +466,7 @@ static int parse_map(struct moberg_device_context *device, channel, &channel->channel_context, device, + descriptor, kind, (union moberg_channel_action) { .encoder_in.context=channel, diff --git a/modules/serial2002/serial2002.c b/modules/serial2002/serial2002.c index 8b9404e..5674770 100644 --- a/modules/serial2002/serial2002.c +++ b/modules/serial2002/serial2002.c @@ -11,6 +11,7 @@ typedef struct moberg_parser_token token_t; typedef struct moberg_parser_context context_t; struct moberg_device_context { + struct moberg *moberg; int (*dlclose)(void *dlhandle); void *dlhandle; int use_count; @@ -49,13 +50,14 @@ struct moberg_channel_encoder_in { struct moberg_channel_context channel_context; }; -static struct moberg_device_context *new_context( - int (*dlclose)(void *dlhandle), - void *dlhandle) +static struct moberg_device_context *new_context(struct moberg *moberg, + int (*dlclose)(void *dlhandle), + void *dlhandle) { struct moberg_device_context *result = malloc(sizeof(*result)); if (result) { memset(result, 0, sizeof(*result)); + result->moberg = moberg; result->dlclose = dlclose; result->dlhandle = dlhandle; } @@ -72,7 +74,8 @@ static int device_down(struct moberg_device_context *context) { context->use_count--; if (context->use_count <= 0) { - moberg_deferred_action(context->dlclose, context->dlhandle); + moberg_deferred_action(context->moberg, + context->dlclose, context->dlhandle); free(context->name); free(context); return 0; diff --git a/test/.config/moberg.d/moberg.conf b/test/.config/moberg.d/moberg.conf index 8db1afc..9332da8 100644 --- a/test/.config/moberg.d/moberg.conf +++ b/test/.config/moberg.d/moberg.conf @@ -23,6 +23,17 @@ driver(comedi) { subdevice[7] route 16 [13], subdevice[7] route 16 [12] }; } +driver(comedi) { + config { + /* Parsed by parse_config in libmoberg_comedi.so */ + device = "/dev/comedi0" ; + modprobe = [ comedi serial2002 ] ; + config = [ serial2002 "100" "115200" ] ; + } + /* Moberg mapping[indices] = {driver specific}[indices] + {driver specific} is parsed by parse_map in libmoberg_comedi.so */ + map analog_in[0:1] = { subdevice[2][0:1] }; +} driver(serial2002) { config { /* Parsed by parse_config in libmoberg_serial2002.so */ diff --git a/test/Makefile b/test/Makefile index c23d259..1515606 100644 --- a/test/Makefile +++ b/test/Makefile @@ -1,15 +1,23 @@ -TEST = test_start_stop +TEST = test_start_stop test_io + +ENV_TEST = LD_LIBRARY_PATH=../build HOME=. -ENV_test_start_stop = LD_LIBRARY_PATH=../build HOME=. all: .PHONY: test test: $(TEST:%=run_%) - echo Testing - exit 1 + echo Tests run + +run_%: build/% + $(ENV_TEST) valgrind --leak-check=full ./build/$* -run_%: % - $(ENV_$*) valgrind --leak-check=full ./$* +.PRECIOUS: build/% -%: %.c +build/%: %.c | build $(CC) $(CCFLAGS) $(LDFLAGS) -o $@ $< + +build: + mkdir build + +clean: + rm -f vgcore.* *~ diff --git a/test/test_io.c b/test/test_io.c new file mode 100644 index 0000000..4e0def3 --- /dev/null +++ b/test/test_io.c @@ -0,0 +1,14 @@ +#include <stdio.h> +#include <moberg.h> + +int main(int argc, char *argv[]) +{ + struct moberg *moberg = moberg_new(NULL); + struct moberg_analog_in ai0; + double ai0_value; + moberg_analog_in_open(moberg, 0, &ai0); + ai0.read(ai0.context, &ai0_value); + fprintf(stderr, "READ ai0: %f\n", ai0_value); + moberg_analog_in_close(moberg, 0, ai0); + moberg_free(moberg); +} -- GitLab