Compare commits
6 Commits
Author | SHA1 | Date |
---|---|---|
Christoph Haas | ffb81213bb | |
Christoph Haas | e66d13e5a3 | |
Christoph Haas | 5543d707df | |
Christoph Haas | 724b025795 | |
Christoph Haas | dea4e8253e | |
Christoph Haas | 243486ec64 |
|
@ -1 +1,7 @@
|
|||
gclogger
|
||||
gclogger
|
||||
build
|
||||
cmake-build-debug
|
||||
cmake-build-release
|
||||
.vscode
|
||||
.idea
|
||||
.clion
|
|
@ -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
|
|
@ -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
21
LICENSE
|
@ -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.
|
35
Makefile
35
Makefile
|
@ -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)
|
25
README.md
25
README.md
|
@ -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
|
||||
```
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
130
include/ini.h
130
include/ini.h
|
@ -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__ */
|
|
@ -0,0 +1 @@
|
|||
EXTERNAL STATIC LIBRARIES GO HERE
|
|
@ -0,0 +1 @@
|
|||
Subproject commit da3ae5feb5353fce48cadc34b6ea79de5ac05ca1
|
|
@ -1,4 +0,0 @@
|
|||
# Ignore everything in this directory
|
||||
*
|
||||
# Except this file
|
||||
!.gitignore
|
|
@ -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)
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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();
|
||||
}
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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
|
349
src/gclogger.c
349
src/gclogger.c
|
@ -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
109
src/gmc.c
|
@ -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
269
src/ini.c
|
@ -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);
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
UNIT TESTS GO HERE
|
Loading…
Reference in New Issue