Navigation

Laravel

Laravel for IoT Applications: Building Device APIs

Build scalable IoT applications with Laravel. Master device authentication, telemetry processing, real-time communication, and monitoring systems for connected device management platforms.

Create secure, scalable IoT device management systems with Laravel, handling sensor data, real-time communication, and device lifecycle management for connected applications.

Table Of Contents

Understanding IoT Architecture with Laravel

Internet of Things (IoT) applications require robust backend systems that can handle massive volumes of sensor data, manage device states, and provide real-time communication channels. Laravel's event-driven architecture, combined with its powerful queue system and broadcasting capabilities, makes it an excellent choice for IoT backends.

Building IoT systems involves managing device authentication, handling time-series data efficiently, implementing command-and-control systems, and ensuring reliable communication protocols. This becomes crucial when developing scalable Laravel applications that need to process thousands of concurrent device connections.

Device Management and Authentication

IoT Device Model and Authentication

Create comprehensive device management with secure authentication:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Support\Str;

class IoTDevice extends Model
{
    protected $table = 'iot_devices';
    
    protected $fillable = [
        'device_id',
        'name',
        'type',
        'model',
        'firmware_version',
        'user_id',
        'location_data',
        'configuration',
        'status',
        'last_seen_at',
        'api_key_hash',
        'certificate_thumbprint',
    ];
    
    protected $casts = [
        'location_data' => 'array',
        'configuration' => 'array',
        'last_seen_at' => 'datetime',
        'is_active' => 'boolean',
    ];
    
    protected $hidden = [
        'api_key_hash',
        'certificate_thumbprint',
    ];
    
    const STATUS_ONLINE = 'online';
    const STATUS_OFFLINE = 'offline';
    const STATUS_MAINTENANCE = 'maintenance';
    const STATUS_ERROR = 'error';
    
    public static function boot()
    {
        parent::boot();
        
        static::creating(function ($device) {
            if (empty($device->device_id)) {
                $device->device_id = 'DEV_' . Str::upper(Str::random(12));
            }
            
            if (empty($device->api_key_hash)) {
                $apiKey = Str::random(64);
                $device->api_key_hash = hash('sha256', $apiKey);
                
                // Store unhashed key temporarily for initial setup
                $device->setAttribute('temp_api_key', $apiKey);
            }
        });
    }
    
    public function owner(): BelongsTo
    {
        return $this->belongsTo(User::class, 'user_id');
    }
    
    public function telemetryData(): HasMany
    {
        return $this->hasMany(DeviceTelemetry::class, 'device_id', 'device_id');
    }
    
    public function commands(): HasMany
    {
        return $this->hasMany(DeviceCommand::class, 'device_id', 'device_id');
    }
    
    public function alerts(): HasMany
    {
        return $this->hasMany(DeviceAlert::class, 'device_id', 'device_id');
    }
    
    public function maintenanceLogs(): HasMany
    {
        return $this->hasMany(DeviceMaintenanceLog::class, 'device_id', 'device_id');
    }
    
    public function scopeOnline($query)
    {
        return $query->where('status', self::STATUS_ONLINE)
                    ->where('last_seen_at', '>=', now()->subMinutes(5));
    }
    
    public function scopeByType($query, string $type)
    {
        return $query->where('type', $type);
    }
    
    public function scopeByLocation($query, float $lat, float $lng, float $radius = 10)
    {
        return $query->whereRaw(
            'ST_Distance_Sphere(
                POINT(JSON_EXTRACT(location_data, "$.longitude"), JSON_EXTRACT(location_data, "$.latitude")),
                POINT(?, ?)
            ) <= ?',
            [$lng, $lat, $radius * 1000] // Convert km to meters
        );
    }
    
    public function updateLastSeen(): void
    {
        $this->update([
            'last_seen_at' => now(),
            'status' => self::STATUS_ONLINE,
        ]);
    }
    
    public function isOnline(): bool
    {
        return $this->status === self::STATUS_ONLINE &&
               $this->last_seen_at &&
               $this->last_seen_at->diffInMinutes(now()) <= 5;
    }
    
    public function generateNewApiKey(): string
    {
        $apiKey = Str::random(64);
        $this->update(['api_key_hash' => hash('sha256', $apiKey)]);
        
        return $apiKey;
    }
    
    public function verifyApiKey(string $apiKey): bool
    {
        return hash_equals($this->api_key_hash, hash('sha256', $apiKey));
    }
    
    public function getCapabilities(): array
    {
        $capabilities = $this->configuration['capabilities'] ?? [];
        
        return array_filter([
            'telemetry' => $capabilities['telemetry'] ?? true,
            'commands' => $capabilities['commands'] ?? false,
            'firmware_update' => $capabilities['firmware_update'] ?? false,
            'configuration_sync' => $capabilities['configuration_sync'] ?? false,
        ]);
    }
}

Device Authentication Middleware

Implement secure device authentication:

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use App\Models\IoTDevice;
use Symfony\Component\HttpFoundation\Response;

class AuthenticateIoTDevice
{
    public function handle(Request $request, Closure $next): Response
    {
        $deviceId = $request->header('X-Device-ID');
        $apiKey = $request->header('X-API-Key');
        $signature = $request->header('X-Signature');
        
        if (!$deviceId || !$apiKey) {
            return response()->json([
                'error' => 'Missing device credentials',
                'code' => 'DEVICE_AUTH_REQUIRED'
            ], 401);
        }
        
        $device = IoTDevice::where('device_id', $deviceId)->first();
        
        if (!$device) {
            return response()->json([
                'error' => 'Device not found',
                'code' => 'DEVICE_NOT_FOUND'
            ], 404);
        }
        
        if (!$device->verifyApiKey($apiKey)) {
            return response()->json([
                'error' => 'Invalid API key',
                'code' => 'INVALID_API_KEY'
            ], 401);
        }
        
        // Verify signature for sensitive operations
        if ($signature && !$this->verifySignature($request, $device, $signature)) {
            return response()->json([
                'error' => 'Invalid signature',
                'code' => 'INVALID_SIGNATURE'
            ], 401);
        }
        
        // Update device last seen
        $device->updateLastSeen();
        
        // Add device to request for use in controllers
        $request->attributes->set('iot_device', $device);
        
        return $next($request);
    }
    
    protected function verifySignature(Request $request, IoTDevice $device, string $signature): bool
    {
        $payload = $request->getContent();
        $timestamp = $request->header('X-Timestamp');
        
        if (!$timestamp || abs(time() - $timestamp) > 300) { // 5 minutes tolerance
            return false;
        }
        
        $expectedSignature = hash_hmac(
            'sha256',
            $timestamp . '.' . $payload,
            $device->api_key_hash
        );
        
        return hash_equals($signature, $expectedSignature);
    }
}

Telemetry Data Management

High-Performance Telemetry Storage

Handle massive volumes of sensor data efficiently:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

class DeviceTelemetry extends Model
{
    protected $table = 'device_telemetry';
    
    protected $fillable = [
        'device_id',
        'sensor_type',
        'measurement_type',
        'value',
        'unit',
        'quality',
        'metadata',
        'recorded_at',
    ];
    
    protected $casts = [
        'value' => 'float',
        'quality' => 'float',
        'metadata' => 'array',
        'recorded_at' => 'datetime',
    ];
    
    public $timestamps = false; // Use recorded_at instead
    
    public function device(): BelongsTo
    {
        return $this->belongsTo(IoTDevice::class, 'device_id', 'device_id');
    }
    
    public function scopeForPeriod($query, \DateTime $start, \DateTime $end)
    {
        return $query->whereBetween('recorded_at', [$start, $end]);
    }
    
    public function scopeBySensorType($query, string $sensorType)
    {
        return $query->where('sensor_type', $sensorType);
    }
    
    public function scopeByMeasurementType($query, string $measurementType)
    {
        return $query->where('measurement_type', $measurementType);
    }
    
    public function scopeWithGoodQuality($query, float $minQuality = 0.8)
    {
        return $query->where('quality', '>=', $minQuality);
    }
}

// Service for efficient telemetry processing
namespace App\Services;

use App\Models\DeviceTelemetry;
use App\Models\IoTDevice;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Redis;
use Carbon\Carbon;

class TelemetryService
{
    protected array $batchBuffer = [];
    protected int $batchSize = 1000;
    
    public function storeTelemetryData(string $deviceId, array $telemetryData): array
    {
        $device = IoTDevice::where('device_id', $deviceId)->first();
        
        if (!$device) {
            throw new \InvalidArgumentException("Device not found: {$deviceId}");
        }
        
        $processedCount = 0;
        $errors = [];
        
        foreach ($telemetryData as $reading) {
            try {
                $this->validateTelemetryReading($reading);
                $this->addToBatch($deviceId, $reading);
                $processedCount++;
                
            } catch (\Exception $e) {
                $errors[] = [
                    'reading' => $reading,
                    'error' => $e->getMessage(),
                ];
            }
        }
        
        $this->flushBatch();
        
        // Update device aggregates in background
        \Illuminate\Support\Facades\Queue::push(
            new \App\Jobs\UpdateDeviceAggregatesJob($deviceId)
        );
        
        return [
            'processed' => $processedCount,
            'errors' => $errors,
            'device_status' => $device->status,
        ];
    }
    
    protected function validateTelemetryReading(array $reading): void
    {
        $required = ['sensor_type', 'measurement_type', 'value', 'recorded_at'];
        
        foreach ($required as $field) {
            if (!isset($reading[$field])) {
                throw new \InvalidArgumentException("Missing required field: {$field}");
            }
        }
        
        if (!is_numeric($reading['value'])) {
            throw new \InvalidArgumentException("Value must be numeric");
        }
        
        // Validate timestamp
        try {
            Carbon::parse($reading['recorded_at']);
        } catch (\Exception $e) {
            throw new \InvalidArgumentException("Invalid timestamp format");
        }
    }
    
    protected function addToBatch(string $deviceId, array $reading): void
    {
        $this->batchBuffer[] = [
            'device_id' => $deviceId,
            'sensor_type' => $reading['sensor_type'],
            'measurement_type' => $reading['measurement_type'],
            'value' => (float) $reading['value'],
            'unit' => $reading['unit'] ?? null,
            'quality' => $reading['quality'] ?? 1.0,
            'metadata' => $reading['metadata'] ?? null,
            'recorded_at' => Carbon::parse($reading['recorded_at']),
        ];
        
        if (count($this->batchBuffer) >= $this->batchSize) {
            $this->flushBatch();
        }
    }
    
    protected function flushBatch(): void
    {
        if (empty($this->batchBuffer)) {
            return;
        }
        
        DB::table('device_telemetry')->insert($this->batchBuffer);
        
        // Cache recent readings for real-time access
        $this->cacheRecentReadings($this->batchBuffer);
        
        $this->batchBuffer = [];
    }
    
    protected function cacheRecentReadings(array $readings): void
    {
        foreach ($readings as $reading) {
            $key = "telemetry:{$reading['device_id']}:{$reading['sensor_type']}:latest";
            
            Redis::setex($key, 3600, json_encode([
                'value' => $reading['value'],
                'unit' => $reading['unit'],
                'recorded_at' => $reading['recorded_at']->toISOString(),
            ]));
        }
    }
    
    public function getLatestReadings(string $deviceId, ?string $sensorType = null): array
    {
        $pattern = $sensorType 
            ? "telemetry:{$deviceId}:{$sensorType}:latest"
            : "telemetry:{$deviceId}:*:latest";
        
        $keys = Redis::keys($pattern);
        $readings = [];
        
        foreach ($keys as $key) {
            $data = Redis::get($key);
            if ($data) {
                $sensorTypeFromKey = explode(':', $key)[2];
                $readings[$sensorTypeFromKey] = json_decode($data, true);
            }
        }
        
        return $readings;
    }
    
    public function getAggregatedData(
        string $deviceId,
        string $sensorType,
        string $measurementType,
        \DateTime $start,
        \DateTime $end,
        string $interval = '1 hour'
    ): array {
        $sql = "
            SELECT 
                DATE_FORMAT(recorded_at, '%Y-%m-%d %H:00:00') as time_bucket,
                AVG(value) as avg_value,
                MIN(value) as min_value,
                MAX(value) as max_value,
                COUNT(*) as sample_count,
                AVG(quality) as avg_quality
            FROM device_telemetry 
            WHERE device_id = ? 
                AND sensor_type = ?
                AND measurement_type = ?
                AND recorded_at BETWEEN ? AND ?
                AND quality >= 0.5
            GROUP BY DATE_FORMAT(recorded_at, '%Y-%m-%d %H:00:00')
            ORDER BY time_bucket
        ";
        
        return collect(DB::select($sql, [
            $deviceId,
            $sensorType,
            $measurementType,
            $start->format('Y-m-d H:i:s'),
            $end->format('Y-m-d H:i:s'),
        ]))->map(function ($row) {
            return [
                'timestamp' => Carbon::parse($row->time_bucket),
                'average' => round($row->avg_value, 2),
                'minimum' => round($row->min_value, 2),
                'maximum' => round($row->max_value, 2),
                'samples' => $row->sample_count,
                'quality' => round($row->avg_quality, 2),
            ];
        })->toArray();
    }
}

Device Command and Control

Command Management System

Implement secure device command and control:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

class DeviceCommand extends Model
{
    protected $fillable = [
        'device_id',
        'command_type',
        'command',
        'parameters',
        'priority',
        'scheduled_at',
        'expires_at',
        'status',
        'response',
        'executed_at',
        'retry_count',
    ];
    
    protected $casts = [
        'parameters' => 'array',
        'response' => 'array',
        'scheduled_at' => 'datetime',
        'expires_at' => 'datetime',
        'executed_at' => 'datetime',
    ];
    
    const STATUS_PENDING = 'pending';
    const STATUS_SENT = 'sent';
    const STATUS_ACKNOWLEDGED = 'acknowledged';
    const STATUS_EXECUTED = 'executed';
    const STATUS_FAILED = 'failed';
    const STATUS_EXPIRED = 'expired';
    
    const PRIORITY_LOW = 1;
    const PRIORITY_NORMAL = 5;
    const PRIORITY_HIGH = 8;
    const PRIORITY_CRITICAL = 10;
    
    public function device(): BelongsTo
    {
        return $this->belongsTo(IoTDevice::class, 'device_id', 'device_id');
    }
    
    public function scopePending($query)
    {
        return $query->where('status', self::STATUS_PENDING)
                    ->where(function ($q) {
                        $q->whereNull('expires_at')
                          ->orWhere('expires_at', '>', now());
                    });
    }
    
    public function scopeByPriority($query, int $minPriority = self::PRIORITY_NORMAL)
    {
        return $query->where('priority', '>=', $minPriority);
    }
    
    public function isExpired(): bool
    {
        return $this->expires_at && $this->expires_at->isPast();
    }
    
    public function canRetry(): bool
    {
        return $this->retry_count < 3 && !$this->isExpired();
    }
    
    public function markAsSent(): void
    {
        $this->update(['status' => self::STATUS_SENT]);
    }
    
    public function markAsExecuted(array $response = null): void
    {
        $this->update([
            'status' => self::STATUS_EXECUTED,
            'response' => $response,
            'executed_at' => now(),
        ]);
    }
    
    public function markAsFailed(string $reason): void
    {
        $this->update([
            'status' => self::STATUS_FAILED,
            'response' => ['error' => $reason],
            'retry_count' => $this->retry_count + 1,
        ]);
    }
}

// Command Service
namespace App\Services;

use App\Models\DeviceCommand;
use App\Models\IoTDevice;
use App\Events\DeviceCommandSent;
use App\Events\DeviceCommandExecuted;
use Illuminate\Support\Facades\Queue;

class DeviceCommandService
{
    public function sendCommand(
        string $deviceId,
        string $commandType,
        string $command,
        array $parameters = [],
        int $priority = DeviceCommand::PRIORITY_NORMAL,
        ?\DateTime $scheduledAt = null,
        ?\DateTime $expiresAt = null
    ): DeviceCommand {
        $device = IoTDevice::where('device_id', $deviceId)->first();
        
        if (!$device) {
            throw new \InvalidArgumentException("Device not found: {$deviceId}");
        }
        
        if (!$device->isOnline() && $priority !== DeviceCommand::PRIORITY_CRITICAL) {
            throw new \RuntimeException("Device is offline: {$deviceId}");
        }
        
        $this->validateCommand($commandType, $command, $parameters);
        
        $deviceCommand = DeviceCommand::create([
            'device_id' => $deviceId,
            'command_type' => $commandType,
            'command' => $command,
            'parameters' => $parameters,
            'priority' => $priority,
            'scheduled_at' => $scheduledAt ?? now(),
            'expires_at' => $expiresAt ?? now()->addHours(24),
            'status' => DeviceCommand::STATUS_PENDING,
        ]);
        
        // Queue command for immediate or scheduled delivery
        if ($scheduledAt && $scheduledAt->isFuture()) {
            Queue::later($scheduledAt, new \App\Jobs\SendDeviceCommandJob($deviceCommand));
        } else {
            Queue::push(new \App\Jobs\SendDeviceCommandJob($deviceCommand));
        }
        
        return $deviceCommand;
    }
    
    protected function validateCommand(string $commandType, string $command, array $parameters): void
    {
        $allowedCommands = [
            'configuration' => ['update_config', 'reset_config', 'sync_config'],
            'firmware' => ['update_firmware', 'rollback_firmware'],
            'control' => ['restart', 'shutdown', 'set_mode'],
            'diagnostic' => ['run_test', 'get_logs', 'health_check'],
        ];
        
        if (!isset($allowedCommands[$commandType])) {
            throw new \InvalidArgumentException("Invalid command type: {$commandType}");
        }
        
        if (!in_array($command, $allowedCommands[$commandType])) {
            throw new \InvalidArgumentException("Invalid command: {$command} for type: {$commandType}");
        }
        
        // Validate parameters based on command
        $this->validateCommandParameters($commandType, $command, $parameters);
    }
    
    protected function validateCommandParameters(string $commandType, string $command, array $parameters): void
    {
        switch ("{$commandType}.{$command}") {
            case 'configuration.update_config':
                if (empty($parameters['config_data'])) {
                    throw new \InvalidArgumentException("Missing config_data parameter");
                }
                break;
                
            case 'firmware.update_firmware':
                if (empty($parameters['firmware_url']) || empty($parameters['version'])) {
                    throw new \InvalidArgumentException("Missing firmware_url or version parameter");
                }
                break;
                
            case 'control.set_mode':
                if (empty($parameters['mode'])) {
                    throw new \InvalidArgumentException("Missing mode parameter");
                }
                break;
        }
    }
    
    public function getPendingCommands(string $deviceId): array
    {
        return DeviceCommand::where('device_id', $deviceId)
                          ->pending()
                          ->orderBy('priority', 'desc')
                          ->orderBy('scheduled_at')
                          ->get()
                          ->toArray();
    }
    
    public function acknowledgeCommand(int $commandId, array $response = []): void
    {
        $command = DeviceCommand::findOrFail($commandId);
        
        $command->update([
            'status' => DeviceCommand::STATUS_ACKNOWLEDGED,
            'response' => array_merge($command->response ?? [], $response),
        ]);
        
        event(new DeviceCommandSent($command));
    }
    
    public function executeCommand(int $commandId, array $response): void
    {
        $command = DeviceCommand::findOrFail($commandId);
        
        $command->markAsExecuted($response);
        
        event(new DeviceCommandExecuted($command));
    }
}

Real-time Communication

WebSocket Integration for Real-time Updates

Implement real-time device communication:

<?php

namespace App\Http\Controllers\Api\IoT;

use App\Http\Controllers\Controller;
use App\Services\TelemetryService;
use App\Services\DeviceCommandService;
use App\Events\DeviceTelemetryReceived;
use App\Events\DeviceStatusChanged;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;

class DeviceCommunicationController extends Controller
{
    protected TelemetryService $telemetryService;
    protected DeviceCommandService $commandService;
    
    public function __construct(
        TelemetryService $telemetryService,
        DeviceCommandService $commandService
    ) {
        $this->middleware('auth.iot.device');
        $this->telemetryService = $telemetryService;
        $this->commandService = $commandService;
    }
    
    public function sendTelemetry(Request $request): JsonResponse
    {
        $device = $request->attributes->get('iot_device');
        
        $request->validate([
            'telemetry' => 'required|array',
            'telemetry.*.sensor_type' => 'required|string',
            'telemetry.*.measurement_type' => 'required|string',
            'telemetry.*.value' => 'required|numeric',
            'telemetry.*.recorded_at' => 'required|date',
            'telemetry.*.unit' => 'nullable|string',
            'telemetry.*.quality' => 'nullable|numeric|between:0,1',
            'telemetry.*.metadata' => 'nullable|array',
        ]);
        
        try {
            $result = $this->telemetryService->storeTelemetryData(
                $device->device_id,
                $request->input('telemetry')
            );
            
            // Broadcast telemetry to subscribers
            event(new DeviceTelemetryReceived($device, $request->input('telemetry')));
            
            return response()->json([
                'status' => 'success',
                'processed' => $result['processed'],
                'errors' => $result['errors'],
                'next_poll' => now()->addMinutes(1)->toISOString(),
            ]);
            
        } catch (\Exception $e) {
            return response()->json([
                'status' => 'error',
                'message' => $e->getMessage(),
            ], 400);
        }
    }
    
    public function pollCommands(Request $request): JsonResponse
    {
        $device = $request->attributes->get('iot_device');
        
        $commands = $this->commandService->getPendingCommands($device->device_id);
        
        return response()->json([
            'commands' => $commands,
            'count' => count($commands),
            'poll_interval' => $this->calculatePollInterval($device),
        ]);
    }
    
    public function acknowledgeCommand(Request $request, int $commandId): JsonResponse
    {
        try {
            $this->commandService->acknowledgeCommand(
                $commandId,
                $request->input('response', [])
            );
            
            return response()->json(['status' => 'acknowledged']);
            
        } catch (\Exception $e) {
            return response()->json([
                'status' => 'error',
                'message' => $e->getMessage(),
            ], 400);
        }
    }
    
    public function reportCommandExecution(Request $request, int $commandId): JsonResponse
    {
        $request->validate([
            'status' => 'required|in:success,failed',
            'response' => 'required|array',
        ]);
        
        try {
            if ($request->input('status') === 'success') {
                $this->commandService->executeCommand(
                    $commandId,
                    $request->input('response')
                );
            } else {
                $command = \App\Models\DeviceCommand::findOrFail($commandId);
                $command->markAsFailed($request->input('response.error', 'Unknown error'));
            }
            
            return response()->json(['status' => 'reported']);
            
        } catch (\Exception $e) {
            return response()->json([
                'status' => 'error',
                'message' => $e->getMessage(),
            ], 400);
        }
    }
    
    public function updateStatus(Request $request): JsonResponse
    {
        $device = $request->attributes->get('iot_device');
        
        $request->validate([
            'status' => 'required|in:online,offline,maintenance,error',
            'metadata' => 'nullable|array',
        ]);
        
        $oldStatus = $device->status;
        
        $device->update([
            'status' => $request->input('status'),
            'last_seen_at' => now(),
        ]);
        
        if ($oldStatus !== $request->input('status')) {
            event(new DeviceStatusChanged($device, $oldStatus, $request->input('status')));
        }
        
        return response()->json([
            'status' => 'updated',
            'device_status' => $device->status,
        ]);
    }
    
    protected function calculatePollInterval(\App\Models\IoTDevice $device): int
    {
        // Adjust poll interval based on device priority and activity
        $baseInterval = 60; // seconds
        
        if ($device->commands()->pending()->exists()) {
            return min($baseInterval, 15); // Faster polling when commands are pending
        }
        
        if ($device->status === \App\Models\IoTDevice::STATUS_ERROR) {
            return min($baseInterval, 30); // Faster polling for error devices
        }
        
        return $baseInterval;
    }
}

// WebSocket Event Broadcasting
namespace App\Events;

use App\Models\IoTDevice;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class DeviceTelemetryReceived implements ShouldBroadcast
{
    use Dispatchable, InteractsWithSockets, SerializesModels;
    
    public IoTDevice $device;
    public array $telemetry;
    
    public function __construct(IoTDevice $device, array $telemetry)
    {
        $this->device = $device;
        $this->telemetry = $telemetry;
    }
    
    public function broadcastOn(): array
    {
        return [
            new PrivateChannel('iot.device.' . $this->device->device_id),
            new PrivateChannel('iot.user.' . $this->device->user_id),
        ];
    }
    
    public function broadcastAs(): string
    {
        return 'telemetry.received';
    }
    
    public function broadcastWith(): array
    {
        return [
            'device_id' => $this->device->device_id,
            'device_name' => $this->device->name,
            'telemetry' => $this->telemetry,
            'timestamp' => now()->toISOString(),
        ];
    }
}

Alert and Monitoring System

Advanced Device Monitoring

Implement comprehensive device monitoring and alerting:

<?php

namespace App\Services;

use App\Models\IoTDevice;
use App\Models\DeviceAlert;
use App\Models\DeviceTelemetry;
use App\Notifications\DeviceAlertNotification;
use Illuminate\Support\Facades\Log;

class DeviceMonitoringService
{
    protected array $alertRules = [];
    
    public function __construct()
    {
        $this->loadAlertRules();
    }
    
    public function monitorDevice(IoTDevice $device): array
    {
        $alerts = [];
        
        // Check device connectivity
        if (!$device->isOnline()) {
            $alerts[] = $this->createConnectivityAlert($device);
        }
        
        // Check telemetry thresholds
        $telemetryAlerts = $this->checkTelemetryThresholds($device);
        $alerts = array_merge($alerts, $telemetryAlerts);
        
        // Check device health
        $healthAlerts = $this->checkDeviceHealth($device);
        $alerts = array_merge($alerts, $healthAlerts);
        
        // Process and store alerts
        foreach ($alerts as $alert) {
            $this->processAlert($device, $alert);
        }
        
        return $alerts;
    }
    
    protected function createConnectivityAlert(IoTDevice $device): array
    {
        $minutesOffline = $device->last_seen_at 
            ? $device->last_seen_at->diffInMinutes(now())
            : 999;
        
        return [
            'type' => 'connectivity',
            'severity' => $minutesOffline > 60 ? 'critical' : 'warning',
            'message' => "Device offline for {$minutesOffline} minutes",
            'metadata' => [
                'last_seen' => $device->last_seen_at?->toISOString(),
                'minutes_offline' => $minutesOffline,
            ],
        ];
    }
    
    protected function checkTelemetryThresholds(IoTDevice $device): array
    {
        $alerts = [];
        $rules = $this->alertRules[$device->type] ?? [];
        
        foreach ($rules as $rule) {
            $latestReading = DeviceTelemetry::where('device_id', $device->device_id)
                                         ->where('sensor_type', $rule['sensor_type'])
                                         ->where('measurement_type', $rule['measurement_type'])
                                         ->orderBy('recorded_at', 'desc')
                                         ->first();
            
            if (!$latestReading) {
                continue;
            }
            
            $alert = $this->evaluateThresholdRule($rule, $latestReading);
            if ($alert) {
                $alerts[] = $alert;
            }
        }
        
        return $alerts;
    }
    
    protected function evaluateThresholdRule(array $rule, DeviceTelemetry $reading): ?array
    {
        $value = $reading->value;
        $thresholds = $rule['thresholds'];
        
        if (isset($thresholds['critical'])) {
            if ($this->checkThreshold($value, $thresholds['critical'])) {
                return [
                    'type' => 'threshold',
                    'severity' => 'critical',
                    'message' => "Critical threshold exceeded for {$reading->sensor_type}",
                    'metadata' => [
                        'sensor_type' => $reading->sensor_type,
                        'measurement_type' => $reading->measurement_type,
                        'value' => $value,
                        'threshold' => $thresholds['critical'],
                        'unit' => $reading->unit,
                    ],
                ];
            }
        }
        
        if (isset($thresholds['warning'])) {
            if ($this->checkThreshold($value, $thresholds['warning'])) {
                return [
                    'type' => 'threshold',
                    'severity' => 'warning',
                    'message' => "Warning threshold exceeded for {$reading->sensor_type}",
                    'metadata' => [
                        'sensor_type' => $reading->sensor_type,
                        'measurement_type' => $reading->measurement_type,
                        'value' => $value,
                        'threshold' => $thresholds['warning'],
                        'unit' => $reading->unit,
                    ],
                ];
            }
        }
        
        return null;
    }
    
    protected function checkThreshold(float $value, array $threshold): bool
    {
        if (isset($threshold['min']) && $value < $threshold['min']) {
            return true;
        }
        
        if (isset($threshold['max']) && $value > $threshold['max']) {
            return true;
        }
        
        return false;
    }
    
    protected function checkDeviceHealth(IoTDevice $device): array
    {
        $alerts = [];
        
        // Check for data quality issues
        $lowQualityCount = DeviceTelemetry::where('device_id', $device->device_id)
                                        ->where('recorded_at', '>=', now()->subHour())
                                        ->where('quality', '<', 0.5)
                                        ->count();
        
        if ($lowQualityCount > 10) {
            $alerts[] = [
                'type' => 'data_quality',
                'severity' => 'warning',
                'message' => "High number of low-quality readings: {$lowQualityCount}",
                'metadata' => [
                    'low_quality_count' => $lowQualityCount,
                    'period' => '1 hour',
                ],
            ];
        }
        
        // Check firmware version
        if ($this->isFirmwareOutdated($device)) {
            $alerts[] = [
                'type' => 'firmware',
                'severity' => 'info',
                'message' => "Firmware update available",
                'metadata' => [
                    'current_version' => $device->firmware_version,
                    'latest_version' => $this->getLatestFirmwareVersion($device->type),
                ],
            ];
        }
        
        return $alerts;
    }
    
    protected function processAlert(IoTDevice $device, array $alertData): void
    {
        // Check if similar alert exists recently
        $existingAlert = DeviceAlert::where('device_id', $device->device_id)
                                  ->where('type', $alertData['type'])
                                  ->where('created_at', '>=', now()->subMinutes(30))
                                  ->first();
        
        if ($existingAlert) {
            // Update existing alert
            $existingAlert->update([
                'count' => $existingAlert->count + 1,
                'last_occurrence' => now(),
                'metadata' => $alertData['metadata'],
            ]);
            return;
        }
        
        // Create new alert
        $alert = DeviceAlert::create([
            'device_id' => $device->device_id,
            'type' => $alertData['type'],
            'severity' => $alertData['severity'],
            'message' => $alertData['message'],
            'metadata' => $alertData['metadata'],
            'status' => 'active',
            'count' => 1,
            'last_occurrence' => now(),
        ]);
        
        // Send notifications based on severity
        if (in_array($alertData['severity'], ['critical', 'warning'])) {
            $device->owner->notify(new DeviceAlertNotification($alert));
        }
        
        Log::info('Device alert created', [
            'device_id' => $device->device_id,
            'alert_type' => $alertData['type'],
            'severity' => $alertData['severity'],
        ]);
    }
    
    protected function loadAlertRules(): void
    {
        $this->alertRules = [
            'temperature_sensor' => [
                [
                    'sensor_type' => 'temperature',
                    'measurement_type' => 'ambient',
                    'thresholds' => [
                        'critical' => ['min' => -20, 'max' => 50],
                        'warning' => ['min' => -10, 'max' => 40],
                    ],
                ],
            ],
            'humidity_sensor' => [
                [
                    'sensor_type' => 'humidity',
                    'measurement_type' => 'relative',
                    'thresholds' => [
                        'critical' => ['min' => 10, 'max' => 90],
                        'warning' => ['min' => 20, 'max' => 80],
                    ],
                ],
            ],
        ];
    }
    
    protected function isFirmwareOutdated(IoTDevice $device): bool
    {
        $latestVersion = $this->getLatestFirmwareVersion($device->type);
        return version_compare($device->firmware_version, $latestVersion, '<');
    }
    
    protected function getLatestFirmwareVersion(string $deviceType): string
    {
        // In real implementation, this would check a firmware repository
        return '1.2.3';
    }
}

Building IoT applications with Laravel requires careful consideration of scalability, security, and real-time communication patterns. These advanced techniques enable creating robust IoT backends that can handle thousands of connected devices while providing comprehensive monitoring and control capabilities essential for modern web applications.

Share this article

Add Comment

No comments yet. Be the first to comment!

More from Laravel