Compare commits

...

6 Commits
master ... c++

26 changed files with 777 additions and 1061 deletions

8
.gitignore vendored
View File

@ -1 +1,7 @@
gclogger
gclogger
build
cmake-build-debug
cmake-build-release
.vscode
.idea
.clion

View File

@ -1,21 +0,0 @@
# This file is only necessary for the gitlab to github sync and does not belong to the gclogger code.
#before_script:
# - ssh-keyscan -t rsa,dsa,ecdsa github.com > /etc/ssh/ssh_known_hosts
stages:
- mirror
variables:
MIRROR_REPOSITORY: "git@github.com:h44z/gclogger.git"
mirror-github:
stage: mirror
cache: {}
script:
# Do a mirror clone of the repository
- git clone --mirror $CI_REPOSITORY_URL
# Mirror push it to the destination
- cd $CI_PROJECT_NAME.git
- git push --mirror $MIRROR_REPOSITORY
# Cleanup
- cd ..; rm -rf $CI_PROJECT_NAME.git

8
CMakeLists.txt Normal file
View File

@ -0,0 +1,8 @@
cmake_minimum_required(VERSION 3.14)
project(gclogger)
set(CMAKE_CXX_STANDARD 17)
# Add sub CMakeList.txt
add_subdirectory(src)
add_subdirectory(libs/inipp)

21
LICENSE
View File

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2018 Christoph Haas
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,35 +0,0 @@
# Copyright 2018 Christoph Haas
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of the standard MIT license. See LICENSE for more details.
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)

View File

@ -1,25 +0,0 @@
# Geiger Counter Logger
Version 0.1
Copyright (C) 2018 Christoph Haas, christoph.h@sprinternet.at
This program comes with ABSOLUTELY NO WARRANTY.
This is free software, and you are welcome to redistribute it.
gclogger is a lightweight daemon for reporting your
Geiger counter readings to various radiation monitoring websites.
Tested Geiger counter devices:
- GMC-320 and GMC-320 Plus
http://www.gqelectronicsllc.com/comersus/store/LeftStart.asp?idCategory=50
## Usage:
Compile the project using make.
Then edit the example config file and run gclogger:
```
./gclogger -c gclogger.ini -d
```

351
data/GQ-RFC1201.txt Normal file
View File

@ -0,0 +1,351 @@
***************************************************
GQ-RFC1201
GQ Geiger Counter Communication Protocol
***************************************************
Ver 1.40 Jan-2015
Status of this Memo
This document specifies a GQ GMC Geiger Counter Communication Protocol for the
communication between GQ GMC Geiger Counter and a computer host via serial port, and requests discussion and suggestions for
improvements. Distribution of this memo is unlimited.
Copyright Notice
Copyright (C) GQ Electronics LLC (2012). All Rights Reserved.
Abstract
This document defines a GQ GMC Geiger Counter Communication Protocol
to support communication between GMC Geiger Counter and a computer host via serial port. The protocol allows to send data request command from a computer host to a GQ GMC geiger counter.
**************************
Serial Port configuration
**************************
For the GMC-300 V3.xx and earlier version, the serial port communication is based on a fixed baud rate.
Baud: 57600
Data bit: 8
Parity: None
Stop bit: 1
Control: None
For the GMC-300 Plus V4.xx and later version firmware, 115200 BPS is used.
For GMC-320, the serial port communication baud rate is variable. It should be one of the followings:
1200,2400,4800,9600,14400,19200,28800,38400,57600,115200 BPS. The factory default is 115200 BPS.
**************************
Command format
**************************
A valid command start with ASCII '<' and ended with ASCII '>>'. Both command and parameters are in between '<' and '>>'.
Command is a ASCII string. All parameters of command are true value in hexadecimal.
Direction: All commands are initiated from computer(HOST).
**************************
Commands
**************************
1. Get hardware model and version
Command: <GETVER>>
Return: total 14 bytes ASCII chars from GQ GMC unit. It includes 7 bytes hardware model and 7 bytes firmware version.
e.g.: GMC-300Re 2.10
Firmware supported: GMC-280, GMC-300 Re.2.0x, Re.2.10 or later
2. Get current CPM value
Command: <GETCPM>>
Return: A 16 bit unsigned integer is returned. In total 2 bytes data return from GQ GMC unit. The first byte is MSB byte data and second byte is LSB byte data.
e.g.: 00 1C the returned CPM is 28.
Firmware supported: GMC-280, GMC-300 Re.2.0x, Re.2.10 or later
3. Turn on the GQ GMC heartbeat
Note: This command enable the GQ GMC unit to send count per second data to host every second automatically.
Command: <HEARTBEAT1>>
Return: A 16 bit unsigned integer is returned every second automatically. Each data package consist of 2 bytes data from GQ GMC unit. The first byte is MSB byte data and second byte is LSB byte data.
e.g.: 10 1C the returned 1 second count is 28. Only lowest 14 bits are used for the valid data bit. The highest bit 15 and bit 14 are reserved data bits.
Firmware supported: GMC-280, GMC-300 Re.2.10 or later
4. Turn off the GQ GMC heartbeat
Command: <HEARTBEAT0>>
Return: None
Firmware supported: Re.2.10 or later
5. Get battery voltage status
Command: <GETVOLT>>
Return: one byte voltage value of battery (X 10V)
e.g.: return 62(hex) is 9.8V
Firmware supported: GMC-280, GMC-300 Re.2.0x, Re.2.10 or later
6. Request history data from internal flash memory
Command: <SPIR[A2][A1][A0][L1][L0]>>
A2,A1,A0 are three bytes address data, from MSB to LSB. The L1,L0 are the data length requested. L1 is high byte of 16 bit integer and L0 is low byte.
The length normally not exceed 4096 bytes in each request.
Return: The history data in raw byte array.
Comment: The minimum address is 0, and maximum address value is the size of the flash memory of the GQ GMC Geiger count. Check the user manual for particular model flash size.
Firmware supported: GMC-300 Re.2.0x, Re.2.10 or later
7. Get configuration data
Command: <GETCFG>>
Return: The configuration data. Total 256 bytes will be returned.
Firmware supported: GMC-280, GMC-300 Re.2.10 or later
8. Erase all configuration data
Command: <ECFG>>
Return: 0xAA
Firmware supported: GMC-280, GMC-300 Re.2.10 or later
9. Write configuration data
Command: <WCFG[A0][D0]>>
A0 is the address and the D0 is the data byte(hex).
Return: 0xAA
Firmware supported: GMC-280, GMC-300 Re.2.10 or later
10. send a key
Command: <key[D0]>>
D0 is the key value from 0 to 3. It represents software key S1~S4.
Return: none
Firmware supported: GMC-280, GMC-300 Re.2.0x, Re.2.10 or later
Note: for Re.2.11 or later, each key can be a ASCII string: <KEY0>>,<KEY1>>,<KEY2>>,<KEY3>>
11. get serial number
Command: <GETSERIAL>>
Return: serial number in 7 bytes.
Firmware supported: GMC-280, GMC-300 Re.2.11 or later
12. Power OFF
Command: <POWEROFF>>
Return: none
Firmware supported: GMC-280, GMC-300 Re.2.11 or later
13. Reload/Update/Refresh Configuration
Command: <CFGUPDATE>>
Return: 0xAA
Firmware supported: GMC-280, GMC-300 Re.2.20 or later
14. Set realtime clock year
command: <SETDATEYY[D0]>>
D0 is the year value in hexdecimal
Return: 0xAA
Firmware supported: GMC-280, GMC-300 Re.2.23 or later
15. Set realtime clock month
command: <SETDATEMM[D0]>>
D0 is the month value in hexdecimal
Return: 0xAA
Firmware supported: GMC-280, GMC-300 Re.2.23 or later
16. Set realtime clock day
command: <SETDATEDD[D0]>>
D0 is the day of the month value in hexdecimal
Return: 0xAA
Firmware supported: GMC-280, GMC-300 Re.2.23 or later
17. Set realtime clock hour
command: <SETTIMEHH[D0]>>
D0 is the hourvalue in hexdecimal
Return: 0xAA
Firmware supported: GMC-280, GMC-300 Re.2.23 or later
18. Set realtime clock minute
command: <SETTIMEMM[D0]>>
D0 is the minute value in hexdecimal
Return: 0xAA
Firmware supported: GMC-280, GMC-300 Re.2.23 or later
19. Set realtime clock second
command: <SETTIMESS[D0]>>
D0 is the second value in hexdecimal
Return: 0xAA
Firmware supported: GMC-280, GMC-300 Re.2.23 or later
20. Reset unit to factory default
command: <FACTORYRESET>>
Return: 0xAA
Firmware supported: GMC-280, GMC-300 Re.3.00 or later
21. Reboot unit
command: <REBOOT>>
Return: None
Firmware supported: GMC-280, GMC-300 Re.3.00 or later
22. Set year date and time
command: <SETDATETIME[YYMMDDHHMMSS]>>
Return: 0xAA
Firmware supported: GMC-280, GMC-300 Re.3.00 or later
23. Get year date and time
command: <GETDATETIME>>
Return: Seven bytes data: YY MM DD HH MM SS 0xAA
Firmware supported: GMC-280, GMC-300 Re.3.00 or later
24. Get temperature
command: <GETTEMP>>
Return: Four bytes celsius degree data in hexdecimal: BYTE1,BYTE2,BYTE3,BYTE4
Here: BYTE1 is the integer part of the temperature.
BYTE2 is the decimal part of the temperature.
BYTE3 is the negative signe if it is not 0. If this byte is 0, the then current temperture is greater than 0, otherwise the temperature is below 0.
` BYTE4 always 0xAA
Firmware supported: GMC-320 Re.3.01 or later
25. Get gyroscope data
command: <GETGYRO>>
Return: Seven bytes gyroscope data in hexdecimal: BYTE1,BYTE2,BYTE3,BYTE4,BYTE5,BYTE6,BYTE7
Here: BYTE1,BYTE2 are the X position data in 16 bits value. The first byte is MSB byte data and second byte is LSB byte data.
BYTE3,BYTE4 are the Y position data in 16 bits value. The first byte is MSB byte data and second byte is LSB byte data.
BYTE5,BYTE6 are the Z position data in 16 bits value. The first byte is MSB byte data and second byte is LSB byte data.
BYTE7 always 0xAA
Firmware supported: GMC-320 Re.3.01 or later
26. Power ON
Command: <POWERON>>
Return: none
Firmware supported: GMC-280, GMC-300, GMC-320 Re.3.10 or later

View File

@ -4,7 +4,7 @@
; Serial device filename
; ------------------------
[device]
[serial]
port=/dev/ttyUSB0
baud=115200
@ -42,5 +42,4 @@ interval=60
; Log to CSV file
; ----------------------------------------------
[csv]
;path=/tmp/gclog.csv
;path=/tmp/gclog.csv

View File

@ -1,61 +0,0 @@
/*
* Copyright 2018 Christoph Haas
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the standard MIT license. See LICENSE for more details.
*/
#ifndef _GCLOGGER_H_
#define _GCLOGGER_H_
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <string.h>
#include <time.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_type;
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

View File

@ -1,33 +0,0 @@
/*
* Copyright 2018 Christoph Haas
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the standard MIT license. See LICENSE for more details.
*/
#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

View File

@ -1,130 +0,0 @@
/* 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__ */

1
libs/.gitkeep Normal file
View File

@ -0,0 +1 @@
EXTERNAL STATIC LIBRARIES GO HERE

1
libs/inipp Submodule

@ -0,0 +1 @@
Subproject commit da3ae5feb5353fce48cadc34b6ea79de5ac05ca1

4
obj/.gitignore vendored
View File

@ -1,4 +0,0 @@
# Ignore everything in this directory
*
# Except this file
!.gitignore

7
src/CMakeLists.txt Normal file
View File

@ -0,0 +1,7 @@
# Notice name prefix of this variable, set by CMake according
# to value given with "project()" in the root CMakeLists.txt.
include_directories(${gclogger_SOURCE_DIR}/include)
add_executable(gclogger main.cpp GcLogger.cpp GcLogger.h SerialPort.cpp SerialPort.h GmcDevice.cpp GmcDevice.h)
# I assume you want to use LibProject as a library in MainProject.
include_directories(${gclogger_SOURCE_DIR}/libs)
link_directories(${gclogger_SOURCE_DIR}/libs)

63
src/GcLogger.cpp Normal file
View File

@ -0,0 +1,63 @@
//
// Created by h44z on 07.05.19.
//
#include <fstream>
#include "GcLogger.h"
void GcLogger::setup(const string &deviceSerialPort, int deviceBaudRate) {
baudRate = deviceBaudRate;
serialPort = deviceSerialPort;
cout << "Serial port configuration: port=" << deviceSerialPort << " baud=" << deviceBaudRate << endl;
device = new GmcDevice(deviceSerialPort, deviceBaudRate);
if (!device->isConnected()) {
cout << "Failed to connect to device!" << endl;
} else {
cout << "Device connected!" << endl;
isSetup = true;
}
}
int GcLogger::run() {
if (!isSetup) {
cout << "Setup not completed successfully, cannot run gclogger!" << endl;
return EXIT_FAILURE;
} else {
cout << "Running gclogger!" << endl;
string deviceVersion = device->getVersion();
cout << "GMC Device version: " << deviceVersion << endl;
int cpm = device->getCPM();
cout << "CPM: " << cpm << endl;
float temperature = device->getTemperature();
cout << "Temperature: " << temperature << endl;
device->close();
return EXIT_SUCCESS;
}
}
void GcLogger::readIni(const string &filePath) {
cout << "Reading configuration from: " << filePath << endl;
ifstream is(filePath);
if (!is.is_open()) {
cout << "Failed to open configuration file: " << filePath << endl;
} else {
this->ini.parse(is);
int iniBaudRate = -1;
string iniSerialPort;
inipp::extract(ini.sections["serial"]["baud"], iniBaudRate);
inipp::extract(ini.sections["serial"]["port"], iniSerialPort);
this->setup(iniSerialPort, iniBaudRate);
}
}

33
src/GcLogger.h Normal file
View File

@ -0,0 +1,33 @@
//
// Created by h44z on 07.05.19.
//
#ifndef GCLOGGER_GCLOGGER_H
#define GCLOGGER_GCLOGGER_H
#include <iostream>
#include <string>
#include "GmcDevice.h"
#include "inipp/inipp/inipp.h"
using namespace std;
class GcLogger {
bool isSetup = false;
string serialPort;
int baudRate;
inipp::Ini<char> ini;
GmcDevice *device;
void setup(const string &, int);
public:
void readIni(const string &);
int run();
};
#endif //GCLOGGER_GCLOGGER_H

109
src/GmcDevice.cpp Normal file
View File

@ -0,0 +1,109 @@
//
// Created by h44z on 07.05.19.
//
#include "GmcDevice.h"
GmcDevice::GmcDevice(const string &serialPort, int baud) {
device = new SerialPort(serialPort, baud);
if (!device->isOpen()) {
cout << "Failed to open gmc device!" << endl;
} else {
cout << "Connection to gmc device established!" << endl;
if (!setHeartbeatOff()) {
cout << "Failed to disable heartbeat!" << endl;
device->serialClose();
}
}
}
GmcDevice::~GmcDevice() {
close();
}
bool GmcDevice::close() {
return device->serialClose();
}
int GmcDevice::getCPM() {
if (!device->isOpen()) {
cout << "Device is not connected, failed to read CPM!" << endl;
return -1;
}
string cmd = "<GETCPM>>";
vector<uint8_t> result;
if (device->serialWrite(cmd) == cmd.length()) {
result = device->serialRead(2); // cpm result has size 2
} else {
cout << "Failed to send command to device!" << endl;
}
return result[0] * 256 + result[1];
}
float GmcDevice::getTemperature() {
if (!device->isOpen()) {
cout << "Device is not connected, failed to read temperature!" << endl;
return -1;
}
string cmd = "<GETTEMP>>";
vector<uint8_t> result;
if (device->serialWrite(cmd) == cmd.length()) {
result = device->serialRead(4); // temp result has size 4
} else {
cout << "Failed to send command to device!" << endl;
}
int sign = result[2] == 0 ? 1 : -1;
float temp = result[0]; // integer part
temp += static_cast<float>(result[1] / 10.0); // float part
temp = temp * sign;
return temp;
}
string GmcDevice::getVersion() {
if (!device->isOpen()) {
cout << "Device is not connected, failed to read version!" << endl;
return "";
}
string cmd = "<GETVER>>";
vector<uint8_t> result;
if (device->serialWrite(cmd) == cmd.length()) {
result = device->serialRead(14); // version result has size 14
} else {
cout << "Failed to send command to device!" << endl;
}
string strResult(result.begin(), result.end());
return strResult;
}
bool GmcDevice::setHeartbeatOff() {
if (!device->isOpen()) {
cout << "Device is not connected, failed to disable heartbeat!" << endl;
return false;
}
string cmd = "<HEARTBEAT0>>";
string result;
if (device->serialWrite(cmd) == cmd.length()) {
return device->clearInput(100); // clear 100 chars
} else {
cout << "Failed to send command to device!" << endl;
}
return false;
}
bool GmcDevice::isConnected() {
return device->isOpen();
}

32
src/GmcDevice.h Normal file
View File

@ -0,0 +1,32 @@
//
// Created by h44z on 07.05.19.
//
#ifndef GCLOGGER_GMCDEVICE_H
#define GCLOGGER_GMCDEVICE_H
#include "SerialPort.h"
class GmcDevice {
SerialPort *device;
public:
GmcDevice(const string &serialPort, int baud);
~GmcDevice();
bool close();
bool isConnected();
int getCPM();
float getTemperature();
string getVersion();
bool setHeartbeatOff();
};
#endif //GCLOGGER_GMCDEVICE_H

100
src/SerialPort.cpp Normal file
View File

@ -0,0 +1,100 @@
//
// Created by h44z on 07.05.19.
//
#include "SerialPort.h"
SerialPort::SerialPort() {
fd = -1;
tioBaud = B0;
tio.c_cflag = CS8 | CREAD | CLOCAL; // 8n1
tio.c_cc[VMIN] = 0;
tio.c_cc[VTIME] = 5;
}
int SerialPort::getTioBaud(int baud) {
switch (baud) {
case 9600:
return B9600;
case 19200:
return B19200;
case 38400:
return B38400;
case 57600:
return B57600;
case 115200:
return B115200;
default:
return B0;
}
}
SerialPort::SerialPort(const string &serialPort, int baud) : SerialPort() {
tioBaud = getTioBaud(baud);
fd = open(serialPort.c_str(), O_RDWR | O_NOCTTY | O_NDELAY);
if (fd == -1) { // if open is unsucessful
cout << "Unable to open serial port: " << serialPort << endl;
} else {
fcntl(fd, F_SETFL, 0);
cout << "Serial port opened: " << serialPort << endl;
if (cfsetspeed(&tio, tioBaud) == 0) { // set baud speed
if (tcsetattr(fd, TCSANOW, &tio) == 0) { // apply baud speed change
cout << "Serial port initialized successfully to BAUD: " << baud << " (" << tioBaud << ")" << endl;
}
} else {
// something failed
close(fd);
fd = -1;
}
}
}
bool SerialPort::isOpen() {
return fd != -1;
}
SerialPort::~SerialPort() {
serialClose();
}
vector<uint8_t> SerialPort::serialRead(int length) {
auto *buf = reinterpret_cast<uint8_t *> (calloc(length, sizeof(uint8_t)));
read(fd, buf, length);
vector<uint8_t> result(buf, buf + length);
free(buf);
return result;
}
int SerialPort::serialWrite(const string &data) {
return write(fd, data.c_str(), data.length());
}
bool SerialPort::clearInput(int size) {
char ch;
// flush input stream
for (int i = 0; i < size; i++) {
if (read(fd, &ch, 1) == 0)
return true; // found end of stream
}
return false; // still data left in the buffer
}
bool SerialPort::serialClose() {
cout << "Closing serial port" << endl;
if (isOpen()) {
bool result = close(fd) == 0;
fd = -1;
return result;
}
return false;
}

45
src/SerialPort.h Normal file
View File

@ -0,0 +1,45 @@
//
// Created by h44z on 07.05.19.
//
#ifndef GCLOGGER_SERIALPORT_H
#define GCLOGGER_SERIALPORT_H
#include <iostream>
#include <string>
#include <vector>
#include <termios.h> // POSIX terminal control definitions
#include <unistd.h>
#include <fcntl.h>
using namespace std;
class SerialPort {
int fd;
struct termios tio;
int tioBaud;
static int getTioBaud(int);
public:
// Constructor
SerialPort();
SerialPort(const string &, int);
~SerialPort();
bool isOpen();
bool serialClose();
vector<uint8_t> serialRead(int);
int serialWrite(const string &);
bool clearInput(int);
};
#endif //GCLOGGER_SERIALPORT_H

View File

@ -1,349 +0,0 @@
/*
* Copyright 2018 Christoph Haas
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the standard MIT license. See LICENSE for more details.
*/
#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", "type")) {
pconfig->custlog_type = 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_type = "GET";
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 = curl_easy_init();
if(curl) {
char *url_buffer = NULL;
size_t url_size;
char *post_buffer = NULL;
size_t post_size;
if(strncmp(config.custlog_type, "GET", 3) == 0) {
// 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);
if(config.debug) {
printf("Final url: %s\n", url_buffer);
}
} else {
url_buffer = strdup(config.custlog_url);
}
curl_easy_setopt(curl, CURLOPT_URL, url_buffer);
if(strncmp(config.custlog_type, "POST", 4) == 0) {
// first get size of final url
post_size = snprintf(NULL, 0, "%s=%s&%s=%d&%s=%f&%s=%s&%s=%ld&%s=%f&%s=%f&%s=%s",
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
post_buffer = (char *)malloc(post_size + 1);
snprintf(post_buffer, post_size+1,"%s=%s&%s=%d&%s=%f&%s=%s&%s=%ld&%s=%f&%s=%f&%s=%s",
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);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, post_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);
if(post_buffer != NULL)
free(post_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);
// setup curl
curl_global_init(CURL_GLOBAL_DEFAULT);
// 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;
}

109
src/gmc.c
View File

@ -1,109 +0,0 @@
/*
* Copyright 2018 Christoph Haas
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the standard MIT license. See LICENSE for more details.
*/
#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
View File

@ -1,269 +0,0 @@
/* 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);
}

17
src/main.cpp Normal file
View File

@ -0,0 +1,17 @@
//
// Created by h44z on 07.05.19.
//
#include <iostream>
#include "GcLogger.h"
using namespace std;
int main(int argc, char *argv[]) {
cout << "Starting up gclogger..." << endl;
GcLogger logger;
logger.readIni("/home/h44z/workspace/gclogger/data/sample.ini");
return logger.run();
}

1
tests/.gitkeep Normal file
View File

@ -0,0 +1 @@
UNIT TESTS GO HERE