diff --git a/modbus_params/Readme b/modbus_params/Readme index b54898c..54076c2 100644 --- a/modbus_params/Readme +++ b/modbus_params/Readme @@ -1,10 +1,14 @@ modbus_par: reading/writing/dumping modbus registers using `dictionary` + -D, --dictionary=arg file with dictionary (format: code register value writeable) + -N, --node=arg node "IP", or path (could be "\0path" for anonymous UNIX-socket) -O, --outdic=arg output dictionary for full device dump by input dictionary registers -R, --readc registers (by keycodes, checked by dictionary) to read; multiply parameter + -U, --unixsock UNIX socket instead of INET -W, --writec write new value to register by keycode (format: keycode=val); multiply parameter + -a, --alias=arg file with aliases in format 'name : command to run' -b, --baudrate=arg modbus baudrate (default: 9600) -d, --device=arg modbus device (default: /dev/ttyUSB0) -h, --help show this help @@ -15,4 +19,3 @@ modbus_par: reading/writing/dumping modbus registers using `dictionary` -t, --dumptime=arg dumping time interval (seconds, default: 0.1) -v, --verbose verbose level (each -v adds 1) -w, --writer write new value to register (format: reg=val); multiply parameter - diff --git a/modbus_params/dictionary.c b/modbus_params/dictionary.c new file mode 100644 index 0000000..464d704 --- /dev/null +++ b/modbus_params/dictionary.c @@ -0,0 +1,510 @@ +/* + * This file is part of the modbus_param project. + * Copyright 2025 Edward V. Emelianov . + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/***************************************************************************** + * WARNING!!!! * + * Be carefull: do not call funtions working with dictionary from several * + * threads if you're planning to open/close dictionaries "on-the-fly" * + *****************************************************************************/ + +#include +#include +#include +#include +#include +#include + +#include "dictionary.h" +#include "modbus.h" +#include "verbose.h" + +// main dictionary and arrays of pointers: sorted by code and by register value +static dicentry_t *dictionary = NULL, **dictbycode = NULL, **dictbyreg = NULL; +// size of opened dictionary +static size_t dictsize = 0; +size_t get_dictsize(){ return dictsize; } + +/* dump */ +static FILE *dumpfile = NULL; // file for dump output +static char *dumpname = NULL; // it's name +static dicentry_t *dumppars = NULL; // array with parameters to dump +static int dumpsize = 0; // it's size +static double dumpTime = 0.1; // period to dump +static atomic_int stopdump = FALSE, isstopped = TRUE; // flags + +/* aliases */ +// list of aliases sorted by name +static alias_t *aliases = NULL; +static size_t aliasessize = 0; +size_t get_aliasessize(){ return aliasessize; } + +// functions for `qsort` (`needle` and `straw` are pointers to pointers) +static int sort_by_code(const void *needle, const void *straw){ + const char *c1 = (*(dicentry_t**)needle)->code, *c2 = (*(dicentry_t**)straw)->code; + return strcmp(c1, c2); +} +static int sort_by_reg(const void *needle, const void *straw){ + const int r1 = (int)(*(dicentry_t**)needle)->reg, r2 = (int)(*(dicentry_t**)straw)->reg; + return r1 - r2; +} +// functions for `binsearch` +static int search_by_code(const void *code, const void *straw){ + const char *c1 = (const char*)code, *c2 = ((dicentry_t*)straw)->code; + return strcmp(c1, c2); +} +static int search_by_reg(const void *reg, const void *straw){ + const int r1 = (int)(*(uint16_t*)reg), r2 = (int)((dicentry_t*)straw)->reg; + return r1 - r2; +} + +// find comment in `str`; substitute '#' in `str` by 0; remove '\n' from comment +// @return pointer to comment start in `str` or NULL if absent +static char *getcomment(char *str){ + char *comment = strchr(str, '#'); + if(comment){ + *comment++ = 0; + while(isspace(*comment)) ++comment; + if(*comment && *comment != '\n'){ + char *nl = strchr(comment, '\n'); + if(nl) *nl = 0; + return comment; + } + } + return NULL; +} + +// open dictionary file and check it; return TRUE if all OK +// all after "#" is comment; +// dictionary format: "'code' 'register' 'value' 'readonly flag'\n", e.g. +// "F00.09 61444 5000 1" +int opendict(const char *dic){ + closedict(); // close early opened dictionary to prevent problems + FILE *f = fopen(dic, "r"); + if(!f){ + WARN("Can't open %s", dic); + return FALSE; + } + size_t dicsz = 0; + size_t linesz = BUFSIZ; + char *line = MALLOC(char, linesz); + dicentry_t curentry; + curentry.code = MALLOC(char, BUFSIZ); + // `help` field of `curentry` isn't used here + int retcode = TRUE; + while(1){ + if(getline(&line, &linesz, f) < 0) break; + // DBG("Original LINE: '%s'", line); + char *comment = getcomment(line); + char *newline = strchr(line, '\n'); + if(newline) *newline = 0; + // DBG("LINE: '%s'", line); + if(*line == 0) continue; + if(4 != sscanf(line, "%s %" SCNu16 " %" SCNu16 " %" SCNu8, curentry.code, &curentry.reg, &curentry.value, &curentry.readonly)){ + WARNX("Can't understand this line: '%s'", line); + continue; + } + // DBG("Got line: '%s %" PRIu16 " %" PRIu16 " %" PRIu8, curentry.code, curentry.reg, curentry.value, curentry.readonly); + if(++dictsize >= dicsz){ + dicsz += 50; + dictionary = realloc(dictionary, sizeof(dicentry_t) * dicsz); + if(!dictionary){ + WARN("Can't allocate memory for dictionary"); + retcode = FALSE; + goto ret; + } + } + dicentry_t *entry = &dictionary[dictsize-1]; + entry->code = strdup(curentry.code); + entry->reg = curentry.reg; + entry->value = curentry.value; + entry->readonly = curentry.readonly; + if(comment) entry->help = strdup(comment); + else entry->help = NULL; + //DBG("Add entry; now dictsize is %d", dictsize); + } + // init dictionaries for sort + dictbycode = MALLOC(dicentry_t*, dictsize); + dictbyreg = MALLOC(dicentry_t*, dictsize); + for(size_t i = 0; i < dictsize; ++i) + dictbyreg[i] = dictbycode[i] = &dictionary[i]; + qsort(dictbycode, dictsize, sizeof(dicentry_t*), sort_by_code); + qsort(dictbyreg, dictsize, sizeof(dicentry_t*), sort_by_reg); +ret: + fclose(f); + FREE(curentry.code); + FREE(line); + return retcode; +} + +/** + * @brief chkdict - check if dictionary is opened + * @return TRUE if dictionary opened + */ +int chkdict(){ + if(dictsize < 1){ + WARNX("Init dictionary first"); + return FALSE; + } + return TRUE; +} + +void closedict(){ + if(!dictsize) return; + FREE(dictbycode); + FREE(dictbyreg); + for(size_t i = 0; i < dictsize; ++i){ + FREE(dictionary[i].code); + FREE(dictionary[i].help); + } + FREE(dictionary); + dictsize = 0; +} + +dicentry_t *binsearch(dicentry_t **base, const void* needle, int(*compar)(const void*, const void*)){ + size_t low = 0, high = dictsize; + //DBG("search"); + while(low < high){ + size_t mid = (high + low) / 2; + //DBG("low=%zd, high=%zd, mid=%zd", low, high, mid); + int cmp = compar(needle, base[mid]); + if(cmp < 0){ + high = mid; + }else if(cmp > 0){ + low = mid + 1; + }else{ + return base[mid]; + } + } + //DBG("not found"); + return NULL; +} + +// find dictionary entry +dicentry_t *findentry_by_code(const char *code){ + if(!chkdict()) return NULL; + return binsearch(dictbycode, (const void*)code, search_by_code); +} +dicentry_t *findentry_by_reg(uint16_t reg){ + if(!chkdict()) return NULL; + return binsearch(dictbyreg, (const void*)(®), search_by_reg); + return NULL; +} + +/** dump functions **/ + +// prepare a list with dump parameters (new call will rewrite previous list) +int setdumppars(char **pars){ + if(!pars || !*pars) return FALSE; + if(!chkdict()) return FALSE; + FNAME(); + char **p = pars; + static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; + static int cursz = -1; // current allocated size + int N = 0; + pthread_mutex_lock(&mutex); + while(*p){ // count parameters and check them + dicentry_t *e = findentry_by_code(*p); + if(!e){ + WARNX("Can't find entry with code %s", *p); + pthread_mutex_unlock(&mutex); + return FALSE; + } + DBG("found entry do dump, reg=%d", e->reg); + if(cursz <= N){ + cursz += 50; + DBG("realloc list to %d", cursz); + dumppars = realloc(dumppars, sizeof(dicentry_t) * (cursz)); + DBG("zero mem"); + bzero(&dumppars[N], sizeof(dicentry_t)*(cursz-N)); + } + FREE(dumppars[N].code); + dumppars[N] = *e; + dumppars[N].code = strdup(e->code); + DBG("Add %s", e->code); + ++N; ++p; + } + dumpsize = N; + pthread_mutex_unlock(&mutex); + return TRUE; +} + +// open dump file and add header; return FALSE if failed +int opendumpfile(const char *name){ + if(!chkdict()) return FALSE; + if(dumpsize < 1){ + WARNX("Set dump parameters first"); + return FALSE; + } + if(!name) return FALSE; + closedumpfile(); + dumpfile = fopen(name, "w+"); + if(!dumpfile){ + WARN("Can't open %s", name); + return FALSE; + } + dumpname = strdup(name); + fprintf(dumpfile, "# time,s "); + for(int i = 0; i < dumpsize; ++i){ + fprintf(dumpfile, "%s ", dumppars[i].code); + } + fprintf(dumpfile, "\n"); + return TRUE; +} + +char *getdumpname(){ return dumpname;} + +void closedumpfile(){ + if(dumpfile && !isstopped){ + if(!isstopped){ + stopdump = TRUE; + while(!isstopped); + } + fclose(dumpfile); + FREE(dumpname); + } +} + +static void *dumpthread(void *p){ + isstopped = FALSE; + stopdump = FALSE; + double dT = *(double*)p; + DBG("Dump thread started. Period: %gs", dT); + double startT = sl_dtime(); + while(!stopdump){ + double t0 = sl_dtime(); + fprintf(dumpfile, "%10.3f ", t0 - startT); + for(int i = 0; i < dumpsize; ++i){ + if(!read_entry(&dumppars[i])) fprintf(dumpfile, "---- "); + else fprintf(dumpfile, "%4d ", dumppars[i].value); + } + fprintf(dumpfile, "\n"); + while(sl_dtime() - t0 < dT) usleep(100); + } + isstopped = TRUE; + return NULL; +} + +int setDumpT(double dT){ + if(dT < 0.){ + WARNX("Time interval should be > 0"); + return FALSE; + } + dumpTime = dT; + DBG("user give dT: %g", dT); + return TRUE; +} + +int rundump(){ + if(!dumpfile){ + WARNX("Open dump file first"); + return FALSE; + } + pthread_t thread; + if(pthread_create(&thread, NULL, dumpthread, (void*)&dumpTime)){ + WARN("Can't create dumping thread"); + return FALSE; + } + DBG("Thread created, detach"); + pthread_detach(thread); + return TRUE; +} + +/** + * @brief dicentry_descr/dicentry_descrN - form string with dictionary entry (common function for dictionary dump) + * @param entry - dictionary entry + * @param N - entry number + * @param buf - outbuf + * @param bufsize - it's size + * @return NULL if error or pointer to `buf` (include '\n' terminating) + */ +char *dicentry_descr(dicentry_t *entry, char *buf, size_t bufsize){ + //DBG("descr of %s", entry->code); + if(!entry || !buf || !bufsize) return NULL; + if(entry->help){ + snprintf(buf, bufsize-1, "%s %4" PRIu16 " %4" PRIu16 " %" PRIu8 " # %s\n", + entry->code, entry->reg, entry->value, entry->readonly, entry->help); + }else{ + snprintf(buf, bufsize-1, "%s %4" PRIu16 " %4" PRIu16 " %" PRIu8 "\n", + entry->code, entry->reg, entry->value, entry->readonly); + } + return buf; +} + +char *dicentry_descrN(size_t N, char *buf, size_t bufsize){ + if(N >= dictsize) return NULL; + return dicentry_descr(&dictionary[N], buf, bufsize); +} + +// dump all registers by input dictionary into output; also modify values of registers in dictionary +int read_dict_entries(const char *outdic){ + if(!chkdict()) return -1; + int got = 0; + FILE *o = fopen(outdic, "w"); + if(!o){ + WARN("Can't open %s", outdic); + return -1; + } + char buf[BUFSIZ]; + for(size_t i = 0; i < dictsize; ++i){ + if(read_entry(&dictionary[i])){ + verbose(LOGLEVEL_MSG, "Read register %d, value: %d\n", dictionary[i].reg, dictionary[i].value); + if(dicentry_descrN(i, buf, BUFSIZ)){ + ++got; + fprintf(o, "%s\n", buf); + } + }else verbose(LOGLEVEL_WARN, "Can't read value of register %d\n", dictionary[i].reg); + } + fclose(o); + return got; +} + + +/********** Aliases **********/ + +// omit leading and trainling spaces +static char *omitspaces(char *ori){ + char *name = sl_omitspaces(ori); + if(!name || !*name) return NULL; + char *e = sl_omitspacesr(name); + if(e) *e = 0; + if(!*name) return NULL; + return name; +} + +static int sortalias(const void *l, const void *r){ + const char *cl = ((alias_t*)l)->name, *cr = ((alias_t*)r)->name; + return strcmp(cl, cr); +} + +// open file with aliases and fill structure +int openaliases(const char *filename){ + closealiases(); + FILE *f = fopen(filename, "r"); + if(!f){ + WARN("Can't open %s", filename); + return FALSE; + } + int retcode = TRUE; + size_t linesz = BUFSIZ, asz = 0; + char *line = MALLOC(char, linesz); + while(1){ + if(getline(&line, &linesz, f) < 0) break; + DBG("aliases line %zd: '%s'", aliasessize, line); + char *comment = getcomment(line); + char *newline = strchr(line, '\n'); + if(newline) *newline = 0; + if(*line == 0) continue; + char *_2run = strchr(line, ':'); + if(!_2run){ + WARNX("Bad string for alias: '%s'", line); + continue; + } + *_2run++ = 0; + _2run = omitspaces(_2run); + if(!_2run){ + WARNX("Empty alias: '%s'", line); + continue; + } + char *name = omitspaces(line); + if(!name || !*name){ + WARNX("Empty `name` field of alias: '%s'", _2run); + continue; + } + if(strchr(name, ' ')){ + WARNX("Space in alias name: '%s'", name); + continue; + } + if(++aliasessize >= asz){ + asz += 50; + aliases = realloc(aliases, sizeof(alias_t) * asz); + if(!aliases){ + WARNX("Can't allocate memory for aliases list"); + retcode = FALSE; + goto ret; + } + } + alias_t *cur = &aliases[aliasessize - 1]; + cur->name = strdup(name); + cur->expr = strdup(_2run); + if(comment) cur->help = strdup(comment); + else cur->help = NULL; + DBG("Read alias; name='%s', expr='%s', comment='%s'", cur->name, cur->expr, cur->help); + } + if(aliasessize == 0) retcode = FALSE; + qsort(aliases, aliasessize, sizeof(alias_t), sortalias); +ret: + fclose(f); + FREE(line); + return retcode; +} + +// return TRUE if aliases list inited +int chkaliases(){ + if(aliasessize < 1){ + WARNX("Init aliases first"); + return FALSE; + } + return TRUE; +} +// remove aliases +void closealiases(){ + if(!aliasessize) return; + for(size_t i = 0; i < aliasessize; ++i){ + FREE(aliases[i].name); + FREE(aliases[i].expr); + FREE(aliases[i].help); + } + FREE(aliases); + aliasessize = 0; +} + +// find alias by name +alias_t *find_alias(const char *name){ + if(!chkaliases()) return NULL; + size_t low = 0, high = aliasessize; + DBG("find alias %s", name); + while(low < high){ + size_t mid = (high + low) / 2; + int cmp = strcmp(name, aliases[mid].name); + DBG("cmp %s and %s give %d", name, aliases[mid].name, cmp); + if(cmp < 0){ + high = mid; + }else if(cmp > 0){ + low = mid + 1; + }else{ + return &aliases[mid]; + } + } + DBG("not found"); + return NULL; +} +// describe alias by entry +char *alias_descr(alias_t *entry, char *buf, size_t bufsize){ + if(!entry || !buf || !bufsize) return NULL; + if(entry->help){ + snprintf(buf, bufsize-1, "%s : %s # %s\n", entry->name, entry->expr, entry->help); + }else{ + snprintf(buf, bufsize-1, "%s : %s\n", entry->name, entry->expr); + } + return buf; +} +// describe alias by number +char *alias_descrN(size_t N, char *buf, size_t bufsize){ + if(N >= aliasessize) return NULL; + return alias_descr(&aliases[N], buf, bufsize); +} diff --git a/modbus_params/dictionary.h b/modbus_params/dictionary.h new file mode 100644 index 0000000..bb510fd --- /dev/null +++ b/modbus_params/dictionary.h @@ -0,0 +1,63 @@ +/* + * This file is part of the modbus_param project. + * Copyright 2025 Edward V. Emelianov . + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include +#include + +typedef struct{ + char *code; // mnemonic code + char *help; // help message (all after '#') + uint16_t value; // value + uint16_t reg; // register number + uint8_t readonly; // ==1 if can't be changed +} dicentry_t; + +typedef struct{ + char *name; // alias name + char *expr; // expression to run + char *help; // help message +} alias_t; + +int opendict(const char *dic); +void closedict(); +int chkdict(); +size_t get_dictsize(); +char *dicentry_descr(dicentry_t *entry, char *buf, size_t bufsize); +char *dicentry_descrN(size_t N, char *buf, size_t bufsize); + +dicentry_t *findentry_by_code(const char *code); +dicentry_t *findentry_by_reg(uint16_t reg); + +int setdumppars(char **pars); +int opendumpfile(const char *name); +void closedumpfile(); +int setDumpT(double dT); +int rundump(); +char *getdumpname(); + +int read_dict_entries(const char *outdic); + +int openaliases(const char *filename); +int chkaliases(); +void closealiases(); +size_t get_aliasessize(); +alias_t *find_alias(const char *name); +char *alias_descr(alias_t *entry, char *buf, size_t bufsize); +char *alias_descrN(size_t N, char *buf, size_t bufsize); diff --git a/modbus_params/main.c b/modbus_params/main.c index ceb01e5..c45c769 100644 --- a/modbus_params/main.c +++ b/modbus_params/main.c @@ -20,6 +20,7 @@ #include #include +#include "dictionary.h" #include "modbus.h" #include "server.h" #include "verbose.h" @@ -29,7 +30,7 @@ typedef struct{ int verbose; // verbose level (not used yet) int slave; // slave ID int isunix; // Unix-socket - char **dumpcodes; // keycodes to dump into file + char **read_keycodes; // keycodes to dump into file char **writeregs; // reg=val to write char **writecodes; // keycode=val to write int **readregs; // regs to write @@ -37,6 +38,7 @@ typedef struct{ char *dumpfile; // dump file name char *outdic; // output dictionary to save everything read from slave char *dicfile; // file with dictionary + char *aliasesfile; // file with aliases char *device; // serial device char *node; // server port or path int baudrate; // baudrate @@ -54,7 +56,7 @@ static sl_option_t cmdlnopts[] = { {"help", NO_ARGS, NULL, 'h', arg_int, APTR(&G.help), "show this help"}, {"verbose", NO_ARGS, NULL, 'v', arg_none, APTR(&G.verbose), "verbose level (each -v adds 1)"}, {"outfile", NEED_ARG, NULL, 'o', arg_string, APTR(&G.dumpfile), "file with parameter's dump"}, - {"dumpkey", MULT_PAR, NULL, 'k', arg_string, APTR(&G.dumpcodes), "dump entry with this keycode; multiply parameter"}, + {"dumpkey", MULT_PAR, NULL, 'k', arg_string, APTR(&G.read_keycodes), "dump entry with this keycode; multiply parameter"}, {"dumptime", NEED_ARG, NULL, 't', arg_double, APTR(&G.dTdump), "dumping time interval (seconds, default: 0.1)"}, {"dictionary", NEED_ARG, NULL, 'D', arg_string, APTR(&G.dicfile), "file with dictionary (format: code register value writeable)"}, {"slave", NEED_ARG, NULL, 's', arg_int, APTR(&G.slave), "slave ID (default: 1)"}, @@ -67,12 +69,15 @@ static sl_option_t cmdlnopts[] = { {"readc", MULT_PAR, NULL, 'R', arg_string, APTR(&G.readcodes), "registers (by keycodes, checked by dictionary) to read; multiply parameter"}, {"node", NEED_ARG, NULL, 'N', arg_string, APTR(&G.node), "node \"IP\", or path (could be \"\\0path\" for anonymous UNIX-socket)"}, {"unixsock", NO_ARGS, NULL, 'U', arg_int, APTR(&G.isunix), "UNIX socket instead of INET"}, + {"alias", NEED_ARG, NULL, 'a', arg_string, APTR(&G.aliasesfile),"file with aliases in format 'name : command to run'"}, end_option }; void signals(int sig){ if(sig > 0) WARNX("Exig with signal %d", sig); close_modbus(); + closedict(); + closealiases(); exit(sig); } @@ -83,9 +88,12 @@ int main(int argc, char **argv){ sl_loglevel_e lvl = G.verbose + LOGLEVEL_ERR; set_verbose_level(lvl); if(lvl >= LOGLEVEL_AMOUNT) lvl = LOGLEVEL_AMOUNT - 1; - if(!G.dicfile) ERRX("Point filename of dictionary"); - if(!opendict(G.dicfile)) signals(-1); - if(G.dumpcodes && !setdumppars(G.dumpcodes)) signals(-1); + if(!G.dicfile) WARNX("Dictionary is absent"); + else if(!opendict(G.dicfile)) signals(-1); + if(G.aliasesfile){ + if(!openaliases(G.aliasesfile)) WARNX("No aliases found in '%s'", G.aliasesfile); + } + if(G.read_keycodes && !setdumppars(G.read_keycodes)) signals(-1); if(G.dumpfile && !opendumpfile(G.dumpfile)) signals(-1); if(!open_modbus(G.device, G.baudrate)) signals(-1); if(!set_slave(G.slave)) signals(-1); @@ -108,13 +116,13 @@ int main(int argc, char **argv){ } if(G.outdic){ DBG("outdic"); - int N = dumpall(G.outdic); + int N = read_dict_entries(G.outdic); if(N < 1) WARNX("Dump full dictionary failed"); else green("Read %N registers, dump to %s\n", N, G.outdic); fflush(stdout); } - if(G.readregs) dumpregs(G.readregs); - if(G.readcodes) dumpcodes(G.readcodes); + if(G.readregs) read_registers(G.readregs); + if(G.readcodes) read_keycodes(G.readcodes); if(G.dumpfile){ if(!setDumpT(G.dTdump)) ERRX("Can't set dumptime %g", G.dTdump); DBG("dumpfile"); diff --git a/modbus_params/modbus.c b/modbus_params/modbus.c index c6bbbe4..3ef5016 100644 --- a/modbus_params/modbus.c +++ b/modbus_params/modbus.c @@ -18,224 +18,17 @@ #include #include -#include #include #include #include +#include "dictionary.h" #include "modbus.h" #include "verbose.h" -static FILE *dumpfile = NULL; -static char *dumpname = NULL; -static dicentry_t *dumppars = NULL; -static int dumpsize = 0; -static double dumpTime = 0.1; - -static dicentry_t *dictionary = NULL; -static int dictsize = 0; - static modbus_t *modbus_ctx = NULL; static pthread_mutex_t modbus_mutex = PTHREAD_MUTEX_INITIALIZER; -static atomic_int stopdump = FALSE, isstopped = TRUE; - -// open dictionary file and check it; return TRUE if all OK -// all after "#" is comment; -// dictionary format: "'code' 'register' 'value' 'readonly flag'\n", e.g. -// "F00.09 61444 5000 1" -int opendict(const char *dic){ - FILE *f = fopen(dic, "r"); - if(!f){ - WARN("Can't open %s", dic); - return FALSE; - } - int dicsz = 0; - size_t linesz = BUFSIZ; - char *line = MALLOC(char, linesz); - dicentry_t curentry; - curentry.code = MALLOC(char, BUFSIZ); - int retcode = TRUE; - while(1){ - if(getline(&line, &linesz, f) < 0) break; - // DBG("Original LINE: '%s'", line); - char *comment = strchr(line, '#'); - if(comment) *comment = 0; - char *newline = strchr(line, '\n'); - if(newline) *newline = 0; - // DBG("LINE: '%s'", line); - if(*line == 0) continue; - if(4 != sscanf(line, "%s %" SCNu16 " %" SCNu16 " %" SCNu8, curentry.code, &curentry.reg, &curentry.value, &curentry.readonly)){ - WARNX("Can't understand this line: '%s'", line); - continue; - } - // DBG("Got line: '%s %" PRIu16 " %" PRIu16 " %" PRIu8, curentry.code, curentry.reg, curentry.value, curentry.readonly); - if(++dictsize >= dicsz){ - dicsz += 50; - dictionary = realloc(dictionary, sizeof(dicentry_t) * dicsz); - if(!dictionary){ - WARN("Can't allocate memory for dictionary"); - retcode = FALSE; - goto ret; - } - } - dicentry_t *entry = &dictionary[dictsize-1]; - entry->code = strdup(curentry.code); - entry->reg = curentry.reg; - entry->value = curentry.value; - entry->readonly = curentry.readonly; - //DBG("Add entry; now dictsize is %d", dictsize); - } -ret: - fclose(f); - FREE(curentry.code); - FREE(line); - return retcode; -} - -static int chkdict(){ - if(dictsize < 1){ - WARNX("Open dictionary first"); - return FALSE; - } - return TRUE; -} - -// find dictionary entry -dicentry_t *findentry_by_code(const char *code){ - if(!chkdict()) return NULL; - for(int i = 0; i < dictsize; ++i){ - if(strcmp(code, dictionary[i].code)) continue; - return &dictionary[i]; - } - return NULL; -} -dicentry_t *findentry_by_reg(uint16_t reg){ - if(!chkdict()) return NULL; - for(int i = 0; i < dictsize; ++i){ - if(reg != dictionary[i].reg) continue; - return &dictionary[i]; - } - return NULL; -} - -// prepare a list with dump parameters -int setdumppars(char **pars){ - if(!pars || !*pars) return FALSE; - if(!chkdict()) return FALSE; - FNAME(); - char **p = pars; - static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; - static int cursz = -1; // current allocated size - int N = 0; - pthread_mutex_lock(&mutex); - while(*p){ // count parameters and check them - dicentry_t *e = findentry_by_code(*p); - if(!e){ - WARNX("Can't find entry with code %s", *p); - pthread_mutex_unlock(&mutex); - return FALSE; - } - DBG("found entry do dump, reg=%d", e->reg); - if(cursz <= N){ - cursz += 50; - DBG("realloc list to %d", cursz); - dumppars = realloc(dumppars, sizeof(dicentry_t) * (cursz)); - DBG("zero mem"); - bzero(&dumppars[N], sizeof(dicentry_t)*(cursz-N)); - } - FREE(dumppars[N].code); - dumppars[N] = *e; - dumppars[N].code = strdup(e->code); - DBG("Add %s", e->code); - ++N; ++p; - } - dumpsize = N; - pthread_mutex_unlock(&mutex); - return TRUE; -} - -// open dump file and add header; return FALSE if failed -int opendumpfile(const char *name){ - if(!chkdict()) return FALSE; - if(dumpsize < 1){ - WARNX("Set dump parameters first"); - return FALSE; - } - if(!name) return FALSE; - closedumpfile(); - dumpfile = fopen(name, "w+"); - if(!dumpfile){ - WARN("Can't open %s", name); - return FALSE; - } - dumpname = strdup(name); - fprintf(dumpfile, "# time,s "); - for(int i = 0; i < dumpsize; ++i){ - fprintf(dumpfile, "%s ", dumppars[i].code); - } - fprintf(dumpfile, "\n"); - return TRUE; -} - -char *getdumpname(){ return dumpname;} - -void closedumpfile(){ - if(dumpfile && !isstopped){ - if(!isstopped){ - stopdump = TRUE; - while(!isstopped); - } - fclose(dumpfile); - FREE(dumpname); - } -} - -static void *dumpthread(void *p){ - isstopped = FALSE; - stopdump = FALSE; - double dT = *(double*)p; - DBG("Dump thread started. Period: %gs", dT); - double startT = sl_dtime(); - while(!stopdump){ - double t0 = sl_dtime(); - fprintf(dumpfile, "%10.3f ", t0 - startT); - for(int i = 0; i < dumpsize; ++i){ - if(!read_entry(&dumppars[i])) fprintf(dumpfile, "---- "); - else fprintf(dumpfile, "%4d ", dumppars[i].value); - } - fprintf(dumpfile, "\n"); - while(sl_dtime() - t0 < dT) usleep(100); - } - isstopped = TRUE; - return NULL; -} - -int setDumpT(double dT){ - if(dT < 0.){ - WARNX("Time interval should be > 0"); - return FALSE; - } - dumpTime = dT; - DBG("user give dT: %g", dT); - return TRUE; -} - -int rundump(){ - if(!dumpfile){ - WARNX("Open dump file first"); - return FALSE; - } - pthread_t thread; - if(pthread_create(&thread, NULL, dumpthread, (void*)&dumpTime)){ - WARN("Can't create dumping thread"); - return FALSE; - } - DBG("Thread created, detach"); - pthread_detach(thread); - return TRUE; -} - void close_modbus(){ if(modbus_ctx){ closedumpfile(); @@ -277,14 +70,14 @@ int write_entry(dicentry_t *entry){ } // write multiply regs (without checking by dict) by NULL-terminated array "reg=val"; return amount of items written -int write_regval(char **keyval){ +int write_regval(char **regval){ int written = 0; dicentry_t entry = {0}; - while(*keyval){ - DBG("Parse %s", *keyval); + while(*regval){ + DBG("Parse %s", *regval); char key[SL_KEY_LEN], value[SL_VAL_LEN]; - if(2 != sl_get_keyval(*keyval, key, value)){ - WARNX("%s isn't in format reg=val", *keyval); + if(2 != sl_get_keyval(*regval, key, value)){ + WARNX("%s isn't in format reg=val", *regval); }else{ DBG("key: %s, val: %s", key, value); int r=-1, v=-1; @@ -299,7 +92,7 @@ int write_regval(char **keyval){ }else verbose(LOGLEVEL_WARN, "Can't write %u to register %u", entry.value, entry.reg); } } - ++keyval; + ++regval; } return written; } @@ -374,28 +167,8 @@ int set_slave(int ID){ return ret; } -// dump all registers by input dictionary into output; also modify values of registers in dictionary -int dumpall(const char *outdic){ - if(!chkdict()) return -1; - int got = 0; - FILE *o = fopen(outdic, "w"); - if(!o){ - WARN("Can't open %s", outdic); - return -1; - } - for(int i = 0; i < dictsize; ++i){ - if(read_entry(&dictionary[i])){ - verbose(LOGLEVEL_MSG, "Read register %d, value: %d\n", dictionary[i].reg, dictionary[i].value); - ++got; - fprintf(o, "%s %4" PRIu16 " %4" PRIu16 " %" PRIu8 "\n", dictionary[i].code, dictionary[i].reg, dictionary[i].value, dictionary[i].readonly); - }else verbose(LOGLEVEL_WARN, "Can't read value of register %d\n", dictionary[i].reg); - } - fclose(o); - return got; -} - -// read dump register values and dump to stdout -void dumpregs(int **addresses){ +// read dump register values (not checking in dict) and dump to stdout (not modifying dictionary) +void read_registers(int **addresses){ dicentry_t entry = {0}; green("Dump registers: (reg val)\n"); while(*addresses){ @@ -411,8 +184,8 @@ void dumpregs(int **addresses){ } printf("\n"); } -// `dumpregs` but by keycodes (not modifying dictionary) -void dumpcodes(char **keycodes){ +// `read_registers` but by keycodes (not modifying dictionary) +void read_keycodes(char **keycodes){ if(!chkdict()) return; dicentry_t entry = {0}; green("Dump registers: (code (reg) val)\n"); diff --git a/modbus_params/modbus.h b/modbus_params/modbus.h index bf409ef..5da93a6 100644 --- a/modbus_params/modbus.h +++ b/modbus_params/modbus.h @@ -18,36 +18,16 @@ #pragma once -#include +#include "dictionary.h" -typedef struct{ - char *code; // mnemonic code - uint16_t value; // value - uint16_t reg; // register number - uint8_t readonly; // ==1 if can't be changed -} dicentry_t; - -int setdumppars(char **pars); -int opendumpfile(const char *name); -void closedumpfile(); -int setDumpT(double dT); -int rundump(); -char *getdumpname(); - -int opendict(const char *dic); - -dicentry_t *findentry_by_code(const char *code); -dicentry_t *findentry_by_reg(uint16_t reg); - -int open_modbus(const char *path, int baudrate); +int open_modbus(const char *path, int baudrate); void close_modbus(); -int set_slave(int ID); +int set_slave(int ID); int read_entry(dicentry_t *entry); int write_entry(dicentry_t *entry); -int write_regval(char **keyval); +int write_regval(char **regval); int write_codeval(char **codeval); -int dumpall(const char *outdic); -void dumpregs(int **addresses); -void dumpcodes(char **keycodes); +void read_registers(int **addresses); +void read_keycodes(char **keycodes); diff --git a/modbus_params/modbus_param.files b/modbus_params/modbus_param.files index d2fac0b..b011bac 100644 --- a/modbus_params/modbus_param.files +++ b/modbus_params/modbus_param.files @@ -1,3 +1,5 @@ +dictionary.c +dictionary.h main.c modbus.c modbus.h diff --git a/modbus_params/relay.aliases b/modbus_params/relay.aliases new file mode 100644 index 0000000..4018008 --- /dev/null +++ b/modbus_params/relay.aliases @@ -0,0 +1,15 @@ +# aliases for test purposes + +allon : id = 5 # turn all on +alloff: id = 0 # turn all off +get : id +#wrong: + : test3 = stop +help : +some wrong +# good but with space inside + space test : test3 = 0 +# newer call +test1 : test2 +# no newline +got : test1 \ No newline at end of file diff --git a/modbus_params/relay.dic b/modbus_params/relay.dic index 285a3af..3a040f4 100644 --- a/modbus_params/relay.dic +++ b/modbus_params/relay.dic @@ -1,4 +1,4 @@ id 0000 1 0 # relay ID -test1 1 0 0 +test1 1 0 0 # test stuff test2 2 0 0 test3 3 0 0 diff --git a/modbus_params/server.c b/modbus_params/server.c index a3303f0..29dd0cc 100644 --- a/modbus_params/server.c +++ b/modbus_params/server.c @@ -19,8 +19,9 @@ #include #include -#include "server.h" +#include "dictionary.h" #include "modbus.h" +#include "server.h" static sl_sock_t *s = NULL; @@ -41,10 +42,58 @@ static sl_sock_hresult_e newdump(sl_sock_t *client, _U_ sl_sock_hitem_t *item, c } return RESULT_OK; } +// list all dictionary or only given +static sl_sock_hresult_e listdict(sl_sock_t *client, _U_ sl_sock_hitem_t *item, const char *req){ + char buf[BUFSIZ]; + size_t N = get_dictsize(); + if(!N) return RESULT_FAIL; + if(!req){ // getter - list all + DBG("list all dict"); + for(size_t i = 0; i < N; ++i){ + if(dicentry_descrN(i, buf, BUFSIZ)){ + sl_sock_sendstrmessage(client, buf); + } + } + }else{ // setter - list by code/reg + DBG("list %s", req); + dicentry_t *e = findentry_by_code(req); + if(!e){ + DBG("User wants number?"); + int x; + if(!sl_str2i(&x, req) || (x < 0 || x > UINT16_MAX)) return RESULT_BADVAL; + DBG("REG: %d", x); + e = findentry_by_reg((uint16_t)x); + if(!e) return RESULT_BADVAL; + } + if(!dicentry_descr(e, buf, BUFSIZ)) return RESULT_FAIL; + sl_sock_sendstrmessage(client, buf); + } + return RESULT_SILENCE; +} +// list all aliases +static sl_sock_hresult_e listaliases(sl_sock_t *client, _U_ sl_sock_hitem_t *item, const char *req){ + char buf[BUFSIZ]; + size_t N = get_aliasessize(); + if(!N) return RESULT_FAIL; + if(!req){ // all + for(size_t i = 0; i < N; ++i){ + if(alias_descrN(i, buf, BUFSIZ)){ + sl_sock_sendstrmessage(client, buf); + } + } + }else{ + alias_t *a = find_alias(req); + if(!a || !alias_descr(a, buf, BUFSIZ)) return RESULT_FAIL; + sl_sock_sendstrmessage(client, buf); + } + return RESULT_SILENCE; +} static sl_sock_hitem_t handlers[] = { {closedump, "clodump", "stop dump and close current dump file", NULL}, {newdump, "newdump", "open new dump file or get name of current", NULL}, + {listdict, "list", "list all dictionary (as getter) or given register (as setter: by codename or value)", NULL}, + {listaliases, "alias", "list all of aliases (as getter) or with given name (by setter)", NULL}, {NULL, NULL, NULL, NULL} }; @@ -76,11 +125,17 @@ static sl_sock_hresult_e defhandler(sl_sock_t *s, const char *str){ entry = findentry_by_code(key); } if(!entry){ + // check alias - should be non-setter!!! + if(n == 1){ + alias_t *alias = find_alias(key); + if(alias) // recoursive call of defhandler + return defhandler(s, alias->expr); + } WARNX("Entry %s not found", key); return RESULT_BADKEY; } if(n == 1){ // getter - if(!read_entry(entry)) return RESULT_BADKEY; + if(!read_entry(entry)) return RESULT_FAIL; snprintf(value, SL_VAL_LEN-1, "%s=%u\n", key, entry->value); }else{ // setter if(!sl_str2i(&N, value)){