mirror of
https://github.com/usetrmnl/byos_laravel.git
synced 2026-01-13 23:18:10 +00:00
feat(#36): add Mail notification on Low Battery
This commit is contained in:
parent
1122764333
commit
2f8d989147
11 changed files with 375 additions and 0 deletions
54
app/Jobs/NotifyDeviceBatteryLowJob.php
Normal file
54
app/Jobs/NotifyDeviceBatteryLowJob.php
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs;
|
||||||
|
|
||||||
|
use App\Models\Device;
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Notifications\BatteryLow;
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
|
class NotifyDeviceBatteryLowJob implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
|
public function __construct() {}
|
||||||
|
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
$devices = Device::all();
|
||||||
|
$batteryThreshold = config('app.notifications.battery_low.warn_at_percent');
|
||||||
|
|
||||||
|
foreach ($devices as $device) {
|
||||||
|
$batteryPercent = $device->battery_percent;
|
||||||
|
|
||||||
|
// If battery is above threshold, reset the notification flag
|
||||||
|
if ($batteryPercent > $batteryThreshold && $device->battery_notification_sent) {
|
||||||
|
$device->battery_notification_sent = false;
|
||||||
|
$device->save();
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip if battery is not low or notification was already sent
|
||||||
|
if ($batteryPercent > $batteryThreshold || $device->battery_notification_sent) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var User|null $user */
|
||||||
|
$user = $device->user;
|
||||||
|
|
||||||
|
if (! $user) {
|
||||||
|
continue; // Skip if no user is associated with the device
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send notification and mark as sent
|
||||||
|
$user->notify(new BatteryLow($device));
|
||||||
|
$device->battery_notification_sent = true;
|
||||||
|
$device->save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -15,6 +15,7 @@ class Device extends Model
|
||||||
protected $guarded = ['id'];
|
protected $guarded = ['id'];
|
||||||
|
|
||||||
protected $casts = [
|
protected $casts = [
|
||||||
|
'battery_notification_sent' => 'boolean',
|
||||||
'proxy_cloud' => 'boolean',
|
'proxy_cloud' => 'boolean',
|
||||||
'last_log_request' => 'json',
|
'last_log_request' => 'json',
|
||||||
'proxy_cloud_response' => 'json',
|
'proxy_cloud_response' => 'json',
|
||||||
|
|
@ -179,4 +180,9 @@ class Device extends Model
|
||||||
{
|
{
|
||||||
return $this->hasMany(DeviceLog::class);
|
return $this->hasMany(DeviceLog::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function user(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(User::class);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -72,4 +72,9 @@ class User extends Authenticatable // implements MustVerifyEmail
|
||||||
{
|
{
|
||||||
return $this->hasMany(Plugin::class);
|
return $this->hasMany(Plugin::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function routeNotificationForWebhook(): ?string
|
||||||
|
{
|
||||||
|
return config('services.webhook.notifications.url');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
71
app/Notifications/BatteryLow.php
Normal file
71
app/Notifications/BatteryLow.php
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Notifications;
|
||||||
|
|
||||||
|
use App\Models\Device;
|
||||||
|
use App\Notifications\Channels\WebhookChannel;
|
||||||
|
use App\Notifications\Messages\WebhookMessage;
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Notifications\Messages\MailMessage;
|
||||||
|
use Illuminate\Notifications\Notification;
|
||||||
|
|
||||||
|
class BatteryLow extends Notification
|
||||||
|
{
|
||||||
|
use Queueable;
|
||||||
|
|
||||||
|
private Device $device;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new notification instance.
|
||||||
|
*/
|
||||||
|
public function __construct(Device $device)
|
||||||
|
{
|
||||||
|
$this->device = $device;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the notification's delivery channels.
|
||||||
|
*
|
||||||
|
* @return array<int, string>
|
||||||
|
*/
|
||||||
|
public function via(object $notifiable): array
|
||||||
|
{
|
||||||
|
return ['mail', WebhookChannel::class];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the mail representation of the notification.
|
||||||
|
*/
|
||||||
|
public function toMail(object $notifiable): MailMessage
|
||||||
|
{
|
||||||
|
return (new MailMessage)->markdown('mail.battery-low', ['device' => $this->device]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toWebhook(object $notifiable)
|
||||||
|
{
|
||||||
|
return WebhookMessage::create()
|
||||||
|
->data([
|
||||||
|
'topic' => config('services.webhook.notifications.topic', 'battery.low'),
|
||||||
|
'message' => "Battery below {$this->device->battery_percent}% on device: {$this->device->name}",
|
||||||
|
'device_id' => $this->device->id,
|
||||||
|
'device_name' => $this->device->name,
|
||||||
|
'battery_percent' => $this->device->battery_percent,
|
||||||
|
|
||||||
|
])
|
||||||
|
->userAgent(config('app.name'))
|
||||||
|
->header('X-TrmnlByos-Event', 'battery.low');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the array representation of the notification.
|
||||||
|
*
|
||||||
|
* @return array<string, mixed>
|
||||||
|
*/
|
||||||
|
public function toArray(object $notifiable): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'device_name' => $this->device->name,
|
||||||
|
'battery_percent' => $this->device->battery_percent,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
60
app/Notifications/Channels/WebhookChannel.php
Normal file
60
app/Notifications/Channels/WebhookChannel.php
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Notifications\Channels;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
use GuzzleHttp\Client;
|
||||||
|
use GuzzleHttp\Exception\GuzzleException;
|
||||||
|
use GuzzleHttp\Psr7\Response;
|
||||||
|
use Illuminate\Notifications\Notification;
|
||||||
|
use Illuminate\Support\Arr;
|
||||||
|
|
||||||
|
class WebhookChannel
|
||||||
|
{
|
||||||
|
/** @var Client */
|
||||||
|
protected $client;
|
||||||
|
|
||||||
|
public function __construct(Client $client)
|
||||||
|
{
|
||||||
|
$this->client = $client;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send the given notification.
|
||||||
|
*
|
||||||
|
* @param mixed $notifiable
|
||||||
|
*
|
||||||
|
* @throws Exception
|
||||||
|
* @throws GuzzleException
|
||||||
|
*/
|
||||||
|
public function send($notifiable, Notification $notification): ?Response
|
||||||
|
{
|
||||||
|
$url = $notifiable->routeNotificationFor('webhook', $notification);
|
||||||
|
|
||||||
|
if (! $url) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! method_exists($notification, 'toWebhook')) {
|
||||||
|
throw new Exception('Notification does not implement toWebhook method.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$webhookData = $notification->toWebhook($notifiable)->toArray();
|
||||||
|
$response = $this->client->post($url, [
|
||||||
|
'query' => Arr::get($webhookData, 'query'),
|
||||||
|
'body' => json_encode(Arr::get($webhookData, 'data')),
|
||||||
|
'verify' => Arr::get($webhookData, 'verify'),
|
||||||
|
'headers' => Arr::get($webhookData, 'headers'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (! $response instanceof Response) {
|
||||||
|
throw new Exception('Webhook request did not return a valid GuzzleHttp\Psr7\Response.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($response->getStatusCode() >= 300 || $response->getStatusCode() < 200) {
|
||||||
|
throw new Exception('Webhook request failed with status code: '.$response->getStatusCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
}
|
||||||
129
app/Notifications/Messages/WebhookMessage.php
Normal file
129
app/Notifications/Messages/WebhookMessage.php
Normal file
|
|
@ -0,0 +1,129 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Notifications\Messages;
|
||||||
|
|
||||||
|
final class WebhookMessage
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The GET parameters of the request.
|
||||||
|
*
|
||||||
|
* @var array|string|null
|
||||||
|
*/
|
||||||
|
private $query;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The POST data of the Webhook request.
|
||||||
|
*
|
||||||
|
* @var mixed
|
||||||
|
*/
|
||||||
|
private $data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The headers to send with the request.
|
||||||
|
*
|
||||||
|
* @var array|null
|
||||||
|
*/
|
||||||
|
private $headers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Guzzle verify option.
|
||||||
|
*
|
||||||
|
* @var bool|string
|
||||||
|
*/
|
||||||
|
private $verify = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param mixed $data
|
||||||
|
* @return static
|
||||||
|
*/
|
||||||
|
public static function create($data = '')
|
||||||
|
{
|
||||||
|
return new self($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param mixed $data
|
||||||
|
*/
|
||||||
|
public function __construct($data = '')
|
||||||
|
{
|
||||||
|
$this->data = $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the Webhook parameters to be URL encoded.
|
||||||
|
*
|
||||||
|
* @param mixed $query
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function query($query)
|
||||||
|
{
|
||||||
|
$this->query = $query;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the Webhook data to be JSON encoded.
|
||||||
|
*
|
||||||
|
* @param mixed $data
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function data($data)
|
||||||
|
{
|
||||||
|
$this->data = $data;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a Webhook request custom header.
|
||||||
|
*
|
||||||
|
* @param string $name
|
||||||
|
* @param string $value
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function header($name, $value)
|
||||||
|
{
|
||||||
|
$this->headers[$name] = $value;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the Webhook request UserAgent.
|
||||||
|
*
|
||||||
|
* @param string $userAgent
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function userAgent($userAgent)
|
||||||
|
{
|
||||||
|
$this->headers['User-Agent'] = $userAgent;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicate that the request should be verified.
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function verify($value = true)
|
||||||
|
{
|
||||||
|
$this->verify = $value;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function toArray()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'query' => $this->query,
|
||||||
|
'data' => $this->data,
|
||||||
|
'headers' => $this->headers,
|
||||||
|
'verify' => $this->verify,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -131,6 +131,12 @@ return [
|
||||||
'puppeteer_docker' => env('PUPPETEER_DOCKER', false),
|
'puppeteer_docker' => env('PUPPETEER_DOCKER', false),
|
||||||
'puppeteer_mode' => env('PUPPETEER_MODE', 'local'),
|
'puppeteer_mode' => env('PUPPETEER_MODE', 'local'),
|
||||||
|
|
||||||
|
'notifications' => [
|
||||||
|
'battery_low' => [
|
||||||
|
'warn_at_percent' => env('NOTIFICATION_BATTERYLOW_WARNATPERCENT', 20),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
| Application Version
|
| Application Version
|
||||||
|
|
|
||||||
|
|
@ -43,4 +43,11 @@ return [
|
||||||
'image_url_timeout' => env('TRMNL_IMAGE_URL_TIMEOUT', 30), // 30 seconds; increase on low-powered devices
|
'image_url_timeout' => env('TRMNL_IMAGE_URL_TIMEOUT', 30), // 30 seconds; increase on low-powered devices
|
||||||
],
|
],
|
||||||
|
|
||||||
|
'webhook' => [
|
||||||
|
'notifications' => [
|
||||||
|
'url' => env('WEBHOOK_NOTIFICATION_URL', null),
|
||||||
|
'topic' => env('WEBHOOK_NOTIFICATION_TOPIC', 'null'),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('devices', function (Blueprint $table) {
|
||||||
|
$table->boolean('battery_notification_sent')->default(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('devices', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('battery_notification_sent');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
7
resources/views/mail/battery-low.blade.php
Normal file
7
resources/views/mail/battery-low.blade.php
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
<x-mail::message>
|
||||||
|
# Battery Low
|
||||||
|
|
||||||
|
The battery of {{ $device->name }} is running below {{ $device->battery_percent }}%. Please charge your device soon.
|
||||||
|
|
||||||
|
{{ config('app.name') }}
|
||||||
|
</x-mail::message>
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
use App\Jobs\CleanupDeviceLogsJob;
|
use App\Jobs\CleanupDeviceLogsJob;
|
||||||
use App\Jobs\FetchProxyCloudResponses;
|
use App\Jobs\FetchProxyCloudResponses;
|
||||||
use App\Jobs\FirmwarePollJob;
|
use App\Jobs\FirmwarePollJob;
|
||||||
|
use App\Jobs\NotifyDeviceBatteryLowJob;
|
||||||
use Illuminate\Support\Facades\Schedule;
|
use Illuminate\Support\Facades\Schedule;
|
||||||
|
|
||||||
Schedule::job(FetchProxyCloudResponses::class, [])->cron(
|
Schedule::job(FetchProxyCloudResponses::class, [])->cron(
|
||||||
|
|
@ -12,3 +13,4 @@ Schedule::job(FetchProxyCloudResponses::class, [])->cron(
|
||||||
|
|
||||||
Schedule::job(FirmwarePollJob::class)->daily();
|
Schedule::job(FirmwarePollJob::class)->daily();
|
||||||
Schedule::job(CleanupDeviceLogsJob::class)->daily();
|
Schedule::job(CleanupDeviceLogsJob::class)->daily();
|
||||||
|
Schedule::job(NotifyDeviceBatteryLowJob::class)->dailyAt('10:00');
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue