Compare commits
No commits in common. "master" and "itemrefactor" have entirely different histories.
master
...
itemrefact
61 changed files with 307 additions and 4442 deletions
129
AGENTS.md
129
AGENTS.md
|
|
@ -1,129 +0,0 @@
|
||||||
# SHinterface - Smart Home Interface
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
SHinterface is a Qt6-based smart home control application that interfaces with microcontrollers and various sensors to manage home automation devices. It supports both primary (master) and secondary (client) modes, allowing for distributed control across multiple devices.
|
|
||||||
|
|
||||||
## Architecture
|
|
||||||
|
|
||||||
### Core Components
|
|
||||||
|
|
||||||
1. **Main Application** (`main.cpp`)
|
|
||||||
- Entry point with command-line argument parsing
|
|
||||||
- Supports three modes:
|
|
||||||
- `PROGRAM_MODE_UI_ONLY`: Secondary client mode
|
|
||||||
- `PROGRAM_MODE_PRIMARY`: Master mode with GUI
|
|
||||||
- `PROGRAM_MODE_HEADLESS_PRIMARY`: Master mode without GUI (server only)
|
|
||||||
|
|
||||||
2. **Main Object** (`mainobject.h`, `mainobject.cpp`)
|
|
||||||
- Base class: `MainObject`
|
|
||||||
- Primary mode: `PrimaryMainObject` - Manages microcontroller, sensors, and item sources
|
|
||||||
- Secondary mode: `SecondaryMainObject` - Connects to primary via TCP
|
|
||||||
|
|
||||||
3. **Microcontroller Interface** (`microcontroller.h`, `microcontroller.cpp`)
|
|
||||||
- Communicates with embedded devices via serial or TCP
|
|
||||||
- Handles relay control, sensor data, RGB lighting, and PWM outputs
|
|
||||||
- Implements a write queue to prevent buffer overflows
|
|
||||||
|
|
||||||
### Key Systems
|
|
||||||
|
|
||||||
#### Items System
|
|
||||||
- **Item** (`items/item.h`, `items/item.cpp`): Base class for all controllable items
|
|
||||||
- Supports different value types: boolean, unsigned integer, no value
|
|
||||||
- Tracks update sources (user, actor, remote, loaded, backend)
|
|
||||||
|
|
||||||
- Item Types:
|
|
||||||
- **Relay** (`items/relay.h`, `items/relay.cpp`): Switchable outputs
|
|
||||||
- **PowerItem**: Power measurement items
|
|
||||||
- **MessageItem**: Display messages
|
|
||||||
- **SystemItem**: System-related controls
|
|
||||||
- **AuxItem**: Auxiliary PWM outputs
|
|
||||||
- **RGBItem**: RGB LED control
|
|
||||||
|
|
||||||
- **ItemStore** (`items/itemstore.h`, `items/itemstore.cpp`): Manages collection of items
|
|
||||||
- **ItemSource** (`items/itemsource.h`): Interface for item providers
|
|
||||||
- **FixedItemSource**: Static predefined items
|
|
||||||
- **ItemLoaderSource**: Loads items from configuration
|
|
||||||
|
|
||||||
#### Actors System
|
|
||||||
Actors trigger actions based on sensor conditions or timers:
|
|
||||||
|
|
||||||
- **Actor** (`actors/actor.h`, `actors/actor.cpp`): Base actor class
|
|
||||||
- Can be active/inactive and exhausted (preventing repeated triggers)
|
|
||||||
|
|
||||||
- Actor Types:
|
|
||||||
- **FactorActor**: Triggers when a factor crosses a threshold
|
|
||||||
- **PolynomalActor**: Uses polynomial calculations for triggering
|
|
||||||
- **SensorActor**: Reacts to specific sensor states
|
|
||||||
- **TimerActor**: Time-based triggering
|
|
||||||
- **AlarmTime**: Alarm/clock functionality
|
|
||||||
- **Regulator**: PID-like regulation control
|
|
||||||
|
|
||||||
#### Sensors System
|
|
||||||
- **Sensor** (`sensors/sensor.h`, `sensors/sensor.cpp`): Represents physical sensors
|
|
||||||
- Sensor types: door, temperature, humidity, pressure, brightness, button, ADC, CO2, PM2.5, VOC, etc.
|
|
||||||
|
|
||||||
- Sensor Sources:
|
|
||||||
- **SunSensorSource** (`sensors/sunsensor.h`, `sensors/sunsensor.cpp`): Solar position calculations
|
|
||||||
- **MqttSensorSource** (`sensors/mqttsensorsource.h`, `sensors/mqttsensorsource.cpp`): MQTT-based sensor data
|
|
||||||
|
|
||||||
#### Networking Services
|
|
||||||
- **TcpServer** (`service/tcpserver.h`, `service/tcpserver.cpp`): TCP server for remote control
|
|
||||||
- **WebSocketServer** (`service/websocketserver.h`, `service/websocketserver.cpp`): WebSocket interface
|
|
||||||
- **TcpClient** (`service/tcpclient.h`, `service/tcpclient.cpp`): Client for secondary instances
|
|
||||||
|
|
||||||
### UI Components
|
|
||||||
- **MainWindow** (`ui/mainwindow.ui`, `ui/mainwindow.cpp`): Main application window
|
|
||||||
- **ItemWidget**: Visual representation of items
|
|
||||||
- **ItemScrollBox**: Scrollable container for items
|
|
||||||
- **SensorListWidget**: Displays sensor information
|
|
||||||
- **ItemCreationDialog**: Create new items
|
|
||||||
- **ItemSettingsDialog**: Configure item properties
|
|
||||||
- **ActorSettingsDialog**: Configure actor behavior
|
|
||||||
|
|
||||||
## Data Flow
|
|
||||||
|
|
||||||
### Primary Mode (Master)
|
|
||||||
```
|
|
||||||
Microcontroller ↔ Items ↔ Actors ↔ Sensors
|
|
||||||
↑ ↑ ↑ ↑
|
|
||||||
TCP/WebSocket │ │ │
|
|
||||||
└──────┬──────┘
|
|
||||||
↓
|
|
||||||
UI (MainWindow)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Secondary Mode (Client)
|
|
||||||
```
|
|
||||||
Secondary Client ↔ TCP Server (Primary) → Items → Microcontroller
|
|
||||||
```
|
|
||||||
|
|
||||||
## Configuration
|
|
||||||
- Settings stored in JSON format
|
|
||||||
- Default location: `~/.config/shinterface.json`
|
|
||||||
- Command-line options:
|
|
||||||
- `-m`, `--master`: Run in master mode
|
|
||||||
- `-H`, `--host`: Set server host IP
|
|
||||||
- `-p`, `--port`: Set server port (default: 38940)
|
|
||||||
- `-c`, `--config`: Specify config file path
|
|
||||||
- `-e`, `--headless`: Run without GUI (master mode only)
|
|
||||||
|
|
||||||
## Build Requirements
|
|
||||||
- CMake 4.0+
|
|
||||||
- Qt6 (Core, Gui, Widgets, Network, Multimedia, SerialPort, Mqtt, WebSockets)
|
|
||||||
- libpipewire-0.3
|
|
||||||
- libnl-3.0
|
|
||||||
|
|
||||||
## Communication Protocols
|
|
||||||
- **Microcontroller**: Text-based protocol over serial or TCP
|
|
||||||
- **Networking**: JSON messages over TCP/WebSocket
|
|
||||||
- **Sensors**: MQTT for remote sensors, calculated values for sun position
|
|
||||||
|
|
||||||
## Key Features
|
|
||||||
1. Distributed control with primary/secondary architecture
|
|
||||||
2. Actor-based automation system
|
|
||||||
3. Multi-protocol sensor support (MQTT, serial, calculated)
|
|
||||||
4. Relay and PWM control
|
|
||||||
5. RGB lighting control
|
|
||||||
6. WebSocket API for remote access
|
|
||||||
7. Configurable via JSON
|
|
||||||
8. Cross-platform Qt application
|
|
||||||
196
CMakeLists.txt
196
CMakeLists.txt
|
|
@ -1,9 +1,9 @@
|
||||||
cmake_minimum_required(VERSION 4.0)
|
cmake_minimum_required(VERSION 4.0)
|
||||||
|
|
||||||
project(smartvos VERSION 1.0 LANGUAGES CXX)
|
project(SHinterface VERSION 1.0 LANGUAGES CXX)
|
||||||
|
|
||||||
# Set C++ standard
|
# Set C++ standard
|
||||||
set(CMAKE_CXX_STANDARD 20)
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
|
||||||
# Enable all warnings
|
# Enable all warnings
|
||||||
|
|
@ -24,116 +24,81 @@ set(CMAKE_AUTOUIC ON)
|
||||||
# Add src to include path for relative includes
|
# Add src to include path for relative includes
|
||||||
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/src)
|
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/src)
|
||||||
|
|
||||||
# Enable testing framework
|
|
||||||
enable_testing()
|
|
||||||
|
|
||||||
# Define shared sources for static library (core sources used by both main and tests)
|
|
||||||
set(SHINTERFACE_CORE_SOURCES
|
|
||||||
src/sensors/mqttsensorsource.h
|
|
||||||
src/sensors/mqttsensorsource.cpp
|
|
||||||
src/items/mqttitem.h
|
|
||||||
src/items/mqttitem.cpp
|
|
||||||
src/mqttclient.h
|
|
||||||
src/mqttclient.cpp
|
|
||||||
src/microcontroller.h
|
|
||||||
src/microcontroller.cpp
|
|
||||||
src/sun.h
|
|
||||||
src/sun.cpp
|
|
||||||
src/programmode.h
|
|
||||||
src/programmode.cpp
|
|
||||||
|
|
||||||
src/service/service.h
|
|
||||||
src/service/service.cpp
|
|
||||||
src/service/tcpclient.h
|
|
||||||
src/service/tcpclient.cpp
|
|
||||||
src/service/server.h
|
|
||||||
src/service/server.cpp
|
|
||||||
src/service/tcpserver.h
|
|
||||||
src/service/tcpserver.cpp
|
|
||||||
src/service/websocketserver.h
|
|
||||||
src/service/websocketserver.cpp
|
|
||||||
|
|
||||||
src/actors/actor.h
|
|
||||||
src/actors/actor.cpp
|
|
||||||
src/actors/factoractor.h
|
|
||||||
src/actors/factoractor.cpp
|
|
||||||
src/actors/polynomalactor.h
|
|
||||||
src/actors/polynomalactor.cpp
|
|
||||||
src/actors/sensoractor.h
|
|
||||||
src/actors/sensoractor.cpp
|
|
||||||
src/actors/alarmtime.h
|
|
||||||
src/actors/alarmtime.cpp
|
|
||||||
src/actors/regulator.h
|
|
||||||
src/actors/regulator.cpp
|
|
||||||
src/actors/timeractor.h
|
|
||||||
src/actors/timeractor.cpp
|
|
||||||
|
|
||||||
src/sensors/sensor.h
|
|
||||||
src/sensors/sensor.cpp
|
|
||||||
src/sensors/sunsensor.h
|
|
||||||
src/sensors/sunsensor.cpp
|
|
||||||
|
|
||||||
src/items/item.h
|
|
||||||
src/items/item.cpp
|
|
||||||
src/items/relay.h
|
|
||||||
src/items/relay.cpp
|
|
||||||
src/items/poweritem.h
|
|
||||||
src/items/poweritem.cpp
|
|
||||||
src/items/messageitem.h
|
|
||||||
src/items/messageitem.cpp
|
|
||||||
src/items/systemitem.h
|
|
||||||
src/items/systemitem.cpp
|
|
||||||
src/items/auxitem.h
|
|
||||||
src/items/auxitem.cpp
|
|
||||||
src/items/rgbitem.h
|
|
||||||
src/items/rgbitem.cpp
|
|
||||||
src/items/itemsource.h
|
|
||||||
src/items/itemsource.cpp
|
|
||||||
src/items/itemloadersource.h
|
|
||||||
src/items/itemloadersource.cpp
|
|
||||||
src/items/fixeditemsource.h
|
|
||||||
src/items/fixeditemsource.cpp
|
|
||||||
src/items/itemstore.h
|
|
||||||
src/items/itemstore.cpp
|
|
||||||
)
|
|
||||||
|
|
||||||
# Create static library
|
|
||||||
add_library(smartvos_core STATIC ${SHINTERFACE_CORE_SOURCES})
|
|
||||||
|
|
||||||
# Link Qt and system libraries to static library
|
|
||||||
target_link_libraries(smartvos_core
|
|
||||||
Qt6::Core
|
|
||||||
Qt6::Gui
|
|
||||||
Qt6::Widgets
|
|
||||||
Qt6::Network
|
|
||||||
Qt6::Multimedia
|
|
||||||
Qt6::SerialPort
|
|
||||||
Qt6::Mqtt
|
|
||||||
Qt6::WebSockets
|
|
||||||
${PIPEWIRE_LIBRARIES}
|
|
||||||
${LIBNL3_LIBRARIES}
|
|
||||||
)
|
|
||||||
|
|
||||||
# Add include paths to static library
|
|
||||||
target_include_directories(smartvos_core PUBLIC
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src
|
|
||||||
${PIPEWIRE_INCLUDE_DIRS}
|
|
||||||
${LIBNL3_INCLUDE_DIRS}
|
|
||||||
)
|
|
||||||
|
|
||||||
# Add subdirectory for tests
|
|
||||||
add_subdirectory(tests)
|
|
||||||
|
|
||||||
# Create executable
|
# Create executable
|
||||||
add_executable(smartvos
|
add_executable(SHinterface
|
||||||
|
src/sensors/mqttsensorsource.h src/sensors/mqttsensorsource.cpp)
|
||||||
|
|
||||||
|
# Add sources to executable
|
||||||
|
target_sources(SHinterface
|
||||||
|
PRIVATE
|
||||||
src/main.cpp
|
src/main.cpp
|
||||||
src/mainobject.h
|
src/mainobject.h
|
||||||
src/mainobject.cpp
|
src/mainobject.cpp
|
||||||
src/apgetconnected.h
|
src/apgetconnected.h
|
||||||
src/apgetconnected.cpp
|
src/apgetconnected.cpp
|
||||||
|
src/microcontroller.h
|
||||||
|
src/microcontroller.cpp
|
||||||
|
src/sun.h
|
||||||
|
src/sun.cpp
|
||||||
|
src/programmode.h
|
||||||
|
src/programmode.cpp
|
||||||
src/pipewire.h
|
src/pipewire.h
|
||||||
src/pipewire.cpp
|
src/pipewire.cpp
|
||||||
|
|
||||||
|
src/service/service.h
|
||||||
|
src/service/service.cpp
|
||||||
|
src/service/tcpclient.h
|
||||||
|
src/service/tcpclient.cpp
|
||||||
|
src/service/server.h
|
||||||
|
src/service/server.cpp
|
||||||
|
src/service/tcpserver.h
|
||||||
|
src/service/tcpserver.cpp
|
||||||
|
src/service/websocketserver.h
|
||||||
|
src/service/websocketserver.cpp
|
||||||
|
|
||||||
|
src/actors/actor.h
|
||||||
|
src/actors/actor.cpp
|
||||||
|
src/actors/factoractor.h
|
||||||
|
src/actors/factoractor.cpp
|
||||||
|
src/actors/polynomalactor.h
|
||||||
|
src/actors/polynomalactor.cpp
|
||||||
|
src/actors/sensoractor.h
|
||||||
|
src/actors/sensoractor.cpp
|
||||||
|
src/actors/alarmtime.h
|
||||||
|
src/actors/alarmtime.cpp
|
||||||
|
src/actors/regulator.h
|
||||||
|
src/actors/regulator.cpp
|
||||||
|
src/actors/timeractor.h
|
||||||
|
src/actors/timeractor.cpp
|
||||||
|
|
||||||
|
src/sensors/sensor.h
|
||||||
|
src/sensors/sensor.cpp
|
||||||
|
src/sensors/sunsensor.h
|
||||||
|
src/sensors/sunsensor.cpp
|
||||||
|
|
||||||
|
src/items/item.h
|
||||||
|
src/items/item.cpp
|
||||||
|
src/items/relay.h
|
||||||
|
src/items/relay.cpp
|
||||||
|
src/items/poweritem.h
|
||||||
|
src/items/poweritem.cpp
|
||||||
|
src/items/messageitem.h
|
||||||
|
src/items/messageitem.cpp
|
||||||
|
src/items/systemitem.h
|
||||||
|
src/items/systemitem.cpp
|
||||||
|
src/items/auxitem.h
|
||||||
|
src/items/auxitem.cpp
|
||||||
|
src/items/rgbitem.h
|
||||||
|
src/items/rgbitem.cpp
|
||||||
|
src/items/itemsource.h
|
||||||
|
src/items/itemsource.cpp
|
||||||
|
src/items/itemloadersource.h
|
||||||
|
src/items/itemloadersource.cpp
|
||||||
|
src/items/fixeditemsource.h
|
||||||
|
src/items/fixeditemsource.cpp
|
||||||
|
src/items/itemstore.h
|
||||||
|
src/items/itemstore.cpp
|
||||||
|
|
||||||
src/ui/mainwindow.h
|
src/ui/mainwindow.h
|
||||||
src/ui/mainwindow.cpp
|
src/ui/mainwindow.cpp
|
||||||
src/ui/itemwidget.h
|
src/ui/itemwidget.h
|
||||||
|
|
@ -168,12 +133,10 @@ add_executable(smartvos
|
||||||
src/ui/itemsettingswidgets/relayitemsettingswidget.cpp
|
src/ui/itemsettingswidgets/relayitemsettingswidget.cpp
|
||||||
src/ui/itemsettingswidgets/systemitemsettingswidget.h
|
src/ui/itemsettingswidgets/systemitemsettingswidget.h
|
||||||
src/ui/itemsettingswidgets/systemitemsettingswidget.cpp
|
src/ui/itemsettingswidgets/systemitemsettingswidget.cpp
|
||||||
src/ui/itemsettingswidgets/mqttitemsettingswidget.h
|
|
||||||
src/ui/itemsettingswidgets/mqttitemsettingswidget.cpp
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Add UI files
|
# Add UI files
|
||||||
target_sources(smartvos
|
target_sources(SHinterface
|
||||||
PRIVATE
|
PRIVATE
|
||||||
src/ui/mainwindow.ui
|
src/ui/mainwindow.ui
|
||||||
src/ui/itemwidget.ui
|
src/ui/itemwidget.ui
|
||||||
|
|
@ -190,18 +153,16 @@ target_sources(smartvos
|
||||||
src/ui/itemsettingswidgets/messageitemsettingswidget.ui
|
src/ui/itemsettingswidgets/messageitemsettingswidget.ui
|
||||||
src/ui/itemsettingswidgets/relayitemsettingswidget.ui
|
src/ui/itemsettingswidgets/relayitemsettingswidget.ui
|
||||||
src/ui/itemsettingswidgets/systemitemsettingswidget.ui
|
src/ui/itemsettingswidgets/systemitemsettingswidget.ui
|
||||||
src/ui/itemsettingswidgets/mqttitemsettingswidget.ui
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Add resource file
|
# Add resource file
|
||||||
target_sources(smartvos
|
target_sources(SHinterface
|
||||||
PRIVATE
|
PRIVATE
|
||||||
resources.qrc
|
resources.qrc
|
||||||
)
|
)
|
||||||
|
|
||||||
# Link libraries - link to static library plus UI-specific dependencies
|
# Link libraries
|
||||||
target_link_libraries(smartvos
|
target_link_libraries(SHinterface
|
||||||
smartvos_core
|
|
||||||
Qt6::Core
|
Qt6::Core
|
||||||
Qt6::Gui
|
Qt6::Gui
|
||||||
Qt6::Widgets
|
Qt6::Widgets
|
||||||
|
|
@ -216,16 +177,3 @@ target_link_libraries(smartvos
|
||||||
|
|
||||||
# Add include paths
|
# Add include paths
|
||||||
include_directories(${PIPEWIRE_INCLUDE_DIRS} ${LIBNL3_INCLUDE_DIRS})
|
include_directories(${PIPEWIRE_INCLUDE_DIRS} ${LIBNL3_INCLUDE_DIRS})
|
||||||
|
|
||||||
# Installation
|
|
||||||
install(TARGETS smartvos DESTINATION bin)
|
|
||||||
install(TARGETS smartvos_core DESTINATION lib)
|
|
||||||
|
|
||||||
# Install icon
|
|
||||||
install(FILES xyz.uvos.icon.png DESTINATION share/icons/hicolor/128x128/apps)
|
|
||||||
|
|
||||||
# Install .desktop file
|
|
||||||
install(FILES xyz.uvos.smartvos.desktop DESTINATION share/applications)
|
|
||||||
|
|
||||||
# Update icon cache (optional, for icon themes)
|
|
||||||
install(CODE "execute_process(COMMAND gtk-update-icon-cache -f -t ${CMAKE_INSTALL_PREFIX}/share/icons/hicolor WORKING_DIRECTORY ${CMAKE_INSTALL_PREFIX})")
|
|
||||||
|
|
|
||||||
90
README.md
90
README.md
|
|
@ -1,90 +0,0 @@
|
||||||
# SHinterface - Smart Home Control Interface
|
|
||||||
|
|
||||||
A Qt6-based smart home control application that interfaces with microcontrollers and sensors to manage home automation devices.
|
|
||||||
|
|
||||||
## Quick Start
|
|
||||||
|
|
||||||
### Building
|
|
||||||
```bash
|
|
||||||
mkdir build
|
|
||||||
cd build
|
|
||||||
cmake ..
|
|
||||||
make
|
|
||||||
```
|
|
||||||
|
|
||||||
### Running
|
|
||||||
|
|
||||||
**Primary (Master) Mode:**
|
|
||||||
```bash
|
|
||||||
./SHinterface -m
|
|
||||||
```
|
|
||||||
|
|
||||||
**Secondary (Client) Mode:**
|
|
||||||
```bash
|
|
||||||
./SHinterface -H 192.168.1.100 -p 38940
|
|
||||||
```
|
|
||||||
|
|
||||||
**Headless Server Mode:**
|
|
||||||
```bash
|
|
||||||
./SHinterface -m -e
|
|
||||||
```
|
|
||||||
|
|
||||||
## Features
|
|
||||||
|
|
||||||
- ✅ Control relays and PWM outputs
|
|
||||||
- ✅ Monitor various sensors (temperature, humidity, doors, etc.)
|
|
||||||
- ✅ MQTT sensor integration
|
|
||||||
- ✅ Actor-based automation system
|
|
||||||
- ✅ RGB lighting control
|
|
||||||
- ✅ WebSocket API for remote access
|
|
||||||
- ✅ Primary/secondary architecture for distributed control
|
|
||||||
|
|
||||||
## Configuration
|
|
||||||
|
|
||||||
Settings are stored in JSON format. Default location:
|
|
||||||
```
|
|
||||||
~/.config/shinterface.json
|
|
||||||
```
|
|
||||||
|
|
||||||
You can specify a custom config file with:
|
|
||||||
```bash
|
|
||||||
./SHinterface -c /path/to/config.json
|
|
||||||
```
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
### Primary Mode (Master)
|
|
||||||
The primary instance connects to your microcontroller and manages all devices. It can run with or without a GUI.
|
|
||||||
|
|
||||||
### Secondary Mode (Client)
|
|
||||||
Secondary instances connect to the primary via TCP and provide additional control points without needing their own microcontroller connection.
|
|
||||||
|
|
||||||
## Command Line Options
|
|
||||||
|
|
||||||
```
|
|
||||||
-m, --master Use in master mode
|
|
||||||
-H, --host <address> Set server host IP address (default: 0.0.0.0)
|
|
||||||
-p, --port <port> Set server port (default: 38940)
|
|
||||||
-c, --config <file> Set config file path
|
|
||||||
-e, --headless Don't start the GUI (master mode only)
|
|
||||||
--help Show help
|
|
||||||
--version Show version
|
|
||||||
```
|
|
||||||
|
|
||||||
## Architecture
|
|
||||||
|
|
||||||
- **Items**: Represent controllable devices (relays, lights, etc.)
|
|
||||||
- **Actors**: Automate actions based on sensors or time
|
|
||||||
- **Sensors**: Provide data from physical sensors and calculated sources
|
|
||||||
- **Services**: TCP and WebSocket interfaces for remote control
|
|
||||||
|
|
||||||
## Dependencies
|
|
||||||
|
|
||||||
- CMake 4.0+
|
|
||||||
- Qt6 (Core, Gui, Widgets, Network, Multimedia, SerialPort, Mqtt, WebSockets)
|
|
||||||
- libpipewire-0.3
|
|
||||||
- libnl-3.0
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
See LICENSE file for details.
|
|
||||||
BIN
UVOSicon.bmp
Normal file
BIN
UVOSicon.bmp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 112 KiB |
529
remote.html
529
remote.html
|
|
@ -1,529 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>SHInterface Control</title>
|
|
||||||
<link rel="icon" type="image/x-icon" href="UVOSicon.bmp">
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
|
||||||
margin: 0;
|
|
||||||
padding: 15px;
|
|
||||||
background-color: #32343d;
|
|
||||||
overflow-x: hidden;
|
|
||||||
}
|
|
||||||
.container {
|
|
||||||
max-width: 600px;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
h1 {
|
|
||||||
color: #333;
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
.status {
|
|
||||||
padding: 10px 30px 10px 15px;
|
|
||||||
border-radius: 5px;
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
margin-top: 0px;
|
|
||||||
font-weight: bold;
|
|
||||||
cursor: pointer;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
.status::after {
|
|
||||||
content: '↻';
|
|
||||||
position: absolute;
|
|
||||||
right: 15px;
|
|
||||||
top: 50%;
|
|
||||||
transform: translateY(-50%);
|
|
||||||
}
|
|
||||||
.connected {
|
|
||||||
background-color: #d4edda;
|
|
||||||
color: #0F0F0F;
|
|
||||||
}
|
|
||||||
.disconnected {
|
|
||||||
background-color: #FF4733;
|
|
||||||
color: #0F0F0F;
|
|
||||||
}
|
|
||||||
.item-list {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
.item-card {
|
|
||||||
background: #707177;
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 15px;
|
|
||||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
.item-info {
|
|
||||||
flex-grow: 1;
|
|
||||||
}
|
|
||||||
.item-name {
|
|
||||||
font-weight: normal;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
.item-id {
|
|
||||||
font-size: 0.8em;
|
|
||||||
color: #c9c9c9;
|
|
||||||
}
|
|
||||||
.toggle-switch {
|
|
||||||
position: relative;
|
|
||||||
width: 50px;
|
|
||||||
height: 25px;
|
|
||||||
background-color: #ccc;
|
|
||||||
border-radius: 25px;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: background-color 0.3s;
|
|
||||||
}
|
|
||||||
.toggle-switch.active {
|
|
||||||
background-color: #313665;
|
|
||||||
}
|
|
||||||
.toggle-knob {
|
|
||||||
position: absolute;
|
|
||||||
top: 2px;
|
|
||||||
left: 2px;
|
|
||||||
width: 21px;
|
|
||||||
height: 21px;
|
|
||||||
background-color: white;
|
|
||||||
border-radius: 50%;
|
|
||||||
transition: transform 0.3s;
|
|
||||||
}
|
|
||||||
.toggle-switch.active .toggle-knob {
|
|
||||||
transform: translateX(25px);
|
|
||||||
}
|
|
||||||
.tab-container {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
height: calc(100vh - 85px);
|
|
||||||
}
|
|
||||||
.tabs {
|
|
||||||
display: flex;
|
|
||||||
background-color: #4a4c56;
|
|
||||||
border-radius: 8px 8px 0 0;
|
|
||||||
overflow: hidden;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
.tab {
|
|
||||||
flex: 1;
|
|
||||||
padding: 12px;
|
|
||||||
text-align: center;
|
|
||||||
cursor: pointer;
|
|
||||||
background-color: #4a4c56;
|
|
||||||
color: #ccc;
|
|
||||||
transition: all 0.3s;
|
|
||||||
border-bottom: 3px solid transparent;
|
|
||||||
}
|
|
||||||
.tab.active {
|
|
||||||
background-color: #707177;
|
|
||||||
color: white;
|
|
||||||
border-bottom-color: #4CAF50;
|
|
||||||
}
|
|
||||||
.tab-content {
|
|
||||||
display: none;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 10px;
|
|
||||||
overflow-y: auto;
|
|
||||||
height: 100%;
|
|
||||||
transition: transform 0.3s ease-in-out;
|
|
||||||
}
|
|
||||||
.tab-content.active {
|
|
||||||
display: flex;
|
|
||||||
transform: translateX(0);
|
|
||||||
}
|
|
||||||
.item-list, .sensor-list {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 10px;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
.sensor-card {
|
|
||||||
background: #707177;
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 12px;
|
|
||||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
.sensor-info {
|
|
||||||
font-size: 0.8em;
|
|
||||||
color: #c9c9c9;
|
|
||||||
}
|
|
||||||
.sensor-value-container {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-end;
|
|
||||||
}
|
|
||||||
.sensor-name {
|
|
||||||
font-weight: normal;
|
|
||||||
margin-bottom: 3px;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
.sensor-value {
|
|
||||||
font-size: 1.2em;
|
|
||||||
font-weight: bold;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
.refresh-btn {
|
|
||||||
display: block;
|
|
||||||
margin: 20px auto;
|
|
||||||
padding: 10px 20px;
|
|
||||||
background-color: #4CAF50;
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
border-radius: 5px;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
.refresh-btn:hover {
|
|
||||||
background-color: #45a049;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="container">
|
|
||||||
<div class="status disconnected" id="status" onclick="connectAndRefresh()">Disconnected</div>
|
|
||||||
|
|
||||||
<div class="tab-container">
|
|
||||||
<div class="tabs">
|
|
||||||
<div class="tab active" onclick="switchTab(0)">Items</div>
|
|
||||||
<div class="tab" onclick="switchTab(1)">Sensors</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="items-tab" class="tab-content active">
|
|
||||||
<div class="item-list" id="itemList"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="sensors-tab" class="tab-content">
|
|
||||||
<div class="sensor-list" id="sensorList"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
let socket;
|
|
||||||
let items = {};
|
|
||||||
let sensors = {};
|
|
||||||
const wsUrl = 'https://' + 'local.uvos.xyz/shws';
|
|
||||||
|
|
||||||
function connectAndRefresh() {
|
|
||||||
if (socket && socket.readyState === WebSocket.OPEN) {
|
|
||||||
refreshItems();
|
|
||||||
refreshSensors();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
socket = new WebSocket(wsUrl);
|
|
||||||
|
|
||||||
socket.onopen = function(e) {
|
|
||||||
console.log('Connected to WebSocket');
|
|
||||||
document.getElementById('status').className = 'status connected';
|
|
||||||
document.getElementById('status').textContent = 'Connected';
|
|
||||||
refreshItems();
|
|
||||||
refreshSensors();
|
|
||||||
};
|
|
||||||
|
|
||||||
socket.onclose = function(e) {
|
|
||||||
console.log('Disconnected from WebSocket');
|
|
||||||
document.getElementById('status').className = 'status disconnected';
|
|
||||||
document.getElementById('status').textContent = 'Disconnected';
|
|
||||||
};
|
|
||||||
|
|
||||||
socket.onerror = function(e) {
|
|
||||||
console.error('WebSocket error:', e);
|
|
||||||
document.getElementById('status').className = 'status disconnected';
|
|
||||||
document.getElementById('status').textContent = 'Error: ' + e.message;
|
|
||||||
};
|
|
||||||
|
|
||||||
socket.onmessage = function(event) {
|
|
||||||
console.log('Message received:', event.data);
|
|
||||||
try {
|
|
||||||
const json = JSON.parse(event.data);
|
|
||||||
handleMessage(json);
|
|
||||||
} catch (e) {
|
|
||||||
console.error('Error parsing JSON:', e);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function refreshItems() {
|
|
||||||
if (socket && socket.readyState === WebSocket.OPEN) {
|
|
||||||
const message = {
|
|
||||||
MessageType: 'GetItems',
|
|
||||||
Data: []
|
|
||||||
};
|
|
||||||
socket.send(JSON.stringify(message));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function refreshSensors() {
|
|
||||||
if (socket && socket.readyState === WebSocket.OPEN) {
|
|
||||||
const message = {
|
|
||||||
MessageType: 'GetSensors',
|
|
||||||
Data: []
|
|
||||||
};
|
|
||||||
socket.send(JSON.stringify(message));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleMessage(json) {
|
|
||||||
if (json.MessageType === 'ItemUpdate') {
|
|
||||||
const fullList = json.FullList || false;
|
|
||||||
|
|
||||||
if (fullList) {
|
|
||||||
items = {}; // Clear existing items
|
|
||||||
json.Data.forEach(item => {
|
|
||||||
items[item.ItemId] = item;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
json.Data.forEach(item => {
|
|
||||||
items[item.ItemId].Value = item.Value;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
renderItems();
|
|
||||||
} else if (json.MessageType === 'SensorUpdate') {
|
|
||||||
const fullList = json.FullList || false;
|
|
||||||
|
|
||||||
if (fullList) {
|
|
||||||
sensors = {}; // Clear existing sensors
|
|
||||||
}
|
|
||||||
|
|
||||||
json.Data.forEach(sensor => {
|
|
||||||
const key = `${sensor.SensorType}-${sensor.Id}`;
|
|
||||||
sensors[key] = sensor;
|
|
||||||
});
|
|
||||||
|
|
||||||
renderSensors();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderItems() {
|
|
||||||
const itemList = document.getElementById('itemList');
|
|
||||||
itemList.innerHTML = '';
|
|
||||||
|
|
||||||
if (Object.keys(items).length === 0) {
|
|
||||||
itemList.innerHTML = '<div style="text-align: center; color: #666; padding: 20px;">No items found</div>';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Object.values(items).forEach(item => {
|
|
||||||
const itemCard = document.createElement('div');
|
|
||||||
itemCard.className = 'item-card';
|
|
||||||
|
|
||||||
const value = item.Value || 0;
|
|
||||||
const type = item.ValueType || 0; // Default to BOOL
|
|
||||||
|
|
||||||
let controlHtml = '';
|
|
||||||
|
|
||||||
switch (type) {
|
|
||||||
case 0: // ITEM_VALUE_BOOL
|
|
||||||
const isActive = value > 0;
|
|
||||||
controlHtml = `
|
|
||||||
<div class="toggle-switch ${isActive ? 'active' : ''}"
|
|
||||||
onclick="toggleItem(${item.ItemId}, ${!isActive})">
|
|
||||||
<div class="toggle-knob"></div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
break;
|
|
||||||
case 1: // ITEM_VALUE_UINT
|
|
||||||
controlHtml = `
|
|
||||||
<input type="number"
|
|
||||||
value="${value}"
|
|
||||||
min="0" max="255"
|
|
||||||
onchange="setUintItem(${item.ItemId}, this.value)"
|
|
||||||
style="width: 80px; padding: 5px; border-radius: 5px; border: 1px solid #ddd;">
|
|
||||||
`;
|
|
||||||
break;
|
|
||||||
case 2: // ITEM_VALUE_NO_VALUE
|
|
||||||
controlHtml = '<div style="color: #b9b9b9;">No control</div>';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
itemCard.innerHTML = `
|
|
||||||
<div class="item-info">
|
|
||||||
<div class="item-name">${item.Name}</div>
|
|
||||||
<div class="item-id">ItemId: ${item.ItemId} | Type: ${getTypeName(type)}</div>
|
|
||||||
</div>
|
|
||||||
${controlHtml}
|
|
||||||
`;
|
|
||||||
|
|
||||||
itemList.appendChild(itemCard);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleItem(itemId, newValue) {
|
|
||||||
if (!socket || socket.readyState !== WebSocket.OPEN) {
|
|
||||||
alert('Not connected to server');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const message = {
|
|
||||||
MessageType: 'ItemUpdate',
|
|
||||||
Data: [{
|
|
||||||
ItemId: itemId,
|
|
||||||
Value: newValue ? 1 : 0
|
|
||||||
}],
|
|
||||||
FullList: false
|
|
||||||
};
|
|
||||||
|
|
||||||
socket.send(JSON.stringify(message));
|
|
||||||
}
|
|
||||||
|
|
||||||
function setUintItem(itemId, value) {
|
|
||||||
if (!socket || socket.readyState !== WebSocket.OPEN) {
|
|
||||||
alert('Not connected to server');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert to integer and clamp to 0-255
|
|
||||||
const intValue = Math.min(255, Math.max(0, parseInt(value) || 0));
|
|
||||||
|
|
||||||
const message = {
|
|
||||||
MessageType: 'ItemUpdate',
|
|
||||||
Data: [{
|
|
||||||
ItemId: itemId,
|
|
||||||
Value: intValue
|
|
||||||
}],
|
|
||||||
FullList: false
|
|
||||||
};
|
|
||||||
|
|
||||||
socket.send(JSON.stringify(message));
|
|
||||||
}
|
|
||||||
|
|
||||||
function getTypeName(type) {
|
|
||||||
switch (type) {
|
|
||||||
case 0: return 'BOOL';
|
|
||||||
case 1: return 'UINT';
|
|
||||||
case 2: return 'NO_VALUE';
|
|
||||||
default: return 'UNKNOWN';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function switchTab(tabIndex) {
|
|
||||||
const tabs = document.querySelectorAll('.tab');
|
|
||||||
const tabContents = document.querySelectorAll('.tab-content');
|
|
||||||
|
|
||||||
tabs.forEach((tab, index) => {
|
|
||||||
tab.classList.toggle('active', index === tabIndex);
|
|
||||||
});
|
|
||||||
|
|
||||||
tabContents.forEach((content, index) => {
|
|
||||||
content.classList.toggle('active', index === tabIndex);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderSensors() {
|
|
||||||
const sensorList = document.getElementById('sensorList');
|
|
||||||
sensorList.innerHTML = '';
|
|
||||||
|
|
||||||
if (Object.keys(sensors).length === 0) {
|
|
||||||
sensorList.innerHTML = '<div style="text-align: center; color: #666; padding: 20px;">No sensors found</div>';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Object.values(sensors).forEach(sensor => {
|
|
||||||
const sensorCard = document.createElement('div');
|
|
||||||
sensorCard.className = 'sensor-card';
|
|
||||||
|
|
||||||
let typeName = '';
|
|
||||||
switch (sensor.SensorType) {
|
|
||||||
case 0: typeName = 'Door'; break;
|
|
||||||
case 1: typeName = 'Temperature'; break;
|
|
||||||
case 2: typeName = 'Humidity'; break;
|
|
||||||
case 3: typeName = 'Pressure'; break;
|
|
||||||
case 4: typeName = 'Brightness'; break;
|
|
||||||
case 5: typeName = 'Button'; break;
|
|
||||||
case 6: typeName = 'ADC'; break;
|
|
||||||
case 7: typeName = 'CO2'; break;
|
|
||||||
case 8: typeName = 'Formaldehyde'; break;
|
|
||||||
case 9: typeName = 'PM2.5'; break;
|
|
||||||
case 10: typeName = 'Total VOC'; break;
|
|
||||||
case 16: typeName = 'Occupancy'; break;
|
|
||||||
case 17: typeName = 'Sun Altitude'; break;
|
|
||||||
default: typeName = 'Unknown';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Truncate to 2 decimal places
|
|
||||||
const fieldValue = Number(sensor.Field).toFixed(2);
|
|
||||||
|
|
||||||
sensorCard.innerHTML = `
|
|
||||||
<div>
|
|
||||||
<div class="sensor-name">${sensor.Name}</div>
|
|
||||||
<div class="sensor-info">Type: ${typeName} | ID: ${sensor.Id}</div>
|
|
||||||
</div>
|
|
||||||
<div class="sensor-value-container">
|
|
||||||
<div class="sensor-value">
|
|
||||||
${fieldValue} ${sensor.Unit || ''}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
sensorList.appendChild(sensorCard);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function getTypeName(type) {
|
|
||||||
switch (type) {
|
|
||||||
case 0: return 'BOOL';
|
|
||||||
case 1: return 'UINT';
|
|
||||||
case 2: return 'NO_VALUE';
|
|
||||||
default: return 'UNKNOWN';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Touch/swipe support for mobile
|
|
||||||
let touchStartX = -1;
|
|
||||||
let touchEndX = -1;
|
|
||||||
|
|
||||||
function handleTouchStart(event) {
|
|
||||||
touchStartX = event.changedTouches[0].screenX;
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleTouchMove(event) {
|
|
||||||
touchEndX = event.changedTouches[0].screenX;
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleTouchEnd() {
|
|
||||||
const diff = touchStartX - touchEndX;
|
|
||||||
if (touchStartX > 0 && touchEndX > 0) {
|
|
||||||
if (diff > 100) { // Swipe left
|
|
||||||
const activeTab = document.querySelector('.tab.active');
|
|
||||||
const tabIndex = Array.from(document.querySelectorAll('.tab')).indexOf(activeTab);
|
|
||||||
if (tabIndex < 1) {
|
|
||||||
switchTab(tabIndex + 1);
|
|
||||||
}
|
|
||||||
} else if (diff < -100) { // Swipe right
|
|
||||||
const activeTab = document.querySelector('.tab.active');
|
|
||||||
const tabIndex = Array.from(document.querySelectorAll('.tab')).indexOf(activeTab);
|
|
||||||
if (tabIndex > 0) {
|
|
||||||
switchTab(tabIndex - 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
touchStartX = -1
|
|
||||||
touchEndX = -1
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add touch event listeners
|
|
||||||
document.addEventListener('touchstart', handleTouchStart, false);
|
|
||||||
document.addEventListener('touchmove', handleTouchMove, false);
|
|
||||||
document.addEventListener('touchend', handleTouchEnd, false);
|
|
||||||
|
|
||||||
// Auto-connect when page loads
|
|
||||||
window.onload = function() {
|
|
||||||
setTimeout(connectAndRefresh, 500);
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<RCC>
|
<RCC>
|
||||||
<qresource prefix="/images">
|
<qresource prefix="/images">
|
||||||
<file>xyz.uvos.icon.bmp</file>
|
<file>UVOSicon.bmp</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
</RCC>
|
</RCC>
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,6 @@ void Actor::performValueAction(uint8_t value)
|
||||||
ItemUpdateRequest request;
|
ItemUpdateRequest request;
|
||||||
request.type = ITEM_UPDATE_ACTOR;
|
request.type = ITEM_UPDATE_ACTOR;
|
||||||
request.payload = ItemData(QRandomGenerator::global()->generate(), "Item", value);
|
request.payload = ItemData(QRandomGenerator::global()->generate(), "Item", value);
|
||||||
request.changes.value = true;
|
|
||||||
sigItemUpdate(request);
|
sigItemUpdate(request);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -48,12 +47,9 @@ void Actor::makeInactive()
|
||||||
QString Actor::actionName()
|
QString Actor::actionName()
|
||||||
{
|
{
|
||||||
QString string;
|
QString string;
|
||||||
if(triggerValue == 0 )
|
if(triggerValue == 0 ) string = "off";
|
||||||
string = "off";
|
else if(triggerValue == 1 ) string = "on";
|
||||||
else if(triggerValue == 1 )
|
else string = "value to " + QString::number(triggerValue);
|
||||||
string = "on";
|
|
||||||
else
|
|
||||||
string = "value to " + QString::number(triggerValue);
|
|
||||||
return string;
|
return string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -39,13 +39,13 @@ public:
|
||||||
virtual void load(const QJsonObject& json, const bool preserve = false);
|
virtual void load(const QJsonObject& json, const bool preserve = false);
|
||||||
|
|
||||||
uint8_t getRepeat();
|
uint8_t getRepeat();
|
||||||
virtual QString getName() const;
|
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
|
|
||||||
void run();
|
void run();
|
||||||
virtual void makeActive();
|
virtual void makeActive();
|
||||||
virtual void makeInactive();
|
virtual void makeInactive();
|
||||||
|
virtual QString getName() const;
|
||||||
void doTick();
|
void doTick();
|
||||||
void changeTime(const QDateTime& time);
|
void changeTime(const QDateTime& time);
|
||||||
void setRepeat(const uint8_t repeat);
|
void setRepeat(const uint8_t repeat);
|
||||||
|
|
|
||||||
|
|
@ -7,13 +7,11 @@ TimerActor::TimerActor(const int timeoutSec, QObject *parent): Actor(parent), ti
|
||||||
timer.setSingleShot(true);
|
timer.setSingleShot(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TimerActor::onItemUpdated(ItemUpdateRequest update)
|
void TimerActor::onValueChanged(uint8_t state)
|
||||||
{
|
{
|
||||||
if(update.changes.value && ((update.payload.getValue() && !triggerValue) || (!update.payload.getValue() && triggerValue)))
|
if((state && !triggerValue) || (!state && triggerValue))
|
||||||
{
|
{
|
||||||
qDebug()<<"Timer started";
|
if(timer.isActive()) timer.stop();
|
||||||
if(timer.isActive())
|
|
||||||
timer.stop();
|
|
||||||
timer.setInterval(timeoutMsec_);
|
timer.setInterval(timeoutMsec_);
|
||||||
timer.start();
|
timer.start();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,15 +16,15 @@ private slots:
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
|
|
||||||
virtual void onItemUpdated(ItemUpdateRequest update) override;
|
virtual void onValueChanged(uint8_t state);
|
||||||
void setTimeout(const int timeoutSec);
|
void setTimeout(const int timeoutSec);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit TimerActor(const int timeoutSec = 60, QObject *parent = nullptr);
|
explicit TimerActor(const int timeoutSec = 60, QObject *parent = nullptr);
|
||||||
virtual QString getName() const override;
|
virtual QString getName() const;
|
||||||
|
|
||||||
int getTimeout();
|
int getTimeout();
|
||||||
|
|
||||||
virtual void store(QJsonObject& json) override;
|
virtual void store(QJsonObject& json);
|
||||||
virtual void load(const QJsonObject& json, bool preserve) override;
|
virtual void load(const QJsonObject& json, bool preserve);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -10,19 +10,5 @@ FixedItemSource::FixedItemSource(Microcontroller* micro, QObject *parent):
|
||||||
|
|
||||||
void FixedItemSource::refresh()
|
void FixedItemSource::refresh()
|
||||||
{
|
{
|
||||||
std::vector<ItemAddRequest> requests;
|
gotItems({powerItem, rgbItem, auxItem}, ITEM_UPDATE_BACKEND);
|
||||||
|
|
||||||
ItemAddRequest request;
|
|
||||||
request.type = ITEM_UPDATE_BACKEND;
|
|
||||||
|
|
||||||
request.payload = powerItem;
|
|
||||||
requests.push_back(request);
|
|
||||||
|
|
||||||
request.payload = rgbItem;
|
|
||||||
requests.push_back(request);
|
|
||||||
|
|
||||||
request.payload = auxItem;
|
|
||||||
requests.push_back(request);
|
|
||||||
|
|
||||||
gotItems(requests);
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,12 +10,11 @@
|
||||||
#include "auxitem.h"
|
#include "auxitem.h"
|
||||||
#include "poweritem.h"
|
#include "poweritem.h"
|
||||||
#include "rgbitem.h"
|
#include "rgbitem.h"
|
||||||
#include "mqttitem.h"
|
|
||||||
|
|
||||||
#include <QJsonArray>
|
#include <QJsonArray>
|
||||||
|
|
||||||
ItemData::ItemData(uint32_t itemIdIn, QString name, uint8_t value, bool loaded, bool hidden, item_value_type_t type, QString groupName):
|
ItemData::ItemData(uint32_t itemIdIn, QString name, uint8_t value, bool loaded, bool hidden, item_value_type_t type):
|
||||||
name_(name), value_(value), itemId_(itemIdIn), loaded_(loaded), hidden_(hidden), type_(type), groupName_(groupName)
|
name_(name), value_(value), itemId_(itemIdIn), loaded_(loaded), hidden_(hidden), type_(type)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -47,69 +46,19 @@ uint32_t ItemData::id() const
|
||||||
|
|
||||||
void ItemData::store(QJsonObject &json)
|
void ItemData::store(QJsonObject &json)
|
||||||
{
|
{
|
||||||
storeWithChanges(json, ItemFieldChanges(true));
|
json["Name"] = name_;
|
||||||
}
|
|
||||||
|
|
||||||
void ItemData::storeWithChanges(QJsonObject& json, const ItemFieldChanges& changes)
|
|
||||||
{
|
|
||||||
json["ItemId"] = static_cast<double>(itemId_);
|
json["ItemId"] = static_cast<double>(itemId_);
|
||||||
json["ValueType"] = type_;
|
json["Value"] = static_cast<double>(value_);
|
||||||
if(changes.name)
|
|
||||||
json["Name"] = name_;
|
|
||||||
if(changes.value)
|
|
||||||
json["Value"] = static_cast<double>(value_);
|
|
||||||
if(changes.groupName)
|
|
||||||
json["GroupName"] = groupName_;
|
|
||||||
if(changes.valueNames)
|
|
||||||
{
|
|
||||||
QJsonArray valueNamesArray;
|
|
||||||
for(const QString& name : valueNames_)
|
|
||||||
valueNamesArray.append(name);
|
|
||||||
json["ValueNames"] = valueNamesArray;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ItemData::load(const QJsonObject &json, const bool preserve)
|
void ItemData::load(const QJsonObject &json, const bool preserve)
|
||||||
{
|
{
|
||||||
loadWithChanges(json, preserve);
|
|
||||||
}
|
|
||||||
|
|
||||||
ItemFieldChanges ItemData::loadWithChanges(const QJsonObject& json, const bool preserve)
|
|
||||||
{
|
|
||||||
ItemFieldChanges changes;
|
|
||||||
if(!preserve)
|
if(!preserve)
|
||||||
{
|
{
|
||||||
if(json.contains("Name"))
|
name_ = json["Name"].toString(name_);
|
||||||
{
|
|
||||||
name_ = json["Name"].toString();
|
|
||||||
changes.name = true;
|
|
||||||
}
|
|
||||||
if(json.contains("Value"))
|
|
||||||
{
|
|
||||||
value_ = json["Value"].toInt();
|
|
||||||
changes.value = true;
|
|
||||||
}
|
|
||||||
if(json.contains("GroupName"))
|
|
||||||
{
|
|
||||||
groupName_ = json["GroupName"].toString();
|
|
||||||
changes.groupName = true;
|
|
||||||
}
|
|
||||||
if(json.contains("ValueType"))
|
|
||||||
{
|
|
||||||
type_ = static_cast<item_value_type_t>(json["ValueType"].toInt());
|
|
||||||
changes.type = true;
|
|
||||||
}
|
|
||||||
if(json.contains("ValueNames"))
|
|
||||||
{
|
|
||||||
valueNames_.clear();
|
|
||||||
QJsonArray valueNamesArray = json["ValueNames"].toArray();
|
|
||||||
for(int i = 0; i < valueNamesArray.size(); ++i)
|
|
||||||
valueNames_.push_back(valueNamesArray[i].toString());
|
|
||||||
changes.valueNames = true;
|
|
||||||
}
|
|
||||||
itemId_ = static_cast<uint32_t>(json["ItemId"].toDouble(0));
|
itemId_ = static_cast<uint32_t>(json["ItemId"].toDouble(0));
|
||||||
|
value_ = json["Value"].toInt();
|
||||||
}
|
}
|
||||||
return changes;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ItemData::getLoaded() const
|
bool ItemData::getLoaded() const
|
||||||
|
|
@ -122,25 +71,15 @@ void ItemData::setLoaded(bool loaded)
|
||||||
loaded_ = loaded;
|
loaded_ = loaded;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ItemData::hasChanged(const ItemData& other) const
|
bool ItemData::hasChanged(const ItemData& other)
|
||||||
{
|
{
|
||||||
ItemFieldChanges changes(true);
|
if(other != *this)
|
||||||
return hasChanged(other, changes);
|
return false;
|
||||||
}
|
if(other.getName() != getName())
|
||||||
|
|
||||||
bool ItemData::hasChanged(const ItemData& other, const ItemFieldChanges& changes) const
|
|
||||||
{
|
|
||||||
if(changes.name && other.getName() != getName())
|
|
||||||
return true;
|
return true;
|
||||||
if(changes.value && other.getValue() != getValue())
|
if(other.getValue() != getValue())
|
||||||
return true;
|
return true;
|
||||||
if(changes.hidden && other.isHidden() != isHidden())
|
if(other.getLoaded() != getLoaded())
|
||||||
return true;
|
|
||||||
if(changes.groupName && other.getGroupName() != getGroupName())
|
|
||||||
return true;
|
|
||||||
if(changes.actors)
|
|
||||||
return true;
|
|
||||||
if(changes.valueNames && other.getValueNames() != getValueNames())
|
|
||||||
return true;
|
return true;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -160,47 +99,10 @@ item_value_type_t ItemData::getValueType()
|
||||||
return type_;
|
return type_;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString ItemData::getGroupName() const
|
|
||||||
{
|
|
||||||
return groupName_;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ItemData::setGroupName(QString groupName)
|
|
||||||
{
|
|
||||||
groupName_ = groupName;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<QString> ItemData::getValueNames() const
|
|
||||||
{
|
|
||||||
return valueNames_;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ItemData::setValueNames(std::vector<QString> valueNames)
|
|
||||||
{
|
|
||||||
valueNames_ = std::move(valueNames);
|
|
||||||
}
|
|
||||||
|
|
||||||
int ItemData::valueNameToIndex(const QString& name) const
|
|
||||||
{
|
|
||||||
for(size_t i = 0; i < valueNames_.size(); ++i)
|
|
||||||
{
|
|
||||||
if(valueNames_[i] == name)
|
|
||||||
return static_cast<int>(i);
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString ItemData::indexToValueName(int index) const
|
|
||||||
{
|
|
||||||
if(index >= 0 && static_cast<size_t>(index) < valueNames_.size())
|
|
||||||
return valueNames_[index];
|
|
||||||
return QString();
|
|
||||||
}
|
|
||||||
|
|
||||||
//item
|
//item
|
||||||
|
|
||||||
Item::Item(uint32_t itemIdIn, QString name, uint8_t value, QObject *parent): QObject(parent), ItemData (itemIdIn, name,
|
Item::Item(uint32_t itemIdIn, QString name, uint8_t value, QObject *parent): QObject(parent), ItemData (itemIdIn, name,
|
||||||
value, false, false, ITEM_VALUE_BOOL, "All")
|
value)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -229,6 +131,7 @@ void Item::store(QJsonObject &json)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
json["Actors"] = actorsArray;
|
json["Actors"] = actorsArray;
|
||||||
|
json["ValueType"] = type_;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Item::load(const QJsonObject &json, const bool preserve)
|
void Item::load(const QJsonObject &json, const bool preserve)
|
||||||
|
|
@ -253,22 +156,13 @@ Item& Item::operator=(const ItemData& other)
|
||||||
value_ = other.getValue();
|
value_ = other.getValue();
|
||||||
itemId_ = other.id();
|
itemId_ = other.id();
|
||||||
hidden_ = other.isHidden();
|
hidden_ = other.isHidden();
|
||||||
groupName_ = other.getGroupName();
|
|
||||||
valueNames_ = other.getValueNames();
|
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Item::requestUpdate(ItemUpdateRequest update)
|
void Item::requestUpdate(ItemUpdateRequest update)
|
||||||
{
|
{
|
||||||
assert(update.type != ITEM_UPDATE_INVALID);
|
assert(update.type != ITEM_UPDATE_INVALID);
|
||||||
assert(!update.changes.isNone());
|
if(update.type != ITEM_UPDATE_LOADED && value_ == update.payload.getValue())
|
||||||
|
|
||||||
if(update.type == ITEM_UPDATE_LOADED)
|
|
||||||
{
|
|
||||||
qDebug()<<__func__<<update.changes.actors<<update.newActors.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!hasChanged(update.payload, update.changes))
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if(update.type == ITEM_UPDATE_ACTOR && override_)
|
if(update.type == ITEM_UPDATE_ACTOR && override_)
|
||||||
|
|
@ -276,30 +170,24 @@ void Item::requestUpdate(ItemUpdateRequest update)
|
||||||
|
|
||||||
qDebug()<<"Item Update Request for"<<getName()<<" type "<<update.type<<" value "<<update.payload.getValue();
|
qDebug()<<"Item Update Request for"<<getName()<<" type "<<update.type<<" value "<<update.payload.getValue();
|
||||||
|
|
||||||
if(update.type != ITEM_UPDATE_LOADED && update.type != ITEM_UPDATE_BACKEND &&
|
if(update.type != ITEM_UPDATE_LOADED &&
|
||||||
(programMode == PROGRAM_MODE_PRIMARY || programMode == PROGRAM_MODE_HEADLESS_PRIMARY) &&
|
update.type != ITEM_UPDATE_BACKEND &&
|
||||||
update.changes.value)
|
(programMode == PROGRAM_MODE_PRIMARY || programMode == PROGRAM_MODE_HEADLESS_PRIMARY))
|
||||||
enactValue(update.payload.getValue());
|
enactValue(update.payload.getValue());
|
||||||
|
|
||||||
|
if(update.type != ITEM_UPDATE_LOADED)
|
||||||
if(update.changes.value)
|
|
||||||
value_ = update.payload.getValue();
|
|
||||||
if(update.changes.name)
|
|
||||||
name_ = update.payload.getName();
|
|
||||||
if(update.changes.hidden)
|
|
||||||
hidden_ = update.payload.isHidden();
|
|
||||||
if(update.changes.groupName)
|
|
||||||
groupName_ = update.payload.getGroupName();
|
|
||||||
if(update.changes.type)
|
|
||||||
type_ = update.payload.getValueType();
|
|
||||||
if(update.changes.actors)
|
|
||||||
{
|
{
|
||||||
|
value_ = update.payload.getValue();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
name_ = update.payload.getName();
|
||||||
|
//itemId_ = update.payload.id();
|
||||||
|
hidden_ = update.payload.isHidden();
|
||||||
actors_.clear();
|
actors_.clear();
|
||||||
for(std::shared_ptr<Actor>& actor : update.newActors)
|
for(std::shared_ptr<Actor>& actor : update.newActors)
|
||||||
addActor(actor);
|
addActor(actor);
|
||||||
}
|
}
|
||||||
if(update.changes.valueNames)
|
|
||||||
valueNames_ = update.payload.getValueNames();
|
|
||||||
update.payload = *this;
|
update.payload = *this;
|
||||||
updated(update);
|
updated(update);
|
||||||
}
|
}
|
||||||
|
|
@ -391,8 +279,6 @@ std::shared_ptr<Item> Item::loadItem(const QJsonObject& json)
|
||||||
newItem = std::shared_ptr<PowerItem>(new PowerItem);
|
newItem = std::shared_ptr<PowerItem>(new PowerItem);
|
||||||
else if(json["Type"].toString("") == "Rgb")
|
else if(json["Type"].toString("") == "Rgb")
|
||||||
newItem = std::shared_ptr<RgbItem>(new RgbItem);
|
newItem = std::shared_ptr<RgbItem>(new RgbItem);
|
||||||
else if(json["Type"].toString("") == "Mqtt")
|
|
||||||
newItem = std::shared_ptr<MqttItem>(new MqttItem);
|
|
||||||
else
|
else
|
||||||
qWarning()<<"Unable to load unkown item type: "<<json["Type"].toString();
|
qWarning()<<"Unable to load unkown item type: "<<json["Type"].toString();
|
||||||
if(newItem)
|
if(newItem)
|
||||||
|
|
@ -403,12 +289,14 @@ std::shared_ptr<Item> Item::loadItem(const QJsonObject& json)
|
||||||
return newItem;
|
return newItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
ItemUpdateRequest Item::createValueUpdateRequest(item_update_type_t type,
|
ItemUpdateRequest Item::createValueUpdateRequest(uint8_t value,
|
||||||
|
item_update_type_t type,
|
||||||
bool withActors)
|
bool withActors)
|
||||||
{
|
{
|
||||||
ItemUpdateRequest update;
|
ItemUpdateRequest update;
|
||||||
update.type = type;
|
update.type = type;
|
||||||
update.payload = *this;
|
update.payload = *this;
|
||||||
|
update.payload.setValueData(value);
|
||||||
if(withActors)
|
if(withActors)
|
||||||
update.newActors = actors_;
|
update.newActors = actors_;
|
||||||
return update;
|
return update;
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,7 @@ class Actor;
|
||||||
typedef enum {
|
typedef enum {
|
||||||
ITEM_VALUE_BOOL = 0,
|
ITEM_VALUE_BOOL = 0,
|
||||||
ITEM_VALUE_UINT,
|
ITEM_VALUE_UINT,
|
||||||
ITEM_VALUE_NO_VALUE,
|
ITEM_VALUE_NO_VALUE
|
||||||
ITEM_VALUE_ENUM,
|
|
||||||
} item_value_type_t;
|
} item_value_type_t;
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
|
|
@ -24,9 +23,6 @@ typedef enum {
|
||||||
ITEM_UPDATE_INVALID
|
ITEM_UPDATE_INVALID
|
||||||
} item_update_type_t;
|
} item_update_type_t;
|
||||||
|
|
||||||
struct ItemFieldChanges;
|
|
||||||
struct ItemUpdateRequest;
|
|
||||||
|
|
||||||
class ItemData
|
class ItemData
|
||||||
{
|
{
|
||||||
protected:
|
protected:
|
||||||
|
|
@ -36,8 +32,6 @@ protected:
|
||||||
bool loaded_;
|
bool loaded_;
|
||||||
bool hidden_;
|
bool hidden_;
|
||||||
item_value_type_t type_;
|
item_value_type_t type_;
|
||||||
QString groupName_;
|
|
||||||
std::vector<QString> valueNames_;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
ItemData(uint32_t itemIdIn = QRandomGenerator::global()->generate(),
|
ItemData(uint32_t itemIdIn = QRandomGenerator::global()->generate(),
|
||||||
|
|
@ -45,8 +39,7 @@ public:
|
||||||
uint8_t value = 0,
|
uint8_t value = 0,
|
||||||
bool loaded = false,
|
bool loaded = false,
|
||||||
bool hidden = false,
|
bool hidden = false,
|
||||||
item_value_type_t type = ITEM_VALUE_BOOL,
|
item_value_type_t type = ITEM_VALUE_BOOL);
|
||||||
QString groupName = "");
|
|
||||||
|
|
||||||
inline bool operator==(const ItemData& in) const
|
inline bool operator==(const ItemData& in) const
|
||||||
{
|
{
|
||||||
|
|
@ -59,8 +52,7 @@ public:
|
||||||
|
|
||||||
uint32_t id() const;
|
uint32_t id() const;
|
||||||
|
|
||||||
bool hasChanged(const ItemData& other) const;
|
bool hasChanged(const ItemData& other);
|
||||||
bool hasChanged(const ItemData& other, const ItemFieldChanges& changes) const;
|
|
||||||
void setName(QString name);
|
void setName(QString name);
|
||||||
uint8_t getValue() const;
|
uint8_t getValue() const;
|
||||||
void setValueData(uint8_t value);
|
void setValueData(uint8_t value);
|
||||||
|
|
@ -69,19 +61,19 @@ public:
|
||||||
bool isHidden() const;
|
bool isHidden() const;
|
||||||
void setHidden(bool hidden);
|
void setHidden(bool hidden);
|
||||||
item_value_type_t getValueType();
|
item_value_type_t getValueType();
|
||||||
QString getGroupName() const;
|
|
||||||
void setGroupName(QString groupName);
|
|
||||||
std::vector<QString> getValueNames() const;
|
|
||||||
void setValueNames(std::vector<QString> valueNames);
|
|
||||||
int valueNameToIndex(const QString& name) const;
|
|
||||||
QString indexToValueName(int index) const;
|
|
||||||
void storeWithChanges(QJsonObject& json, const ItemFieldChanges& changes);
|
|
||||||
ItemFieldChanges loadWithChanges(const QJsonObject& json, const bool preserve = false);
|
|
||||||
virtual QString getName() const;
|
virtual QString getName() const;
|
||||||
virtual void store(QJsonObject& json);
|
virtual void store(QJsonObject& json);
|
||||||
virtual void load(const QJsonObject& json, const bool preserve = false);
|
virtual void load(const QJsonObject& json, const bool preserve = false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct ItemUpdateRequest
|
||||||
|
{
|
||||||
|
item_update_type_t type = ITEM_UPDATE_INVALID;
|
||||||
|
ItemData payload;
|
||||||
|
std::vector<std::shared_ptr<Actor> > newActors;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
class Item: public QObject, public ItemData
|
class Item: public QObject, public ItemData
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
@ -112,7 +104,8 @@ public:
|
||||||
void setActorsActive(bool in);
|
void setActorsActive(bool in);
|
||||||
void setOverride(const bool in);
|
void setOverride(const bool in);
|
||||||
bool getOverride();
|
bool getOverride();
|
||||||
ItemUpdateRequest createValueUpdateRequest(item_update_type_t type,
|
ItemUpdateRequest createValueUpdateRequest(uint8_t value,
|
||||||
|
item_update_type_t type,
|
||||||
bool withActors = false);
|
bool withActors = false);
|
||||||
|
|
||||||
virtual void store(QJsonObject& json);
|
virtual void store(QJsonObject& json);
|
||||||
|
|
@ -125,54 +118,3 @@ protected:
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
struct ItemFieldChanges
|
|
||||||
{
|
|
||||||
bool name :1;
|
|
||||||
bool value :1;
|
|
||||||
bool hidden :1;
|
|
||||||
bool type :1;
|
|
||||||
bool groupName :1;
|
|
||||||
bool actors :1;
|
|
||||||
bool valueNames :1;
|
|
||||||
ItemFieldChanges(bool defaultVal = false)
|
|
||||||
{
|
|
||||||
name = defaultVal;
|
|
||||||
value = defaultVal;
|
|
||||||
hidden = defaultVal;
|
|
||||||
type = defaultVal;
|
|
||||||
groupName = defaultVal;
|
|
||||||
actors = false;
|
|
||||||
valueNames = defaultVal;
|
|
||||||
}
|
|
||||||
inline bool isNone() const
|
|
||||||
{
|
|
||||||
return !name && !value && !hidden && !type && !groupName && !actors && !valueNames;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ItemUpdateRequest
|
|
||||||
{
|
|
||||||
item_update_type_t type = ITEM_UPDATE_INVALID;
|
|
||||||
ItemData payload;
|
|
||||||
ItemFieldChanges changes;
|
|
||||||
std::vector<std::shared_ptr<Actor> > newActors;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ItemAddRequest
|
|
||||||
{
|
|
||||||
item_update_type_t type = ITEM_UPDATE_INVALID;
|
|
||||||
std::shared_ptr<Item> payload;
|
|
||||||
ItemFieldChanges changes;
|
|
||||||
inline ItemUpdateRequest updateRequest() const
|
|
||||||
{
|
|
||||||
ItemUpdateRequest update;
|
|
||||||
update.payload = *payload;
|
|
||||||
update.type = type;
|
|
||||||
update.changes = changes;
|
|
||||||
if(changes.actors)
|
|
||||||
update.newActors = payload->getActors();
|
|
||||||
return update;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ ItemLoaderSource::ItemLoaderSource(const QJsonObject& json, QObject *parent):
|
||||||
|
|
||||||
void ItemLoaderSource::refresh()
|
void ItemLoaderSource::refresh()
|
||||||
{
|
{
|
||||||
std::vector<ItemAddRequest> itemAddRequests;
|
std::vector<std::shared_ptr<Item>> items;
|
||||||
const QJsonArray itemsArray(json["Items"].toArray());
|
const QJsonArray itemsArray(json["Items"].toArray());
|
||||||
for(int i = 0; i < itemsArray.size(); ++i)
|
for(int i = 0; i < itemsArray.size(); ++i)
|
||||||
{
|
{
|
||||||
|
|
@ -21,16 +21,11 @@ void ItemLoaderSource::refresh()
|
||||||
std::shared_ptr<Item> newItem = Item::loadItem(itemObject);
|
std::shared_ptr<Item> newItem = Item::loadItem(itemObject);
|
||||||
if(newItem)
|
if(newItem)
|
||||||
{
|
{
|
||||||
|
items.push_back(newItem);
|
||||||
qDebug()<<"Loaded item"<<newItem->getName();
|
qDebug()<<"Loaded item"<<newItem->getName();
|
||||||
ItemAddRequest request;
|
|
||||||
request.type = ITEM_UPDATE_LOADED;
|
|
||||||
request.payload = newItem;
|
|
||||||
request.changes = ItemFieldChanges(true);
|
|
||||||
request.changes.value = false;
|
|
||||||
itemAddRequests.push_back(request);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
gotItems(itemAddRequests);
|
gotItems(items, ITEM_UPDATE_LOADED);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ItemLoaderSource::updateJson(const QJsonObject& json)
|
void ItemLoaderSource::updateJson(const QJsonObject& json)
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ public slots:
|
||||||
virtual void refresh() = 0;
|
virtual void refresh() = 0;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void gotItems(std::vector<ItemAddRequest> items);
|
void gotItems(std::vector<std::shared_ptr<Item>> items, item_update_type_t updateType);
|
||||||
void requestReplaceItems(std::vector<std::shared_ptr<Item>> items);
|
void requestReplaceItems(std::vector<std::shared_ptr<Item>> items);
|
||||||
void updateItems(std::vector<ItemUpdateRequest> updates);
|
void updateItems(std::vector<ItemUpdateRequest> updates);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -6,38 +6,39 @@ ItemStore::ItemStore(QObject *parent): QObject(parent)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void ItemStore::addItem(const ItemAddRequest& item)
|
void ItemStore::addItem(const std::shared_ptr<Item>& item, item_update_type_t updateType)
|
||||||
{
|
{
|
||||||
qDebug()<<"Item add request for"<<item.payload->getName()<<item.payload->id();
|
|
||||||
std::shared_ptr<Item> matched = nullptr;
|
std::shared_ptr<Item> matched = nullptr;
|
||||||
for(unsigned i = 0; i < items_.size(); i++ )
|
for(unsigned i = 0; i < items_.size(); i++ )
|
||||||
{
|
{
|
||||||
if(*items_[i] == *item.payload)
|
if(*items_[i] == *item)
|
||||||
{
|
{
|
||||||
matched = items_[i];
|
matched = items_[i];
|
||||||
assert(matched->id() == items_[i]->id());
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(!matched)
|
if(!matched)
|
||||||
{
|
{
|
||||||
items_.push_back(item.payload);
|
items_.push_back(std::shared_ptr<Item>(item));
|
||||||
connect(item.payload.get(), &Item::updated, this, &ItemStore::itemUpdateSlot);
|
connect(item.get(), &Item::updated, this, &ItemStore::itemUpdateSlot);
|
||||||
qDebug()<<"Item"<<item.payload->getName()<<"added"<<(item.payload->getLoaded() ? "from loaded" : "");
|
qDebug()<<"Item"<<item->getName()<<"added"<<(item->getLoaded() ? "from loaded" : "");
|
||||||
itemAdded(std::weak_ptr<Item>(items_.back()));
|
itemAdded(std::weak_ptr<Item>(items_.back()));
|
||||||
}
|
}
|
||||||
else if(!item.changes.isNone())
|
else
|
||||||
{
|
{
|
||||||
qDebug()<<"Item"<<item.payload->getName()<<"was matched with"<<matched->getName()<<"and has changes";
|
ItemUpdateRequest request = item->createValueUpdateRequest(item->getValue(),
|
||||||
ItemUpdateRequest request = item.updateRequest();
|
updateType,
|
||||||
|
updateType == ITEM_UPDATE_LOADED);
|
||||||
|
request.newActors = item->getActors();
|
||||||
updateItem(request);
|
updateItem(request);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ItemStore::addItems(const std::vector<ItemAddRequest>& itemIn)
|
void ItemStore::addItems(const std::vector<std::shared_ptr<Item>>& itemIn,
|
||||||
|
item_update_type_t updateType)
|
||||||
{
|
{
|
||||||
for(unsigned j = 0; j < itemIn.size(); j++)
|
for(unsigned j = 0; j < itemIn.size(); j++)
|
||||||
addItem(itemIn[j]);
|
addItem(itemIn[j], updateType);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ItemStore::removeItem(const ItemData& item)
|
void ItemStore::removeItem(const ItemData& item)
|
||||||
|
|
@ -56,15 +57,8 @@ void ItemStore::removeItem(const ItemData& item)
|
||||||
|
|
||||||
void ItemStore::replaceItems(const std::vector<std::shared_ptr<Item>>& items)
|
void ItemStore::replaceItems(const std::vector<std::shared_ptr<Item>>& items)
|
||||||
{
|
{
|
||||||
for(const std::shared_ptr<Item>& item : items)
|
qDebug()<<__func__;
|
||||||
{
|
addItems(items, ITEM_UPDATE_LOADED);
|
||||||
ItemAddRequest request;
|
|
||||||
request.changes = ItemFieldChanges(true);
|
|
||||||
request.changes.actors = true;
|
|
||||||
request.type = ITEM_UPDATE_LOADED;
|
|
||||||
request.payload = item;
|
|
||||||
addItem(request);
|
|
||||||
}
|
|
||||||
std::vector<ItemData> deletedItems;
|
std::vector<ItemData> deletedItems;
|
||||||
for(std::shared_ptr<Item> item : items_)
|
for(std::shared_ptr<Item> item : items_)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -39,8 +39,8 @@ signals:
|
||||||
public slots:
|
public slots:
|
||||||
|
|
||||||
void removeItem(const ItemData& item);
|
void removeItem(const ItemData& item);
|
||||||
void addItem(const ItemAddRequest& item);
|
void addItem(const std::shared_ptr<Item>& item, item_update_type_t updateType);
|
||||||
void addItems(const std::vector<ItemAddRequest>& itemsIn);
|
void addItems(const std::vector<std::shared_ptr<Item>>& itemsIn, item_update_type_t updateType);
|
||||||
void replaceItems(const std::vector<std::shared_ptr<Item>>& items);
|
void replaceItems(const std::vector<std::shared_ptr<Item>>& items);
|
||||||
void updateItems(const std::vector<ItemUpdateRequest>& updates);
|
void updateItems(const std::vector<ItemUpdateRequest>& updates);
|
||||||
void updateItem(const ItemUpdateRequest& update);
|
void updateItem(const ItemUpdateRequest& update);
|
||||||
|
|
|
||||||
|
|
@ -1,391 +0,0 @@
|
||||||
#include "mqttitem.h"
|
|
||||||
|
|
||||||
#include <QJsonArray>
|
|
||||||
#include <QJsonObject>
|
|
||||||
#include <QJsonDocument>
|
|
||||||
#include <QtMqtt/QMqttClient>
|
|
||||||
|
|
||||||
#include "mqttclient.h"
|
|
||||||
#include "programmode.h"
|
|
||||||
|
|
||||||
MqttItem::MqttItem(QString name, uint8_t value, QObject *parent)
|
|
||||||
: Item(0, name, value, parent),
|
|
||||||
topic_(""),
|
|
||||||
valueKey_("state"),
|
|
||||||
valueOn_("ON"),
|
|
||||||
valueOff_("OFF")
|
|
||||||
{
|
|
||||||
hashId();
|
|
||||||
std::shared_ptr<MqttClient> workClient = client.lock();
|
|
||||||
assert(workClient || programMode == PROGRAM_MODE_UI_ONLY);
|
|
||||||
|
|
||||||
if(workClient)
|
|
||||||
connect(workClient->getClient().get(), &QMqttClient::stateChanged, this, &MqttItem::onClientStateChanged);
|
|
||||||
}
|
|
||||||
|
|
||||||
MqttItem::~MqttItem()
|
|
||||||
{
|
|
||||||
qDebug()<<__func__;
|
|
||||||
std::shared_ptr<MqttClient> workClient = client.lock();
|
|
||||||
if(!workClient)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if(subscription)
|
|
||||||
{
|
|
||||||
disconnect(subscription->subscription, &QMqttSubscription::messageReceived, this, &MqttItem::onMessageReceived);
|
|
||||||
workClient->unsubscribe(subscription);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(devicesSubscription)
|
|
||||||
{
|
|
||||||
disconnect(devicesSubscription->subscription, &QMqttSubscription::messageReceived, this, &MqttItem::onDevicesMessageReceived);
|
|
||||||
workClient->unsubscribe(devicesSubscription);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MqttItem::onClientStateChanged(QMqttClient::ClientState state)
|
|
||||||
{
|
|
||||||
if(state == QMqttClient::Connected)
|
|
||||||
{
|
|
||||||
refreshSubscription();
|
|
||||||
// Subscribe to bridge/devices to get exposes
|
|
||||||
std::shared_ptr<MqttClient> workClient = client.lock();
|
|
||||||
if(workClient && !exposeLoaded_)
|
|
||||||
{
|
|
||||||
devicesSubscription = workClient->subscribe(workClient->getBaseTopic() + "/bridge/devices");
|
|
||||||
connect(devicesSubscription->subscription, &QMqttSubscription::messageReceived, this, &MqttItem::onDevicesMessageReceived);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MqttItem::refreshSubscription()
|
|
||||||
{
|
|
||||||
std::shared_ptr<MqttClient> workClient = client.lock();
|
|
||||||
if(!workClient || topic_.isEmpty())
|
|
||||||
return;
|
|
||||||
|
|
||||||
if(workClient->getClient()->state() != QMqttClient::Connected)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if(subscription)
|
|
||||||
{
|
|
||||||
disconnect(subscription->subscription, &QMqttSubscription::messageReceived, this, &MqttItem::onMessageReceived);
|
|
||||||
workClient->unsubscribe(subscription);
|
|
||||||
}
|
|
||||||
|
|
||||||
subscription = workClient->subscribe(workClient->getBaseTopic() + "/" + getTopic());
|
|
||||||
connect(subscription->subscription, &QMqttSubscription::messageReceived, this, &MqttItem::onMessageReceived);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MqttItem::onDevicesMessageReceived(const QMqttMessage& message)
|
|
||||||
{
|
|
||||||
if(exposeLoaded_)
|
|
||||||
return;
|
|
||||||
|
|
||||||
QJsonDocument doc = QJsonDocument::fromJson(message.payload());
|
|
||||||
if(!doc.isArray())
|
|
||||||
return;
|
|
||||||
|
|
||||||
QJsonArray devices = doc.array();
|
|
||||||
for(const QJsonValue& deviceValue : devices)
|
|
||||||
{
|
|
||||||
if(!deviceValue.isObject())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
QJsonObject device = deviceValue.toObject();
|
|
||||||
QString ieeeAddr = device["ieee_address"].toString();
|
|
||||||
|
|
||||||
// Check if this device matches our topic (friendly_name)
|
|
||||||
QString friendlyName = device["friendly_name"].toString();
|
|
||||||
if(friendlyName == topic_)
|
|
||||||
{
|
|
||||||
loadExposeFromDevice(device);
|
|
||||||
exposeLoaded_ = true;
|
|
||||||
|
|
||||||
// Unsubscribe from devices topic since we found our device
|
|
||||||
std::shared_ptr<MqttClient> workClient = client.lock();
|
|
||||||
if(workClient && devicesSubscription)
|
|
||||||
{
|
|
||||||
disconnect(devicesSubscription->subscription, &QMqttSubscription::messageReceived, this, &MqttItem::onDevicesMessageReceived);
|
|
||||||
workClient->unsubscribe(devicesSubscription);
|
|
||||||
devicesSubscription = nullptr;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MqttItem::loadExposeFromDevice(const QJsonObject& device)
|
|
||||||
{
|
|
||||||
// Get definition - may be null for unsupported devices
|
|
||||||
QJsonObject definition = device["definition"].toObject();
|
|
||||||
if(definition.isEmpty())
|
|
||||||
{
|
|
||||||
qWarning() << "MqttItem" << topic_ << "device has no definition (unsupported)";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get exposes from definition
|
|
||||||
QJsonArray exposes = definition["exposes"].toArray();
|
|
||||||
if(exposes.isEmpty())
|
|
||||||
{
|
|
||||||
qWarning() << "MqttItem" << topic_ << "device has no exposes";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for(const QJsonValue& exposeValue : exposes)
|
|
||||||
{
|
|
||||||
if(!exposeValue.isObject())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
QJsonObject expose = exposeValue.toObject();
|
|
||||||
QString property = expose["property"].toString();
|
|
||||||
|
|
||||||
// Check if this expose matches our valueKey
|
|
||||||
if(property == valueKey_)
|
|
||||||
{
|
|
||||||
setFromExpose(expose);
|
|
||||||
qDebug() << "MqttItem" << topic_ << "detected type" << expose["type"].toString() << "for property" << valueKey_;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if it's a composite type with features
|
|
||||||
if(expose["type"].toString() == "composite" || expose["type"].toString() == "light")
|
|
||||||
{
|
|
||||||
QJsonArray features = expose["features"].toArray();
|
|
||||||
for(const QJsonValue& featureValue : features)
|
|
||||||
{
|
|
||||||
if(!featureValue.isObject())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
QJsonObject feature = featureValue.toObject();
|
|
||||||
if(feature["property"].toString() == valueKey_)
|
|
||||||
{
|
|
||||||
setFromExpose(feature);
|
|
||||||
qDebug() << "MqttItem" << topic_ << "detected type" << feature["type"].toString() << "for property" << valueKey_;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
qWarning() << "MqttItem" << topic_ << "could not find expose for property" << valueKey_;
|
|
||||||
}
|
|
||||||
|
|
||||||
void MqttItem::onMessageReceived(const QMqttMessage& message)
|
|
||||||
{
|
|
||||||
QJsonDocument doc = QJsonDocument::fromJson(message.payload());
|
|
||||||
if(doc.isObject())
|
|
||||||
{
|
|
||||||
QJsonObject obj = doc.object();
|
|
||||||
if(obj.contains(getValueKey()))
|
|
||||||
{
|
|
||||||
QJsonValue value = obj[getValueKey()];
|
|
||||||
ItemUpdateRequest req = createValueUpdateRequest(ITEM_UPDATE_BACKEND);
|
|
||||||
req.changes.value = true;
|
|
||||||
|
|
||||||
if(getValueType() == ITEM_VALUE_UINT)
|
|
||||||
{
|
|
||||||
// Numeric value
|
|
||||||
req.payload.setValueData(value.toInt(0));
|
|
||||||
}
|
|
||||||
else if(getValueType() == ITEM_VALUE_ENUM)
|
|
||||||
{
|
|
||||||
// Enum value - find index
|
|
||||||
QString strValue = value.toString();
|
|
||||||
int index = valueNameToIndex(strValue);
|
|
||||||
if(index >= 0)
|
|
||||||
req.payload.setValueData(index);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Binary value
|
|
||||||
QString strValue = value.toString();
|
|
||||||
if(strValue == getValueOn() || strValue == "ON" || strValue == "true")
|
|
||||||
req.payload.setValueData(true);
|
|
||||||
else
|
|
||||||
req.payload.setValueData(false);
|
|
||||||
}
|
|
||||||
requestUpdate(req);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MqttItem::hashId()
|
|
||||||
{
|
|
||||||
QString hashString = topic_ + "/" + valueKey_;
|
|
||||||
itemId_ = qHash(hashString.toLatin1());
|
|
||||||
}
|
|
||||||
|
|
||||||
void MqttItem::setTopic(const QString& topic)
|
|
||||||
{
|
|
||||||
topic_ = topic;
|
|
||||||
hashId();
|
|
||||||
refreshSubscription();
|
|
||||||
}
|
|
||||||
|
|
||||||
void MqttItem::setValueKey(const QString& valueKey)
|
|
||||||
{
|
|
||||||
valueKey_ = valueKey;
|
|
||||||
hashId();
|
|
||||||
}
|
|
||||||
|
|
||||||
void MqttItem::setValueOn(const QString& valueOn)
|
|
||||||
{
|
|
||||||
valueOn_ = valueOn;
|
|
||||||
}
|
|
||||||
|
|
||||||
void MqttItem::setValueOff(const QString& valueOff)
|
|
||||||
{
|
|
||||||
valueOff_ = valueOff;
|
|
||||||
}
|
|
||||||
|
|
||||||
void MqttItem::setValueMin(int min)
|
|
||||||
{
|
|
||||||
valueMin_ = min;
|
|
||||||
}
|
|
||||||
|
|
||||||
void MqttItem::setValueMax(int max)
|
|
||||||
{
|
|
||||||
valueMax_ = max;
|
|
||||||
}
|
|
||||||
|
|
||||||
void MqttItem::setValueStep(int step)
|
|
||||||
{
|
|
||||||
valueStep_ = step;
|
|
||||||
}
|
|
||||||
|
|
||||||
void MqttItem::setValueType(item_value_type_t type)
|
|
||||||
{
|
|
||||||
type_ = type;
|
|
||||||
}
|
|
||||||
|
|
||||||
void MqttItem::setFromExpose(const QJsonObject& expose)
|
|
||||||
{
|
|
||||||
QString type = expose["type"].toString();
|
|
||||||
QString property = expose["property"].toString();
|
|
||||||
|
|
||||||
setValueKey(property);
|
|
||||||
|
|
||||||
if(type == "binary")
|
|
||||||
{
|
|
||||||
type_ = ITEM_VALUE_BOOL;
|
|
||||||
setValueOn(expose["value_on"].toString("ON"));
|
|
||||||
setValueOff(expose["value_off"].toString("OFF"));
|
|
||||||
}
|
|
||||||
else if(type == "numeric")
|
|
||||||
{
|
|
||||||
type_ = ITEM_VALUE_UINT;
|
|
||||||
setValueMin(expose["value_min"].toInt(0));
|
|
||||||
setValueMax(expose["value_max"].toInt(255));
|
|
||||||
setValueStep(expose["value_step"].toInt(1));
|
|
||||||
}
|
|
||||||
else if(type == "enum")
|
|
||||||
{
|
|
||||||
type_ = ITEM_VALUE_ENUM;
|
|
||||||
QJsonArray values = expose["values"].toArray();
|
|
||||||
std::vector<QString> valueNames;
|
|
||||||
for(const QJsonValue& v : values)
|
|
||||||
valueNames.push_back(v.toString());
|
|
||||||
setValueNames(valueNames);
|
|
||||||
}
|
|
||||||
|
|
||||||
hashId();
|
|
||||||
}
|
|
||||||
|
|
||||||
QString MqttItem::getTopic() const
|
|
||||||
{
|
|
||||||
return topic_;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString MqttItem::getValueKey() const
|
|
||||||
{
|
|
||||||
return valueKey_;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString MqttItem::getValueOn() const
|
|
||||||
{
|
|
||||||
return valueOn_;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString MqttItem::getValueOff() const
|
|
||||||
{
|
|
||||||
return valueOff_;
|
|
||||||
}
|
|
||||||
|
|
||||||
int MqttItem::getValueMin() const
|
|
||||||
{
|
|
||||||
return valueMin_;
|
|
||||||
}
|
|
||||||
|
|
||||||
int MqttItem::getValueMax() const
|
|
||||||
{
|
|
||||||
return valueMax_;
|
|
||||||
}
|
|
||||||
|
|
||||||
int MqttItem::getValueStep() const
|
|
||||||
{
|
|
||||||
return valueStep_;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool MqttItem::getExposeLoaded() const
|
|
||||||
{
|
|
||||||
return exposeLoaded_;
|
|
||||||
}
|
|
||||||
|
|
||||||
void MqttItem::store(QJsonObject& json)
|
|
||||||
{
|
|
||||||
Item::store(json);
|
|
||||||
json["Type"] = "Mqtt";
|
|
||||||
json["Topic"] = topic_;
|
|
||||||
json["ValueKey"] = valueKey_;
|
|
||||||
json["ValueOn"] = valueOn_;
|
|
||||||
json["ValueOff"] = valueOff_;
|
|
||||||
json["ValueMin"] = valueMin_;
|
|
||||||
json["ValueMax"] = valueMax_;
|
|
||||||
json["ValueStep"] = valueStep_;
|
|
||||||
}
|
|
||||||
|
|
||||||
void MqttItem::load(const QJsonObject& json, const bool preserve)
|
|
||||||
{
|
|
||||||
Item::load(json, preserve);
|
|
||||||
topic_ = json["Topic"].toString();
|
|
||||||
valueKey_ = json["ValueKey"].toString("state");
|
|
||||||
valueOn_ = json["ValueOn"].toString("ON");
|
|
||||||
valueOff_ = json["ValueOff"].toString("OFF");
|
|
||||||
valueMin_ = json["ValueMin"].toInt(0);
|
|
||||||
valueMax_ = json["ValueMax"].toInt(255);
|
|
||||||
valueStep_ = json["ValueStep"].toInt(1);
|
|
||||||
exposeLoaded_ = json["ExposeLoaded"].toBool(false);
|
|
||||||
hashId();
|
|
||||||
refreshSubscription();
|
|
||||||
}
|
|
||||||
|
|
||||||
void MqttItem::enactValue(uint8_t value)
|
|
||||||
{
|
|
||||||
std::shared_ptr<MqttClient> workClient = client.lock();
|
|
||||||
if(!workClient || topic_.isEmpty())
|
|
||||||
return;
|
|
||||||
|
|
||||||
QString fullTopic = workClient->getBaseTopic() + "/" + topic_ + "/set";
|
|
||||||
QJsonObject payload;
|
|
||||||
|
|
||||||
if(getValueType() == ITEM_VALUE_UINT)
|
|
||||||
{
|
|
||||||
payload[valueKey_] = static_cast<int>(value);
|
|
||||||
}
|
|
||||||
else if(getValueType() == ITEM_VALUE_ENUM)
|
|
||||||
{
|
|
||||||
payload[valueKey_] = indexToValueName(value);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
payload[valueKey_] = value ? valueOn_ : valueOff_;
|
|
||||||
}
|
|
||||||
|
|
||||||
QJsonDocument doc(payload);
|
|
||||||
QByteArray data = doc.toJson(QJsonDocument::Compact);
|
|
||||||
|
|
||||||
qDebug() << "MqttItem publishing to" << fullTopic << ":" << data;
|
|
||||||
workClient->getClient()->publish(fullTopic, data);
|
|
||||||
}
|
|
||||||
|
|
@ -1,70 +0,0 @@
|
||||||
#ifndef MQTTITEM_H
|
|
||||||
#define MQTTITEM_H
|
|
||||||
|
|
||||||
#include "item.h"
|
|
||||||
#include "mqttclient.h"
|
|
||||||
|
|
||||||
class QString;
|
|
||||||
|
|
||||||
class MqttItem : public Item
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
inline static std::weak_ptr<MqttClient> client;
|
|
||||||
|
|
||||||
private:
|
|
||||||
QString topic_;
|
|
||||||
QString valueKey_;
|
|
||||||
QString valueOn_ = "ON";
|
|
||||||
QString valueOff_ = "OFF";
|
|
||||||
int valueMin_ = 0;
|
|
||||||
int valueMax_ = 255;
|
|
||||||
int valueStep_ = 1;
|
|
||||||
bool exposeLoaded_ = false;
|
|
||||||
|
|
||||||
MqttClient::Subscription* subscription = nullptr;
|
|
||||||
MqttClient::Subscription* devicesSubscription = nullptr;
|
|
||||||
|
|
||||||
void hashId();
|
|
||||||
void refreshSubscription();
|
|
||||||
void onMessageReceived(const QMqttMessage& message);
|
|
||||||
void onClientStateChanged(QMqttClient::ClientState state);
|
|
||||||
void onDevicesMessageReceived(const QMqttMessage& message);
|
|
||||||
void loadExposeFromDevice(const QJsonObject& device);
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit MqttItem(QString name = "MqttItem",
|
|
||||||
uint8_t value = 0,
|
|
||||||
QObject *parent = nullptr);
|
|
||||||
virtual ~MqttItem() override;
|
|
||||||
|
|
||||||
void setTopic(const QString& topic);
|
|
||||||
void setValueKey(const QString& valueKey);
|
|
||||||
void setBaseTopic(const QString& baseTopic);
|
|
||||||
void setValueOn(const QString& valueOn);
|
|
||||||
void setValueOff(const QString& valueOff);
|
|
||||||
void setValueMin(int min);
|
|
||||||
void setValueMax(int max);
|
|
||||||
void setValueStep(int step);
|
|
||||||
void setValueType(item_value_type_t type);
|
|
||||||
|
|
||||||
// Configure from Zigbee2MQTT expose info
|
|
||||||
void setFromExpose(const QJsonObject& expose);
|
|
||||||
|
|
||||||
QString getTopic() const;
|
|
||||||
QString getValueKey() const;
|
|
||||||
QString getValueOn() const;
|
|
||||||
QString getValueOff() const;
|
|
||||||
int getValueMin() const;
|
|
||||||
int getValueMax() const;
|
|
||||||
int getValueStep() const;
|
|
||||||
bool getExposeLoaded() const;
|
|
||||||
|
|
||||||
virtual void store(QJsonObject& json) override;
|
|
||||||
virtual void load(const QJsonObject& json, const bool preserve = false) override;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual void enactValue(uint8_t value) override;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // MQTTITEM_H
|
|
||||||
11
src/main.cpp
11
src/main.cpp
|
|
@ -115,7 +115,12 @@ int main(int argc, char *argv[])
|
||||||
QObject::connect(&mainObject.micro, SIGNAL(textRecived(QString)), w, SLOT(changeHeaderLableText(QString)));
|
QObject::connect(&mainObject.micro, SIGNAL(textRecived(QString)), w, SLOT(changeHeaderLableText(QString)));
|
||||||
QObject::connect(w, &MainWindow::sigSetRgb, &mainObject.micro, &Microcontroller::changeRgbColor);
|
QObject::connect(w, &MainWindow::sigSetRgb, &mainObject.micro, &Microcontroller::changeRgbColor);
|
||||||
QObject::connect(w, &MainWindow::sigSave, &mainObject, [&mainObject, settingsPath](){mainObject.storeToDisk(settingsPath);});
|
QObject::connect(w, &MainWindow::sigSave, &mainObject, [&mainObject, settingsPath](){mainObject.storeToDisk(settingsPath);});
|
||||||
QObject::connect(w, &MainWindow::createdItem, &globalItems, &ItemStore::addItem);
|
QObject::connect(w,
|
||||||
|
&MainWindow::createdItem,
|
||||||
|
&globalItems,
|
||||||
|
[](std::shared_ptr<Item> item) {
|
||||||
|
globalItems.addItem(item, ITEM_UPDATE_USER);
|
||||||
|
});
|
||||||
w->show();
|
w->show();
|
||||||
}
|
}
|
||||||
retVal = a.exec();
|
retVal = a.exec();
|
||||||
|
|
@ -127,7 +132,9 @@ int main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
SecondaryMainObject mainObject(parser.value(hostOption), parser.value(portOption).toInt());
|
SecondaryMainObject mainObject(parser.value(hostOption), parser.value(portOption).toInt());
|
||||||
MainWindow w(&mainObject);
|
MainWindow w(&mainObject);
|
||||||
QObject::connect(&w, &MainWindow::createdItem, &globalItems, &ItemStore::addItem);
|
QObject::connect(&w, &MainWindow::createdItem, &globalItems, [](std::shared_ptr<Item> item) {
|
||||||
|
globalItems.addItem(item, ITEM_UPDATE_USER);
|
||||||
|
});
|
||||||
QObject::connect(&w, &MainWindow::sigSave, mainObject.tcpClient, &TcpClient::sendItems);
|
QObject::connect(&w, &MainWindow::sigSave, mainObject.tcpClient, &TcpClient::sendItems);
|
||||||
w.show();
|
w.show();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,6 @@
|
||||||
#include<QJsonArray>
|
#include<QJsonArray>
|
||||||
#include<QMessageBox>
|
#include<QMessageBox>
|
||||||
|
|
||||||
#include "mqttclient.h"
|
|
||||||
#include "items/mqttitem.h"
|
|
||||||
#include "items/itemstore.h"
|
#include "items/itemstore.h"
|
||||||
|
|
||||||
MainObject::MainObject(QObject *parent) :
|
MainObject::MainObject(QObject *parent) :
|
||||||
|
|
@ -76,12 +74,9 @@ PrimaryMainObject::PrimaryMainObject(QIODevice* microDevice, const QString& sett
|
||||||
micro(microDevice),
|
micro(microDevice),
|
||||||
tcpServer(new TcpServer(this)),
|
tcpServer(new TcpServer(this)),
|
||||||
webServer(new WebSocketServer("shinterface", this)),
|
webServer(new WebSocketServer("shinterface", this)),
|
||||||
mqttClient(new MqttClient),
|
|
||||||
sunSensorSource(49.824972, 8.702194),
|
sunSensorSource(49.824972, 8.702194),
|
||||||
fixedItems(µ)
|
fixedItems(µ)
|
||||||
{
|
{
|
||||||
MqttItem::client = mqttClient;
|
|
||||||
|
|
||||||
//connect sensors subsystem
|
//connect sensors subsystem
|
||||||
connect(&globalSensors, &SensorStore::sensorChangedState, tcpServer, &TcpServer::sensorEvent);
|
connect(&globalSensors, &SensorStore::sensorChangedState, tcpServer, &TcpServer::sensorEvent);
|
||||||
connect(tcpServer, &TcpServer::gotSensor, &globalSensors, &SensorStore::sensorGotState);
|
connect(tcpServer, &TcpServer::gotSensor, &globalSensors, &SensorStore::sensorGotState);
|
||||||
|
|
@ -103,8 +98,7 @@ PrimaryMainObject::PrimaryMainObject(QIODevice* microDevice, const QString& sett
|
||||||
loadFromDisk(settingsPath);
|
loadFromDisk(settingsPath);
|
||||||
|
|
||||||
QJsonObject mqttJson = settings["Mqtt"].toObject();
|
QJsonObject mqttJson = settings["Mqtt"].toObject();
|
||||||
mqttClient->start(mqttJson);
|
mqttSensorSource.start(mqttJson);
|
||||||
mqttSensorSource.start(mqttClient, mqttJson);
|
|
||||||
|
|
||||||
tcpServer->launch(QHostAddress(host), port);
|
tcpServer->launch(QHostAddress(host), port);
|
||||||
webServer->launch(QHostAddress(host), port+1);
|
webServer->launch(QHostAddress(host), port+1);
|
||||||
|
|
@ -121,7 +115,6 @@ void PrimaryMainObject::store(QJsonObject &json)
|
||||||
{
|
{
|
||||||
globalItems.store(json);
|
globalItems.store(json);
|
||||||
QJsonObject mqttJson = json["Mqtt"].toObject();
|
QJsonObject mqttJson = json["Mqtt"].toObject();
|
||||||
mqttClient->store(mqttJson);
|
|
||||||
mqttSensorSource.store(mqttJson);
|
mqttSensorSource.store(mqttJson);
|
||||||
json["Mqtt"] = mqttJson;
|
json["Mqtt"] = mqttJson;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,6 @@ public:
|
||||||
Microcontroller micro;
|
Microcontroller micro;
|
||||||
TcpServer* tcpServer;
|
TcpServer* tcpServer;
|
||||||
WebSocketServer* webServer;
|
WebSocketServer* webServer;
|
||||||
std::shared_ptr<MqttClient> mqttClient;
|
|
||||||
|
|
||||||
//sensors
|
//sensors
|
||||||
SunSensorSource sunSensorSource;
|
SunSensorSource sunSensorSource;
|
||||||
|
|
|
||||||
|
|
@ -157,31 +157,16 @@ void Microcontroller::processList(const QString& buffer)
|
||||||
else if(buffer.contains("EOL"))
|
else if(buffer.contains("EOL"))
|
||||||
{
|
{
|
||||||
listMode = false;
|
listMode = false;
|
||||||
std::vector<ItemAddRequest> requests;
|
gotItems(relayList, ITEM_UPDATE_BACKEND);
|
||||||
for(const std::shared_ptr<Item>& item : relayList)
|
|
||||||
{
|
|
||||||
ItemAddRequest request;
|
|
||||||
request.changes.name = true;
|
|
||||||
request.changes.value = true;
|
|
||||||
request.payload = item;
|
|
||||||
request.type = ITEM_UPDATE_BACKEND;
|
|
||||||
requests.push_back(request);
|
|
||||||
}
|
|
||||||
gotItems(requests);
|
|
||||||
relayList.clear();
|
relayList.clear();
|
||||||
}
|
}
|
||||||
else
|
else listMode = false;
|
||||||
{
|
|
||||||
listMode = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Microcontroller::processRelayState(const QString& buffer)
|
void Microcontroller::processRelayState(const QString& buffer)
|
||||||
{
|
{
|
||||||
ItemUpdateRequest update;
|
ItemUpdateRequest update;
|
||||||
update.type = ITEM_UPDATE_BACKEND;
|
update.type = ITEM_UPDATE_BACKEND;
|
||||||
update.changes.name = true;
|
|
||||||
update.changes.value = true;
|
|
||||||
update.payload = static_cast<ItemData>(*processRelayLine(buffer));
|
update.payload = static_cast<ItemData>(*processRelayLine(buffer));
|
||||||
updateItems({update});
|
updateItems({update});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,115 +0,0 @@
|
||||||
#include "mqttclient.h"
|
|
||||||
|
|
||||||
MqttClient::MqttClient():
|
|
||||||
client(new QMqttClient)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void MqttClient::start(const QJsonObject& settings)
|
|
||||||
{
|
|
||||||
baseTopicName = settings["BaseTopic"].toString("zigbee2mqtt");
|
|
||||||
|
|
||||||
QMqttClient* cl = client.get();
|
|
||||||
connect(cl, &QMqttClient::stateChanged, this, &MqttClient::onClientStateChanged);
|
|
||||||
connect(cl, &QMqttClient::errorChanged, this, &MqttClient::onClientError);
|
|
||||||
|
|
||||||
client->setHostname(settings["Host"].toString("127.0.0.1"));
|
|
||||||
client->setPort(settings["Port"].toInt(1883));
|
|
||||||
client->setClientId(settings["ClientId"].toString("smartvos"));
|
|
||||||
if(settings.contains("User"))
|
|
||||||
client->setUsername(settings["User"].toString());
|
|
||||||
if(settings.contains("Password"))
|
|
||||||
client->setPassword(settings["Password"].toString());
|
|
||||||
client->setProtocolVersion(QMqttClient::MQTT_3_1);
|
|
||||||
|
|
||||||
client->connectToHost();
|
|
||||||
}
|
|
||||||
|
|
||||||
void MqttClient::onClientError(QMqttClient::ClientError error)
|
|
||||||
{
|
|
||||||
qWarning()<<"MQTT Client error:"<<error;
|
|
||||||
}
|
|
||||||
|
|
||||||
void MqttClient::onClientStateChanged(QMqttClient::ClientState state)
|
|
||||||
{
|
|
||||||
if(state == QMqttClient::ClientState::Connected)
|
|
||||||
qInfo()<<"Connected to MQTT broker at "<<client->hostname()<<client->port();
|
|
||||||
else if (state == QMqttClient::ClientState::Disconnected)
|
|
||||||
qWarning()<<"Lost connection to MQTT broker";
|
|
||||||
else if(state == QMqttClient::ClientState::Connecting)
|
|
||||||
qInfo()<<"Connecting to MQTT broker at "<<client->hostname()<<client->port();
|
|
||||||
}
|
|
||||||
|
|
||||||
void MqttClient::store(QJsonObject& json)
|
|
||||||
{
|
|
||||||
json["Host"] = client->hostname();
|
|
||||||
json["Port"] = client->port();
|
|
||||||
json["BaseTopic"] = baseTopicName;
|
|
||||||
if(client->username() != "")
|
|
||||||
json["User"] = client->username();
|
|
||||||
if(client->password() != "")
|
|
||||||
json["Password"] = client->password();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::shared_ptr<QMqttClient> MqttClient::getClient()
|
|
||||||
{
|
|
||||||
return client;
|
|
||||||
}
|
|
||||||
|
|
||||||
MqttClient::Subscription* MqttClient::subscribe(QString topic)
|
|
||||||
{
|
|
||||||
if(subscriptions.contains(topic))
|
|
||||||
{
|
|
||||||
MqttClient::Subscription* sub = subscriptions[topic];
|
|
||||||
++sub->ref;
|
|
||||||
return sub;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
qDebug()<<"MqttClient: subscibeing to"<<topic;
|
|
||||||
MqttClient::Subscription* sub = new Subscription;
|
|
||||||
sub->subscription = client->subscribe(topic);
|
|
||||||
sub->ref = 1;
|
|
||||||
subscriptions.insert({topic, sub});
|
|
||||||
return sub;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MqttClient::unsubscribe(MqttClient::Subscription* subscription)
|
|
||||||
{
|
|
||||||
QString topic = subscription->subscription->topic().filter();
|
|
||||||
unsubscribe(topic);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MqttClient::unsubscribe(QString topic)
|
|
||||||
{
|
|
||||||
MqttClient::Subscription* sub = subscriptions[topic];
|
|
||||||
if(!sub)
|
|
||||||
{
|
|
||||||
qWarning()<<"MqttClient: Trying to unsubscribe from unkown topic:"<<topic;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(--sub->ref > 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
qDebug()<<"MqttClient: unsubscibeing"<<sub->subscription->topic().filter();
|
|
||||||
client->unsubscribe(sub->subscription->topic());
|
|
||||||
subscriptions.erase(topic);
|
|
||||||
delete sub;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString MqttClient::getBaseTopic()
|
|
||||||
{
|
|
||||||
return baseTopicName;
|
|
||||||
}
|
|
||||||
|
|
||||||
MqttClient::~MqttClient()
|
|
||||||
{
|
|
||||||
for(const std::pair<QString, Subscription*> sub : subscriptions)
|
|
||||||
{
|
|
||||||
qWarning()<<sub.first<<"not unregistered at exit!";
|
|
||||||
client->unsubscribe(sub.second->subscription->topic());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
#ifndef MQTTCLIENT_H
|
|
||||||
#define MQTTCLIENT_H
|
|
||||||
|
|
||||||
#include <QMqttClient>
|
|
||||||
#include <QObject>
|
|
||||||
#include <QJsonObject>
|
|
||||||
#include <map>
|
|
||||||
|
|
||||||
class MqttClient: public QObject
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
struct Subscription
|
|
||||||
{
|
|
||||||
int ref;
|
|
||||||
QMqttSubscription* subscription;
|
|
||||||
};
|
|
||||||
|
|
||||||
private:
|
|
||||||
QString baseTopicName;
|
|
||||||
std::shared_ptr<QMqttClient> client;
|
|
||||||
std::map<QString, Subscription*> subscriptions;
|
|
||||||
|
|
||||||
private slots:
|
|
||||||
void onClientStateChanged(QMqttClient::ClientState state);
|
|
||||||
void onClientError(QMqttClient::ClientError error);
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit MqttClient();
|
|
||||||
~MqttClient();
|
|
||||||
void start(const QJsonObject& settings);
|
|
||||||
void store(QJsonObject& json);
|
|
||||||
std::shared_ptr<QMqttClient> getClient();
|
|
||||||
Subscription* subscribe(QString topic);
|
|
||||||
void unsubscribe(Subscription* subscription);
|
|
||||||
void unsubscribe(QString topic);
|
|
||||||
QString getBaseTopic();
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // MQTTCLIENT_H
|
|
||||||
|
|
@ -7,11 +7,22 @@ MqttSensorSource::MqttSensorSource(QObject *parent)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void MqttSensorSource::start(std::shared_ptr<MqttClient> client, const QJsonObject& settings)
|
void MqttSensorSource::start(const QJsonObject& settings)
|
||||||
{
|
{
|
||||||
this->client = client;
|
baseTopicName = settings["BaseTopic"].toString("zigbee2mqtt");
|
||||||
|
|
||||||
connect(client->getClient().get(), &QMqttClient::stateChanged, this, &MqttSensorSource::onClientStateChanged);
|
connect(&client, &QMqttClient::stateChanged, this, &MqttSensorSource::onClientStateChanged);
|
||||||
|
connect(&client, &QMqttClient::errorChanged, this, &MqttSensorSource::onClientError);
|
||||||
|
|
||||||
|
client.setHostname(settings["Host"].toString("127.0.0.1"));
|
||||||
|
client.setPort(settings["Port"].toInt(1883));
|
||||||
|
if(settings.contains("User"))
|
||||||
|
client.setUsername(settings["User"].toString());
|
||||||
|
if(settings.contains("Password"))
|
||||||
|
client.setPassword(settings["Password"].toString());
|
||||||
|
client.setProtocolVersion(QMqttClient::MQTT_5_0);
|
||||||
|
|
||||||
|
client.connectToHost();
|
||||||
|
|
||||||
QJsonArray sensorsArray = settings["Sensors"].toArray();
|
QJsonArray sensorsArray = settings["Sensors"].toArray();
|
||||||
|
|
||||||
|
|
@ -26,16 +37,21 @@ void MqttSensorSource::start(std::shared_ptr<MqttClient> client, const QJsonObje
|
||||||
sensor.name = sensor.topic;
|
sensor.name = sensor.topic;
|
||||||
else
|
else
|
||||||
sensor.name = sensorObject["Name"].toString();
|
sensor.name = sensorObject["Name"].toString();
|
||||||
sensor.id = qHash(client->getBaseTopic() + "/" + sensor.topic);
|
sensor.id = qHash(baseTopicName + "/" + sensor.topic);
|
||||||
sensors.push_back(sensor);
|
sensors.push_back(sensor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MqttSensorSource::onClientError(QMqttClient::ClientError error)
|
||||||
|
{
|
||||||
|
qWarning()<<"MQTT Client error:"<<error;
|
||||||
|
}
|
||||||
|
|
||||||
MqttSensorSource::SensorSubscription& MqttSensorSource::findSubscription(const QString& topic)
|
MqttSensorSource::SensorSubscription& MqttSensorSource::findSubscription(const QString& topic)
|
||||||
{
|
{
|
||||||
for(SensorSubscription& sensor : sensors)
|
for(SensorSubscription& sensor : sensors)
|
||||||
{
|
{
|
||||||
if(client->getBaseTopic() + "/" + sensor.topic == topic)
|
if(baseTopicName + "/" + sensor.topic == topic)
|
||||||
return sensor;
|
return sensor;
|
||||||
}
|
}
|
||||||
assert(false);
|
assert(false);
|
||||||
|
|
@ -45,23 +61,30 @@ void MqttSensorSource::onClientStateChanged(QMqttClient::ClientState state)
|
||||||
{
|
{
|
||||||
if(state == QMqttClient::ClientState::Connected)
|
if(state == QMqttClient::ClientState::Connected)
|
||||||
{
|
{
|
||||||
|
qInfo()<<"Connected to MQTT broker at "<<client.hostname()<<client.port();
|
||||||
for(SensorSubscription& sensor : sensors)
|
for(SensorSubscription& sensor : sensors)
|
||||||
{
|
{
|
||||||
sensor.subscription = client->subscribe(client->getBaseTopic() + "/" + sensor.topic);
|
qDebug()<<"MQTT subscribeing to"<<baseTopicName + "/" + sensor.topic;
|
||||||
connect(sensor.subscription->subscription, &QMqttSubscription::messageReceived, this, &MqttSensorSource::onMessageReceived);
|
sensor.subscription = client.subscribe(baseTopicName + "/" + sensor.topic);
|
||||||
|
connect(sensor.subscription, &QMqttSubscription::messageReceived, this, &MqttSensorSource::onMessageReceived);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (state == QMqttClient::ClientState::Disconnected)
|
else if (state == QMqttClient::ClientState::Disconnected)
|
||||||
{
|
{
|
||||||
|
qWarning()<<"Lost connection to MQTT broker";
|
||||||
for(SensorSubscription& sensor : sensors)
|
for(SensorSubscription& sensor : sensors)
|
||||||
{
|
{
|
||||||
if(sensor.subscription)
|
if(sensor.subscription)
|
||||||
{
|
{
|
||||||
client->unsubscribe(client->getBaseTopic() + "/" + sensor.topic);
|
client.unsubscribe(sensor.topic);
|
||||||
sensor.subscription = nullptr;
|
sensor.subscription = nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if(state == QMqttClient::ClientState::Connecting)
|
||||||
|
{
|
||||||
|
qInfo()<<"Connecting to MQTT broker at "<<client.hostname()<<client.port();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MqttSensorSource::onMessageReceived(const QMqttMessage& message)
|
void MqttSensorSource::onMessageReceived(const QMqttMessage& message)
|
||||||
|
|
@ -84,14 +107,6 @@ void MqttSensorSource::onMessageReceived(const QMqttMessage& message)
|
||||||
stateChanged(sensor);
|
stateChanged(sensor);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(obj.contains("local_temperature"))
|
|
||||||
{
|
|
||||||
sensor.name = baseName + " Temperature";
|
|
||||||
sensor.type = Sensor::TYPE_TEMPERATURE;
|
|
||||||
sensor.field = obj["local_temperature"].toDouble(0);
|
|
||||||
stateChanged(sensor);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(obj.contains("humidity"))
|
if(obj.contains("humidity"))
|
||||||
{
|
{
|
||||||
sensor.name = baseName + " Humidity";
|
sensor.name = baseName + " Humidity";
|
||||||
|
|
@ -147,35 +162,18 @@ void MqttSensorSource::onMessageReceived(const QMqttMessage& message)
|
||||||
sensor.field = obj["voc"].toDouble(0);
|
sensor.field = obj["voc"].toDouble(0);
|
||||||
stateChanged(sensor);
|
stateChanged(sensor);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(obj.contains("power"))
|
|
||||||
{
|
|
||||||
sensor.name = baseName + " Power";
|
|
||||||
sensor.type = Sensor::TYPE_POWER;
|
|
||||||
sensor.field = obj["power"].toDouble(0);
|
|
||||||
stateChanged(sensor);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(obj.contains("energy"))
|
|
||||||
{
|
|
||||||
sensor.name = baseName + " Energy";
|
|
||||||
sensor.type = Sensor::TYPE_ENERGY_USE;
|
|
||||||
sensor.field = obj["energy"].toDouble(0);
|
|
||||||
stateChanged(sensor);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(obj.contains("voltage"))
|
|
||||||
{
|
|
||||||
sensor.name = baseName + " Voltage";
|
|
||||||
sensor.type = Sensor::TYPE_VOLTAGE;
|
|
||||||
sensor.field = obj["voltage"].toDouble(0);
|
|
||||||
stateChanged(sensor);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MqttSensorSource::store(QJsonObject& json)
|
void MqttSensorSource::store(QJsonObject& json)
|
||||||
{
|
{
|
||||||
|
json["Host"] = client.hostname();
|
||||||
|
json["Port"] = client.port();
|
||||||
|
json["BaseTopic"] = baseTopicName;
|
||||||
|
if(client.username() != "")
|
||||||
|
json["User"] = client.username();
|
||||||
|
if(client.password() != "")
|
||||||
|
json["Password"] = client.password();
|
||||||
QJsonArray sensorsArray;
|
QJsonArray sensorsArray;
|
||||||
for(const SensorSubscription& sensor : sensors)
|
for(const SensorSubscription& sensor : sensors)
|
||||||
{
|
{
|
||||||
|
|
@ -187,9 +185,3 @@ void MqttSensorSource::store(QJsonObject& json)
|
||||||
json["Sensors"] = sensorsArray;
|
json["Sensors"] = sensorsArray;
|
||||||
}
|
}
|
||||||
|
|
||||||
MqttSensorSource::~MqttSensorSource()
|
|
||||||
{
|
|
||||||
for(SensorSubscription& sub : sensors)
|
|
||||||
client->unsubscribe(client->getBaseTopic() + "/" + sub.topic);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "sensor.h"
|
#include "sensor.h"
|
||||||
#include "mqttclient.h"
|
|
||||||
|
|
||||||
class MqttSensorSource : public QObject
|
class MqttSensorSource : public QObject
|
||||||
{
|
{
|
||||||
|
|
@ -18,11 +17,12 @@ class MqttSensorSource : public QObject
|
||||||
uint64_t id;
|
uint64_t id;
|
||||||
QString topic;
|
QString topic;
|
||||||
QString name;
|
QString name;
|
||||||
MqttClient::Subscription* subscription = nullptr;
|
QMqttSubscription* subscription = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
QString baseTopicName;
|
||||||
std::vector<SensorSubscription> sensors;
|
std::vector<SensorSubscription> sensors;
|
||||||
std::shared_ptr<MqttClient> client;
|
QMqttClient client;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
SensorSubscription& findSubscription(const QString& topic);
|
SensorSubscription& findSubscription(const QString& topic);
|
||||||
|
|
@ -30,11 +30,11 @@ private:
|
||||||
private slots:
|
private slots:
|
||||||
void onClientStateChanged(QMqttClient::ClientState state);
|
void onClientStateChanged(QMqttClient::ClientState state);
|
||||||
void onMessageReceived(const QMqttMessage& message);
|
void onMessageReceived(const QMqttMessage& message);
|
||||||
|
void onClientError(QMqttClient::ClientError error);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit MqttSensorSource(QObject *parent = nullptr);
|
explicit MqttSensorSource(QObject *parent = nullptr);
|
||||||
~MqttSensorSource();
|
void start(const QJsonObject& settings);
|
||||||
void start(std::shared_ptr<MqttClient> client, const QJsonObject& settings);
|
|
||||||
void store(QJsonObject& json);
|
void store(QJsonObject& json);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
|
|
|
||||||
|
|
@ -22,9 +22,6 @@ public:
|
||||||
TYPE_FORMALDEHYD,
|
TYPE_FORMALDEHYD,
|
||||||
TYPE_PM25,
|
TYPE_PM25,
|
||||||
TYPE_TOTAL_VOC,
|
TYPE_TOTAL_VOC,
|
||||||
TYPE_ENERGY_USE,
|
|
||||||
TYPE_POWER,
|
|
||||||
TYPE_VOLTAGE,
|
|
||||||
TYPE_LOWBATTERY = 128,
|
TYPE_LOWBATTERY = 128,
|
||||||
TYPE_SHUTDOWN_IMMINENT = 251,
|
TYPE_SHUTDOWN_IMMINENT = 251,
|
||||||
TYPE_OCUPANCY,
|
TYPE_OCUPANCY,
|
||||||
|
|
@ -148,12 +145,6 @@ public:
|
||||||
return "ppb";
|
return "ppb";
|
||||||
case TYPE_SUN_ALTITUDE:
|
case TYPE_SUN_ALTITUDE:
|
||||||
return "°";
|
return "°";
|
||||||
case TYPE_POWER:
|
|
||||||
return "W";
|
|
||||||
case TYPE_ENERGY_USE:
|
|
||||||
return "kWh";
|
|
||||||
case TYPE_VOLTAGE:
|
|
||||||
return "V";
|
|
||||||
default:
|
default:
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,6 @@ void Server::processIncomeingJson(const QByteArray& jsonbytes)
|
||||||
QJsonArray data = json["Data"].toArray();
|
QJsonArray data = json["Data"].toArray();
|
||||||
bool FullList = json["FullList"].toBool(false);
|
bool FullList = json["FullList"].toBool(false);
|
||||||
std::vector<std::shared_ptr<Item>> items;
|
std::vector<std::shared_ptr<Item>> items;
|
||||||
std::vector<ItemFieldChanges> fieldChanges;
|
|
||||||
for(QJsonValueRef itemjson : data)
|
for(QJsonValueRef itemjson : data)
|
||||||
{
|
{
|
||||||
QJsonObject jsonobject = itemjson.toObject();
|
QJsonObject jsonobject = itemjson.toObject();
|
||||||
|
|
@ -34,7 +33,6 @@ void Server::processIncomeingJson(const QByteArray& jsonbytes)
|
||||||
{
|
{
|
||||||
qDebug()<<"Server got item"<<item->getName();
|
qDebug()<<"Server got item"<<item->getName();
|
||||||
item->setLoaded(FullList);
|
item->setLoaded(FullList);
|
||||||
fieldChanges.push_back(item->loadWithChanges(jsonobject));
|
|
||||||
items.push_back(item);
|
items.push_back(item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -45,16 +43,7 @@ void Server::processIncomeingJson(const QByteArray& jsonbytes)
|
||||||
}
|
}
|
||||||
else if(!items.empty())
|
else if(!items.empty())
|
||||||
{
|
{
|
||||||
std::vector<ItemUpdateRequest> updates;
|
gotItems(items, ITEM_UPDATE_REMOTE);
|
||||||
for(size_t i = 0; i < items.size(); i++)
|
|
||||||
{
|
|
||||||
ItemUpdateRequest request;
|
|
||||||
request.payload = *items[i];
|
|
||||||
request.changes = fieldChanges[i];
|
|
||||||
request.type = ITEM_UPDATE_REMOTE;
|
|
||||||
updates.push_back(request);
|
|
||||||
}
|
|
||||||
updateItems(updates);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
@ -107,7 +96,7 @@ void Server::itemUpdated(ItemUpdateRequest update)
|
||||||
{
|
{
|
||||||
QJsonArray items;
|
QJsonArray items;
|
||||||
QJsonObject itemjson;
|
QJsonObject itemjson;
|
||||||
update.payload.storeWithChanges(itemjson, update.changes);
|
update.payload.store(itemjson);
|
||||||
items.append(itemjson);
|
items.append(itemjson);
|
||||||
QJsonObject json = createMessage("ItemUpdate", items);
|
QJsonObject json = createMessage("ItemUpdate", items);
|
||||||
json["FullList"] = false;
|
json["FullList"] = false;
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,7 @@ void Service::sendItems()
|
||||||
sendJson(json);
|
sendJson(json);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void Service::processIncomeingJson(const QByteArray& jsonbytes)
|
void Service::processIncomeingJson(const QByteArray& jsonbytes)
|
||||||
{
|
{
|
||||||
QJsonDocument doc = QJsonDocument::fromJson(jsonbytes);
|
QJsonDocument doc = QJsonDocument::fromJson(jsonbytes);
|
||||||
|
|
@ -81,6 +82,7 @@ void Service::processIncomeingJson(const QByteArray& jsonbytes)
|
||||||
else if(type == "SensorUpdate")
|
else if(type == "SensorUpdate")
|
||||||
{
|
{
|
||||||
QJsonArray data = json["Data"].toArray();
|
QJsonArray data = json["Data"].toArray();
|
||||||
|
qWarning()<<"Got sensor update with no/empty sensors array";
|
||||||
for(QJsonValueRef sensorjson : data)
|
for(QJsonValueRef sensorjson : data)
|
||||||
{
|
{
|
||||||
QJsonObject jsonobject = sensorjson.toObject();
|
QJsonObject jsonobject = sensorjson.toObject();
|
||||||
|
|
|
||||||
|
|
@ -34,40 +34,20 @@ void TcpClient::processIncomeingJson(const QByteArray& jsonbytes)
|
||||||
if(type == "ItemUpdate")
|
if(type == "ItemUpdate")
|
||||||
{
|
{
|
||||||
QJsonArray data = json["Data"].toArray();
|
QJsonArray data = json["Data"].toArray();
|
||||||
bool FullList = json["FullList"].toBool(false);
|
|
||||||
std::vector<std::shared_ptr<Item>> items;
|
std::vector<std::shared_ptr<Item>> items;
|
||||||
std::vector<ItemFieldChanges> fieldChanges;
|
|
||||||
for(QJsonValueRef itemjson : data)
|
for(QJsonValueRef itemjson : data)
|
||||||
{
|
{
|
||||||
QJsonObject jsonobject = itemjson.toObject();
|
QJsonObject jsonobject = itemjson.toObject();
|
||||||
std::shared_ptr<Item> item = Item::loadItem(jsonobject);
|
std::shared_ptr<Item> item = Item::loadItem(jsonobject);
|
||||||
if(item)
|
if(item)
|
||||||
{
|
{
|
||||||
item->setLoaded(FullList);
|
qDebug()<<"Client got item"<<item->getName();
|
||||||
fieldChanges.push_back(item->loadWithChanges(jsonobject));
|
item->setLoaded(false);
|
||||||
items.push_back(item);
|
items.push_back(item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(FullList && !items.empty())
|
if(!items.empty())
|
||||||
{
|
gotItems(items, ITEM_UPDATE_REMOTE);
|
||||||
qDebug()<<"Client replaceing items";
|
|
||||||
requestReplaceItems(items);
|
|
||||||
}
|
|
||||||
else if(!items.empty())
|
|
||||||
{
|
|
||||||
std::vector<ItemAddRequest> itemAddRequests;
|
|
||||||
qDebug()<<"Client updateing items";
|
|
||||||
for(size_t i = 0; i < items.size(); i++)
|
|
||||||
{
|
|
||||||
ItemAddRequest request;
|
|
||||||
request.type = ITEM_UPDATE_REMOTE;
|
|
||||||
request.payload = items[i];
|
|
||||||
request.changes = fieldChanges[i];
|
|
||||||
itemAddRequests.push_back(request);
|
|
||||||
qDebug()<<"Payload"<<request.payload->id();
|
|
||||||
}
|
|
||||||
gotItems(itemAddRequests);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -120,7 +100,7 @@ void TcpClient::itemUpdated(ItemUpdateRequest update)
|
||||||
{
|
{
|
||||||
QJsonArray items;
|
QJsonArray items;
|
||||||
QJsonObject itemjson;
|
QJsonObject itemjson;
|
||||||
update.payload.storeWithChanges(itemjson, update.changes);
|
update.payload.store(itemjson);
|
||||||
items.append(itemjson);
|
items.append(itemjson);
|
||||||
QJsonObject json = createMessage("ItemUpdate", items);
|
QJsonObject json = createMessage("ItemUpdate", items);
|
||||||
json["FullList"] = false;
|
json["FullList"] = false;
|
||||||
|
|
|
||||||
|
|
@ -16,8 +16,6 @@ public:
|
||||||
TcpServer(QObject* parent = nullptr);
|
TcpServer(QObject* parent = nullptr);
|
||||||
virtual bool launch(const QHostAddress &address = QHostAddress::Any, quint16 port = 0) override;
|
virtual bool launch(const QHostAddress &address = QHostAddress::Any, quint16 port = 0) override;
|
||||||
virtual void sendJson(const QJsonObject& json) override;
|
virtual void sendJson(const QJsonObject& json) override;
|
||||||
quint16 getServerPort() const { return server.serverPort(); }
|
|
||||||
bool isListening() const { return server.isListening(); }
|
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void sigRequestSave();
|
void sigRequestSave();
|
||||||
|
|
|
||||||
|
|
@ -106,11 +106,8 @@ ActorSettingsDialog::~ActorSettingsDialog()
|
||||||
|
|
||||||
void ActorSettingsDialog::editAsItem()
|
void ActorSettingsDialog::editAsItem()
|
||||||
{
|
{
|
||||||
setModal(false);
|
ItemSettingsDialog itemSettingsDiag(actor_, this);
|
||||||
ItemSettingsDialog itemSettingsDiag(actor_, false, this);
|
|
||||||
itemSettingsDiag.setModal(false);
|
|
||||||
itemSettingsDiag.exec();
|
itemSettingsDiag.exec();
|
||||||
setModal(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ActorSettingsDialog::setEnabled()
|
void ActorSettingsDialog::setEnabled()
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,6 @@
|
||||||
|
|
||||||
#include "itemsettingswidgets/messageitemsettingswidget.h"
|
#include "itemsettingswidgets/messageitemsettingswidget.h"
|
||||||
#include "itemsettingswidgets/systemitemsettingswidget.h"
|
#include "itemsettingswidgets/systemitemsettingswidget.h"
|
||||||
#include "itemsettingswidgets/mqttitemsettingswidget.h"
|
|
||||||
#include "items/mqttitem.h"
|
|
||||||
|
|
||||||
ItemCreationDialog::ItemCreationDialog(QWidget *parent) :
|
ItemCreationDialog::ItemCreationDialog(QWidget *parent) :
|
||||||
QDialog(parent),
|
QDialog(parent),
|
||||||
|
|
@ -43,13 +41,6 @@ void ItemCreationDialog::itemTypeChanged(const QString& type)
|
||||||
widget = new SystemItemSettingsWidget(systemItem, this);
|
widget = new SystemItemSettingsWidget(systemItem, this);
|
||||||
ui->verticalLayout->addWidget(widget);
|
ui->verticalLayout->addWidget(widget);
|
||||||
}
|
}
|
||||||
if(type == "Mqtt")
|
|
||||||
{
|
|
||||||
std::shared_ptr<MqttItem> mqttItem(new MqttItem);
|
|
||||||
item = mqttItem;
|
|
||||||
widget = new MqttItemSettingsWidget(mqttItem, this);
|
|
||||||
ui->verticalLayout->addWidget(widget);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ItemCreationDialog::itemNameChanged(const QString& name)
|
void ItemCreationDialog::itemNameChanged(const QString& name)
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>400</width>
|
<width>400</width>
|
||||||
<height>274</height>
|
<height>140</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<property name="windowTitle">
|
<property name="windowTitle">
|
||||||
|
|
@ -39,11 +39,6 @@
|
||||||
<string>System</string>
|
<string>System</string>
|
||||||
</property>
|
</property>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
|
||||||
<property name="text">
|
|
||||||
<string>Mqtt</string>
|
|
||||||
</property>
|
|
||||||
</item>
|
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
|
|
@ -69,26 +64,13 @@
|
||||||
<item>
|
<item>
|
||||||
<layout class="QVBoxLayout" name="verticalLayout"/>
|
<layout class="QVBoxLayout" name="verticalLayout"/>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
|
||||||
<spacer name="verticalSpacer">
|
|
||||||
<property name="orientation">
|
|
||||||
<enum>Qt::Orientation::Vertical</enum>
|
|
||||||
</property>
|
|
||||||
<property name="sizeHint" stdset="0">
|
|
||||||
<size>
|
|
||||||
<width>20</width>
|
|
||||||
<height>40</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
</spacer>
|
|
||||||
</item>
|
|
||||||
<item>
|
<item>
|
||||||
<widget class="QDialogButtonBox" name="buttonBox">
|
<widget class="QDialogButtonBox" name="buttonBox">
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
<enum>Qt::Orientation::Horizontal</enum>
|
<enum>Qt::Horizontal</enum>
|
||||||
</property>
|
</property>
|
||||||
<property name="standardButtons">
|
<property name="standardButtons">
|
||||||
<set>QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok</set>
|
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,15 @@
|
||||||
#include "itemscrollbox.h"
|
#include "itemscrollbox.h"
|
||||||
#include "ui_relayscrollbox.h"
|
#include "ui_relayscrollbox.h"
|
||||||
#include <QScrollArea>
|
#include "../items/auxitem.h"
|
||||||
#include <QScroller>
|
#include "../items/messageitem.h"
|
||||||
#include <QFrame>
|
|
||||||
|
|
||||||
ItemScrollBox::ItemScrollBox(QWidget *parent) :
|
ItemScrollBox::ItemScrollBox(QWidget *parent) :
|
||||||
QWidget(parent),
|
QWidget(parent),
|
||||||
ui(new Ui::RelayScrollBox)
|
ui(new Ui::RelayScrollBox)
|
||||||
{
|
{
|
||||||
ui->setupUi(this);
|
ui->setupUi(this);
|
||||||
|
QScroller::grabGesture(ui->scrollArea, QScroller::TouchGesture);
|
||||||
ensureTabExists("All");
|
QScroller::grabGesture(ui->scrollArea, QScroller::LeftMouseButtonGesture);
|
||||||
ui->tabWidget->setCurrentIndex(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ItemScrollBox::~ItemScrollBox()
|
ItemScrollBox::~ItemScrollBox()
|
||||||
|
|
@ -25,167 +23,24 @@ void ItemScrollBox::addItem(std::weak_ptr<Item> item)
|
||||||
{
|
{
|
||||||
if(workItem->isHidden())
|
if(workItem->isHidden())
|
||||||
return;
|
return;
|
||||||
|
widgets_.push_back(new ItemWidget(item));
|
||||||
// Add to "All" tab
|
ui->relayWidgetVbox->addWidget(widgets_.back());
|
||||||
widgets_["All"].push_back(new ItemWidget(item, false));
|
connect(widgets_.back(), &ItemWidget::deleteRequest, this, &ItemScrollBox::deleteRequest);
|
||||||
QWidget* allScrollContent = tabs_["All"].content;
|
connect(widgets_.back(), &ItemWidget::deleteRequest, this, &ItemScrollBox::removeItem);
|
||||||
QLayout* layout = allScrollContent->layout();
|
|
||||||
layout->removeItem(tabs_["All"].spacer);
|
|
||||||
layout->addWidget(widgets_["All"].back());
|
|
||||||
layout->addItem(tabs_["All"].spacer);
|
|
||||||
connect(widgets_["All"].back(), &ItemWidget::deleteRequest, this, &ItemScrollBox::deleteRequest);
|
|
||||||
connect(widgets_["All"].back(), &ItemWidget::deleteRequest, this, &ItemScrollBox::removeItem);
|
|
||||||
|
|
||||||
addItemToTabs(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ItemScrollBox::addItemToTabs(std::weak_ptr<Item> item)
|
|
||||||
{
|
|
||||||
if(auto workItem = item.lock())
|
|
||||||
{
|
|
||||||
if(workItem->isHidden())
|
|
||||||
return;
|
|
||||||
|
|
||||||
QString groupName = workItem->getGroupName();
|
|
||||||
if(groupName.isEmpty() || groupName != "All")
|
|
||||||
{
|
|
||||||
ensureTabExists(groupName);
|
|
||||||
ItemWidget* groupWidget = new ItemWidget(item, true);
|
|
||||||
widgets_[groupName].push_back(groupWidget);
|
|
||||||
|
|
||||||
QWidget* scrollContent = tabs_[groupName].content;
|
|
||||||
QLayout* groupLayout = scrollContent->layout();
|
|
||||||
groupLayout->removeItem(tabs_[groupName].spacer);
|
|
||||||
groupLayout->addWidget(groupWidget);
|
|
||||||
groupLayout->addItem(tabs_[groupName].spacer);
|
|
||||||
|
|
||||||
connect(groupWidget, &ItemWidget::deleteRequest, this, &ItemScrollBox::deleteRequest);
|
|
||||||
connect(groupWidget, &ItemWidget::deleteRequest, this, &ItemScrollBox::removeItem);
|
|
||||||
|
|
||||||
connect(widgets_[groupName].back(), &ItemWidget::deleteRequest, this, &ItemScrollBox::deleteRequest);
|
|
||||||
connect(widgets_[groupName].back(), &ItemWidget::deleteRequest, this, &ItemScrollBox::removeItem);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ItemScrollBox::removeItem(const ItemData& item)
|
void ItemScrollBox::removeItem(const ItemData& item)
|
||||||
{
|
{
|
||||||
QString key = "All";
|
for(unsigned i = 0; i < widgets_.size(); i++)
|
||||||
std::vector<ItemWidget*>& widgets = widgets_[key];
|
|
||||||
for(unsigned i = 0; i < widgets.size(); i++)
|
|
||||||
{
|
{
|
||||||
if(widgets[i]->controles(item))
|
if(widgets_[i]->controles(item))
|
||||||
{
|
{
|
||||||
QWidget* tabContent = tabs_[key].content;
|
ui->relayWidgetVbox->removeWidget(widgets_[i]);
|
||||||
if(tabContent)
|
delete widgets_[i];
|
||||||
{
|
widgets_.erase(widgets_.begin()+i);
|
||||||
QLayout* layout = tabContent->layout();
|
|
||||||
if(layout)
|
|
||||||
layout->removeWidget(widgets[i]);
|
|
||||||
}
|
|
||||||
delete widgets[i];
|
|
||||||
widgets.erase(widgets.begin()+i);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
removeItemFromTabs(item);
|
|
||||||
cleanupEmptyTabs();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ItemScrollBox::removeItemFromTabs(const ItemData& item)
|
|
||||||
{
|
|
||||||
for(const QString& key : widgets_.keys())
|
|
||||||
{
|
|
||||||
if(key == "All")
|
|
||||||
continue;
|
|
||||||
std::vector<ItemWidget*>& widgets = widgets_[key];
|
|
||||||
for(unsigned i = 0; i < widgets.size(); i++)
|
|
||||||
{
|
|
||||||
if(widgets[i]->controles(item))
|
|
||||||
{
|
|
||||||
QWidget* tabContent = tabs_[key].content;
|
|
||||||
if(tabContent)
|
|
||||||
{
|
|
||||||
QLayout* layout = tabContent->layout();
|
|
||||||
if(layout)
|
|
||||||
layout->removeWidget(widgets[i]);
|
|
||||||
}
|
|
||||||
delete widgets[i];
|
|
||||||
widgets.erase(widgets.begin()+i);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanupEmptyTabs();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void ItemScrollBox::onItemUpdate(const ItemUpdateRequest& update)
|
|
||||||
{
|
|
||||||
if(!update.changes.groupName)
|
|
||||||
return;
|
|
||||||
|
|
||||||
for(ItemWidget* widget : widgets_["All"])
|
|
||||||
{
|
|
||||||
if(widget->controles(update.payload))
|
|
||||||
{
|
|
||||||
qDebug()<<"ItemUpdate with group change for item"<<update.payload.getName()<<"type"<<update.type;
|
|
||||||
std::weak_ptr<Item> item = widget->getItem();
|
|
||||||
removeItemFromTabs(update.payload);
|
|
||||||
addItemToTabs(item);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ItemScrollBox::ensureTabExists(const QString& groupName)
|
|
||||||
{
|
|
||||||
if(!tabs_.contains(groupName))
|
|
||||||
{
|
|
||||||
Tab tab;
|
|
||||||
tab.scroller = new QScrollArea(ui->tabWidget);
|
|
||||||
tab.scroller->setWidgetResizable(true);
|
|
||||||
tab.scroller->setFrameShape(QFrame::NoFrame);
|
|
||||||
tab.scroller->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
|
||||||
QScroller::grabGesture(tab.scroller->viewport(), QScroller::LeftMouseButtonGesture);
|
|
||||||
|
|
||||||
tab.content = new QWidget(tab.scroller);
|
|
||||||
QVBoxLayout* scrollLayout = new QVBoxLayout(tab.content);
|
|
||||||
scrollLayout->setContentsMargins(0, 0, 0, 0);
|
|
||||||
tab.content->setLayout(scrollLayout);
|
|
||||||
tab.content->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum);
|
|
||||||
tab.scroller->setWidget(tab.content);
|
|
||||||
|
|
||||||
tab.spacer = new QSpacerItem(0, 0, QSizePolicy::Fixed, QSizePolicy::Expanding);
|
|
||||||
scrollLayout->addSpacerItem(tab.spacer);
|
|
||||||
|
|
||||||
ui->tabWidget->addTab(tab.scroller, groupName);
|
|
||||||
tabs_[groupName] = tab;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ItemScrollBox::cleanupEmptyTabs()
|
|
||||||
{
|
|
||||||
for(auto it = tabs_.begin(); it != tabs_.end(); ++it)
|
|
||||||
{
|
|
||||||
QString groupName = it.key();
|
|
||||||
if(groupName == "All")
|
|
||||||
continue;
|
|
||||||
|
|
||||||
qDebug()<<__func__<<it.value().content->layout()->count();
|
|
||||||
|
|
||||||
if(it.value().content->layout()->count() <= 1)
|
|
||||||
{
|
|
||||||
int index = ui->tabWidget->indexOf(tabs_[groupName].scroller);
|
|
||||||
if(index >= 0)
|
|
||||||
ui->tabWidget->removeTab(index);
|
|
||||||
Tab tab = tabs_.take(groupName);
|
|
||||||
delete tab.content;
|
|
||||||
delete tab.scroller;
|
|
||||||
cleanupEmptyTabs();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,9 @@
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <QScrollArea>
|
#include <QScroller>
|
||||||
#include <QSpacerItem>
|
|
||||||
#include "itemwidget.h"
|
#include "itemwidget.h"
|
||||||
|
#include "../items/relay.h"
|
||||||
#include "../items/item.h"
|
#include "../items/item.h"
|
||||||
#include "../items/itemstore.h"
|
#include "../items/itemstore.h"
|
||||||
|
|
||||||
|
|
@ -20,16 +20,7 @@ class ItemScrollBox : public QWidget
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
private:
|
private:
|
||||||
struct Tab
|
std::vector< ItemWidget* > widgets_;
|
||||||
{
|
|
||||||
QScrollArea* scroller;
|
|
||||||
QWidget* content;
|
|
||||||
QSpacerItem* spacer;
|
|
||||||
};
|
|
||||||
|
|
||||||
QMap<QString, Tab> tabs_;
|
|
||||||
QMap<QString, std::vector<ItemWidget*>> widgets_;
|
|
||||||
Ui::RelayScrollBox *ui;
|
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void deleteRequest(const ItemData& item);
|
void deleteRequest(const ItemData& item);
|
||||||
|
|
@ -45,15 +36,9 @@ public slots:
|
||||||
|
|
||||||
void addItem(std::weak_ptr<Item> item);
|
void addItem(std::weak_ptr<Item> item);
|
||||||
void removeItem(const ItemData& item);
|
void removeItem(const ItemData& item);
|
||||||
void onItemUpdate(const ItemUpdateRequest& update);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void ensureTabExists(const QString& groupName);
|
Ui::RelayScrollBox *ui;
|
||||||
void cleanupEmptyTabs();
|
|
||||||
|
|
||||||
private:
|
|
||||||
void addItemToTabs(std::weak_ptr<Item> item);
|
|
||||||
void removeItemFromTabs(const ItemData& item);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // RELAYSCROLLBOX_H
|
#endif // RELAYSCROLLBOX_H
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
#include "itemsettingsdialog.h"
|
#include "itemsettingsdialog.h"
|
||||||
#include "ui_itemsettingsdialog.h"
|
#include "ui_itemsettingsdialog.h"
|
||||||
#include "../items/itemstore.h"
|
|
||||||
#include "actorsettingsdialog.h"
|
#include "actorsettingsdialog.h"
|
||||||
#include "../actors/alarmtime.h"
|
#include "../actors/alarmtime.h"
|
||||||
#include "../actors/sensoractor.h"
|
#include "../actors/sensoractor.h"
|
||||||
|
|
@ -12,11 +11,9 @@
|
||||||
#include "itemsettingswidgets/messageitemsettingswidget.h"
|
#include "itemsettingswidgets/messageitemsettingswidget.h"
|
||||||
#include "itemsettingswidgets/systemitemsettingswidget.h"
|
#include "itemsettingswidgets/systemitemsettingswidget.h"
|
||||||
#include "itemsettingswidgets/relayitemsettingswidget.h"
|
#include "itemsettingswidgets/relayitemsettingswidget.h"
|
||||||
#include "itemsettingswidgets/mqttitemsettingswidget.h"
|
|
||||||
#include "../items/mqttitem.h"
|
|
||||||
#include<memory>
|
#include<memory>
|
||||||
|
|
||||||
ItemSettingsDialog::ItemSettingsDialog(std::shared_ptr<Item> item, bool noGroup, QWidget *parent) :
|
ItemSettingsDialog::ItemSettingsDialog(std::shared_ptr<Item> item, QWidget *parent) :
|
||||||
QDialog(parent),
|
QDialog(parent),
|
||||||
item_(item),
|
item_(item),
|
||||||
ui(new Ui::ItemSettingsDialog)
|
ui(new Ui::ItemSettingsDialog)
|
||||||
|
|
@ -25,17 +22,9 @@ ItemSettingsDialog::ItemSettingsDialog(std::shared_ptr<Item> item, bool noGroup,
|
||||||
|
|
||||||
setModal(false);
|
setModal(false);
|
||||||
|
|
||||||
if(noGroup)
|
|
||||||
ui->comboBox_Group->setEnabled(false);
|
|
||||||
|
|
||||||
ui->label_name->setText(item_->getName());
|
ui->label_name->setText(item_->getName());
|
||||||
ui->checkBox_Override->setChecked(item_->getOverride());
|
ui->checkBox_Override->setChecked(item_->getOverride());
|
||||||
|
|
||||||
// Setup group combobox with editable mode for creating new groups
|
|
||||||
ui->comboBox_Group->setEditable(true);
|
|
||||||
ui->comboBox_Group->addItem("All");
|
|
||||||
ui->comboBox_Group->addItems(getExistingGroups());
|
|
||||||
ui->comboBox_Group->setCurrentText(item_->getGroupName());
|
|
||||||
|
|
||||||
if(std::shared_ptr<Relay> relay = std::dynamic_pointer_cast<Relay>(item_))
|
if(std::shared_ptr<Relay> relay = std::dynamic_pointer_cast<Relay>(item_))
|
||||||
{
|
{
|
||||||
|
|
@ -49,10 +38,6 @@ ItemSettingsDialog::ItemSettingsDialog(std::shared_ptr<Item> item, bool noGroup,
|
||||||
{
|
{
|
||||||
itemSpecificWidget_ = new SystemItemSettingsWidget(sysItem);
|
itemSpecificWidget_ = new SystemItemSettingsWidget(sysItem);
|
||||||
}
|
}
|
||||||
else if(std::shared_ptr<MqttItem> mqttItem = std::dynamic_pointer_cast<MqttItem>(item_))
|
|
||||||
{
|
|
||||||
itemSpecificWidget_ = new MqttItemSettingsWidget(mqttItem);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(itemSpecificWidget_)
|
if(itemSpecificWidget_)
|
||||||
{
|
{
|
||||||
|
|
@ -63,7 +48,6 @@ ItemSettingsDialog::ItemSettingsDialog(std::shared_ptr<Item> item, bool noGroup,
|
||||||
connect(ui->pushButton_remove, &QPushButton::clicked, this, &ItemSettingsDialog::removeActor);
|
connect(ui->pushButton_remove, &QPushButton::clicked, this, &ItemSettingsDialog::removeActor);
|
||||||
connect(ui->pushButton_edit, &QPushButton::clicked, this, &ItemSettingsDialog::editActor);
|
connect(ui->pushButton_edit, &QPushButton::clicked, this, &ItemSettingsDialog::editActor);
|
||||||
connect(ui->checkBox_Override, &QPushButton::clicked, this, &ItemSettingsDialog::changeOverride);
|
connect(ui->checkBox_Override, &QPushButton::clicked, this, &ItemSettingsDialog::changeOverride);
|
||||||
connect(ui->comboBox_Group, &QComboBox::currentTextChanged, this, &ItemSettingsDialog::changeGroup);
|
|
||||||
|
|
||||||
|
|
||||||
ui->tableWidget->setHorizontalHeaderItem(0, new QTableWidgetItem("Actor"));
|
ui->tableWidget->setHorizontalHeaderItem(0, new QTableWidgetItem("Actor"));
|
||||||
|
|
@ -77,23 +61,10 @@ ItemSettingsDialog::ItemSettingsDialog(std::shared_ptr<Item> item, bool noGroup,
|
||||||
|
|
||||||
ItemSettingsDialog::~ItemSettingsDialog()
|
ItemSettingsDialog::~ItemSettingsDialog()
|
||||||
{
|
{
|
||||||
if(itemSpecificWidget_)
|
if(itemSpecificWidget_) delete itemSpecificWidget_;
|
||||||
delete itemSpecificWidget_;
|
|
||||||
delete ui;
|
delete ui;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ItemSettingsDialog::changeGroup()
|
|
||||||
{
|
|
||||||
QString newGroup = ui->comboBox_Group->currentText();
|
|
||||||
if(newGroup != item_->getGroupName())
|
|
||||||
{
|
|
||||||
ItemUpdateRequest update = item_->createValueUpdateRequest(ITEM_UPDATE_USER);
|
|
||||||
update.payload.setGroupName(newGroup);
|
|
||||||
update.changes.groupName = true;
|
|
||||||
item_->requestUpdate(update);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ItemSettingsDialog::changeOverride()
|
void ItemSettingsDialog::changeOverride()
|
||||||
{
|
{
|
||||||
item_->setOverride(ui->checkBox_Override->isChecked());
|
item_->setOverride(ui->checkBox_Override->isChecked());
|
||||||
|
|
@ -195,21 +166,14 @@ void ItemSettingsDialog::editActor()
|
||||||
|
|
||||||
ActorSettingsDialog* dialog;
|
ActorSettingsDialog* dialog;
|
||||||
|
|
||||||
if(alarmTime)
|
if(alarmTime) dialog = new ActorSettingsDialog(alarmTime, this);
|
||||||
dialog = new ActorSettingsDialog(alarmTime, this);
|
else if(regulator) dialog = new ActorSettingsDialog(regulator, this);
|
||||||
else if(regulator)
|
else if(sensorActor) dialog = new ActorSettingsDialog(sensorActor, this);
|
||||||
dialog = new ActorSettingsDialog(regulator, this);
|
else if(timerActor) dialog = new ActorSettingsDialog(timerActor, this);
|
||||||
else if(sensorActor)
|
else if(polynomalActor) dialog = new ActorSettingsDialog(polynomalActor, this);
|
||||||
dialog = new ActorSettingsDialog(sensorActor, this);
|
else if(factorActor) dialog = new ActorSettingsDialog(factorActor, this);
|
||||||
else if(timerActor)
|
else dialog = new ActorSettingsDialog(actor, this);
|
||||||
dialog = new ActorSettingsDialog(timerActor, this);
|
dialog->setParent(this);
|
||||||
else if(polynomalActor)
|
|
||||||
dialog = new ActorSettingsDialog(polynomalActor, this);
|
|
||||||
else if(factorActor)
|
|
||||||
dialog = new ActorSettingsDialog(factorActor, this);
|
|
||||||
else
|
|
||||||
dialog = new ActorSettingsDialog(actor, this);
|
|
||||||
|
|
||||||
dialog->show();
|
dialog->show();
|
||||||
dialog->exec();
|
dialog->exec();
|
||||||
|
|
||||||
|
|
@ -219,19 +183,5 @@ void ItemSettingsDialog::editActor()
|
||||||
ui->tableWidget->item(i, 1)->setText(item_->getActors()[i]->actionName());
|
ui->tableWidget->item(i, 1)->setText(item_->getActors()[i]->actionName());
|
||||||
ui->tableWidget->item(i, 2)->setText(item_->getActors()[i]->isActive() ? "Y" : "N");
|
ui->tableWidget->item(i, 2)->setText(item_->getActors()[i]->isActive() ? "Y" : "N");
|
||||||
}
|
}
|
||||||
delete dialog;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QStringList ItemSettingsDialog::getExistingGroups()
|
|
||||||
{
|
|
||||||
QSet<QString> uniqueGroups;
|
|
||||||
for(const auto& item : *globalItems.getItems())
|
|
||||||
{
|
|
||||||
if(!item->getGroupName().isEmpty() && item->getGroupName() != "All")
|
|
||||||
{
|
|
||||||
uniqueGroups.insert(item->getGroupName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return uniqueGroups.values();
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -19,10 +19,9 @@ class ItemSettingsDialog : public QDialog
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void loadActorList();
|
void loadActorList();
|
||||||
QStringList getExistingGroups();
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit ItemSettingsDialog(std::shared_ptr<Item> item, bool noGroup = false, QWidget *parent = nullptr);
|
explicit ItemSettingsDialog(std::shared_ptr<Item> item, QWidget *parent = nullptr);
|
||||||
~ItemSettingsDialog();
|
~ItemSettingsDialog();
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
|
|
@ -31,7 +30,6 @@ private slots:
|
||||||
void addActor();
|
void addActor();
|
||||||
void editActor();
|
void editActor();
|
||||||
void changeOverride();
|
void changeOverride();
|
||||||
void changeGroup();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Ui::ItemSettingsDialog *ui;
|
Ui::ItemSettingsDialog *ui;
|
||||||
|
|
|
||||||
|
|
@ -63,20 +63,6 @@
|
||||||
<item>
|
<item>
|
||||||
<layout class="QVBoxLayout" name="verticalLayout_2"/>
|
<layout class="QVBoxLayout" name="verticalLayout_2"/>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout_group">
|
|
||||||
<item>
|
|
||||||
<widget class="QLabel" name="label_Group">
|
|
||||||
<property name="text">
|
|
||||||
<string>Group:</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QComboBox" name="comboBox_Group"/>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</item>
|
|
||||||
<item>
|
<item>
|
||||||
<widget class="QCheckBox" name="checkBox_Override">
|
<widget class="QCheckBox" name="checkBox_Override">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
|
|
|
||||||
|
|
@ -1,62 +0,0 @@
|
||||||
#include "mqttitemsettingswidget.h"
|
|
||||||
#include "ui_mqttitemsettingswidget.h"
|
|
||||||
|
|
||||||
#include <QDebug>
|
|
||||||
|
|
||||||
MqttItemSettingsWidget::MqttItemSettingsWidget(std::weak_ptr<MqttItem> item, QWidget *parent) :
|
|
||||||
QWidget(parent),
|
|
||||||
item_(item),
|
|
||||||
ui(new Ui::MqttItemSettingsWidget)
|
|
||||||
{
|
|
||||||
ui->setupUi(this);
|
|
||||||
|
|
||||||
if(auto workingItem = item_.lock())
|
|
||||||
{
|
|
||||||
ui->lineEdit_topic->setText(workingItem->getTopic());
|
|
||||||
ui->lineEdit_valueKey->setText(workingItem->getValueKey());
|
|
||||||
ui->lineEdit_valueOn->setText(workingItem->getValueOn());
|
|
||||||
ui->lineEdit_valueOff->setText(workingItem->getValueOff());
|
|
||||||
}
|
|
||||||
|
|
||||||
connect(ui->lineEdit_topic, &QLineEdit::textChanged, this, &MqttItemSettingsWidget::setTopic);
|
|
||||||
connect(ui->lineEdit_valueKey, &QLineEdit::textChanged, this, &MqttItemSettingsWidget::setValueKey);
|
|
||||||
connect(ui->lineEdit_valueOn, &QLineEdit::textChanged, this, &MqttItemSettingsWidget::setValueOn);
|
|
||||||
connect(ui->lineEdit_valueOff, &QLineEdit::textChanged, this, &MqttItemSettingsWidget::setValueOff);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MqttItemSettingsWidget::setTopic(const QString& topic)
|
|
||||||
{
|
|
||||||
if(auto workingItem = item_.lock())
|
|
||||||
{
|
|
||||||
workingItem->setTopic(topic);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MqttItemSettingsWidget::setValueKey(const QString& valueKey)
|
|
||||||
{
|
|
||||||
if(auto workingItem = item_.lock())
|
|
||||||
{
|
|
||||||
workingItem->setValueKey(valueKey);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MqttItemSettingsWidget::setValueOn(const QString& valueOn)
|
|
||||||
{
|
|
||||||
if(auto workingItem = item_.lock())
|
|
||||||
{
|
|
||||||
workingItem->setValueOn(valueOn);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MqttItemSettingsWidget::setValueOff(const QString& valueOff)
|
|
||||||
{
|
|
||||||
if(auto workingItem = item_.lock())
|
|
||||||
{
|
|
||||||
workingItem->setValueOff(valueOff);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MqttItemSettingsWidget::~MqttItemSettingsWidget()
|
|
||||||
{
|
|
||||||
delete ui;
|
|
||||||
}
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
#ifndef MQTTITEMSETTINGSWIDGET_H
|
|
||||||
#define MQTTITEMSETTINGSWIDGET_H
|
|
||||||
|
|
||||||
#include <QWidget>
|
|
||||||
#include <memory>
|
|
||||||
#include "../../items/mqttitem.h"
|
|
||||||
|
|
||||||
namespace Ui
|
|
||||||
{
|
|
||||||
class MqttItemSettingsWidget;
|
|
||||||
}
|
|
||||||
|
|
||||||
class MqttItemSettingsWidget : public QWidget
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
std::weak_ptr<MqttItem> item_;
|
|
||||||
|
|
||||||
private slots:
|
|
||||||
void setTopic(const QString& topic);
|
|
||||||
void setValueKey(const QString& valueKey);
|
|
||||||
void setValueOn(const QString& valueOn);
|
|
||||||
void setValueOff(const QString& valueOff);
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit MqttItemSettingsWidget(std::weak_ptr<MqttItem> item, QWidget *parent = nullptr);
|
|
||||||
~MqttItemSettingsWidget();
|
|
||||||
|
|
||||||
private:
|
|
||||||
Ui::MqttItemSettingsWidget *ui;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // MQTTITEMSETTINGSWIDGET_H
|
|
||||||
|
|
@ -1,108 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<ui version="4.0">
|
|
||||||
<class>MqttItemSettingsWidget</class>
|
|
||||||
<widget class="QWidget" name="MqttItemSettingsWidget">
|
|
||||||
<property name="geometry">
|
|
||||||
<rect>
|
|
||||||
<x>0</x>
|
|
||||||
<y>0</y>
|
|
||||||
<width>400</width>
|
|
||||||
<height>216</height>
|
|
||||||
</rect>
|
|
||||||
</property>
|
|
||||||
<property name="windowTitle">
|
|
||||||
<string>Form</string>
|
|
||||||
</property>
|
|
||||||
<layout class="QVBoxLayout" name="verticalLayout">
|
|
||||||
<item>
|
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout_topic">
|
|
||||||
<property name="topMargin">
|
|
||||||
<number>0</number>
|
|
||||||
</property>
|
|
||||||
<item>
|
|
||||||
<widget class="QLabel" name="label_topic">
|
|
||||||
<property name="text">
|
|
||||||
<string>Topic:</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QLineEdit" name="lineEdit_topic">
|
|
||||||
<property name="placeholderText">
|
|
||||||
<string>e.g., kitchen/light</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout_valueKey">
|
|
||||||
<property name="topMargin">
|
|
||||||
<number>0</number>
|
|
||||||
</property>
|
|
||||||
<item>
|
|
||||||
<widget class="QLabel" name="label_valueKey">
|
|
||||||
<property name="text">
|
|
||||||
<string>Value Key:</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QLineEdit" name="lineEdit_valueKey">
|
|
||||||
<property name="text">
|
|
||||||
<string>state</string>
|
|
||||||
</property>
|
|
||||||
<property name="placeholderText">
|
|
||||||
<string>e.g., state, brightness</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout_valueOn">
|
|
||||||
<property name="topMargin">
|
|
||||||
<number>0</number>
|
|
||||||
</property>
|
|
||||||
<item>
|
|
||||||
<widget class="QLabel" name="label_valueOn">
|
|
||||||
<property name="text">
|
|
||||||
<string>Value On:</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QLineEdit" name="lineEdit_valueOn">
|
|
||||||
<property name="text">
|
|
||||||
<string>ON</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout_valueOff">
|
|
||||||
<property name="topMargin">
|
|
||||||
<number>0</number>
|
|
||||||
</property>
|
|
||||||
<item>
|
|
||||||
<widget class="QLabel" name="label_valueOff">
|
|
||||||
<property name="text">
|
|
||||||
<string>Value Off:</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QLineEdit" name="lineEdit_valueOff">
|
|
||||||
<property name="text">
|
|
||||||
<string>OFF</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</widget>
|
|
||||||
<resources/>
|
|
||||||
<connections/>
|
|
||||||
</ui>
|
|
||||||
|
|
@ -2,15 +2,12 @@
|
||||||
#include "ui_itemwidget.h"
|
#include "ui_itemwidget.h"
|
||||||
|
|
||||||
#include <QCheckBox>
|
#include <QCheckBox>
|
||||||
#include <QComboBox>
|
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QSlider>
|
#include <QSlider>
|
||||||
#include "itemsettingsdialog.h"
|
|
||||||
|
|
||||||
ItemWidget::ItemWidget(std::weak_ptr<Item> item, bool noGroupEdit, QWidget *parent) :
|
ItemWidget::ItemWidget(std::weak_ptr<Item> item, QWidget *parent) :
|
||||||
QWidget(parent),
|
QWidget(parent),
|
||||||
item_(item),
|
item_(item),
|
||||||
noGroupEdit_(noGroupEdit),
|
|
||||||
ui(new Ui::ItemWidget)
|
ui(new Ui::ItemWidget)
|
||||||
{
|
{
|
||||||
ui->setupUi(this);
|
ui->setupUi(this);
|
||||||
|
|
@ -21,28 +18,15 @@ ItemWidget::ItemWidget(std::weak_ptr<Item> item, bool noGroupEdit, QWidget *pare
|
||||||
{
|
{
|
||||||
ui->horizontalSpacer->changeSize(0,0);
|
ui->horizontalSpacer->changeSize(0,0);
|
||||||
ui->checkBox->hide();
|
ui->checkBox->hide();
|
||||||
ui->comboBox->hide();
|
|
||||||
}
|
}
|
||||||
else if(workingItem->getValueType() == ITEM_VALUE_NO_VALUE)
|
else if(workingItem->getValueType() == ITEM_VALUE_NO_VALUE)
|
||||||
{
|
{
|
||||||
ui->checkBox->hide();
|
ui->checkBox->hide();
|
||||||
ui->slider->hide();
|
ui->slider->hide();
|
||||||
ui->comboBox->hide();
|
|
||||||
}
|
|
||||||
else if(workingItem->getValueType() == ITEM_VALUE_ENUM)
|
|
||||||
{
|
|
||||||
ui->slider->hide();
|
|
||||||
ui->checkBox->hide();
|
|
||||||
QStringList list;
|
|
||||||
for(const QString& name : workingItem->getValueNames())
|
|
||||||
list.append(name);
|
|
||||||
ui->comboBox->addItems(list);
|
|
||||||
ui->comboBox->setCurrentIndex(workingItem->getValue());
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ui->slider->hide();
|
ui->slider->hide();
|
||||||
ui->comboBox->hide();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ui->checkBox->setChecked(workingItem->getValue());
|
ui->checkBox->setChecked(workingItem->getValue());
|
||||||
|
|
@ -51,20 +35,17 @@ ItemWidget::ItemWidget(std::weak_ptr<Item> item, bool noGroupEdit, QWidget *pare
|
||||||
|
|
||||||
if(workingItem->getValueType() == ITEM_VALUE_UINT)
|
if(workingItem->getValueType() == ITEM_VALUE_UINT)
|
||||||
connect(ui->slider, &QSlider::valueChanged, this, &ItemWidget::moveToValue);
|
connect(ui->slider, &QSlider::valueChanged, this, &ItemWidget::moveToValue);
|
||||||
else if(workingItem->getValueType() == ITEM_VALUE_ENUM)
|
|
||||||
connect(ui->comboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &ItemWidget::moveToValue);
|
|
||||||
else
|
else
|
||||||
connect(ui->checkBox, &QCheckBox::toggled, this, &ItemWidget::moveToState);
|
connect(ui->checkBox, &QCheckBox::toggled, this, &ItemWidget::moveToState);
|
||||||
connect(ui->pushButton, &QPushButton::clicked, this, &ItemWidget::showSettingsDialog);
|
connect(ui->pushButton, &QPushButton::clicked, this, &ItemWidget::showSettingsDialog);
|
||||||
connect(workingItem.get(), &Item::updated, this, &ItemWidget::onItemUpdated);
|
connect(workingItem.get(), &Item::updated, this, &ItemWidget::onItemUpdated);
|
||||||
connect(ui->pushButton_Remove, &QPushButton::clicked, this, &ItemWidget::deleteItem);
|
connect(ui->pushButton_Remove, &QPushButton::clicked, this, &ItemWidget::deleteItem);
|
||||||
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
disable();
|
disable();
|
||||||
}
|
}
|
||||||
|
|
||||||
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ItemWidget::deleteItem()
|
void ItemWidget::deleteItem()
|
||||||
|
|
@ -79,9 +60,7 @@ void ItemWidget::moveToValue(int value)
|
||||||
{
|
{
|
||||||
if(auto workingItem = item_.lock())
|
if(auto workingItem = item_.lock())
|
||||||
{
|
{
|
||||||
ItemUpdateRequest request = workingItem->createValueUpdateRequest(ITEM_UPDATE_USER);
|
ItemUpdateRequest request = workingItem->createValueUpdateRequest(value, ITEM_UPDATE_USER);
|
||||||
request.payload.setValueData(value);
|
|
||||||
request.changes.value = true;
|
|
||||||
workingItem->requestUpdate(request);
|
workingItem->requestUpdate(request);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
@ -92,7 +71,15 @@ void ItemWidget::moveToValue(int value)
|
||||||
|
|
||||||
void ItemWidget::moveToState(bool state)
|
void ItemWidget::moveToState(bool state)
|
||||||
{
|
{
|
||||||
moveToValue(state);
|
if(auto workingItem = item_.lock())
|
||||||
|
{
|
||||||
|
ItemUpdateRequest request = workingItem->createValueUpdateRequest(state, ITEM_UPDATE_USER);
|
||||||
|
workingItem->requestUpdate(request);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
disable();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ItemWidget::disable()
|
void ItemWidget::disable()
|
||||||
|
|
@ -100,7 +87,6 @@ void ItemWidget::disable()
|
||||||
ui->checkBox->setEnabled(false);
|
ui->checkBox->setEnabled(false);
|
||||||
ui->label->setEnabled(false);
|
ui->label->setEnabled(false);
|
||||||
ui->slider->setEnabled(false);
|
ui->slider->setEnabled(false);
|
||||||
ui->comboBox->setEnabled(false);
|
|
||||||
ui->pushButton_Remove->setEnabled(false);
|
ui->pushButton_Remove->setEnabled(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -118,9 +104,8 @@ void ItemWidget::showSettingsDialog()
|
||||||
{
|
{
|
||||||
if(auto workingItem = item_.lock())
|
if(auto workingItem = item_.lock())
|
||||||
{
|
{
|
||||||
ItemSettingsDialog dialog(workingItem, noGroupEdit_, this);
|
ItemSettingsDialog dialog(workingItem, this);
|
||||||
dialog.exec();
|
dialog.exec();
|
||||||
|
|
||||||
}
|
}
|
||||||
else disable();
|
else disable();
|
||||||
}
|
}
|
||||||
|
|
@ -132,16 +117,6 @@ std::weak_ptr<Item> ItemWidget::getItem()
|
||||||
|
|
||||||
void ItemWidget::onItemUpdated(ItemUpdateRequest update)
|
void ItemWidget::onItemUpdated(ItemUpdateRequest update)
|
||||||
{
|
{
|
||||||
if(update.changes.valueNames)
|
|
||||||
{
|
|
||||||
ui->comboBox->blockSignals(true);
|
|
||||||
ui->comboBox->clear();
|
|
||||||
QStringList list;
|
|
||||||
for(const QString& name : update.payload.getValueNames())
|
|
||||||
list.append(name);
|
|
||||||
ui->comboBox->addItems(list);
|
|
||||||
ui->comboBox->blockSignals(false);
|
|
||||||
}
|
|
||||||
stateChanged(update.payload.getValue());
|
stateChanged(update.payload.getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -153,9 +128,6 @@ void ItemWidget::stateChanged(int state)
|
||||||
ui->checkBox->blockSignals(true);
|
ui->checkBox->blockSignals(true);
|
||||||
ui->checkBox->setChecked(state);
|
ui->checkBox->setChecked(state);
|
||||||
ui->checkBox->blockSignals(false);
|
ui->checkBox->blockSignals(false);
|
||||||
ui->comboBox->blockSignals(true);
|
|
||||||
ui->comboBox->setCurrentIndex(state);
|
|
||||||
ui->comboBox->blockSignals(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ItemWidget::~ItemWidget()
|
ItemWidget::~ItemWidget()
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include "itemsettingsdialog.h"
|
||||||
#include "../items/item.h"
|
#include "../items/item.h"
|
||||||
|
|
||||||
namespace Ui
|
namespace Ui
|
||||||
|
|
@ -15,7 +16,6 @@ class ItemWidget : public QWidget
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
private:
|
private:
|
||||||
std::weak_ptr<Item> item_;
|
std::weak_ptr<Item> item_;
|
||||||
bool noGroupEdit_;
|
|
||||||
|
|
||||||
void disable();
|
void disable();
|
||||||
|
|
||||||
|
|
@ -30,7 +30,7 @@ private slots:
|
||||||
void deleteItem();
|
void deleteItem();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit ItemWidget(std::weak_ptr<Item> item, bool noGroupEdit = false, QWidget *parent = nullptr);
|
explicit ItemWidget(std::weak_ptr<Item> item, QWidget *parent = nullptr);
|
||||||
std::weak_ptr<Item> getItem();
|
std::weak_ptr<Item> getItem();
|
||||||
bool controles(const ItemData& relay);
|
bool controles(const ItemData& relay);
|
||||||
~ItemWidget();
|
~ItemWidget();
|
||||||
|
|
|
||||||
|
|
@ -59,16 +59,6 @@
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
|
||||||
<widget class="QComboBox" name="comboBox">
|
|
||||||
<property name="sizePolicy">
|
|
||||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
|
||||||
<horstretch>0</horstretch>
|
|
||||||
<verstretch>0</verstretch>
|
|
||||||
</sizepolicy>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
<item>
|
||||||
<widget class="QCheckBox" name="checkBox">
|
<widget class="QCheckBox" name="checkBox">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,6 @@ MainWindow::MainWindow(MainObject * const mainObject, QWidget *parent) :
|
||||||
connect(ui->pushButton_refesh, &QPushButton::clicked, mainObject, &MainObject::refresh);
|
connect(ui->pushButton_refesh, &QPushButton::clicked, mainObject, &MainObject::refresh);
|
||||||
connect(&globalItems, &ItemStore::itemAdded, ui->relayList, &ItemScrollBox::addItem);
|
connect(&globalItems, &ItemStore::itemAdded, ui->relayList, &ItemScrollBox::addItem);
|
||||||
connect(&globalItems, &ItemStore::itemDeleted, ui->relayList, &ItemScrollBox::removeItem);
|
connect(&globalItems, &ItemStore::itemDeleted, ui->relayList, &ItemScrollBox::removeItem);
|
||||||
connect(&globalItems, &ItemStore::itemUpdated, ui->relayList, &ItemScrollBox::onItemUpdate);
|
|
||||||
|
|
||||||
for(size_t i = 0; i < globalItems.getItems()->size(); ++i)
|
for(size_t i = 0; i < globalItems.getItems()->size(); ++i)
|
||||||
ui->relayList->addItem(globalItems.getItems()->at(i));
|
ui->relayList->addItem(globalItems.getItems()->at(i));
|
||||||
|
|
@ -66,7 +65,7 @@ void MainWindow::showPowerItemDialog()
|
||||||
}
|
}
|
||||||
if(powerItem)
|
if(powerItem)
|
||||||
{
|
{
|
||||||
ItemSettingsDialog diag(std::shared_ptr<Item>(powerItem), false, this);
|
ItemSettingsDialog diag(std::shared_ptr<Item>(powerItem), this);
|
||||||
diag.show();
|
diag.show();
|
||||||
diag.exec();
|
diag.exec();
|
||||||
}
|
}
|
||||||
|
|
@ -80,13 +79,7 @@ void MainWindow::showItemCreationDialog()
|
||||||
ItemCreationDialog diag(this);
|
ItemCreationDialog diag(this);
|
||||||
diag.show();
|
diag.show();
|
||||||
if(diag.exec())
|
if(diag.exec())
|
||||||
{
|
createdItem(diag.item);
|
||||||
ItemAddRequest request;
|
|
||||||
request.type = ITEM_UPDATE_USER;
|
|
||||||
request.changes = ItemFieldChanges(true);
|
|
||||||
request.payload = diag.item;
|
|
||||||
createdItem(request);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::changeHeaderLableText(QString string)
|
void MainWindow::changeHeaderLableText(QString string)
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ private:
|
||||||
signals:
|
signals:
|
||||||
|
|
||||||
void sigSave();
|
void sigSave();
|
||||||
void createdItem(ItemAddRequest request);
|
void createdItem(std::shared_ptr<Item> item);
|
||||||
void sigSetRgb(const QColor color);
|
void sigSetRgb(const QColor color);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@
|
||||||
</property>
|
</property>
|
||||||
<property name="windowIcon">
|
<property name="windowIcon">
|
||||||
<iconset resource="../../resources.qrc">
|
<iconset resource="../../resources.qrc">
|
||||||
<normaloff>:/images/xyz.uvos.icon.bmp</normaloff>:/images/xyz.uvos.icon.bmp</iconset>
|
<normaloff>:/images/UVOSicon.bmp</normaloff>:/images/UVOSicon.bmp</iconset>
|
||||||
</property>
|
</property>
|
||||||
<widget class="QWidget" name="centralWidget">
|
<widget class="QWidget" name="centralWidget">
|
||||||
<property name="sizePolicy">
|
<property name="sizePolicy">
|
||||||
|
|
@ -94,7 +94,7 @@
|
||||||
</property>
|
</property>
|
||||||
<property name="minimumSize">
|
<property name="minimumSize">
|
||||||
<size>
|
<size>
|
||||||
<width>350</width>
|
<width>400</width>
|
||||||
<height>0</height>
|
<height>0</height>
|
||||||
</size>
|
</size>
|
||||||
</property>
|
</property>
|
||||||
|
|
|
||||||
|
|
@ -13,15 +13,52 @@
|
||||||
<property name="windowTitle">
|
<property name="windowTitle">
|
||||||
<string>Form</string>
|
<string>Form</string>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QVBoxLayout" name="verticalLayout">
|
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||||
<item>
|
<item>
|
||||||
<widget class="QTabWidget" name="tabWidget">
|
<widget class="QScrollArea" name="scrollArea">
|
||||||
<property name="tabsClosable">
|
<property name="frameShape">
|
||||||
<bool>false</bool>
|
<enum>QFrame::NoFrame</enum>
|
||||||
</property>
|
</property>
|
||||||
<property name="movable">
|
<property name="lineWidth">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="verticalScrollBarPolicy">
|
||||||
|
<enum>Qt::ScrollBarAlwaysOff</enum>
|
||||||
|
</property>
|
||||||
|
<property name="horizontalScrollBarPolicy">
|
||||||
|
<enum>Qt::ScrollBarAlwaysOff</enum>
|
||||||
|
</property>
|
||||||
|
<property name="widgetResizable">
|
||||||
<bool>true</bool>
|
<bool>true</bool>
|
||||||
</property>
|
</property>
|
||||||
|
<widget class="QWidget" name="scrollAreaWidgetContents">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>388</width>
|
||||||
|
<height>288</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<item>
|
||||||
|
<layout class="QVBoxLayout" name="relayWidgetVbox"/>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="verticalSpacer">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Vertical</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>20</width>
|
||||||
|
<height>40</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
|
|
|
||||||
|
|
@ -1,46 +0,0 @@
|
||||||
cmake_minimum_required(VERSION 4.0)
|
|
||||||
|
|
||||||
# Enable testing
|
|
||||||
enable_testing()
|
|
||||||
|
|
||||||
# Find Qt packages for tests
|
|
||||||
find_package(Qt6 COMPONENTS Core Gui Widgets Multimedia Test REQUIRED)
|
|
||||||
|
|
||||||
# Add test executables - link to static library instead of compiling sources
|
|
||||||
add_executable(test_item unit/items/test_item.cpp)
|
|
||||||
add_executable(test_sensor unit/sensors/test_sensor.cpp)
|
|
||||||
add_executable(test_actor unit/actors/test_actor.cpp)
|
|
||||||
add_executable(test_itemstore unit/items/test_itemstore.cpp)
|
|
||||||
add_executable(test_itemloadersource unit/items/test_itemloadersource.cpp)
|
|
||||||
add_executable(test_mqttitem unit/items/test_mqttitem.cpp)
|
|
||||||
add_executable(test_tcp unit/service/test_tcp.cpp)
|
|
||||||
|
|
||||||
# Link all tests to static library
|
|
||||||
foreach(test test_item test_sensor test_actor test_itemstore test_itemloadersource test_mqttitem test_tcp)
|
|
||||||
target_link_libraries(${test}
|
|
||||||
smartvos_core
|
|
||||||
Qt6::Core
|
|
||||||
Qt6::Gui
|
|
||||||
Qt6::Widgets
|
|
||||||
Qt6::Multimedia
|
|
||||||
Qt6::Network
|
|
||||||
Qt6::WebSockets
|
|
||||||
Qt6::Mqtt
|
|
||||||
Qt6::Test
|
|
||||||
)
|
|
||||||
|
|
||||||
# Include paths - the static library already has the correct include paths
|
|
||||||
target_include_directories(${test} PRIVATE
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/../src
|
|
||||||
${Qt6Gui_PRIVATE_INCLUDE_DIRS}
|
|
||||||
)
|
|
||||||
endforeach()
|
|
||||||
|
|
||||||
# Add tests to CTest
|
|
||||||
add_test(NAME test_item COMMAND test_item)
|
|
||||||
add_test(NAME test_sensor COMMAND test_sensor)
|
|
||||||
add_test(NAME test_actor COMMAND test_actor)
|
|
||||||
add_test(NAME test_itemstore COMMAND test_itemstore)
|
|
||||||
add_test(NAME test_itemloadersource COMMAND test_itemloadersource)
|
|
||||||
add_test(NAME test_mqttitem COMMAND test_mqttitem)
|
|
||||||
add_test(NAME test_tcp COMMAND test_tcp)
|
|
||||||
|
|
@ -1,333 +0,0 @@
|
||||||
#include <QtTest/QtTest>
|
|
||||||
#include "actors/actor.h"
|
|
||||||
#include "actors/timeractor.h"
|
|
||||||
#include "actors/sensoractor.h"
|
|
||||||
|
|
||||||
class TestActor : public QObject
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
private slots:
|
|
||||||
void initTestCase()
|
|
||||||
{
|
|
||||||
// Setup for all tests
|
|
||||||
}
|
|
||||||
|
|
||||||
void testActorCreation()
|
|
||||||
{
|
|
||||||
Actor actor;
|
|
||||||
|
|
||||||
// Actor should be active by default
|
|
||||||
QVERIFY(actor.isActive());
|
|
||||||
|
|
||||||
// Actor should not be exhausted by default
|
|
||||||
QVERIFY(!actor.isExausted());
|
|
||||||
}
|
|
||||||
|
|
||||||
void testActorActivation()
|
|
||||||
{
|
|
||||||
Actor actor;
|
|
||||||
|
|
||||||
// Test makeActive/makeInactive
|
|
||||||
actor.makeActive();
|
|
||||||
QVERIFY(actor.isActive());
|
|
||||||
|
|
||||||
actor.makeInactive();
|
|
||||||
QVERIFY(!actor.isActive());
|
|
||||||
}
|
|
||||||
|
|
||||||
void testActorSetActive()
|
|
||||||
{
|
|
||||||
Actor actor;
|
|
||||||
|
|
||||||
// Test setActive
|
|
||||||
actor.setActive(1);
|
|
||||||
QVERIFY(actor.isActive());
|
|
||||||
|
|
||||||
actor.setActive(0);
|
|
||||||
QVERIFY(!actor.isActive());
|
|
||||||
}
|
|
||||||
|
|
||||||
void testActorTriggerValue()
|
|
||||||
{
|
|
||||||
Actor actor;
|
|
||||||
|
|
||||||
// Default trigger value should be 0
|
|
||||||
QVERIFY(actor.getTriggerValue() == 0);
|
|
||||||
|
|
||||||
// Set trigger value
|
|
||||||
actor.setTriggerValue(1);
|
|
||||||
QVERIFY(actor.getTriggerValue() == 1);
|
|
||||||
|
|
||||||
actor.setTriggerValue(255);
|
|
||||||
QVERIFY(actor.getTriggerValue() == 255);
|
|
||||||
}
|
|
||||||
|
|
||||||
void testActorActionName()
|
|
||||||
{
|
|
||||||
Actor actor;
|
|
||||||
|
|
||||||
// Default trigger value is 0, so action name should be "off"
|
|
||||||
QVERIFY(actor.actionName() == "off");
|
|
||||||
|
|
||||||
// Set trigger value to 1
|
|
||||||
actor.setTriggerValue(1);
|
|
||||||
QVERIFY(actor.actionName() == "on");
|
|
||||||
|
|
||||||
// Set trigger value to something else
|
|
||||||
actor.setTriggerValue(5);
|
|
||||||
QVERIFY(actor.actionName() == "value to 5");
|
|
||||||
}
|
|
||||||
|
|
||||||
void testActorJsonSerialization()
|
|
||||||
{
|
|
||||||
Actor actor;
|
|
||||||
actor.setTriggerValue(1);
|
|
||||||
actor.makeInactive();
|
|
||||||
|
|
||||||
QJsonObject json;
|
|
||||||
actor.store(json);
|
|
||||||
|
|
||||||
// Verify JSON contents
|
|
||||||
QVERIFY(json.contains("Active"));
|
|
||||||
QVERIFY(json.contains("Exausted"));
|
|
||||||
QVERIFY(json.contains("TriggerValue"));
|
|
||||||
|
|
||||||
QVERIFY(json["Active"].toBool() == false);
|
|
||||||
QVERIFY(json["Exausted"].toBool() == false);
|
|
||||||
QVERIFY(json["TriggerValue"].toInt() == 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
void testActorJsonDeserialization()
|
|
||||||
{
|
|
||||||
QJsonObject json;
|
|
||||||
json["Active"] = false;
|
|
||||||
json["Exausted"] = true;
|
|
||||||
json["TriggerValue"] = 5;
|
|
||||||
|
|
||||||
Actor actor;
|
|
||||||
actor.load(json);
|
|
||||||
|
|
||||||
QVERIFY(actor.isActive() == false);
|
|
||||||
QVERIFY(actor.isExausted() == true);
|
|
||||||
QVERIFY(actor.getTriggerValue() == 5);
|
|
||||||
}
|
|
||||||
|
|
||||||
void testActorCreateActor()
|
|
||||||
{
|
|
||||||
// Test creating different actor types
|
|
||||||
std::shared_ptr<Actor> alarmActor = Actor::createActor("Alarm");
|
|
||||||
QVERIFY(alarmActor != nullptr);
|
|
||||||
|
|
||||||
std::shared_ptr<Actor> sensorActor = Actor::createActor("Sensor");
|
|
||||||
QVERIFY(sensorActor != nullptr);
|
|
||||||
|
|
||||||
std::shared_ptr<Actor> timerActor = Actor::createActor("Timer");
|
|
||||||
QVERIFY(timerActor != nullptr);
|
|
||||||
|
|
||||||
std::shared_ptr<Actor> regulatorActor = Actor::createActor("Regulator");
|
|
||||||
QVERIFY(regulatorActor != nullptr);
|
|
||||||
|
|
||||||
std::shared_ptr<Actor> polynomalActor = Actor::createActor("Polynomal");
|
|
||||||
QVERIFY(polynomalActor != nullptr);
|
|
||||||
|
|
||||||
std::shared_ptr<Actor> factorActor = Actor::createActor("MultiFactor");
|
|
||||||
QVERIFY(factorActor != nullptr);
|
|
||||||
|
|
||||||
std::shared_ptr<Actor> genericActor = Actor::createActor("Actor");
|
|
||||||
QVERIFY(genericActor != nullptr);
|
|
||||||
|
|
||||||
// Test unknown type returns nullptr
|
|
||||||
std::shared_ptr<Actor> unknownActor = Actor::createActor("UnknownType");
|
|
||||||
QVERIFY(unknownActor == nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
void testActorLoadActor()
|
|
||||||
{
|
|
||||||
QJsonObject json;
|
|
||||||
json["Type"] = "Timer";
|
|
||||||
json["Active"] = true;
|
|
||||||
json["Exausted"] = false;
|
|
||||||
json["TriggerValue"] = 1;
|
|
||||||
json["Timeout"] = 5000;
|
|
||||||
|
|
||||||
std::shared_ptr<Actor> actor = Actor::loadActor(json);
|
|
||||||
QVERIFY(actor != nullptr);
|
|
||||||
|
|
||||||
// Verify the actor was loaded with correct values
|
|
||||||
QVERIFY(actor->isActive());
|
|
||||||
QVERIFY(!actor->isExausted());
|
|
||||||
QVERIFY(actor->getTriggerValue() == 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
void testTimerActorCreation()
|
|
||||||
{
|
|
||||||
TimerActor actor(60);
|
|
||||||
|
|
||||||
// Default timeout should be 60 seconds
|
|
||||||
QVERIFY(actor.getTimeout() == 60);
|
|
||||||
}
|
|
||||||
|
|
||||||
void testTimerActorSetTimeout()
|
|
||||||
{
|
|
||||||
TimerActor actor(60);
|
|
||||||
|
|
||||||
actor.setTimeout(120);
|
|
||||||
QVERIFY(actor.getTimeout() == 120);
|
|
||||||
|
|
||||||
actor.setTimeout(5);
|
|
||||||
QVERIFY(actor.getTimeout() == 5);
|
|
||||||
}
|
|
||||||
|
|
||||||
void testTimerActorJsonSerialization()
|
|
||||||
{
|
|
||||||
TimerActor actor(120);
|
|
||||||
|
|
||||||
QJsonObject json;
|
|
||||||
actor.store(json);
|
|
||||||
|
|
||||||
// Verify JSON contents
|
|
||||||
QVERIFY(json.contains("Type"));
|
|
||||||
QVERIFY(json.contains("Timeout"));
|
|
||||||
|
|
||||||
QVERIFY(json["Type"].toString() == "Timer");
|
|
||||||
QVERIFY(json["Timeout"].toInt() == 120000); // Converted to milliseconds
|
|
||||||
}
|
|
||||||
|
|
||||||
void testTimerActorJsonDeserialization()
|
|
||||||
{
|
|
||||||
QJsonObject json;
|
|
||||||
json["Type"] = "Timer";
|
|
||||||
json["Timeout"] = 30000; // 30 seconds in milliseconds
|
|
||||||
json["Active"] = true;
|
|
||||||
json["Exausted"] = false;
|
|
||||||
json["TriggerValue"] = 1;
|
|
||||||
|
|
||||||
TimerActor actor;
|
|
||||||
actor.load(json, false);
|
|
||||||
|
|
||||||
// Timeout should be 30 seconds
|
|
||||||
QVERIFY(actor.getTimeout() == 30);
|
|
||||||
}
|
|
||||||
|
|
||||||
void testTimerActorGetName()
|
|
||||||
{
|
|
||||||
TimerActor actor(60);
|
|
||||||
|
|
||||||
QString name = actor.getName();
|
|
||||||
QVERIFY(name.contains("60"));
|
|
||||||
}
|
|
||||||
|
|
||||||
void testSensorActorCreation()
|
|
||||||
{
|
|
||||||
Sensor sensor(Sensor::TYPE_TEMPERATURE, 1, 25.0, "temp_sensor");
|
|
||||||
SensorActor actor(sensor);
|
|
||||||
|
|
||||||
// Verify sensor was set correctly
|
|
||||||
QVERIFY(actor.getSensor().type == Sensor::TYPE_TEMPERATURE);
|
|
||||||
QVERIFY(actor.getSensor().id == 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
void testSensorActorDefaultCreation()
|
|
||||||
{
|
|
||||||
SensorActor actor;
|
|
||||||
|
|
||||||
// Default sensor should be dummy
|
|
||||||
QVERIFY(actor.getSensor().type == Sensor::TYPE_DUMMY);
|
|
||||||
}
|
|
||||||
|
|
||||||
void testSensorActorSetSensor()
|
|
||||||
{
|
|
||||||
SensorActor actor;
|
|
||||||
|
|
||||||
Sensor newSensor(Sensor::TYPE_HUMIDITY, 2, 60.0, "humidity_sensor");
|
|
||||||
actor.setSensor(newSensor);
|
|
||||||
|
|
||||||
QVERIFY(actor.getSensor().type == Sensor::TYPE_HUMIDITY);
|
|
||||||
QVERIFY(actor.getSensor().id == 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
void testSensorActorSetThreshold()
|
|
||||||
{
|
|
||||||
SensorActor actor;
|
|
||||||
|
|
||||||
actor.setThreshold(25.0);
|
|
||||||
QVERIFY(actor.getThreshold() == 25.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
void testSensorActorSetSlope()
|
|
||||||
{
|
|
||||||
SensorActor actor;
|
|
||||||
|
|
||||||
actor.setSloap(SensorActor::SLOPE_UP);
|
|
||||||
QVERIFY(actor.getSloap() == SensorActor::SLOPE_UP);
|
|
||||||
|
|
||||||
actor.setSloap(SensorActor::SLOPE_DOWN);
|
|
||||||
QVERIFY(actor.getSloap() == SensorActor::SLOPE_DOWN);
|
|
||||||
|
|
||||||
actor.setSloap(SensorActor::SLOPE_BOTH);
|
|
||||||
QVERIFY(actor.getSloap() == SensorActor::SLOPE_BOTH);
|
|
||||||
}
|
|
||||||
|
|
||||||
void testSensorActorJsonSerialization()
|
|
||||||
{
|
|
||||||
Sensor sensor(Sensor::TYPE_TEMPERATURE, 1, 25.0, "temp_sensor");
|
|
||||||
SensorActor actor(sensor);
|
|
||||||
actor.setThreshold(30.0);
|
|
||||||
actor.setSloap(SensorActor::SLOPE_UP);
|
|
||||||
|
|
||||||
QJsonObject json;
|
|
||||||
actor.store(json);
|
|
||||||
|
|
||||||
// Verify JSON contents
|
|
||||||
QVERIFY(json.contains("Type"));
|
|
||||||
QVERIFY(json.contains("Threshold"));
|
|
||||||
QVERIFY(json.contains("Sloap"));
|
|
||||||
QVERIFY(json.contains("SensorType"));
|
|
||||||
|
|
||||||
QVERIFY(json["Type"].toString() == "Sensor");
|
|
||||||
QVERIFY(json["Threshold"].toDouble() == 30.0);
|
|
||||||
QVERIFY(json["Sloap"].toInt() == SensorActor::SLOPE_UP);
|
|
||||||
}
|
|
||||||
|
|
||||||
void testSensorActorJsonDeserialization()
|
|
||||||
{
|
|
||||||
QJsonObject json;
|
|
||||||
json["Type"] = "Sensor";
|
|
||||||
json["Threshold"] = 25.5;
|
|
||||||
json["Sloap"] = SensorActor::SLOPE_DOWN;
|
|
||||||
json["SensorType"] = Sensor::TYPE_HUMIDITY;
|
|
||||||
json["SensorId"] = 3;
|
|
||||||
json["SensorField"] = 65.0;
|
|
||||||
json["SensorName"] = "humidity";
|
|
||||||
json["Active"] = true;
|
|
||||||
json["Exausted"] = false;
|
|
||||||
json["TriggerValue"] = 1;
|
|
||||||
|
|
||||||
SensorActor actor;
|
|
||||||
actor.load(json, false);
|
|
||||||
|
|
||||||
QVERIFY(actor.getThreshold() == 25.5);
|
|
||||||
QVERIFY(actor.getSloap() == SensorActor::SLOPE_DOWN);
|
|
||||||
}
|
|
||||||
|
|
||||||
void testSensorActorGetName()
|
|
||||||
{
|
|
||||||
Sensor sensor(Sensor::TYPE_TEMPERATURE, 1, 25.0, "temp_sensor");
|
|
||||||
SensorActor actor(sensor);
|
|
||||||
actor.setThreshold(30.0);
|
|
||||||
|
|
||||||
QString name = actor.getName();
|
|
||||||
QVERIFY(name.contains("temp_sensor"));
|
|
||||||
QVERIFY(name.contains("30"));
|
|
||||||
}
|
|
||||||
|
|
||||||
void cleanupTestCase()
|
|
||||||
{
|
|
||||||
// Cleanup after all tests
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
QTEST_APPLESS_MAIN(TestActor)
|
|
||||||
|
|
||||||
#include "test_actor.moc"
|
|
||||||
|
|
@ -1,225 +0,0 @@
|
||||||
#include <QtTest/QtTest>
|
|
||||||
#include "items/item.h"
|
|
||||||
#include "items/itemstore.h"
|
|
||||||
#include "items/itemsource.h"
|
|
||||||
#include "items/itemloadersource.h"
|
|
||||||
|
|
||||||
class TestItem : public QObject
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
private slots:
|
|
||||||
void initTestCase()
|
|
||||||
{
|
|
||||||
// Setup for all tests
|
|
||||||
}
|
|
||||||
|
|
||||||
void testItemCreation()
|
|
||||||
{
|
|
||||||
Item item(0, "test_item", 0);
|
|
||||||
QCOMPARE(item.getName(), QString("test_item"));
|
|
||||||
}
|
|
||||||
|
|
||||||
void testOverrideFunctionality()
|
|
||||||
{
|
|
||||||
Item item(0, "test_item", 0);
|
|
||||||
|
|
||||||
// Test override on/off
|
|
||||||
item.setOverride(true);
|
|
||||||
QVERIFY(item.getOverride());
|
|
||||||
|
|
||||||
item.setOverride(false);
|
|
||||||
QVERIFY(!item.getOverride());
|
|
||||||
}
|
|
||||||
|
|
||||||
void testItemValueTypes()
|
|
||||||
{
|
|
||||||
// Test default value type is BOOL
|
|
||||||
Item item(1, "test_item", 0);
|
|
||||||
QCOMPARE(item.getValueType(), ITEM_VALUE_BOOL);
|
|
||||||
|
|
||||||
// Test setting and getting value
|
|
||||||
item.setValueData(1);
|
|
||||||
QCOMPARE(item.getValue(), static_cast<uint8_t>(1));
|
|
||||||
|
|
||||||
item.setValueData(0);
|
|
||||||
QCOMPARE(item.getValue(), static_cast<uint8_t>(0));
|
|
||||||
}
|
|
||||||
|
|
||||||
void testItemId()
|
|
||||||
{
|
|
||||||
Item item(42, "test_item", 0);
|
|
||||||
QCOMPARE(item.id(), static_cast<uint32_t>(42));
|
|
||||||
}
|
|
||||||
|
|
||||||
void testItemGroupName()
|
|
||||||
{
|
|
||||||
Item item(1, "test_item", 0);
|
|
||||||
QCOMPARE(item.getGroupName(), QString("All")); // Default group
|
|
||||||
|
|
||||||
item.setGroupName("Living Room");
|
|
||||||
QCOMPARE(item.getGroupName(), QString("Living Room"));
|
|
||||||
}
|
|
||||||
|
|
||||||
void testItemHidden()
|
|
||||||
{
|
|
||||||
Item item(1, "test_item", 0);
|
|
||||||
QVERIFY(!item.isHidden());
|
|
||||||
|
|
||||||
item.setHidden(true);
|
|
||||||
QVERIFY(item.isHidden());
|
|
||||||
|
|
||||||
item.setHidden(false);
|
|
||||||
QVERIFY(!item.isHidden());
|
|
||||||
}
|
|
||||||
|
|
||||||
void testItemLoaded()
|
|
||||||
{
|
|
||||||
Item item(1, "test_item", 0);
|
|
||||||
QVERIFY(!item.getLoaded());
|
|
||||||
|
|
||||||
item.setLoaded(true);
|
|
||||||
QVERIFY(item.getLoaded());
|
|
||||||
}
|
|
||||||
|
|
||||||
void testItemJsonSerialization()
|
|
||||||
{
|
|
||||||
Item item(42, "test_item", 1);
|
|
||||||
item.setGroupName("TestGroup");
|
|
||||||
|
|
||||||
QJsonObject json;
|
|
||||||
item.store(json);
|
|
||||||
|
|
||||||
// Verify JSON contents
|
|
||||||
QCOMPARE(json["ItemId"].toDouble(), 42.0);
|
|
||||||
// Note: Name is only stored if changes.name is true
|
|
||||||
// Value is only stored if changes.value is true
|
|
||||||
}
|
|
||||||
|
|
||||||
void testItemJsonDeserialization()
|
|
||||||
{
|
|
||||||
QJsonObject json;
|
|
||||||
json["ItemId"] = 100;
|
|
||||||
json["Name"] = "loaded_item";
|
|
||||||
json["Value"] = 1;
|
|
||||||
json["GroupName"] = "Bedroom";
|
|
||||||
|
|
||||||
Item item;
|
|
||||||
item.load(json);
|
|
||||||
|
|
||||||
// Note: load() uses loadWithChanges which requires preserve=false to apply changes
|
|
||||||
// The id should be set
|
|
||||||
// The name should be set (since preserve defaults to false)
|
|
||||||
}
|
|
||||||
|
|
||||||
void testItemUpdateRequest()
|
|
||||||
{
|
|
||||||
Item item(1, "test_item", 0);
|
|
||||||
|
|
||||||
// Test creating update request
|
|
||||||
ItemUpdateRequest request = item.createValueUpdateRequest(ITEM_UPDATE_USER, false);
|
|
||||||
|
|
||||||
QVERIFY(request.type == ITEM_UPDATE_USER);
|
|
||||||
// Note: changes is not automatically set by createValueUpdateRequest
|
|
||||||
// The caller is expected to set the changes they want
|
|
||||||
QVERIFY(request.newActors.empty());
|
|
||||||
}
|
|
||||||
|
|
||||||
void testItemUpdateRequestWithActors()
|
|
||||||
{
|
|
||||||
Item item(1, "test_item", 0);
|
|
||||||
|
|
||||||
// Test creating update request with actors
|
|
||||||
ItemUpdateRequest request = item.createValueUpdateRequest(ITEM_UPDATE_USER, true);
|
|
||||||
|
|
||||||
QVERIFY(request.type == ITEM_UPDATE_USER);
|
|
||||||
// With actors=true, newActors should be populated
|
|
||||||
// Note: changes.actors is set by the caller, not by createValueUpdateRequest
|
|
||||||
}
|
|
||||||
|
|
||||||
void testItemHasActors()
|
|
||||||
{
|
|
||||||
Item item(1, "test_item", 0);
|
|
||||||
|
|
||||||
QVERIFY(!item.hasActors());
|
|
||||||
|
|
||||||
// Note: Adding actors requires a valid Actor pointer
|
|
||||||
}
|
|
||||||
|
|
||||||
void testItemRemoveAllActors()
|
|
||||||
{
|
|
||||||
Item item(1, "test_item", 0);
|
|
||||||
|
|
||||||
item.removeAllActors();
|
|
||||||
QVERIFY(!item.hasActors());
|
|
||||||
}
|
|
||||||
|
|
||||||
void testItemSetActorsActive()
|
|
||||||
{
|
|
||||||
Item item(1, "test_item", 0);
|
|
||||||
|
|
||||||
// Should not crash when no actors
|
|
||||||
item.setActorsActive(true);
|
|
||||||
item.setActorsActive(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
void testItemDataChangeDetection()
|
|
||||||
{
|
|
||||||
ItemData data1(1, "item1", 0);
|
|
||||||
ItemData data2(1, "item1", 1);
|
|
||||||
|
|
||||||
// Test value change detection
|
|
||||||
ItemFieldChanges changes(true);
|
|
||||||
QVERIFY(data1.hasChanged(data2, changes));
|
|
||||||
|
|
||||||
// Test no change
|
|
||||||
changes = ItemFieldChanges(false);
|
|
||||||
QVERIFY(!data1.hasChanged(data2, changes));
|
|
||||||
}
|
|
||||||
|
|
||||||
void testItemDataNameChange()
|
|
||||||
{
|
|
||||||
ItemData data1(1, "item1", 0);
|
|
||||||
ItemData data2(1, "item2", 0);
|
|
||||||
|
|
||||||
ItemFieldChanges changes;
|
|
||||||
changes.name = true;
|
|
||||||
|
|
||||||
QVERIFY(data1.hasChanged(data2, changes));
|
|
||||||
}
|
|
||||||
|
|
||||||
void testItemDataGroupNameChange()
|
|
||||||
{
|
|
||||||
ItemData data1(1, "item1", 0);
|
|
||||||
data1.setGroupName("Group1");
|
|
||||||
|
|
||||||
ItemData data2(1, "item1", 0);
|
|
||||||
data2.setGroupName("Group2");
|
|
||||||
|
|
||||||
ItemFieldChanges changes;
|
|
||||||
changes.groupName = true;
|
|
||||||
|
|
||||||
QVERIFY(data1.hasChanged(data2, changes));
|
|
||||||
}
|
|
||||||
|
|
||||||
void testItemFieldChanges()
|
|
||||||
{
|
|
||||||
ItemFieldChanges changes(false);
|
|
||||||
QVERIFY(changes.isNone());
|
|
||||||
|
|
||||||
changes.value = true;
|
|
||||||
QVERIFY(!changes.isNone());
|
|
||||||
|
|
||||||
ItemFieldChanges allChanges(true);
|
|
||||||
QVERIFY(!allChanges.isNone());
|
|
||||||
}
|
|
||||||
|
|
||||||
void cleanupTestCase()
|
|
||||||
{
|
|
||||||
// Cleanup after all tests
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
QTEST_APPLESS_MAIN(TestItem)
|
|
||||||
|
|
||||||
#include "test_item.moc"
|
|
||||||
|
|
@ -1,240 +0,0 @@
|
||||||
#include <QtTest/QtTest>
|
|
||||||
#include "items/itemloadersource.h"
|
|
||||||
#include "items/item.h"
|
|
||||||
|
|
||||||
class TestItemLoaderSource : public QObject
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
private slots:
|
|
||||||
void initTestCase()
|
|
||||||
{
|
|
||||||
// Setup for all tests
|
|
||||||
}
|
|
||||||
|
|
||||||
void testItemLoaderSourceCreation()
|
|
||||||
{
|
|
||||||
ItemLoaderSource source;
|
|
||||||
|
|
||||||
// Should be created with empty JSON
|
|
||||||
QVERIFY(true); // No crash
|
|
||||||
}
|
|
||||||
|
|
||||||
void testItemLoaderSourceCreationWithJson()
|
|
||||||
{
|
|
||||||
QJsonObject json;
|
|
||||||
json["Items"] = QJsonArray();
|
|
||||||
|
|
||||||
ItemLoaderSource source(json);
|
|
||||||
|
|
||||||
// Should be created with JSON
|
|
||||||
QVERIFY(true); // No crash
|
|
||||||
}
|
|
||||||
|
|
||||||
void testItemLoaderSourceRefreshEmpty()
|
|
||||||
{
|
|
||||||
ItemLoaderSource source;
|
|
||||||
|
|
||||||
// Should not crash with empty JSON
|
|
||||||
source.refresh();
|
|
||||||
|
|
||||||
// No items should be emitted
|
|
||||||
QVERIFY(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void testItemLoaderSourceRefreshWithItems()
|
|
||||||
{
|
|
||||||
QJsonObject json;
|
|
||||||
QJsonArray itemsArray;
|
|
||||||
|
|
||||||
QJsonObject item1;
|
|
||||||
item1["Type"] = "Item";
|
|
||||||
item1["ItemId"] = 1;
|
|
||||||
item1["Name"] = "test_item1";
|
|
||||||
item1["Value"] = 0;
|
|
||||||
itemsArray.append(item1);
|
|
||||||
|
|
||||||
QJsonObject item2;
|
|
||||||
item2["Type"] = "Item";
|
|
||||||
item2["ItemId"] = 2;
|
|
||||||
item2["Name"] = "test_item2";
|
|
||||||
item2["Value"] = 1;
|
|
||||||
itemsArray.append(item2);
|
|
||||||
|
|
||||||
json["Items"] = itemsArray;
|
|
||||||
|
|
||||||
ItemLoaderSource source(json);
|
|
||||||
|
|
||||||
// Capture the gotItems signal
|
|
||||||
std::vector<ItemAddRequest> capturedItems;
|
|
||||||
connect(&source, &ItemLoaderSource::gotItems,
|
|
||||||
[&capturedItems](std::vector<ItemAddRequest> items) {
|
|
||||||
capturedItems = items;
|
|
||||||
});
|
|
||||||
|
|
||||||
source.refresh();
|
|
||||||
|
|
||||||
// Should have 2 items
|
|
||||||
QVERIFY(capturedItems.size() == 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
void testItemLoaderSourceRefreshWithRelay()
|
|
||||||
{
|
|
||||||
QJsonObject json;
|
|
||||||
QJsonArray itemsArray;
|
|
||||||
|
|
||||||
QJsonObject relay;
|
|
||||||
relay["Type"] = "Relay";
|
|
||||||
relay["ItemId"] = 100;
|
|
||||||
relay["Name"] = "test_relay";
|
|
||||||
relay["Value"] = 0;
|
|
||||||
relay["Id"] = 1;
|
|
||||||
relay["Address"] = 0x2000;
|
|
||||||
itemsArray.append(relay);
|
|
||||||
|
|
||||||
json["Items"] = itemsArray;
|
|
||||||
|
|
||||||
ItemLoaderSource source(json);
|
|
||||||
|
|
||||||
// Capture the gotItems signal
|
|
||||||
std::vector<ItemAddRequest> capturedItems;
|
|
||||||
connect(&source, &ItemLoaderSource::gotItems,
|
|
||||||
[&capturedItems](std::vector<ItemAddRequest> items) {
|
|
||||||
capturedItems = items;
|
|
||||||
});
|
|
||||||
|
|
||||||
source.refresh();
|
|
||||||
|
|
||||||
// Should have 1 item
|
|
||||||
QVERIFY(capturedItems.size() == 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
void testItemLoaderSourceUpdateJson()
|
|
||||||
{
|
|
||||||
ItemLoaderSource source;
|
|
||||||
|
|
||||||
// Initial JSON
|
|
||||||
QJsonObject json1;
|
|
||||||
QJsonArray items1;
|
|
||||||
QJsonObject item1;
|
|
||||||
item1["Type"] = "Item";
|
|
||||||
item1["ItemId"] = 1;
|
|
||||||
item1["Name"] = "item1";
|
|
||||||
items1.append(item1);
|
|
||||||
json1["Items"] = items1;
|
|
||||||
|
|
||||||
source.updateJson(json1);
|
|
||||||
|
|
||||||
// Update JSON
|
|
||||||
QJsonObject json2;
|
|
||||||
QJsonArray items2;
|
|
||||||
QJsonObject item2;
|
|
||||||
item2["Type"] = "Item";
|
|
||||||
item2["ItemId"] = 2;
|
|
||||||
item2["Name"] = "item2";
|
|
||||||
items2.append(item2);
|
|
||||||
json2["Items"] = items2;
|
|
||||||
|
|
||||||
source.updateJson(json2);
|
|
||||||
|
|
||||||
// Should not crash
|
|
||||||
QVERIFY(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void testItemLoaderSourceRefreshWithInvalidItems()
|
|
||||||
{
|
|
||||||
QJsonObject json;
|
|
||||||
QJsonArray itemsArray;
|
|
||||||
|
|
||||||
// Add invalid item (no Type)
|
|
||||||
QJsonObject item1;
|
|
||||||
item1["ItemId"] = 1;
|
|
||||||
itemsArray.append(item1);
|
|
||||||
|
|
||||||
json["Items"] = itemsArray;
|
|
||||||
|
|
||||||
ItemLoaderSource source(json);
|
|
||||||
|
|
||||||
// Capture the gotItems signal
|
|
||||||
std::vector<ItemAddRequest> capturedItems;
|
|
||||||
connect(&source, &ItemLoaderSource::gotItems,
|
|
||||||
[&capturedItems](std::vector<ItemAddRequest> items) {
|
|
||||||
capturedItems = items;
|
|
||||||
});
|
|
||||||
|
|
||||||
source.refresh();
|
|
||||||
|
|
||||||
// Should handle invalid item gracefully (may return 0 items)
|
|
||||||
QVERIFY(capturedItems.size() == 0 || capturedItems.size() == 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
void testItemLoaderSourceRefreshWithMessageItem()
|
|
||||||
{
|
|
||||||
QJsonObject json;
|
|
||||||
QJsonArray itemsArray;
|
|
||||||
|
|
||||||
QJsonObject messageItem;
|
|
||||||
messageItem["Type"] = "Message";
|
|
||||||
messageItem["ItemId"] = 200;
|
|
||||||
messageItem["Name"] = "alert_item";
|
|
||||||
messageItem["Value"] = 0;
|
|
||||||
messageItem["Message"] = "Test message";
|
|
||||||
itemsArray.append(messageItem);
|
|
||||||
|
|
||||||
json["Items"] = itemsArray;
|
|
||||||
|
|
||||||
ItemLoaderSource source(json);
|
|
||||||
|
|
||||||
// Capture the gotItems signal
|
|
||||||
std::vector<ItemAddRequest> capturedItems;
|
|
||||||
connect(&source, &ItemLoaderSource::gotItems,
|
|
||||||
[&capturedItems](std::vector<ItemAddRequest> items) {
|
|
||||||
capturedItems = items;
|
|
||||||
});
|
|
||||||
|
|
||||||
source.refresh();
|
|
||||||
|
|
||||||
// Should have 1 item
|
|
||||||
QVERIFY(capturedItems.size() == 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
void testItemLoaderSourceRefreshWithSystemItem()
|
|
||||||
{
|
|
||||||
QJsonObject json;
|
|
||||||
QJsonArray itemsArray;
|
|
||||||
|
|
||||||
QJsonObject systemItem;
|
|
||||||
systemItem["Type"] = "System";
|
|
||||||
systemItem["ItemId"] = 300;
|
|
||||||
systemItem["Name"] = "system_item";
|
|
||||||
systemItem["Value"] = 0;
|
|
||||||
systemItem["OnCommand"] = "echo on";
|
|
||||||
systemItem["OffCommand"] = "echo off";
|
|
||||||
itemsArray.append(systemItem);
|
|
||||||
|
|
||||||
json["Items"] = itemsArray;
|
|
||||||
|
|
||||||
ItemLoaderSource source(json);
|
|
||||||
|
|
||||||
// Capture the gotItems signal
|
|
||||||
std::vector<ItemAddRequest> capturedItems;
|
|
||||||
connect(&source, &ItemLoaderSource::gotItems,
|
|
||||||
[&capturedItems](std::vector<ItemAddRequest> items) {
|
|
||||||
capturedItems = items;
|
|
||||||
});
|
|
||||||
|
|
||||||
source.refresh();
|
|
||||||
|
|
||||||
// Should have 1 item
|
|
||||||
QVERIFY(capturedItems.size() == 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
void cleanupTestCase()
|
|
||||||
{
|
|
||||||
// Cleanup after all tests
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
QTEST_APPLESS_MAIN(TestItemLoaderSource)
|
|
||||||
|
|
||||||
#include "test_itemloadersource.moc"
|
|
||||||
|
|
@ -1,255 +0,0 @@
|
||||||
#include <QtTest/QtTest>
|
|
||||||
#include "items/itemstore.h"
|
|
||||||
#include "items/item.h"
|
|
||||||
|
|
||||||
class TestItemStore : public QObject
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
private slots:
|
|
||||||
void initTestCase()
|
|
||||||
{
|
|
||||||
// Setup for all tests
|
|
||||||
}
|
|
||||||
|
|
||||||
void testItemStoreCreation()
|
|
||||||
{
|
|
||||||
ItemStore store;
|
|
||||||
|
|
||||||
// Should start empty
|
|
||||||
QVERIFY(store.getItems()->empty());
|
|
||||||
}
|
|
||||||
|
|
||||||
void testItemStoreAddItem()
|
|
||||||
{
|
|
||||||
ItemStore store;
|
|
||||||
|
|
||||||
std::shared_ptr<Item> item(new Item(1, "test_item", 0));
|
|
||||||
|
|
||||||
ItemAddRequest request;
|
|
||||||
request.type = ITEM_UPDATE_USER;
|
|
||||||
request.payload = item;
|
|
||||||
request.changes = ItemFieldChanges(true);
|
|
||||||
|
|
||||||
store.addItem(request);
|
|
||||||
|
|
||||||
// Item should be added
|
|
||||||
QVERIFY(store.getItems()->size() == 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
void testItemStoreAddMultipleItems()
|
|
||||||
{
|
|
||||||
ItemStore store;
|
|
||||||
|
|
||||||
std::shared_ptr<Item> item1(new Item(1, "item1", 0));
|
|
||||||
std::shared_ptr<Item> item2(new Item(2, "item2", 1));
|
|
||||||
|
|
||||||
std::vector<ItemAddRequest> requests;
|
|
||||||
|
|
||||||
ItemAddRequest request1;
|
|
||||||
request1.type = ITEM_UPDATE_USER;
|
|
||||||
request1.payload = item1;
|
|
||||||
request1.changes = ItemFieldChanges(true);
|
|
||||||
requests.push_back(request1);
|
|
||||||
|
|
||||||
ItemAddRequest request2;
|
|
||||||
request2.type = ITEM_UPDATE_USER;
|
|
||||||
request2.payload = item2;
|
|
||||||
request2.changes = ItemFieldChanges(true);
|
|
||||||
requests.push_back(request2);
|
|
||||||
|
|
||||||
store.addItems(requests);
|
|
||||||
|
|
||||||
// Both items should be added
|
|
||||||
QVERIFY(store.getItems()->size() == 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
void testItemStoreGetItem()
|
|
||||||
{
|
|
||||||
ItemStore store;
|
|
||||||
|
|
||||||
std::shared_ptr<Item> item(new Item(42, "test_item", 0));
|
|
||||||
|
|
||||||
ItemAddRequest request;
|
|
||||||
request.type = ITEM_UPDATE_USER;
|
|
||||||
request.payload = item;
|
|
||||||
request.changes = ItemFieldChanges(true);
|
|
||||||
|
|
||||||
store.addItem(request);
|
|
||||||
|
|
||||||
// Get item by id
|
|
||||||
std::shared_ptr<Item> found = store.getItem(42);
|
|
||||||
QVERIFY(found != nullptr);
|
|
||||||
QVERIFY(found->getName() == "test_item");
|
|
||||||
|
|
||||||
// Get non-existent item
|
|
||||||
std::shared_ptr<Item> notFound = store.getItem(999);
|
|
||||||
QVERIFY(notFound == nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
void testItemStoreRemoveItem()
|
|
||||||
{
|
|
||||||
ItemStore store;
|
|
||||||
|
|
||||||
std::shared_ptr<Item> item(new Item(1, "test_item", 0));
|
|
||||||
|
|
||||||
ItemAddRequest request;
|
|
||||||
request.type = ITEM_UPDATE_USER;
|
|
||||||
request.payload = item;
|
|
||||||
request.changes = ItemFieldChanges(true);
|
|
||||||
|
|
||||||
store.addItem(request);
|
|
||||||
QVERIFY(store.getItems()->size() == 1);
|
|
||||||
|
|
||||||
// Remove item
|
|
||||||
ItemData itemData(1, "test_item", 0);
|
|
||||||
store.removeItem(itemData);
|
|
||||||
|
|
||||||
QVERIFY(store.getItems()->empty());
|
|
||||||
}
|
|
||||||
|
|
||||||
void testItemStoreClear()
|
|
||||||
{
|
|
||||||
ItemStore store;
|
|
||||||
|
|
||||||
// Add multiple items
|
|
||||||
for (int i = 0; i < 5; i++) {
|
|
||||||
std::shared_ptr<Item> item(new Item(i, "item" + QString::number(i), 0));
|
|
||||||
ItemAddRequest request;
|
|
||||||
request.type = ITEM_UPDATE_USER;
|
|
||||||
request.payload = item;
|
|
||||||
request.changes = ItemFieldChanges(true);
|
|
||||||
store.addItem(request);
|
|
||||||
}
|
|
||||||
|
|
||||||
QVERIFY(store.getItems()->size() == 5);
|
|
||||||
|
|
||||||
// Clear all items
|
|
||||||
store.clear();
|
|
||||||
|
|
||||||
QVERIFY(store.getItems()->empty());
|
|
||||||
}
|
|
||||||
|
|
||||||
void testItemStoreUpdateItem()
|
|
||||||
{
|
|
||||||
ItemStore store;
|
|
||||||
|
|
||||||
std::shared_ptr<Item> item(new Item(1, "test_item", 0));
|
|
||||||
|
|
||||||
ItemAddRequest request;
|
|
||||||
request.type = ITEM_UPDATE_USER;
|
|
||||||
request.payload = item;
|
|
||||||
request.changes = ItemFieldChanges(true);
|
|
||||||
|
|
||||||
store.addItem(request);
|
|
||||||
|
|
||||||
// Update item value
|
|
||||||
ItemUpdateRequest update;
|
|
||||||
update.type = ITEM_UPDATE_USER;
|
|
||||||
ItemData updatedData(1, "test_item", 1); // value = 1
|
|
||||||
update.payload = updatedData;
|
|
||||||
update.changes.value = true;
|
|
||||||
|
|
||||||
store.updateItem(update);
|
|
||||||
|
|
||||||
// Verify value was updated
|
|
||||||
std::shared_ptr<Item> found = store.getItem(1);
|
|
||||||
QVERIFY(found->getValue() == 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
void testItemStoreUpdateMultipleItems()
|
|
||||||
{
|
|
||||||
ItemStore store;
|
|
||||||
|
|
||||||
// Add items
|
|
||||||
for (int i = 0; i < 3; i++) {
|
|
||||||
std::shared_ptr<Item> item(new Item(i, "item" + QString::number(i), 0));
|
|
||||||
ItemAddRequest request;
|
|
||||||
request.type = ITEM_UPDATE_USER;
|
|
||||||
request.payload = item;
|
|
||||||
request.changes = ItemFieldChanges(true);
|
|
||||||
store.addItem(request);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update multiple items
|
|
||||||
std::vector<ItemUpdateRequest> updates;
|
|
||||||
|
|
||||||
for (int i = 0; i < 3; i++) {
|
|
||||||
ItemUpdateRequest update;
|
|
||||||
update.type = ITEM_UPDATE_USER;
|
|
||||||
ItemData data(i, "item" + QString::number(i), 1);
|
|
||||||
update.payload = data;
|
|
||||||
update.changes.value = true;
|
|
||||||
updates.push_back(update);
|
|
||||||
}
|
|
||||||
|
|
||||||
store.updateItems(updates);
|
|
||||||
|
|
||||||
// Verify all values were updated
|
|
||||||
for (int i = 0; i < 3; i++) {
|
|
||||||
std::shared_ptr<Item> found = store.getItem(i);
|
|
||||||
QVERIFY(found->getValue() == 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void testItemStoreJsonSerialization()
|
|
||||||
{
|
|
||||||
ItemStore store;
|
|
||||||
|
|
||||||
// Add items
|
|
||||||
for (int i = 0; i < 2; i++) {
|
|
||||||
std::shared_ptr<Item> item(new Item(i, "item" + QString::number(i), i));
|
|
||||||
item->setGroupName("TestGroup");
|
|
||||||
ItemAddRequest request;
|
|
||||||
request.type = ITEM_UPDATE_USER;
|
|
||||||
request.payload = item;
|
|
||||||
request.changes = ItemFieldChanges(true);
|
|
||||||
store.addItem(request);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Serialize to JSON
|
|
||||||
QJsonObject json;
|
|
||||||
store.store(json);
|
|
||||||
|
|
||||||
// Verify JSON contains items array
|
|
||||||
QVERIFY(json.contains("Items"));
|
|
||||||
QVERIFY(json["Items"].toArray().size() == 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
void testItemStoreReplaceItems()
|
|
||||||
{
|
|
||||||
ItemStore store;
|
|
||||||
|
|
||||||
// Add initial items
|
|
||||||
std::shared_ptr<Item> item1(new Item(1, "item1", 0));
|
|
||||||
ItemAddRequest request1;
|
|
||||||
request1.type = ITEM_UPDATE_USER;
|
|
||||||
request1.payload = item1;
|
|
||||||
request1.changes = ItemFieldChanges(true);
|
|
||||||
store.addItem(request1);
|
|
||||||
|
|
||||||
QVERIFY(store.getItems()->size() == 1);
|
|
||||||
|
|
||||||
// Replace with new items
|
|
||||||
std::vector<std::shared_ptr<Item>> newItems;
|
|
||||||
newItems.push_back(std::shared_ptr<Item>(new Item(2, "new_item1", 0)));
|
|
||||||
newItems.push_back(std::shared_ptr<Item>(new Item(3, "new_item2", 1)));
|
|
||||||
|
|
||||||
store.replaceItems(newItems);
|
|
||||||
|
|
||||||
// Should have new items
|
|
||||||
QVERIFY(store.getItems()->size() == 2);
|
|
||||||
|
|
||||||
// Old item should be removed
|
|
||||||
QVERIFY(store.getItem(1) == nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
void cleanupTestCase()
|
|
||||||
{
|
|
||||||
// Cleanup after all tests
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
QTEST_APPLESS_MAIN(TestItemStore)
|
|
||||||
|
|
||||||
#include "test_itemstore.moc"
|
|
||||||
|
|
@ -1,306 +0,0 @@
|
||||||
#include <QtTest/QtTest>
|
|
||||||
#include <QJsonDocument>
|
|
||||||
#include <QJsonArray>
|
|
||||||
#include <QJsonObject>
|
|
||||||
#include <QSignalSpy>
|
|
||||||
#include <QStandardPaths>
|
|
||||||
#include <QFile>
|
|
||||||
#include <QDebug>
|
|
||||||
#include <QTextStream>
|
|
||||||
|
|
||||||
#include "items/mqttitem.h"
|
|
||||||
#include "mqttclient.h"
|
|
||||||
#include "programmode.h"
|
|
||||||
|
|
||||||
class TestMqttItem : public QObject
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
private slots:
|
|
||||||
void initTestCase()
|
|
||||||
{
|
|
||||||
// Setup for all tests
|
|
||||||
// Try to load config and connect to MQTT broker if configured
|
|
||||||
QString settingsPath = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + "/shinterface.json";
|
|
||||||
|
|
||||||
QJsonObject json;
|
|
||||||
if (QFile::exists(settingsPath)) {
|
|
||||||
QFile file(settingsPath);
|
|
||||||
if (file.open(QIODevice::ReadOnly)) {
|
|
||||||
QByteArray data = file.readAll();
|
|
||||||
file.close();
|
|
||||||
QJsonParseError error;
|
|
||||||
QJsonDocument doc = QJsonDocument::fromJson(data, &error);
|
|
||||||
if (error.error == QJsonParseError::NoError) {
|
|
||||||
json = doc.object();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QJsonObject mqttJson = json["Mqtt"].toObject();
|
|
||||||
QString host = mqttJson["Host"].toString();
|
|
||||||
int port = mqttJson["Port"].toInt(1883);
|
|
||||||
|
|
||||||
// If MQTT is configured with a host, try to connect
|
|
||||||
static std::shared_ptr<MqttClient> mqttClient;
|
|
||||||
if (!host.isEmpty()) {
|
|
||||||
qDebug() << "MQTT configured:" << host << port;
|
|
||||||
mqttClient = std::make_shared<MqttClient>();
|
|
||||||
mqttClient->start(mqttJson);
|
|
||||||
// Give it a moment to connect
|
|
||||||
QTest::qWait(1000);
|
|
||||||
|
|
||||||
// Check if connected or connecting
|
|
||||||
auto qClient = mqttClient->getClient();
|
|
||||||
if (qClient && (qClient->state() == QMqttClient::Connected ||
|
|
||||||
qClient->state() == QMqttClient::Connecting)) {
|
|
||||||
qDebug() << "MQTT connected/connecting, using client";
|
|
||||||
MqttItem::client = mqttClient;
|
|
||||||
} else {
|
|
||||||
qDebug() << "MQTT connection failed, using UI_ONLY mode";
|
|
||||||
programMode = PROGRAM_MODE_UI_ONLY;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
qDebug() << "No MQTT host configured, using UI_ONLY mode";
|
|
||||||
programMode = PROGRAM_MODE_UI_ONLY;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void testMqttItemCreation()
|
|
||||||
{
|
|
||||||
MqttItem item("test_mqtt", 0);
|
|
||||||
QCOMPARE(item.getName(), QString("test_mqtt"));
|
|
||||||
QVERIFY(item.getTopic().isEmpty());
|
|
||||||
QVERIFY(item.getValueKey() == "state");
|
|
||||||
}
|
|
||||||
|
|
||||||
void testMqttItemSetTopic()
|
|
||||||
{
|
|
||||||
MqttItem item("test_mqtt", 0);
|
|
||||||
item.setTopic("my_device");
|
|
||||||
item.setValueKey("state");
|
|
||||||
|
|
||||||
QVERIFY(item.getTopic() == "my_device");
|
|
||||||
QVERIFY(item.getValueKey() == "state");
|
|
||||||
}
|
|
||||||
|
|
||||||
void testMqttItemSetValueType()
|
|
||||||
{
|
|
||||||
MqttItem item("test_mqtt", 0);
|
|
||||||
|
|
||||||
// Default should be BOOL
|
|
||||||
QVERIFY(item.getValueType() == ITEM_VALUE_BOOL);
|
|
||||||
|
|
||||||
// Set to UINT
|
|
||||||
item.setValueType(ITEM_VALUE_UINT);
|
|
||||||
QVERIFY(item.getValueType() == ITEM_VALUE_UINT);
|
|
||||||
|
|
||||||
// Set to ENUM
|
|
||||||
item.setValueType(ITEM_VALUE_ENUM);
|
|
||||||
QVERIFY(item.getValueType() == ITEM_VALUE_ENUM);
|
|
||||||
}
|
|
||||||
|
|
||||||
void testMqttItemValueNames()
|
|
||||||
{
|
|
||||||
MqttItem item("test_mqtt", 0);
|
|
||||||
|
|
||||||
// Initially empty
|
|
||||||
QVERIFY(item.getValueNames().empty());
|
|
||||||
|
|
||||||
// Set value names
|
|
||||||
std::vector<QString> names = {"off", "heat", "cool"};
|
|
||||||
item.setValueNames(names);
|
|
||||||
|
|
||||||
auto storedNames = item.getValueNames();
|
|
||||||
QVERIFY(storedNames.size() == 3);
|
|
||||||
QVERIFY(storedNames[0] == "off");
|
|
||||||
QVERIFY(storedNames[1] == "heat");
|
|
||||||
QVERIFY(storedNames[2] == "cool");
|
|
||||||
}
|
|
||||||
|
|
||||||
void testValueNameConversion()
|
|
||||||
{
|
|
||||||
MqttItem item("test_mqtt", 0);
|
|
||||||
|
|
||||||
// Set value names for enum
|
|
||||||
std::vector<QString> names = {"off", "heat", "cool", "auto"};
|
|
||||||
item.setValueNames(names);
|
|
||||||
item.setValueType(ITEM_VALUE_ENUM);
|
|
||||||
|
|
||||||
// Test name to index
|
|
||||||
QVERIFY(item.valueNameToIndex("heat") == 1);
|
|
||||||
QVERIFY(item.valueNameToIndex("cool") == 2);
|
|
||||||
QVERIFY(item.valueNameToIndex("unknown") == -1);
|
|
||||||
|
|
||||||
// Test index to name
|
|
||||||
QVERIFY(item.indexToValueName(0) == "off");
|
|
||||||
QVERIFY(item.indexToValueName(3) == "auto");
|
|
||||||
QVERIFY(item.indexToValueName(99).isEmpty()); // Out of bounds
|
|
||||||
}
|
|
||||||
|
|
||||||
void testSetFromExposeBinary()
|
|
||||||
{
|
|
||||||
MqttItem item("test_mqtt", 0);
|
|
||||||
|
|
||||||
QJsonObject expose;
|
|
||||||
expose["type"] = "binary";
|
|
||||||
expose["property"] = "state";
|
|
||||||
expose["value_on"] = "ON";
|
|
||||||
expose["value_off"] = "OFF";
|
|
||||||
|
|
||||||
item.setFromExpose(expose);
|
|
||||||
|
|
||||||
QVERIFY(item.getValueType() == ITEM_VALUE_BOOL);
|
|
||||||
QVERIFY(item.getValueKey() == "state");
|
|
||||||
QVERIFY(item.getValueOn() == "ON");
|
|
||||||
QVERIFY(item.getValueOff() == "OFF");
|
|
||||||
}
|
|
||||||
|
|
||||||
void testSetFromExposeNumeric()
|
|
||||||
{
|
|
||||||
MqttItem item("test_mqtt", 0);
|
|
||||||
|
|
||||||
QJsonObject expose;
|
|
||||||
expose["type"] = "numeric";
|
|
||||||
expose["property"] = "brightness";
|
|
||||||
expose["value_min"] = 0;
|
|
||||||
expose["value_max"] = 254;
|
|
||||||
expose["value_step"] = 1;
|
|
||||||
|
|
||||||
item.setFromExpose(expose);
|
|
||||||
|
|
||||||
QVERIFY(item.getValueType() == ITEM_VALUE_UINT);
|
|
||||||
QVERIFY(item.getValueKey() == "brightness");
|
|
||||||
QVERIFY(item.getValueMin() == 0);
|
|
||||||
QVERIFY(item.getValueMax() == 254);
|
|
||||||
QVERIFY(item.getValueStep() == 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
void testSetFromExposeEnum()
|
|
||||||
{
|
|
||||||
MqttItem item("test_mqtt", 0);
|
|
||||||
|
|
||||||
QJsonObject expose;
|
|
||||||
expose["type"] = "enum";
|
|
||||||
expose["property"] = "system_mode";
|
|
||||||
expose["values"] = QJsonArray{"off", "heat", "cool", "auto"};
|
|
||||||
|
|
||||||
item.setFromExpose(expose);
|
|
||||||
|
|
||||||
QVERIFY(item.getValueType() == ITEM_VALUE_ENUM);
|
|
||||||
QVERIFY(item.getValueKey() == "system_mode");
|
|
||||||
|
|
||||||
auto names = item.getValueNames();
|
|
||||||
QVERIFY(names.size() == 4);
|
|
||||||
QVERIFY(names[0] == "off");
|
|
||||||
QVERIFY(names[1] == "heat");
|
|
||||||
QVERIFY(names[2] == "cool");
|
|
||||||
QVERIFY(names[3] == "auto");
|
|
||||||
}
|
|
||||||
|
|
||||||
void testJsonSerialization()
|
|
||||||
{
|
|
||||||
MqttItem item("test_mqtt", 1);
|
|
||||||
item.setTopic("my_device");
|
|
||||||
item.setValueKey("state");
|
|
||||||
item.setValueOn("ON");
|
|
||||||
item.setValueOff("OFF");
|
|
||||||
|
|
||||||
QJsonObject json;
|
|
||||||
item.store(json);
|
|
||||||
|
|
||||||
QVERIFY(json["Type"] == "Mqtt");
|
|
||||||
QVERIFY(json["Topic"] == "my_device");
|
|
||||||
QVERIFY(json["ValueKey"] == "state");
|
|
||||||
QVERIFY(json["ValueOn"] == "ON");
|
|
||||||
QVERIFY(json["ValueOff"] == "OFF");
|
|
||||||
}
|
|
||||||
|
|
||||||
void testJsonDeserialization()
|
|
||||||
{
|
|
||||||
QJsonObject json;
|
|
||||||
json["Type"] = "Mqtt";
|
|
||||||
json["ItemId"] = 100;
|
|
||||||
json["Name"] = "loaded_mqtt";
|
|
||||||
json["Topic"] = "test_device";
|
|
||||||
json["ValueKey"] = "state";
|
|
||||||
json["ValueOn"] = "ON";
|
|
||||||
json["ValueOff"] = "OFF";
|
|
||||||
json["Value"] = 1;
|
|
||||||
|
|
||||||
MqttItem item;
|
|
||||||
item.load(json);
|
|
||||||
|
|
||||||
QVERIFY(item.getTopic() == "test_device");
|
|
||||||
QVERIFY(item.getValueKey() == "state");
|
|
||||||
QVERIFY(item.getValueOn() == "ON");
|
|
||||||
QVERIFY(item.getValueOff() == "OFF");
|
|
||||||
}
|
|
||||||
|
|
||||||
void testLoadExposeFromDevice()
|
|
||||||
{
|
|
||||||
// Create item with specific topic and valueKey
|
|
||||||
MqttItem item("test", 0);
|
|
||||||
item.setTopic("0xa4c138ef510950e3");
|
|
||||||
item.setValueKey("system_mode");
|
|
||||||
|
|
||||||
// Simulate device data from zigbee2mqtt/bridge/devices
|
|
||||||
QJsonObject device;
|
|
||||||
device["friendly_name"] = "0xa4c138ef510950e3";
|
|
||||||
device["ieee_address"] = "0xa4c138ef510950e3";
|
|
||||||
|
|
||||||
QJsonObject definition;
|
|
||||||
definition["model"] = "TS0601_thermostat";
|
|
||||||
definition["vendor"] = "Tuya";
|
|
||||||
definition["description"] = "Thermostat";
|
|
||||||
|
|
||||||
QJsonArray exposes;
|
|
||||||
|
|
||||||
// Binary expose
|
|
||||||
QJsonObject stateExpose;
|
|
||||||
stateExpose["type"] = "binary";
|
|
||||||
stateExpose["property"] = "state";
|
|
||||||
stateExpose["value_on"] = "ON";
|
|
||||||
stateExpose["value_off"] = "OFF";
|
|
||||||
exposes.append(stateExpose);
|
|
||||||
|
|
||||||
// Enum expose - the one we're looking for
|
|
||||||
QJsonObject systemModeExpose;
|
|
||||||
systemModeExpose["type"] = "enum";
|
|
||||||
systemModeExpose["property"] = "system_mode";
|
|
||||||
systemModeExpose["values"] = QJsonArray{"off", "heat", "cool", "auto"};
|
|
||||||
exposes.append(systemModeExpose);
|
|
||||||
|
|
||||||
// Numeric expose
|
|
||||||
QJsonObject tempExpose;
|
|
||||||
tempExpose["type"] = "numeric";
|
|
||||||
tempExpose["property"] = "current_temperature";
|
|
||||||
tempExpose["value_min"] = 0;
|
|
||||||
tempExpose["value_max"] = 100;
|
|
||||||
exposes.append(tempExpose);
|
|
||||||
|
|
||||||
definition["exposes"] = exposes;
|
|
||||||
device["definition"] = definition;
|
|
||||||
|
|
||||||
// Call the private method via public API - we need to test the logic
|
|
||||||
// Since loadExposeFromDevice is private, we test via setFromExpose
|
|
||||||
QJsonObject enumExpose;
|
|
||||||
enumExpose["type"] = "enum";
|
|
||||||
enumExpose["property"] = "system_mode";
|
|
||||||
enumExpose["values"] = QJsonArray{"off", "heat", "cool", "auto"};
|
|
||||||
|
|
||||||
item.setFromExpose(enumExpose);
|
|
||||||
|
|
||||||
QVERIFY(item.getValueType() == ITEM_VALUE_ENUM);
|
|
||||||
QVERIFY(item.getValueNames().size() == 4);
|
|
||||||
}
|
|
||||||
|
|
||||||
void cleanupTestCase()
|
|
||||||
{
|
|
||||||
// Cleanup after all tests
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
QTEST_APPLESS_MAIN(TestMqttItem)
|
|
||||||
|
|
||||||
#include "test_mqttitem.moc"
|
|
||||||
|
|
@ -1,230 +0,0 @@
|
||||||
#include <QtTest/QtTest>
|
|
||||||
#include "sensors/sensor.h"
|
|
||||||
|
|
||||||
class TestSensor : public QObject
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
private slots:
|
|
||||||
void initTestCase()
|
|
||||||
{
|
|
||||||
// Setup for all tests
|
|
||||||
}
|
|
||||||
|
|
||||||
void testSensorCreation()
|
|
||||||
{
|
|
||||||
Sensor sensor(Sensor::TYPE_TEMPERATURE, 1, 0.0, "test_sensor");
|
|
||||||
|
|
||||||
QCOMPARE(sensor.type, Sensor::TYPE_TEMPERATURE);
|
|
||||||
QCOMPARE(sensor.name, QString("test_sensor"));
|
|
||||||
}
|
|
||||||
|
|
||||||
void testSensorData()
|
|
||||||
{
|
|
||||||
Sensor sensor(Sensor::TYPE_TEMPERATURE, 1, 0.0, "temp_sensor");
|
|
||||||
|
|
||||||
// Test setting and getting values
|
|
||||||
sensor.field = 25.0;
|
|
||||||
QCOMPARE(sensor.field, 25.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
void testSensorTypes()
|
|
||||||
{
|
|
||||||
// Test different sensor types
|
|
||||||
Sensor doorSensor(Sensor::TYPE_DOOR, 1);
|
|
||||||
Sensor tempSensor(Sensor::TYPE_TEMPERATURE, 2);
|
|
||||||
Sensor humiditySensor(Sensor::TYPE_HUMIDITY, 3);
|
|
||||||
|
|
||||||
QCOMPARE(doorSensor.type, Sensor::TYPE_DOOR);
|
|
||||||
QCOMPARE(tempSensor.type, Sensor::TYPE_TEMPERATURE);
|
|
||||||
QCOMPARE(humiditySensor.type, Sensor::TYPE_HUMIDITY);
|
|
||||||
}
|
|
||||||
|
|
||||||
void testSensorStringParsing()
|
|
||||||
{
|
|
||||||
// Test parsing sensor from string (format: "SENSOR TYPE: X ID: Y FIELD: Z TIME: T")
|
|
||||||
// This tests the sensorFromString function
|
|
||||||
QString sensorStr = "SENSOR TYPE: 1 ID: 5 FIELD: 250 TIME: 1234567890";
|
|
||||||
Sensor sensor = Sensor::sensorFromString(sensorStr);
|
|
||||||
|
|
||||||
// The function should parse the type, id, and field
|
|
||||||
// Note: temperature and humidity values are divided by 10
|
|
||||||
QVERIFY(sensor.type != Sensor::TYPE_DUMMY || sensor.field != 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
void testSensorStringParsingInvalid()
|
|
||||||
{
|
|
||||||
// Test parsing invalid string returns dummy sensor
|
|
||||||
QString invalidStr = "invalid data";
|
|
||||||
Sensor sensor = Sensor::sensorFromString(invalidStr);
|
|
||||||
|
|
||||||
// Should return a dummy sensor with hidden=true
|
|
||||||
QVERIFY(sensor.hidden);
|
|
||||||
}
|
|
||||||
|
|
||||||
void testSensorToString()
|
|
||||||
{
|
|
||||||
Sensor sensor(Sensor::TYPE_TEMPERATURE, 1, 25.5, "test_sensor");
|
|
||||||
QString str = sensor.toString();
|
|
||||||
|
|
||||||
// Should contain type, id, and field info
|
|
||||||
QVERIFY(str.contains("SENSOR TYPE"));
|
|
||||||
QVERIFY(str.contains("ID:"));
|
|
||||||
QVERIFY(str.contains("FIELD:"));
|
|
||||||
}
|
|
||||||
|
|
||||||
void testSensorJsonSerialization()
|
|
||||||
{
|
|
||||||
Sensor sensor(Sensor::TYPE_TEMPERATURE, 1, 25.0, "test_sensor");
|
|
||||||
|
|
||||||
QJsonObject json;
|
|
||||||
sensor.store(json);
|
|
||||||
|
|
||||||
// Verify JSON contents
|
|
||||||
QVERIFY(json.contains("Type"));
|
|
||||||
QVERIFY(json.contains("SensorType"));
|
|
||||||
QVERIFY(json.contains("Id"));
|
|
||||||
QVERIFY(json.contains("Field"));
|
|
||||||
QVERIFY(json.contains("Name"));
|
|
||||||
QVERIFY(json.contains("Unit"));
|
|
||||||
|
|
||||||
// Check values
|
|
||||||
QVERIFY(json["Type"].toString() == "Sensor");
|
|
||||||
QVERIFY(json["SensorType"].toInt() == Sensor::TYPE_TEMPERATURE);
|
|
||||||
QVERIFY(json["Id"].toInt() == 1);
|
|
||||||
QVERIFY(json["Field"].toDouble() == 25.0);
|
|
||||||
QVERIFY(json["Name"].toString() == "test_sensor");
|
|
||||||
}
|
|
||||||
|
|
||||||
void testSensorJsonDeserialization()
|
|
||||||
{
|
|
||||||
QJsonObject json;
|
|
||||||
json["SensorType"] = Sensor::TYPE_HUMIDITY;
|
|
||||||
json["Id"] = 5;
|
|
||||||
json["Field"] = 60.5;
|
|
||||||
json["Name"] = "humidity_sensor";
|
|
||||||
json["Hidden"] = false;
|
|
||||||
|
|
||||||
Sensor sensor(json);
|
|
||||||
|
|
||||||
QVERIFY(sensor.type == Sensor::TYPE_HUMIDITY);
|
|
||||||
QVERIFY(sensor.id == 5);
|
|
||||||
QVERIFY(sensor.field == 60.5);
|
|
||||||
QVERIFY(sensor.name == "humidity_sensor");
|
|
||||||
QVERIFY(!sensor.hidden);
|
|
||||||
}
|
|
||||||
|
|
||||||
void testSensorEquality()
|
|
||||||
{
|
|
||||||
Sensor sensor1(Sensor::TYPE_TEMPERATURE, 1, 25.0);
|
|
||||||
Sensor sensor2(Sensor::TYPE_TEMPERATURE, 1, 30.0);
|
|
||||||
Sensor sensor3(Sensor::TYPE_TEMPERATURE, 2, 25.0);
|
|
||||||
|
|
||||||
// Same type and id means equal
|
|
||||||
QVERIFY(sensor1 == sensor2);
|
|
||||||
|
|
||||||
// Different id means not equal
|
|
||||||
QVERIFY(sensor1 != sensor3);
|
|
||||||
}
|
|
||||||
|
|
||||||
void testSensorUpdateSeen()
|
|
||||||
{
|
|
||||||
Sensor sensor(Sensor::TYPE_TEMPERATURE, 1, 25.0);
|
|
||||||
QDateTime before = sensor.lastSeen;
|
|
||||||
|
|
||||||
// Wait a tiny bit and update
|
|
||||||
sensor.updateSeen();
|
|
||||||
|
|
||||||
QVERIFY(sensor.lastSeen >= before);
|
|
||||||
}
|
|
||||||
|
|
||||||
void testSensorGenerateName()
|
|
||||||
{
|
|
||||||
// Test auto-generated names for different sensor types
|
|
||||||
Sensor tempSensor(Sensor::TYPE_TEMPERATURE, 1);
|
|
||||||
QVERIFY(tempSensor.name.contains("Temperature"));
|
|
||||||
|
|
||||||
Sensor doorSensor(Sensor::TYPE_DOOR, 2);
|
|
||||||
QVERIFY(doorSensor.name.contains("Door"));
|
|
||||||
|
|
||||||
Sensor buttonSensor(Sensor::TYPE_BUTTON, 3);
|
|
||||||
QVERIFY(buttonSensor.name.contains("Button"));
|
|
||||||
|
|
||||||
Sensor humiditySensor(Sensor::TYPE_HUMIDITY, 4);
|
|
||||||
QVERIFY(humiditySensor.name.contains("Humidity"));
|
|
||||||
}
|
|
||||||
|
|
||||||
void testSensorGetUnit()
|
|
||||||
{
|
|
||||||
Sensor tempSensor(Sensor::TYPE_TEMPERATURE, 1);
|
|
||||||
QVERIFY(tempSensor.getUnit() == "°C");
|
|
||||||
|
|
||||||
Sensor humiditySensor(Sensor::TYPE_HUMIDITY, 1);
|
|
||||||
QVERIFY(humiditySensor.getUnit() == "%");
|
|
||||||
|
|
||||||
Sensor pressureSensor(Sensor::TYPE_PRESSURE, 1);
|
|
||||||
QVERIFY(pressureSensor.getUnit() == "hPa");
|
|
||||||
|
|
||||||
Sensor brightnessSensor(Sensor::TYPE_BRIGHTNESS, 1);
|
|
||||||
QVERIFY(brightnessSensor.getUnit() == "lx");
|
|
||||||
|
|
||||||
Sensor co2Sensor(Sensor::TYPE_CO2, 1);
|
|
||||||
QVERIFY(co2Sensor.getUnit() == "ppm");
|
|
||||||
|
|
||||||
Sensor vocSensor(Sensor::TYPE_TOTAL_VOC, 1);
|
|
||||||
QVERIFY(vocSensor.getUnit() == "ppb");
|
|
||||||
}
|
|
||||||
|
|
||||||
void testSensorHidden()
|
|
||||||
{
|
|
||||||
Sensor sensor(Sensor::TYPE_TEMPERATURE, 1, 25.0, "test", false);
|
|
||||||
QVERIFY(!sensor.hidden);
|
|
||||||
|
|
||||||
Sensor hiddenSensor(Sensor::TYPE_TEMPERATURE, 2, 25.0, "test", true);
|
|
||||||
QVERIFY(hiddenSensor.hidden);
|
|
||||||
}
|
|
||||||
|
|
||||||
void testSensorAllTypes()
|
|
||||||
{
|
|
||||||
// Test all sensor types can be created
|
|
||||||
Sensor door(Sensor::TYPE_DOOR, 1);
|
|
||||||
Sensor temp(Sensor::TYPE_TEMPERATURE, 1);
|
|
||||||
Sensor humidity(Sensor::TYPE_HUMIDITY, 1);
|
|
||||||
Sensor pressure(Sensor::TYPE_PRESSURE, 1);
|
|
||||||
Sensor brightness(Sensor::TYPE_BRIGHTNESS, 1);
|
|
||||||
Sensor button(Sensor::TYPE_BUTTON, 1);
|
|
||||||
Sensor adc(Sensor::TYPE_ADC, 1);
|
|
||||||
Sensor co2(Sensor::TYPE_CO2, 1);
|
|
||||||
Sensor pm25(Sensor::TYPE_PM25, 1);
|
|
||||||
Sensor voc(Sensor::TYPE_TOTAL_VOC, 1);
|
|
||||||
Sensor lowBattery(Sensor::TYPE_LOWBATTERY, 1);
|
|
||||||
Sensor occupancy(Sensor::TYPE_OCUPANCY, 1);
|
|
||||||
Sensor sunAlt(Sensor::TYPE_SUN_ALTITUDE, 1);
|
|
||||||
Sensor audio(Sensor::TYPE_AUDIO_OUTPUT, 1);
|
|
||||||
|
|
||||||
// All should be valid
|
|
||||||
QVERIFY(door.type == Sensor::TYPE_DOOR);
|
|
||||||
QVERIFY(temp.type == Sensor::TYPE_TEMPERATURE);
|
|
||||||
QVERIFY(humidity.type == Sensor::TYPE_HUMIDITY);
|
|
||||||
QVERIFY(pressure.type == Sensor::TYPE_PRESSURE);
|
|
||||||
QVERIFY(brightness.type == Sensor::TYPE_BRIGHTNESS);
|
|
||||||
QVERIFY(button.type == Sensor::TYPE_BUTTON);
|
|
||||||
QVERIFY(adc.type == Sensor::TYPE_ADC);
|
|
||||||
QVERIFY(co2.type == Sensor::TYPE_CO2);
|
|
||||||
QVERIFY(pm25.type == Sensor::TYPE_PM25);
|
|
||||||
QVERIFY(voc.type == Sensor::TYPE_TOTAL_VOC);
|
|
||||||
QVERIFY(lowBattery.type == Sensor::TYPE_LOWBATTERY);
|
|
||||||
QVERIFY(occupancy.type == Sensor::TYPE_OCUPANCY);
|
|
||||||
QVERIFY(sunAlt.type == Sensor::TYPE_SUN_ALTITUDE);
|
|
||||||
QVERIFY(audio.type == Sensor::TYPE_AUDIO_OUTPUT);
|
|
||||||
}
|
|
||||||
|
|
||||||
void cleanupTestCase()
|
|
||||||
{
|
|
||||||
// Cleanup after all tests
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
QTEST_APPLESS_MAIN(TestSensor)
|
|
||||||
|
|
||||||
#include "test_sensor.moc"
|
|
||||||
|
|
@ -1,343 +0,0 @@
|
||||||
#include <QtTest/QtTest>
|
|
||||||
#include <QTcpServer>
|
|
||||||
#include <QTcpSocket>
|
|
||||||
#include <QJsonDocument>
|
|
||||||
#include <QJsonArray>
|
|
||||||
#include <QSignalSpy>
|
|
||||||
|
|
||||||
#include "service/tcpserver.h"
|
|
||||||
#include "service/tcpclient.h"
|
|
||||||
#include "service/server.h"
|
|
||||||
#include "service/service.h"
|
|
||||||
#include "items/item.h"
|
|
||||||
|
|
||||||
class TestTcp : public QObject
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
private slots:
|
|
||||||
void initTestCase()
|
|
||||||
{
|
|
||||||
// Setup for all tests
|
|
||||||
}
|
|
||||||
|
|
||||||
void testTcpServerCreation()
|
|
||||||
{
|
|
||||||
TcpServer server;
|
|
||||||
|
|
||||||
// Server should be created successfully
|
|
||||||
QVERIFY(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void testTcpServerLaunch()
|
|
||||||
{
|
|
||||||
TcpServer server;
|
|
||||||
|
|
||||||
// Launch server on any address, port 0 means dynamic port
|
|
||||||
bool result = server.launch(QHostAddress::Any, 0);
|
|
||||||
|
|
||||||
QVERIFY(result);
|
|
||||||
|
|
||||||
// Server should be listening after launch
|
|
||||||
QVERIFY(server.isListening());
|
|
||||||
}
|
|
||||||
|
|
||||||
void testTcpServerLaunchSpecificPort()
|
|
||||||
{
|
|
||||||
TcpServer server;
|
|
||||||
|
|
||||||
// Try to launch on a specific port
|
|
||||||
bool result = server.launch(QHostAddress::LocalHost, 0);
|
|
||||||
|
|
||||||
QVERIFY(result);
|
|
||||||
|
|
||||||
// Server should be listening
|
|
||||||
QVERIFY(server.isListening());
|
|
||||||
}
|
|
||||||
|
|
||||||
void testTcpClientCreation()
|
|
||||||
{
|
|
||||||
TcpClient client;
|
|
||||||
|
|
||||||
// Client should be created successfully
|
|
||||||
QVERIFY(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void testTcpProtocolMessageFormat()
|
|
||||||
{
|
|
||||||
// Test the protocol message format: "MSG JSON LEN <size>\n<json>"
|
|
||||||
QJsonObject json;
|
|
||||||
json["MessageType"] = "TestMessage";
|
|
||||||
json["Data"] = QJsonArray();
|
|
||||||
|
|
||||||
QByteArray jsonData = QJsonDocument(json).toJson();
|
|
||||||
QString message = QString("MSG JSON LEN %1\n").arg(jsonData.size()) + QString::fromUtf8(jsonData);
|
|
||||||
|
|
||||||
// Verify message format
|
|
||||||
QVERIFY(message.startsWith("MSG JSON LEN "));
|
|
||||||
QVERIFY(message.contains("\n"));
|
|
||||||
|
|
||||||
// Parse the message size
|
|
||||||
QStringList parts = message.split("\n");
|
|
||||||
QVERIFY(parts.size() >= 1);
|
|
||||||
|
|
||||||
QString sizeStr = parts[0].mid(13); // Skip "MSG JSON LEN "
|
|
||||||
bool ok;
|
|
||||||
quint64 size = sizeStr.toUInt(&ok);
|
|
||||||
QVERIFY(ok);
|
|
||||||
QVERIFY(size == jsonData.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
void testTcpProtocolParseCommand()
|
|
||||||
{
|
|
||||||
// Test parsing a command from the protocol
|
|
||||||
QByteArray command = "MSG JSON LEN 123\n";
|
|
||||||
|
|
||||||
QVERIFY(command.startsWith("MSG JSON LEN "));
|
|
||||||
|
|
||||||
QByteArray sizeStr = command.mid(13);
|
|
||||||
sizeStr.chop(1); // Remove newline
|
|
||||||
|
|
||||||
bool ok;
|
|
||||||
quint64 size = sizeStr.toLongLong(&ok);
|
|
||||||
QVERIFY(ok);
|
|
||||||
QVERIFY(size == 123);
|
|
||||||
}
|
|
||||||
|
|
||||||
void testTcpServerSendJson()
|
|
||||||
{
|
|
||||||
// Start server
|
|
||||||
TcpServer server;
|
|
||||||
bool serverResult = server.launch(QHostAddress::LocalHost, 0);
|
|
||||||
QVERIFY(serverResult);
|
|
||||||
|
|
||||||
// Connect a client to trigger client list
|
|
||||||
QTcpSocket clientSocket;
|
|
||||||
clientSocket.connectToHost(QHostAddress::LocalHost, server.getServerPort());
|
|
||||||
QVERIFY(clientSocket.waitForConnected(1000));
|
|
||||||
|
|
||||||
// Give server time to accept connection
|
|
||||||
QTest::qWait(200);
|
|
||||||
|
|
||||||
// Send JSON from server
|
|
||||||
QJsonObject json;
|
|
||||||
json["MessageType"] = "TestMessage";
|
|
||||||
json["Data"] = QJsonArray();
|
|
||||||
|
|
||||||
server.sendJson(json);
|
|
||||||
|
|
||||||
// Give more time for data to be sent
|
|
||||||
QTest::qWait(200);
|
|
||||||
|
|
||||||
// Client should receive data (or at least the connection should work)
|
|
||||||
// Note: This may fail due to timing in test environment
|
|
||||||
QVERIFY(clientSocket.state() == QTcpSocket::ConnectedState);
|
|
||||||
|
|
||||||
// Cleanup
|
|
||||||
clientSocket.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
void testTcpServerProcessIncomingJson()
|
|
||||||
{
|
|
||||||
// Start server
|
|
||||||
TcpServer server;
|
|
||||||
bool serverResult = server.launch(QHostAddress::LocalHost, 0);
|
|
||||||
QVERIFY(serverResult);
|
|
||||||
|
|
||||||
// Connect client
|
|
||||||
QTcpSocket clientSocket;
|
|
||||||
clientSocket.connectToHost(QHostAddress::LocalHost, server.getServerPort());
|
|
||||||
QVERIFY(clientSocket.waitForConnected(1000));
|
|
||||||
|
|
||||||
// Give server time to accept connection
|
|
||||||
QTest::qWait(100);
|
|
||||||
|
|
||||||
// Send a message that the server can process
|
|
||||||
QJsonObject json;
|
|
||||||
json["MessageType"] = "GetItems";
|
|
||||||
json["Data"] = QJsonArray();
|
|
||||||
|
|
||||||
QByteArray jsonData = QJsonDocument(json).toJson();
|
|
||||||
QString message = QString("MSG JSON LEN %1\n").arg(jsonData.size()) + QString::fromUtf8(jsonData);
|
|
||||||
|
|
||||||
clientSocket.write(message.toUtf8());
|
|
||||||
clientSocket.flush();
|
|
||||||
|
|
||||||
// Give server time to process
|
|
||||||
QTest::qWait(100);
|
|
||||||
|
|
||||||
// Cleanup
|
|
||||||
clientSocket.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
void testTcpServerMultipleClients()
|
|
||||||
{
|
|
||||||
// Start server
|
|
||||||
TcpServer server;
|
|
||||||
bool serverResult = server.launch(QHostAddress::LocalHost, 0);
|
|
||||||
QVERIFY(serverResult);
|
|
||||||
|
|
||||||
// Connect multiple clients
|
|
||||||
QTcpSocket client1;
|
|
||||||
client1.connectToHost(QHostAddress::LocalHost, server.getServerPort());
|
|
||||||
QVERIFY(client1.waitForConnected(1000));
|
|
||||||
|
|
||||||
QTcpSocket client2;
|
|
||||||
client2.connectToHost(QHostAddress::LocalHost, server.getServerPort());
|
|
||||||
QVERIFY(client2.waitForConnected(1000));
|
|
||||||
|
|
||||||
// Give server time to accept connections
|
|
||||||
QTest::qWait(200);
|
|
||||||
|
|
||||||
// Send message to all clients
|
|
||||||
QJsonObject json;
|
|
||||||
json["MessageType"] = "TestMessage";
|
|
||||||
json["Data"] = QJsonArray();
|
|
||||||
|
|
||||||
server.sendJson(json);
|
|
||||||
|
|
||||||
// Give time for data to be sent
|
|
||||||
QTest::qWait(200);
|
|
||||||
|
|
||||||
// Both clients should be connected (timing may affect actual data receipt)
|
|
||||||
QVERIFY(client1.state() == QTcpSocket::ConnectedState);
|
|
||||||
QVERIFY(client2.state() == QTcpSocket::ConnectedState);
|
|
||||||
|
|
||||||
// Cleanup
|
|
||||||
client1.close();
|
|
||||||
client2.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
void testItemUpdateMessageFormat()
|
|
||||||
{
|
|
||||||
// Test creating an item update message format
|
|
||||||
Item item(1, "test_item", 1);
|
|
||||||
|
|
||||||
QJsonObject itemJson;
|
|
||||||
item.store(itemJson);
|
|
||||||
|
|
||||||
QJsonArray items;
|
|
||||||
items.append(itemJson);
|
|
||||||
|
|
||||||
// Manually create the message (since createMessage is protected)
|
|
||||||
QJsonObject message;
|
|
||||||
message["MessageType"] = "ItemUpdate";
|
|
||||||
message["Data"] = items;
|
|
||||||
message["FullList"] = false;
|
|
||||||
|
|
||||||
QVERIFY(message["MessageType"].toString() == "ItemUpdate");
|
|
||||||
QVERIFY(message["Data"].toArray().size() == 1);
|
|
||||||
QVERIFY(message["FullList"].toBool() == false);
|
|
||||||
}
|
|
||||||
|
|
||||||
void testSensorUpdateMessageFormat()
|
|
||||||
{
|
|
||||||
// Test creating a sensor update message format
|
|
||||||
Sensor sensor(Sensor::TYPE_TEMPERATURE, 1, 25.0, "temp_sensor");
|
|
||||||
|
|
||||||
QJsonObject sensorJson;
|
|
||||||
sensor.store(sensorJson);
|
|
||||||
|
|
||||||
QJsonArray sensors;
|
|
||||||
sensors.append(sensorJson);
|
|
||||||
|
|
||||||
// Manually create the message (since createMessage is protected)
|
|
||||||
QJsonObject message;
|
|
||||||
message["MessageType"] = "SensorUpdate";
|
|
||||||
message["Data"] = sensors;
|
|
||||||
|
|
||||||
QVERIFY(message["MessageType"].toString() == "SensorUpdate");
|
|
||||||
QVERIFY(message["Data"].toArray().size() == 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
void testTcpClientSocketState()
|
|
||||||
{
|
|
||||||
// Test that TcpClient creates a socket
|
|
||||||
TcpClient client;
|
|
||||||
|
|
||||||
// The client should have a valid socket (internal implementation detail)
|
|
||||||
// We can verify the client was created successfully
|
|
||||||
QVERIFY(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void testTcpServerClientConnection()
|
|
||||||
{
|
|
||||||
// Start server
|
|
||||||
TcpServer server;
|
|
||||||
bool serverResult = server.launch(QHostAddress::LocalHost, 0);
|
|
||||||
QVERIFY(serverResult);
|
|
||||||
|
|
||||||
// Connect a client
|
|
||||||
QTcpSocket clientSocket;
|
|
||||||
clientSocket.connectToHost(QHostAddress::LocalHost, server.getServerPort());
|
|
||||||
|
|
||||||
bool waitResult = clientSocket.waitForConnected(1000);
|
|
||||||
QVERIFY(waitResult);
|
|
||||||
|
|
||||||
// Verify connection state
|
|
||||||
QVERIFY(clientSocket.state() == QTcpSocket::ConnectedState);
|
|
||||||
|
|
||||||
// Cleanup
|
|
||||||
clientSocket.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
void testTcpServerDisconnection()
|
|
||||||
{
|
|
||||||
// Start server
|
|
||||||
TcpServer server;
|
|
||||||
bool serverResult = server.launch(QHostAddress::LocalHost, 0);
|
|
||||||
QVERIFY(serverResult);
|
|
||||||
|
|
||||||
// Connect a client
|
|
||||||
QTcpSocket* client = new QTcpSocket();
|
|
||||||
client->connectToHost(QHostAddress::LocalHost, server.getServerPort());
|
|
||||||
QVERIFY(client->waitForConnected(1000));
|
|
||||||
|
|
||||||
// Give server time to accept connection
|
|
||||||
QTest::qWait(100);
|
|
||||||
|
|
||||||
// Disconnect client
|
|
||||||
client->disconnectFromHost();
|
|
||||||
QTest::qWait(100);
|
|
||||||
|
|
||||||
// Cleanup
|
|
||||||
delete client;
|
|
||||||
}
|
|
||||||
|
|
||||||
void testTcpProtocolLargeMessage()
|
|
||||||
{
|
|
||||||
// Test the protocol with a larger message
|
|
||||||
QJsonObject json;
|
|
||||||
json["MessageType"] = "TestMessage";
|
|
||||||
|
|
||||||
// Create a large data array
|
|
||||||
QJsonArray data;
|
|
||||||
for (int i = 0; i < 100; i++) {
|
|
||||||
data.append(QJsonObject{{"id", i}, {"value", QString("item%1").arg(i)}});
|
|
||||||
}
|
|
||||||
json["Data"] = data;
|
|
||||||
|
|
||||||
QByteArray jsonData = QJsonDocument(json).toJson();
|
|
||||||
QString message = QString("MSG JSON LEN %1\n").arg(jsonData.size()) + QString::fromUtf8(jsonData);
|
|
||||||
|
|
||||||
// Verify message format
|
|
||||||
QVERIFY(message.startsWith("MSG JSON LEN "));
|
|
||||||
|
|
||||||
// Parse the message size
|
|
||||||
QStringList parts = message.split("\n");
|
|
||||||
QString sizeStr = parts[0].mid(13);
|
|
||||||
bool ok;
|
|
||||||
quint64 size = sizeStr.toUInt(&ok);
|
|
||||||
QVERIFY(ok);
|
|
||||||
QVERIFY(size == jsonData.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
void cleanupTestCase()
|
|
||||||
{
|
|
||||||
// Cleanup after all tests
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
QTEST_APPLESS_MAIN(TestTcp)
|
|
||||||
|
|
||||||
#include "test_tcp.moc"
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 3.1 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 2.9 KiB |
|
|
@ -1,10 +0,0 @@
|
||||||
[Desktop Entry]
|
|
||||||
Name=SmartVOS
|
|
||||||
Comment=Smart Home Interface
|
|
||||||
Exec=smartvos -H 10.0.0.1
|
|
||||||
Icon=xyz.uvos.icon
|
|
||||||
Terminal=false
|
|
||||||
Type=Application
|
|
||||||
Categories=Utility;HomeAutomation;
|
|
||||||
Keywords=smart;home;automation;iot;
|
|
||||||
StartupNotify=true
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue