Compare commits
5 Commits
09f8e5ec5a
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 4982b964a9 | |||
| 7da503ce68 | |||
| e3424352d9 | |||
| 9ac670a746 | |||
| 137651d6c3 |
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
?>
|
||||
|
||||
Reference in New Issue
Block a user