Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions include/radio/rmt/Sequence.h
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#pragma once

Check warning on line 1 in include/radio/rmt/Sequence.h

View workflow job for this annotation

GitHub Actions / C/C++ Linter

include/radio/rmt/Sequence.h:1:1 [portability-avoid-pragma-once]

avoid 'pragma once' directive; use include guards instead

#include "Common.h"
#include "ShockerCommandType.h"
Expand All @@ -10,32 +10,36 @@

namespace OpenShock::Rmt {
class Sequence {
DISABLE_COPY(Sequence);

Check warning on line 13 in include/radio/rmt/Sequence.h

View workflow job for this annotation

GitHub Actions / C/C++ Linter

include/radio/rmt/Sequence.h:13:5 [modernize-use-trailing-return-type]

use a trailing return type for this function

public:
Sequence()

Check warning on line 16 in include/radio/rmt/Sequence.h

View workflow job for this annotation

GitHub Actions / C/C++ Linter

include/radio/rmt/Sequence.h:16:5 [cppcoreguidelines-pro-type-member-init]

constructor does not initialize these fields: m_size, m_transmitEnd, m_shockerId, m_intensity
: m_data(nullptr)
, m_size(0)
, m_transmitEnd(0)
, m_shockerId(0)
, m_shockerModel()
, m_commandType()
, m_intensity(0)
{
}
Sequence(ShockerModelType shockerModel, uint16_t shockerId, int64_t transmitEnd);
Sequence(Sequence&& other) noexcept

Check warning on line 27 in include/radio/rmt/Sequence.h

View workflow job for this annotation

GitHub Actions / C/C++ Linter

include/radio/rmt/Sequence.h:27:5 [cppcoreguidelines-pro-type-member-init]

constructor does not initialize these fields: m_size, m_transmitEnd, m_shockerId, m_intensity
: m_data(other.m_data)
, m_size(other.m_size)
, 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();
}
~Sequence() { free(m_data); }

inline bool is_valid() const noexcept { return m_data != nullptr && m_size > 0; }

Check warning on line 40 in include/radio/rmt/Sequence.h

View workflow job for this annotation

GitHub Actions / C/C++ Linter

include/radio/rmt/Sequence.h:40:17 [modernize-use-trailing-return-type]

use a trailing return type for this function

Check warning on line 40 in include/radio/rmt/Sequence.h

View workflow job for this annotation

GitHub Actions / C/C++ Linter

include/radio/rmt/Sequence.h:40:5 [readability-redundant-inline-specifier]

function 'is_valid' has inline specifier but is implicitly inlined

Check warning on line 40 in include/radio/rmt/Sequence.h

View workflow job for this annotation

GitHub Actions / C/C++ Linter

include/radio/rmt/Sequence.h:40:5 [modernize-use-nodiscard]

function 'is_valid' should be marked [[nodiscard]]

inline ShockerModelType shockerModel() const noexcept { return m_shockerModel; }

Check warning on line 42 in include/radio/rmt/Sequence.h

View workflow job for this annotation

GitHub Actions / C/C++ Linter

include/radio/rmt/Sequence.h:42:29 [modernize-use-trailing-return-type]

use a trailing return type for this function

Check warning on line 42 in include/radio/rmt/Sequence.h

View workflow job for this annotation

GitHub Actions / C/C++ Linter

include/radio/rmt/Sequence.h:42:5 [readability-redundant-inline-specifier]

function 'shockerModel' has inline specifier but is implicitly inlined

Check warning on line 42 in include/radio/rmt/Sequence.h

View workflow job for this annotation

GitHub Actions / C/C++ Linter

include/radio/rmt/Sequence.h:42:5 [modernize-use-nodiscard]

function 'shockerModel' should be marked [[nodiscard]]
inline uint16_t shockerId() const noexcept { return m_shockerId; }

inline int64_t transmitEnd() const noexcept { return m_transmitEnd; }
Expand All @@ -48,6 +52,7 @@
inline size_t size() const noexcept { return m_size; }

bool fill(ShockerCommandType commandType, uint8_t intensity);
bool refill();

Sequence& operator=(Sequence&& other)
{
Expand All @@ -60,6 +65,8 @@
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();

Expand All @@ -74,12 +81,16 @@
m_transmitEnd = 0;
m_shockerId = 0;
m_shockerModel = static_cast<ShockerModelType>(0);
m_commandType = static_cast<ShockerCommandType>(0);
m_intensity = 0;
}

rmt_data_t* m_data;
size_t m_size;
int64_t m_transmitEnd;
uint16_t m_shockerId;
ShockerModelType m_shockerModel;
ShockerCommandType m_commandType;
uint8_t m_intensity;
};
} // namespace OpenShock::Rmt
2 changes: 1 addition & 1 deletion src/radio/RFTransmitter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ static void writeSequences(rmt_obj_t* rmt_handle, std::vector<Rmt::Sequence>& 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
Expand Down
17 changes: 17 additions & 0 deletions src/radio/rmt/Sequence.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
38 changes: 37 additions & 1 deletion src/radio/rmt/T330Encoder.cpp
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
#include "radio/rmt/T330Encoder.h"

#include "Core.h"
#include "radio/rmt/internal/Shared.h"

#include <algorithm>
#include <unordered_map>

const rmt_data_t kRmtPreamble = {960, 1, 790, 0};
const rmt_data_t kRmtOne = {220, 1, 980, 0};
Expand All @@ -11,6 +13,14 @@ const rmt_data_t kRmtPostamble = {220, 1, 135, 0};

using namespace OpenShock;

struct ShockRollingState {
int64_t lastFillTime = 0;
int64_t transmitStart = 0;
uint8_t baseCounter = 0;
};

static std::unordered_map<uint16_t, ShockRollingState> s_shockState;

size_t Rmt::WellturnT330Encoder::GetBufferSize()
{
return 43;
Expand All @@ -37,10 +47,36 @@ bool Rmt::WellturnT330Encoder::FillBuffer(rmt_data_t* sequence, uint16_t shocker
return false; // Invalid type
}

// Shock intensity byte: [toggle:1][counter:3][level:4]
// 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;

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 periods = elapsed / 1000;

bool toggle = (periods % 2) != 0;
uint8_t counter = (state.baseCounter + static_cast<uint8_t>(periods / 2)) & 0x7;

intensityByte = (static_cast<uint8_t>(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.

// Payload layout: [channelId:4][typeU:4][transmitterId:16][intensity:8][typeL:4][channelId:4]
uint64_t data = (static_cast<uint64_t>(channelId & 0xF) << 36) | (static_cast<uint64_t>(typeVal & 0xF0) << 28) | (static_cast<uint64_t>(shockerId) << 16) | (static_cast<uint64_t>(intensity) << 8) | (static_cast<uint64_t>(typeVal & 0xF) << 4)
uint64_t data = (static_cast<uint64_t>(channelId & 0xF) << 36) | (static_cast<uint64_t>(typeVal & 0xF0) << 28) | (static_cast<uint64_t>(shockerId) << 16) | (static_cast<uint64_t>(intensityByte) << 8) | (static_cast<uint64_t>(typeVal & 0xF) << 4)
| static_cast<uint64_t>(channelId & 0xF);

// Shift the data left by 1 bit to append a zero
Expand Down
Loading