Radar/pir no-motion state debounce period (#486)
Adding pir/radar timeout logic Timeouts configurable via MQTT Co-authored-by: Darrell Turner <DT@Terastar.biz>
This commit is contained in:
parent
d2e4f452ce
commit
3e65b914fd
|
@ -1,4 +1,5 @@
|
|||
#include "GUI.h"
|
||||
|
||||
#ifdef M5STICK
|
||||
#include "tb_display.h"
|
||||
#endif
|
||||
|
@ -79,7 +80,7 @@ void GUI::connected(bool wifi = false, bool mqtt = false)
|
|||
#ifdef LED_BUILTIN
|
||||
digitalWrite(LED_BUILTIN, !LED_BUILTIN_ON);
|
||||
#endif
|
||||
status("Wifi: %s Mqtt: %s", (wifi ? "yes" : "no"), (mqtt ? "yes" : "no"));
|
||||
status("Wifi:%s Mqtt:%s", (wifi ? "yes" : "no"), (mqtt ? "yes" : "no"));
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -89,7 +90,7 @@ void GUI::added(BleFingerprint *f)
|
|||
Serial.printf("%u New %s | MAC: %s, ID: %-60s %s\n", xPortGetCoreID(), f->getRmAsst() ? "R" : (f->getAllowQuery() ? "Q" : " "), f->getMac().c_str(), f->getId().c_str(), f->getDiscriminator().c_str());
|
||||
}
|
||||
|
||||
void GUI::removed(BleFingerprint *f)
|
||||
void GUI::removed(BleFingerprint *f)
|
||||
{
|
||||
if (f->getIgnore() || !f->getAdded()) return;
|
||||
Serial.printf("\u001b[38;5;236m%u Del | MAC: %s, ID: %-60s %s\u001b[0m\n", xPortGetCoreID(), f->getMac().c_str(), f->getId().c_str(), f->getDiscriminator().c_str());
|
||||
|
@ -105,21 +106,33 @@ void GUI::minusOne(BleFingerprint *f)
|
|||
Serial.printf("\u001b[35m%u C# -1 | MAC: %s, ID: %-60s (%.2fm) %lums\u001b[0m\n", xPortGetCoreID(), f->getMac().c_str(), f->getId().c_str(), f->getDistance(), f->getMsSinceLastSeen());
|
||||
}
|
||||
|
||||
void GUI::close(BleFingerprint *f)
|
||||
void GUI::close(BleFingerprint *f)
|
||||
{
|
||||
if (f->getIgnore()) return;
|
||||
Serial.printf("\u001b[32m%u Close | MAC: %s, ID: %-60s (%.2fm) %ddBm\u001b[0m\n", xPortGetCoreID(), f->getMac().c_str(), f->getId().c_str(), f->getDistance(), f->getNewestRssi());
|
||||
status("C: %s", f->getId().c_str());
|
||||
status("C:%s", f->getId().c_str());
|
||||
}
|
||||
|
||||
void GUI::left(BleFingerprint *f)
|
||||
void GUI::left(BleFingerprint *f)
|
||||
{
|
||||
if (f->getIgnore()) return;
|
||||
Serial.printf("\u001b[33m%u Left | MAC: %s, ID: %-60s (%.2fm) %ddBm\u001b[0m\n", xPortGetCoreID(), f->getMac().c_str(), f->getId().c_str(), f->getDistance(), f->getNewestRssi());
|
||||
status("L: %s", f->getId().c_str());
|
||||
status("L:%s", f->getId().c_str());
|
||||
}
|
||||
|
||||
void GUI::status(const char *format, ...)
|
||||
void GUI::radar(bool value)
|
||||
{
|
||||
Serial.printf("%u Radar | %s\n", xPortGetCoreID(), value ? "detected" : "cleared");
|
||||
status("Radar:%s", value ? "detected" : "cleared");
|
||||
}
|
||||
|
||||
void GUI::pir(bool value)
|
||||
{
|
||||
Serial.printf("%u Pir | %s\n", xPortGetCoreID(), value ? "detected" : "cleared");
|
||||
status("Pir:%s", value ? "detected" : "cleared");
|
||||
}
|
||||
|
||||
void GUI::status(const char *format, ...)
|
||||
{
|
||||
begin();
|
||||
#ifdef M5STICK
|
||||
|
@ -141,14 +154,14 @@ void GUI::minusOne(BleFingerprint *f)
|
|||
#endif
|
||||
}
|
||||
|
||||
void GUI::setup()
|
||||
void GUI::setup()
|
||||
{
|
||||
#ifdef LED_BUILTIN
|
||||
pinMode(LED_BUILTIN, OUTPUT);
|
||||
#endif
|
||||
}
|
||||
|
||||
void GUI::begin()
|
||||
void GUI::begin()
|
||||
{
|
||||
if (!GUI::init)
|
||||
{
|
||||
|
@ -163,30 +176,29 @@ void GUI::minusOne(BleFingerprint *f)
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
void GUI::updateStart()
|
||||
void GUI::updateStart()
|
||||
{
|
||||
#ifdef LED_BUILTIN
|
||||
if (GUI::statusLed) digitalWrite(LED_BUILTIN, LED_BUILTIN_ON);
|
||||
#endif
|
||||
}
|
||||
|
||||
void GUI::updateProgress(unsigned int percent)
|
||||
void GUI::updateProgress(unsigned int percent)
|
||||
{
|
||||
#ifdef LED_BUILTIN
|
||||
if (GUI::statusLed) digitalWrite(LED_BUILTIN, percent % 2);
|
||||
#endif
|
||||
}
|
||||
|
||||
void GUI::updateEnd()
|
||||
void GUI::updateEnd()
|
||||
{
|
||||
#ifdef LED_BUILTIN
|
||||
digitalWrite(LED_BUILTIN, !LED_BUILTIN_ON);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool GUI::init=false;
|
||||
bool GUI::statusLed=false;
|
||||
bool GUI::init = false;
|
||||
bool GUI::statusLed = false;
|
||||
|
||||
#ifdef M5STICK
|
||||
bool GUI::dirty = false;
|
||||
|
|
|
@ -26,6 +26,9 @@ public:
|
|||
static void close(BleFingerprint *f);
|
||||
static void left(BleFingerprint *f);
|
||||
|
||||
static void pir(bool value);
|
||||
static void radar(bool value);
|
||||
|
||||
static void erasing();
|
||||
static void erased();
|
||||
|
||||
|
@ -55,4 +58,5 @@ private:
|
|||
static TFT_eSprite sprite;
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -4,31 +4,46 @@
|
|||
|
||||
// for #define ESPMAC
|
||||
#include "string_utils.h"
|
||||
#include "defaults.h"
|
||||
#include "GUI.h"
|
||||
|
||||
// TODO: Not a fan of externs, but this helps refactoring for now
|
||||
extern char buffer[2048];
|
||||
extern String room;
|
||||
extern String roomsTopic;
|
||||
extern void commonDiscovery();
|
||||
extern bool sendNumberDiscovery(const String& name, const String& entityCategory);
|
||||
extern bool pub(const char *topic, uint8_t qos, bool retain, const char *payload, size_t length = 0, bool dup = false, uint16_t message_id = 0);
|
||||
extern bool spurt(const String& fn, const String& content);
|
||||
|
||||
namespace Motion
|
||||
{
|
||||
int lastMotionValue = -1;
|
||||
|
||||
int pirPin = 0;
|
||||
float pirTimeout = 0;
|
||||
int lastPirValue = -1;
|
||||
unsigned long lastPirMilli = 0;
|
||||
|
||||
int radarPin = 0;
|
||||
float radarTimeout = 0;
|
||||
int lastRadarValue = -1;
|
||||
unsigned long lastRadarMilli = 0;
|
||||
|
||||
void Setup()
|
||||
{
|
||||
if (pirPin) pinMode(pirPin, INPUT_PULLUP);
|
||||
if (radarPin) pinMode(radarPin, INPUT_PULLUP);
|
||||
if (pirPin)
|
||||
pinMode(pirPin, INPUT_PULLUP);
|
||||
if (radarPin)
|
||||
pinMode(radarPin, INPUT_PULLUP);
|
||||
}
|
||||
|
||||
void ConnectToWifi()
|
||||
{
|
||||
pirPin = WiFiSettings.integer("pir_pin", 0, "PIR motion pin (0 for disable)");
|
||||
pirTimeout = WiFiSettings.floating("pir_timeout", 0, 300, DEFAULT_DEBOUNCE_TIMEOUT, "PIR motion timeout (in seconds)");
|
||||
radarPin = WiFiSettings.integer("radar_pin", 0, "Radar motion pin (0 for disable)");
|
||||
radarTimeout = WiFiSettings.floating("radar_timeout", 0, 300, DEFAULT_DEBOUNCE_TIMEOUT, "Radar motion timeout (in seconds)");
|
||||
}
|
||||
|
||||
void SerialReport()
|
||||
|
@ -42,56 +57,45 @@ namespace Motion
|
|||
static void PirLoop(AsyncMqttClient& mqttClient)
|
||||
{
|
||||
if (!pirPin) return;
|
||||
int pirValue = digitalRead(pirPin);
|
||||
bool detected = digitalRead(pirPin) == HIGH;
|
||||
if (detected) lastPirMilli = millis();
|
||||
unsigned long since = millis() - lastPirMilli;
|
||||
int pirValue = (detected || since < (pirTimeout * 1000)) ? HIGH : LOW;
|
||||
|
||||
if (pirValue != lastPirValue)
|
||||
{
|
||||
if (pirValue == HIGH)
|
||||
{
|
||||
mqttClient.publish((roomsTopic + "/motion").c_str(), 0, true, "ON");
|
||||
Serial.println("PIR MOTION DETECTED!!!");
|
||||
}
|
||||
else
|
||||
{
|
||||
mqttClient.publish((roomsTopic + "/motion").c_str(), 0, true, "OFF");
|
||||
Serial.println("NO PIR MOTION DETECTED!!!");
|
||||
}
|
||||
|
||||
lastPirValue = pirValue;
|
||||
}
|
||||
if (lastPirValue == pirValue) return;
|
||||
mqttClient.publish((roomsTopic + "/pir").c_str(), 0, true, pirValue == HIGH ? "ON" : "OFF");
|
||||
GUI::pir(pirValue == HIGH);
|
||||
lastPirValue = pirValue;
|
||||
}
|
||||
|
||||
static void RadarLoop(AsyncMqttClient& mqttClient)
|
||||
{
|
||||
if (!radarPin) return;
|
||||
int radarValue = digitalRead(radarPin);
|
||||
bool detected = digitalRead(radarPin) == HIGH;
|
||||
if (detected) lastRadarMilli = millis();
|
||||
unsigned long since = millis() - lastRadarMilli;
|
||||
int radarValue = (detected || since < (radarTimeout * 1000)) ? HIGH : LOW;
|
||||
|
||||
if (radarValue != lastRadarValue)
|
||||
{
|
||||
if (radarValue == HIGH)
|
||||
{
|
||||
mqttClient.publish((roomsTopic + "/motion").c_str(), 0, true, "ON");
|
||||
Serial.println("Radar MOTION DETECTED!!!");
|
||||
}
|
||||
else
|
||||
{
|
||||
mqttClient.publish((roomsTopic + "/motion").c_str(), 0, true, "OFF");
|
||||
Serial.println("NO Radar MOTION DETECTED!!!");
|
||||
}
|
||||
|
||||
lastRadarValue = radarValue;
|
||||
}
|
||||
if (lastRadarValue == radarValue) return;
|
||||
mqttClient.publish((roomsTopic + "/radar").c_str(), 0, true, radarValue == HIGH ? "ON" : "OFF");
|
||||
GUI::radar(radarValue == HIGH);
|
||||
lastRadarValue = radarValue;
|
||||
}
|
||||
|
||||
void Loop(AsyncMqttClient& mqttClient)
|
||||
{
|
||||
PirLoop(mqttClient);
|
||||
RadarLoop(mqttClient);
|
||||
int motionValue = (lastRadarValue == HIGH || lastPirValue == HIGH) ? HIGH : LOW;
|
||||
if (lastMotionValue == motionValue) return;
|
||||
mqttClient.publish((roomsTopic + "/motion").c_str(), 0, true, motionValue == HIGH ? "ON" : "OFF");
|
||||
lastMotionValue = motionValue;
|
||||
}
|
||||
|
||||
bool SendDiscovery(DynamicJsonDocument& doc)
|
||||
{
|
||||
if (!pirPin && !radarPin) return true;
|
||||
if (!pirPin && !radarPin)
|
||||
return true;
|
||||
|
||||
commonDiscovery();
|
||||
doc["~"] = roomsTopic;
|
||||
|
@ -104,6 +108,29 @@ namespace Motion
|
|||
serializeJson(doc, buffer);
|
||||
String discoveryTopic = "homeassistant/binary_sensor/espresense_" + ESPMAC + "/motion/config";
|
||||
|
||||
return pub(discoveryTopic.c_str(), 0, true, buffer);
|
||||
if (!pub(discoveryTopic.c_str(), 0, true, buffer)) return false;
|
||||
return (!pirPin || sendNumberDiscovery("Pir Timeout", EC_CONFIG)) && (!radarPin || sendNumberDiscovery("Radar Timeout", EC_CONFIG));
|
||||
}
|
||||
|
||||
bool Command(String& command, String& pay)
|
||||
{
|
||||
if (command == "pir_timeout")
|
||||
{
|
||||
pirTimeout = pay.toInt();
|
||||
spurt("/pir_timeout", pay);
|
||||
}
|
||||
else if (command == "radar_timeout")
|
||||
{
|
||||
radarTimeout = pay.toInt();
|
||||
spurt("/radar_timeout", pay);
|
||||
}
|
||||
else return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SendOnline(DynamicJsonDocument& doc)
|
||||
{
|
||||
return pub((roomsTopic + "/pir_timeout").c_str(), 0, true, String(pirTimeout).c_str())
|
||||
&& pub((roomsTopic + "/radar_timeout").c_str(), 0, true, String(radarTimeout).c_str());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,15 +1,17 @@
|
|||
#pragma once
|
||||
|
||||
#include <ArduinoJson.h>
|
||||
|
||||
// Forward declares
|
||||
class AsyncMqttClient;
|
||||
|
||||
namespace Motion
|
||||
{
|
||||
void Setup();
|
||||
void ConnectToWifi();
|
||||
void SerialReport();
|
||||
void Loop(AsyncMqttClient& mqttClient);
|
||||
bool SendDiscovery(DynamicJsonDocument& doc);
|
||||
}
|
||||
#pragma once
|
||||
|
||||
#include <ArduinoJson.h>
|
||||
|
||||
// Forward declares
|
||||
class AsyncMqttClient;
|
||||
|
||||
namespace Motion
|
||||
{
|
||||
void Setup();
|
||||
void ConnectToWifi();
|
||||
void SerialReport();
|
||||
void Loop(AsyncMqttClient& mqttClient);
|
||||
bool SendDiscovery(DynamicJsonDocument& doc);
|
||||
bool SendOnline(DynamicJsonDocument& doc);
|
||||
bool Command(String& command, String& pay);
|
||||
}
|
||||
|
|
|
@ -15,6 +15,9 @@
|
|||
// Maximum distance (in meters) to report. Devices that are calculated to be further than this distance in meters will not be reported
|
||||
#define DEFAULT_MAX_DISTANCE 16
|
||||
|
||||
// Seconds before reporting radar/motion cleared
|
||||
#define DEFAULT_DEBOUNCE_TIMEOUT 0.5
|
||||
|
||||
// Define the base topic for room detection. Usually "espresense"
|
||||
#define CHANNEL String("espresense")
|
||||
|
||||
|
@ -51,3 +54,6 @@
|
|||
#define DEFAULT_AUTO_UPDATE false
|
||||
#define DEFAULT_ARDUINO_OTA true
|
||||
#endif
|
||||
|
||||
static const char *const EC_DIAGNOSTIC = "diagnostic";
|
||||
static const char *const EC_CONFIG = "config";
|
||||
|
|
20
src/main.cpp
20
src/main.cpp
|
@ -7,7 +7,8 @@ bool sendTelemetry(int totalSeen, int totalFpSeen, int totalFpQueried, int total
|
|||
{
|
||||
if (!online)
|
||||
{
|
||||
if (sendOnline())
|
||||
if (pub(statusTopic.c_str(), 0, true, "online") && pub((roomsTopic + "/max_distance").c_str(), 0, true, String(BleFingerprintCollection::maxDistance).c_str()) && pub((roomsTopic + "/absorption").c_str(), 0, true, String(BleFingerprintCollection::absorption).c_str()) && pub((roomsTopic + "/query").c_str(), 0, true, BleFingerprintCollection::query.c_str()) && pub((roomsTopic + "/include").c_str(), 0, true, BleFingerprintCollection::include.c_str()) && pub((roomsTopic + "/exclude").c_str(), 0, true, BleFingerprintCollection::exclude.c_str()) && pub((roomsTopic + "/known_macs").c_str(), 0, true, BleFingerprintCollection::knownMacs.c_str()) && pub((roomsTopic + "/count_ids").c_str(), 0, true, BleFingerprintCollection::countIds.c_str()) && pub((roomsTopic + "/status_led").c_str(), 0, true, String(GUI::statusLed ? "ON" : "OFF").c_str()) && pub((roomsTopic + "/arduino_ota").c_str(), 0, true, String(arduinoOta ? "ON" : "OFF").c_str()) &&
|
||||
pub((roomsTopic + "/auto_update").c_str(), 0, true, String(autoUpdate ? "ON" : "OFF").c_str()) && pub((roomsTopic + "/prerelease").c_str(), 0, true, String(prerelease ? "ON" : "OFF").c_str()) && pub((roomsTopic + "/active_scan").c_str(), 0, true, String(activeScan ? "ON" : "OFF").c_str()) && Motion::SendOnline(doc))
|
||||
{
|
||||
online = true;
|
||||
reconnectTries = 0;
|
||||
|
@ -196,6 +197,7 @@ void setupNetwork()
|
|||
|
||||
WiFiSettings.heading("Misc <a href='https://espresense.com/configuration/settings#misc' target='_blank'>ℹ️</a>", false);
|
||||
GUI::statusLed = WiFiSettings.checkbox("status_led", true, "Status LED");
|
||||
|
||||
Motion::ConnectToWifi();
|
||||
#ifdef SENSORS
|
||||
dht11Pin = WiFiSettings.integer("dht11_pin", 0, "DHT11 sensor pin (0 for disable)");
|
||||
|
@ -318,77 +320,66 @@ void onMqttMessage(char *topic, char *payload, AsyncMqttClientMessageProperties
|
|||
if (commandPos < 0) return;
|
||||
auto command = top.substring(commandPos + 1, setPos);
|
||||
|
||||
bool changed = true;
|
||||
if (command == "max_distance")
|
||||
{
|
||||
BleFingerprintCollection::maxDistance = pay.toFloat();
|
||||
spurt("/max_dist", pay);
|
||||
online = false;
|
||||
}
|
||||
else if (command == "absorption")
|
||||
{
|
||||
BleFingerprintCollection::absorption = pay.toFloat();
|
||||
spurt("/absorption", pay);
|
||||
online = false;
|
||||
}
|
||||
else if (command == "active_scan")
|
||||
{
|
||||
activeScan = pay == "ON";
|
||||
spurt("/active_scan", String(activeScan));
|
||||
online = false;
|
||||
}
|
||||
else if (command == "query")
|
||||
{
|
||||
BleFingerprintCollection::query = pay;
|
||||
spurt("/query", pay);
|
||||
online = false;
|
||||
}
|
||||
else if (command == "include")
|
||||
{
|
||||
BleFingerprintCollection::include = pay;
|
||||
spurt("/include", pay);
|
||||
online = false;
|
||||
}
|
||||
else if (command == "exclude")
|
||||
{
|
||||
BleFingerprintCollection::exclude = pay;
|
||||
spurt("/exclude", pay);
|
||||
online = false;
|
||||
}
|
||||
else if (command == "known_macs")
|
||||
{
|
||||
BleFingerprintCollection::knownMacs = pay;
|
||||
spurt("/known_macs", pay);
|
||||
online = false;
|
||||
}
|
||||
else if (command == "count_ids")
|
||||
{
|
||||
BleFingerprintCollection::countIds = pay;
|
||||
spurt("/count_ids", pay);
|
||||
online = false;
|
||||
}
|
||||
else if (command == "status_led")
|
||||
{
|
||||
GUI::statusLed = pay == "ON";
|
||||
spurt("/status_led", String(GUI::statusLed));
|
||||
online = false;
|
||||
}
|
||||
else if (command == "arduino_ota")
|
||||
{
|
||||
arduinoOta = pay == "ON";
|
||||
spurt("/arduino_ota", String(arduinoOta));
|
||||
online = false;
|
||||
}
|
||||
else if (command == "auto_update")
|
||||
{
|
||||
autoUpdate = pay == "ON";
|
||||
spurt("/auto_update", String(autoUpdate));
|
||||
online = false;
|
||||
}
|
||||
else if (command == "prerelease")
|
||||
{
|
||||
prerelease = pay == "ON";
|
||||
spurt("/prerelease", String(prerelease));
|
||||
online = false;
|
||||
}
|
||||
else if (command == "restart")
|
||||
{
|
||||
|
@ -398,6 +389,9 @@ void onMqttMessage(char *topic, char *payload, AsyncMqttClientMessageProperties
|
|||
{
|
||||
heap_caps_dump_all();
|
||||
}
|
||||
else if (Motion::Command(command, pay)){}
|
||||
else changed = false;
|
||||
if (changed) online = false;
|
||||
}
|
||||
|
||||
void reconnect(TimerHandle_t xTimer)
|
||||
|
|
10
src/main.h
10
src/main.h
|
@ -56,9 +56,6 @@ int BH1750_I2c_Bus;
|
|||
#include "HX711.h"
|
||||
#endif
|
||||
|
||||
static const char *const EC_DIAGNOSTIC = "diagnostic";
|
||||
static const char *const EC_CONFIG = "config";
|
||||
|
||||
AsyncMqttClient mqttClient;
|
||||
TimerHandle_t reconnectTimer;
|
||||
TaskHandle_t scanTaskHandle, reportTaskHandle;
|
||||
|
@ -328,11 +325,6 @@ bool pub(const char *topic, uint8_t qos, bool retain, const char *payload, size_
|
|||
return false;
|
||||
}
|
||||
|
||||
bool sendOnline()
|
||||
{
|
||||
return pub(statusTopic.c_str(), 0, true, "online") && pub((roomsTopic + "/max_distance").c_str(), 0, true, String(BleFingerprintCollection::maxDistance).c_str()) && pub((roomsTopic + "/absorption").c_str(), 0, true, String(BleFingerprintCollection::absorption).c_str()) && pub((roomsTopic + "/query").c_str(), 0, true, BleFingerprintCollection::query.c_str()) && pub((roomsTopic + "/include").c_str(), 0, true, BleFingerprintCollection::include.c_str()) && pub((roomsTopic + "/exclude").c_str(), 0, true, BleFingerprintCollection::exclude.c_str()) && pub((roomsTopic + "/known_macs").c_str(), 0, true, BleFingerprintCollection::knownMacs.c_str()) && pub((roomsTopic + "/count_ids").c_str(), 0, true, BleFingerprintCollection::countIds.c_str()) && pub((roomsTopic + "/status_led").c_str(), 0, true, String(GUI::statusLed ? "ON" : "OFF").c_str()) && pub((roomsTopic + "/arduino_ota").c_str(), 0, true, String(arduinoOta ? "ON" : "OFF").c_str()) && pub((roomsTopic + "/auto_update").c_str(), 0, true, String(autoUpdate ? "ON" : "OFF").c_str()) && pub((roomsTopic + "/prerelease").c_str(), 0, true, String(prerelease ? "ON" : "OFF").c_str()) && pub((roomsTopic + "/active_scan").c_str(), 0, true, String(activeScan ? "ON" : "OFF").c_str());
|
||||
}
|
||||
|
||||
void commonDiscovery()
|
||||
{
|
||||
doc.clear();
|
||||
|
@ -525,7 +517,7 @@ bool sendNumberDiscovery(const String &name, const String &entityCategory)
|
|||
doc["avty_t"] = "~/status";
|
||||
doc["stat_t"] = "~/" + slug;
|
||||
doc["cmd_t"] = "~/" + slug + "/set";
|
||||
doc["step"] = "0.01";
|
||||
doc["step"] = "0.1";
|
||||
doc["entity_category"] = entityCategory;
|
||||
|
||||
serializeJson(doc, buffer);
|
||||
|
|
Loading…
Reference in New Issue