First running version

This commit is contained in:
2018-04-06 17:04:17 +02:00
parent 4b6daded8e
commit ef6408c5a6
10 changed files with 961 additions and 0 deletions

300
src/gclogger.c Normal file
View 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
View 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
View 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);
}