First running version
This commit is contained in:
parent
4b6daded8e
commit
ef6408c5a6
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
gclogger
|
30
Makefile
Normal file
30
Makefile
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
EXE = gclogger
|
||||||
|
|
||||||
|
SRC_DIR = src
|
||||||
|
OBJ_DIR = obj
|
||||||
|
|
||||||
|
SRC = $(wildcard $(SRC_DIR)/*.c)
|
||||||
|
OBJ = $(SRC:$(SRC_DIR)/%.c=$(OBJ_DIR)/%.o)
|
||||||
|
|
||||||
|
CPPFLAGS += -Iinclude
|
||||||
|
CFLAGS += -Wall -Wextra -Os
|
||||||
|
|
||||||
|
#LDFLAGS += -Llib
|
||||||
|
LDLIBS += -lcurl
|
||||||
|
|
||||||
|
.PHONY: all clean
|
||||||
|
|
||||||
|
all: $(EXE)
|
||||||
|
|
||||||
|
# Linking:
|
||||||
|
$(EXE): $(OBJ)
|
||||||
|
$(CC) $(LDFLAGS) $^ $(LDLIBS) -o $@
|
||||||
|
|
||||||
|
# Compiling:
|
||||||
|
$(OBJ_DIR)/%.o: $(SRC_DIR)/%.c
|
||||||
|
$(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@
|
||||||
|
|
||||||
|
# Cleaning:
|
||||||
|
clean:
|
||||||
|
$(RM) $(OBJ)
|
||||||
|
$(RM) $(EXE)
|
45
gclogger.example.ini
Normal file
45
gclogger.example.ini
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
;
|
||||||
|
; Geiger Counter Logger - configuration file
|
||||||
|
;
|
||||||
|
|
||||||
|
; Serial device filename
|
||||||
|
; ------------------------
|
||||||
|
[device]
|
||||||
|
port=/dev/ttyUSB0
|
||||||
|
baud=115200
|
||||||
|
|
||||||
|
; GPS location of your sensor
|
||||||
|
; -----------------------------
|
||||||
|
location=Innsbruck, AT
|
||||||
|
latitude=47.2530
|
||||||
|
longitude=11.3969
|
||||||
|
|
||||||
|
; Polling interval in seconds (60..3600)
|
||||||
|
; ----------------------------------------
|
||||||
|
interval=60
|
||||||
|
|
||||||
|
;
|
||||||
|
; Log portal configurations
|
||||||
|
;
|
||||||
|
; Uncomment sections to enable them
|
||||||
|
;
|
||||||
|
|
||||||
|
; Custom REST logging portal
|
||||||
|
; ----------------------------------------------
|
||||||
|
[custlog]
|
||||||
|
;id=example_device_id
|
||||||
|
;url=https://api.mygclog.com/log
|
||||||
|
;param_id=id
|
||||||
|
;param_cpm=cpm
|
||||||
|
;param_temp=temp
|
||||||
|
;param_lng=lng
|
||||||
|
;param_lat=lat
|
||||||
|
;param_loc=loc
|
||||||
|
;param_version=version
|
||||||
|
;param_time=captured_at
|
||||||
|
|
||||||
|
; Log to CSV file
|
||||||
|
; ----------------------------------------------
|
||||||
|
[csv]
|
||||||
|
;path=/tmp/gclog.csv
|
||||||
|
|
54
include/gclogger.h
Normal file
54
include/gclogger.h
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
#ifndef _GCLOGGER_H_
|
||||||
|
#define _GCLOGGER_H_
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <curl/curl.h>
|
||||||
|
|
||||||
|
#include "ini.h"
|
||||||
|
#include "gmc.h"
|
||||||
|
|
||||||
|
#define GCLOGGER_VERSION "0.1"
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
bool debug;
|
||||||
|
char* dev_port;
|
||||||
|
int dev_baud;
|
||||||
|
char* dev_location;
|
||||||
|
float dev_latitude;
|
||||||
|
float dev_longitude;
|
||||||
|
int dev_interval;
|
||||||
|
|
||||||
|
char* radmon_user;
|
||||||
|
char* radmon_pass;
|
||||||
|
char* safecast_key;
|
||||||
|
char* safecast_device;
|
||||||
|
char* netc_id;
|
||||||
|
char* custlog_url;
|
||||||
|
char* custlog_id;
|
||||||
|
char* custlog_param_id;
|
||||||
|
char* custlog_param_cpm;
|
||||||
|
char* custlog_param_temp;
|
||||||
|
char* custlog_param_lng;
|
||||||
|
char* custlog_param_lat;
|
||||||
|
char* custlog_param_loc;
|
||||||
|
char* custlog_param_version;
|
||||||
|
char* custlog_param_time;
|
||||||
|
char* csv_path;
|
||||||
|
} configuration;
|
||||||
|
|
||||||
|
static int confighandler(void* config, const char* section, const char* name, const char* value);
|
||||||
|
bool str_isset(char *str);
|
||||||
|
void init_configuration(configuration* config);
|
||||||
|
void signal_handler(int sig);
|
||||||
|
bool send_custlog(const configuration config, int cpm, float temperature, const char *version, struct tm *tm);
|
||||||
|
bool send_tocsv(const configuration config, int cpm, float temperature, const char *version, const struct tm *tm);
|
||||||
|
void show_usage();
|
||||||
|
|
||||||
|
#endif
|
26
include/gmc.h
Normal file
26
include/gmc.h
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
#ifndef _GC_GMC_H_
|
||||||
|
#define _GC_GMC_H_
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <termios.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
// GQ Geiger Counter Communication Protocol: http://www.gqelectronicsllc.com/download/GQ-RFC1201.txt
|
||||||
|
|
||||||
|
int gmc_open(const char *device, int baud);
|
||||||
|
void gmc_close(int device);
|
||||||
|
|
||||||
|
int gmc_get_cpm(int device);
|
||||||
|
float gmc_get_temperature(int device);
|
||||||
|
int gmc_get_version(int device, char *buf);
|
||||||
|
|
||||||
|
bool gmc_set_heartbeat_off(int device);
|
||||||
|
|
||||||
|
int gmc_write(int device, const char *cmd);
|
||||||
|
int gmc_read(int device, char *buf, int length);
|
||||||
|
bool gmc_flush(int device);
|
||||||
|
|
||||||
|
#endif
|
130
include/ini.h
Normal file
130
include/ini.h
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
/* inih -- simple .INI file parser
|
||||||
|
|
||||||
|
inih is released under the New BSD license (see LICENSE.txt). Go to the project
|
||||||
|
home page for more info:
|
||||||
|
|
||||||
|
https://github.com/benhoyt/inih
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __INI_H__
|
||||||
|
#define __INI_H__
|
||||||
|
|
||||||
|
/* Make this header file easier to include in C++ code */
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
/* Nonzero if ini_handler callback should accept lineno parameter. */
|
||||||
|
#ifndef INI_HANDLER_LINENO
|
||||||
|
#define INI_HANDLER_LINENO 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Typedef for prototype of handler function. */
|
||||||
|
#if INI_HANDLER_LINENO
|
||||||
|
typedef int (*ini_handler)(void* user, const char* section,
|
||||||
|
const char* name, const char* value,
|
||||||
|
int lineno);
|
||||||
|
#else
|
||||||
|
typedef int (*ini_handler)(void* user, const char* section,
|
||||||
|
const char* name, const char* value);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Typedef for prototype of fgets-style reader function. */
|
||||||
|
typedef char* (*ini_reader)(char* str, int num, void* stream);
|
||||||
|
|
||||||
|
/* Parse given INI-style file. May have [section]s, name=value pairs
|
||||||
|
(whitespace stripped), and comments starting with ';' (semicolon). Section
|
||||||
|
is "" if name=value pair parsed before any section heading. name:value
|
||||||
|
pairs are also supported as a concession to Python's configparser.
|
||||||
|
|
||||||
|
For each name=value pair parsed, call handler function with given user
|
||||||
|
pointer as well as section, name, and value (data only valid for duration
|
||||||
|
of handler call). Handler should return nonzero on success, zero on error.
|
||||||
|
|
||||||
|
Returns 0 on success, line number of first error on parse error (doesn't
|
||||||
|
stop on first error), -1 on file open error, or -2 on memory allocation
|
||||||
|
error (only when INI_USE_STACK is zero).
|
||||||
|
*/
|
||||||
|
int ini_parse(const char* filename, ini_handler handler, void* user);
|
||||||
|
|
||||||
|
/* Same as ini_parse(), but takes a FILE* instead of filename. This doesn't
|
||||||
|
close the file when it's finished -- the caller must do that. */
|
||||||
|
int ini_parse_file(FILE* file, ini_handler handler, void* user);
|
||||||
|
|
||||||
|
/* Same as ini_parse(), but takes an ini_reader function pointer instead of
|
||||||
|
filename. Used for implementing custom or string-based I/O (see also
|
||||||
|
ini_parse_string). */
|
||||||
|
int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler,
|
||||||
|
void* user);
|
||||||
|
|
||||||
|
/* Same as ini_parse(), but takes a zero-terminated string with the INI data
|
||||||
|
instead of a file. Useful for parsing INI data from a network socket or
|
||||||
|
already in memory. */
|
||||||
|
int ini_parse_string(const char* string, ini_handler handler, void* user);
|
||||||
|
|
||||||
|
/* Nonzero to allow multi-line value parsing, in the style of Python's
|
||||||
|
configparser. If allowed, ini_parse() will call the handler with the same
|
||||||
|
name for each subsequent line parsed. */
|
||||||
|
#ifndef INI_ALLOW_MULTILINE
|
||||||
|
#define INI_ALLOW_MULTILINE 1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Nonzero to allow a UTF-8 BOM sequence (0xEF 0xBB 0xBF) at the start of
|
||||||
|
the file. See http://code.google.com/p/inih/issues/detail?id=21 */
|
||||||
|
#ifndef INI_ALLOW_BOM
|
||||||
|
#define INI_ALLOW_BOM 1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Chars that begin a start-of-line comment. Per Python configparser, allow
|
||||||
|
both ; and # comments at the start of a line by default. */
|
||||||
|
#ifndef INI_START_COMMENT_PREFIXES
|
||||||
|
#define INI_START_COMMENT_PREFIXES ";#"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Nonzero to allow inline comments (with valid inline comment characters
|
||||||
|
specified by INI_INLINE_COMMENT_PREFIXES). Set to 0 to turn off and match
|
||||||
|
Python 3.2+ configparser behaviour. */
|
||||||
|
#ifndef INI_ALLOW_INLINE_COMMENTS
|
||||||
|
#define INI_ALLOW_INLINE_COMMENTS 1
|
||||||
|
#endif
|
||||||
|
#ifndef INI_INLINE_COMMENT_PREFIXES
|
||||||
|
#define INI_INLINE_COMMENT_PREFIXES ";"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Nonzero to use stack for line buffer, zero to use heap (malloc/free). */
|
||||||
|
#ifndef INI_USE_STACK
|
||||||
|
#define INI_USE_STACK 1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Maximum line length for any line in INI file (stack or heap). Note that
|
||||||
|
this must be 3 more than the longest line (due to '\r', '\n', and '\0'). */
|
||||||
|
#ifndef INI_MAX_LINE
|
||||||
|
#define INI_MAX_LINE 200
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Nonzero to allow heap line buffer to grow via realloc(), zero for a
|
||||||
|
fixed-size buffer of INI_MAX_LINE bytes. Only applies if INI_USE_STACK is
|
||||||
|
zero. */
|
||||||
|
#ifndef INI_ALLOW_REALLOC
|
||||||
|
#define INI_ALLOW_REALLOC 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Initial size in bytes for heap line buffer. Only applies if INI_USE_STACK
|
||||||
|
is zero. */
|
||||||
|
#ifndef INI_INITIAL_ALLOC
|
||||||
|
#define INI_INITIAL_ALLOC 200
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Stop parsing on first error (default is to keep parsing). */
|
||||||
|
#ifndef INI_STOP_ON_FIRST_ERROR
|
||||||
|
#define INI_STOP_ON_FIRST_ERROR 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* __INI_H__ */
|
4
obj/.gitignore
vendored
Normal file
4
obj/.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
# Ignore everything in this directory
|
||||||
|
*
|
||||||
|
# Except this file
|
||||||
|
!.gitignore
|
300
src/gclogger.c
Normal file
300
src/gclogger.c
Normal file
@ -0,0 +1,300 @@
|
|||||||
|
#include "gclogger.h"
|
||||||
|
|
||||||
|
extern char *optarg;
|
||||||
|
extern int optind;
|
||||||
|
|
||||||
|
static volatile bool running = true;
|
||||||
|
|
||||||
|
static int confighandler(void* config, const char* section, const char* name, const char* value) {
|
||||||
|
configuration* pconfig = (configuration*)config;
|
||||||
|
|
||||||
|
#define MATCH(s, n) strcmp(section, s) == 0 && strcmp(name, n) == 0
|
||||||
|
if (MATCH("device", "port")) {
|
||||||
|
pconfig->dev_port = strdup(value);
|
||||||
|
} else if (MATCH("device", "baud")) {
|
||||||
|
pconfig->dev_baud = atoi(value);
|
||||||
|
} else if (MATCH("device", "location")) {
|
||||||
|
pconfig->dev_location = strdup(value);
|
||||||
|
} else if (MATCH("device", "latitude")) {
|
||||||
|
pconfig->dev_latitude = atof(value);
|
||||||
|
} else if (MATCH("device", "longitude")) {
|
||||||
|
pconfig->dev_longitude = atof(value);
|
||||||
|
} else if (MATCH("device", "interval")) {
|
||||||
|
pconfig->dev_interval = atoi(value);
|
||||||
|
} else if (MATCH("custlog", "url")) {
|
||||||
|
pconfig->custlog_url = strdup(value);
|
||||||
|
} else if (MATCH("custlog", "id")) {
|
||||||
|
pconfig->custlog_id = strdup(value);
|
||||||
|
} else if (MATCH("custlog", "param_id")) {
|
||||||
|
pconfig->custlog_param_id = strdup(value);
|
||||||
|
} else if (MATCH("custlog", "param_cpm")) {
|
||||||
|
pconfig->custlog_param_cpm = strdup(value);
|
||||||
|
} else if (MATCH("custlog", "param_temp")) {
|
||||||
|
pconfig->custlog_param_temp = strdup(value);
|
||||||
|
} else if (MATCH("custlog", "param_lng")) {
|
||||||
|
pconfig->custlog_param_lng = strdup(value);
|
||||||
|
} else if (MATCH("custlog", "param_lat")) {
|
||||||
|
pconfig->custlog_param_lat = strdup(value);
|
||||||
|
} else if (MATCH("custlog", "param_loc")) {
|
||||||
|
pconfig->custlog_param_loc = strdup(value);
|
||||||
|
} else if (MATCH("custlog", "param_version")) {
|
||||||
|
pconfig->custlog_param_version = strdup(value);
|
||||||
|
} else if (MATCH("custlog", "param_time")) {
|
||||||
|
pconfig->custlog_param_time = strdup(value);
|
||||||
|
} else if (MATCH("csv", "path")) {
|
||||||
|
pconfig->csv_path = strdup(value);
|
||||||
|
} else {
|
||||||
|
return 0; /* unknown section/name, error */
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool str_isset(char *str) {
|
||||||
|
return str != NULL && str[0] != '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
void init_configuration(configuration* config) {
|
||||||
|
config->debug = false;
|
||||||
|
|
||||||
|
config->dev_port = "";
|
||||||
|
config->dev_baud = 115200;
|
||||||
|
config->dev_location = "";
|
||||||
|
config->dev_latitude = 0.0;
|
||||||
|
config->dev_longitude = 0.0;
|
||||||
|
config->dev_interval = 60;
|
||||||
|
config->custlog_url = "";
|
||||||
|
config->custlog_id = "";
|
||||||
|
config->custlog_param_id = "id";
|
||||||
|
config->custlog_param_cpm = "cpm";
|
||||||
|
config->custlog_param_temp = "temp";
|
||||||
|
config->custlog_param_lng = "lng";
|
||||||
|
config->custlog_param_lat = "lat";
|
||||||
|
config->custlog_param_loc = "loc";
|
||||||
|
config->custlog_param_version = "version";
|
||||||
|
config->custlog_param_time = "time";
|
||||||
|
config->csv_path = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
void signal_handler(int sig) {
|
||||||
|
switch (sig) {
|
||||||
|
case SIGTERM:
|
||||||
|
case SIGINT:
|
||||||
|
case SIGQUIT:
|
||||||
|
case SIGHUP:
|
||||||
|
running = false;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool send_custlog(const configuration config, int cpm, float temperature, const char *version, struct tm *tm) {
|
||||||
|
CURL *curl;
|
||||||
|
CURLcode res;
|
||||||
|
|
||||||
|
curl_global_init(CURL_GLOBAL_DEFAULT);
|
||||||
|
|
||||||
|
curl = curl_easy_init();
|
||||||
|
|
||||||
|
if(curl) {
|
||||||
|
char *url_buffer;
|
||||||
|
size_t url_size;
|
||||||
|
// first get size of final url
|
||||||
|
url_size = snprintf(NULL, 0, "%s?%s=%s&%s=%d&%s=%f&%s=%s&%s=%ld&%s=%f&%s=%f&%s=%s",
|
||||||
|
config.custlog_url,
|
||||||
|
config.custlog_param_id, config.custlog_id,
|
||||||
|
config.custlog_param_cpm, cpm,
|
||||||
|
config.custlog_param_temp, temperature,
|
||||||
|
config.custlog_param_version, version,
|
||||||
|
config.custlog_param_time, mktime(tm),
|
||||||
|
config.custlog_param_lng, config.dev_longitude,
|
||||||
|
config.custlog_param_lat, config.dev_latitude,
|
||||||
|
config.custlog_param_loc, config.dev_location);
|
||||||
|
|
||||||
|
// now allocate buffer and build url
|
||||||
|
url_buffer = (char *)malloc(url_size + 1);
|
||||||
|
snprintf(url_buffer, url_size+1,"%s?%s=%s&%s=%d&%s=%f&%s=%s&%s=%ld&%s=%f&%s=%f&%s=%s",
|
||||||
|
config.custlog_url,
|
||||||
|
config.custlog_param_id, config.custlog_id,
|
||||||
|
config.custlog_param_cpm, cpm,
|
||||||
|
config.custlog_param_temp, temperature,
|
||||||
|
config.custlog_param_version, version,
|
||||||
|
config.custlog_param_time, mktime(tm),
|
||||||
|
config.custlog_param_lng, config.dev_longitude,
|
||||||
|
config.custlog_param_lat, config.dev_latitude,
|
||||||
|
config.custlog_param_loc, config.dev_location);
|
||||||
|
|
||||||
|
printf("final url: %s\n", url_buffer);
|
||||||
|
|
||||||
|
curl_easy_setopt(curl, CURLOPT_URL, url_buffer);
|
||||||
|
|
||||||
|
#ifdef SKIP_PEER_VERIFICATION
|
||||||
|
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef SKIP_HOSTNAME_VERIFICATION
|
||||||
|
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
res = curl_easy_perform(curl);
|
||||||
|
if(res != CURLE_OK)
|
||||||
|
printf("curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
|
||||||
|
else
|
||||||
|
printf("curl_easy_perform() ok!");
|
||||||
|
|
||||||
|
free(url_buffer);
|
||||||
|
curl_easy_cleanup(curl);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool send_tocsv(const configuration config, int cpm, float temperature, const char *version, const struct tm *tm) {
|
||||||
|
FILE *fp;
|
||||||
|
|
||||||
|
if ((fp = fopen(config.csv_path, "a")) == NULL) {
|
||||||
|
printf("Error while opening the csv file '%s'.\n", config.csv_path);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *ts = asctime(tm);
|
||||||
|
ts[strlen(ts) - 1] = 0; // get rid of \n
|
||||||
|
fprintf(fp, "%s,%d,%07.3f,%s\n", ts, cpm, temperature, version);
|
||||||
|
|
||||||
|
fclose(fp);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void show_usage() {
|
||||||
|
printf("Geiger Counter Logger\n");
|
||||||
|
printf("Version %s\n", GCLOGGER_VERSION);
|
||||||
|
printf("Copyright (C) 2018 Christoph Haas, christoph.h@sprinternet.at\n\n");
|
||||||
|
printf("This program comes with ABSOLUTELY NO WARRANTY.\n");
|
||||||
|
printf("This is free software, and you are welcome to redistribute it.\n\n");
|
||||||
|
printf("Usage: gclogger -c <file> [-d]\n");
|
||||||
|
printf(" -c <file> configuration file path\n");
|
||||||
|
printf(" -d enable verbose mode\n\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char *argv[]) {
|
||||||
|
int opt = 0;
|
||||||
|
int gc_fd = -1;
|
||||||
|
configuration config;
|
||||||
|
|
||||||
|
// setup signal handlers
|
||||||
|
signal(SIGTERM, signal_handler);
|
||||||
|
signal(SIGINT, signal_handler);
|
||||||
|
signal(SIGQUIT, signal_handler);
|
||||||
|
signal(SIGHUP, signal_handler);
|
||||||
|
|
||||||
|
// parse config file
|
||||||
|
init_configuration(&config);
|
||||||
|
while ((opt = getopt(argc, argv, "c:d")) != -1) {
|
||||||
|
switch (opt) {
|
||||||
|
case 'd':
|
||||||
|
config.debug = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'c':
|
||||||
|
if (ini_parse(optarg, confighandler, &config) < 0) {
|
||||||
|
printf("Can't load config file '%s'\n", optarg);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
printf("Config loaded from '%s': device_port=%s, interval=%d\n",
|
||||||
|
optarg, config.dev_port, config.dev_interval);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default: /* '?' */
|
||||||
|
show_usage();
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// additional check if extra/no arguments where given
|
||||||
|
if (optind < argc || argc == 1) {
|
||||||
|
show_usage();
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if device port is set
|
||||||
|
if(!str_isset(config.dev_port)) {
|
||||||
|
printf("Device port must be set!'\n");
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
gc_fd = gmc_open(config.dev_port, config.dev_baud);
|
||||||
|
|
||||||
|
// check if device connection was successful
|
||||||
|
if(gc_fd == -1) {
|
||||||
|
printf("Connection to device (%s) failed!'\n", config.dev_port);
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// read version
|
||||||
|
char version[15] = { 0 };
|
||||||
|
if(gmc_get_version(gc_fd, version) == -1) {
|
||||||
|
printf("Unable to read Geiger counter version!'\n");
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.debug) {
|
||||||
|
printf("GC VERSION: %s\n", version);
|
||||||
|
}
|
||||||
|
|
||||||
|
time_t last = time(NULL);
|
||||||
|
int cpm, sum = 0, count = 0, tcount = 0;
|
||||||
|
float temperature, tsum = 0;
|
||||||
|
|
||||||
|
// main loop
|
||||||
|
while (running) {
|
||||||
|
// read cpm
|
||||||
|
if ((cpm = gmc_get_cpm(gc_fd)) > 0) {
|
||||||
|
sum += cpm;
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// read temperature
|
||||||
|
tsum += gmc_get_temperature(gc_fd);
|
||||||
|
tcount++;
|
||||||
|
|
||||||
|
if (difftime(time(NULL), last) >= config.dev_interval) {
|
||||||
|
if (count > 0) {
|
||||||
|
struct tm *tm = gmtime(&last);
|
||||||
|
cpm = sum / count;
|
||||||
|
temperature = tsum / tcount;
|
||||||
|
|
||||||
|
if (config.debug) {
|
||||||
|
printf("CPM: %d (= %d/%d), TEMP: %07.3f, Timestamp: %s\n", cpm, sum, count, temperature, asctime(tm));
|
||||||
|
}
|
||||||
|
|
||||||
|
// log to custom REST api
|
||||||
|
if (str_isset(config.custlog_url)) {
|
||||||
|
printf("Uploading to %s.\n", config.custlog_url);
|
||||||
|
if(!send_custlog(config, cpm, temperature, version, tm)) {
|
||||||
|
printf("Upload to %s failed.\n", config.custlog_url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// log to csv
|
||||||
|
if (str_isset(config.csv_path)) {
|
||||||
|
if(!send_tocsv(config, cpm, temperature, version, tm)) {
|
||||||
|
printf("Logging to %s failed.\n", config.csv_path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
time(&last);
|
||||||
|
sum = tsum = count = tcount = 0;
|
||||||
|
} else {
|
||||||
|
printf("Reading ZERO value from Geiger tube.\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sleep(1); // sleep one second
|
||||||
|
}
|
||||||
|
|
||||||
|
gmc_close(gc_fd);
|
||||||
|
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
102
src/gmc.c
Normal file
102
src/gmc.c
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
#include "gmc.h"
|
||||||
|
|
||||||
|
int gmc_open(const char *device, int baud) {
|
||||||
|
int gc_fd = -1;
|
||||||
|
struct termios tio;
|
||||||
|
|
||||||
|
memset(&tio, 0, sizeof(struct termios));
|
||||||
|
tio.c_cflag = CS8 | CREAD | CLOCAL; // 8n1
|
||||||
|
tio.c_cc[VMIN] = 0;
|
||||||
|
tio.c_cc[VTIME] = 5;
|
||||||
|
|
||||||
|
int tio_baud = B115200;
|
||||||
|
switch(baud) {
|
||||||
|
case 9600: tio_baud = B9600; break;
|
||||||
|
case 19200: tio_baud = B19200; break;
|
||||||
|
case 38400: tio_baud = B38400; break;
|
||||||
|
case 57600: tio_baud = B57600; break;
|
||||||
|
case 115200: tio_baud = B115200; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((gc_fd = open(device, O_RDWR)) != -1) {
|
||||||
|
if (cfsetspeed(&tio, tio_baud) == 0) { // set baud speed
|
||||||
|
if (tcsetattr(gc_fd, TCSANOW, &tio) == 0) { // apply baud speed change
|
||||||
|
if (gmc_set_heartbeat_off(gc_fd)) { // disable heartbeat, we use polling
|
||||||
|
return gc_fd;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// something failed
|
||||||
|
close(gc_fd);
|
||||||
|
gc_fd = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return gc_fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
void gmc_close(int device) {
|
||||||
|
close(device);
|
||||||
|
}
|
||||||
|
|
||||||
|
int gmc_get_cpm(int device) {
|
||||||
|
const char cmd[] = "<GETCPM>>";
|
||||||
|
char buf[2] = { 0 };
|
||||||
|
|
||||||
|
if (gmc_write(device, cmd) == (ssize_t) strlen(cmd))
|
||||||
|
gmc_read(device, buf, 2);
|
||||||
|
|
||||||
|
return buf[0] * 256 + buf[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
float gmc_get_temperature(int device) {
|
||||||
|
const char cmd[] = "<GETTEMP>>";
|
||||||
|
char buf[4] = { 0 };
|
||||||
|
|
||||||
|
if (gmc_write(device, cmd) == (ssize_t) strlen(cmd))
|
||||||
|
gmc_read(device, buf, 4);
|
||||||
|
|
||||||
|
int sign = buf[2] == 0 ? 1 : -1;
|
||||||
|
float temp = buf[0];
|
||||||
|
temp += (float) buf[1] / 10;
|
||||||
|
temp = temp * sign;
|
||||||
|
return temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
int gmc_get_version(int device, char *version) {
|
||||||
|
const char cmd[] = "<GETVER>>";
|
||||||
|
|
||||||
|
if (gmc_write(device, cmd) == (ssize_t) strlen(cmd))
|
||||||
|
return gmc_read(device, version, 14);
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool gmc_set_heartbeat_off(int device) {
|
||||||
|
const char cmd[] = "<HEARTBEAT0>>";
|
||||||
|
|
||||||
|
if (gmc_write(device, cmd) == (ssize_t) strlen(cmd))
|
||||||
|
return gmc_flush(device);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int gmc_write(int device, const char *cmd) {
|
||||||
|
return write(device, cmd, strlen(cmd));
|
||||||
|
}
|
||||||
|
|
||||||
|
int gmc_read(int device, char *buf, int length) {
|
||||||
|
return read(device, buf, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool gmc_flush(int device) {
|
||||||
|
char ch;
|
||||||
|
|
||||||
|
// flush input stream (max 100 bytes)
|
||||||
|
for (int i = 0; i < 100; i++) {
|
||||||
|
if (read(device, &ch, 1) == 0)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
269
src/ini.c
Normal file
269
src/ini.c
Normal file
@ -0,0 +1,269 @@
|
|||||||
|
/* inih -- simple .INI file parser
|
||||||
|
|
||||||
|
inih is released under the New BSD license (see LICENSE.txt). Go to the project
|
||||||
|
home page for more info:
|
||||||
|
|
||||||
|
https://github.com/benhoyt/inih
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
|
||||||
|
#define _CRT_SECURE_NO_WARNINGS
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "ini.h"
|
||||||
|
|
||||||
|
#if !INI_USE_STACK
|
||||||
|
#include <stdlib.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define MAX_SECTION 50
|
||||||
|
#define MAX_NAME 50
|
||||||
|
|
||||||
|
/* Used by ini_parse_string() to keep track of string parsing state. */
|
||||||
|
typedef struct {
|
||||||
|
const char* ptr;
|
||||||
|
size_t num_left;
|
||||||
|
} ini_parse_string_ctx;
|
||||||
|
|
||||||
|
/* Strip whitespace chars off end of given string, in place. Return s. */
|
||||||
|
static char* rstrip(char* s)
|
||||||
|
{
|
||||||
|
char* p = s + strlen(s);
|
||||||
|
while (p > s && isspace((unsigned char)(*--p)))
|
||||||
|
*p = '\0';
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Return pointer to first non-whitespace char in given string. */
|
||||||
|
static char* lskip(const char* s)
|
||||||
|
{
|
||||||
|
while (*s && isspace((unsigned char)(*s)))
|
||||||
|
s++;
|
||||||
|
return (char*)s;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Return pointer to first char (of chars) or inline comment in given string,
|
||||||
|
or pointer to null at end of string if neither found. Inline comment must
|
||||||
|
be prefixed by a whitespace character to register as a comment. */
|
||||||
|
static char* find_chars_or_comment(const char* s, const char* chars)
|
||||||
|
{
|
||||||
|
#if INI_ALLOW_INLINE_COMMENTS
|
||||||
|
int was_space = 0;
|
||||||
|
while (*s && (!chars || !strchr(chars, *s)) &&
|
||||||
|
!(was_space && strchr(INI_INLINE_COMMENT_PREFIXES, *s))) {
|
||||||
|
was_space = isspace((unsigned char)(*s));
|
||||||
|
s++;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
while (*s && (!chars || !strchr(chars, *s))) {
|
||||||
|
s++;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return (char*)s;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Version of strncpy that ensures dest (size bytes) is null-terminated. */
|
||||||
|
static char* strncpy0(char* dest, const char* src, size_t size)
|
||||||
|
{
|
||||||
|
strncpy(dest, src, size);
|
||||||
|
dest[size - 1] = '\0';
|
||||||
|
return dest;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* See documentation in header file. */
|
||||||
|
int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler,
|
||||||
|
void* user)
|
||||||
|
{
|
||||||
|
/* Uses a fair bit of stack (use heap instead if you need to) */
|
||||||
|
#if INI_USE_STACK
|
||||||
|
char line[INI_MAX_LINE];
|
||||||
|
int max_line = INI_MAX_LINE;
|
||||||
|
#else
|
||||||
|
char* line;
|
||||||
|
int max_line = INI_INITIAL_ALLOC;
|
||||||
|
#endif
|
||||||
|
#if INI_ALLOW_REALLOC
|
||||||
|
char* new_line;
|
||||||
|
int offset;
|
||||||
|
#endif
|
||||||
|
char section[MAX_SECTION] = "";
|
||||||
|
char prev_name[MAX_NAME] = "";
|
||||||
|
|
||||||
|
char* start;
|
||||||
|
char* end;
|
||||||
|
char* name;
|
||||||
|
char* value;
|
||||||
|
int lineno = 0;
|
||||||
|
int error = 0;
|
||||||
|
|
||||||
|
#if !INI_USE_STACK
|
||||||
|
line = (char*)malloc(INI_INITIAL_ALLOC);
|
||||||
|
if (!line) {
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if INI_HANDLER_LINENO
|
||||||
|
#define HANDLER(u, s, n, v) handler(u, s, n, v, lineno)
|
||||||
|
#else
|
||||||
|
#define HANDLER(u, s, n, v) handler(u, s, n, v)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Scan through stream line by line */
|
||||||
|
while (reader(line, max_line, stream) != NULL) {
|
||||||
|
#if INI_ALLOW_REALLOC
|
||||||
|
offset = strlen(line);
|
||||||
|
while (offset == max_line - 1 && line[offset - 1] != '\n') {
|
||||||
|
max_line *= 2;
|
||||||
|
if (max_line > INI_MAX_LINE)
|
||||||
|
max_line = INI_MAX_LINE;
|
||||||
|
new_line = realloc(line, max_line);
|
||||||
|
if (!new_line) {
|
||||||
|
free(line);
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
|
line = new_line;
|
||||||
|
if (reader(line + offset, max_line - offset, stream) == NULL)
|
||||||
|
break;
|
||||||
|
if (max_line >= INI_MAX_LINE)
|
||||||
|
break;
|
||||||
|
offset += strlen(line + offset);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
lineno++;
|
||||||
|
|
||||||
|
start = line;
|
||||||
|
#if INI_ALLOW_BOM
|
||||||
|
if (lineno == 1 && (unsigned char)start[0] == 0xEF &&
|
||||||
|
(unsigned char)start[1] == 0xBB &&
|
||||||
|
(unsigned char)start[2] == 0xBF) {
|
||||||
|
start += 3;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
start = lskip(rstrip(start));
|
||||||
|
|
||||||
|
if (strchr(INI_START_COMMENT_PREFIXES, *start)) {
|
||||||
|
/* Start-of-line comment */
|
||||||
|
}
|
||||||
|
#if INI_ALLOW_MULTILINE
|
||||||
|
else if (*prev_name && *start && start > line) {
|
||||||
|
/* Non-blank line with leading whitespace, treat as continuation
|
||||||
|
of previous name's value (as per Python configparser). */
|
||||||
|
if (!HANDLER(user, section, prev_name, start) && !error)
|
||||||
|
error = lineno;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
else if (*start == '[') {
|
||||||
|
/* A "[section]" line */
|
||||||
|
end = find_chars_or_comment(start + 1, "]");
|
||||||
|
if (*end == ']') {
|
||||||
|
*end = '\0';
|
||||||
|
strncpy0(section, start + 1, sizeof(section));
|
||||||
|
*prev_name = '\0';
|
||||||
|
}
|
||||||
|
else if (!error) {
|
||||||
|
/* No ']' found on section line */
|
||||||
|
error = lineno;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (*start) {
|
||||||
|
/* Not a comment, must be a name[=:]value pair */
|
||||||
|
end = find_chars_or_comment(start, "=:");
|
||||||
|
if (*end == '=' || *end == ':') {
|
||||||
|
*end = '\0';
|
||||||
|
name = rstrip(start);
|
||||||
|
value = end + 1;
|
||||||
|
#if INI_ALLOW_INLINE_COMMENTS
|
||||||
|
end = find_chars_or_comment(value, NULL);
|
||||||
|
if (*end)
|
||||||
|
*end = '\0';
|
||||||
|
#endif
|
||||||
|
value = lskip(value);
|
||||||
|
rstrip(value);
|
||||||
|
|
||||||
|
/* Valid name[=:]value pair found, call handler */
|
||||||
|
strncpy0(prev_name, name, sizeof(prev_name));
|
||||||
|
if (!HANDLER(user, section, name, value) && !error)
|
||||||
|
error = lineno;
|
||||||
|
}
|
||||||
|
else if (!error) {
|
||||||
|
/* No '=' or ':' found on name[=:]value line */
|
||||||
|
error = lineno;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if INI_STOP_ON_FIRST_ERROR
|
||||||
|
if (error)
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
#if !INI_USE_STACK
|
||||||
|
free(line);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* See documentation in header file. */
|
||||||
|
int ini_parse_file(FILE* file, ini_handler handler, void* user)
|
||||||
|
{
|
||||||
|
return ini_parse_stream((ini_reader)fgets, file, handler, user);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* See documentation in header file. */
|
||||||
|
int ini_parse(const char* filename, ini_handler handler, void* user)
|
||||||
|
{
|
||||||
|
FILE* file;
|
||||||
|
int error;
|
||||||
|
|
||||||
|
file = fopen(filename, "r");
|
||||||
|
if (!file)
|
||||||
|
return -1;
|
||||||
|
error = ini_parse_file(file, handler, user);
|
||||||
|
fclose(file);
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* An ini_reader function to read the next line from a string buffer. This
|
||||||
|
is the fgets() equivalent used by ini_parse_string(). */
|
||||||
|
static char* ini_reader_string(char* str, int num, void* stream) {
|
||||||
|
ini_parse_string_ctx* ctx = (ini_parse_string_ctx*)stream;
|
||||||
|
const char* ctx_ptr = ctx->ptr;
|
||||||
|
size_t ctx_num_left = ctx->num_left;
|
||||||
|
char* strp = str;
|
||||||
|
char c;
|
||||||
|
|
||||||
|
if (ctx_num_left == 0 || num < 2)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
while (num > 1 && ctx_num_left != 0) {
|
||||||
|
c = *ctx_ptr++;
|
||||||
|
ctx_num_left--;
|
||||||
|
*strp++ = c;
|
||||||
|
if (c == '\n')
|
||||||
|
break;
|
||||||
|
num--;
|
||||||
|
}
|
||||||
|
|
||||||
|
*strp = '\0';
|
||||||
|
ctx->ptr = ctx_ptr;
|
||||||
|
ctx->num_left = ctx_num_left;
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* See documentation in header file. */
|
||||||
|
int ini_parse_string(const char* string, ini_handler handler, void* user) {
|
||||||
|
ini_parse_string_ctx ctx;
|
||||||
|
|
||||||
|
ctx.ptr = string;
|
||||||
|
ctx.num_left = strlen(string);
|
||||||
|
return ini_parse_stream((ini_reader)ini_reader_string, &ctx, handler,
|
||||||
|
user);
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user