From e36bdfefaf89962b2215003e4c28a9c0c20d57d3 Mon Sep 17 00:00:00 2001 From: mh Date: Sun, 11 Oct 2020 21:06:19 +0200 Subject: [PATCH] Initial commit --- .gitignore | 3 + Makefile | 11 +++ README.md | 25 ++++++ humireader.cpp | 161 ++++++++++++++++++++++++++++++++++++ humireader.h | 60 ++++++++++++++ main.cpp | 144 ++++++++++++++++++++++++++++++++ startturtlewatch.sh | 2 + templates/index.html | 92 +++++++++++++++++++++ templates/template-off.html | 9 ++ templates/template.html | 92 +++++++++++++++++++++ turtlewatch.sh | 75 +++++++++++++++++ 11 files changed, 674 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 README.md create mode 100644 humireader.cpp create mode 100644 humireader.h create mode 100644 main.cpp create mode 100755 startturtlewatch.sh create mode 100644 templates/index.html create mode 100644 templates/template-off.html create mode 100644 templates/template.html create mode 100644 turtlewatch.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6f53bb7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*~ +Debug/ +template-fonts/ diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..e57ff67 --- /dev/null +++ b/Makefile @@ -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 + diff --git a/README.md b/README.md new file mode 100644 index 0000000..4a761da --- /dev/null +++ b/README.md @@ -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 diff --git a/humireader.cpp b/humireader.cpp new file mode 100644 index 0000000..a8db6a8 --- /dev/null +++ b/humireader.cpp @@ -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 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(); +} diff --git a/humireader.h b/humireader.h new file mode 100644 index 0000000..af0c3da --- /dev/null +++ b/humireader.h @@ -0,0 +1,60 @@ +#ifndef __HUMIREADER_H__ +#define __HUMIREADER_H__ + + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + + + +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 cb); + + void run(); + + +private: + int fd=-1; + bool cancelLoop; + std::string devPath; + + //void (*cb)(const HumiResult&); + std::function cb; + + + void closeDevice(); + int setupDevice(); + int parseLine(const char *buf); + + bool hasIResult; + bool hasAResult; + HumiResult currentResult; + +}; + + +#endif + diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..bb38af9 --- /dev/null +++ b/main.cpp @@ -0,0 +1,144 @@ +#include "humireader.h" + +#include +#include +#include +#include + +#include + + +void showHelp() { + fprintf(stderr, "# Parameters"); + fprintf(stderr, "# [--once] [--help] [--(a|i)(t|h)min] [--(a|i)(t|h)max] \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; + } +} + diff --git a/startturtlewatch.sh b/startturtlewatch.sh new file mode 100755 index 0000000..eab9e31 --- /dev/null +++ b/startturtlewatch.sh @@ -0,0 +1,2 @@ +cd /usr/src/eprj/tempreader +nohup sh turtlewatch.sh & diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..7632f8f --- /dev/null +++ b/templates/index.html @@ -0,0 +1,92 @@ + + + + + + + + + +
+
+ Alarm!! +
+
+
+
+ O: 18°C, 58% rH +
+ +
+ U: 17°C, 39% rH +
+
+ + + + + + \ No newline at end of file diff --git a/templates/template-off.html b/templates/template-off.html new file mode 100644 index 0000000..b28104d --- /dev/null +++ b/templates/template-off.html @@ -0,0 +1,9 @@ + + + + + + +
500 Kaputt/Inaktiv. F5?
+
+
diff --git a/templates/template.html b/templates/template.html
new file mode 100644
index 0000000..06f547c
--- /dev/null
+++ b/templates/template.html
@@ -0,0 +1,92 @@
+
+
+    
+    
+    
+
+
+
+
+
+
+ Alarm!! +
+
+
+
+ O: {IT}°C, {IH}% rH +
+ +
+ U: {AT}°C, {AH}% rH +
+
+ + + + + + \ No newline at end of file diff --git a/turtlewatch.sh b/turtlewatch.sh new file mode 100644 index 0000000..331880c --- /dev/null +++ b/turtlewatch.sh @@ -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 + +