-
Notifications
You must be signed in to change notification settings - Fork 11
Expand file tree
/
Copy pathMachineGatewayController.php
More file actions
107 lines (95 loc) · 3.96 KB
/
Copy pathMachineGatewayController.php
File metadata and controls
107 lines (95 loc) · 3.96 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
<?php
namespace App\Http\Controllers\Api\V1;
use App\Http\Controllers\Controller;
use App\Models\MachineConnection;
use App\Models\MachineTag;
use App\Services\Machine\MachineSignalIngestor;
use App\Services\Machine\RuntimeMonitor;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
/**
* Bridge endpoint for external protocol gateways (OPC UA sidecar, custom REST
* pushers). The gateway fetches its config (which nodes/tags to read), then
* posts normalized readings back here; each reading flows through the same
* MachineSignalIngestor as Modbus/MQTT. Posting also refreshes the runtime
* heartbeat so the UI knows the gateway is alive.
*/
class MachineGatewayController extends Controller
{
public function __construct(
private readonly MachineSignalIngestor $ingestor,
private readonly RuntimeMonitor $runtime,
) {}
/**
* Config the gateway needs to connect and subscribe.
*/
public function config(MachineConnection $machineConnection): JsonResponse
{
$machineConnection->load(['opcuaConnection', 'activeTags']);
$opc = $machineConnection->opcuaConnection;
return response()->json([
'connection' => [
'id' => $machineConnection->id,
'name' => $machineConnection->name,
'protocol' => $machineConnection->protocol,
'is_active' => $machineConnection->is_active,
],
'opcua' => $opc ? [
'endpoint_url' => $opc->endpoint_url,
'security_policy' => $opc->security_policy,
'security_mode' => $opc->security_mode,
'auth_mode' => $opc->auth_mode,
'username' => $opc->username,
'publishing_interval_ms' => $opc->publishing_interval_ms,
] : null,
'tags' => $machineConnection->activeTags->map(fn (MachineTag $t) => [
'id' => $t->id,
'name' => $t->name,
'node_id' => $t->address,
'signal_type' => $t->signal_type,
'data_type' => $t->data_type,
])->values(),
]);
}
/**
* Receive normalized readings from the gateway and ingest them.
*
* Body: { readings: [ { tag_id?, node_id?, value, ts? }, ... ] }
*/
public function ingest(Request $request, MachineConnection $machineConnection): JsonResponse
{
$data = $request->validate([
'readings' => ['required', 'array', 'min:1'],
'readings.*.tag_id' => ['nullable', 'integer'],
'readings.*.node_id' => ['nullable', 'string'],
'readings.*.value' => ['present'],
'readings.*.ts' => ['nullable', 'date'],
]);
// Heartbeat: a posting gateway is, by definition, alive.
$this->runtime->heartbeat($machineConnection->protocol, $machineConnection->id);
$machineConnection->markConnected();
$tags = $machineConnection->activeTags()->get()->keyBy('id');
$byAddress = $machineConnection->activeTags()->get()->keyBy('address');
$accepted = 0;
foreach ($data['readings'] as $r) {
$tag = isset($r['tag_id']) ? $tags->get($r['tag_id']) : null;
$tag ??= isset($r['node_id']) ? $byAddress->get($r['node_id']) : null;
if (! $tag) {
continue;
}
$at = isset($r['ts']) ? \Illuminate\Support\Carbon::parse($r['ts']) : null;
$this->ingestor->ingest($tag, $r['value'], $at);
$machineConnection->increment('messages_received');
$accepted++;
}
return response()->json(['accepted' => $accepted]);
}
/**
* Standalone heartbeat (gateway connected but no readings to push yet).
*/
public function heartbeat(MachineConnection $machineConnection): JsonResponse
{
$this->runtime->heartbeat($machineConnection->protocol, $machineConnection->id);
return response()->json(['ok' => true]);
}
}