commit e36bdfefaf89962b2215003e4c28a9c0c20d57d3 Author: mh Date: Sun Oct 11 21:06:19 2020 +0200 Initial commit 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 + +