mirror of
https://github.com/rstrouse/ESPSomfy-RTS.git
synced 2026-04-20 11:02:14 +02:00
Merge branch 'cjkas:main' into main
This commit is contained in:
commit
ff031eec92
8 changed files with 192 additions and 6 deletions
|
|
@ -913,6 +913,10 @@
|
||||||
<input id="cbEnableRadio" name="enableRadio" type="checkbox" data-bind="transceiver.config.enabled" style="display:inline-block;" />
|
<input id="cbEnableRadio" name="enableRadio" type="checkbox" data-bind="transceiver.config.enabled" style="display:inline-block;" />
|
||||||
<label for="cbEnableRadio" style="display:inline-block;cursor:pointer;">Enable Radio</label>
|
<label for="cbEnableRadio" style="display:inline-block;cursor:pointer;">Enable Radio</label>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="field-group" style="vertical-align:middle;margin-left:7px;float:right;display:inline-block;width:auto;">
|
||||||
|
<input id="cbNoiseDetection" name="noiseDetection" type="checkbox" data-bind="transceiver.config.noiseDetection" style="display:inline-block;" title="When enabled, the RX interrupt is automatically disabled if more than 100 pulses are detected within 10 seconds (RF noise burst). The interrupt is re-enabled after a 100ms cooldown. Enable this if your radio is frequently disrupted by nearby RF interference." />
|
||||||
|
<label for="cbNoiseDetection" style="display:inline-block;cursor:pointer;" title="When enabled, the RX interrupt is automatically disabled if more than 100 pulses are detected within 10 seconds (RF noise burst). The interrupt is re-enabled after a 100ms cooldown. Enable this if your radio is frequently disrupted by nearby RF interference.">Noise Detection</label>
|
||||||
|
</div>
|
||||||
<div class="field-group" style="margin-top:-18px;"><label style="font-size:12px;">(default when adding new motors)</label></div>
|
<div class="field-group" style="margin-top:-18px;"><label style="font-size:12px;">(default when adding new motors)</label></div>
|
||||||
<div class="field-group1" style="white-space:nowrap">
|
<div class="field-group1" style="white-space:nowrap">
|
||||||
<div class="field-group radioPins">
|
<div class="field-group radioPins">
|
||||||
|
|
|
||||||
|
|
@ -1954,7 +1954,7 @@ class Somfy {
|
||||||
this.loadPins('input', document.getElementById('selTransRXPin'));
|
this.loadPins('input', document.getElementById('selTransRXPin'));
|
||||||
//this.loadSomfy();
|
//this.loadSomfy();
|
||||||
ui.toElement(document.getElementById('divTransceiverSettings'), {
|
ui.toElement(document.getElementById('divTransceiverSettings'), {
|
||||||
transceiver: { config: { proto: 0, SCKPin: 18, CSNPin: 5, MOSIPin: 23, MISOPin: 19, TXPin: 12, RXPin: 13, frequency: 433.42, rxBandwidth: 97.96, type: 56, deviation: 11.43, txPower: 10, enabled: false } }
|
transceiver: { config: { proto: 0, SCKPin: 18, CSNPin: 5, MOSIPin: 23, MISOPin: 19, TXPin: 12, RXPin: 13, frequency: 433.42, rxBandwidth: 97.96, type: 56, deviation: 11.43, txPower: 10, enabled: false, noiseDetection: false } }
|
||||||
});
|
});
|
||||||
this.loadPins('out', document.getElementById('selShadeGPIOUp'));
|
this.loadPins('out', document.getElementById('selShadeGPIOUp'));
|
||||||
this.loadPins('out', document.getElementById('selShadeGPIODown'));
|
this.loadPins('out', document.getElementById('selShadeGPIODown'));
|
||||||
|
|
|
||||||
|
|
@ -3,4 +3,5 @@ nvs, data, nvs, 0x9000, 0x5000
|
||||||
otadata, data, ota, 0xE000, 0x2000
|
otadata, data, ota, 0xE000, 0x2000
|
||||||
app0, app, ota_0, 0x10000, 0x180000
|
app0, app, ota_0, 0x10000, 0x180000
|
||||||
app1, app, ota_1, 0x190000, 0x180000
|
app1, app, ota_1, 0x190000, 0x180000
|
||||||
spiffs, data, spiffs, 0x310000, 0x0F0000
|
spiffs, data, spiffs, 0x310000, 0x0E0000
|
||||||
|
coredump, data, coredump, 0x3F0000, 0x10000
|
||||||
|
|
|
||||||
|
|
|
@ -709,6 +709,7 @@ bool ShadeConfigFile::readTransRecord(transceiver_config_t &cfg) {
|
||||||
cfg.rxBandwidth = this->readFloat(cfg.rxBandwidth);
|
cfg.rxBandwidth = this->readFloat(cfg.rxBandwidth);
|
||||||
cfg.deviation = this->readFloat(cfg.deviation);
|
cfg.deviation = this->readFloat(cfg.deviation);
|
||||||
cfg.txPower = this->readInt8(cfg.txPower);
|
cfg.txPower = this->readInt8(cfg.txPower);
|
||||||
|
cfg.noiseDetection = this->readBool(false);
|
||||||
if(this->file.position() != startPos + this->header.transRecordSize) {
|
if(this->file.position() != startPos + this->header.transRecordSize) {
|
||||||
ESP_LOGD(TAG, "Reading to end of transceiver record");
|
ESP_LOGD(TAG, "Reading to end of transceiver record");
|
||||||
this->seekChar(CFG_REC_END);
|
this->seekChar(CFG_REC_END);
|
||||||
|
|
@ -1065,7 +1066,8 @@ bool ShadeConfigFile::writeTransRecord(transceiver_config_t &cfg) {
|
||||||
this->writeFloat(cfg.frequency, 3);
|
this->writeFloat(cfg.frequency, 3);
|
||||||
this->writeFloat(cfg.rxBandwidth, 2);
|
this->writeFloat(cfg.rxBandwidth, 2);
|
||||||
this->writeFloat(cfg.deviation, 2);
|
this->writeFloat(cfg.deviation, 2);
|
||||||
this->writeInt8(cfg.txPower, CFG_REC_END);
|
this->writeInt8(cfg.txPower);
|
||||||
|
this->writeBool(cfg.noiseDetection, CFG_REC_END);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
bool ShadeConfigFile::exists() { return LittleFS.exists("/shades.cfg"); }
|
bool ShadeConfigFile::exists() { return LittleFS.exists("/shades.cfg"); }
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
#include <SPI.h>
|
#include <SPI.h>
|
||||||
#include <esp_task_wdt.h>
|
#include <esp_task_wdt.h>
|
||||||
#include <esp_chip_info.h>
|
#include <esp_chip_info.h>
|
||||||
|
#include "driver/gpio.h"
|
||||||
#include "esp_log.h"
|
#include "esp_log.h"
|
||||||
#include "Utils.h"
|
#include "Utils.h"
|
||||||
#include "ConfigSettings.h"
|
#include "ConfigSettings.h"
|
||||||
|
|
@ -45,6 +46,9 @@ int sort_asc(const void *cmp1, const void *cmp2) {
|
||||||
}
|
}
|
||||||
|
|
||||||
static int interruptPin = 0;
|
static int interruptPin = 0;
|
||||||
|
static volatile bool noiseDetected = false;
|
||||||
|
static volatile unsigned long noiseStart = 0;
|
||||||
|
static volatile uint32_t noiseCount = 0;
|
||||||
static uint8_t bit_length = 56;
|
static uint8_t bit_length = 56;
|
||||||
somfy_commands translateSomfyCommand(const String& string) {
|
somfy_commands translateSomfyCommand(const String& string) {
|
||||||
if (string.equalsIgnoreCase("My")) return somfy_commands::My;
|
if (string.equalsIgnoreCase("My")) return somfy_commands::My;
|
||||||
|
|
@ -4187,6 +4191,21 @@ void Transceiver::sendFrame(byte *frame, uint8_t sync, uint8_t bitLength) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void RECEIVE_ATTR Transceiver::handleReceive() {
|
void RECEIVE_ATTR Transceiver::handleReceive() {
|
||||||
|
if (noiseDetected) return;
|
||||||
|
if (somfy.transceiver.config.noiseDetection) {
|
||||||
|
unsigned long now = millis();
|
||||||
|
if (noiseStart == 0) noiseStart = now;
|
||||||
|
if (now - noiseStart >= 10000) {
|
||||||
|
noiseStart = now;
|
||||||
|
noiseCount = 0;
|
||||||
|
}
|
||||||
|
noiseCount++;
|
||||||
|
if (noiseCount > 100) {
|
||||||
|
gpio_intr_disable((gpio_num_t)interruptPin);
|
||||||
|
noiseDetected = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
static unsigned long last_time = 0;
|
static unsigned long last_time = 0;
|
||||||
const long time = micros();
|
const long time = micros();
|
||||||
const unsigned int duration = time - last_time;
|
const unsigned int duration = time - last_time;
|
||||||
|
|
@ -4358,7 +4377,7 @@ void Transceiver::processFrequencyScan(bool received) {
|
||||||
currRSSI = -100;
|
currRSSI = -100;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(millis() - lastScan > 100 && somfy_rx.status == waiting_synchro) {
|
if(millis() - lastScan > 100 && (somfy_rx.status == waiting_synchro || noiseDetected)) {
|
||||||
lastScan = millis();
|
lastScan = millis();
|
||||||
this->emitFrequencyScan();
|
this->emitFrequencyScan();
|
||||||
currFreq += 0.01f;
|
currFreq += 0.01f;
|
||||||
|
|
@ -4538,6 +4557,7 @@ void transceiver_config_t::fromJSON(JsonObject& obj) {
|
||||||
if(!obj["enabled"].isNull()) this->enabled = obj["enabled"];
|
if(!obj["enabled"].isNull()) this->enabled = obj["enabled"];
|
||||||
if(!obj["txPower"].isNull()) this->txPower = obj["txPower"];
|
if(!obj["txPower"].isNull()) this->txPower = obj["txPower"];
|
||||||
if(!obj["proto"].isNull()) this->proto = static_cast<radio_proto>(obj["proto"].as<uint8_t>());
|
if(!obj["proto"].isNull()) this->proto = static_cast<radio_proto>(obj["proto"].as<uint8_t>());
|
||||||
|
if(!obj["noiseDetection"].isNull()) this->noiseDetection = obj["noiseDetection"];
|
||||||
/*
|
/*
|
||||||
if (!obj["internalCCMode"].isNull()) this->internalCCMode = obj["internalCCMode"];
|
if (!obj["internalCCMode"].isNull()) this->internalCCMode = obj["internalCCMode"];
|
||||||
if (!obj["modulationMode"].isNull()) this->modulationMode = obj["modulationMode"];
|
if (!obj["modulationMode"].isNull()) this->modulationMode = obj["modulationMode"];
|
||||||
|
|
@ -4625,6 +4645,7 @@ void transceiver_config_t::save() {
|
||||||
pref.putBool("radioInit", true);
|
pref.putBool("radioInit", true);
|
||||||
pref.putChar("txPower", this->txPower);
|
pref.putChar("txPower", this->txPower);
|
||||||
pref.putChar("proto", static_cast<uint8_t>(this->proto));
|
pref.putChar("proto", static_cast<uint8_t>(this->proto));
|
||||||
|
pref.putBool("noiseDetect", this->noiseDetection);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
pref.putBool("internalCCMode", this->internalCCMode);
|
pref.putBool("internalCCMode", this->internalCCMode);
|
||||||
|
|
@ -4724,6 +4745,7 @@ void transceiver_config_t::load() {
|
||||||
this->txPower = pref.getChar("txPower", this->txPower);
|
this->txPower = pref.getChar("txPower", this->txPower);
|
||||||
this->rxBandwidth = pref.getFloat("rxBandwidth", this->rxBandwidth);
|
this->rxBandwidth = pref.getFloat("rxBandwidth", this->rxBandwidth);
|
||||||
this->proto = static_cast<radio_proto>(pref.getChar("proto", static_cast<uint8_t>(this->proto)));
|
this->proto = static_cast<radio_proto>(pref.getChar("proto", static_cast<uint8_t>(this->proto)));
|
||||||
|
this->noiseDetection = pref.getBool("noiseDetect", false);
|
||||||
this->removeNVSKey("internalCCMode");
|
this->removeNVSKey("internalCCMode");
|
||||||
this->removeNVSKey("modulationMode");
|
this->removeNVSKey("modulationMode");
|
||||||
this->removeNVSKey("channel");
|
this->removeNVSKey("channel");
|
||||||
|
|
@ -4857,6 +4879,14 @@ bool Transceiver::begin() {
|
||||||
}
|
}
|
||||||
void Transceiver::loop() {
|
void Transceiver::loop() {
|
||||||
somfy_rx_t rx;
|
somfy_rx_t rx;
|
||||||
|
if (noiseDetected && rxmode != 3 && this->config.noiseDetection) {
|
||||||
|
if (millis() - noiseStart > 100) {
|
||||||
|
gpio_intr_enable((gpio_num_t)interruptPin);
|
||||||
|
noiseDetected = false;
|
||||||
|
noiseStart = 0;
|
||||||
|
noiseCount = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
if(rxmode == 3) {
|
if(rxmode == 3) {
|
||||||
if(this->receive(&rx))
|
if(this->receive(&rx))
|
||||||
this->processFrequencyScan(true);
|
this->processFrequencyScan(true);
|
||||||
|
|
|
||||||
|
|
@ -426,6 +426,7 @@ struct transceiver_config_t {
|
||||||
float deviation = 47.60; // Set the Frequency deviation in kHz. Value from 1.58 to 380.85. Default is 47.60 kHz.
|
float deviation = 47.60; // Set the Frequency deviation in kHz. Value from 1.58 to 380.85. Default is 47.60 kHz.
|
||||||
float rxBandwidth = 99.97; // Receive bandwidth in kHz. Value from 58.03 to 812.50. Default is 99.97kHz.
|
float rxBandwidth = 99.97; // Receive bandwidth in kHz. Value from 58.03 to 812.50. Default is 99.97kHz.
|
||||||
int8_t txPower = 10; // Transmission power {-30, -20, -15, -10, -6, 0, 5, 7, 10, 11, 12}. Default is 12.
|
int8_t txPower = 10; // Transmission power {-30, -20, -15, -10, -6, 0, 5, 7, 10, 11, 12}. Default is 12.
|
||||||
|
bool noiseDetection = false; // Disable RX interrupt when RF noise is detected (>100 pulses/10s)
|
||||||
/*
|
/*
|
||||||
bool internalCCMode = false; // Use internal transmission mode FIFO buffers.
|
bool internalCCMode = false; // Use internal transmission mode FIFO buffers.
|
||||||
byte modulationMode = 2; // Modulation mode. 0 = 2-FSK, 1 = GFSK, 2 = ASK/OOK, 3 = 4-FSK, 4 = MSK.
|
byte modulationMode = 2; // Modulation mode. 0 = 2-FSK, 1 = GFSK, 2 = ASK/OOK, 3 = 4-FSK, 4 = MSK.
|
||||||
|
|
|
||||||
147
src/SomfyController.ino.cpp
Normal file
147
src/SomfyController.ino.cpp
Normal file
|
|
@ -0,0 +1,147 @@
|
||||||
|
# 1 "C:\\Users\\oem\\AppData\\Local\\Temp\\tmpahrx2jqr"
|
||||||
|
#include <Arduino.h>
|
||||||
|
# 1 "C:/Users/oem/Documents/PlatformIO/Projects/ESPSomfy-RTS/src/SomfyController.ino"
|
||||||
|
#include "esp_log.h"
|
||||||
|
#include <WiFi.h>
|
||||||
|
#include <LittleFS.h>
|
||||||
|
#include <esp_task_wdt.h>
|
||||||
|
#include "ConfigSettings.h"
|
||||||
|
#include "ESPNetwork.h"
|
||||||
|
#include "Web.h"
|
||||||
|
#include "Sockets.h"
|
||||||
|
#include "Utils.h"
|
||||||
|
#include "Somfy.h"
|
||||||
|
#include "MQTT.h"
|
||||||
|
#include "GitOTA.h"
|
||||||
|
#include "esp_core_dump.h"
|
||||||
|
|
||||||
|
static const char *TAG = "Main";
|
||||||
|
|
||||||
|
ConfigSettings settings;
|
||||||
|
Web webServer;
|
||||||
|
SocketEmitter sockEmit;
|
||||||
|
ESPNetwork net;
|
||||||
|
rebootDelay_t rebootDelay;
|
||||||
|
SomfyShadeController somfy;
|
||||||
|
MQTTClass mqtt;
|
||||||
|
GitUpdater git;
|
||||||
|
|
||||||
|
uint32_t oldheap = 0;
|
||||||
|
void listDir(const char *dirname, uint8_t levels);
|
||||||
|
void setup();
|
||||||
|
void loop();
|
||||||
|
#line 28 "C:/Users/oem/Documents/PlatformIO/Projects/ESPSomfy-RTS/src/SomfyController.ino"
|
||||||
|
void listDir(const char *dirname, uint8_t levels) {
|
||||||
|
ESP_LOGI(TAG, "Listing: %s", dirname);
|
||||||
|
File root = LittleFS.open(dirname);
|
||||||
|
if (!root || !root.isDirectory()) {
|
||||||
|
ESP_LOGE(TAG, "Failed to open directory");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
File file = root.openNextFile();
|
||||||
|
while (file) {
|
||||||
|
if (file.isDirectory()) {
|
||||||
|
ESP_LOGI(TAG, " DIR : %s", file.name());
|
||||||
|
if (levels) listDir(file.path(), levels - 1);
|
||||||
|
} else {
|
||||||
|
ESP_LOGI(TAG, " FILE: %-30s %d bytes", file.name(), file.size());
|
||||||
|
}
|
||||||
|
file = root.openNextFile();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
Serial.begin(115200);
|
||||||
|
ESP_LOGI(TAG, "Startup/Boot....");
|
||||||
|
esp_core_dump_summary_t summary;
|
||||||
|
if (esp_core_dump_get_summary(&summary) == ESP_OK) {
|
||||||
|
ESP_LOGW(TAG, "*** Previous crash coredump found ***");
|
||||||
|
ESP_LOGW(TAG, " Task: %s", summary.exc_task);
|
||||||
|
ESP_LOGW(TAG, " PC: 0x%08x", summary.exc_pc);
|
||||||
|
#ifdef CONFIG_IDF_TARGET_ARCH_XTENSA
|
||||||
|
ESP_LOGW(TAG, " Cause: %d", summary.ex_info.exc_cause);
|
||||||
|
char bt_buf[256] = {0};
|
||||||
|
int pos = 0;
|
||||||
|
for (int i = 0; i < summary.exc_bt_info.depth; i++) {
|
||||||
|
pos += snprintf(bt_buf + pos, sizeof(bt_buf) - pos, " 0x%08x", summary.exc_bt_info.bt[i]);
|
||||||
|
}
|
||||||
|
ESP_LOGW(TAG, " Backtrace:%s", bt_buf);
|
||||||
|
#elif CONFIG_IDF_TARGET_ARCH_RISCV
|
||||||
|
ESP_LOGW(TAG, " Cause: %d", summary.ex_info.mcause);
|
||||||
|
ESP_LOGW(TAG, " MTVAL: 0x%08x RA: 0x%08x SP: 0x%08x",
|
||||||
|
summary.ex_info.mtval, summary.ex_info.ra, summary.ex_info.sp);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
ESP_LOGI(TAG, "Mounting File System...");
|
||||||
|
|
||||||
|
|
||||||
|
if (LittleFS.begin()) {
|
||||||
|
ESP_LOGI(TAG, "Total: %d bytes", LittleFS.totalBytes());
|
||||||
|
ESP_LOGI(TAG, "Used: %d bytes", LittleFS.usedBytes());
|
||||||
|
ESP_LOGI(TAG, "Free: %d bytes", LittleFS.totalBytes() - LittleFS.usedBytes());
|
||||||
|
listDir("/", 3);
|
||||||
|
} else {
|
||||||
|
ESP_LOGE(TAG, "LittleFS mount failed!");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(LittleFS.begin()) ESP_LOGI(TAG, "File system mounted successfully");
|
||||||
|
else ESP_LOGE(TAG, "Error mounting file system");
|
||||||
|
settings.begin();
|
||||||
|
if(WiFi.status() == WL_CONNECTED) WiFi.disconnect(true);
|
||||||
|
delay(10);
|
||||||
|
webServer.startup();
|
||||||
|
webServer.begin();
|
||||||
|
delay(1000);
|
||||||
|
net.setup();
|
||||||
|
somfy.begin();
|
||||||
|
|
||||||
|
#if ESP_ARDUINO_VERSION_MAJOR >= 3
|
||||||
|
const esp_task_wdt_config_t wdt_config = { .timeout_ms = 15000, .idle_core_mask = 1, .trigger_panic = true };
|
||||||
|
esp_task_wdt_init(&wdt_config);
|
||||||
|
#else
|
||||||
|
esp_task_wdt_init(15, true);
|
||||||
|
#endif
|
||||||
|
esp_task_wdt_add(NULL);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
|
||||||
|
|
||||||
|
if(rebootDelay.reboot && millis() > rebootDelay.rebootTime) {
|
||||||
|
ESP_LOGI(TAG, "Rebooting after %lums", rebootDelay.rebootTime);
|
||||||
|
net.end();
|
||||||
|
ESP.restart();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
uint32_t timing = millis();
|
||||||
|
|
||||||
|
net.loop();
|
||||||
|
if(millis() - timing > 100) ESP_LOGD(TAG, "Timing Net: %ldms", millis() - timing);
|
||||||
|
timing = millis();
|
||||||
|
esp_task_wdt_reset();
|
||||||
|
somfy.loop();
|
||||||
|
if(millis() - timing > 100) ESP_LOGD(TAG, "Timing Somfy: %ldms", millis() - timing);
|
||||||
|
timing = millis();
|
||||||
|
esp_task_wdt_reset();
|
||||||
|
if(net.connected() || net.softAPOpened) {
|
||||||
|
if(!rebootDelay.reboot && net.connected() && !net.softAPOpened) {
|
||||||
|
git.loop();
|
||||||
|
esp_task_wdt_reset();
|
||||||
|
}
|
||||||
|
webServer.loop();
|
||||||
|
esp_task_wdt_reset();
|
||||||
|
if(millis() - timing > 100) ESP_LOGD(TAG, "Timing WebServer: %ldms", millis() - timing);
|
||||||
|
esp_task_wdt_reset();
|
||||||
|
timing = millis();
|
||||||
|
sockEmit.loop();
|
||||||
|
if(millis() - timing > 100) ESP_LOGD(TAG, "Timing Socket: %ldms", millis() - timing);
|
||||||
|
esp_task_wdt_reset();
|
||||||
|
timing = millis();
|
||||||
|
}
|
||||||
|
if(rebootDelay.reboot && millis() > rebootDelay.rebootTime) {
|
||||||
|
net.end();
|
||||||
|
ESP.restart();
|
||||||
|
}
|
||||||
|
esp_task_wdt_reset();
|
||||||
|
}
|
||||||
|
|
@ -374,6 +374,7 @@ static void serializeTransceiverConfig(JsonFormatter &json) {
|
||||||
json.addElem("txPower", cfg.txPower);
|
json.addElem("txPower", cfg.txPower);
|
||||||
json.addElem("proto", static_cast<uint8_t>(cfg.proto));
|
json.addElem("proto", static_cast<uint8_t>(cfg.proto));
|
||||||
json.addElem("enabled", cfg.enabled);
|
json.addElem("enabled", cfg.enabled);
|
||||||
|
json.addElem("noiseDetection", cfg.noiseDetection);
|
||||||
json.addElem("radioInit", cfg.radioInit);
|
json.addElem("radioInit", cfg.radioInit);
|
||||||
}
|
}
|
||||||
static void serializeAppVersion(JsonFormatter &json, appver_t &ver) {
|
static void serializeAppVersion(JsonFormatter &json, appver_t &ver) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue