diff --git a/src/lib/ebus/message.cpp b/src/lib/ebus/message.cpp new file mode 100644 index 00000000..b174e3b8 --- /dev/null +++ b/src/lib/ebus/message.cpp @@ -0,0 +1,169 @@ +/* + * Copyright (C) John Baier 2014 + * + * This file is part of ebusd. + * + * ebusd is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ebusd is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ebusd. If not, see http://www.gnu.org/licenses/. + */ + +#include "message.h" +#include "data.h" +#include "result.h" +#include "symbol.h" +#include +#include +#include + +result_t Message::create(std::vector::iterator& it, const std::vector::iterator end, + const std::map templates, Message*& returnValue) +{ + result_t result; + // [type];class;name;[comment];[QQ];ZZ;id;fields... + if (it == end) + return RESULT_ERR_EOF; + + const char* str = (*it++).c_str(); + if (it == end) + return RESULT_ERR_EOF; + bool isSetMessage, isActiveMessage; + unsigned int pollPriority = 0; + if (strcasecmp(str, "W") == 0) { + isActiveMessage = true; + isSetMessage = true; + } else if (str[0] == 'C' || str[0] == 'c') { + isActiveMessage = false; + isSetMessage = str[1] == 'W' || str[1] == 'w'; + } else if (str[0] == 'P' || str[0] == 'p') { + isActiveMessage = true; + isSetMessage = false; + if (str[1] == 0) + pollPriority = 1; + else { + result_t result; + pollPriority = parseInt(str+1, 10, 1, 9, result); + if (result != RESULT_OK) + return result; + } + } else { + isActiveMessage = true; + isSetMessage = false; + } + + std::string clazz = *it++; + if (it == end) + return RESULT_ERR_EOF; + + std::string name = *it++; + if (it == end) + return RESULT_ERR_EOF; + if (name.length() == 0) + return RESULT_ERR_INVALID_ARG; // empty name + + std::string comment = *it++; + if (it == end) + return RESULT_ERR_EOF; + + str = (*it++).c_str(); + if (it == end) + return RESULT_ERR_EOF; + unsigned char srcAddress; + if (*str == 0 || isActiveMessage == true) + srcAddress = SYN; // no specific source defined, or ignore for active message + else { + srcAddress = parseInt(str, 16, 0, 0xff, result); + if (result != RESULT_OK) + return result; + if (isMaster(srcAddress) == false) + return RESULT_ERR_INVALID_ARG; + } + + str = (*it++).c_str(); + if (it == end) + return RESULT_ERR_EOF; + + unsigned char dstAddress = parseInt(str, 16, 0, 0xff, result); + if (result != RESULT_OK) + return result; + if (isValidAddress(dstAddress) == false) + return RESULT_ERR_INVALID_ARG; + + std::istringstream input(*it++); // message id (PBSB + optional master data) + std::vector id; + std::string token; + if (it == end) + return RESULT_ERR_EOF; + while (input.eof() == false) { + while (input.peek() == ' ') + input.get(); + if (input.eof() == true) // no more digits + break; + token.clear(); + token.push_back(input.get()); + if (input.eof() == true) + return RESULT_ERR_INVALID_ARG; // too short hex + token.push_back(input.get()); + + unsigned char value = parseInt(token.c_str(), 16, 0, 0xff, result); + if (result != RESULT_OK) + return result; // invalid hex value + id.push_back(value); + } + if (id.size() < 2 || id.size() > 6) + return RESULT_ERR_INVALID_ARG; // missing/too short/too long ID + + DataField* data = NULL; + result = DataField::create(it, end, templates, data, isSetMessage, dstAddress); + if (result != RESULT_OK) + return result; + + returnValue = new Message(clazz, name, isSetMessage, isActiveMessage, comment, srcAddress, dstAddress, id, data, pollPriority); + return RESULT_OK; +} + +result_t Message::prepare(const unsigned char srcAddress, SymbolString& masterData, std::istringstream& input, char separator) +{ + if (m_isActiveMessage == true) { + masterData.clear(); + masterData.push_back(srcAddress, false); + masterData.push_back(m_dstAddress, false); + masterData.push_back(m_id[0], false); + masterData.push_back(m_id[1], false); + masterData.push_back(m_id.size() - 2, false); // TODO adjust length + for (size_t i=2; iwrite(input, masterData, slaveData, separator); + if (result != RESULT_OK) + return result; + masterData.push_back(masterData.getCRC(), false, false); + } + return RESULT_OK; +} + +result_t Message::handle(SymbolString& masterData, SymbolString& slaveData, + std::ostringstream& output, char separator, bool answer) +{ + if (m_isActiveMessage == true) { + result_t result = m_data->read(masterData, slaveData, output, false, separator); + if (result != RESULT_OK) + return result; + } + else if (answer == true) { + std::istringstream input; // TODO create input from database of internal variables + result_t result = m_data->write(input, masterData, slaveData, separator); + if (result != RESULT_OK) + return result; + } + return RESULT_OK; +} diff --git a/src/lib/ebus/message.h b/src/lib/ebus/message.h new file mode 100644 index 00000000..8d5aadda --- /dev/null +++ b/src/lib/ebus/message.h @@ -0,0 +1,171 @@ +/* + * Copyright (C) John Baier 2014 + * + * This file is part of ebusd. + * + * ebusd is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ebusd is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ebusd. If not, see http://www.gnu.org/licenses/. + */ + +#ifndef LIBEBUS_MESSAGE_H_ +#define LIBEBUS_MESSAGE_H_ + +#include "data.h" +#include "result.h" +#include "symbol.h" +#include +#include + +/** + * @brief Base class for all kinds of bus messages. + */ +class Message +{ +public: + + /** + * @brief Constructs a new instance. + * @param class the optional device class. + * @param name the message name (unique within the same class and type). + * @param isSetMessage whether this is a set message. + * @param isActiveMessage true if message can be initiated by the daemon + * itself any any other participant, false if message can only be initiated + * by a participant other than the daemon. + * @param comment the comment. + * @param srcAddress the source address (optional if passive), or @a SYN for any. + * @param dstAddress the destination address. + * @param id the primary, secondary, and optional further ID bytes. + * @param data the @a DataField for encoding/decoding the message. + * @param pollPriority the priority for polling, or 0 for no polling at all. + */ + Message(const std::string clazz, const std::string name, const bool isSetMessage, + const bool isActiveMessage, const std::string comment, + const unsigned char srcAddress, const unsigned char dstAddress, + const std::vector id, DataField* data, + const unsigned int pollPriority) + : m_class(clazz), m_name(name), m_isSetMessage(isSetMessage), + m_isActiveMessage(isActiveMessage), m_comment(comment), + m_srcAddress(srcAddress), m_dstAddress(dstAddress), + m_id(id), m_data(data), m_pollPriority(pollPriority) {} + /** + * @brief Destructor. + */ + virtual ~Message() { delete m_data; } + /** + * @brief Factory method for creating a new instance. + * @param it the iterator to traverse for the definition parts. + * @param end the iterator pointing to the end of the definition parts. + * @param templates a map of @a DataField templates to be referenced by name. + * @param returnValue the variable in which to store the created instance. + * @return @a RESULT_OK on success, or an error code. + * Note: the caller needs to free the created instance. + */ + static result_t create(std::vector::iterator& it, const std::vector::iterator end, + const std::map templates, Message*& returnValue); + /** + * @brief Get the optional device class. + * @return the optional device class. + */ + std::string getClass() const { return m_class; } + /** + * @brief Get the message name (unique within the same class and type). + * @return the message name (unique within the same class and type). + */ + std::string getName() const { return m_name; } + /** + * @brief Get whether this is a set message. + * @return whether this is a set message. + */ + bool isSetMessage() const { return m_isSetMessage; } + /** + * @brief Get whether message can be initiated by the daemon itself and any other + * participant. + * @return true if message can be initiated by the daemon itself and any other + * participant, false if message can only be initiated by a participant + * other than the daemon. + */ + bool isActiveMessage() const { return m_isActiveMessage; } + /** + * @brief Get the comment. + * @return the comment. + */ + std::string getComment() const { return m_comment; } + /** + * @brief Get the source address. + * @return the source address, or @a SYN for any. + */ + unsigned char getSrcAddress() const { return m_srcAddress; } + /** + * @brief Get the destination address. + * @return the destination address. + */ + unsigned char getDstAddress() const { return m_dstAddress; } + /** + * @brief Get the command ID bytes. + * @return the primary, secondary, and optionally further command ID bytes. + */ + std::vector getId() const { return m_id; } + /** + * @brief Reads the value from the master or slave @a SymbolString. + * @param masterData the unescaped master data @a SymbolString for reading binary data. + * @param slaveData the unescaped slave data @a SymbolString for reading binary data. + * @param output the @a std::ostringstream to append the formatted value to. + * @param verbose whether to prepend the name, append the unit (if present), and append + * the comment in square brackets (if present). + * @param separator the separator character between multiple fields. + * @return @a RESULT_OK on success, or an error code. + */ + //result_t read(SymbolString& masterData, SymbolString& slaveData, std::ostringstream& output, + // bool verbose=false, char separator=';') = 0; + /** + * @brief Writes the value to the master or slave @a SymbolString. + * @param input the @a std::istringstream to parse the formatted value from. + * @param masterData the unescaped master data @a SymbolString for writing binary data. + * @param slaveData the unescaped slave data @a SymbolString for writing binary data. + * @param separator the separator character between multiple fields. + * @return @a RESULT_OK on success, or an error code. + */ + result_t prepare(const unsigned char srcAddress, SymbolString& masterData, + std::istringstream& input, char separator=';'); + result_t handle(SymbolString& masterData, SymbolString& slaveData, + std::ostringstream& output, char separator=';', bool answer=false); + + +private: + + /** the optional device class. */ + const std::string m_class; + /** the message name (unique within the same class and type). */ + const std::string m_name; + /** whether this is a set message. */ + const bool m_isSetMessage; + /** true if message can be initiated by the daemon itself and any other + * participant, false if message can only be initiated by a participant + * other than the daemon. */ + const bool m_isActiveMessage; + /** the comment. */ + const std::string m_comment; + /** the source address (optional if passive), or @a SYN for any. */ + const unsigned char m_srcAddress; + /** the destination address. */ + const unsigned char m_dstAddress; + /** the primary, secondary, and optionally further command ID bytes. */ + const std::vector m_id; + /** the @a DataField for encoding/decoding the message. */ + DataField* m_data; + /** the priority for polling, or 0 for no polling at all. */ + const unsigned char m_pollPriority; + +}; + +#endif // LIBEBUS_MESSAGE_H_ diff --git a/src/lib/ebus/test/test_message.cpp b/src/lib/ebus/test/test_message.cpp new file mode 100644 index 00000000..fe4a9723 --- /dev/null +++ b/src/lib/ebus/test/test_message.cpp @@ -0,0 +1,111 @@ +/* + * Copyright (C) John Baier 2014 + * + * This file is part of libebus. + * + * libebus is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * libebus is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with libebus. If not, see http://www.gnu.org/licenses/. + */ + +#include "message.h" +#include +#include + +void verify(bool expectFailMatch, std::string type, std::string input, + bool match, std::string expectStr, std::string gotStr) +{ + if (expectFailMatch == true) { + if (match == true) + std::cout << " failed " << type << " match >" << input + << "< error: unexpectedly succeeded" << std::endl; + else + std::cout << " failed " << type << " match >" << input << "< OK" + << std::endl; + } + else if (match == true) + std::cout << " " << type << " >" << input << "< OK" << std::endl; + else + std::cout << " " << type << " >" << input << "< error: got >" << gotStr + << "<, expected >" << expectStr << "<" << std::endl; +} + +int main() +{ + // message= [type];class;name;[comment];[QQ];ZZ;id;fields... + // field= name;[pos];type[;[divisor|values][;[unit][;[comment]]]] + std::string checks[][5] = { + // "message", "flags" + {";;first;;;fe;0700;x;;bda", "26.10.2014", "10fe07000426100014", "00", ""}, + {";;first;;;15;b509;id;x;;bda", "26.10.2014", "10fe07000426100014", "00", ""}, + }; + std::map templates; + Message* message = NULL; + for (size_t i = 0; i < sizeof(checks) / sizeof(checks[0]); i++) { + std::string check[5] = checks[i]; + std::istringstream isstr(check[0]); + std::string expectStr = check[1]; + SymbolString mstr = SymbolString(check[2], false); + SymbolString sstr = SymbolString(check[3], false); + std::string flags = check[4]; + bool failedCreate = flags.find('c') != std::string::npos; + std::string item; + std::vector entries; + + while (std::getline(isstr, item, ';') != 0) + entries.push_back(item); + + if (message != NULL) { + delete message; + message = NULL; + } + std::vector::iterator it = entries.begin(); + result_t result = Message::create(it, entries.end(), templates, message); + + if (failedCreate == true) { + if (result == RESULT_OK) + std::cout << "\"" << check[0] << "\": failed create error: unexpectedly succeeded" << std::endl; + else + std::cout << "\"" << check[0] << "\": failed create OK" << std::endl; + continue; + } + if (result != RESULT_OK) { + std::cout << "\"" << check[0] << "\": create error: " + << getResultCode(result) << std::endl; + continue; + } + if (message == NULL) { + std::cout << "\"" << check[0] << "\": create error: NULL" << std::endl; + continue; + } + if (it != entries.end()) { + std::cout << "\"" << check[0] << "\": create error: trailing input" << std::endl; + continue; + } + std::cout << "\"" << check[0] << "\": create OK" << std::endl; + + std::istringstream input(expectStr); + result = message->prepare(SYN, mstr, input); + if (result != RESULT_OK) + std::cout << " prepare >" << expectStr << "< error: " + << getResultCode(result) << std::endl; + + delete message; + message = NULL; + } + + for (std::map::iterator it = templates.begin(); it != templates.end(); it++) + delete it->second; + + return 0; + +}