Compare commits

..

5 Commits

Author SHA1 Message Date
Aaska Black Wolf 4982b964a9 Added check for changes in parameters by checksum calculation to reduce write cycles to disk 2026-06-03 21:44:54 +02:00
Aaska Black Wolf 7da503ce68 completed monitor queries and added option for test messages 2026-06-03 20:14:56 +02:00
Aaska Black Wolf e3424352d9 added information regarding test messages to README.md 2026-06-03 20:13:51 +02:00
Aaska Black Wolf 9ac670a746 Merge branch 'main' of https://git.schauaus.at/VE-Tools/VE.Direct-notify 2026-06-03 13:28:03 +02:00
Aaska Black Wolf 137651d6c3 initial README.md 2026-06-03 13:23:59 +02:00
2 changed files with 241 additions and 28 deletions
+135 -1
View File
@@ -1,3 +1,137 @@
# VE.Direct-notify
Customisable notification service using unified push providers like ntfy
Welcome to VE.Direct-notfiy. A customisable notification service using unified push providers like ntfy.
A PHP installation with the SQLite3-module activated and a network connection to your preferred notification service is needed to run the program. It is developed, tested and verified with PHP version 8.5.
## Configuration
VE.Direct-notify needs some configuration settings to work properly. These are collected in the JSON-formatted file config.json in VE.Direct-notify's root directory.
The file is structured by directives controlling the various aspects of the programm:
### service
In this directive you define the notification service to use. VE.Direct-notify is tested and proofed working with the OpenSource notification service ntfy.
The topic URL to use is derived from the parameters url and topic combined. the parameter url is to be given **without a trailing slash**.
VE.Direct-notify supports conneting to notification services with and without authentication. Authentication can be by Username/Password or Authentication Token.
example without authentication:
```
"service": {
"url": "https://ntfy.yourdomain.com",
"topic": "yourTopicName"
}
```
example with authentication using a username and password:
```
"service": {
"url": "https://ntfy.yourdomain.com",
"username": "yourUserName",
"password": "yourComplexPassword",
"topic": "yourTopicName"
}
```
example with authentication using an authentication token:
```
"service": {
"url": "https://ntfy.yourdomain.com",
"auth_token": "tk_AbCdEfGhIjKlMnOpQrStUvWxYz0123456789",
"topic": "yourTopicName"
}
```
### database
In this directive the path to the SQLite-database to be used is defined. Usually, that will be the same as your VE.Direct-Text-Reader instance is using.
The path can be absolute or relative to the VE.Direct-notify root directory.
example:
`"database": "/path/to/your/VEDirect.db"`
### monitors
The monitors represent the actual parameters to be checked by VE.Direct-notfy as well as the corresponding test conditions to each parameter.
There can be an unlimitted number of parameters to monitor defined. All monitors are collected in the directive "monitors" as an array of objects containing the actual parameter(s) to monitor and the corresponding test conditions.
example:
```
"monitors": [
{
"parameter": "ERR",
"error": ["gt", 0]
}
]
```
#### test conditions
Test conditions can be defined as warnings or errors or both as object elements with an array containing 2 elements. The first one defines how the value shall be interpreted (see table below), the second is the value to test against.
example: `"error": ["gt", 0]`
| value | Description |
| ----: | :----------------------------------- |
| e | equal to given value |
| gt | greater than given value |
| gte | greater than or equal to given value |
| lt | lower than given value |
| lte | lower than or equal to given value |
| ne | not equal to given valeu |
table of test conditions
### example config.json
```
{
"service": {
"url": "https://ntfy.example.com",
"username": "",
"password": "",
"auth_token": "tk_AbCdEfGhIjKlMnOpQrStUvWxYz0123456789",
"topic": "MakeThisRandomOrComplexWithoutAuthentication"
},
"monitors": [
{
"parameter": "ERR",
"error": ["gt", 0]
},
{
"parameter": "V",
"warning": ["lt", 11.5],
"error": ["lte", 10.5]
}
],
"database": "/path/to/your/VE.Direct.db"
}
```
## Run the program
Once the configuration is set up properly, the programm can be run by:
```
$ cd /path/to/your/VE.Direct-ntfy-instance
$ php worker.php
```
Once tested successfully, it can be set up as a cron-job:
`$ crontab -e`
In the cron table fill in a line like this:
`0 * * * * php /path/to/your/VE.Direct-ntfy-instance/worker.php`
### send test message to notification server
VE.Direct-notify is able to send test messages to the notification serivce to check whether it can use the configured service properly.
To send a test message the following command can be used on the command line:
```
$ cd /path/to/your/VE.Direct-ntfy-instance
$ php worker.php test <notificationType>
```
<notificationType> shall be replaced by either warning or error.
## Troubleshooting
## Contributing
VE.Direct-notify is an OpenSource project and we greatly appreciate any kind of support.
For more details check CONTRIBUTING.md
## More VE.Direct tools
All our tools to interact with devices equipped with a VE Direct protocol compatible communictaion port can be found on our [git-server](https://git.schauaus.at/VE-Tools).
+106 -27
View File
@@ -8,38 +8,52 @@
* You should have received a copy of the license with this software
*/
// read configuration
$config = readConfig();
if( !isset($config) )
exit("Error reading configuration! Exit!\r\n");
// check whether test message shall be delivered to notification service
if( $argc >= 3 && $argv[1] == "test" && isset($argv[2]) ) {
sendMessage($argv[2] . ": Testmessage");
exit();
}
queryMonitors();
function readConfig() {
return json_decode( file_get_contents('config.json') );
}
function sendMessage($message) {
global $config;
$authorization = "";
if( defined($config->service->username) && defined($config->service->password) )
$authorization = 'Basic ' + base64_encode($config->service->username + ':' + $config->service->password);
elseif( defined($config->service->token) )
$authorization = 'Bearer ' + $config->service->token;
$result = file_get_contents( $config->service->url + '/' + $config->service->topic, false, stream_context_create([
'http' => [
'method' => 'POST',
'header' =>
'Content-Type: text/plain\r\n' .
'Authorization: ' + $authorization,
'content' => $message
]
]));
return true;
function checkCondition($value, $condition) {
switch($condition[0]) {
case "e": // equal
if($value == $condition[1])
return ' equals ' . $condition[1];
break;
case "gt": // greater than
if( floatval($value) > floatval($condition[1]) )
return ' = ' . $value . ' and greater than ' . $condition[1];
break;
case "gte": // greater than or equal
if( floatval($value) >= floatval($condition[1]) )
return ' = ' . $value . ' and greater than or equal ' . $condition[1];
break;
case "lt": // lower than
if( floatval($value) < floatval($condition[1]) )
return ' = ' . $value . ' and lower than ' . $condition[1];
break;
case "lte": // lower than or equal
if( floatval($value) <= floatval($condition[1]) )
return ' = ' . $value . ' and lower than or equal ' . $condition[1];
break;
case "ne": // not equal
if($value != $condition[1])
return ' = ' . $value . ' and not equal to ' . $condition[1];
break;
}
return false;
}
function queryMonitors() {
global $config;
// establish database connection
$dbPath = (str_starts_with($config->database, '/')) ? $config->database : dirname(__FILE__) . '/' . $config->database;
try {
@@ -49,9 +63,10 @@ function queryMonitors() {
print ('error detected: ' . $e->getMessage() . "\r\n");
return false;
}
// query devices from database
$sql = "SELECT name FROM sqlite_schema WHERE name NOT LIKE 'sqlite_%' AND name <> 'meta' ORDER BY name ASC;";
$data = $db->query($sql);
$sql = "SELECT name FROM sqlite_schema WHERE name NOT LIKE 'sqlite_%' AND name <> 'meta' ORDER BY name ASC;";
$data = $db->query($sql);
// process every device found on database
while( $device = $data->fetchArray(SQLITE3_ASSOC) ) {
@@ -59,12 +74,76 @@ function queryMonitors() {
$sql = "SELECT * FROM '" . $device['name'] . "' ORDER BY date DESC, time DESC LIMIT 1;";
$res = $db->query($sql)->fetchArray(SQLITE3_NUM);
$dataset = json_decode($res[2]);
print_r($dataset);
// read last status of warnings and errors for comparison to avoid double sending of notifications
$statusFile = __DIR__ . '/status/' . $device['name'];
$lastStatus = [];
if( is_file($statusFile) )
$lastStatus = json_decode( file_get_contents( $statusFile ), true );
$checksum = hash( 'sha256', json_encode($lastStatus) );
// process configured monitors
foreach($config->monitors as $monitor) {
if( !empty($monitor->warning) ) {
if( !isset( $lastStatus[$monitor->parameter]['warning'] ) )
$lastStatus[$monitor->parameter]['warning'] = 0;
$status = checkCondition($dataset->{$monitor->parameter}, $monitor->warning);
if( $status && $lastStatus[$monitor->parameter]['warning'] == 0 ) { // new warning
sendMessage('Warning from ' . $device['name'] . ': ' . $monitor->parameter . $status);
$lastStatus[$monitor->parameter]['warning'] = 1;
}
else if( !$status && $lastStatus[$monitor->parameter]['warning'] == 1) { // warning cleared
sendMessage( 'Warning cleared on ' . $device['name'] . '. ' . $monitor->parameter . ' = ' . $dataset->{$monitor->parameter} . ' and back to normal.');
$lastStatus[$monitor->parameter]['warning'] = 0;
}
}
if( !empty($monitor->error) ) {
if( !isset( $lastStatus[$monitor->parameter]['error'] ) )
$lastStatus[$monitor->parameter]['error'] = 0;
$status = checkCondition($dataset->{$monitor->parameter}, $monitor->error);
if( $status && $lastStatus[$monitor->parameter]['error'] == 0 ) { // new error!
sendMessage('Error from ' . $device['name'] . ': ' . $monitor->parameter . $status);
$lastStatus[$monitor->parameter]['error'] = 1;
}
else if( !$status && $lastStatus[$monitor->parameter]['error'] == 1 ) { // error cleared
sendMessage('Error cleared on ' . $device['name'] . '. ' . $monitor->parameter . ' = ' . $dataset->{$monitor->parameter} . ' and out of critical range.');
$lastStatus[$monitor->parameter]['error'] = 0;
}
}
}
if( hash( 'sha256', json_encode($lastStatus) ) !== $checksum)
file_put_contents( $statusFile, json_encode($lastStatus) );
}
}
function readConfig() {
return json_decode( file_get_contents(__DIR__ . '/config.json') );
}
function sendMessage($message) {
global $config;
$authorization = "Authorization: ";
if( !empty($config->service->username) && !empty($config->service->password) )
$authorization .= 'Basic ' . base64_encode($config->service->username . ':' . $config->service->password);
elseif( !empty($config->service->auth_token) )
$authorization .= 'Bearer ' . $config->service->auth_token;
else
$authorization = '';
$result = file_get_contents( $config->service->url . '/' . $config->service->topic, false, stream_context_create([
'http' => [
'method' => 'POST',
'header' =>
"Content-Type: text/plain\r\n" .
$authorization,
'content' => $message
]
]));
return true;
}
?>