diff --git a/include/ShockerModelType.h b/include/ShockerModelType.h index edb9cd36..0e013c4a 100644 --- a/include/ShockerModelType.h +++ b/include/ShockerModelType.h @@ -1,12 +1,16 @@ #pragma once -#include "serialization/_fbs/ShockerModelType_generated.h" - #include #include namespace OpenShock { - typedef OpenShock::Serialization::Types::ShockerModelType ShockerModelType; + enum class ShockerModelType : uint8_t { + CaiXianlin, + Petrainer, + Petrainer998DR, + T330, + D80 + }; inline bool ShockerModelTypeFromString(const char* str, ShockerModelType& out, bool allowTypo = false) { if (strcasecmp(str, "caixianlin") == 0 || strcasecmp(str, "cai-xianlin") == 0) { @@ -34,6 +38,16 @@ namespace OpenShock { return true; } + if (strcasecmp(str, "t330") == 0) { + out = ShockerModelType::T330; + return true; + } + + if (strcasecmp(str, "d80") == 0) { + out = ShockerModelType::D80; + return true; + } + return false; } } // namespace OpenShock diff --git a/include/radio/rmt/D80Encoder.h b/include/radio/rmt/D80Encoder.h new file mode 100644 index 00000000..e3c313c8 --- /dev/null +++ b/include/radio/rmt/D80Encoder.h @@ -0,0 +1,12 @@ +#pragma once + +#include "ShockerCommandType.h" + +#include + +#include + +namespace OpenShock::Rmt::D80Encoder { + size_t GetBufferSize(); + bool FillBuffer(rmt_data_t* data, uint16_t shockerId, ShockerCommandType type, uint8_t intensity); +} diff --git a/src/message_handlers/websocket/gateway/ShockerCommandList.cpp b/src/message_handlers/websocket/gateway/ShockerCommandList.cpp index 1ff544ac..0d93fc9a 100644 --- a/src/message_handlers/websocket/gateway/ShockerCommandList.cpp +++ b/src/message_handlers/websocket/gateway/ShockerCommandList.cpp @@ -35,6 +35,28 @@ void _Private::HandleShockerCommandList(const OpenShock::Serialization::Gateway: FbsModelType fbsModel = command->model(); FbsCommandType fbsCommandType = command->type(); + OpenShock::ShockerModelType model; + switch (fbsModel) { + case FbsModelType::CaiXianlin: + model = OpenShock::ShockerModelType::CaiXianlin; + break; + case FbsModelType::Petrainer: + model = OpenShock::ShockerModelType::Petrainer; + break; + case FbsModelType::Petrainer998DR: + model = OpenShock::ShockerModelType::Petrainer998DR; + break; + // case FbsModelType::T330: + // model = OpenShock::ShockerModelType::T330; + // break; + // case FbsModelType::D80: + // model = OpenShock::ShockerModelType::D80; + // break; + default: + OS_LOGE(TAG, "Unsupported shocker model: %s", OpenShock::Serialization::Types::EnumNameShockerModelType(fbsModel)); + continue; + } + OpenShock::ShockerCommandType commandType; switch (fbsCommandType) { case FbsCommandType::Stop: @@ -57,7 +79,7 @@ void _Private::HandleShockerCommandList(const OpenShock::Serialization::Gateway: continue; } - if (!OpenShock::CommandHandler::HandleCommand(fbsModel, id, commandType, intensity, durationMs)) { + if (!OpenShock::CommandHandler::HandleCommand(model, id, commandType, intensity, durationMs)) { OS_LOGE(TAG, "Remote command failed/rejected!"); } } diff --git a/src/radio/RFTransmitter.cpp b/src/radio/RFTransmitter.cpp index 95b510fe..8456931c 100644 --- a/src/radio/RFTransmitter.cpp +++ b/src/radio/RFTransmitter.cpp @@ -13,6 +13,8 @@ const char* const TAG = "RFTransmitter"; #include +#include + const UBaseType_t kQueueSize = 64; const BaseType_t kTaskPriority = 1; const uint32_t kTaskStackSize = 4096; // PROFILED: 1.4KB stack usage diff --git a/src/radio/rmt/CaiXianlinEncoder.cpp b/src/radio/rmt/CaiXianlinEncoder.cpp index 419d8b71..72077146 100644 --- a/src/radio/rmt/CaiXianlinEncoder.cpp +++ b/src/radio/rmt/CaiXianlinEncoder.cpp @@ -39,6 +39,9 @@ bool Rmt::CaiXianlinEncoder::FillBuffer(rmt_data_t* sequence, uint16_t shockerId typeVal = 0x03; intensity = 0; // Sound intensity must be 0 for some shockers, otherwise it wont work, or they soft lock until restarted break; + case ShockerCommandType::Light: + typeVal = 0x04; + break; default: return false; // Invalid type } diff --git a/src/radio/rmt/D80Encoder.cpp b/src/radio/rmt/D80Encoder.cpp new file mode 100644 index 00000000..721b3b1e --- /dev/null +++ b/src/radio/rmt/D80Encoder.cpp @@ -0,0 +1,62 @@ +#include "radio/rmt/D80Encoder.h" + +#include "radio/rmt/internal/Shared.h" + +#include "Checksum.h" + +#include + +const rmt_data_t kRmtPreamble = {1900, 1, 4000, 0}; +const rmt_data_t kRmtOne = {900, 1, 300, 0}; +const rmt_data_t kRmtZero = {300, 1, 900, 0}; +const rmt_data_t kRmtPostamble = {200, 1, 2200, 0}; + +using namespace OpenShock; + +size_t Rmt::D80Encoder::GetBufferSize() +{ + return 42; +} + +bool Rmt::D80Encoder::FillBuffer(rmt_data_t* sequence, uint16_t shockerId, ShockerCommandType type, uint8_t intensity) +{ + // Intensity must be between 0 and 15, this should mimic the rounding of the original remote which + // allows you to select from 1-99 when the protocol only has 4 bits for intensity (0-15). + if (intensity > 0) + intensity = std::max((intensity*15)/100, 1); + + uint8_t typeVal = 0; + switch (type) { + case ShockerCommandType::Shock: + typeVal = 0x01; + break; + case ShockerCommandType::Vibrate: + typeVal = 0x02; + break; + case ShockerCommandType::Sound: + typeVal = 0x03; + intensity = 0; // The remote always sends 0, I don't know what happens if you send something else. + break; + default: + return false; // Invalid type + } + + uint8_t channelId = 1; // Channel ID is 1 or 2 for separate control or 3 for both channels + + // Payload layout: 00000100[shockerId:16][type:2][channelId:2][intensity:4] + // The first byte is always 0x04, the two remotes both use this and it wont pair to other values. + uint32_t payload = 0x04000000 | (static_cast(shockerId) << 8) | (static_cast(typeVal & 0x3) << 6) | (static_cast(channelId & 0x3) << 4) | static_cast(intensity & 0xF); + + // Calculate the checksum of the payload + uint8_t checksum = Checksum::Sum8(payload); + + // Add the checksum to the payload + uint64_t data = (static_cast(payload) << 8) | static_cast(checksum); + + // Generate the sequence + sequence[0] = kRmtPreamble; + Rmt::Internal::EncodeBits<40>(sequence + 1, data, kRmtOne, kRmtZero); + sequence[41] = kRmtPostamble; + + return true; +} diff --git a/src/radio/rmt/Sequence.cpp b/src/radio/rmt/Sequence.cpp index ce64d6d2..a842c8f1 100644 --- a/src/radio/rmt/Sequence.cpp +++ b/src/radio/rmt/Sequence.cpp @@ -7,6 +7,7 @@ const char* const TAG = "Sequence"; #include "radio/rmt/Petrainer998DREncoder.h" #include "radio/rmt/PetrainerEncoder.h" #include "radio/rmt/T330Encoder.h" +#include "radio/rmt/D80Encoder.h" using namespace OpenShock; @@ -19,6 +20,10 @@ inline static size_t getSequenceBufferSize(ShockerModelType shockerModelType) return Rmt::Petrainer998DREncoder::GetBufferSize(); case ShockerModelType::Petrainer: return Rmt::PetrainerEncoder::GetBufferSize(); + case ShockerModelType::T330: + return Rmt::T330Encoder::GetBufferSize(); + case ShockerModelType::D80: + return Rmt::D80Encoder::GetBufferSize(); default: return 0; } @@ -33,6 +38,10 @@ inline static bool fillSequenceImpl(rmt_data_t* data, ShockerModelType modelType return Rmt::PetrainerEncoder::FillBuffer(data, shockerId, commandType, intensity); case ShockerModelType::Petrainer998DR: return Rmt::Petrainer998DREncoder::FillBuffer(data, shockerId, commandType, intensity); + case ShockerModelType::T330: + return Rmt::T330Encoder::FillBuffer(data, shockerId, commandType, intensity); + case ShockerModelType::D80: + return Rmt::D80Encoder::FillBuffer(data, shockerId, commandType, intensity); default: OS_LOGE(TAG, "Unknown shocker model: %u", modelType); return false; diff --git a/src/serial/command_handlers/rftransmit.cpp b/src/serial/command_handlers/rftransmit.cpp index 6b4905a1..4f63926b 100644 --- a/src/serial/command_handlers/rftransmit.cpp +++ b/src/serial/command_handlers/rftransmit.cpp @@ -42,7 +42,7 @@ OpenShock::Serial::CommandGroup OpenShock::Serial::CommandHandlers::RfTransmitHa "json"sv, "must be a JSON object with the following fields:"sv, "{\"model\":\"caixianlin\",\"id\":12345,\"type\":\"vibrate\",\"intensity\":99,\"durationMs\":500}"sv, - {"model (string) Model of the shocker (\"caixianlin\", \"petrainer\", \"petrainer998dr\")"sv, + {"model (string) Model of the shocker (\"caixianlin\", \"petrainer\", \"petrainer998dr\", \"t330\", \"d80\")"sv, "id (number) ID of the shocker (0-65535)"sv, "type (string) Type of the command (\"shock\", \"vibrate\", \"sound\", \"light\", \"stop\")"sv, "intensity (number) Intensity of the command (0-255)"sv, diff --git a/src/serialization/JsonSerial.cpp b/src/serialization/JsonSerial.cpp index a089b07d..ef22b63f 100644 --- a/src/serialization/JsonSerial.cpp +++ b/src/serialization/JsonSerial.cpp @@ -22,9 +22,9 @@ bool JsonSerial::ParseShockerCommand(const cJSON* root, JsonSerial::ShockerComma OS_LOGE(TAG, "value at 'model' is not a string"); return false; } - ShockerModelType modelType = ShockerModelType::MIN; + ShockerModelType modelType = ShockerModelType::CaiXianlin; if (!ShockerModelTypeFromString(model->valuestring, modelType)) { - OS_LOGE(TAG, "value at 'model' is not a valid shocker model (caixianlin, petrainer, petrainer998dr)"); + OS_LOGE(TAG, "value at 'model' is not a valid shocker model (caixianlin, petrainer, petrainer998dr, t330, d80)"); return false; }