pridany skript pre kontrolu zlyhania merani
This commit is contained in:
@ -6,6 +6,7 @@ This repository contains a small ESP8266/ESP32 Arduino project for monitoring wa
|
|||||||
|
|
||||||
- `arduino/arduino.ino` contains the Arduino sketch and main application logic.
|
- `arduino/arduino.ino` contains the Arduino sketch and main application logic.
|
||||||
- `server/log.php` accepts measurement POST requests and appends them as JSON Lines.
|
- `server/log.php` accepts measurement POST requests and appends them as JSON Lines.
|
||||||
|
- `server/data-checker.php` is intended for hourly CRON runs and checks whether recent measurements are still being written.
|
||||||
- `server/functions.inc.php` contains shared PHP helpers, including JSON responses, HTTP requests, and Telegram notifications.
|
- `server/functions.inc.php` contains shared PHP helpers, including JSON responses, HTTP requests, and Telegram notifications.
|
||||||
- `data/YYYY.jsonl` is created at runtime for logged measurements and should not be treated as source code.
|
- `data/YYYY.jsonl` is created at runtime for logged measurements and should not be treated as source code.
|
||||||
- `README.md` gives the high-level project purpose.
|
- `README.md` gives the high-level project purpose.
|
||||||
@ -30,6 +31,7 @@ arduino-cli upload -p COM3 --fqbn esp32:esp32:esp32 arduino
|
|||||||
arduino-cli monitor -p COM3 -c baudrate=115200
|
arduino-cli monitor -p COM3 -c baudrate=115200
|
||||||
|
|
||||||
php -l server/log.php
|
php -l server/log.php
|
||||||
|
php -l server/data-checker.php
|
||||||
php -l server/functions.inc.php
|
php -l server/functions.inc.php
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -74,10 +76,13 @@ Before committing firmware changes:
|
|||||||
Before committing server changes:
|
Before committing server changes:
|
||||||
|
|
||||||
- Run `php -l server/log.php`.
|
- Run `php -l server/log.php`.
|
||||||
|
- Run `php -l server/data-checker.php` when the CRON checker changes.
|
||||||
- Run `php -l server/functions.inc.php` when shared helpers change.
|
- Run `php -l server/functions.inc.php` when shared helpers change.
|
||||||
- Test that non-POST requests return HTTP 405 with `Allow: POST`.
|
- Test that non-POST requests return HTTP 405 with `Allow: POST`.
|
||||||
- Test a form POST such as `level1=1&level2=0&level3=0&level4=1`.
|
- Test a form POST such as `level1=1&level2=0&level3=0&level4=1`.
|
||||||
- Verify that a single compact JSON object is appended to `data/YYYY.jsonl`.
|
- Verify that a single compact JSON object is appended to `data/YYYY.jsonl`.
|
||||||
|
- Verify the CRON checker stays silent when `data/YYYY.jsonl` is missing, empty, younger than 45 minutes, or older than 3 hours and 45 minutes.
|
||||||
|
- Verify the CRON checker walks backward to the nearest valid JSON line when the last line is invalid.
|
||||||
- For notification changes, verify that Telegram messages are sent only when `level1..level4` differ from the previous saved measurement.
|
- For notification changes, verify that Telegram messages are sent only when `level1..level4` differ from the previous saved measurement.
|
||||||
- Verify invalid level combinations such as `0100`, `1010`, and `1101` produce the measurement error notification without causing an HTTP error.
|
- Verify invalid level combinations such as `0100`, `1010`, and `1101` produce the measurement error notification without causing an HTTP error.
|
||||||
|
|
||||||
|
|||||||
77
server/data-checker.php
Normal file
77
server/data-checker.php
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
require_once __DIR__.'/functions.inc.php';
|
||||||
|
|
||||||
|
const MIN_AGE_SECONDS = 45 * 60;
|
||||||
|
const MAX_AGE_SECONDS = (3 * 60 * 60) + (45 * 60);
|
||||||
|
|
||||||
|
function loadLastValidRecord(string $filename): ?array
|
||||||
|
{
|
||||||
|
if (!is_file($filename)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$lines = file($filename, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
|
||||||
|
if ($lines === false) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Walk backwards so a partially written or invalid last line does not break the check.
|
||||||
|
for ($i = count($lines) - 1; $i >= 0; $i--) {
|
||||||
|
$record = json_decode($lines[$i], true);
|
||||||
|
if (is_array($record) && json_last_error() === JSON_ERROR_NONE) {
|
||||||
|
return $record;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseRecordTime(array $record): ?DateTimeImmutable
|
||||||
|
{
|
||||||
|
if (!isset($record['timestamp']) || !is_string($record['timestamp'])) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return new DateTimeImmutable($record['timestamp']);
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
error_log('Invalid data timestamp: ' . $e->getMessage());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatLevels(array $record): string
|
||||||
|
{
|
||||||
|
$values = [];
|
||||||
|
foreach (['level1', 'level2', 'level3', 'level4'] as $name) {
|
||||||
|
$values[] = $name . '=' . (isset($record[$name]) ? (int) $record[$name] : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return implode(', ', $values);
|
||||||
|
}
|
||||||
|
|
||||||
|
$record = loadLastValidRecord(DATA_JSONL_FILEPATH);
|
||||||
|
if ($record === null) {
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
$recordTime = parseRecordTime($record);
|
||||||
|
if ($recordTime === null) {
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
$ageSeconds = time() - $recordTime->getTimestamp();
|
||||||
|
if ($ageSeconds <= MIN_AGE_SECONDS || $ageSeconds >= MAX_AGE_SECONDS) {
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
$message = sprintf(
|
||||||
|
'Nebola zaznamenaná nová hodnota hladiny kondenzu, posledná získaná hodnota je z %s (%s). Toto upozornenie príde len 3x, potom sa už nekontroluje.',
|
||||||
|
$recordTime->format('Y-m-d H:i:s'),
|
||||||
|
formatLevels($record)
|
||||||
|
);
|
||||||
|
|
||||||
|
notifyUsers($message);
|
||||||
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
define('DATA_JSONL_FILEPATH', __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . date('Y') . '.jsonl');
|
||||||
|
|
||||||
function respondJson(int $statusCode, array $payload): void
|
function respondJson(int $statusCode, array $payload): void
|
||||||
{
|
{
|
||||||
http_response_code($statusCode);
|
http_response_code($statusCode);
|
||||||
|
|||||||
@ -125,7 +125,7 @@ if (!is_dir($dataDir)) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$filename = $dataDir . DIRECTORY_SEPARATOR . date('Y') . '.jsonl';
|
$filename = DATA_JSONL_FILEPATH;
|
||||||
$json = json_encode($record);
|
$json = json_encode($record);
|
||||||
if ($json === false) {
|
if ($json === false) {
|
||||||
respondJson(500, [
|
respondJson(500, [
|
||||||
|
|||||||
Reference in New Issue
Block a user