Skip to content

Commit

Permalink
Improve Petrainer998DR protocol handling
Browse files Browse the repository at this point in the history
  • Loading branch information
hhvrc committed Nov 21, 2024
1 parent 493d8b2 commit 9bf87bd
Show file tree
Hide file tree
Showing 6 changed files with 70 additions and 27 deletions.
15 changes: 15 additions & 0 deletions include/Checksum.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,21 @@ namespace OpenShock::Checksum {
SUM8_INT_FN(uint32_t)
SUM8_INT_FN(int64_t)
SUM8_INT_FN(uint64_t)


/**
* Make sure the uint8 only has its high bits (0x0F) set before using this function
*/
constexpr uint8_t ReverseNibble(uint8_t b) {
return (0xF7B3D591E6A2C480 >> (b * 4)) & 0xF; // Trust me bro
}

/**
* Make sure the uint8 only has its high bits (0x0F) set before using this function
*/
constexpr uint8_t ReverseInverseNibble(uint8_t b) {
return ~ReverseNibble(b);
}
} // namespace OpenShock::Checksum

#undef SUM8_INT_FN
14 changes: 11 additions & 3 deletions include/ShockerCommandType.h
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
#pragma once

#include "serialization/_fbs/ShockerCommandType_generated.h"

#include <cstdint>
#include <cstring>

namespace OpenShock {
typedef OpenShock::Serialization::Types::ShockerCommandType ShockerCommandType;
enum class ShockerCommandType {
Stop,
Shock,
Vibrate,
Sound,
Light
};

inline bool ShockerCommandTypeFromString(const char* str, ShockerCommandType& out) {
if (strcasecmp(str, "stop") == 0) {
Expand All @@ -20,6 +25,9 @@ namespace OpenShock {
} else if (strcasecmp(str, "sound") == 0) {
out = ShockerCommandType::Sound;
return true;
} else if (strcasecmp(str, "light") == 0) {
out = ShockerCommandType::Light;
return true;
} else {
return false;
}
Expand Down
39 changes: 29 additions & 10 deletions src/message_handlers/websocket/gateway/ShockerCommandList.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ const char* const TAG = "ServerMessageHandlers";
#include <cstdint>

using namespace OpenShock::MessageHandlers::Server;
using FbsModelType = OpenShock::Serialization::Types::ShockerModelType;
using FbsCommandType = OpenShock::Serialization::Types::ShockerCommandType;

void _Private::HandleShockerCommandList(const OpenShock::Serialization::Gateway::GatewayToHubMessage* root)
{
Expand All @@ -27,18 +29,35 @@ void _Private::HandleShockerCommandList(const OpenShock::Serialization::Gateway:
OS_LOGV(TAG, "Received command list from API (%u commands)", commands->size());

for (auto command : *commands) {
uint16_t id = command->id();
uint8_t intensity = command->intensity();
uint16_t durationMs = command->duration();
OpenShock::ShockerModelType model = command->model();
OpenShock::ShockerCommandType type = command->type();
uint16_t id = command->id();
uint8_t intensity = command->intensity();
uint16_t durationMs = command->duration();
FbsModelType fbsModel = command->model();
FbsCommandType fbsCommandType = command->type();

const char* modelStr = OpenShock::Serialization::Types::EnumNameShockerModelType(model);
const char* typeStr = OpenShock::Serialization::Types::EnumNameShockerCommandType(type);

OS_LOGV(TAG, " ID %u, Intensity %u, Duration %u, Model %s, Type %s", id, intensity, durationMs, modelStr, typeStr);
OpenShock::ShockerCommandType commandType;
switch (fbsCommandType) {
case FbsCommandType::Stop:
commandType = OpenShock::ShockerCommandType::Stop;
break;
case FbsCommandType::Shock:
commandType = OpenShock::ShockerCommandType::Shock;
break;
case FbsCommandType::Vibrate:
commandType = OpenShock::ShockerCommandType::Vibrate;
break;
case FbsCommandType::Sound:
commandType = OpenShock::ShockerCommandType::Sound;
break;
// case FbsCommandType::Light:
// commandType = OpenShock::ShockerCommandType::Light;
// break;
default:
OS_LOGE(TAG, "Unsupported command type: %s", OpenShock::Serialization::Types::EnumNameShockerCommandType(fbsCommandType));
continue;
}

if (!OpenShock::CommandHandler::HandleCommand(model, id, type, intensity, durationMs)) {
if (!OpenShock::CommandHandler::HandleCommand(fbsModel, id, commandType, intensity, durationMs)) {
OS_LOGE(TAG, "Remote command failed/rejected!");
}
}
Expand Down
22 changes: 11 additions & 11 deletions src/radio/rmt/Petrainer998DREncoder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@

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

#include "Checksum.h"

const rmt_data_t kRmtPreamble = {1500, 1, 750, 0};
const rmt_data_t kRmtOne = {750, 1, 250, 0};
const rmt_data_t kRmtZero = {250, 1, 750, 0};
const rmt_data_t kRmtPostamble = {1500, 0, 1500, 0}; // Some subvariants expect a quiet period between commands
const rmt_data_t kRmtPostamble = {250, 1, 3750, 0}; // Some subvariants expect a quiet period between commands, this is a last 1 bit followed by a very long pause

using namespace OpenShock;

Expand All @@ -25,32 +27,30 @@ std::vector<rmt_data_t> Rmt::Petrainer998DREncoder::GetSequence(uint16_t shocker
case ShockerCommandType::Sound:
typeShift = 2;
break;
// case ShockerCommandType::Light:
// nShift = 3;
// break;
case ShockerCommandType::Light:
typeShift = 3;
break;
default:
return {}; // Invalid type
}

uint8_t typeVal = 0b0001 << typeShift;
uint8_t typeInvert = ~(0b1000 >> typeShift);
uint8_t typeInvert = Checksum::ReverseInverseNibble(typeVal);

// TODO: Channel argument?
uint8_t channel = 0b1000; // Can be [1000] or [1111], 4 bits wide
uint8_t channelInvert = 0b1110; // Can be [1110] or [0000], 4 bits wide
uint8_t channel = 0b1000; // Can be [1000] for CH1 or [1111] for CH2, 4 bits wide
uint8_t channelInvert = Checksum::ReverseInverseNibble(channel);

// TODO: Below ShockerID is 17 bits wide, and intensity is 7 bits wide. This is weird as ShockerID has 1 more bit and intensity has 1 less bit than 8 bit alignment. This needs investigation.
// Payload layout: [channel:4][typeVal:4][shockerID:17][intensity:7][typeInvert:4][channelInvert:4] (40 bits)
// Payload layout: [channel:4][typeVal:4][shockerID:16][intensity:8][typeInvert:4][channelInvert:4] (40 bits)
uint64_t data
= (static_cast<uint64_t>(channel & 0b1111) << 36 | static_cast<uint64_t>(typeVal & 0b1111) << 32 | static_cast<uint64_t>(shockerId & 0x1FFFF) << 15 | static_cast<uint64_t>(intensity & 0x7F) << 8 | static_cast<uint64_t>(typeInvert & 0b1111) << 4 | static_cast<uint64_t>(channelInvert & 0b1111));

std::vector<rmt_data_t> pulses;
pulses.reserve(43);
pulses.reserve(42);

// Generate the sequence
pulses.push_back(kRmtPreamble);
Internal::EncodeBits<40>(pulses, data, kRmtOne, kRmtZero);
pulses.push_back(kRmtZero); // Idk why this is here, the decoded protocol has it
pulses.push_back(kRmtPostamble);

return pulses;
Expand Down
2 changes: 1 addition & 1 deletion src/serial/command_handlers/rftransmit.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ OpenShock::Serial::CommandGroup OpenShock::Serial::CommandHandlers::RfTransmitHa
"{\"model\":\"caixianlin\",\"id\":12345,\"type\":\"vibrate\",\"intensity\":99,\"durationMs\":500}"sv,
{"model (string) Model of the shocker (\"caixianlin\", \"petrainer\", \"petrainer998dr\")"sv,
"id (number) ID of the shocker (0-65535)"sv,
"type (string) Type of the command (\"shock\", \"vibrate\", \"sound\", \"stop\")"sv,
"type (string) Type of the command (\"shock\", \"vibrate\", \"sound\", \"light\", \"stop\")"sv,
"intensity (number) Intensity of the command (0-255)"sv,
"durationMs (number) Duration of the command in milliseconds (0-65535)"sv}
);
Expand Down
5 changes: 3 additions & 2 deletions src/serialization/JsonSerial.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ const char* const TAG = "JsonSerial";

using namespace OpenShock::Serialization;

bool JsonSerial::ParseShockerCommand(const cJSON* root, JsonSerial::ShockerCommand& out) {
bool JsonSerial::ParseShockerCommand(const cJSON* root, JsonSerial::ShockerCommand& out)
{
if (cJSON_IsObject(root) == 0) {
OS_LOGE(TAG, "not an object");
return false;
Expand Down Expand Up @@ -54,7 +55,7 @@ bool JsonSerial::ParseShockerCommand(const cJSON* root, JsonSerial::ShockerComma
}
ShockerCommandType commandType;
if (!ShockerCommandTypeFromString(command->valuestring, commandType)) {
OS_LOGE(TAG, "value at 'type' is not a valid shocker command (stop, shock, vibrate, sound)");
OS_LOGE(TAG, "value at 'type' is not a valid shocker command (shock, vibrate, sound, light, stop)");
return false;
}

Expand Down

1 comment on commit 9bf87bd

@github-actions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cpp-Linter Report ⚠️

Some files did not pass the configured checks!

clang-format (v18.1.8) reports: 2 file(s) not formatted
  • include/Checksum.h
  • include/ShockerCommandType.h

Have any feedback or feature suggestions? Share it here.

Please sign in to comment.