mirror of
https://github.com/eddyem/eddys_snippets.git
synced 2025-12-06 02:35:12 +03:00
some fixes with dictionary; add aliases
This commit is contained in:
parent
0074eac5d8
commit
5f4e06bb7b
@ -1,10 +1,14 @@
|
|||||||
|
|
||||||
modbus_par: reading/writing/dumping modbus registers using `dictionary`
|
modbus_par: reading/writing/dumping modbus registers using `dictionary`
|
||||||
|
|
||||||
|
|
||||||
-D, --dictionary=arg file with dictionary (format: code register value writeable)
|
-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
|
-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
|
-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
|
-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)
|
-b, --baudrate=arg modbus baudrate (default: 9600)
|
||||||
-d, --device=arg modbus device (default: /dev/ttyUSB0)
|
-d, --device=arg modbus device (default: /dev/ttyUSB0)
|
||||||
-h, --help show this help
|
-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)
|
-t, --dumptime=arg dumping time interval (seconds, default: 0.1)
|
||||||
-v, --verbose verbose level (each -v adds 1)
|
-v, --verbose verbose level (each -v adds 1)
|
||||||
-w, --writer write new value to register (format: reg=val); multiply parameter
|
-w, --writer write new value to register (format: reg=val); multiply parameter
|
||||||
|
|
||||||
|
|||||||
510
modbus_params/dictionary.c
Normal file
510
modbus_params/dictionary.c
Normal file
@ -0,0 +1,510 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of the modbus_param project.
|
||||||
|
* Copyright 2025 Edward V. Emelianov <edward.emelianoff@gmail.com>.
|
||||||
|
*
|
||||||
|
* 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*****************************************************************************
|
||||||
|
* 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 <ctype.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <stdatomic.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <usefull_macros.h>
|
||||||
|
|
||||||
|
#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);
|
||||||
|
}
|
||||||
63
modbus_params/dictionary.h
Normal file
63
modbus_params/dictionary.h
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of the modbus_param project.
|
||||||
|
* Copyright 2025 Edward V. Emelianov <edward.emelianoff@gmail.com>.
|
||||||
|
*
|
||||||
|
* 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
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);
|
||||||
@ -20,6 +20,7 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <usefull_macros.h>
|
#include <usefull_macros.h>
|
||||||
|
|
||||||
|
#include "dictionary.h"
|
||||||
#include "modbus.h"
|
#include "modbus.h"
|
||||||
#include "server.h"
|
#include "server.h"
|
||||||
#include "verbose.h"
|
#include "verbose.h"
|
||||||
@ -29,7 +30,7 @@ typedef struct{
|
|||||||
int verbose; // verbose level (not used yet)
|
int verbose; // verbose level (not used yet)
|
||||||
int slave; // slave ID
|
int slave; // slave ID
|
||||||
int isunix; // Unix-socket
|
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 **writeregs; // reg=val to write
|
||||||
char **writecodes; // keycode=val to write
|
char **writecodes; // keycode=val to write
|
||||||
int **readregs; // regs to write
|
int **readregs; // regs to write
|
||||||
@ -37,6 +38,7 @@ typedef struct{
|
|||||||
char *dumpfile; // dump file name
|
char *dumpfile; // dump file name
|
||||||
char *outdic; // output dictionary to save everything read from slave
|
char *outdic; // output dictionary to save everything read from slave
|
||||||
char *dicfile; // file with dictionary
|
char *dicfile; // file with dictionary
|
||||||
|
char *aliasesfile; // file with aliases
|
||||||
char *device; // serial device
|
char *device; // serial device
|
||||||
char *node; // server port or path
|
char *node; // server port or path
|
||||||
int baudrate; // baudrate
|
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"},
|
{"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)"},
|
{"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"},
|
{"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)"},
|
{"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)"},
|
{"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)"},
|
{"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"},
|
{"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)"},
|
{"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"},
|
{"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
|
end_option
|
||||||
};
|
};
|
||||||
|
|
||||||
void signals(int sig){
|
void signals(int sig){
|
||||||
if(sig > 0) WARNX("Exig with signal %d", sig);
|
if(sig > 0) WARNX("Exig with signal %d", sig);
|
||||||
close_modbus();
|
close_modbus();
|
||||||
|
closedict();
|
||||||
|
closealiases();
|
||||||
exit(sig);
|
exit(sig);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,9 +88,12 @@ int main(int argc, char **argv){
|
|||||||
sl_loglevel_e lvl = G.verbose + LOGLEVEL_ERR;
|
sl_loglevel_e lvl = G.verbose + LOGLEVEL_ERR;
|
||||||
set_verbose_level(lvl);
|
set_verbose_level(lvl);
|
||||||
if(lvl >= LOGLEVEL_AMOUNT) lvl = LOGLEVEL_AMOUNT - 1;
|
if(lvl >= LOGLEVEL_AMOUNT) lvl = LOGLEVEL_AMOUNT - 1;
|
||||||
if(!G.dicfile) ERRX("Point filename of dictionary");
|
if(!G.dicfile) WARNX("Dictionary is absent");
|
||||||
if(!opendict(G.dicfile)) signals(-1);
|
else if(!opendict(G.dicfile)) signals(-1);
|
||||||
if(G.dumpcodes && !setdumppars(G.dumpcodes)) 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(G.dumpfile && !opendumpfile(G.dumpfile)) signals(-1);
|
||||||
if(!open_modbus(G.device, G.baudrate)) signals(-1);
|
if(!open_modbus(G.device, G.baudrate)) signals(-1);
|
||||||
if(!set_slave(G.slave)) signals(-1);
|
if(!set_slave(G.slave)) signals(-1);
|
||||||
@ -108,13 +116,13 @@ int main(int argc, char **argv){
|
|||||||
}
|
}
|
||||||
if(G.outdic){
|
if(G.outdic){
|
||||||
DBG("outdic");
|
DBG("outdic");
|
||||||
int N = dumpall(G.outdic);
|
int N = read_dict_entries(G.outdic);
|
||||||
if(N < 1) WARNX("Dump full dictionary failed");
|
if(N < 1) WARNX("Dump full dictionary failed");
|
||||||
else green("Read %N registers, dump to %s\n", N, G.outdic);
|
else green("Read %N registers, dump to %s\n", N, G.outdic);
|
||||||
fflush(stdout);
|
fflush(stdout);
|
||||||
}
|
}
|
||||||
if(G.readregs) dumpregs(G.readregs);
|
if(G.readregs) read_registers(G.readregs);
|
||||||
if(G.readcodes) dumpcodes(G.readcodes);
|
if(G.readcodes) read_keycodes(G.readcodes);
|
||||||
if(G.dumpfile){
|
if(G.dumpfile){
|
||||||
if(!setDumpT(G.dTdump)) ERRX("Can't set dumptime %g", G.dTdump);
|
if(!setDumpT(G.dTdump)) ERRX("Can't set dumptime %g", G.dTdump);
|
||||||
DBG("dumpfile");
|
DBG("dumpfile");
|
||||||
|
|||||||
@ -18,224 +18,17 @@
|
|||||||
|
|
||||||
#include <inttypes.h>
|
#include <inttypes.h>
|
||||||
#include <modbus/modbus.h>
|
#include <modbus/modbus.h>
|
||||||
#include <stdatomic.h>
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <usefull_macros.h>
|
#include <usefull_macros.h>
|
||||||
|
|
||||||
|
#include "dictionary.h"
|
||||||
#include "modbus.h"
|
#include "modbus.h"
|
||||||
#include "verbose.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 modbus_t *modbus_ctx = NULL;
|
||||||
static pthread_mutex_t modbus_mutex = PTHREAD_MUTEX_INITIALIZER;
|
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(){
|
void close_modbus(){
|
||||||
if(modbus_ctx){
|
if(modbus_ctx){
|
||||||
closedumpfile();
|
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
|
// 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;
|
int written = 0;
|
||||||
dicentry_t entry = {0};
|
dicentry_t entry = {0};
|
||||||
while(*keyval){
|
while(*regval){
|
||||||
DBG("Parse %s", *keyval);
|
DBG("Parse %s", *regval);
|
||||||
char key[SL_KEY_LEN], value[SL_VAL_LEN];
|
char key[SL_KEY_LEN], value[SL_VAL_LEN];
|
||||||
if(2 != sl_get_keyval(*keyval, key, value)){
|
if(2 != sl_get_keyval(*regval, key, value)){
|
||||||
WARNX("%s isn't in format reg=val", *keyval);
|
WARNX("%s isn't in format reg=val", *regval);
|
||||||
}else{
|
}else{
|
||||||
DBG("key: %s, val: %s", key, value);
|
DBG("key: %s, val: %s", key, value);
|
||||||
int r=-1, v=-1;
|
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);
|
}else verbose(LOGLEVEL_WARN, "Can't write %u to register %u", entry.value, entry.reg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
++keyval;
|
++regval;
|
||||||
}
|
}
|
||||||
return written;
|
return written;
|
||||||
}
|
}
|
||||||
@ -374,28 +167,8 @@ int set_slave(int ID){
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
// dump all registers by input dictionary into output; also modify values of registers in dictionary
|
// read dump register values (not checking in dict) and dump to stdout (not modifying dictionary)
|
||||||
int dumpall(const char *outdic){
|
void read_registers(int **addresses){
|
||||||
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){
|
|
||||||
dicentry_t entry = {0};
|
dicentry_t entry = {0};
|
||||||
green("Dump registers: (reg val)\n");
|
green("Dump registers: (reg val)\n");
|
||||||
while(*addresses){
|
while(*addresses){
|
||||||
@ -411,8 +184,8 @@ void dumpregs(int **addresses){
|
|||||||
}
|
}
|
||||||
printf("\n");
|
printf("\n");
|
||||||
}
|
}
|
||||||
// `dumpregs` but by keycodes (not modifying dictionary)
|
// `read_registers` but by keycodes (not modifying dictionary)
|
||||||
void dumpcodes(char **keycodes){
|
void read_keycodes(char **keycodes){
|
||||||
if(!chkdict()) return;
|
if(!chkdict()) return;
|
||||||
dicentry_t entry = {0};
|
dicentry_t entry = {0};
|
||||||
green("Dump registers: (code (reg) val)\n");
|
green("Dump registers: (code (reg) val)\n");
|
||||||
|
|||||||
@ -18,26 +18,7 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <stdint.h>
|
#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();
|
void close_modbus();
|
||||||
@ -45,9 +26,8 @@ int set_slave(int ID);
|
|||||||
|
|
||||||
int read_entry(dicentry_t *entry);
|
int read_entry(dicentry_t *entry);
|
||||||
int write_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 write_codeval(char **codeval);
|
||||||
|
|
||||||
int dumpall(const char *outdic);
|
void read_registers(int **addresses);
|
||||||
void dumpregs(int **addresses);
|
void read_keycodes(char **keycodes);
|
||||||
void dumpcodes(char **keycodes);
|
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
dictionary.c
|
||||||
|
dictionary.h
|
||||||
main.c
|
main.c
|
||||||
modbus.c
|
modbus.c
|
||||||
modbus.h
|
modbus.h
|
||||||
|
|||||||
15
modbus_params/relay.aliases
Normal file
15
modbus_params/relay.aliases
Normal file
@ -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
|
||||||
@ -1,4 +1,4 @@
|
|||||||
id 0000 1 0 # relay ID
|
id 0000 1 0 # relay ID
|
||||||
test1 1 0 0
|
test1 1 0 0 # test stuff
|
||||||
test2 2 0 0
|
test2 2 0 0
|
||||||
test3 3 0 0
|
test3 3 0 0
|
||||||
|
|||||||
@ -19,8 +19,9 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include "server.h"
|
#include "dictionary.h"
|
||||||
#include "modbus.h"
|
#include "modbus.h"
|
||||||
|
#include "server.h"
|
||||||
|
|
||||||
static sl_sock_t *s = NULL;
|
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;
|
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[] = {
|
static sl_sock_hitem_t handlers[] = {
|
||||||
{closedump, "clodump", "stop dump and close current dump file", NULL},
|
{closedump, "clodump", "stop dump and close current dump file", NULL},
|
||||||
{newdump, "newdump", "open new dump file or get name of current", 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}
|
{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);
|
entry = findentry_by_code(key);
|
||||||
}
|
}
|
||||||
if(!entry){
|
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);
|
WARNX("Entry %s not found", key);
|
||||||
return RESULT_BADKEY;
|
return RESULT_BADKEY;
|
||||||
}
|
}
|
||||||
if(n == 1){ // getter
|
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);
|
snprintf(value, SL_VAL_LEN-1, "%s=%u\n", key, entry->value);
|
||||||
}else{ // setter
|
}else{ // setter
|
||||||
if(!sl_str2i(&N, value)){
|
if(!sl_str2i(&N, value)){
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user