diff --git a/deploy/data/usr/share/homed-zigbee/expose.json b/deploy/data/usr/share/homed-zigbee/expose.json new file mode 100644 index 00000000..45240b12 --- /dev/null +++ b/deploy/data/usr/share/homed-zigbee/expose.json @@ -0,0 +1,135 @@ +{ + "alarm": {"type": "binary", "icon": "mdi:bell", "_comment_": "may be toggle"}, + "batteryLow": {"type": "binary", "class": "battery"}, + "contact": {"type": "binary", "class": "door"}, + "fault": {"type": "binary", "icon": "mdi:alert"}, + "gas": {"type": "binary", "class": "gas"}, + "motion": {"type": "binary", "class": "motion"}, + "occupancy": {"type": "binary", "class": "occupancy"}, + "smoke": {"type": "binary", "class": "smoke"}, + "tamper": {"type": "binary", "class": "tamper"}, + "test": {"type": "binary", "icon": "mdi:eyedropper"}, + "vibration": {"type": "binary", "class": "vibration"}, + "waterLeak": {"type": "binary", "class": "moisture"}, + "windowOpen": {"type": "binary", "icon": "mdi:mdi:window-open-variant"}, + + "action": {"type": "sensor", "icon": "mdi:gesture-double-tap"}, + "battery": {"type": "sensor", "class": "battery", "unit": "%", "round": 1}, + "co2": {"type": "sensor", "class": "carbon_dioxide", "unit": "ppm"}, + "count": {"type": "sensor", "icon": "mdi:counter", "_comment_": "may be numeric"}, + "current": {"type": "sensor", "class": "current", "unit": "A", "round": 3}, + "dosePerHour": {"type": "sensor", "unit": "μR/h", "icon": "mdi:radioactive"}, + "eco2": {"type": "sensor", "class": "carbon_dioxide", "unit": "ppm"}, + "energy": {"type": "sensor", "class": "energy", "unit": "kWn", "round": 2}, + "event": {"type": "sensor", "icon": "mdi:bell"}, + "eventsPerMinute": {"type": "sensor", "icon": "mdi:radioactive"}, + "fallStatus": {"type": "sensor", "icon": "mdi:alert"}, + "formaldehyde": {"type": "sensor", "class": "volatile_organic_compounds", "unit": "µg/m³"}, + "frequency": {"type": "sensor", "class": "frequency", "unit": "Hz", "round": 1}, + "humidity": {"type": "sensor", "class": "humidity", "unit": "%", "round": 1}, + "illuminance": {"type": "sensor", "class": "illuminance", "unit": "lx"}, + "moisture": {"type": "sensor", "class": "moisture", "unit": "%", "round": 1}, + "motionSpeed": {"type": "sensor", "icon": "mdi:motion"}, + "motionStatus": {"type": "sensor", "icon": "mdi:motion"}, + "pm1": {"type": "sensor", "class": "pm1", "unit": "µg/m³", "icon": "mdi:molecule"}, + "pm10": {"type": "sensor", "class": "pm10", "unit": "µg/m³", "icon": "mdi:molecule"}, + "pm25": {"type": "sensor", "class": "pm25", "unit": "µg/m³"}, + "position": {"type": "sensor", "icon": "mdi:valve", "unit": "%"}, + "power": {"type": "sensor", "class": "power", "unit": "W", "round": 2}, + "presenceStatus": {"type": "sensor", "icon": "mdi:home"}, + "pressure": {"type": "sensor", "class": "pressure", "unit": "kPa", "round": 1}, + "scene": {"type": "sensor", "icon": "mdi:gesture-tap-button"}, + "smokeConcentration": {"type": "sensor", "unit": "ppm", "icon": "mdi:smoke"}, + "staticDwellAlarm": {"type": "sensor", "icon": "mdi:bell"}, + "targetDistance": {"type": "sensor", "class": "distance", "unit": "m", "round": 1}, + "temperature": {"type": "sensor", "class": "temperature", "unit": "°C", "round": 1}, + "voc": {"type": "sensor", "class": "volatile_organic_compounds_parts", "unit": "ppb"}, + "voltage": {"type": "sensor", "class": "voltage", "unit": "V", "round": 1}, + + "autoBrightness": {"type": "toggle", "icon": "mdi:brightness-4"}, + "boost": {"type": "toggle", "control": true, "icon": "mdi:fire"}, + "buzzerFeedback": {"type": "toggle", "icon": "mdi:music"}, + "calibration": {"type": "toggle", "icon": "mdi:swap-horizontal-bold"}, + "childLock": {"type": "toggle", "control": true, "icon": "mdi:lock"}, + "co2AutoCalibration": {"type": "toggle", "icon": "mdi:molecule-co2"}, + "co2LongChart": {"type": "toggle", "icon": "mdi:chart-box"}, + "co2Relay": {"type": "toggle", "icon": "mdi:molecule-co2"}, + "co2RelayInvert": {"type": "toggle", "icon": "mdi:molecule-co2"}, + "ecoMode": {"type": "toggle", "control": true, "icon": "mdi:leaf"}, + "frostProtection": {"type": "toggle", "control": true, "icon": "mdi:snowflake"}, + "humidityRelay": {"type": "toggle", "icon": "mdi:water-percent"}, + "humidityRelayInvert": {"type": "toggle", "icon": "mdi:water-percent"}, + "indicator": {"type": "toggle", "icon": "mdi:led-on"}, + "interlock": {"type": "toggle", "icon": "mdi:lock"}, + "ledFeedback": {"type": "toggle", "icon": "mdi:led-on"}, + "nightBacklight": {"type": "toggle", "icon": "mdi:brightness-4"}, + "pm25Relay": {"type": "toggle", "icon": "mdi:molecule"}, + "pm25RelayInvert": {"type": "toggle", "icon": "mdi:molecule"}, + "pressureLongChart": {"type": "toggle", "icon": "mdi:chart-box"}, + "reverse": {"type": "toggle", "icon": "mdi:swap-horizontal-bold"}, + "statusMemory": {"type": "toggle", "icon": "mdi:memory"}, + "temperatureRelay": {"type": "toggle", "icon": "mdi:thermometer"}, + "temperatureRelayInvert": {"type": "toggle", "icon": "mdi:thermometer"}, + "tumbleSwitch": {"type": "toggle", "icon": "mdi:dip-switch"}, + "vocRelay": {"type": "toggle", "icon": "mdi:molecule"}, + "vocRelayInvert": {"type": "toggle", "icon": "mdi:molecule"}, + "windowDetection": {"type": "toggle", "icon": "mdi:mdi:window-open-variant"}, + + "altitude": {"type": "number", "unit": "m", "icon": "mdi:altimeter"}, + "awayDays": {"type": "number", "icon": "mdi:calendar-week"}, + "awayTemperature": {"type": "number", "unit": "°C", "icon": "mdi:thermometer"}, + "boostTimeout": {"type": "number", "unit": "sec", "icon": "mdi:timer"}, + "co2High": {"type": "number", "unit": "ppm", "icon": "mdi:molecule-co2"}, + "co2Low": {"type": "number", "unit": "ppm", "icon": "mdi:molecule-co2"}, + "co2ManualCalibration": {"type": "number", "unit": "ppm", "icon": "mdi:molecule-co2"}, + "comfortTemperature": {"type": "number", "unit": "°C", "icon": "mdi:thermometer"}, + "detectionDelay": {"type": "number", "unit": "sec", "icon": "mdi:timer"}, + "distanceMax": {"type": "number", "unit": "m", "icon": "mdi:arrow-left-right"}, + "distanceMin": {"type": "number", "unit": "m", "icon": "mdi:arrow-left-right"}, + "duration": {"type": "number", "unit": "sec", "icon": "mdi:timer"}, + "ecoTemperature": {"type": "number", "unit": "°C", "icon": "mdi:thermometer"}, + "fadingTime": {"type": "number", "unit": "sec", "icon": "mdi:timer"}, + "fallSensitivity": {"type": "number", "icon": "mdi:dumbbell"}, + "humidityHigh": {"type": "number", "unit": "%", "icon": "mdi:water-percent"}, + "humidityLow": {"type": "number", "unit": "%", "icon": "mdi:water-percent"}, + "humidityOffset": {"type": "number", "unit": "%", "icon": "mdi:water-percent"}, + "hysteresis": {"type": "number", "unit": "°C", "icon": "mdi:thermometer"}, + "indicatorLevel": {"type": "number", "icon": "mdi:led-on"}, + "melody": {"type": "number", "icon": "mdi:music-note"}, + "motionSensitivity": {"type": "number", "icon": "mdi:dumbbell"}, + "pattern": {"type": "number", "control": true, "icon": "mdi:swap-horizontal-bold"}, + "pm25High": {"type": "number", "unit": "µg/m³", "icon": "mdi:molecule"}, + "pm25Low": {"type": "number", "unit": "µg/m³", "icon": "mdi:molecule"}, + "pressureOffset": {"type": "number", "unit": "kPa", "icon": "mdi:gauge"}, + "readInterval": {"type": "number", "unit": "sec", "icon": "mdi:timer"}, + "reportingDelay": {"type": "number", "unit": "sec", "icon": "mdi:timer"}, + "sensitivity": {"type": "number", "icon": "mdi:dumbbell"}, + "sensorCount": {"type": "number", "icon": "mdi:dip-switch"}, + "temperatureHigh": {"type": "number", "unit": "°C", "icon": "mdi:thermometer"}, + "temperatureLow": {"type": "number", "unit": "°C", "icon": "mdi:thermometer"}, + "temperatureOffset": {"type": "number", "unit": "°C", "icon": "mdi:thermometer"}, + "threshold": {"type": "number", "_comment_": "need icon and unit, maybe delete?"}, + "timer": {"type": "number", "control": true, "unit": "min", "icon": "mdi:timer"}, + "tumbleAlarmTime": {"type": "number", "unit": "min", "icon": "mdi:timer"}, + "vocHigh": {"type": "number", "unit": "ppb", "icon": "mdi:molecule"}, + "vocLow": {"type": "number", "unit": "ppb", "icon": "mdi:molecule"}, + + "buttonMode": {"type": "select"}, + "detectionMode": {"type": "select"}, + "displayMode": {"type": "select"}, + "distanceMode": {"type": "select", "icon": "mdi:arrow-left-right"}, + "indicatorMode": {"type": "select", "icon": "mdi:lightbulb-on"}, + "leftMode": {"type": "select"}, + "lightType": {"type": "select", "icon": "mdi:lightbulb-on"}, + "operationMode": {"type": "select"}, + "powerOnStatus": {"type": "select"}, + "radarScene": {"type": "select"}, + "rightMode": {"type": "select"}, + "sensitivityMode": {"type": "select", "icon": "mdi:dumbbell"}, + "sensorType": {"type": "select"}, + "switchMode": {"type": "select"}, + "switchType": {"type": "select"}, + "timeoutMode": {"type": "select", "icon": "mdi:timer"}, + "volumeMode": {"type": "select", "icon": "mdi:volume-high"}, + "weekMode": {"type": "select", "icon": "mdi:calendar-week"} +} diff --git a/device.cpp b/device.cpp index 327f61fb..a381fa05 100644 --- a/device.cpp +++ b/device.cpp @@ -7,6 +7,8 @@ DeviceList::DeviceList(QSettings *config, QObject *parent) : QObject(parent), m_databaseTimer(new QTimer(this)), m_propertiesTimer(new QTimer(this)), m_names(false), m_permitJoin(false), m_sync(false) { + QFile file; + PropertyObject::registerMetaTypes(); ActionObject::registerMetaTypes(); BindingObject::registerMetaTypes(); @@ -21,6 +23,14 @@ DeviceList::DeviceList(QSettings *config, QObject *parent) : QObject(parent), m_ m_libraryDir.setPath(config->value("device/library", "/usr/share/homed-zigbee").toString()); m_offsets = config->value("device/offsets", true).toBool(); + file.setFileName(QString("%1/expose.json").arg(m_libraryDir.path())); + + if (file.open(QFile::ReadOnly)) + { + m_exposeOptions = QJsonDocument::fromJson(file.readAll()).object().toVariantMap(); + file.close(); + } + connect(m_databaseTimer, &QTimer::timeout, this, &DeviceList::writeDatabase); connect(m_propertiesTimer, &QTimer::timeout, this, &DeviceList::writeProperties); @@ -181,7 +191,7 @@ void DeviceList::setupDevice(const Device &device) QFile file(QString("%1/%2").arg(it->path(), list.at(i))); QJsonArray array; - if (!file.open(QFile::ReadOnly)) + if (list.at(i) == "expose.json" || !file.open(QFile::ReadOnly)) continue; array = QJsonDocument::fromJson(file.readAll()).object().value(manufacturerName).toArray(); @@ -348,34 +358,21 @@ void DeviceList::setupEndpoint(const Endpoint &endpoint, const QJsonObject &json for (auto it = exposes.begin(); it != exposes.end(); it++) { + QString exposeName = it->toString(), itemName = it->toString().split('_').value(0), optionName = multiple ? QString("%1_%2").arg(itemName, QString::number(endpoint->id())) : itemName; + QMap option = m_exposeOptions.value(itemName).toMap(); + QList list = {"light", "switch", "lock", "cover", "thermostat"}; Expose expose; - QString name = it->toString(); - QList list = name.split('_'); - QMap option = device->options().value(multiple ? QString("%1_%2").arg(list.value(0), QString::number(endpoint->id())) : list.value(0)).toMap(); - int type = QMetaType::type(list.value(0).append("Expose").toUtf8()); + int type; - if (type) - { - expose = Expose(reinterpret_cast (QMetaType::create(type))); - - if (list.count() > 1) - expose->setName(name); - } - else if (option.value("binary").toBool()) - expose = Expose(new BinaryObject(name)); - else if (option.value("sensor").toBool()) - expose = Expose(new SensorObject(name, true)); - else if (option.value("boolean").toBool()) - expose = Expose(new BooleanObject(name)); - else if (option.contains("min") && option.contains("max")) - expose = Expose(new NumberObject(name)); - else if (option.contains("enum")) - expose = Expose(new SelectObject(name)); - else - expose = Expose(new ExposeObject(name)); + option.insert(device->options().value(optionName).toMap()); + type = QMetaType::type(QString(list.contains(itemName) ? itemName : option.value("type").toString()).append("Expose").toUtf8()); + expose = Expose(type ? reinterpret_cast (QMetaType::create(type)) : new ExposeObject(itemName)); + expose->setName(exposeName); expose->setParent(endpoint.data()); expose->setMultiple(multiple); + + device->options().insert(optionName, option); endpoint->exposes().append(expose); } @@ -420,14 +417,15 @@ void DeviceList::recognizeDevice(const Device &device) it.value()->properties().append(Property(new Properties::BatteryPercentage)); it.value()->bindings().append(Binding(new Bindings::Battery)); it.value()->reportings().append(Reporting(new Reportings::BatteryPercentage)); - it.value()->exposes().append(Expose(new Sensor::Battery)); + it.value()->exposes().append(Expose(new SensorObject("battery"))); break; case CLUSTER_TEMPERATURE_CONFIGURATION: it.value()->properties().append(Property(new Properties::DeviceTemperature)); it.value()->bindings().append(Binding(new Bindings::DeviceTemperature)); it.value()->reportings().append(Reporting(new Reportings::DeviceTemperature)); - it.value()->exposes().append(Expose(new Sensor::Temperature)); + it.value()->exposes().append(Expose(new SensorObject("temperature"))); + device->options().insert(QString("temperature_%1").arg(it.key()), QMap {{"diagnostic", true}}); break; case CLUSTER_ON_OFF: @@ -477,8 +475,8 @@ void DeviceList::recognizeDevice(const Device &device) it.value()->bindings().append(Binding(new Bindings::Thermostat)); it.value()->reportings().append(Reporting(new Reportings::Thermostat)); it.value()->exposes().append(Expose(new ThermostatObject)); - device->options().insert(QString("targetTemperature_%1").arg(it.key()), QVariant(QMap {{"min", 7}, {"max", 30}, {"step", 0.1}, {"unit", "°C"}})); - device->options().insert(QString("systemMode_%1").arg(it.key()), QVariant(QMap {{"enum", QVariant(QList {"off", "auto", "heat"})}})); + device->options().insert(QString("targetTemperature_%1").arg(it.key()), QMap {{"min", 7}, {"max", 30}, {"step", 0.1}, {"unit", "°C"}}); + device->options().insert(QString("systemMode_%1").arg(it.key()), QMap {{"enum", QVariant(QList {"off", "auto", "heat"})}}); device->options().insert(QString("heatingStatus_%1").arg(it.key()), true); break; @@ -523,61 +521,61 @@ void DeviceList::recognizeDevice(const Device &device) it.value()->properties().append(Property(new Properties::Illuminance)); it.value()->bindings().append(Binding(new Bindings::Illuminance)); it.value()->reportings().append(Reporting(new Reportings::Illuminance)); - it.value()->exposes().append(Expose(new Sensor::Illuminance)); + it.value()->exposes().append(Expose(new SensorObject("illuminance"))); break; case CLUSTER_TEMPERATURE_MEASUREMENT: it.value()->properties().append(Property(new Properties::Temperature)); it.value()->bindings().append(Binding(new Bindings::Temperature)); it.value()->reportings().append(Reporting(new Reportings::Temperature)); - it.value()->exposes().append(Expose(new Sensor::Temperature)); + it.value()->exposes().append(Expose(new SensorObject("temperature"))); break; case CLUSTER_PRESSURE_MEASUREMENT: it.value()->properties().append(Property(new Properties::Pressure)); it.value()->bindings().append(Binding(new Bindings::Pressure)); it.value()->reportings().append(Reporting(new Reportings::Pressure)); - it.value()->exposes().append(Expose(new Sensor::Pressure)); + it.value()->exposes().append(Expose(new SensorObject("pressure"))); break; case CLUSTER_HUMIDITY_MEASUREMENT: it.value()->properties().append(Property(new Properties::Humidity)); it.value()->bindings().append(Binding(new Bindings::Humidity)); it.value()->reportings().append(Reporting(new Reportings::Humidity)); - it.value()->exposes().append(Expose(new Sensor::Humidity)); + it.value()->exposes().append(Expose(new SensorObject("humidity"))); break; case CLUSTER_OCCUPANCY_SENSING: it.value()->properties().append(Property(new Properties::Occupancy)); - it.value()->exposes().append(Expose(new Binary::Occupancy)); + it.value()->exposes().append(Expose(new BinaryObject("occupancy"))); break; case CLUSTER_MOISTURE_MEASUREMENT: it.value()->properties().append(Property(new Properties::Moisture)); it.value()->bindings().append(Binding(new Bindings::Moisture)); it.value()->reportings().append(Reporting(new Reportings::Moisture)); - it.value()->exposes().append(Expose(new Sensor::Moisture)); + it.value()->exposes().append(Expose(new SensorObject("moisture"))); break; case CLUSTER_CO2_CONCENTRATION: it.value()->properties().append(Property(new Properties::CO2)); it.value()->bindings().append(Binding(new Bindings::CO2)); it.value()->reportings().append(Reporting(new Reportings::CO2)); - it.value()->exposes().append(Expose(new Sensor::CO2)); + it.value()->exposes().append(Expose(new SensorObject("co2"))); break; case CLUSTER_PM25_CONCENTRATION: it.value()->properties().append(Property(new Properties::PM25)); it.value()->bindings().append(Binding(new Bindings::PM25)); it.value()->reportings().append(Reporting(new Reportings::PM25)); - it.value()->exposes().append(Expose(new Sensor::PM25)); + it.value()->exposes().append(Expose(new SensorObject("pm25"))); break; case CLUSTER_SMART_ENERGY_METERING: it.value()->properties().append(Property(new Properties::Energy)); it.value()->bindings().append(Binding(new Bindings::Energy)); it.value()->reportings().append(Reporting(new Reportings::Energy)); - it.value()->exposes().append(Expose(new Sensor::Energy)); + it.value()->exposes().append(Expose(new SensorObject("energy"))); break; case CLUSTER_ELECTRICAL_MEASUREMENT: @@ -588,9 +586,9 @@ void DeviceList::recognizeDevice(const Device &device) it.value()->reportings().append(Reporting(new Reportings::Voltage)); it.value()->reportings().append(Reporting(new Reportings::Current)); it.value()->reportings().append(Reporting(new Reportings::Power)); - it.value()->exposes().append(Expose(new Sensor::Voltage)); - it.value()->exposes().append(Expose(new Sensor::Current)); - it.value()->exposes().append(Expose(new Sensor::Power)); + it.value()->exposes().append(Expose(new SensorObject("voltage"))); + it.value()->exposes().append(Expose(new SensorObject("current"))); + it.value()->exposes().append(Expose(new SensorObject("power"))); break; case CLUSTER_IAS_ZONE: @@ -599,27 +597,27 @@ void DeviceList::recognizeDevice(const Device &device) { case 0x000D: it.value()->properties().append(Property(new PropertiesIAS::Occupancy)); - it.value()->exposes().append(Expose(new Binary::Occupancy)); + it.value()->exposes().append(Expose(new BinaryObject("occupancy"))); break; case 0x0015: it.value()->properties().append(Property(new PropertiesIAS::Contact)); - it.value()->exposes().append(Expose(new Binary::Contact)); + it.value()->exposes().append(Expose(new BinaryObject("contact"))); break; case 0x002A: it.value()->properties().append(Property(new PropertiesIAS::WaterLeak)); - it.value()->exposes().append(Expose(new Binary::WaterLeak)); + it.value()->exposes().append(Expose(new BinaryObject("waterLeak"))); break; default: it.value()->properties().append(Property(new PropertiesIAS::ZoneStatus)); - it.value()->exposes().append(Expose(new BinaryObject)); + it.value()->exposes().append(Expose(new BinaryObject("alarm"))); break; } - it.value()->exposes().append(Expose(new Binary::BatteryLow)); - it.value()->exposes().append(Expose(new Binary::Tamper)); + it.value()->exposes().append(Expose(new BinaryObject("batteryLow"))); + it.value()->exposes().append(Expose(new BinaryObject("tamper"))); break; } } diff --git a/device.h b/device.h index 8dac8787..32232e9b 100644 --- a/device.h +++ b/device.h @@ -211,6 +211,8 @@ class DeviceList : public QObject, public QMap QDir m_externalDir, m_libraryDir; bool m_offsets, m_names, m_permitJoin, m_sync; + QMap m_exposeOptions; + void unserializeDevices(const QJsonArray &devices); void unserializeProperties(const QJsonObject &properties); diff --git a/homed-zigbee.pro b/homed-zigbee.pro index db3962d2..45ccdc12 100644 --- a/homed-zigbee.pro +++ b/homed-zigbee.pro @@ -69,6 +69,7 @@ SOURCES += \ DISTFILES += \ deploy/data/usr/share/homed-zigbee/efekta.json \ + deploy/data/usr/share/homed-zigbee/expose.json \ deploy/data/usr/share/homed-zigbee/gledopto.json \ deploy/data/usr/share/homed-zigbee/gs.json \ deploy/data/usr/share/homed-zigbee/homed.json \