Add support for RTW protocols #38

This commit is contained in:
Robert Strouse 2023-05-12 18:17:57 -07:00
parent ac6d6ee34d
commit 65e4caf3b2
10 changed files with 106 additions and 34 deletions

View file

@ -6,9 +6,9 @@
extern Preferences pref;
#define SHADE_HDR_VER 6
#define SHADE_HDR_VER 7
#define SHADE_HDR_SIZE 16
#define SHADE_REC_SIZE 228
#define SHADE_REC_SIZE 232
bool ConfigFile::begin(const char* filename, bool readOnly) {
this->file = LittleFS.open(filename, readOnly ? "r" : "w");
@ -285,7 +285,9 @@ bool ShadeConfigFile::loadFile(SomfyShadeController *s, const char *filename) {
shade->tiltType = this->readBool(false) ? tilt_types::none : tilt_types::tiltmotor;
else
shade->tiltType = static_cast<tilt_types>(this->readUInt8(0));
if(this->header.version > 6) {
shade->proto = static_cast<radio_proto>(this->readUInt8(0));
}
if(this->header.version > 1) {
shade->bitLength = this->readUInt8(56);
}
@ -345,6 +347,7 @@ bool ShadeConfigFile::writeShadeRecord(SomfyShade *shade) {
this->writeUInt32(shade->getRemoteAddress());
this->writeString(shade->name, sizeof(shade->name));
this->writeUInt8(static_cast<uint8_t>(shade->tiltType));
this->writeUInt8(static_cast<uint8_t>(shade->proto));
this->writeUInt8(shade->bitLength);
this->writeUInt32(shade->upTime);
this->writeUInt32(shade->downTime);

View file

@ -3,7 +3,7 @@
#ifndef configsettings_h
#define configsettings_h
#define FW_VERSION "v1.5.4"
#define FW_VERSION "v1.6.0beta"
enum DeviceStatus {
DS_OK = 0,
DS_ERROR = 1,

View file

@ -120,6 +120,23 @@ void somfy_frame_t::decodeFrame(byte* frame) {
// Pull in the data for an 80-bit step command.
if(this->cmd == somfy_commands::StepDown)
this->cmd = (somfy_commands)((decoded[1] >> 4) | ((decoded[8] & 0x08) << 4));
if(this->cmd == somfy_commands::RTWProto) {
this->proto = radio_proto::RTW;
switch(this->encKey) {
case 140:
this->cmd = somfy_commands::Prog;
break;
case 134:
this->cmd = somfy_commands::Up;
break;
case 133:
this->cmd = somfy_commands::My;
break;
case 136:
this->cmd = somfy_commands::Down;
break;
}
}
this->rollingCode = decoded[3] + (decoded[2] << 8);
this->remoteAddress = (decoded[6] + (decoded[5] << 8) + (decoded[4] << 16));
this->valid = this->checksum == checksum && this->remoteAddress > 0 && this->remoteAddress < 16777215 && this->rollingCode > 0;
@ -141,7 +158,7 @@ void somfy_frame_t::decodeFrame(byte* frame) {
case somfy_commands::UnknownC:
case somfy_commands::UnknownD:
case somfy_commands::UnknownE:
case somfy_commands::UnknownF:
case somfy_commands::RTWProto:
this->valid = false;
break;
case somfy_commands::StepUp:
@ -223,6 +240,25 @@ void somfy_frame_t::encodeFrame(byte *frame) {
frame[7] = 132;
frame[8] = 0;
frame[9] = 29;
// Ok so if this is an RTW things are a bit different.
if(this->proto == radio_proto::RTW) {
frame[1] = 0xF0;
switch(this->cmd) {
case somfy_commands::Prog:
frame[0] = 140;
break;
case somfy_commands::Up:
frame[0] = 134;
break;
case somfy_commands::Down:
frame[0] = 136;
break;
case somfy_commands::My:
frame[0] = 133;
break;
}
}
else {
switch(this->cmd) {
case somfy_commands::StepUp:
frame[7] = 132;
@ -240,6 +276,7 @@ void somfy_frame_t::encodeFrame(byte *frame) {
frame[9] = 25;
break;
}
}
byte checksum = 0;
for (byte i = 0; i < 7; i++) {
@ -1348,6 +1385,7 @@ bool SomfyShade::fromJSON(JsonObject &obj) {
if(obj.containsKey("stepSize")) this->stepSize = obj["stepSize"];
if(obj.containsKey("hasTilt")) this->tiltType = static_cast<bool>(obj["hasTilt"]) ? tilt_types::none : tilt_types::tiltmotor;
if(obj.containsKey("bitLength")) this->bitLength = obj["bitLength"];
if(obj.containsKey("proto")) this->proto = static_cast<radio_proto>(obj["proto"].as<uint8_t>());
if(obj.containsKey("shadeType")) {
if(obj["shadeType"].is<const char *>()) {
if(strncmp(obj["shadeType"].as<const char *>(), "roller", 7) == 0)
@ -1417,6 +1455,7 @@ bool SomfyShade::toJSON(JsonObject &obj) {
obj["tiltTime"] = this->tiltTime;
obj["shadeType"] = static_cast<uint8_t>(this->shadeType);
obj["bitLength"] = this->bitLength;
obj["proto"] = static_cast<uint8_t>(this->proto);
SomfyRemote::toJSON(obj);
JsonArray arr = obj.createNestedArray("linkedRemotes");
for(uint8_t i = 0; i < SOMFY_MAX_LINKED_REMOTES; i++) {
@ -1583,6 +1622,7 @@ void SomfyRemote::sendCommand(somfy_commands cmd, uint8_t repeat) {
frame.bitLength = this->bitLength;
// Match the encKey to the rolling code. These keys range from 160 to 175.
frame.encKey = 0xA0 | static_cast<uint8_t>(frame.rollingCode & 0x000F);
frame.proto = this->proto;
if(frame.bitLength == 0) frame.bitLength = bit_length;
this->lastRollingCode = frame.rollingCode;
somfy.sendFrame(frame, repeat);
@ -2115,7 +2155,7 @@ void transceiver_config_t::fromJSON(JsonObject& obj) {
if(obj.containsKey("deviation")) this->deviation = obj["deviation"]; // float
if(obj.containsKey("enabled")) this->enabled = obj["enabled"];
if(obj.containsKey("txPower")) this->txPower = obj["txPower"];
if(obj.containsKey("proto")) this->proto = static_cast<radio_proto>(obj["proto"].as<uint8_t>());
/*
if (obj.containsKey("internalCCMode")) this->internalCCMode = obj["internalCCMode"];
if (obj.containsKey("modulationMode")) this->modulationMode = obj["modulationMode"];
@ -2155,6 +2195,7 @@ void transceiver_config_t::toJSON(JsonObject& obj) {
obj["frequency"] = this->frequency; // float
obj["deviation"] = this->deviation; // float
obj["txPower"] = this->txPower;
obj["proto"] = static_cast<uint8_t>(this->proto);
/*
obj["internalCCMode"] = this->internalCCMode;
obj["modulationMode"] = this->modulationMode;
@ -2201,6 +2242,7 @@ void transceiver_config_t::save() {
pref.putBool("enabled", this->enabled);
pref.putBool("radioInit", true);
pref.putChar("txPower", this->txPower);
pref.putChar("proto", static_cast<uint8_t>(this->proto));
/*
pref.putBool("internalCCMode", this->internalCCMode);
@ -2253,7 +2295,7 @@ void transceiver_config_t::load() {
this->enabled = pref.getBool("enabled", this->enabled);
this->txPower = pref.getChar("txPower", this->txPower);
this->rxBandwidth = pref.getFloat("rxBandwidth", this->rxBandwidth);
this->proto = static_cast<radio_proto>(pref.getChar("proto", static_cast<uint8_t>(this->proto)));
this->removeNVSKey("internalCCMode");
this->removeNVSKey("modulationMode");
this->removeNVSKey("channel");

10
Somfy.h
View file

@ -9,7 +9,10 @@ typedef struct appver_t {
uint8_t minor;
uint8_t build;
};
enum class radio_proto : byte {
RTS = 0x00,
RTW = 0x01
};
enum class somfy_commands : byte {
Unknown0 = 0x0,
My = 0x1,
@ -26,7 +29,7 @@ enum class somfy_commands : byte {
UnknownC = 0xC,
UnknownD = 0xD,
UnknownE = 0xE, // This command byte has been witnessed in the wild but cannot tell if it is from Somfy. No rolling code is sent with this and it is 56-bits.
UnknownF = 0xF,
RTWProto = 0xF, // RTW Protocol
// Command extensions for 80 bit frames
StepUp = 0x8B
};
@ -93,6 +96,7 @@ typedef struct somfy_tx_queue_t {
typedef struct somfy_frame_t {
bool valid = false;
bool processed = false;
radio_proto proto = radio_proto::RTS;
int rssi = 0;
byte lqi = 0x0;
somfy_commands cmd;
@ -121,6 +125,7 @@ class SomfyRemote {
char m_remotePrefId[11] = "";
uint32_t m_remoteAddress = 0;
public:
radio_proto proto = radio_proto::RTS;
uint8_t bitLength = 0;
char *getRemotePrefId() {return m_remotePrefId;}
virtual bool toJSON(JsonObject &obj);
@ -202,6 +207,7 @@ typedef struct transceiver_config_t {
bool printBuffer = false;
bool enabled = false;
uint8_t type = 56; // 56 or 80 bit protocol.
radio_proto proto = radio_proto::RTS;
uint8_t SCKPin = 18;
uint8_t TXPin = 12;
uint8_t RXPin = 13;

Binary file not shown.

Binary file not shown.

View file

@ -469,12 +469,14 @@ void Web::begin() {
});
server.on("/getNextShade", []() {
webServer.sendCORSHeaders();
StaticJsonDocument<128> doc;
StaticJsonDocument<256> doc;
uint8_t shadeId = somfy.getNextShadeId();
JsonObject obj = doc.to<JsonObject>();
obj["shadeId"] = shadeId;
obj["remoteAddress"] = somfy.getNextRemoteAddress(shadeId);
obj["bitLength"] = somfy.transceiver.config.type;
obj["stepSize"] = 100;
obj["proto"] = static_cast<uint8_t>(somfy.transceiver.config.proto);
serializeJson(doc, g_content);
server.send(200, _encoding_json, g_content);
});

View file

@ -1 +1 @@
1.5.4
1.6.0

View file

@ -3,10 +3,10 @@
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta charset="UTF-8">
<link rel="stylesheet" href="main.css?v=1.5.4" type="text/css" />
<link rel="stylesheet" href="icons.css?v=1.5.4" type="text/css" />
<link rel="stylesheet" href="main.css?v=1.6.0b" type="text/css" />
<link rel="stylesheet" href="icons.css?v=1.6.0b" type="text/css" />
<link rel="icon" type="image/png" href="favicon.png" />
<script type="text/javascript" src="index.js?v=1.5.4"></script>
<script type="text/javascript" src="index.js?v=1.6.0b"></script>
</head>
<body>
<div id="divContainer" class="container" style="user-select:none;position:relative;border-radius:27px;">
@ -219,8 +219,15 @@
<div>
<div class="field-group" style="width:127px;display:inline-block;margin-top:-20px;float:left;">
<div class="field-group">
<div class="field-group">
<select id="selShadeBitLength" name="bitLength" style="width:100%;" onchange="somfy.onShadeBitLengthChanged(this);">
<div class="field-group" style="margin-top:-10px;">
<select id="selShadeProto" name="proto" style="width:50%;">
<option value="0">RTS</option>
<option value="1">RTW</option>
</select>
<label for="selShadeBitLength">Protocol</label>
</div>
<div class="field-group" style="margin-top:-10px;">
<select id="selShadeBitLength" name="bitLength" style="width:50%;" onchange="somfy.onShadeBitLengthChanged(this);">
<option value="56">56-BIT</option>
<option value="80">80-BIT</option>
</select>
@ -318,7 +325,14 @@
<div id="somfyTransceiver" style="display:none;position:relative;">
<form>
<div id="divRadioSettings" name="divRadioSettings" class="field-group1" style="display:block;position:relative">
<div class="field-group" style="">
<div class="field-group" style="display:inline-block;margin-right:7px;">
<label for="selRadioProto">Protocol</label>
<select id="selRadioProto" name="radioType" style="width:77px;">
<option value="0">RTS</option>
<option value="1">RTW</option>
</select>
</div>
<div class="field-group" style="display:inline-block;">
<label for="selRadioType">Radio</label>
<select id="selRadioType" name="radioType" style="width:77px;">
<option value="none" selected>None</option>

View file

@ -378,7 +378,7 @@ async function reopenSocket() {
await initSockets();
}
class General {
appVersion = 'v1.5.4';
appVersion = 'v1.6.0beta';
reloadApp = false;
async init() {
this.setAppVersion();
@ -924,6 +924,7 @@ class Somfy {
document.getElementById('selTransTXPin').value = somfy.transceiver.config.TXPin.toString();
document.getElementById('selTransRXPin').value = somfy.transceiver.config.RXPin.toString();
document.getElementById('selRadioType').value = somfy.transceiver.config.type;
document.getElementById('selRadioProto').value = somfy.transceiver.config.proto;
document.getElementById('spanMaxShades').innerText = somfy.maxShades;
document.getElementById('spanRxBandwidth').innerText = (Math.round(somfy.transceiver.config.rxBandwidth * 100) / 100).fmt('#,##0.00');
document.getElementById('slidRxBandwidth').value = Math.round(somfy.transceiver.config.rxBandwidth * 100);
@ -961,6 +962,7 @@ class Somfy {
let obj = {
enabled: document.getElementsByName('enableRadio')[0].checked,
type: parseInt(document.getElementById('selRadioType').value, 10),
proto: parseInt(document.getElementById('selRadioProto').value, 10),
SCKPin: getIntValue('selTransSCKPin'),
CSNPin: getIntValue('selTransCSNPin'),
MOSIPin: getIntValue('selTransMOSIPin'),
@ -1446,6 +1448,7 @@ class Somfy {
document.getElementById('divLinkedRemoteList').innerHTML = '';
document.getElementById('btnSetRollingCode').style.display = 'none';
document.getElementById('selShadeBitLength').value = shade.bitLength || 56;
document.getElementById('selShadeProto').value = shade.proto || 0;
document.getElementById('slidStepSize').value = shade.stepSize || 100;
document.getElementById('spanStepSize').innerHTML = shade.stepSize.fmt('#,##0');
}
@ -1472,6 +1475,7 @@ class Somfy {
document.getElementById('btnLinkRemote').style.display = '';
document.getElementById('selShadeType').value = shade.shadeType;
document.getElementById('selShadeBitLength').value = shade.bitLength;
document.getElementById('selShadeProto').value = shade.proto;
document.getElementsByName('shadeAddress')[0].value = shade.remoteAddress;
document.getElementsByName('shadeName')[0].value = shade.name;
document.getElementsByName('shadeUpTime')[0].value = shade.upTime;
@ -1532,6 +1536,7 @@ class Somfy {
shadeType: parseInt(document.getElementById('selShadeType').value, 10),
tiltTime: parseInt(document.getElementById('fldTiltTime').value, 10),
bitLength: parseInt(document.getElementById('selShadeBitLength').value, 10) || 56,
proto: parseInt(document.getElementById('selShadeProto').value, 10) || 0,
stepSize: parseInt(document.getElementById('slidStepSize').value, 10) || 100
};
if (obj.shadeType === 1) {