1
2
3
4
5
6
7
8
9
10
11
12
13
// Loop de controle a 20 kHz, sem alocação dinâmica
void MotorControl::onPwmTick() noexcept {
const auto i = adc_.readPhaseCurrents();
const auto v = pid_.update(setpoint_, i.q);
pwm_.apply(svm::modulate(v, encoder_.angle()));
if (i.peak() > limits_.overcurrent) {
pwm_.disableOutputs(); // falha segura
faults_.raise(Fault::Overcurrent);
}
telemetry_.push(Sample{tick_++, i, v});
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
public partial class MachinePanel : UserControl
{
public MachinePanel()
{
InitializeComponent();
}
private void CheckLimits()
{
if (machine.Rpm > machine.Limits.Rpm)
events.Log(Event.Overspeed, machine.Rpm);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
SUMMARY = "Imagem de produção: IHM .NET + telemetria"
LICENSE = "MIT"
inherit core-image
IMAGE_FEATURES += "read-only-rootfs"
IMAGE_INSTALL:append = " \
dotnet-runtime \
eger-hmi \
telemetry-agent \
swupdate \
"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1/devices")
class TelemetryController {
private final TelemetryService service;
@PostMapping("/{serial}/telemetry")
ResponseEntity<Void> ingest(@PathVariable String serial,
@Valid @RequestBody TelemetryBatch batch) {
service.store(serial, batch);
return ResponseEntity.accepted().build();
}
}