Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 2 additions & 5 deletions .github/workflows/docker-image.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ jobs:
run: |
VERSION=$(jq -r '.version' config.json)
echo "version=${VERSION}" >> $GITHUB_OUTPUT

- name: Docker meta
id: meta
uses: docker/metadata-action@v5
Expand All @@ -56,7 +55,6 @@ jobs:
else
echo "platforms=linux/amd64,linux/arm64" >> $GITHUB_OUTPUT
fi

- name: Build and push container image
uses: docker/build-push-action@v6.13.0
id: docker_build
Expand Down Expand Up @@ -88,7 +86,6 @@ jobs:
org.opencontainers.image.vendor=HSRM
org.opencontainers.image.authors="Philipp Seelos"
org.opencontainers.image.version=${{ steps.extract_info.outputs.version }}
org.opencontainers.image.url=https://github.com/phiph-s/metermonitor-managementserver

org.opencontainers.image.url=https://github.com/phiph-s/metermonitor-managementserver
- name: Image digest
run: echo ${{ steps.docker_build.outputs.digest }}
run: echo ${{ steps.docker_build.outputs.digest }}
1 change: 1 addition & 0 deletions .idea/metermonitor-managementserver.iml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions .idea/vcs.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
v3.3.0 - 06.02.2026
- Added E2E tests and Python unit-tests
- Fixed static extractor

v3.2.x - 02.02.2026
- Fixed non-MQTT sources (HTTP, Home Assistant Camera) not publishing meter values to MQTT broker
- Added dedicated MQTT publisher client for polling/capture operations
Expand Down
3 changes: 1 addition & 2 deletions config.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "Meter Monitor",
"version": "3.2.3",
"version": "3.3.0",
"image": "ghcr.io/phiph-s/metermonitor-managementserver",
"slug": "meter_monitor",
"description": "Integrate ESP32 based meter monitor cams with Home Assistant using AI",
Expand All @@ -26,7 +26,6 @@
"allow_negative_correction": true,
"publish_to": "homeassistant/sensor/watermeter_{device}/",
"dbfile": "/data/metermonitor.db",

"homeassistant": {
"use_supervisor_token": true,
"url": "http://supervisor/core",
Expand Down
51 changes: 39 additions & 12 deletions db/migrations.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ def run_migrations(db_file):
picture_height INTEGER,
picture_length INTEGER,
picture_data TEXT,
setup BOOLEAN DEFAULT 0
setup BOOLEAN DEFAULT 0,
picture_data_bbox BLOB
)
''')
cursor.execute('''
Expand All @@ -36,29 +37,55 @@ def run_migrations(db_file):
shrink_last_3 BOOLEAN,
extended_last_digit BOOLEAN,
max_flow_rate FLOAT,
roi_extractor TEXT,
template_id TEXT,
conf_threshold REAL DEFAULT NULL,
roi_extractor TEXT DEFAULT 'yolo',
template_id TEXT DEFAULT NULL,
use_correctional_alg BOOLEAN DEFAULT true,
FOREIGN KEY (name) REFERENCES watermeters (name)
)
''')
# Add evaluations table
# Add evaluations table (latest schema)
cursor.execute('''
CREATE TABLE IF NOT EXISTS evaluations
(
name TEXT,
eval TEXT,
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
colored_digits TEXT,
th_digits TEXT,
predictions TEXT,
timestamp DATETIME,
result INTEGER,
total_confidence REAL,
outdated BOOLEAN DEFAULT 0,
denied_digits TEXT,
th_digits_inverted TEXT,
used_confidence REAL DEFAULT -1.0,
flow_rate_m3h REAL,
delta_m3 REAL,
delta_raw INTEGER,
time_diff_min REAL,
rejection_reason TEXT,
negative_correction_applied BOOLEAN,
fallback_digit_count INTEGER,
digits_changed_vs_last INTEGER,
digits_changed_vs_top_pred INTEGER,
prediction_rank_used_counts TEXT,
denied_digits_count INTEGER,
timestamp_adjusted BOOLEAN,
FOREIGN KEY (name) REFERENCES watermeters (name)
)
''')
cursor.execute('''
CREATE TABLE IF NOT EXISTS history
(
name TEXT,
value INTEGER,
confidence FLOAT,
target_brightness FLOAT,
timestamp TEXT,
manual BOOLEAN,
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
value INTEGER,
confidence REAL,
used_confidence REAL DEFAULT -1.0,
target_brightness REAL,
timestamp TEXT,
manual BOOLEAN,
FOREIGN KEY (name) REFERENCES watermeters (name)
)
''')
Expand Down
16 changes: 14 additions & 2 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,34 @@
"scripts": {
"dev": "vite",
"serve": "vite preview",
"build": "vite build"
"build": "vite build",
"test:e2e": "VITE_HOST=/ VITE_E2E_SKIP_THRESHOLD_SEARCH=true yarn build && playwright test",
"test:e2e:headed": "VITE_HOST=/ VITE_E2E_SKIP_THRESHOLD_SEARCH=true yarn build && playwright test --headed",
"test:component": "vitest run",
"test:component:watch": "vitest"
},
"dependencies": {
"@vicons/material": "^0.13.0",
"apexcharts": "^4.4.0",
"naive-ui": "^2.41.0",
"pinia": "^3.0.4",
"playwright": "^1.58.1",
"vue": "^3.2.13",
"vue-router": "^4.5.0",
"vue3-apexcharts": "^1.8.0"
},
"devDependencies": {
"@playwright/test": "^1.49.1",
"@vitejs/plugin-vue": "^5.2.0",
"@vue/test-utils": "^2.4.6",
"aedes": "^0.51.2",
"eslint": "^7.32.0",
"eslint-plugin-vue": "^8.0.3",
"vite": "^6.0.6"
"happy-dom": "^14.11.0",
"mqtt": "^5.7.2",
"vite": "^6.0.6",
"vitest": "^1.6.0",
"ws": "^8.17.1"
},
"eslintConfig": {
"root": true,
Expand Down
11 changes: 6 additions & 5 deletions frontend/src/components/AddSourceDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,19 @@
<n-form :model="form" :disabled="saving || testing" label-placement="top">
<n-grid :cols="24" :x-gap="12" :y-gap="8">
<n-form-item-gi :span="24" label="Source type">
<n-select v-model:value="selectedType" :options="typeOptions" />
<n-select v-model:value="selectedType" :options="typeOptions" data-testid="source-type-select" />
</n-form-item-gi>

<n-form-item-gi :span="12" label="Watermeter name" v-if="selectedType !== 'mqtt'">
<n-input v-model:value="form.name" placeholder="e.g. Hauptzaehler" />
<n-input v-model:value="form.name" placeholder="e.g. Hauptzaehler" data-testid="source-name-input" />
</n-form-item-gi>

<n-form-item-gi :span="6" label="Enabled" v-if="selectedType !== 'mqtt'">
<n-switch v-model:value="form.enabled" />
</n-form-item-gi>

<n-form-item-gi :span="6" label="Poll interval (m)" v-if="selectedType !== 'mqtt'">
<n-input-number v-model:value="form.poll_interval_m" :min="1" :max="3600" />
<n-input-number v-model:value="form.poll_interval_m" :min="1" :max="3600" data-testid="source-poll-interval" />
</n-form-item-gi>

<!-- Home Assistant camera config -->
Expand All @@ -29,6 +29,7 @@
filterable
placeholder="Select a Home Assistant camera.* entity"
:loading="loadingCameras"
data-testid="ha-camera-select"
/>
</n-form-item-gi>

Expand Down Expand Up @@ -88,7 +89,7 @@
<!-- HTTP source config -->
<template v-if="selectedType === 'http'">
<n-form-item-gi :span="24" label="Image URL">
<n-input v-model:value="form.http_url" placeholder="https://example.com/camera.jpg" />
<n-input v-model:value="form.http_url" placeholder="https://example.com/camera.jpg" data-testid="http-url-input" />
</n-form-item-gi>

<n-form-item-gi :span="12" label="Headers (JSON)">
Expand Down Expand Up @@ -144,7 +145,7 @@

<n-space justify="end">
<n-button @click="close" :disabled="saving || testing">Cancel</n-button>
<n-button type="success" @click="create" :loading="saving" :disabled="!canCreate || testing">
<n-button type="success" @click="create" :loading="saving" :disabled="!canCreate || testing" data-testid="create-source-button">
Create source
</n-button>
</n-space>
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/stores/watermeterStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ export const useWatermeterStore = defineStore('watermeter', () => {
const capturing = ref(false);
const settings = reactive({
threshold_low: 0,
threshold_high: 100,
threshold_high: 125,
threshold_last_low: 0,
threshold_last_high: 100,
threshold_last_high: 125,
islanding_padding: 0,
segments: 0,
extended_last_digit: false,
Expand Down
6 changes: 3 additions & 3 deletions frontend/src/views/DiscoveryView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
</n-button>
</n-flex>
</div>
<div class="add-card" @click="showAddSource = true" style="margin-left: 20px;">
<div class="add-card" data-testid="add-watermeter-card" @click="showAddSource = true" style="margin-left: 20px;">
<div class="add-card-inner">
<n-icon><AddOutlined /></n-icon>
<span>Add watermeter</span>
Expand Down Expand Up @@ -99,7 +99,7 @@
:source_type="item[7]"
@removed="getData"
/>
<div class="add-card" @click="showAddSource = true">
<div class="add-card" data-testid="add-watermeter-card" @click="showAddSource = true">
<div class="add-card-inner">
<n-icon><AddOutlined /></n-icon>
<span>Add watermeter</span>
Expand All @@ -108,7 +108,7 @@
</n-flex>
</template>
<n-flex class="watermeters-row" v-else>
<div class="add-card" @click="showAddSource = true" >
<div class="add-card" data-testid="add-watermeter-card" @click="showAddSource = true" >
<div class="add-card-inner">
<n-icon><AddOutlined /></n-icon>
<span>Add watermeter</span>
Expand Down
4 changes: 3 additions & 1 deletion frontend/src/views/SetupView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,9 @@ const onSegmentationNext = () => {
currentlyFocusedStep.value = 2;

// Automatically start threshold search with default depth (10)
setupStore.searchThresholds(id, 10);
if (import.meta.env.VITE_E2E_SKIP_THRESHOLD_SEARCH !== 'true') {
setupStore.searchThresholds(id, 10);
}
};

onMounted(() => {
Expand Down
Loading