Initial commit

This commit is contained in:
mh 2020-10-11 21:06:19 +02:00
commit e36bdfefaf
11 changed files with 674 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
*~
Debug/
template-fonts/

11
Makefile Normal file
View File

@ -0,0 +1,11 @@
all: Debug/tempreader
clean:
rm -f Debug/*.o
rm -f Debug/tempreader
Debug/tempreader: main.cpp humireader.cpp humireader.h
g++ -g -Wall -o Debug/tempreader humireader.cpp main.cpp

25
README.md Normal file
View File

@ -0,0 +1,25 @@
## HumiReader / TurtleWatch
### Humireader
Small C++-project reading temperature + humidity from the
refurbished Arduino-based "HumiMaster" (another pet project)
via then RS232-Debug-output of the device.
The "humireader"-class is responsible for reading and converting the data.
The main.cpp (results in the tempreader binary) converts the retrieved data
so it can be consumed in a Bash-script. Start w/o params to get more infos.
### Turtlewatch
The turtlewatch.sh uses the tempreader to retrieve data an generate a HTMl
status page, monitoring the environment of my Dad's turtles when they are stored
in a cooler when in winter-hiatus.
Check vars at beginning of script. Place html-files form the template-folder
in the $WEBPATH as defined in the script.
Fonts used in the templates are from:
- Bitwise: https://www.dafont.com/de/bitwise.font
- Turtles: https://www.dafont.com/de/keyas-turtles.font

161
humireader.cpp Normal file
View File

@ -0,0 +1,161 @@
#include "humireader.h"
HumiReader::HumiReader(const std::string devPath) : devPath(std::move(devPath)) {
}
HumiReader::~HumiReader() {
closeDevice();
}
void HumiReader::closeDevice() {
if (fd > 0) close(fd);
fd = -1;
}
int HumiReader::setupDevice() {
const int speed = B2400;
fd = open(devPath.c_str(), O_RDWR | O_NOCTTY | O_SYNC | O_NONBLOCK);
if (fd <= 0) {
throw std::runtime_error("Can't open device\n");
//fprintf(stderr, "Can't open device\n");
//return 0;
}
struct termios tty;
if (tcgetattr(fd, &tty) < 0) {
throw std::runtime_error(std::string("Error from tcgetattr: ")
.append(strerror(errno))
.append(std::string("\n")));
//fprintf(stderr, "Error from tcgetattr: %s\n", strerror(errno));
//return 0;
}
cfsetospeed(&tty, (speed_t)speed);
cfsetispeed(&tty, (speed_t)speed);
tty.c_cflag |= (CLOCAL | CREAD); /* ignore modem controls */
tty.c_cflag &= ~CSIZE;
tty.c_cflag |= CS8; /* 8-bit characters */
tty.c_cflag &= ~PARENB; /* no parity bit */
tty.c_cflag &= ~CSTOPB; /* only need 1 stop bit */
tty.c_cflag &= ~CRTSCTS; /* no hardware flowcontrol */
/* fetch bytes as they become available */
tty.c_cc[VMIN] = 1;
tty.c_cc[VTIME] = 1;
if (tcsetattr(fd, TCSANOW, &tty) != 0) {
throw std::runtime_error(std::string("Error from tcsetattr: ")
.append(strerror(errno))
.append(std::string("\n")));
//fprintf(stderr, "Error from tcsetattr: %s\n", strerror(errno));
//fflush(0);
//return 0;
}
return 1;
}
int HumiReader::parseLine(const char* buf) {
if (buf) {
//printf("Got: %s\n", buf);
int len = strlen(buf);
if (len > 15 && buf[0] == '.') {
char t=buf[2];
errno = 0;
int tempRes = strtol(&buf[5], nullptr, 10);
if (errno != 0) return -1;
int humiRes = strtol(&buf[10], nullptr, 10);
if (errno != 0) return -1;
//printf ("\t- %c %i %i\n", t, tempRes, humiRes);
if (t == 'A') {
currentResult.sensorATemp = tempRes;
currentResult.sensorARelHumi = humiRes;
hasAResult = true;
}
else {
currentResult.sensorITemp = tempRes;
currentResult.sensorIRelHumi = humiRes;
hasIResult = true;
}
}
}
return 0;
}
void HumiReader::setCallback(std::function<bool(const HumiResult&)> cb) {
this->cb = cb;
}
void HumiReader::run() {
if (!setupDevice()) {
throw std::runtime_error("Can't setup device\n");
//throw "Can't setup device";
}
cancelLoop = false;
hasIResult = false;
hasAResult = false;
char buf[32+1];
char outbuf[256];
unsigned int outbufsiz =0;
unsigned int sleepMax = 10000;
while(!cancelLoop) {
int siz = read(fd, buf, sizeof(buf)-1);
if (siz < 0) {
if (errno != EAGAIN) {
throw "Read failed";
}
else {
if (sleepMax <= 0) {
throw "Timeout";
}
usleep(10*1000);
sleepMax -= 10;
}
}
else if (siz > 0) {
sleepMax = 10000;
buf[siz] = 0;
//printf("Got: %s", buf);
for (int i=0; i < siz; i++) {
char ch = buf[i];
if ((ch != 10) && (ch != 13)) {
outbuf[outbufsiz] = ch;
outbufsiz++;
outbuf[outbufsiz] = 0;
}
else {
ch = 13;
}
if (((outbufsiz > 0) && (ch == 13)) || (outbufsiz > sizeof(outbuf)-1)) {
parseLine((const char*) outbuf);
outbufsiz = 0;
// Both sensor read? then send to output
if (hasIResult && hasAResult) {
if (cb != nullptr) {
bool res = this->cb(currentResult);
if (!res) cancelLoop=true;
}
hasIResult = false;
hasAResult = false;
}
}
}
}
}
closeDevice();
}

60
humireader.h Normal file
View File

@ -0,0 +1,60 @@
#ifndef __HUMIREADER_H__
#define __HUMIREADER_H__
#include <iostream>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>
#include <errno.h>
#include <functional>
struct HumiResult {
int sensorITemp;
int sensorATemp;
int sensorIRelHumi;
int sensorARelHumi;
bool winCouldBeOpened;
};
class HumiReader {
public:
explicit HumiReader(const std::string devPath);
~HumiReader();
//void setCallback(void (*cb)(const HumiResult&));
void setCallback(std::function<bool(const HumiResult&)> cb);
void run();
private:
int fd=-1;
bool cancelLoop;
std::string devPath;
//void (*cb)(const HumiResult&);
std::function<bool(const HumiResult&)> cb;
void closeDevice();
int setupDevice();
int parseLine(const char *buf);
bool hasIResult;
bool hasAResult;
HumiResult currentResult;
};
#endif

144
main.cpp Normal file
View File

@ -0,0 +1,144 @@
#include "humireader.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <functional>
void showHelp() {
fprintf(stderr, "# Parameters");
fprintf(stderr, "# [--once] [--help] [--(a|i)(t|h)min] [--(a|i)(t|h)max] <dev-path>\n");
fprintf(stderr, "# --once: run once then exit, other read/output continously \n");
fprintf(stderr, "# -- help: this\n");
fprintf(stderr, "# devPath: e.g. /dev/ttyUSB0");
fprintf(stderr, "# other parameters for emitting warnings when limits exceeded like:\n");
fprintf(stderr, "# (a|i) sensor A or sensor I of Humimaster\n");
fprintf(stderr, "# (t|h) limit for temp or humidty\n");
fprintf(stderr, "# min or max temp or humidity (degree C or 0-100 in percent)\n");
fprintf(stderr, "# On limit exceed output will by like TR_SENS_A_TEMPFAIL=1\n");
}
int main(int argc, char **argv)
{
std::string devPath;
// Min max limit for sensor a / i, humidity / temp
int ahmin=0;
int ahmax=100;
int atmin=-9999;
int atmax=9999;
int ihmin=0;
int ihmax=100;
int itmin=-999;
int itmax=999;
bool runOnce = false;
std::string prmMask = std::string("--abcde=");
std::cout << "# TempReader 0.1 (c) 2018 MH" << std::endl;
std::cout << "# (reads temp+humidty from 'HumiMaster')" << std::endl;
try {
for(int i=1; i < argc; i++) {
auto s = std::string{argv[i]};
auto p = s.substr(0, prmMask.size());
auto limitNam = std::string();
int *currLimit = nullptr;
//fprintf(stderr, "%i %i %s\n",i, argc, s.c_str());
if (p.compare("--ahmin=") == 0) {
limitNam = "TR_A_HUMI_MIN";
currLimit = &ahmin;
}
else if (p.compare("--ahmax=") == 0) {
limitNam = "TR_A_HUMI_MAX";
currLimit = &ahmax;
}
else if (p.compare("--atmin=") == 0) {
limitNam = "TR_A_TEMP_MIN";
currLimit = &atmin;
}
else if (p.compare("--atmax=") == 0) {
limitNam = "TR_A_TEMP_MAX";
currLimit = &atmax;
}
else if (p.compare("--ihmin=") == 0) {
limitNam = "TR_I_HUMI_MIN";
currLimit = &ihmin;
}
else if (p.compare("--ihmax=") == 0) {
limitNam = "TR_I_HUMI_MAX";
currLimit = &ihmax;
}
else if (p.compare("--itmin=") == 0) {
limitNam = "TR_I_TEMP_MIN";
currLimit = &itmin;
}
else if (p.compare("--itmax=") == 0) {
limitNam = "TR_I_TEMP_MAX";
currLimit = &itmax;
}
else if (s.compare("--once") == 0) {
runOnce = true;
}
else if (s.compare("--help") == 0) {
showHelp();
return 0;
}
else if (i == argc -1) {
devPath = s;
}
if (currLimit != nullptr) {
*currLimit=strtol(&s.c_str()[prmMask.size()], nullptr, 10);
std::cout << limitNam << "=" << *currLimit << std::endl;
}
}
if (argc == 0 || devPath.size() == 0) {
showHelp();
return 1;
}
HumiReader reader(devPath);
reader.setCallback([&, runOnce](const HumiResult& res) -> bool {
fprintf(stdout, "# ------------\n");
fprintf(stdout, "TR_NEW_CYCLE=1\n");
fprintf(stdout, "TR_SENS_A_TEMP=%.2i\n", res.sensorATemp);
fprintf(stdout, "TR_SENS_A_TEMP_DISP=\"%+.3i°C\"\n", res.sensorATemp);
fprintf(stdout, "TR_SENS_A_HUMI=%i\n", res.sensorARelHumi);
fprintf(stdout, "TR_SENS_A_HUMI_DISP=\"%.3i%%rH\"\n", res.sensorARelHumi);
fprintf(stdout, "TR_SENS_I_TEMP=%.2i\n", res.sensorITemp);
fprintf(stdout, "TR_SENS_I_TEMP_DISP=\"%+.3i°C\"\n", res.sensorITemp);
fprintf(stdout, "TR_SENS_I_HUMI=%i\n", res.sensorIRelHumi);
fprintf(stdout, "TR_SENS_I_HUMI_DISP=\"%.3i%%rH\"\n", res.sensorIRelHumi);
//fprintf(stdout, "A: %+.3iC°, %.3i%%rH | I: %+.3iC°, %.3i%%rH\n", res.sensorATemp, res.sensorARelHumi, res.sensorITemp, res.sensorIRelHumi);
int attrip = (res.sensorATemp < atmin || res.sensorATemp > atmax) ? 1 : 0;
int ahtrip = (res.sensorARelHumi < ahmin || res.sensorARelHumi > ahmax) ? 1 : 0;
int ittrip = (res.sensorITemp < itmin || res.sensorITemp > itmax) ? 1 : 0;
int ihtrip = (res.sensorIRelHumi < ihmin || res.sensorIRelHumi > ihmax) ? 1 : 0;
int trip = attrip || ahtrip || ittrip || ihtrip ? 1 : 0;
fprintf(stdout, "TR_SENS_A_TEMP_TRIP=%i\n", attrip);
fprintf(stdout, "TR_SENS_A_HUMI_TRIP=%i\n", ahtrip);
fprintf(stdout, "TR_SENS_I_TEMP_TRIP=%i\n", ittrip);
fprintf(stdout, "TR_SENS_I_HUMI_TRIP=%i\n", ihtrip);
fprintf(stdout, "TR_TRIP=%i\n",trip);
//fprintf(stdout, "\n", res.sensorITemp, res.sensorIRelHumi);
return !runOnce;
});
reader.run();
return 0;
}
catch (const std::exception& e) {
std::cerr << "Error executing " << e.what();
return 1;
}
}

2
startturtlewatch.sh Executable file
View File

@ -0,0 +1,2 @@
cd /usr/src/eprj/tempreader
nohup sh turtlewatch.sh &

92
templates/index.html Normal file
View File

@ -0,0 +1,92 @@
<html>
<head>
<meta http-equiv="expires" content="0">
<meta http-equiv="refresh" content="30">
<style type="text/css">
@font-face {
font-family: 'Bitwise';
src: URL('bitwise.ttf') format('truetype');
}
@font-face {
font-family: 'Turtle';
src: URL('turtle.ttf') format('truetype');
}
body {
background: black;
color: red;
}
.turtle {
font-family: Turtle;
margin-right: 5px;
}
.header {
font-family: 'Bitwise';
font-size: 12vh;
text-align: center;
width:100%;
height: 12vh;
text-align: center;
}
.header-alarm {
visibility: visible
}
.outer {
width: 100%;
height: 80%;
display: flex;
flex-flow: column;
justify-content: center;
}
.top {
font-family: 'Bitwise';
font-size: 9vw;
width:100%;
text-align: center;
}
.footer {
font-family: 'Bitwise';
font-size: 7vh;
text-align: right;
width:100%;
height: 8%;
text-align: right;
}
</style>
</head>
<body>
<div class="header">
<div class="header-alarm">
Alarm!!
</div>
</div>
<div class="outer">
<div class="top sensor-i">
O: 18&deg;C, 58% rH
</div>
<div class="top sensor-o">
U: 17&deg;C, 39% rH
</div>
</div>
<div class="footer">
<span class="turtle">n</span><span>Stand: So 20:56:01 11.10.20</span>
</div>
</body>
</html>

View File

@ -0,0 +1,9 @@
<html>
<head>
<meta http-equiv="expires" content="0">
<meta http-equiv="refresh" content="30">
</head>
<body >
<pre style='color:red'>500 Kaputt/Inaktiv. F5?</h3>
</body>

92
templates/template.html Normal file
View File

@ -0,0 +1,92 @@
<html>
<head>
<meta http-equiv="expires" content="0">
<meta http-equiv="refresh" content="30">
<style type="text/css">
@font-face {
font-family: 'Bitwise';
src: URL('bitwise.ttf') format('truetype');
}
@font-face {
font-family: 'Turtle';
src: URL('turtle.ttf') format('truetype');
}
body {
background: black;
color: {TRIPCOLOR};
}
.turtle {
font-family: Turtle;
margin-right: 5px;
}
.header {
font-family: 'Bitwise';
font-size: 12vh;
text-align: center;
width:100%;
height: 12vh;
text-align: center;
}
.header-alarm {
visibility: {TRIP}
}
.outer {
width: 100%;
height: 80%;
display: flex;
flex-flow: column;
justify-content: center;
}
.top {
font-family: 'Bitwise';
font-size: 9vw;
width:100%;
text-align: center;
}
.footer {
font-family: 'Bitwise';
font-size: 7vh;
text-align: right;
width:100%;
height: 8%;
text-align: right;
}
</style>
</head>
<body>
<div class="header">
<div class="header-alarm">
Alarm!!
</div>
</div>
<div class="outer">
<div class="top sensor-i">
O: {IT}&deg;C, {IH}% rH
</div>
<div class="top sensor-o">
U: {AT}&deg;C, {AH}% rH
</div>
</div>
<div class="footer">
<span class="turtle">n</span><span>Stand: {DATE}</span>
</div>
</body>
</html>

75
turtlewatch.sh Normal file
View File

@ -0,0 +1,75 @@
#!/bin/sh
PIDFILE="/run/turtlewatch.pid"
WEBPATH="/var/www/zeus/htdocs/turtletemp"
TEMPREADER="Debug/tempreader"
stop() {
echo "Setting stop page"
cat "$WEBPATH/template-off.html" > $WEBPATH/index.html
rm $PIDFILE
}
showerrorpage() {
echo "Setting error page"
cat "$WEBPATH/template-off.html" > $WEBPATH/index.html
}
trap stop EXIT
run() {
TR_NEW_CYCLE=0
for i in `$TEMPREADER --once --atmin=5 --atmax=11 --itmin=5 --itmax=11 --ihmin=40 --ihmax=80 /dev/ttyUSB0 | grep -v "^#"`; do
echo $i
eval export $i
done
#echo "--"
#set
#echo "vv"
if [ "$TR_NEW_CYCLE" != "1" ]; then
showerrorpage
return
fi
if [ "$TR_TRIP" == "1" ]; then
TRIPCOLOR="red"
TRIP="visible"
else
TRIPCOLOR="green"
TRIP="hidden"
fi
DATE=`date +"%a %H:%M:%S %d.%m.%y"`
cat "$WEBPATH/template.html" \
| sed "s/{TRIPCOLOR}/$TRIPCOLOR/g" \
| sed "s/{IT}/$TR_SENS_I_TEMP/g" \
| sed "s/{IH}/$TR_SENS_I_HUMI/g" \
| sed "s/{AT}/$TR_SENS_A_TEMP/g" \
| sed "s/{AH}/$TR_SENS_A_HUMI/g" \
| sed "s/{DATE}/$DATE/g" \
| sed "s/{TRIP}/$TRIP/g" \
> $WEBPATH/index.html
}
if [ -f $PIDFILE ]; then
echo "Already running, killing other instance "`cat $PIDFILE`
kill -TERM `cat $PIDFILE`
sleep 1
rm $PIDFILE
fi
echo "Abort with STRG-C"
echo $$ > $PIDFILE
while [ "1" == "1" ]; do
run
sleep 10
done