Compare commits
No commits in common. "ffe8b0095de4ca4c4f3a0fa064aa254a03b1f7d0" and "32dd6bc2feb8737f74216fe3fc30041ddc61353a" have entirely different histories.
ffe8b0095d
...
32dd6bc2fe
4 changed files with 8 additions and 148 deletions
|
|
@ -1,7 +1,7 @@
|
||||||
# smartvos - Smart Home System
|
# SHinterface - Smart Home Interface
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
smartvos 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.
|
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
|
## Architecture
|
||||||
|
|
||||||
|
|
|
||||||
142
remote.html
142
remote.html
|
|
@ -185,25 +185,6 @@
|
||||||
.refresh-btn:hover {
|
.refresh-btn:hover {
|
||||||
background-color: #45a049;
|
background-color: #45a049;
|
||||||
}
|
}
|
||||||
.filter-container {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
.filter-label {
|
|
||||||
color: #ccc;
|
|
||||||
font-size: 0.9em;
|
|
||||||
}
|
|
||||||
.filter-select {
|
|
||||||
flex: 1;
|
|
||||||
padding: 8px;
|
|
||||||
border-radius: 5px;
|
|
||||||
border: 1px solid #555;
|
|
||||||
background-color: #4a4c56;
|
|
||||||
color: white;
|
|
||||||
font-size: 0.9em;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
@ -217,22 +198,10 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="items-tab" class="tab-content active">
|
<div id="items-tab" class="tab-content active">
|
||||||
<div class="filter-container">
|
|
||||||
<span class="filter-label">Group:</span>
|
|
||||||
<select id="itemGroupFilter" class="filter-select" onchange="renderItems()">
|
|
||||||
<option value="">All</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="item-list" id="itemList"></div>
|
<div class="item-list" id="itemList"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="sensors-tab" class="tab-content">
|
<div id="sensors-tab" class="tab-content">
|
||||||
<div class="filter-container">
|
|
||||||
<span class="filter-label">Group:</span>
|
|
||||||
<select id="sensorGroupFilter" class="filter-select" onchange="renderSensors()">
|
|
||||||
<option value="">All</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="sensor-list" id="sensorList"></div>
|
<div class="sensor-list" id="sensorList"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -341,24 +310,14 @@
|
||||||
|
|
||||||
function renderItems() {
|
function renderItems() {
|
||||||
const itemList = document.getElementById('itemList');
|
const itemList = document.getElementById('itemList');
|
||||||
const groupFilter = document.getElementById('itemGroupFilter').value;
|
|
||||||
itemList.innerHTML = '';
|
itemList.innerHTML = '';
|
||||||
|
|
||||||
// Update group filter options
|
if (Object.keys(items).length === 0) {
|
||||||
updateItemGroupFilter();
|
|
||||||
|
|
||||||
// Filter items by group
|
|
||||||
let filteredItems = Object.values(items);
|
|
||||||
if (groupFilter) {
|
|
||||||
filteredItems = filteredItems.filter(item => item.GroupName === groupFilter);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (filteredItems.length === 0) {
|
|
||||||
itemList.innerHTML = '<div style="text-align: center; color: #666; padding: 20px;">No items found</div>';
|
itemList.innerHTML = '<div style="text-align: center; color: #666; padding: 20px;">No items found</div>';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
filteredItems.forEach(item => {
|
Object.values(items).forEach(item => {
|
||||||
const itemCard = document.createElement('div');
|
const itemCard = document.createElement('div');
|
||||||
itemCard.className = 'item-card';
|
itemCard.className = 'item-card';
|
||||||
|
|
||||||
|
|
@ -389,26 +348,12 @@
|
||||||
case 2: // ITEM_VALUE_NO_VALUE
|
case 2: // ITEM_VALUE_NO_VALUE
|
||||||
controlHtml = '<div style="color: #b9b9b9;">No control</div>';
|
controlHtml = '<div style="color: #b9b9b9;">No control</div>';
|
||||||
break;
|
break;
|
||||||
case 3: // ITEM_VALUE_ENUM
|
|
||||||
const valueNames = item.ValueNames || [];
|
|
||||||
let optionsHtml = '';
|
|
||||||
valueNames.forEach((name, index) => {
|
|
||||||
optionsHtml += `<option value="${index}" ${value === index ? 'selected' : ''}>${name}</option>`;
|
|
||||||
});
|
|
||||||
controlHtml = `
|
|
||||||
<select onchange="setEnumItem(${item.ItemId}, this.value)"
|
|
||||||
style="padding: 5px; border-radius: 5px; border: 1px solid #ddd; min-width: 100px;">
|
|
||||||
${optionsHtml}
|
|
||||||
</select>
|
|
||||||
`;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const groupInfo = item.GroupName ? ` | Group: ${item.GroupName}` : '';
|
|
||||||
itemCard.innerHTML = `
|
itemCard.innerHTML = `
|
||||||
<div class="item-info">
|
<div class="item-info">
|
||||||
<div class="item-name">${item.Name}</div>
|
<div class="item-name">${item.Name}</div>
|
||||||
<div class="item-id">ItemId: ${item.ItemId} | Type: ${getTypeName(type)}${groupInfo}</div>
|
<div class="item-id">ItemId: ${item.ItemId} | Type: ${getTypeName(type)}</div>
|
||||||
</div>
|
</div>
|
||||||
${controlHtml}
|
${controlHtml}
|
||||||
`;
|
`;
|
||||||
|
|
@ -417,28 +362,6 @@
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateItemGroupFilter() {
|
|
||||||
const select = document.getElementById('itemGroupFilter');
|
|
||||||
const currentValue = select.value;
|
|
||||||
const groups = new Set();
|
|
||||||
|
|
||||||
Object.values(items).forEach(item => {
|
|
||||||
if (item.GroupName) {
|
|
||||||
groups.add(item.GroupName);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
select.innerHTML = '<option value="">All</option>';
|
|
||||||
Array.from(groups).sort().forEach(group => {
|
|
||||||
const option = document.createElement('option');
|
|
||||||
option.value = group;
|
|
||||||
option.textContent = group;
|
|
||||||
select.appendChild(option);
|
|
||||||
});
|
|
||||||
|
|
||||||
select.value = currentValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleItem(itemId, newValue) {
|
function toggleItem(itemId, newValue) {
|
||||||
if (!socket || socket.readyState !== WebSocket.OPEN) {
|
if (!socket || socket.readyState !== WebSocket.OPEN) {
|
||||||
alert('Not connected to server');
|
alert('Not connected to server');
|
||||||
|
|
@ -478,30 +401,11 @@
|
||||||
socket.send(JSON.stringify(message));
|
socket.send(JSON.stringify(message));
|
||||||
}
|
}
|
||||||
|
|
||||||
function setEnumItem(itemId, value) {
|
|
||||||
if (!socket || socket.readyState !== WebSocket.OPEN) {
|
|
||||||
alert('Not connected to server');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const message = {
|
|
||||||
MessageType: 'ItemUpdate',
|
|
||||||
Data: [{
|
|
||||||
ItemId: itemId,
|
|
||||||
Value: parseInt(value)
|
|
||||||
}],
|
|
||||||
FullList: false
|
|
||||||
};
|
|
||||||
|
|
||||||
socket.send(JSON.stringify(message));
|
|
||||||
}
|
|
||||||
|
|
||||||
function getTypeName(type) {
|
function getTypeName(type) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 0: return 'BOOL';
|
case 0: return 'BOOL';
|
||||||
case 1: return 'UINT';
|
case 1: return 'UINT';
|
||||||
case 2: return 'NO_VALUE';
|
case 2: return 'NO_VALUE';
|
||||||
case 3: return 'ENUM';
|
|
||||||
default: return 'UNKNOWN';
|
default: return 'UNKNOWN';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -521,24 +425,14 @@
|
||||||
|
|
||||||
function renderSensors() {
|
function renderSensors() {
|
||||||
const sensorList = document.getElementById('sensorList');
|
const sensorList = document.getElementById('sensorList');
|
||||||
const groupFilter = document.getElementById('sensorGroupFilter').value;
|
|
||||||
sensorList.innerHTML = '';
|
sensorList.innerHTML = '';
|
||||||
|
|
||||||
// Update group filter options
|
if (Object.keys(sensors).length === 0) {
|
||||||
updateSensorGroupFilter();
|
|
||||||
|
|
||||||
// Filter sensors by group
|
|
||||||
let filteredSensors = Object.values(sensors);
|
|
||||||
if (groupFilter) {
|
|
||||||
filteredSensors = filteredSensors.filter(sensor => sensor.GroupName === groupFilter);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (filteredSensors.length === 0) {
|
|
||||||
sensorList.innerHTML = '<div style="text-align: center; color: #666; padding: 20px;">No sensors found</div>';
|
sensorList.innerHTML = '<div style="text-align: center; color: #666; padding: 20px;">No sensors found</div>';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
filteredSensors.forEach(sensor => {
|
Object.values(sensors).forEach(sensor => {
|
||||||
const sensorCard = document.createElement('div');
|
const sensorCard = document.createElement('div');
|
||||||
sensorCard.className = 'sensor-card';
|
sensorCard.className = 'sensor-card';
|
||||||
|
|
||||||
|
|
@ -562,12 +456,11 @@
|
||||||
|
|
||||||
// Truncate to 2 decimal places
|
// Truncate to 2 decimal places
|
||||||
const fieldValue = Number(sensor.Field).toFixed(2);
|
const fieldValue = Number(sensor.Field).toFixed(2);
|
||||||
const groupInfo = sensor.GroupName ? ` | Group: ${sensor.GroupName}` : '';
|
|
||||||
|
|
||||||
sensorCard.innerHTML = `
|
sensorCard.innerHTML = `
|
||||||
<div>
|
<div>
|
||||||
<div class="sensor-name">${sensor.Name}</div>
|
<div class="sensor-name">${sensor.Name}</div>
|
||||||
<div class="sensor-info">Type: ${typeName} | ID: ${sensor.Id}${groupInfo}</div>
|
<div class="sensor-info">Type: ${typeName} | ID: ${sensor.Id}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="sensor-value-container">
|
<div class="sensor-value-container">
|
||||||
<div class="sensor-value">
|
<div class="sensor-value">
|
||||||
|
|
@ -580,34 +473,11 @@
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateSensorGroupFilter() {
|
|
||||||
const select = document.getElementById('sensorGroupFilter');
|
|
||||||
const currentValue = select.value;
|
|
||||||
const groups = new Set();
|
|
||||||
|
|
||||||
Object.values(sensors).forEach(sensor => {
|
|
||||||
if (sensor.GroupName) {
|
|
||||||
groups.add(sensor.GroupName);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
select.innerHTML = '<option value="">All</option>';
|
|
||||||
Array.from(groups).sort().forEach(group => {
|
|
||||||
const option = document.createElement('option');
|
|
||||||
option.value = group;
|
|
||||||
option.textContent = group;
|
|
||||||
select.appendChild(option);
|
|
||||||
});
|
|
||||||
|
|
||||||
select.value = currentValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getTypeName(type) {
|
function getTypeName(type) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 0: return 'BOOL';
|
case 0: return 'BOOL';
|
||||||
case 1: return 'UINT';
|
case 1: return 'UINT';
|
||||||
case 2: return 'NO_VALUE';
|
case 2: return 'NO_VALUE';
|
||||||
case 3: return 'ENUM';
|
|
||||||
default: return 'UNKNOWN';
|
default: return 'UNKNOWN';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -191,14 +191,6 @@ void MqttSensorSource::onMessageReceived(const QMqttMessage& message)
|
||||||
sensor.field = obj["voltage"].toDouble(0);
|
sensor.field = obj["voltage"].toDouble(0);
|
||||||
stateChanged(sensor, SENSOR_UPDATE_BACKEND);
|
stateChanged(sensor, SENSOR_UPDATE_BACKEND);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(obj.contains("soil_moisture"))
|
|
||||||
{
|
|
||||||
sensor.name = baseName + " Soil Moisture";
|
|
||||||
sensor.type = Sensor::TYPE_SOIL_MOISTURE;
|
|
||||||
sensor.field = obj["soil_moisture"].toDouble(0);
|
|
||||||
stateChanged(sensor, SENSOR_UPDATE_BACKEND);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,6 @@ public:
|
||||||
TYPE_ENERGY_USE,
|
TYPE_ENERGY_USE,
|
||||||
TYPE_POWER,
|
TYPE_POWER,
|
||||||
TYPE_VOLTAGE,
|
TYPE_VOLTAGE,
|
||||||
TYPE_SOIL_MOISTURE,
|
|
||||||
TYPE_LOWBATTERY = 128,
|
TYPE_LOWBATTERY = 128,
|
||||||
TYPE_SHUTDOWN_IMMINENT = 251,
|
TYPE_SHUTDOWN_IMMINENT = 251,
|
||||||
TYPE_OCUPANCY,
|
TYPE_OCUPANCY,
|
||||||
|
|
@ -144,7 +143,6 @@ public:
|
||||||
case TYPE_TEMPERATURE:
|
case TYPE_TEMPERATURE:
|
||||||
return "°C";
|
return "°C";
|
||||||
case TYPE_HUMIDITY:
|
case TYPE_HUMIDITY:
|
||||||
case TYPE_SOIL_MOISTURE:
|
|
||||||
return "%";
|
return "%";
|
||||||
case TYPE_PRESSURE:
|
case TYPE_PRESSURE:
|
||||||
return "hPa";
|
return "hPa";
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue