#include <Arduino.h>
#include <SoftwareSerial.h> // https://github.com/plerup/espsoftwareserial/
//////////////////////////////////////////////////////////////////////////////
/// @brief Class for reading in data via a stream (serial) interface
///
/// At least one char array must be passed to the constructor as a buffer.
/// Optionally, one character each can be specified as a start and end marker
/// for the input.
///
/// If neither of these parameters is specified, the end marker is '\n' (return).
/// If no start marker but an end marker != '\n' is to be used,
/// enter '\0' as the start marker.
///
//////////////////////////////////////////////////////////////////////////////
class SerialReceive {
public:
template <size_t ITEMS>
SerialReceive(char (&buffer)[ITEMS], const char startMarker = '\0', const char endMarker = '\n')
: strBuffer {buffer}, buffLen {ITEMS}, startMarker {startMarker}, endMarker {endMarker} {
static_assert(ITEMS > 2, "Buffer deklaration too small (must be > 1)");
}
uint16_t receive(Stream &);
const char *buffer() const { return strBuffer; }
size_t bufferLen() const { return buffLen; }
size_t strLen() const { return strlen(strBuffer); }
private:
void reset(Stream &stream) {
recvInProgress = false;
index = 0;
while (stream.available()) { stream.read(); }
}
char *strBuffer;
const size_t buffLen;
const char startMarker;
const char endMarker;
bool recvInProgress {false};
byte index;
};
//////////////////////////////////////////////////////////////////////////////
/// @brief Receive data from a serial interface
///
/// @param &stream Reference to an (serial) stream object
/// @return size_t Number of characters read in
//////////////////////////////////////////////////////////////////////////////
uint16_t SerialReceive::receive(Stream &stream) {
uint16_t counter {0};
while (stream.available() > 0 && counter == 0) {
// if no start marker is specified don't wait for it
if (startMarker == '\0' && index == 0) { recvInProgress = true; }
const int rb {stream.read()};
if (recvInProgress == true) {
switch (rb == endMarker) {
case false:
if (rb == '\n' || rb == '\r') { // Input completed but endMarker was not transmitted
strBuffer[0] = '\0'; // terminate the string
reset(stream); // Clear the send buffer if data was sent without the required end marker
} else {
strBuffer[index] = rb;
index = (index < buffLen) ? index + 1 : index;
}
break;
case true:
counter = index;
strBuffer[index] = '\0'; // terminate the string
reset(stream); // Clear the send buffer for the next input
break;
}
} else if (rb == startMarker) {
recvInProgress = true;
}
}
return counter;
}
/// Class End ////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
/// Definition of constants and global variables/objects
///
//////////////////////////////////////////////////////////////////////////////
// Constants for SoftwareSerial
constexpr byte SSERIAL_RX {GPIO_NUM_26};
constexpr byte SSERIAL_TX {GPIO_NUM_27};
constexpr unsigned long BAUD_RATE {4800};
// Constants for voltage calculations
constexpr int MAX_VCC {477}; // Maximum Voltage
constexpr int MIN_VCC {310}; // Minimum Voltage
constexpr int PERCENT_DIVIDER {MAX_VCC - MIN_VCC};
constexpr byte DATA_ITEMS_FROM_SENDER {3};
constexpr unsigned char DATA_FIELD_SEPARATOR {','};
// Structure with text elements for output on the display.
// The constants will also used for snprintf!
// Caution with Unicode characters e.g. two byte chars like ° !
constexpr byte TEMPLEN {10}; // -XX.X° C0
constexpr byte HUMIDITYLEN {6}; // XXX %0
constexpr byte VCCLEN {7}; // X.XX V0
constexpr byte VCCPERCENTLEN {6}; // XXX %0
struct DisplayData {
char sTemperature[TEMPLEN];
char sHumidity[HUMIDITYLEN];
char sVcc[VCCLEN];
char sVccPercent[VCCPERCENTLEN];
};
char recBuffer[30]; // Buffer memory for receiving transmitter data
DisplayData dpData;
SoftwareSerial HC12Serial;
// SerialReceive<SoftwareSerial> rb {recBuffer}; // Object for controlling the input via a serial interface.
SerialReceive rb {recBuffer, '\0', '@'}; // Object for controlling the input via a serial interface.
//////////////////////////////////////////////////////////////////////////////
/// Forwardeclarations of used functions
///
//////////////////////////////////////////////////////////////////////////////
void prepareData(const char[], DisplayData &);
unsigned int strCnt(const char *, unsigned int, unsigned char);
bool checkContent(const char *, unsigned int);
void genTestText();
//////////////////////////////////////////////////////////////////////////////
/// Main Program
///
//////////////////////////////////////////////////////////////////////////////
void setup() {
// HC12Serial.begin(BAUD_RATE);
HC12Serial.begin(BAUD_RATE, EspSoftwareSerial::SWSERIAL_8N1, SSERIAL_RX, SSERIAL_TX);
delay(5000);
HC12Serial.println(F("\r\nSample Data (copy into the input line):"));
genTestText();
}
void loop() {
if (rb.receive(HC12Serial) > 0) {
const char *bufferPtr = rb.buffer();
unsigned int strLen = rb.strLen();
if (strCnt(bufferPtr, strLen, DATA_FIELD_SEPARATOR) == DATA_ITEMS_FROM_SENDER &&
checkContent(bufferPtr, strLen) == true) {
prepareData(bufferPtr, dpData);
} else {
prepareData("99.9,99,0.00,", dpData); // Error
}
//
// Insert the function call for the display here
//
// E.g. doDisplay(DisplayData &dpData);
// call it with "doDisplay(dpData)"
//
}
//
// Add the remaining required operations here.
//
}
//////////////////////////////////////////////////////////////////////////////
/// @brief The data received via the serial interface is prepared
/// for output on the display.
///
/// @param dataString
/// @param dpd
//////////////////////////////////////////////////////////////////////////////
void prepareData(const char dataString[], DisplayData &dpd) {
float temp;
int humidity;
float vcc;
sscanf(dataString, "%f,%d,%f,", &temp, &humidity, &vcc);
snprintf(dpd.sTemperature, TEMPLEN, "%5.1f° C", temp); // -XX.X Three digits with one decimal place + decimal point.
snprintf(dpd.sHumidity, HUMIDITYLEN, "%3d %%", humidity); // XXX Three digits with no decimal places.
snprintf(dpd.sVcc, VCCLEN, "%4.2f V", vcc); // X.XX Three digits with two decimal places + decimal point.
int vccValue = static_cast<int>(vcc * 100);
// Conversion of the VCC value into a percentage
int vccPercent = constrain(((vccValue - MIN_VCC) * 100) / PERCENT_DIVIDER, 0, 100);
snprintf(dpd.sVccPercent, VCCPERCENTLEN, "%3d %%", vccPercent); // XXX
HC12Serial.print(dpd.sTemperature);
HC12Serial.print(" - ");
HC12Serial.print(dpd.sHumidity);
HC12Serial.print(" - ");
HC12Serial.print(dpd.sVcc);
HC12Serial.print(" - ");
HC12Serial.println(dpd.sVccPercent);
}
//////////////////////////////////////////////////////////////////////////////
/// @brief Determine whether a specific character occurs in the transferred string.
/// The number of characters found is returned.
///
/// @param string
/// @param len // Length of the String
/// @param searchChar // Character you are looking for
/// @return unsigned int // Number of characters found
//////////////////////////////////////////////////////////////////////////////
unsigned int strCnt(const char *string, unsigned int len, unsigned char searchChar) {
unsigned int count = 0;
for (unsigned int i = 0; i < len; ++i) {
if (string[i] == searchChar) ++count;
}
return count;
}
//////////////////////////////////////////////////////////////////////////////
/// @brief Check whether the string passed contains only digits
/// and the characters "," , "." and "-".
///
/// @param string
/// @param len // Length of the String
/// @return true // The string corresponds to the test pattern.
/// @return false // it do not correspod to
//////////////////////////////////////////////////////////////////////////////
bool checkContent(const char *string, unsigned int len) {
bool isOk = true;
for (unsigned int i = 0; i < len; ++i) {
unsigned char Char = string[i];
switch (Char) {
case '0' ... '9': [[fallthrough]];
case '.': [[fallthrough]];
case '-': [[fallthrough]];
case ',': break;
default: isOk = false;
}
}
return isOk;
}
//////////////////////////////////////////////////////////////////////////////
/// @brief Create a test string This can be copied to the console
/// as serial input. No production code. Just for testting purposes.
///
/// @param toSend
/// @return char*
//////////////////////////////////////////////////////////////////////////////
void genTestText() {
char toSend[30];
float Temperatur {20.3};
float Luftfeuchtigkeit {57.3};
float vcc {4.32};
// if (dht_read_data(&dht, &Temperatur, &Luftfeuchtigkeit) == 1) {
char tempData[5];
char humData[5];
char vccData[5];
char trenner[] = ",";
// 4 is mininum width, 1 is precision; float value is copied onto buff
dtostrf(Temperatur, 4, 1, tempData);
dtostrf(Luftfeuchtigkeit, 2, 0, humData);
dtostrf(vcc, 4, 2, vccData); // Adjust precision as needed
// toSend[0] = '{';
strcat(toSend, tempData);
strcat(toSend, trenner);
strcat(toSend, humData);
strcat(toSend, trenner);
strcat(toSend, vccData);
strcat(toSend, trenner);
// strcat(toSend, "}");
// } else {
// strcpy(toSend, "{99.9,99,0.00,}"); // Error
// }
HC12Serial.println(toSend);
}