From bfed07b2bcedfa37a9e819735e321ad5b34f0c5a Mon Sep 17 00:00:00 2001 From: Martin Wagner Date: Sat, 2 May 2026 12:34:53 +0200 Subject: [PATCH 1/8] Fix: data packet to small, writes past end... tmp_pkt is three bytes too small and fails in Read(...). S nippet taken from "Write()" function, assuming same header size. --- libconcord/remote_z.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libconcord/remote_z.cpp b/libconcord/remote_z.cpp index 8e51eb6..1af735a 100644 --- a/libconcord/remote_z.cpp +++ b/libconcord/remote_z.cpp @@ -619,7 +619,7 @@ int CRemoteZ_USBNET::ReadRegion(uint8_t region, uint32_t &rgn_len, uint8_t *rd, uint32_t pkt_len; unsigned int data_to_read = rgn_len; uint8_t *rd_ptr = rd; - uint8_t tmp_pkt[USBNET_MAX_PACKET_SIZE]; + uint8_t tmp_pkt[USBNET_MAX_PACKET_SIZE+3]; /* add standard 3-byte header */ cmd[0] = 0x01; // 1 parameter cmd[1] = 0x01; // 1st parameter, 1 byte (region id) cmd[2] = region; From 9859760fe15a2105364a1476f925997cd59930c6 Mon Sep 17 00:00:00 2001 From: Martin Wagner Date: Sat, 2 May 2026 14:32:24 +0200 Subject: [PATCH 2/8] fix: double free happens when deinit_concord() is called by the user code and the class destructor. --- libconcord/libconcord.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libconcord/libconcord.cpp b/libconcord/libconcord.cpp index 13b9c82..273701f 100644 --- a/libconcord/libconcord.cpp +++ b/libconcord/libconcord.cpp @@ -763,8 +763,10 @@ int init_concord() int deinit_concord() { ShutdownUSB(); - if (rmt) + if (rmt) { delete rmt; + rmt = NULL; + } return 0; } From 34acc6b596defc1ad2b961d7d31510872f1ef31a Mon Sep 17 00:00:00 2001 From: Martin Wagner Date: Sun, 3 May 2026 11:24:38 +0200 Subject: [PATCH 3/8] Add: decoder, command to remote_z --- libconcord/Makefile.am | 5 +- libconcord/libconcord.h | 1 + libconcord/protocol_z.h | 6 +- libconcord/remote.h | 5 + libconcord/remote_z.cpp | 177 +++++++++++++++++++++++- libconcord/remote_z_learn/base.cpp | 62 +++++++++ libconcord/remote_z_learn/base.h | 93 +++++++++++++ libconcord/remote_z_learn/binary.h | 90 ++++++++++++ libconcord/remote_z_learn/data.cpp | 197 +++++++++++++++++++++++++++ libconcord/remote_z_learn/data.h | 110 +++++++++++++++ libconcord/remote_z_learn/single.cpp | 68 +++++++++ libconcord/remote_z_learn/single.h | 49 +++++++ libconcord/remote_z_learn/start.h | 41 ++++++ libconcord/remote_z_learn/stop.h | 41 ++++++ libconcord/remote_z_learn/stream.cpp | 68 +++++++++ libconcord/remote_z_learn/stream.h | 49 +++++++ 16 files changed, 1057 insertions(+), 5 deletions(-) create mode 100644 libconcord/remote_z_learn/base.cpp create mode 100644 libconcord/remote_z_learn/base.h create mode 100644 libconcord/remote_z_learn/binary.h create mode 100644 libconcord/remote_z_learn/data.cpp create mode 100644 libconcord/remote_z_learn/data.h create mode 100644 libconcord/remote_z_learn/single.cpp create mode 100644 libconcord/remote_z_learn/single.h create mode 100644 libconcord/remote_z_learn/start.h create mode 100644 libconcord/remote_z_learn/stop.h create mode 100644 libconcord/remote_z_learn/stream.cpp create mode 100644 libconcord/remote_z_learn/stream.h diff --git a/libconcord/Makefile.am b/libconcord/Makefile.am index b3036c5..25ea30a 100644 --- a/libconcord/Makefile.am +++ b/libconcord/Makefile.am @@ -1,10 +1,13 @@ # libconcord ACLOCAL_AMFLAGS = -I m4 lib_LTLIBRARIES = libconcord.la +AUTOMAKE_OPTIONS = subdir-objects libconcord_la_SOURCES = remote.cpp remote_z.cpp libconcord.cpp binaryfile.cpp \ web.cpp usblan.cpp binaryfile.h hid.h protocol_z.h \ remote_info.h web.h protocol.h remote.h usblan.h xml_headers.h \ - operationfile.cpp remote_mh.cpp libusbhid.cpp libhidapi.cpp + operationfile.cpp remote_mh.cpp libusbhid.cpp libhidapi.cpp \ + remote_z_learn/data.cpp remote_z_learn/base.cpp \ + remote_z_learn/single.cpp remote_z_learn/stream.cpp include_HEADERS = libconcord.h libconcord_la_CPPFLAGS = -Wall libconcord_la_LDFLAGS = -version-info 6:0:0 $(LIBCONCORD_LDFLAGS) -lzip -lcurl diff --git a/libconcord/libconcord.h b/libconcord/libconcord.h index c0d2ba0..7024b6a 100644 --- a/libconcord/libconcord.h +++ b/libconcord/libconcord.h @@ -42,6 +42,7 @@ #define LC_ERROR_UNSUPP 15 #define LC_ERROR_INVALID_CONFIG 16 #define LC_ERROR_IR_OVERFLOW 17 +#define LC_ERROR_IR_TIMEOUT 18 /* * Filetypes, used by identity_file() diff --git a/libconcord/protocol_z.h b/libconcord/protocol_z.h index 7c27d80..8bbfa89 100644 --- a/libconcord/protocol_z.h +++ b/libconcord/protocol_z.h @@ -109,9 +109,9 @@ #define COMMAND_SEND_LONG_ZWAVE_RESPONSE_DATA 0x8B #define COMMAND_SEND_LONG_ZWAVE_RESPONSE_DATA_DONE 0x8C #define COMMAND_INITIATE_LEARNIR_TCP_CHANNEL 0xA0 -#define COMMAND_LEARNIR 0xA1 -#define COMMAND_LEARNIR_HEADER 0xA2 -#define COMMAND_LEARNIR_DATA 0xA3 +#define COMMAND_LEARNIR_START 0xA1 +#define COMMAND_LEARNIR_SINGLE 0xA2 +#define COMMAND_LEARNIR_STREAM 0xA3 #define COMMAND_LEARNIR_DONE 0xA4 #define COMMAND_LEARNIR_STOP 0xA5 // 1000 only diff --git a/libconcord/remote.h b/libconcord/remote.h index 370cf53..169cead 100644 --- a/libconcord/remote.h +++ b/libconcord/remote.h @@ -380,6 +380,11 @@ class CRemoteZ_USBNET : public CRemoteZ_Base | x[2]<<8 | x[3]; }; virtual int ReadRegion(uint8_t region, uint32_t &len, uint8_t *rd, lc_callback cb, void *cb_arg, uint32_t cb_stage); + virtual int SendLearnStart(); + virtual int SendLearnStop(); + virtual int ReadIrData(uint32_t *freq, uint32_t **ir_signal, + uint32_t *ir_signal_length, lc_callback cb, void *cb_arg, + uint32_t cb_stage); public: CRemoteZ_USBNET() {}; diff --git a/libconcord/remote_z.cpp b/libconcord/remote_z.cpp index 1af735a..68125c1 100644 --- a/libconcord/remote_z.cpp +++ b/libconcord/remote_z.cpp @@ -26,6 +26,11 @@ #include "remote.h" #include "usblan.h" #include "protocol_z.h" +#include "remote_z_learn/single.h" +#include "remote_z_learn/start.h" +#include "remote_z_learn/stop.h" +#include "remote_z_learn/stream.h" +#include "remote_z_learn/data.h" /* Have we acked the syn packet yet? */ static bool SYN_ACKED = false; @@ -583,7 +588,177 @@ int CRemoteZ_USBNET::LearnIR(uint32_t *freq, uint32_t **ir_signal, uint32_t *ir_signal_length, lc_callback cb, void *cb_arg, uint32_t cb_stage) { - return LC_ERROR_UNSUPP; + int err = 0; + + *freq = 0; + *ir_signal_length = 0; + *ir_signal = nullptr; + + if ((err = SendLearnStart())) { + debug("Failed starting learn mode"); + return err; + } + + if ((err = ReadIrData( + freq, ir_signal, ir_signal_length, cb, cb_arg, cb_stage))) { + SendLearnStop(); //always close! + debug("Failed starting learn mode"); + return err; + } + + if ((err = SendLearnStop())) { + debug("Failed stopping learn mode"); + return err; + } + + return 0; +} + +int CRemoteZ_USBNET::SendLearnStart() +{ + remote_z::Start start; + uint8_t rsp[USBNET_MAX_PACKET_SIZE + 3]; /* add standard 3-byte header */ + + debug("START_IR_LEARN"); + + int err = 0; + auto req = start.get(); + if ((err = Write( + TYPE_REQUEST, COMMAND_LEARNIR_START, req.size(), req.data()))) { + debug("Failed to write to remote"); + return LC_ERROR_WRITE; + } + unsigned int len; + uint8_t status; + if ((err = Read(status, len, rsp))) { + debug("Failed to read to remote"); + return LC_ERROR_READ; + } + + auto ret = start.check(std::vector(rsp, rsp + len)); + if (ret != remote_z::Start::Status::OK) { + debug("Incorrect response type from remote"); + return LC_ERROR_INVALID_DATA_FROM_REMOTE; + } + return 0; +} + +int CRemoteZ_USBNET::SendLearnStop() +{ + remote_z::Stop stop; + uint8_t rsp[USBNET_MAX_PACKET_SIZE + 3]; /* add standard 3-byte header */ + + debug("STOP_IR_LEARN"); + + int err = 0; + auto req = stop.get(); + if ((err = Write( + TYPE_REQUEST, COMMAND_LEARNIR_DONE, req.size(), req.data()))) { + debug("Failed to write to remote"); + return LC_ERROR_WRITE; + } + unsigned int len; + uint8_t status; + if ((err = Read(status, len, rsp))) { + debug("Failed to read to remote"); + return LC_ERROR_READ; + } + + auto ret = stop.check(std::vector(rsp, rsp + len)); + if (ret != remote_z::Stop::Status::OK) { + debug("Incorrect response type from remote"); + return LC_ERROR_INVALID_DATA_FROM_REMOTE; + } + return 0; +} + +int CRemoteZ_USBNET::ReadIrData(uint32_t *freq, uint32_t **ir_signal, + uint32_t *ir_signal_length, lc_callback cb, + void *cb_arg, uint32_t cb_stage) +{ + remote_z::Single single; + uint8_t rsp[USBNET_MAX_PACKET_SIZE + 3]; /* add standard 3-byte header */ + int err = 0; + int cb_count = 0; + bool firstChunk = true; + + debug("READING_IR_DATA"); + + /* + * Caller is responsible for deallocation of *ir_signal after use. + * + * Loop while we haven not: + * - any error + * - Timeout. Timeout is set by the remote itself answering "timeout", you can't change it. + * - EoF + */ + while (err == 0) { + auto req = single.get(); + if ((err = Write(TYPE_REQUEST, + COMMAND_LEARNIR_SINGLE, + req.size(), + req.data()))) { + debug("Failed to write to remote"); + err = LC_ERROR_WRITE; + break; + } + unsigned int len; + uint8_t status; + if ((err = Read(status, len, rsp))) { + debug("Failed to read to remote"); + err = LC_ERROR_READ; + break; + } + + debug("DATA %d, read %d bytest", cb_count, len) + + auto ret = single.addChunk(std::vector(rsp, rsp + len), + firstChunk); + if (ret == remote_z::Single::Status::DONE) break; + switch (ret) { + case remote_z::Single::Status::OK: + break; + case remote_z::Single::Status::ERR_TIMEOUT: + err = LC_ERROR_IR_TIMEOUT; + break; + default: + debug("Incorrect response type from remote (%d)", (int)ret); + err = LC_ERROR_INVALID_DATA_FROM_REMOTE; + break; + } + + firstChunk = false; + + if (cb) { + cb(cb_stage, + cb_count++, + single.getPayload().size(), + 0, + LC_CB_COUNTER_TYPE_STEPS, + cb_arg, + NULL); + } + } + + if (err != 0) return err; + + debug("DECODING_IR_DATA"); + + //standardise + auto payload = single.getPayload(); + if (payload.empty()) return 0; + auto timingStream = remote_z::TimingStream::fromMarkSegment(payload); + + //convert to libconcord format + const auto &t = timingStream.convertMarkPause(); + *freq = single.getClock(); + *ir_signal_length = t.size(); + *ir_signal = new uint32_t[t.size()]; + for (uint32_t i = 0; i < t.size(); i++) (*ir_signal)[i] = t[i]; + + debug("READ_IR_DONE"); + + return 0; } int CRemoteZ_USBNET::ReadRegion(uint8_t region, uint32_t &rgn_len, uint8_t *rd, diff --git a/libconcord/remote_z_learn/base.cpp b/libconcord/remote_z_learn/base.cpp new file mode 100644 index 0000000..7b03c9e --- /dev/null +++ b/libconcord/remote_z_learn/base.cpp @@ -0,0 +1,62 @@ +/* + * vim:tw=80:ai:tabstop=4:softtabstop=4:shiftwidth=4:expandtab + * + * 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * (C) Copyright Martin Wagner 2026 + */ +#include "base.h" + +using namespace std; + +namespace remote_z +{ + +Base::Status Base::check(const std::vector &data) +{ + if (data.size() < getHeaderMinSize()) return Status::ERR_SIZE; + if ((data[0] != 0x20) || (data[1] != getProtoclCmd()) || + (data[3] != getProtocolRespByte3())) { + return Status::ERR_RESPONSE_FORMAT; + } + switch (static_cast(data[2])) { + case ProtocolStatus::OK: + return Status::OK; + case ProtocolStatus::TIMEOUT: + return Status::ERR_TIMEOUT; + default: + return Status::ERR_UNKNOWN_RETURNCODE; + } +} + +uint8_t Base::getErrorByte(const std::vector &data) +{ + if (data.size() < getHeaderMinSize()) return 255; + return data[2]; +} + +void Base::moveExcessBytes(bool check) +{ + if (payload.size() < 3) return; + + if (check) { + excess.push_back(payload[0]); + excess.push_back(payload[2]); + payload.erase(payload.begin() + 2); + payload.erase(payload.begin() + 0); + } +} + +} // namespace remote_z diff --git a/libconcord/remote_z_learn/base.h b/libconcord/remote_z_learn/base.h new file mode 100644 index 0000000..702f915 --- /dev/null +++ b/libconcord/remote_z_learn/base.h @@ -0,0 +1,93 @@ +/* + * vim:tw=80:ai:tabstop=4:softtabstop=4:shiftwidth=4:expandtab + * + * 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * (C) Copyright Martin Wagner 2026 + */ + +#pragma once + +#include +#include + +#include "protocol_z.h" + +namespace remote_z +{ + +class Base +{ + public: + enum class Status { + OK, + DONE, + ERR_SIZE, + ERR_UNKNOWN_RETURNCODE, //received unknown return code from remote, see ProtocolStatus + ERR_RESPONSE_FORMAT, //response heder, layout format/values not as expected + ERR_PAYLOAD_FORMAT, //dito, payload + ERR_TERM, + ERR_TIMEOUT, + ERR_UNKNOWN + }; + + protected: + std::vector payload; + double clock = 0; + std::vector excess; + + //static values from child classes + virtual uint32_t getHeaderMinSize() = 0; + virtual uint32_t getProtoclCmd() = 0; + virtual uint32_t getProtocolRespByte3() = 0; + + //assume status is byte data[2] in response + enum class ProtocolStatus { OK = 0x01, TIMEOUT = 0x02 }; + + void moveExcessBytes(bool check); + + public: + /** generate request */ + virtual std::vector get() = 0; + + /** check data block */ + virtual Status check(const std::vector &data); + + /** get uninterpretet error byte */ + uint8_t getErrorByte(const std::vector &data); + + /** + * append data block + * + * @param data response data block + * @param first this is the first ever block in this transfer (command single/stream doesn't matter) + * @return see 'Status' + */ + virtual Status addChunk(const std::vector &data, bool first) + { + return Status::OK; + }; + + /** access payload. size() == 0 -> nothing available */ + virtual const std::vector &getPayload() { return payload; } + + /** get IR clock */ + virtual const double getClock() { return clock; } + + /** read non-timing related words. word = unknown use */ + virtual const std::vector &getExcess() { return excess; } +}; + +} // namespace remote_z diff --git a/libconcord/remote_z_learn/binary.h b/libconcord/remote_z_learn/binary.h new file mode 100644 index 0000000..7501bf1 --- /dev/null +++ b/libconcord/remote_z_learn/binary.h @@ -0,0 +1,90 @@ +/* + * vim:tw=80:ai:tabstop=4:softtabstop=4:shiftwidth=4:expandtab + * + * 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * (C) Copyright Martin Wagner 2026 + */ + +#pragma once + +#include +#include + +namespace remote_z +{ + +//use on network data stream: parseHarmony16(p[i], p[i+1]) +inline uint16_t parseHarmony16_network(uint8_t h, uint8_t l) +{ + return static_cast(l) | (static_cast(h) << 8); +} + +inline bool parseHarmony16_network(const std::vector &in, + std::vector &out) +{ + if ((in.size() % 2) != 0) return false; + + for (size_t i = 0; i < in.size(); i += 2) + out.push_back(parseHarmony16_network(in[i], in[i + 1])); + return true; +} + +//use on network data stream: parseHarmony16(p[i], p[i+1]) +inline uint16_t parseHarmony16_file(uint8_t h, uint8_t l) +{ + return static_cast(h) | (static_cast(l) << 8); +} + +inline uint32_t parseHarmony32_file(uint8_t h, + uint8_t m1, + uint8_t m2, + uint8_t l) +{ + return static_cast(h) | (static_cast(m1) << 8) | + (static_cast(m2) << 16) | (static_cast(l) << 24); +} + +inline bool parseHarmony16_file(const std::vector &in, + std::vector &out) +{ + if ((in.size() % 2) != 0) return false; + + for (size_t i = 0; i < in.size(); i += 2) + out.push_back(parseHarmony16_file(in[i], in[i + 1])); + return true; +} + +inline void setHarmony16_file(uint16_t data, std::vector &out) +{ + out.push_back(data); + out.push_back(data >> 8); +} + +inline void setHarmony32_file(uint32_t data, std::vector &out) +{ + out.push_back(data); + out.push_back(data >> 8); + out.push_back(data >> 16); + out.push_back(data >> 24); +} + +inline void setHarmony16_file(const std::vector &in, + std::vector &out) +{ + for (const auto &d : in) setHarmony16_file(d, out); +} + +} // namespace remote_z diff --git a/libconcord/remote_z_learn/data.cpp b/libconcord/remote_z_learn/data.cpp new file mode 100644 index 0000000..e38f405 --- /dev/null +++ b/libconcord/remote_z_learn/data.cpp @@ -0,0 +1,197 @@ +/* + * vim:tw=80:ai:tabstop=4:softtabstop=4:shiftwidth=4:expandtab + * + * 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * (C) Copyright Martin Wagner 2026 + */ + +#include +#include +#include + +#include "data.h" + +using namespace std; + +namespace remote_z +{ + +void TimingStream::addMarkSegment(const vector &raw) +{ + for (int i = 0; i < raw.size(); i += 2) + if (i + 1 < raw.size()) + data.push_back(Block::fromMarkSegment(raw[i], raw[i + 1])); +} + +void TimingStream::addMarkPause(const vector &raw) +{ + for (int i = 0; i < raw.size(); i += 2) + if (i + 1 < raw.size()) + data.push_back(Block::fromMarkPause(raw[i], raw[i + 1])); +} + +vector TimingStream::convertMarkPause() const +{ + vector stream; + + for (const auto &item : data) { + stream.push_back(item.mark_us); + stream.push_back(item.pause_us); + } + return stream; +} + +string TimingStream::convertGnuplot(bool activeHigh) const +{ + vector> plotData; + uint32_t time_us = 0; + stringstream str; + + uint32_t markValue = 1; + if (!activeHigh) markValue = 0; + + for (const auto &block : data) { + // Start of mark (ON state) + plotData.push_back({time_us, markValue}); + + // End of mark, start of segment (OFF state) + time_us += block.mark_us; + plotData.push_back({time_us, 0}); + + // End of segment (prepare for next mark) + time_us += block.pause_us; + } + + str << "time(µs) mark" << endl; + for (const auto &[time, amp] : plotData) str << time << " " << amp << endl; + return str.str(); +} + +string TimingStream::convertHexString() const +{ + stringstream str; + + for (const auto &block : data) { + str << hex << setw(4) << setfill('0') << block.mark_us << " " << hex + << setw(4) << setfill('0') << block.segment_us + << " "; //raw data, not mark/pause! + } + str << dec; + + return str.str(); +} + +string TimingStream::convertIntString() const +{ + stringstream str; + + for (const auto &block : data) { + str << "MP" << block.mark_us << ":" << block.pause_us << "; "; + //str << "MS" << block.mark_us << ":" << block.segment_us << "; "; + } + + return str.str(); +} + +string TimingStream::convertAsciiPlot(uint32_t width, bool activeHigh) const +{ + //todo active high... + string header; + string top; + string bottom; + uint32_t used = 0; + uint32_t base = 250; //us per char //todo so am einfachsten. + bool level = false; + bool truncated = false; + auto append = [&](const string &t, const string &b, uint32_t count = 1) { + if (used < width) { + top += t; + bottom += b; + used += count; + } + }; + string truncationMarker = "..."; + width = width - 3; + + if (data.empty() || (width < 25)) return ""; + + header = "µs per div: " + to_string(base); + + for (const auto &block : data) { + // divs, do round. for shorter pulse min 1 div, even on round-down + int mark_divs = max(1u, (block.mark_us + base / 2) / base); + if (block.mark_us == 0) { + //silence block, no pulse + mark_divs = 0; + } + int pause_divs = max(1u, (block.pause_us + base / 2) / base); + + // rising edge + if (!level && (mark_divs > 0)) { + append("┌", "┘"); + level = true; + if (used >= width) { + truncated = true; + break; + } + } + + for (int i = 1; i < mark_divs; i++) { + append("─", " "); + if (used >= width) { + truncated = true; + break; + } + } + if (used >= width) { + truncated = true; + break; + } + + // falling edge + if (level && (mark_divs > 0)) { + append("┐", "└"); + level = false; + if (used >= width) { + truncated = true; + break; + } + } + + for (int i = 1; i < pause_divs; i++) { + append(" ", "─"); + if ((i > 4) && (pause_divs > 12)) { + //crop empty data + append(" ", "...──", 5); + break; + } + if (used >= width) { + truncated = true; + break; + } + } + if (used >= width) { + truncated = true; + break; + } + } + + if (truncated) bottom += "\\++"; + + return header + '\n' + top + '\n' + bottom + '\n'; +} + +} // namespace remote_z diff --git a/libconcord/remote_z_learn/data.h b/libconcord/remote_z_learn/data.h new file mode 100644 index 0000000..e351bb3 --- /dev/null +++ b/libconcord/remote_z_learn/data.h @@ -0,0 +1,110 @@ +/* + * vim:tw=80:ai:tabstop=4:softtabstop=4:shiftwidth=4:expandtab + * + * 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * (C) Copyright Martin Wagner 2026 + */ + +#pragma once + +#include +#include +#include +#include + +namespace remote_z +{ + +/** single data block */ +class Block +{ + public: + static Block fromMarkSegment(uint16_t mark_us, uint16_t segment_us) + { + return Block(mark_us, segment_us, segment_us - mark_us); + } + + static Block fromMarkPause(uint16_t mark_us, uint16_t pause_us) + { + return Block(mark_us, pause_us + mark_us, pause_us); + } + + protected: + Block(uint16_t mark_us, uint16_t segment_us, uint16_t pause_us) + : mark_us(mark_us), pause_us(pause_us), segment_us(segment_us) + { + } + + public: + const uint16_t mark_us; + const uint16_t pause_us; + const uint16_t segment_us; + + std::chrono::microseconds mark() const + { + return std::chrono::microseconds(mark_us); + } + std::chrono::microseconds segment() const + { + return std::chrono::microseconds(segment_us); + } + std::chrono::microseconds pause() const + { + return std::chrono::microseconds(pause_us); + } +}; + +/** entire stream (single frame or actual stream) of + * timing data */ +class TimingStream +{ + protected: + std::vector data; + + public: + TimingStream() {} + + /** Stream lesen, Codierung Mark, Mark+Pause (=Periodendauer). + * Wir so in irlearn verwendet */ + static TimingStream fromMarkSegment(const std::vector &raw) + { + TimingStream ts; + ts.addMarkSegment(raw); + return ts; + } + + /** Stream lesen, Codierung Mark, Pause */ + static TimingStream fromMarkPause(const std::vector &raw) + { + TimingStream ts; + ts.addMarkPause(raw); + return ts; + } + + void addMarkSegment(const std::vector &raw); + void addMarkPause(const std::vector &raw); + + std::vector convertMarkPause() const; + std::string convertGnuplot(bool activeHigh = true) const; + std::string convertHexString() const; + std::string convertIntString() const; + std::string convertAsciiPlot(uint32_t width = 100, + bool activeHigh = true) const; + + const std::vector &timings() const { return data; } +}; + +} // namespace remote_z diff --git a/libconcord/remote_z_learn/single.cpp b/libconcord/remote_z_learn/single.cpp new file mode 100644 index 0000000..eef134b --- /dev/null +++ b/libconcord/remote_z_learn/single.cpp @@ -0,0 +1,68 @@ +/* + * vim:tw=80:ai:tabstop=4:softtabstop=4:shiftwidth=4:expandtab + * + * 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * (C) Copyright Martin Wagner 2026 + */ + +#include "single.h" +#include "binary.h" + +using namespace std; + +namespace remote_z +{ + +Single::Status Single::parse(const std::vector &p) +{ + //0x02 0xXX 0xXX -> size mod 3 + if ((p.size() % 3) != 0) return Status::ERR_SIZE; + + //drop 0x02, 0xXX 0xXX big endian + for (size_t i = 0; (i + 2) < p.size(); i += 3) { + if (p[i] == 0x02) { + payload.push_back( + remote_z::parseHarmony16_network(p[i + 1], p[i + 2])); + } else { + return Status::ERR_PAYLOAD_FORMAT; + } + } + return Status::OK; +} + +Single::Status Single::addChunk(const std::vector &data, bool first) +{ + auto status = Base::check(data); + if (status != Status::OK) return status; + + //data available, check remaining header + size + if ((data.size() != VALID_CHUNK_SIZE) || (data[4] != 0x01)) + return Status::ERR_RESPONSE_FORMAT; + + auto ret = parse({data.begin() + 6, data.end()}); + if (ret != Status::OK) return ret; + + //move non-timimg words + moveExcessBytes(first); + //calculate clock + if (first) clock = static_cast(excess[1]) * 1000000.0 / payload[0]; + + //check for end marker + if (data[5] == 0) return Status::DONE; + return Status::OK; +} + +} // namespace remote_z diff --git a/libconcord/remote_z_learn/single.h b/libconcord/remote_z_learn/single.h new file mode 100644 index 0000000..4eb0b68 --- /dev/null +++ b/libconcord/remote_z_learn/single.h @@ -0,0 +1,49 @@ +/* + * vim:tw=80:ai:tabstop=4:softtabstop=4:shiftwidth=4:expandtab + * + * 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * (C) Copyright Martin Wagner 2026 + */ + +#pragma once + +#include "base.h" + +namespace remote_z +{ + +//see learn.md _read single frame_ +class Single : public Base +{ + public: + private: + const std::vector frameReq = {0x00}; + + uint32_t getHeaderMinSize() { return 5; }; + uint32_t getProtoclCmd() { return COMMAND_LEARNIR_SINGLE; }; + uint32_t getProtocolRespByte3() { return 5; }; + + const uint32_t VALID_CHUNK_SIZE = 18; + + Status parse(const std::vector &p); + + public: + std::vector get() { return frameReq; } + + Status addChunk(const std::vector &data, bool first) override; +}; + +} // namespace remote_z diff --git a/libconcord/remote_z_learn/start.h b/libconcord/remote_z_learn/start.h new file mode 100644 index 0000000..53a1cda --- /dev/null +++ b/libconcord/remote_z_learn/start.h @@ -0,0 +1,41 @@ +/* + * vim:tw=80:ai:tabstop=4:softtabstop=4:shiftwidth=4:expandtab + * + * 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * (C) Copyright Martin Wagner 2026 + */ + +#pragma once + +#include "base.h" + +namespace remote_z +{ + +class Start : public Base +{ + private: + const std::vector frameReq = {0x01, 0x01, 0x00}; + + uint32_t getHeaderMinSize() { return 4; }; + uint32_t getProtoclCmd() { return COMMAND_LEARNIR_START; }; + uint32_t getProtocolRespByte3() { return 0; }; + + public: + std::vector get() { return frameReq; } +}; + +} // namespace remote_z diff --git a/libconcord/remote_z_learn/stop.h b/libconcord/remote_z_learn/stop.h new file mode 100644 index 0000000..3a25c0c --- /dev/null +++ b/libconcord/remote_z_learn/stop.h @@ -0,0 +1,41 @@ +/* + * vim:tw=80:ai:tabstop=4:softtabstop=4:shiftwidth=4:expandtab + * + * 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * (C) Copyright Martin Wagner 2026 + */ + +#pragma once + +#include "base.h" + +namespace remote_z +{ + +class Stop : public Base +{ + private: + const std::vector frameReq = {0x00}; + + uint32_t getHeaderMinSize() { return 4; }; + uint32_t getProtoclCmd() { return COMMAND_LEARNIR_DONE; }; + uint32_t getProtocolRespByte3() { return 0; }; + + public: + std::vector get() { return frameReq; } +}; + +} // namespace remote_z diff --git a/libconcord/remote_z_learn/stream.cpp b/libconcord/remote_z_learn/stream.cpp new file mode 100644 index 0000000..7b2f553 --- /dev/null +++ b/libconcord/remote_z_learn/stream.cpp @@ -0,0 +1,68 @@ +/* + * vim:tw=80:ai:tabstop=4:softtabstop=4:shiftwidth=4:expandtab + * + * 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * (C) Copyright Martin Wagner 2026 + */ + +#include "stream.h" +#include "binary.h" + +using namespace std; + +namespace remote_z +{ + +Stream::Status Stream::parse(const std::vector &p) +{ + uint16_t v; + + if ((p.size() % 2) != 0) return Status::ERR_SIZE; + + for (size_t i = 0; i < p.size(); i += 2) { + uint16_t a = p[i + 1]; + uint16_t b = p[i]; + v = a | (b << 8); + payload.push_back(v); + } + return Status::OK; +} + +Stream::Status Stream::addChunk(const std::vector &data, bool first) +{ + auto status = Base::check(data); + if (status != Status::OK) return status; + + //data available, check remaining header + size + if ((data.size() != VALID_CHUNK_SIZE) || (data[4] != 0x70)) + return Status::ERR_RESPONSE_FORMAT; + //terminator + if ((data[data.size() - 2] != 0x01) || (data[data.size() - 1] != 0x30)) + return Status::ERR_TERM; + + auto ret = remote_z::parseHarmony16_network( + {data.begin() + 5, data.end() - 2}, payload); + if (ret != true) return Status::ERR_SIZE; + + //move non-timing words + moveExcessBytes(first); + //calculate clock + if (first) clock = static_cast(excess[1]) * 1000000.0 / payload[0]; + + return Status::OK; +} + +} // namespace remote_z diff --git a/libconcord/remote_z_learn/stream.h b/libconcord/remote_z_learn/stream.h new file mode 100644 index 0000000..d354b24 --- /dev/null +++ b/libconcord/remote_z_learn/stream.h @@ -0,0 +1,49 @@ +/* + * vim:tw=80:ai:tabstop=4:softtabstop=4:shiftwidth=4:expandtab + * + * 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * (C) Copyright Martin Wagner 2026 + */ + +#pragma once + +#include "base.h" + +namespace remote_z +{ + +//see learn.md _read stream_ +class Stream : public Base +{ + public: + private: + const std::vector frameReq = {0x00}; + + uint32_t getHeaderMinSize() { return 5; }; + uint32_t getProtoclCmd() { return COMMAND_LEARNIR_STREAM; }; + uint32_t getProtocolRespByte3() { return 2; }; + + const uint32_t VALID_CHUNK_SIZE = 199; + + Status parse(const std::vector &p); + + public: + std::vector get() { return frameReq; } + + Status addChunk(const std::vector &data, bool first) override; +}; + +} // namespace remote_z From 4997f5d9eb56577381a1ed9dad9ecf9a1910539f Mon Sep 17 00:00:00 2001 From: Martin Wagner Date: Fri, 8 May 2026 17:57:56 +0200 Subject: [PATCH 4/8] Fix: signed warnings --- libconcord/remote_z_learn/data.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libconcord/remote_z_learn/data.cpp b/libconcord/remote_z_learn/data.cpp index e38f405..a856035 100644 --- a/libconcord/remote_z_learn/data.cpp +++ b/libconcord/remote_z_learn/data.cpp @@ -31,14 +31,14 @@ namespace remote_z void TimingStream::addMarkSegment(const vector &raw) { - for (int i = 0; i < raw.size(); i += 2) + for (size_t i = 0; i < raw.size(); i += 2) if (i + 1 < raw.size()) data.push_back(Block::fromMarkSegment(raw[i], raw[i + 1])); } void TimingStream::addMarkPause(const vector &raw) { - for (int i = 0; i < raw.size(); i += 2) + for (size_t i = 0; i < raw.size(); i += 2) if (i + 1 < raw.size()) data.push_back(Block::fromMarkPause(raw[i], raw[i + 1])); } From 7804948cce9d974c99e587f55bf71372772ae085 Mon Sep 17 00:00:00 2001 From: Martin Wagner Date: Fri, 8 May 2026 17:59:20 +0200 Subject: [PATCH 5/8] Add: H900 IR stream learning mode you can set a time and the remote will record all ir data for this time --- libconcord/libconcord.cpp | 16 +++++ libconcord/libconcord.h | 16 +++++ libconcord/remote.h | 7 ++ libconcord/remote_z.cpp | 134 ++++++++++++++++++++++++++++++++++++-- 4 files changed, 167 insertions(+), 6 deletions(-) diff --git a/libconcord/libconcord.cpp b/libconcord/libconcord.cpp index 273701f..e5d9ea1 100644 --- a/libconcord/libconcord.cpp +++ b/libconcord/libconcord.cpp @@ -1701,6 +1701,22 @@ void delete_key_names(char **key_names, uint32_t key_names_length) } } +/* + * set USBNET learn mode / time + */ +int set_learning_mode(int mode, uint32_t timeout_ms) +{ + if (rmt == NULL){ + return LC_ERROR_CONNECT; + } + + auto *p = dynamic_cast(rmt); + if (p == nullptr) { + return LC_ERROR; + } + return p->SetIrMode(mode, timeout_ms); +} + /* * Fill ir_data with IR code learned from other remote * via Harmony IR receiver. diff --git a/libconcord/libconcord.h b/libconcord/libconcord.h index 7024b6a..ed58f69 100644 --- a/libconcord/libconcord.h +++ b/libconcord/libconcord.h @@ -51,6 +51,11 @@ #define LC_FILE_TYPE_CONFIGURATION 2 #define LC_FILE_TYPE_FIRMWARE 3 #define LC_FILE_TYPE_LEARN_IR 4 +/* + * IR Learn mode + */ +#define LC_LEARN_SINGLE 0 +#define LC_LEARN_STREAM 1 /* * Callback counter types */ @@ -456,6 +461,17 @@ int get_key_names(char ***key_names, uint32_t *key_names_length); void delete_key_names(char **key_names, uint32_t key_names_length); +/* + * H900 only + * Set mode that will be used for learing. There are two available + * - Single: Remote waits a fixed time for a command, and if received + * returns the data of one single IR frame + * - Stream: Remote records IR data for the time given in _timeout_ms_ + * Any data within this window is returned, including silence. Data + * before the first reception is dropped. + */ +int set_learning_mode(int mode, uint32_t timeout_ms); + /* * Fill ir_data with IR code learned from other remote * via Harmony IR receiver. diff --git a/libconcord/remote.h b/libconcord/remote.h index 169cead..e3e9b4e 100644 --- a/libconcord/remote.h +++ b/libconcord/remote.h @@ -369,6 +369,9 @@ class CRemoteZ_HID : public CRemoteZ_Base class CRemoteZ_USBNET : public CRemoteZ_Base { protected: + int ir_learn_mode = LC_LEARN_SINGLE; + uint32_t ir_stream_timeout_ms = 1000; + int TCPSendAndCheck(uint8_t cmd, uint32_t len=0, uint8_t *data=NULL); virtual int Write(uint8_t typ, uint8_t cmd, uint32_t len=0, uint8_t *data=NULL); @@ -385,6 +388,9 @@ class CRemoteZ_USBNET : public CRemoteZ_Base virtual int ReadIrData(uint32_t *freq, uint32_t **ir_signal, uint32_t *ir_signal_length, lc_callback cb, void *cb_arg, uint32_t cb_stage); + virtual int ReadIrStream(uint32_t *freq, uint32_t **ir_signal, + uint32_t *ir_signal_length, lc_callback cb, void *cb_arg, + uint32_t cb_stage); public: CRemoteZ_USBNET() {}; @@ -395,6 +401,7 @@ class CRemoteZ_USBNET : public CRemoteZ_Base int GetTime(const TRemoteInfo &ri, THarmonyTime &ht); int SetTime(const TRemoteInfo &ri, const THarmonyTime &ht, lc_callback cb=NULL, void *cb_arg=NULL, uint32_t cb_stage=0); + int SetIrMode(int mode = LC_LEARN_SINGLE, uint32_t stream_timeout_ms = 1000); int LearnIR(uint32_t *freq, uint32_t **ir_signal, uint32_t *ir_signal_length, lc_callback cb=NULL, void *cb_arg=NULL, uint32_t cb_stage=0); diff --git a/libconcord/remote_z.cpp b/libconcord/remote_z.cpp index 68125c1..d52843a 100644 --- a/libconcord/remote_z.cpp +++ b/libconcord/remote_z.cpp @@ -584,6 +584,18 @@ int CRemoteZ_USBNET::SetTime(const TRemoteInfo &ri, const THarmonyTime &ht, return 0; } + +int CRemoteZ_USBNET::SetIrMode(int mode, uint32_t stream_timeout_ms) +{ + if (mode == LC_LEARN_SINGLE || mode == LC_LEARN_STREAM) { + ir_learn_mode = mode; + } else { + return -1; + } + ir_stream_timeout_ms = stream_timeout_ms; + return 0; +} + int CRemoteZ_USBNET::LearnIR(uint32_t *freq, uint32_t **ir_signal, uint32_t *ir_signal_length, lc_callback cb, void *cb_arg, uint32_t cb_stage) @@ -599,8 +611,14 @@ int CRemoteZ_USBNET::LearnIR(uint32_t *freq, uint32_t **ir_signal, return err; } - if ((err = ReadIrData( - freq, ir_signal, ir_signal_length, cb, cb_arg, cb_stage))) { + if (ir_learn_mode == LC_LEARN_STREAM) { + err = ReadIrStream(freq, ir_signal, ir_signal_length, cb, cb_arg, + cb_stage); + } else { + err = ReadIrData(freq, ir_signal, ir_signal_length, cb, cb_arg, + cb_stage); + } + if (err) { SendLearnStop(); //always close! debug("Failed starting learn mode"); return err; @@ -712,8 +730,8 @@ int CRemoteZ_USBNET::ReadIrData(uint32_t *freq, uint32_t **ir_signal, debug("DATA %d, read %d bytest", cb_count, len) - auto ret = single.addChunk(std::vector(rsp, rsp + len), - firstChunk); + auto ret = single.addChunk(std::vector(rsp, rsp + len), + firstChunk); if (ret == remote_z::Single::Status::DONE) break; switch (ret) { case remote_z::Single::Status::OK: @@ -746,7 +764,8 @@ int CRemoteZ_USBNET::ReadIrData(uint32_t *freq, uint32_t **ir_signal, //standardise auto payload = single.getPayload(); - if (payload.empty()) return 0; + if (payload.empty()) + return 0; auto timingStream = remote_z::TimingStream::fromMarkSegment(payload); //convert to libconcord format @@ -754,7 +773,110 @@ int CRemoteZ_USBNET::ReadIrData(uint32_t *freq, uint32_t **ir_signal, *freq = single.getClock(); *ir_signal_length = t.size(); *ir_signal = new uint32_t[t.size()]; - for (uint32_t i = 0; i < t.size(); i++) (*ir_signal)[i] = t[i]; + for (uint32_t i = 0; i < t.size(); i++) { + (*ir_signal)[i] = t[i]; + } + + debug("READ_IR_DONE"); + + return 0; +} + +int CRemoteZ_USBNET::ReadIrStream(uint32_t *freq, uint32_t **ir_signal, + uint32_t *ir_signal_length, lc_callback cb, + void *cb_arg, uint32_t cb_stage) +{ + remote_z::Stream stream; + uint8_t rsp[USBNET_MAX_PACKET_SIZE + 3]; /* add standard 3-byte header */ + int err = 0; + int cb_count = 0; + bool firstChunk = true; + chrono::milliseconds timeout(ir_stream_timeout_ms); + + debug("READING_IR_STREAM"); + + auto t_start = chrono::steady_clock::now(); + + /* + * Caller is responsible for deallocation of *ir_signal after use. + * + * Loop while we haven not: + * - any error + * - timeout (not an error, we record for this amount of time) + * be aware that timeout is a minimum value. the remote will + * always fill a data frame before returning. + */ + while (err == 0) { + auto req = stream.get(); + if ((err = Write(TYPE_REQUEST, + COMMAND_LEARNIR_STREAM, + req.size(), + req.data()))) { + debug("Failed to write to remote"); + err = LC_ERROR_WRITE; + break; + } + unsigned int len; + uint8_t status; + if ((err = Read(status, len, rsp))) { + debug("Failed to read to remote"); + err = LC_ERROR_READ; + break; + } + + debug("DATA %d, read %d bytest", cb_count, len) + + auto ret = stream.addChunk(std::vector(rsp, rsp + len), + firstChunk); + if (ret == remote_z::Single::Status::DONE) break; + switch (ret) { + case remote_z::Single::Status::OK: + break; + case remote_z::Single::Status::ERR_TIMEOUT: + err = LC_ERROR_IR_TIMEOUT; + break; + default: + debug("Incorrect response type from remote (%d)", (int)ret); + err = LC_ERROR_INVALID_DATA_FROM_REMOTE; + break; + } + + firstChunk = false; + + if (cb) { + cb(cb_stage, + cb_count++, + stream.getPayload().size(), + 0, + LC_CB_COUNTER_TYPE_STEPS, + cb_arg, + NULL); + } + + auto t_now = chrono::steady_clock::now(); + if ((t_now - t_start) > timeout) { + break; + } + } + + if (err != 0) return err; + + debug("DECODING_IR_DATA"); + + //standardise + auto payload = stream.getPayload(); + if (payload.empty()) + return 0; + auto timingStream = remote_z::TimingStream::fromMarkSegment(payload); + + //convert to libconcord format + const auto &t = timingStream.convertMarkPause(); + *freq = stream.getClock(); + *ir_signal_length = t.size(); + *ir_signal = new uint32_t[t.size()]; + for (uint32_t i = 0; i < t.size(); i++) { + (*ir_signal)[i] = t[i]; + } debug("READ_IR_DONE"); From 9337ed350cb34e3952ab24ca4078d958b1b0660b Mon Sep 17 00:00:00 2001 From: Martin Wagner Date: Sat, 9 May 2026 08:30:40 +0200 Subject: [PATCH 6/8] Change: add h900 learning to docs --- SupportedModels.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/SupportedModels.md b/SupportedModels.md index 9ac9f45..0fa4315 100644 --- a/SupportedModels.md +++ b/SupportedModels.md @@ -21,6 +21,7 @@ Don't see your model? We may have missed one. Just give it a shot, and if you ru | 10 | 890
895 | Working | Not working | Not working | | | 12 | One | Working | Working | Not working | | | 14 | 700 | Working | Believed working | Not working | Needs kernel > 2.6.37 | -| 15 | 900
1000
1000i
1100
Xbox | Working | Not working | Not working | Not yet supported on Mac | -| 16 | 200
300 | Working | Working | Not working | | -| 17 | Link | Working | Working | Not working | | +| 15 | 900 | Working | Working | Not working | Not yet supported on Mac | +| 16 | 1000
1000i
1100
Xbox | Working | Not working | Not working | Not yet supported on Mac | +| 17 | 200
300 | Working | Working | Not working | | +| 18 | Link | Working | Working | Not working | | From 0c44e58399e89646dc40f6cbde014fecbde20a1d Mon Sep 17 00:00:00 2001 From: Martin Wagner Date: Fri, 10 Jul 2026 15:19:44 +0200 Subject: [PATCH 7/8] Fix: first column is arch, not row number --- SupportedModels.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/SupportedModels.md b/SupportedModels.md index 0fa4315..ea53cd0 100644 --- a/SupportedModels.md +++ b/SupportedModels.md @@ -22,6 +22,6 @@ Don't see your model? We may have missed one. Just give it a shot, and if you ru | 12 | One | Working | Working | Not working | | | 14 | 700 | Working | Believed working | Not working | Needs kernel > 2.6.37 | | 15 | 900 | Working | Working | Not working | Not yet supported on Mac | -| 16 | 1000
1000i
1100
Xbox | Working | Not working | Not working | Not yet supported on Mac | -| 17 | 200
300 | Working | Working | Not working | | -| 18 | Link | Working | Working | Not working | | +| | 1000
1000i
1100
Xbox | Working | Not working | Not working | Not yet supported on Mac | +| 16 | 200
300 | Working | Working | Not working | | +| 17 | Link | Working | Working | Not working | | From 14f2bd43b24386cc9ac5f6b427c1a661624d0896 Mon Sep 17 00:00:00 2001 From: Phil Dibowitz Date: Fri, 10 Jul 2026 11:29:48 -0700 Subject: [PATCH 8/8] Make it clear both rows are arch 15 Signed-off-by: Phil Dibowitz --- SupportedModels.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SupportedModels.md b/SupportedModels.md index ea53cd0..18c3d06 100644 --- a/SupportedModels.md +++ b/SupportedModels.md @@ -22,6 +22,6 @@ Don't see your model? We may have missed one. Just give it a shot, and if you ru | 12 | One | Working | Working | Not working | | | 14 | 700 | Working | Believed working | Not working | Needs kernel > 2.6.37 | | 15 | 900 | Working | Working | Not working | Not yet supported on Mac | -| | 1000
1000i
1100
Xbox | Working | Not working | Not working | Not yet supported on Mac | +| 15 | 1000
1000i
1100
Xbox | Working | Not working | Not working | Not yet supported on Mac | | 16 | 200
300 | Working | Working | Not working | | | 17 | Link | Working | Working | Not working | |