From 9c1e9c3d3c0ae46d78169b3f2d696bc310400c51 Mon Sep 17 00:00:00 2001 From: LucHeart Date: Wed, 27 May 2026 04:00:00 +0200 Subject: [PATCH 1/3] fix: Encode T330 shock intensity as 4-bit level with rolling state The T330 collar variant reads shock intensity from the lower nibble (0-15) of the intensity byte, not as a raw 0-100 value. The upper bits carry a rolling counter and toggle bit. Map 0-100 intensity to 0-15 level in the lower nibble and maintain per-shocker rolling state (counter + toggle) in the upper bits. Vibrate and sound encoding remain unchanged. --- src/radio/rmt/T330Encoder.cpp | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/radio/rmt/T330Encoder.cpp b/src/radio/rmt/T330Encoder.cpp index 59896daa..4e19da55 100644 --- a/src/radio/rmt/T330Encoder.cpp +++ b/src/radio/rmt/T330Encoder.cpp @@ -3,6 +3,7 @@ #include "radio/rmt/internal/Shared.h" #include +#include const rmt_data_t kRmtPreamble = {960, 1, 790, 0}; const rmt_data_t kRmtOne = {220, 1, 980, 0}; @@ -11,6 +12,13 @@ const rmt_data_t kRmtPostamble = {220, 1, 135, 0}; using namespace OpenShock; +struct ShockRollingState { + uint8_t counter = 0; + bool toggle = false; +}; + +static std::unordered_map s_shockState; + size_t Rmt::WellturnT330Encoder::GetBufferSize() { return 43; @@ -37,10 +45,23 @@ bool Rmt::WellturnT330Encoder::FillBuffer(rmt_data_t* sequence, uint16_t shocker return false; // Invalid type } + // Shock intensity byte: [toggle:1][counter:3][level:4] + // The collar reads shock intensity from the lower nibble (0-15), not as a raw 0-100 value. + uint8_t intensityByte = intensity; + if (type == ShockerCommandType::Shock) { + auto& state = s_shockState[shockerId]; + uint8_t level = (intensity * 15) / 100; + intensityByte = (static_cast(state.toggle) << 7) | ((state.counter & 0x7) << 4) | (level & 0xF); + state.toggle = !state.toggle; + if (!state.toggle) { + state.counter = (state.counter + 1) & 0x7; + } + } + uint8_t channelId = 0; // CH1 is 0b0000 and CH2 is 0b1110 on my remote but other values probably work. // Payload layout: [channelId:4][typeU:4][transmitterId:16][intensity:8][typeL:4][channelId:4] - uint64_t data = (static_cast(channelId & 0xF) << 36) | (static_cast(typeVal & 0xF0) << 28) | (static_cast(shockerId) << 16) | (static_cast(intensity) << 8) | (static_cast(typeVal & 0xF) << 4) + uint64_t data = (static_cast(channelId & 0xF) << 36) | (static_cast(typeVal & 0xF0) << 28) | (static_cast(shockerId) << 16) | (static_cast(intensityByte) << 8) | (static_cast(typeVal & 0xF) << 4) | static_cast(channelId & 0xF); // Shift the data left by 1 bit to append a zero From 84829ed3725eacd63993e92d6ec425e21f7698ca Mon Sep 17 00:00:00 2001 From: LucHeart Date: Wed, 27 May 2026 20:57:18 +0200 Subject: [PATCH 2/3] very bad impl of alternating the toggle bit --- include/radio/rmt/Sequence.h | 11 +++++++++++ src/radio/RFTransmitter.cpp | 2 +- src/radio/rmt/Sequence.cpp | 17 +++++++++++++++++ src/radio/rmt/T330Encoder.cpp | 29 ++++++++++++++++++++++------- 4 files changed, 51 insertions(+), 8 deletions(-) diff --git a/include/radio/rmt/Sequence.h b/include/radio/rmt/Sequence.h index ba043258..ea8ae17d 100644 --- a/include/radio/rmt/Sequence.h +++ b/include/radio/rmt/Sequence.h @@ -19,6 +19,8 @@ namespace OpenShock::Rmt { , m_transmitEnd(0) , m_shockerId(0) , m_shockerModel() + , m_commandType() + , m_intensity(0) { } Sequence(ShockerModelType shockerModel, uint16_t shockerId, int64_t transmitEnd); @@ -28,6 +30,8 @@ namespace OpenShock::Rmt { , m_transmitEnd(other.m_transmitEnd) , m_shockerId(other.m_shockerId) , m_shockerModel(other.m_shockerModel) + , m_commandType(other.m_commandType) + , m_intensity(other.m_intensity) { other.reset(); } @@ -48,6 +52,7 @@ namespace OpenShock::Rmt { inline size_t size() const noexcept { return m_size; } bool fill(ShockerCommandType commandType, uint8_t intensity); + bool refill(); Sequence& operator=(Sequence&& other) { @@ -60,6 +65,8 @@ namespace OpenShock::Rmt { m_transmitEnd = other.m_transmitEnd; m_shockerId = other.m_shockerId; m_shockerModel = other.m_shockerModel; + m_commandType = other.m_commandType; + m_intensity = other.m_intensity; other.reset(); @@ -74,6 +81,8 @@ namespace OpenShock::Rmt { m_transmitEnd = 0; m_shockerId = 0; m_shockerModel = static_cast(0); + m_commandType = static_cast(0); + m_intensity = 0; } rmt_data_t* m_data; @@ -81,5 +90,7 @@ namespace OpenShock::Rmt { int64_t m_transmitEnd; uint16_t m_shockerId; ShockerModelType m_shockerModel; + ShockerCommandType m_commandType; + uint8_t m_intensity; }; } // namespace OpenShock::Rmt diff --git a/src/radio/RFTransmitter.cpp b/src/radio/RFTransmitter.cpp index 9f5de283..50de338f 100644 --- a/src/radio/RFTransmitter.cpp +++ b/src/radio/RFTransmitter.cpp @@ -180,7 +180,7 @@ static void writeSequences(rmt_obj_t* rmt_handle, std::vector& se int64_t timeToLive = seq->transmitEnd() - OpenShock::millis(); if (timeToLive > 0) { - // Send the command + seq->refill(); rmtWriteBlocking(rmt_handle, seq->payload(), seq->size()); } else { // Remove command if it has sent out its termination sequence for long enough diff --git a/src/radio/rmt/Sequence.cpp b/src/radio/rmt/Sequence.cpp index 9d952cc8..0d820574 100644 --- a/src/radio/rmt/Sequence.cpp +++ b/src/radio/rmt/Sequence.cpp @@ -73,5 +73,22 @@ Rmt::Sequence::Sequence(ShockerModelType shockerModel, uint16_t shockerId, int64 bool Rmt::Sequence::fill(ShockerCommandType commandType, uint8_t intensity) { + m_commandType = commandType; + m_intensity = intensity; return fillSequenceImpl(payload(), m_shockerModel, m_shockerId, commandType, intensity); } + +static bool refillSequenceImpl(rmt_data_t* data, ShockerModelType modelType, uint16_t shockerId, ShockerCommandType commandType, uint8_t intensity) +{ + switch (modelType) { + case ShockerModelType::WellturnT330: + return Rmt::WellturnT330Encoder::FillBuffer(data, shockerId, commandType, intensity); + default: + return true; + } +} + +bool Rmt::Sequence::refill() +{ + return refillSequenceImpl(payload(), m_shockerModel, m_shockerId, m_commandType, m_intensity); +} diff --git a/src/radio/rmt/T330Encoder.cpp b/src/radio/rmt/T330Encoder.cpp index 4e19da55..1fde1581 100644 --- a/src/radio/rmt/T330Encoder.cpp +++ b/src/radio/rmt/T330Encoder.cpp @@ -1,5 +1,6 @@ #include "radio/rmt/T330Encoder.h" +#include "Core.h" #include "radio/rmt/internal/Shared.h" #include @@ -13,8 +14,9 @@ const rmt_data_t kRmtPostamble = {220, 1, 135, 0}; using namespace OpenShock; struct ShockRollingState { - uint8_t counter = 0; - bool toggle = false; + int64_t lastFillTime = 0; + int64_t transmitStart = 0; + uint8_t baseCounter = 0; }; static std::unordered_map s_shockState; @@ -46,16 +48,29 @@ bool Rmt::WellturnT330Encoder::FillBuffer(rmt_data_t* sequence, uint16_t shocker } // Shock intensity byte: [toggle:1][counter:3][level:4] - // The collar reads shock intensity from the lower nibble (0-15), not as a raw 0-100 value. + // Toggle flips every ~1s, counter increments every ~2s (each toggle cycle). + // Computed from wall-clock time so the buffer can be re-filled each send. uint8_t intensityByte = intensity; if (type == ShockerCommandType::Shock) { auto& state = s_shockState[shockerId]; uint8_t level = (intensity * 15) / 100; - intensityByte = (static_cast(state.toggle) << 7) | ((state.counter & 0x7) << 4) | (level & 0xF); - state.toggle = !state.toggle; - if (!state.toggle) { - state.counter = (state.counter + 1) & 0x7; + + int64_t now = OpenShock::millis(); + if (state.lastFillTime == 0 || (now - state.lastFillTime) > 200) { + if (state.lastFillTime != 0) { + state.baseCounter = (state.baseCounter + 1) & 0x7; + } + state.transmitStart = now; } + state.lastFillTime = now; + + int64_t elapsed = now - state.transmitStart; + int64_t seconds = elapsed / 1000; + + bool toggle = (seconds % 2) != 0; + uint8_t counter = (state.baseCounter + static_cast(seconds / 2)) & 0x7; + + intensityByte = (static_cast(toggle) << 7) | ((counter & 0x7) << 4) | (level & 0xF); } uint8_t channelId = 0; // CH1 is 0b0000 and CH2 is 0b1110 on my remote but other values probably work. From 567dad692f0017a020772d14940173dd7da00d78 Mon Sep 17 00:00:00 2001 From: LucHeart Date: Wed, 27 May 2026 23:01:07 +0200 Subject: [PATCH 3/3] meh --- src/radio/rmt/T330Encoder.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/radio/rmt/T330Encoder.cpp b/src/radio/rmt/T330Encoder.cpp index 1fde1581..6e7e08c2 100644 --- a/src/radio/rmt/T330Encoder.cpp +++ b/src/radio/rmt/T330Encoder.cpp @@ -65,10 +65,10 @@ bool Rmt::WellturnT330Encoder::FillBuffer(rmt_data_t* sequence, uint16_t shocker state.lastFillTime = now; int64_t elapsed = now - state.transmitStart; - int64_t seconds = elapsed / 1000; + int64_t periods = elapsed / 1000; - bool toggle = (seconds % 2) != 0; - uint8_t counter = (state.baseCounter + static_cast(seconds / 2)) & 0x7; + bool toggle = (periods % 2) != 0; + uint8_t counter = (state.baseCounter + static_cast(periods / 2)) & 0x7; intensityByte = (static_cast(toggle) << 7) | ((counter & 0x7) << 4) | (level & 0xF); }