/* * ebusd - daemon for communication with eBUS heating systems. * Copyright (C) 2014-2023 John Baier * * This program 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. * * This program 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 this program. If not, see . */ #ifdef HAVE_CONFIG_H # include #endif #include #include #include "lib/ebus/protocol.h" #include "lib/ebus/protocol_direct.h" #include "lib/utils/log.h" namespace ebusd { using std::hex; using std::setfill; using std::setw; bool ActiveBusRequest::notify(result_t result, const SlaveSymbolString& slave) { if (result == RESULT_OK) { string str = slave.getStr(); logDebug(lf_bus, "read res: %s", str.c_str()); } m_result = result; *m_slave = slave; return false; } ProtocolHandler* ProtocolHandler::create(const ebus_protocol_config_t config, ProtocolListener* listener) { Device *device = Device::create(config.device, config.extraLatency, !config.noDeviceCheck); if (device == nullptr) { return nullptr; } return new DirectProtocolHandler(config, device, listener); } result_t ProtocolHandler::open() { result_t result = m_device->open(); if (result != RESULT_OK) { logError(lf_bus, "unable to open %s: %s", m_device->getName(), getResultCode(result)); } else if (!m_device->isValid()) { logError(lf_bus, "device %s not available", m_device->getName()); } return result; } void ProtocolHandler::formatInfo(ostringstream* ostream, bool verbose, bool noWait) { m_device->formatInfo(ostream, verbose, true); if (isReadOnly()) { *ostream << ", readonly"; } if (noWait) { return; } m_device->formatInfo(ostream, verbose, false); } void ProtocolHandler::formatInfoJson(ostringstream* ostream) { m_device->formatInfoJson(ostream); } void ProtocolHandler::notifyDeviceData(symbol_t symbol, bool received) { if (received && m_dumpFile) { m_dumpFile->write(&symbol, 1); } if (!m_logRawFile && !m_logRawEnabled) { return; } if (m_logRawBytes) { if (m_logRawFile) { m_logRawFile->write(&symbol, 1, received); } else if (m_logRawEnabled) { if (received) { logNotice(lf_bus, "<%02x", symbol); } else { logNotice(lf_bus, ">%02x", symbol); } } return; } if (symbol != SYN) { if (received && !m_logRawLastReceived && symbol == m_logRawLastSymbol) { return; // skip received echo of previously sent symbol } if (m_logRawBuffer.tellp() == 0 || received != m_logRawLastReceived) { m_logRawLastReceived = received; if (m_logRawBuffer.tellp() == 0 && m_logRawLastSymbol != SYN) { m_logRawBuffer << "..."; } m_logRawBuffer << (received ? "<" : ">"); } m_logRawBuffer << setw(2) << setfill('0') << hex << static_cast(symbol); } m_logRawLastSymbol = symbol; if (m_logRawBuffer.tellp() > (symbol == SYN ? 0 : 64)) { // flush: direction+5 hdr+24 max data+crc+direction+ack+1 if (symbol != SYN) { m_logRawBuffer << "..."; } const string bufStr = m_logRawBuffer.str(); const char* str = bufStr.c_str(); if (m_logRawFile) { m_logRawFile->write((const unsigned char*)str, strlen(str), received, false); } else { logNotice(lf_bus, str); } m_logRawBuffer.str(""); } } void ProtocolHandler::notifyStatus(bool error, const char* message) { if (error) { logError(lf_bus, "device status: %s", message); } else { logNotice(lf_bus, "device status: %s", message); } } void ProtocolHandler::clear() { memset(m_seenAddresses, 0, sizeof(m_seenAddresses)); m_masterCount = 1; } result_t ProtocolHandler::addRequest(BusRequest* request, bool wait) { if (m_config.readOnly) { return RESULT_ERR_DEVICE; } m_nextRequests.push(request); if (!wait || m_finishedRequests.remove(request, true)) { return RESULT_OK; } return RESULT_ERR_TIMEOUT; } result_t ProtocolHandler::sendAndWait(const MasterSymbolString& master, SlaveSymbolString* slave) { if (!hasSignal()) { return RESULT_ERR_NO_SIGNAL; // don't wait when there is no signal } result_t result = RESULT_ERR_NO_SIGNAL; slave->clear(); ActiveBusRequest request(master, slave); logInfo(lf_bus, "send message: %s", master.getStr().c_str()); for (int sendRetries = m_config.failedSendRetries + 1; sendRetries > 0; sendRetries--) { result = addRequest(&request, true); bool success = result == RESULT_OK; if (success) { result = request.m_result; } if (result == RESULT_OK) { break; } if (!success || result == RESULT_ERR_NO_SIGNAL || result == RESULT_ERR_SEND || result == RESULT_ERR_DEVICE) { logError(lf_bus, "send to %2.2x: %s, give up", master[1], getResultCode(result)); break; } logError(lf_bus, "send to %2.2x: %s%s", master[1], getResultCode(result), sendRetries > 1 ? ", retry" : ""); request.m_busLostRetries = 0; } return result; } void ProtocolHandler::measureLatency(struct timespec* sentTime, struct timespec* recvTime) { int64_t latencyLong = (recvTime->tv_sec*1000000000 + recvTime->tv_nsec - sentTime->tv_sec*1000000000 - sentTime->tv_nsec)/1000000; if (latencyLong < 0 || latencyLong > 1000) { return; // clock skew or out of reasonable range } auto latency = static_cast(latencyLong); logDebug(lf_bus, "send/receive symbol latency %d ms", latency); if (m_symbolLatencyMin >= 0 && (latency >= m_symbolLatencyMin && latency <= m_symbolLatencyMax)) { return; } if (m_symbolLatencyMin == -1 || latency < m_symbolLatencyMin) { m_symbolLatencyMin = latency; } if (m_symbolLatencyMax == -1 || latency > m_symbolLatencyMax) { m_symbolLatencyMax = latency; } logInfo(lf_bus, "send/receive symbol latency %d - %d ms", m_symbolLatencyMin, m_symbolLatencyMax); } bool ProtocolHandler::addSeenAddress(symbol_t address) { if (!isValidAddress(address, false)) { return false; } if (!isMaster(address)) { if (!m_config.readOnly && address == m_ownSlaveAddress) { if (!m_addressConflict) { m_addressConflict = true; logError(lf_bus, "own slave address %2.2x is used by another participant", address); } } if (!m_seenAddresses[address]) { m_listener->notifyProtocolSeenAddress(address); } m_seenAddresses[address] = true; address = getMasterAddress(address); if (address == SYN) { return false; } } if (m_seenAddresses[address]) { return false; } bool ret = false; if (!m_config.readOnly && address == m_ownMasterAddress) { if (!m_addressConflict) { m_addressConflict = true; logError(lf_bus, "own master address %2.2x is used by another participant", address); } } else { m_masterCount++; ret = true; logNotice(lf_bus, "new master %2.2x, master count %d", address, m_masterCount); } m_listener->notifyProtocolSeenAddress(address); m_seenAddresses[address] = true; return ret; } void ProtocolHandler::setDumpFile(const char* dumpFile, unsigned int dumpSize, bool dumpFlush) { if (m_dumpFile) { delete m_dumpFile; m_dumpFile = nullptr; } if (dumpFile && dumpFile[0]) { m_dumpFile = new RotateFile(dumpFile, dumpSize, false, dumpFlush ? 1 : 16); } } bool ProtocolHandler::toggleDump() { if (!m_dumpFile) { return false; } bool enabled = !m_dumpFile->isEnabled(); m_dumpFile->setEnabled(enabled); return enabled; } void ProtocolHandler::setLogRawFile(const char* logRawFile, unsigned int logRawSize) { if (logRawFile[0]) { m_logRawFile = new RotateFile(logRawFile, logRawSize, true); m_logRawFile->setEnabled(m_logRawEnabled); } else { m_logRawFile = nullptr; } } bool ProtocolHandler::toggleLogRaw(bool bytes) { bool enabled; m_logRawBytes = bytes; if (m_logRawFile) { enabled = !m_logRawFile->isEnabled(); m_logRawFile->setEnabled(enabled); } else { enabled = !m_logRawEnabled; m_logRawEnabled = enabled; } return enabled; } } // namespace ebusd