Skip to content

Commit

Permalink
add option to answer request to non-own addresses (part of #42)
Browse files Browse the repository at this point in the history
  • Loading branch information
john30 committed Mar 8, 2024
1 parent d6ea68d commit c063fb3
Show file tree
Hide file tree
Showing 8 changed files with 299 additions and 88 deletions.
57 changes: 23 additions & 34 deletions src/ebusd/bushandler.cpp
Expand Up @@ -32,9 +32,6 @@ using std::setfill;
using std::setw;
using std::endl;

// the string used for answering to a scan request (07h 04h)
#define SCAN_ANSWER ("ebusd.eu;" PACKAGE_NAME ";" SCAN_VERSION ";100")


result_t PollRequest::prepare(symbol_t ownMasterAddress) {
istringstream input;
Expand Down Expand Up @@ -378,33 +375,11 @@ void BusHandler::notifyProtocolStatus(ProtocolState state, result_t result) {
}
}

result_t BusHandler::notifyProtocolAnswer(const MasterSymbolString& command, SlaveSymbolString* response) {
Message* message = m_messages->find(command);
if (message == nullptr) {
message = m_messages->find(command, true);
if (message != nullptr && message->getSrcAddress() != SYN) {
message = nullptr;
}
}
if (message == nullptr || message->isWrite()) {
// don't know this request or definition has wrong direction, deny
return RESULT_ERR_INVALID_ARG;
}
istringstream input; // TODO create input from database of internal variables
if (message == m_messages->getScanMessage()
|| message == m_messages->getScanMessage(m_protocol->getOwnMasterAddress())) {
input.str(SCAN_ANSWER);
}
// build response and store in m_response for sending back to requesting master
return message->prepareSlave(&input, response);
}


void BusHandler::notifyProtocolSeenAddress(symbol_t address) {
m_seenAddresses[address] |= SEEN;
}

void BusHandler::notifyProtocolMessage(bool sent, const MasterSymbolString& command,
void BusHandler::notifyProtocolMessage(MessageDirection direction, const MasterSymbolString& command,
const SlaveSymbolString& response) {
symbol_t srcAddress = command[0], dstAddress = command[1];
bool master = isMaster(dstAddress);
Expand Down Expand Up @@ -465,7 +440,21 @@ void BusHandler::notifyProtocolMessage(bool sent, const MasterSymbolString& comm
}
m_grabbedMessages[key].setLastData(command, response);
}
const char* prefix = sent ? "sent" : "received";
if (direction == md_answer) {
size_t idLen = command.getDataSize();
if (master && idLen >= response.size()) {
// build MS auto-answer from MM with same ID
SlaveSymbolString answer;
answer.push_back(0); // room for length
idLen -= response.size();
for (size_t pos = idLen; pos < response.size(); pos++) {
answer.push_back(command.dataAt(pos));
}
m_protocol->setAnswer(SYN, command[1], command[2], command[3], command.data() + 5, idLen, answer);
// TODO could use loaded messages for identifying MM/MS message pair
}
}
const char* prefix = direction == md_answer ? "answered" : direction == md_send ? "sent" : "received";
if (message == nullptr) {
if (dstAddress == BROADCAST || master) {
logNotice(lf_update, "%s unknown %s cmd: %s", prefix, master ? "MM" : "BC", command.getStr().c_str());
Expand Down Expand Up @@ -493,7 +482,7 @@ void BusHandler::notifyProtocolMessage(bool sent, const MasterSymbolString& comm
string data = output.str();
if (m_protocol->isOwnAddress(dstAddress)) {
logNotice(lf_update, "%s %s self-update %s %s QQ=%2.2x: %s", prefix, mode, circuit.c_str(), name.c_str(),
srcAddress, data.c_str()); // TODO store in database of internal variables
srcAddress, data.c_str());
} else if (message->getDstAddress() == SYN) { // any destination
if (message->getSrcAddress() == SYN) { // any destination and any source
logNotice(lf_update, "%s %s %s %s QQ=%2.2x ZZ=%2.2x: %s", prefix, mode, circuit.c_str(), name.c_str(),
Expand Down Expand Up @@ -676,12 +665,12 @@ void BusHandler::formatSeenInfo(ostringstream* output) const {
}
if (ownAddress) {
*output << ", ebusd";
if (m_protocol->isAnswering()) {
*output << " (answering)";
}
if (m_protocol->isAddressConflict(address)) {
*output << ", conflict";
}
}
if (m_protocol->hasAnswer(address)) {
*output << " (answering)";
}
if (ownAddress && m_protocol->isAddressConflict(address)) {
*output << ", conflict";
}
if ((m_seenAddresses[address]&SCAN_DONE) != 0) {
*output << ", scanned";
Expand Down
6 changes: 2 additions & 4 deletions src/ebusd/bushandler.h
Expand Up @@ -398,14 +398,12 @@ class BusHandler : public ProtocolListener {
// @copydoc
void notifyProtocolStatus(ProtocolState state, result_t result) override;

// @copydoc
result_t notifyProtocolAnswer(const MasterSymbolString& master, SlaveSymbolString* slave) override;

// @copydoc
void notifyProtocolSeenAddress(symbol_t address) override;

// @copydoc
void notifyProtocolMessage(bool sent, const MasterSymbolString& master, const SlaveSymbolString& slave) override;
void notifyProtocolMessage(MessageDirection direction, const MasterSymbolString& master,
const SlaveSymbolString& slave) override;

private:
/**
Expand Down
10 changes: 10 additions & 0 deletions src/ebusd/main.cpp
Expand Up @@ -415,6 +415,16 @@ int main(int argc, char* argv[], char* envp[]) {
return EINVAL;
}
s_busHandler->setProtocol(s_protocol);
if (s_opt.answer) {
istringstream input;
input.str("ebusd.eu;" PACKAGE_NAME ";" SCAN_VERSION ";100");
Message* message = s_messageMap->getScanMessage();
SlaveSymbolString response;
if (message && message->prepareSlave(&input, &response) == RESULT_OK) {
s_protocol->setAnswer(SYN, s_protocol->getOwnSlaveAddress(), message->getPrimaryCommand(),
message->getSecondaryCommand(), nullptr, 0, response);
}
}

if (!s_opt.foreground) {
if (!setLogFile(s_opt.logFile)) {
Expand Down
90 changes: 88 additions & 2 deletions src/ebusd/mainloop.cpp
Expand Up @@ -484,6 +484,13 @@ result_t MainLoop::decodeRequest(Request* req, bool* connected, RequestMode* req
*ostream << "ERR: command not enabled";
return RESULT_OK;
}
if (cmd == "ANSWER") {
if (m_enableHex && !m_protocol->isReadOnly()) {
return executeAnswer(args, ostream);
}
*ostream << "ERR: command not enabled";
return RESULT_OK;
}
if (cmd == "F" || cmd == "FIND") {
return executeFind(args, getUserLevels(*user), ostream);
}
Expand Down Expand Up @@ -1170,7 +1177,7 @@ result_t MainLoop::executeInject(const vector<string>& args, ostringstream* ostr
if (!m_scanHelper->parseMessage(args[argPos++], false, &master, &slave)) {
return RESULT_ERR_INVALID_ARG;
}
m_busHandler->notifyProtocolMessage(false, master, slave);
m_busHandler->notifyProtocolMessage(md_recv, master, slave);
return RESULT_OK;
}
*ostream << "usage: inject QQZZPBSBNN[DD]*/[NN[DD]*]\n"
Expand All @@ -1183,6 +1190,84 @@ result_t MainLoop::executeInject(const vector<string>& args, ostringstream* ostr
return RESULT_OK;
}

result_t MainLoop::executeAnswer(const vector<string>& args, ostringstream* ostream) {
size_t argPos = 1;
symbol_t srcAddress = SYN;
symbol_t dstAddress = SYN;
bool master = false;
while (args.size() > argPos && args[argPos][0] == '-') {
if (args[argPos] == "-s" && argPos + 1 < args.size()) {
result_t ret;
argPos++;
symbol_t address = (symbol_t)parseInt(args[argPos].c_str(), 16, 0, 0xff, &ret);
if (ret != RESULT_OK || !isValidAddress(address, false) || !isMaster(address)) {
return RESULT_ERR_INVALID_ADDR;
}
srcAddress = address;
} else if (args[argPos] == "-d" && argPos + 1 < args.size()) {
result_t ret;
argPos++;
symbol_t address = (symbol_t)parseInt(args[argPos].c_str(), 16, 0, 0xff, &ret);
if (ret != RESULT_OK || !isValidAddress(address)) {
return RESULT_ERR_INVALID_ADDR;
}
dstAddress = address;
} else if (args[argPos] == "-m") {
master = true;
} else {
argPos = 0; // print usage
break;
}
argPos++;
}
MasterSymbolString id;
if (argPos > 0 && argPos < args.size()) {
result_t ret = id.parseHex(args[argPos++]);
if (ret != RESULT_OK) {
return ret;
}
if (id.size() < 2 || id.size() > 6) {
return RESULT_ERR_INVALID_POS;
}
}
SlaveSymbolString answer;
if (argPos > 0 && argPos < args.size()) {
answer.push_back(0); // room for length byte
result_t ret = answer.parseHex(args[argPos++]);
if (ret != RESULT_OK) {
return ret;
}
if (answer.size() > 16) {
return RESULT_ERR_INVALID_POS;
}
answer[0] = (symbol_t)(answer.size()-1);
}
if (argPos < args.size()) {
argPos = 0; // print usage
}
if (argPos <= 1) {
*ostream << "usage: answer [-m] [-s QQ] [-d ZZ] PBSB[ID]* [DD]*\n"
" Answer to a message from the bus.\n"
" -m destination is a master\n"
" -s QQ source address to limit to\n"
" -d ZZ override destination address (instead of own address)\n"
" PB SB primary/secondary command byte\n"
" ID further ID bytes\n"
" DD data bytes (only length used with -m)";
return RESULT_OK;
}
if (isMaster(dstAddress)) {
master = true;
} else if (dstAddress == SYN) {
dstAddress = master ? m_address : getSlaveAddress(m_address);
}
if (!m_protocol->setAnswer(srcAddress, dstAddress, id[0], id[1], id.data()+2
, id.size()-2, answer)) {
return RESULT_ERR_INVALID_ARG;
}
return RESULT_OK;
}

result_t MainLoop::executeDirect(const vector<string>& args, RequestMode* reqMode, ostringstream* ostream) {
if (reqMode->listenMode != lm_direct) {
if (args.size() == 1) {
Expand Down Expand Up @@ -1915,12 +2000,13 @@ result_t MainLoop::executeHelp(ostringstream* ostream) {
" listen|l Listen for updates: listen [-v|-V] [-n|-N] [-u|-U] [stop]\n"
" hex Send hex data: hex [-s QQ] [-n] ZZPBSB[NN][DD]* (if enabled)\n"
" inject Inject hex data: inject QQZZPBSBNN[DD]*/[NN[DD]*] (if enabled)\n"
" answer Answer a message: answer [-m] [-s QQ] [-d ZZ] PBSB[ID]* [DD]* (if enabled)\n"
" direct Enter direct mode\n"
" state|s Report bus state\n"
" info|i Report information about the daemon, configuration, seen participants, and the device.\n"
" grab|g Grab messages: grab [stop]\n"
" Report the messages: grab result [all|decode]\n"
" define Define new message: define [-r] DEFINITION\n"
" define Define new message: define [-r] DEFINITION (if enabled)\n"
" decode|d Decode field(s): decode [-v|-V] [-n|-N] DEFINITION DD[DD]*\n"
" encode|e Encode field(s): encode DEFINITION VALUE[;VALUE]*\n"
" scan Scan slaves: scan [full|ZZ]\n"
Expand Down
11 changes: 10 additions & 1 deletion src/ebusd/mainloop.h
Expand Up @@ -224,6 +224,15 @@ class MainLoop : public Thread {
*/
result_t executeDirect(const vector<string>& args, RequestMode* reqMode, ostringstream* ostream);

/**
* Execute the answer command.
* @param args the arguments passed to the command (starting with the command itself), or empty for help.
* @param reqMode the @a RequestMode to use and update.
* @param ostream the @a ostringstream to format the result string to.
* @return the result code.
*/
result_t executeAnswer(const vector<string>& args, ostringstream* ostream);

/**
* Execute the find command.
* @param args the arguments passed to the command (starting with the command itself), or empty for help.
Expand Down Expand Up @@ -403,7 +412,7 @@ class MainLoop : public Thread {
/** true when the poll interval is non zero. */
const bool m_polling;

/** whether to enable the hex command. */
/** whether to enable the hex, inject, and answer commands. */
const bool m_enableHex;

/** the MessageMap for handling newly defined messages for testing (if enabled), or nullptr. */
Expand Down
51 changes: 34 additions & 17 deletions src/lib/ebus/protocol.h
Expand Up @@ -204,6 +204,13 @@ class ActiveBusRequest : public BusRequest {
};


/** the possible message directions. */
enum MessageDirection {
md_recv, //!< message received from bus
md_send, //!< message sent to bus
md_answer, //!< answered to a message received from bus
};

/**
* Interface for listening to eBUS protocol data.
*/
Expand All @@ -229,22 +236,13 @@ class ProtocolListener {

/**
* Listener method that is called when a message was sent or received.
* @param sent true when the master part was actively sent, false if the whole message
* was received only.
* @param master the @a MasterSymbolString received.
* @param slave the @a SlaveSymbolString received.
* @param direction the message direction.
* @param master the @a MasterSymbolString received/sent.
* @param slave the @a SlaveSymbolString received/sent or the answer passed to @a ProtocolHandler::setAnswer() with
* the the length of the data part following the ID as master.
*/
virtual void notifyProtocolMessage(bool sent, const MasterSymbolString& master,
virtual void notifyProtocolMessage(MessageDirection direction, const MasterSymbolString& master,
const SlaveSymbolString& slave) = 0; // abstract

/**
* Listener method that is called when in answer mode and a message targeting ourself was received.
* @param master the @a MasterSymbolString received.
* @param slave the @a SlaveSymbolString for writing the response to.
* @return @a RESULT_OK on success, or an error code.
*/
virtual result_t notifyProtocolAnswer(const MasterSymbolString& master,
SlaveSymbolString* slave) = 0; // abstract
};


Expand Down Expand Up @@ -350,9 +348,28 @@ class ProtocolHandler : public WaitThread, public DeviceListener {
symbol_t getOwnSlaveAddress() const { return m_ownSlaveAddress; }

/**
* @return @p true if answering queries for the own master/slave address (if not readonly).
* @return @p true if answering queries (if not readonly).
*/
virtual bool isAnswering() const { return false; }

/**
* Add a message to be answered.
* @param srcAddress the source address to limit to, or @a SYN for any.
* @param dstAddress the destination address (either master or slave address).
* @param pb the primary ID byte.
* @param sb the secondary ID byte.
* @param id optional further ID bytes.
* @param idLen the length of the further ID bytes (maximum 4).
* @param answer the sequence to respond when addressed as slave or the length of the data part following the ID as master.
* @return @p true on success, @p false on error (e.g. invalid address, read only, or too long id).
*/
virtual bool setAnswer(symbol_t srcAddress, symbol_t dstAddress, symbol_t pb, symbol_t sb, const symbol_t* id,
size_t idLen, const SlaveSymbolString& answer) { return false; }

/**
* @return @p true if an answer was set for the destination address.
*/
bool isAnswering() const { return m_config.answer; }
virtual bool hasAnswer(symbol_t dstAddress) const { return false; }

/**
* @param address the address to check.
Expand Down Expand Up @@ -520,7 +537,7 @@ class ProtocolHandler : public WaitThread, public DeviceListener {
*/
virtual bool addSeenAddress(symbol_t address);

/** the client configuration to use. */
/** the configuration to use. */
const ebus_protocol_config_t m_config;

/** the @a Device instance for accessing the bus. */
Expand Down

0 comments on commit c063fb3

Please sign in to comment.