A fast MQTT dashboard application and rule engine framework written in C for Linux, Raspberry Pi and WINDOWS.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

477 lines
18 KiB

/* HDDASHGEN.C (c) Markus Hoffmann */
/* This file is part of MQTT-Hyperdash, the MQTT Dashboard
* ============================================================
* MQTT-Hyperdash is free software and comes with NO WARRANTY - read the file
* COPYING for details
*/
/* This tool makes a series of generic .dash files out of a (sorted) list
of topics, like the list created by mqtt-list-topics.
A common way to create such a parameter tree is by excecuting
mqtt-list-topics --broker localhost | sort | hddashgen --broker localhost
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <unistd.h>
#include <ctype.h>
#include <fnmatch.h>
#include <dirent.h>
#include <string.h>
#include <sys/stat.h>
#if defined WINDOWS
size_t getline(char **lineptr, size_t *n, FILE *stream);
#define EX_OK 0
#else
#include <sysexits.h>
#endif
#include "file.h"
#include "hddashgen.h"
#include "element_groups.h"
/* Global variables and defaults */
int verbose=0; /* Verbosity level */
char *broker_url=DEFAULT_BROKER;
char *broker_user=NULL;
char *broker_passwd=NULL;
char *dashboard_prefix="";
char *dashboarddir="."; /* by default use current dir.*/
static void hddashgen_set_defaults() {
/* Set the default path where the .dash files are searched for.
* The path can be overridden by a commandline parameter.
*/
char *envptr;
static char path[256];
envptr=getenv("HOME");
if(envptr) {
snprintf(path,sizeof(path),"%s/.hyperdash/dashboards",envptr);
if(exist(path)) { /* It does exist! */
dashboarddir=path;
}
}
}
static void intro() {
puts("hddashgen " HDDASHGEN_VERSION " (c) 2020 by Markus Hoffmann\n"
"This tool is part of MQTT-Hyperdash, the universal MQTT Dashboard for linux.");
}
static void usage() {
printf(
"\nUsage: %s [-hvq] ---\tcreate dashboards from topic lists.\n\n"
" -h --help\t\t---\tusage\n"
" --broker <url>\t---\tdefine the broker url used [%s]\n"
" --user <user>\t\t---\tuse this username for broker.\n"
" --passwd <passwd>\t---\tuse this password for broker.\n"
" --dashpath <path>\t---\tset path for dash files [%s]\n"
" --prefix <name>\t---\tset prefix for dashboard file names [%s]\n"
" -v\t\t\t---\tbe more verbose\n"
" -q\t\t\t---\tbe more quiet\n"
,"hddashgen",broker_url,dashboarddir,dashboard_prefix);
}
static void kommandozeile(int anzahl, char *argumente[]) {
int count,quitflag=0;
/* process command line parameters */
for(count=1;count<anzahl;count++) {
if(!strcmp(argumente[count],"-h") || !strcmp(argumente[count],"--help")) {
intro();
usage();
quitflag=1;
}
else if(!strcmp(argumente[count],"--version")) {
intro();
quitflag=1;
}
else if(!strcmp(argumente[count],"--broker")) broker_url=argumente[++count];
else if(!strcmp(argumente[count],"--user")) broker_user=argumente[++count];
else if(!strcmp(argumente[count],"--passwd")) broker_passwd=argumente[++count];
else if(!strcmp(argumente[count],"--dashpath")) dashboarddir=argumente[++count];
else if(!strcmp(argumente[count],"--prefix")) dashboard_prefix=argumente[++count];
else if(!strcmp(argumente[count],"-v")) verbose++;
else if(!strcmp(argumente[count],"-q")) verbose--;
else if(*(argumente[count])=='-') ; /* do nothing, these could be options for the rule itself */
else {
/* do nothing, these could be options for rule itself */
}
}
if(quitflag) exit(EX_OK);
}
typedef struct {
char *name;
char *typ;
int anz;
char *inhalt;
} TOPIC;
TOPIC topics[MAX_ANZ_TOPICS];
int anztopics=0;
static void free_topics() {
int i;
for(i=0;i<anztopics;i++) {
free(topics[i].name);
free(topics[i].typ);
free(topics[i].inhalt);
}
anztopics=0;
}
#define advance() y+=ADVANCE_Y; \
if(y>MAX_Y) {y=FIELD_START_Y;x+=ADVANCE_X;} \
if(h<y+ADVANCE_Y) h=y+ADVANCE_Y; \
if(w<x+ADVANCE_X) w=x+ADVANCE_X
int do_level(int level,char *match, char *filename) {
int i,offset=0,x=5,y;
char filenameprefix[256];
char fullfilename[256];
char buffer[128];
char old[128]="/"; /* start with something impossible */
char *p1,*p2;
int sublevel;
int w=200;
int h=100;
/* Open/create file */
snprintf(filenameprefix, sizeof(filenameprefix),"%s%s",dashboard_prefix,filename);
snprintf(fullfilename, sizeof(fullfilename),"%s/%s%s",dashboarddir,dashboard_prefix,filename);
printf("--> %s [%s]\n",fullfilename,match);
FILE *fp=fopen(fullfilename,"w");
if(fp==NULL) {
printf("ERROR: could not create %s\n",fullfilename);
return(-1);
}
/* Write Header information
*/
fprintf(fp,"PANEL: TITLE=\"%s\" W=%d H=%d " WHITE BGBLACK "\n#####################################\n",filename,w,h);
fprintf(fp,"# generated by hddasghen V." HDDASHGEN_VERSION "\n");
fprintf(fp,"# \n");
if(broker_user) {
fprintf(fp,"BROKER: URL=\"%s\" USER=\"%s\" PASSWD=\"%s\"\n",broker_url,broker_user,broker_passwd);
} else fprintf(fp,"BROKER: URL=\"%s\"\n",broker_url);
y=FIELD_START_Y;
fprintf(fp,"# HEADER\n");
if(match) {
offset=strlen(match);
fprintf(fp,"TEXT: X=10 Y=30 FGC=$FFFF00FF h=40 TEXT=\"%s\" FONT=\"Arial_Bold\" FONTSIZE=20\n",match);
if(w<10+strlen(match)*14) w=10+strlen(match)*14;
} else {
fprintf(fp,"TEXT: X=10 Y=10 FGC=$FFFFFFFF h=20 TEXT=\"%s\" FONT=\"Arial_Bold\" FONTSIZE=20\n","Root of Broker");
fprintf(fp,"TEXT: X=10 Y=30 FGC=$FFFFFFFF h=20 TEXT=\"%s\" FONT=\"Arial_Bold\" FONTSIZE=16\n",dashboard_prefix);
fprintf(fp,"\n");
/* Create a button for each *mainroot_hd.dash it finds. */
DIR *dp;
struct dirent *entry;
struct stat statbuf;
if((dp = opendir(dashboarddir)) == NULL) {
fprintf(stderr,"cannot open directory: %s\n",dashboarddir);
} else {
while((entry = readdir(dp)) != NULL) {
stat(entry->d_name,&statbuf);
if(S_ISDIR(statbuf.st_mode)) {
/* Found a directory, but ignore . and .. */
if(strcmp(".",entry->d_name) == 0 ||
strcmp("..",entry->d_name) == 0)
continue;
} else {
if(!fnmatch("*mainroot_hd.dash",entry->d_name,FNM_NOESCAPE|FNM_PATHNAME)) {
if(strcmp(entry->d_name,filenameprefix)) {
printf("%s\n",entry->d_name);
char buf[256];
char buf2[256];
strncpy(buf,entry->d_name,256);
strncpy(buf2,entry->d_name,256);
int l=strlen(buf);
if(l>=5) buf[l-5]=0;
if(l>16) buf2[l-16]=0;
fprintf(fp,"# LINK TO MAINROOT DASHBOARD\n");
fprintf(fp,"PBOX: " XYWH BGGRAY2 GRAY "\n", x+5, y,180,20);
fprintf(fp,"TEXT: " XYHT WHITE FONT_BUTTON "\n",x+10,y, 20,buf2);
fprintf(fp,"DASH: " XYWH "DASH=\"%s\"\n", x+5, y,180,20,buf);
fprintf(fp,"FRAMETOGGLE: " XYWH "\n", x+5, y,180,20);
fprintf(fp,"\n");
advance();
}
}
}
}
closedir(dp);
y+=10; /* a little extra space */
}
}
for(i=0;i<anztopics;i++) {
if(match==NULL || !strncmp(topics[i].name,match,strlen(match))) {
p1=&(topics[i].name[offset]);
p2=buffer;
sublevel=0;
while(*p1 && *p1!='/') *p2++=*p1++;
if(*p1=='/') sublevel=1;
*p2=0;
if(!((*old==0 && *buffer==0) || !strncmp(old,buffer,128))) {
if(sublevel) {
/* Make a Button to follow the tree */
/* Process new branch*/
char newmatch[128];
if(match) snprintf(newmatch,sizeof(newmatch),"%s%s/",match,buffer);
else snprintf(newmatch,sizeof(newmatch),"%s/",buffer);
char nfilename[128];
p1=nfilename;
p2=newmatch;
while(*p2) {
if(*p2=='/') *p1='_';
else if(*p2=='$') *p1='_';
else *p1=tolower(*p2);
p1++;
p2++;
}
*p1=0;
strcat(nfilename,"hd");
char text[256];
p1=text;
p2=newmatch;
while(*p2) {
if(*p2=='/' && p2[1]) p1=text;
else *p1++=*p2;
p2++;
}
if(p1>text) --p1;
*p1=0;
char buf[256];
snprintf(buf,sizeof(buf),"%s%s",dashboard_prefix,nfilename);
fprintf(fp,"# LINK to next TREE LEVEL\n");
fprintf(fp,"PBOX: " XYWH GRAY BGGRAY "\n",x+5,y,180,20);
fprintf(fp,"TEXT: " XYHT WHITE FONT_BUTTON "\n",x+10,y,20,text);
fprintf(fp,"DASH: " XYWH "DASH=\"%s\"\n",x+5,y,180,20,buf);
fprintf(fp,"FRAMETOGGLE: " XYWH "\n",x+5,y,180,20);
fprintf(fp,"\n");
strcat(nfilename,".dash");
do_level(level+1,newmatch,nfilename);
advance();
strncpy(old,buffer,128); /* Ignore all topics here which are in the Subtree. */
} else {
if(!strcmp(buffer,"ACTIVITY_DM")) {
fprintf(fp,"# ACTIVITY indicator\n");
fprintf(fp,"TEXT: " XYHT WHITE FONT_PARNAME "\n",x+10,y,20,buffer);
fprintf(fp,"BITMAPLABEL: " XY BGBLUE "TOPIC=\"%s\" "
" BITMAP[0]=\"0|Disc1|$FFFFFFFF\" \\\n"
" BITMAP[1]=\"1|Disc2|$FFFFFFFF\" \\\n"
" BITMAP[2]=\"2|Disc3|$FFFFFFFF\" \\\n"
" BITMAP[3]=\"3|Disc4|$FFFFFFFF\" \n",x+160,y,topics[i].name);
fprintf(fp,"\n");
advance();
} else if(!strcmp(buffer,"STATUS_DM")) {
fprintf(fp,"# STATUS indicator\n");
fprintf(fp,"TEXT: " XYHT WHITE FONT_PARNAME "\n",x+10,y,20,buffer);
fprintf(fp,"BITMAPLABEL: " XY BGBLUE "TOPIC=\"%s\" "
" BITMAP[0]=\"0|SmallCircle|$00FF00FF\" \\\n"
" BITMAP[1]=\"1|SmallCircle|$FF0000FF\" \\\n"
" BITMAP[2]=\"2|SmallCircle|$FFFF00FF\" \\\n"
" BITMAP[3]=\"3|SmallCircle|$FF00FFFF\" \n",x+160,y,topics[i].name);
fprintf(fp,"\n");
advance();
} else if(!fnmatch("*ONOFF_DC",buffer,FNM_NOESCAPE|FNM_PATHNAME)) {
fprintf(fp,"# ON/OFF button group\n");
fprintf(fp,"TEXT: " XYHT WHITE FONT_PARNAME "\n",x+10,y,20,buffer);
fprintf(fp,"PBOX: " XYWH GRAY BGDARKGRAY "\n",x+150,y,45,20);
fprintf(fp,"PBOX: " XYWH GRAY BGDARKGRAY "\n",x+205,y,45,20);
fprintf(fp,"TEXT: " XYHT GREEN FONT_BUTTON "\n",x+160,y+1,20,"ON");
fprintf(fp,"TEXT: " XYHT RED FONT_BUTTON "\n",x+210,y+1,20,"OFF");
fprintf(fp,"FRAMELABEL: " XYWHT "MATCH=\"1\"\n",x+150,y,45,20,topics[i].name);
fprintf(fp,"FRAMELABEL: " XYWHT "MATCH=\"0\"\n",x+205,y,45,20,topics[i].name);
fprintf(fp,"TOPICINAREA: " XYWHT "VALUE=\"1\"\n",x+150,y,45,20,topics[i].name);
fprintf(fp,"TOPICINAREA: " XYWHT "VALUE=\"0\"\n",x+205,y,45,20,topics[i].name);
fprintf(fp,"\n");
advance();
} else if(!strcmp(buffer,"STATUS_SM")) {
fprintf(fp,"# STATUS string\n");
fprintf(fp,"TEXT: " XYHT WHITE FONT_PARNAME "\n",x+10,y,20,buffer);
fprintf(fp,"TOPICSTRING: " XYWHT BGBLACK YELLOW FONT_PAR "\n",x+130,y,110,20,topics[i].name);
fprintf(fp,"FRAME: " XYWHR "\n",x+130-2,y-2,110+4,20+4);
fprintf(fp,"\n");
advance();
} else if(!fnmatch("*_AM",buffer,FNM_NOESCAPE|FNM_PATHNAME)) {
fprintf(fp,"# ANALOG MEASURED parameter\n");
fprintf(fp,"TEXT: " XYHT WHITE FONT_PARNAME "\n",x+10,y,20,buffer);
fprintf(fp,"TOPICNUMBER: " XYWHT BGBLUE YELLOW FONT_PAR ANAFORMAT "\n",x+150,y,100,20,topics[i].name);
fprintf(fp,"\n");
advance();
} else if(!fnmatch("*_AD",buffer,FNM_NOESCAPE|FNM_PATHNAME)) {
fprintf(fp,"# ANALOG DATA parameter\n");
fprintf(fp,"TEXT: " XYHT WHITE FONT_PARNAME "\n",x+10,y,20,buffer);
fprintf(fp,"TOPICNUMBER: " XYWHT BGBLACK ORANGE FONT_PAR ANAFORMAT "\n",x+150,y,100,20,topics[i].name);
fprintf(fp,"TOPICINNUMBER: " XYWHT ANAFORMAT "MIN=0 MAX=1000\n",x+150-2,y-2,100+4,20+4,topics[i].name);
fprintf(fp,"FRAME: " XYWHR "\n",x+150-2,y-2,100+4,20+4);
fprintf(fp,"\n");
advance();
} else if(!fnmatch("*_AC",buffer,FNM_NOESCAPE|FNM_PATHNAME)) {
fprintf(fp,"# ANALOG CONTROL parameter\n");
fprintf(fp,"TEXT: " XYHT WHITE FONT_PARNAME "\n",x+10,y,20,buffer);
fprintf(fp,"TOPICNUMBER: " XYWHT BGBLUE CYAN FONT_PAR ANAFORMAT "\n",x+150,y,100,20,topics[i].name);
advance();
fprintf(fp,"PBOX: " XYWH BGGRAY GRAY "\n",x+7,y,240,20);
fprintf(fp,"FRAME: " XYWH "\n",x+7-2,y-2,240+4,20+4);
fprintf(fp,"FRAME: " XYWHR "\n",x+7-2+20,y+2,240+4-40,20-4-1);
fprintf(fp,"BITMAP: " XY WHITE "BITMAP=\"TickLeft\"\n",x+7,y+2);
fprintf(fp,"BITMAP: " XY WHITE "BITMAP=\"TickRight\"\n",x+7+220+2,y+2);
fprintf(fp,"HSCALER: " XYWHT ANAMM BGBLACK GRAY "TIC=0.05 AGC=$0\n",x+7+20,y+2,240-40,20-4-2,topics[i].name);
fprintf(fp,"TICKER: " XYWHT ANAMM "TIC=-0.05\n",x+7, y+2,16,16,topics[i].name);
fprintf(fp,"TICKER: " XYWHT ANAMM "TIC=0.05\n", x+7+220+2,y+2,16,16,topics[i].name);
fprintf(fp,"\n");
advance();
} else if(!fnmatch("*_DM",buffer,FNM_NOESCAPE|FNM_PATHNAME)) {
fprintf(fp,"# DIGITAL MEASURED parameter\n");
fprintf(fp,"TEXT: " XYHT WHITE FONT_PARNAME "\n",x+10,y,20,buffer);
fprintf(fp,"TOPICNUMBER: " XYWHT BGBLUE YELLOW FONT_PAR DIGIFORMAT "\n",x+150,y,100,20,topics[i].name);
fprintf(fp,"\n");
advance();
} else if(!fnmatch("*_DD",buffer,FNM_NOESCAPE|FNM_PATHNAME)) {
fprintf(fp,"# DIGITAL DATA parameter\n");
fprintf(fp,"TEXT: " XYHT WHITE FONT_PARNAME "\n",x+10,y,20,buffer);
fprintf(fp,"TOPICNUMBER: " XYWHT BGBLACK ORANGE FONT_PAR DIGIFORMAT "\n",x+150,y,100,20,topics[i].name);
fprintf(fp,"TOPICINNUMBER: " XYWHT DIGIFORMAT DIGIMM "\n",x+150-2,y-2,100+4,20+4,topics[i].name);
fprintf(fp,"FRAME: " XYWHR "\n",x+150-2,y-2,100+4,20+4);
fprintf(fp,"\n");
advance();
} else if(!fnmatch("*_DC",buffer,FNM_NOESCAPE|FNM_PATHNAME)) {
fprintf(fp,"# DIGITAL CONTROL parameter\n");
fprintf(fp,"TEXT: " XYHT WHITE FONT_PARNAME "\n",x+10,y,20,buffer);
fprintf(fp,"TOPICNUMBER: " XYWHT BGBLACK CYAN FONT_PAR DIGIFORMAT "\n",x+150,y,100,20,topics[i].name);
fprintf(fp,"TOPICINNUMBER: " XYWHT DIGIFORMAT DIGIMM "\n",x+150-2,y-2,100+4,20+4,topics[i].name);
fprintf(fp,"FRAME: " XYWHR "\n",x+150-2,y-2,100+4,20+4);
fprintf(fp,"\n");
advance();
} else if(!fnmatch("*_SM",buffer,FNM_NOESCAPE|FNM_PATHNAME)) {
fprintf(fp,"# MEASURED STRING parameter\n");
fprintf(fp,"TEXT: " XYHT WHITE FONT_PARNAME "\n",x+10,y,20,buffer);
fprintf(fp,"TOPICSTRING: " XYWHT BGBLUE YELLOW FONT_PAR "\n",x+130,y,110,20,topics[i].name);
fprintf(fp,"\n");
advance();
} else if(!fnmatch("*_SC",buffer,FNM_NOESCAPE|FNM_PATHNAME)) {
fprintf(fp,"# CONTROL STRING parameter\n");
fprintf(fp,"TEXT: " XYHT WHITE FONT_PARNAME "\n",x+10,y,20,buffer);
fprintf(fp,"TOPICSTRING: " XYWHT BGBLACK WHITE FONT_PAR "\n",x+130,y,110,20,topics[i].name);
fprintf(fp,"FRAME: " XYWHR "\n",x+130-2,y-2,110+4,20+4);
fprintf(fp,"TOPICINSTRING: " XYWHT "\n",x+150-2,y-2,100+4,20+4,topics[i].name);
fprintf(fp,"\n");
advance();
} else if(!fnmatch("*_SD",buffer,FNM_NOESCAPE|FNM_PATHNAME)) {
fprintf(fp,"# generic STRING parameter\n");
fprintf(fp,"TEXT: " XYHT WHITE FONT_PARNAME "\n",x+10,y,20,buffer);
fprintf(fp,"TOPICSTRING: " XYWHT BGBLACK ORANGE FONT_PAR "\n",x+130,y,110,20,topics[i].name);
fprintf(fp,"FRAME: " XYWHR "\n",x+130-2,y-2,110+4,20+4);
fprintf(fp,"TOPICINSTRING: " XYWHT "\n",x+150-2,y-2,100+4,20+4,topics[i].name);
fprintf(fp,"\n");
advance();
} else {
fprintf(fp,"# unknown parameter type\n");
fprintf(fp,"TEXT: " XYHT WHITE FONT_PARNAME "\n",x+10,y,20,buffer);
if(!strcmp(topics[i].typ,"binary")) {
fprintf(fp,"TEXTAREA: " XYWHT BGBLACK MAGENTA FONT_SMALL "\n",x+150,y,100,20,topics[i].name);
} else if(!strcmp(topics[i].typ,"IMAGE")) {
fprintf(fp,"TOPICIMAGE: " XYWHT "\n",x+150,y,100,90,topics[i].name);
advance();
advance();
advance();
} else if(!strcmp(topics[i].typ,"JSON")) {
fprintf(fp,"TEXTAREA: " XYWHT BGBLACK WHITE FONT_SMALL "\n",x+150,y,100,50,topics[i].name);
fprintf(fp,"TOPICINSTRING: " XYWHT "\n",x+150-2,y-2,100+4,50+4,topics[i].name);
fprintf(fp,"FRAME: " XYWHR "\n",x+150-2,y-2,100+4,50+4);
advance();
} else {
fprintf(fp,"TEXTAREA: " XYWHT BGBLACK WHITE FONT_SMALL "\n",x+150,y,100,20,topics[i].name);
fprintf(fp,"TOPICINSTRING: " XYWHT "\n",x+150-2,y-2,100+4,20+4,topics[i].name);
fprintf(fp,"FRAME: " XYWHR "\n",x+150-2,y-2,100+4,20+4);
}
fprintf(fp,"\n");
advance();
}
}
}
} else {
/* doesnt match */
old[0]='/';
old[1]=0;
}
}
fprintf(fp,"# FOOTER\n");
fprintf(fp,"TEXT: " XYHT YELLOW FONT_TINY "\n",x+5,y+5,20,"generated by hddashgen V." HDDASHGEN_VERSION);
fprintf(fp,"\n");
/*
* Now write the PANEL element. It must be the first visible element.
* therefore we have to rewind the file and insert this line.
*/
rewind(fp);
fprintf(fp,"PANEL: TITLE=\"%s\" W=%d H=%d" WHITE BGBLUE "\n",filename,w,h);
fclose(fp); /* close file */
// printf("} leave level <%s>\n",match);
return(1);
}
#define next_column() p1=p2; \
while(*p1 && isspace(*p1)) p1++; \
p2=p1; \
while(*p2 && !isspace(*p2)) p2++; \
*p2++=0
int main(int argc, char* argv[]) {
char *line = NULL;
char *p1,*p2;
size_t size;
hddashgen_set_defaults(); /* Set default dashboard directory. */
kommandozeile(argc, argv); /* process command line */
/* Read from stdin */
while(getline(&line,&size,stdin)!=-1) {
p1=line;
if(*p1==0) continue;
while(*p1 && isspace(*p1)) p1++;
if(*p1==0) continue;
if(*p1=='#') continue;
p2=p1;
while(*p2 && !isspace(*p2)) p2++;
*p2++=0;
topics[anztopics].name=strdup(p1);
next_column(); topics[anztopics].anz=atoi(p1);
next_column(); topics[anztopics].typ=strdup(p1);
next_column(); topics[anztopics].inhalt=strdup(p1);
anztopics++;
if(anztopics>=MAX_ANZ_TOPICS) {
printf("ERROR: Maximum number of topics reached. Cannot handle more than %d.",MAX_ANZ_TOPICS);
break;
}
}
free(line);
printf("hddashgen: Have %d topics from list.\n",anztopics);
do_level(0,NULL,"mainroot_hd.dash");
free_topics();
return(EX_OK);
}