Plancia configurabile su Modbus RTU
Due programmi che condividono uModbusRTU e uGauge: - Console/PlanciaConsole: plancia nautica descritta da file XML, con modalita' plancia e modalita' configurazione. Comandi, spie, selettori, strumenti e allarmi sonori; il bus gira in un thread suo perche' la finestra non si fermi mai. - ProjectPlancia: il programma di prova piu' vecchio, usato per collaudare i canali. Le plance sono in Console/*.xml, la documentazione in Console/LEGGIMI.md. Esclusi dal versionamento i compilati (.exe, .dcu), i file dell'IDE e un audio da 17 MB. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
commit
4a01a2ec88
69 files changed
+11658
No files matched your search
@@ -0,0 +1,5 @@
|
||||
@echo off
|
||||
call "F:\Program Files (x86)\Embarcadero\Studio\37.0\bin\rsvars.bat" >nul
|
||||
cd /d D:\dev\plancia\Console\strumenti
|
||||
dcc32.exe -B "-NSSystem;System.Win;Winapi;Vcl;Data;Xml" -E. -N. -U"D:\dev\plancia;F:\Program Files (x86)\Embarcadero\Studio\37.0\lib\Win32\release" dumpframe.dpr
|
||||
echo ==== EXIT: %ERRORLEVEL% ====
|
||||
@@ -0,0 +1,5 @@
|
||||
@echo off
|
||||
call "F:\Program Files (x86)\Embarcadero\Studio\37.0\bin\rsvars.bat" >nul
|
||||
cd /d D:\dev\plancia\Console\strumenti
|
||||
dcc32.exe -B "-NSSystem;System.Win;Winapi;Vcl;Data;Xml" -E. -N. -U"D:\dev\plancia;F:\Program Files (x86)\Embarcadero\Studio\37.0\lib\Win32\release" monitorio.dpr
|
||||
echo ==== EXIT: %ERRORLEVEL% ====
|
||||
@@ -0,0 +1,5 @@
|
||||
@echo off
|
||||
call "F:\Program Files (x86)\Embarcadero\Studio\37.0\bin\rsvars.bat" >nul
|
||||
cd /d D:\dev\plancia\Console\strumenti
|
||||
dcc32.exe -B "-NSSystem;System.Win;Winapi;Vcl;Vcl.Imaging;Data;Xml" -E. -N. -U"D:\dev\plancia\Console;D:\dev\plancia;F:\Program Files (x86)\Embarcadero\Studio\37.0\lib\Win32\release" ringtest.dpr
|
||||
echo ==== EXIT: %ERRORLEVEL% ====
|
||||
@@ -0,0 +1,5 @@
|
||||
@echo off
|
||||
call "F:\Program Files (x86)\Embarcadero\Studio\37.0\bin\rsvars.bat" >nul
|
||||
cd /d D:\dev\plancia\Console\strumenti
|
||||
dcc32.exe -B "-NSSystem;System.Win;Winapi;Vcl;Data;Xml" -E. -N. -U"D:\dev\plancia;F:\Program Files (x86)\Embarcadero\Studio\37.0\lib\Win32\release" scanbus.dpr
|
||||
echo ==== EXIT: %ERRORLEVEL% ====
|
||||
@@ -0,0 +1,128 @@
|
||||
program dumpframe;
|
||||
|
||||
{
|
||||
Manda una singola richiesta Modbus e stampa i byte grezzi della risposta,
|
||||
senza interpretarli. Serve quando una lettura fallisce e non si sa se il
|
||||
problema sia nel modulo, nel cablaggio o nel codice che decodifica.
|
||||
|
||||
Uso: dumpframe [porta] [baud] [slave] [funzione] [start] [quantita]
|
||||
Es.: dumpframe COM7 9600 1 2 0 8
|
||||
}
|
||||
|
||||
{$APPTYPE CONSOLE}
|
||||
|
||||
uses
|
||||
Winapi.Windows, System.SysUtils;
|
||||
|
||||
function CRC16(const ABuf: array of Byte; ALen: Integer): Word;
|
||||
var
|
||||
I, J: Integer;
|
||||
begin
|
||||
Result := $FFFF;
|
||||
for I := 0 to ALen - 1 do
|
||||
begin
|
||||
Result := Result xor ABuf[I];
|
||||
for J := 0 to 7 do
|
||||
if (Result and 1) <> 0 then
|
||||
Result := (Result shr 1) xor $A001
|
||||
else
|
||||
Result := Result shr 1;
|
||||
end;
|
||||
end;
|
||||
|
||||
function Hex(const ABuf: TBytes; ALen: Integer): string;
|
||||
var
|
||||
I: Integer;
|
||||
begin
|
||||
Result := '';
|
||||
for I := 0 to ALen - 1 do
|
||||
Result := Result + IntToHex(ABuf[I], 2) + ' ';
|
||||
end;
|
||||
|
||||
var
|
||||
Port: string;
|
||||
Baud, Slave, Func, Start, Qty: Integer;
|
||||
H: THandle;
|
||||
DCB: TDCB;
|
||||
TO_: TCommTimeouts;
|
||||
Req: array[0..7] of Byte;
|
||||
C: Word;
|
||||
Buf: TBytes;
|
||||
Written, Got: DWORD;
|
||||
Expected, I: Integer;
|
||||
|
||||
begin
|
||||
Port := 'COM7'; Baud := 9600; Slave := 1; Func := 2; Start := 0; Qty := 8;
|
||||
if ParamCount >= 1 then Port := ParamStr(1);
|
||||
if ParamCount >= 2 then Baud := StrToIntDef(ParamStr(2), 9600);
|
||||
if ParamCount >= 3 then Slave := StrToIntDef(ParamStr(3), 1);
|
||||
if ParamCount >= 4 then Func := StrToIntDef(ParamStr(4), 2);
|
||||
if ParamCount >= 5 then Start := StrToIntDef(ParamStr(5), 0);
|
||||
if ParamCount >= 6 then Qty := StrToIntDef(ParamStr(6), 8);
|
||||
|
||||
H := CreateFile(PChar('\\.\' + Port), GENERIC_READ or GENERIC_WRITE, 0, nil,
|
||||
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
|
||||
if H = THandle(-1) then
|
||||
begin
|
||||
Writeln(Format('Impossibile aprire %s (errore %d)', [Port, GetLastError]));
|
||||
Readln; Halt(1);
|
||||
end;
|
||||
|
||||
try
|
||||
FillChar(DCB, SizeOf(DCB), 0);
|
||||
DCB.DCBlength := SizeOf(DCB);
|
||||
GetCommState(H, DCB);
|
||||
DCB.BaudRate := Baud;
|
||||
DCB.ByteSize := 8;
|
||||
DCB.Parity := NOPARITY;
|
||||
DCB.StopBits := ONESTOPBIT;
|
||||
DCB.Flags := 0;
|
||||
SetCommState(H, DCB);
|
||||
|
||||
TO_.ReadIntervalTimeout := 50;
|
||||
TO_.ReadTotalTimeoutMultiplier := 10;
|
||||
TO_.ReadTotalTimeoutConstant := 500;
|
||||
TO_.WriteTotalTimeoutMultiplier := 10;
|
||||
TO_.WriteTotalTimeoutConstant := 500;
|
||||
SetCommTimeouts(H, TO_);
|
||||
PurgeComm(H, PURGE_RXCLEAR or PURGE_TXCLEAR);
|
||||
|
||||
Req[0] := Slave;
|
||||
Req[1] := Func;
|
||||
Req[2] := Hi(Word(Start)); Req[3] := Lo(Word(Start));
|
||||
Req[4] := Hi(Word(Qty)); Req[5] := Lo(Word(Qty));
|
||||
C := CRC16(Req, 6);
|
||||
Req[6] := Lo(C); Req[7] := Hi(C);
|
||||
|
||||
Write ('RICHIESTA (8 byte): ');
|
||||
for I := 0 to 7 do Write(IntToHex(Req[I], 2), ' ');
|
||||
Writeln;
|
||||
|
||||
WriteFile(H, Req[0], 8, Written, nil);
|
||||
|
||||
// Legge molto piu' del necessario: se arriva un eco della richiesta o
|
||||
// byte di troppo si devono vedere, non nascondere troncando.
|
||||
Expected := 64;
|
||||
SetLength(Buf, Expected);
|
||||
Sleep(150); // lascia il tempo allo slave di rispondere per intero
|
||||
ReadFile(H, Buf[0], Expected, Got, nil);
|
||||
|
||||
Writeln(Format('RISPOSTA (%d byte): ', [Got]) + Hex(Buf, Got));
|
||||
|
||||
if Got >= 4 then
|
||||
begin
|
||||
C := CRC16(Buf, Got - 2);
|
||||
Writeln(Format('CRC calcolata sui primi %d byte: %s %s - nel frame: %s %s -> %s',
|
||||
[Got - 2, IntToHex(Lo(C), 2), IntToHex(Hi(C), 2),
|
||||
IntToHex(Buf[Got - 2], 2), IntToHex(Buf[Got - 1], 2),
|
||||
BoolToStr((Lo(C) = Buf[Got - 2]) and (Hi(C) = Buf[Got - 1]), True)]));
|
||||
end;
|
||||
|
||||
finally
|
||||
CloseHandle(H);
|
||||
end;
|
||||
|
||||
Writeln;
|
||||
Write('Premi INVIO per chiudere.');
|
||||
Readln;
|
||||
end.
|
||||
@@ -0,0 +1,91 @@
|
||||
# Grafica della plancia multifunzione (plancia-mfd.xml), sul modello della
|
||||
# foto "Esempio plancia": cornice scura con il filo chiaro, schermo nero al
|
||||
# centro. Produce anche la sagoma della barca ANTAGO in chiaro, perche' quella
|
||||
# originale e' disegnata in nero e sullo schermo nero sparirebbe.
|
||||
#
|
||||
# powershell -File genera-mfd.ps1
|
||||
|
||||
param(
|
||||
[string]$Dir = 'D:\dev\Plancia\Console\immagini',
|
||||
[int]$W = 1900,
|
||||
[int]$H = 1080,
|
||||
# Schermo: deve coincidere con la disposizione degli elementi nell'XML.
|
||||
[int]$ScreenLeft = 214,
|
||||
[int]$ScreenTop = 34,
|
||||
[int]$ScreenRight = 1686,
|
||||
[int]$ScreenBottom = 716
|
||||
)
|
||||
|
||||
Add-Type -AssemblyName System.Drawing
|
||||
|
||||
function RoundRect([int]$x, [int]$y, [int]$w, [int]$h, [int]$r) {
|
||||
$p = New-Object System.Drawing.Drawing2D.GraphicsPath
|
||||
$d = 2 * $r
|
||||
$p.AddArc($x, $y, $d, $d, 180, 90)
|
||||
$p.AddArc(($x + $w - $d), $y, $d, $d, 270, 90)
|
||||
$p.AddArc(($x + $w - $d), ($y + $h - $d), $d, $d, 0, 90)
|
||||
$p.AddArc($x, ($y + $h - $d), $d, $d, 90, 90)
|
||||
$p.CloseFigure()
|
||||
return $p
|
||||
}
|
||||
|
||||
# --- sfondo --------------------------------------------------------------
|
||||
$bmp = New-Object System.Drawing.Bitmap($W, $H, [System.Drawing.Imaging.PixelFormat]::Format32bppArgb)
|
||||
$g = [System.Drawing.Graphics]::FromImage($bmp)
|
||||
$g.SmoothingMode = 'AntiAlias'
|
||||
|
||||
# Cornice: grafite opaca, appena piu' chiara in alto.
|
||||
$rect = New-Object System.Drawing.Rectangle(0, 0, $W, $H)
|
||||
$c1 = [System.Drawing.Color]::FromArgb(255, 44, 46, 50)
|
||||
$c2 = [System.Drawing.Color]::FromArgb(255, 22, 23, 26)
|
||||
$br = New-Object System.Drawing.Drawing2D.LinearGradientBrush($rect, $c1, $c2, 90.0)
|
||||
$g.FillRectangle($br, $rect)
|
||||
|
||||
$edge = New-Object System.Drawing.Pen ([System.Drawing.Color]::FromArgb(255, 10, 10, 12)), 4
|
||||
$path = RoundRect 2 2 ($W - 5) ($H - 5) 26
|
||||
$g.DrawPath($edge, $path)
|
||||
|
||||
# Il filo chiaro che corre tutto attorno, come nella foto.
|
||||
$line = New-Object System.Drawing.Pen ([System.Drawing.Color]::FromArgb(255, 200, 204, 209)), 2
|
||||
$path = RoundRect 16 16 ($W - 33) ($H - 33) 20
|
||||
$g.DrawPath($line, $path)
|
||||
|
||||
# Schermo: incassato (ombra scura attorno), nero, con un filo grigio.
|
||||
$sw = $ScreenRight - $ScreenLeft
|
||||
$sh = $ScreenBottom - $ScreenTop
|
||||
$shadow = New-Object System.Drawing.Pen ([System.Drawing.Color]::FromArgb(255, 8, 8, 9)), 8
|
||||
$g.DrawRectangle($shadow, ($ScreenLeft - 4), ($ScreenTop - 4), ($sw + 8), ($sh + 8))
|
||||
$screen = New-Object System.Drawing.SolidBrush ([System.Drawing.Color]::FromArgb(255, 4, 5, 6))
|
||||
$g.FillRectangle($screen, $ScreenLeft, $ScreenTop, $sw, $sh)
|
||||
$glass = New-Object System.Drawing.Pen ([System.Drawing.Color]::FromArgb(255, 92, 97, 104)), 2
|
||||
$g.DrawRectangle($glass, $ScreenLeft, $ScreenTop, $sw, $sh)
|
||||
|
||||
# Separazione fra lo schermo e la fila dei selettori.
|
||||
$sep = New-Object System.Drawing.Pen ([System.Drawing.Color]::FromArgb(255, 70, 74, 80)), 1
|
||||
$g.DrawLine($sep, 36, ($ScreenBottom + 16), ($W - 36), ($ScreenBottom + 16))
|
||||
|
||||
$g.Dispose()
|
||||
$out = Join-Path $Dir 'mfd-sfondo.png'
|
||||
$bmp.Save($out, [System.Drawing.Imaging.ImageFormat]::Png)
|
||||
$bmp.Dispose()
|
||||
Write-Output ("{0} {1}x{2}" -f $out, $W, $H)
|
||||
|
||||
# --- sagoma della barca in chiaro ---------------------------------------
|
||||
# Stessa trasparenza dell'originale, colore invertito: il tratto nero diventa
|
||||
# grigio chiaro, le sfumature di bordo restano sfumature.
|
||||
$src = New-Object System.Drawing.Bitmap (Join-Path $Dir 'antago-barca.png')
|
||||
$dst = New-Object System.Drawing.Bitmap($src.Width, $src.Height, [System.Drawing.Imaging.PixelFormat]::Format32bppArgb)
|
||||
for ($y = 0; $y -lt $src.Height; $y++) {
|
||||
for ($x = 0; $x -lt $src.Width; $x++) {
|
||||
$p = $src.GetPixel($x, $y)
|
||||
if ($p.A -eq 0) { continue }
|
||||
$v = [int](220 - ($p.R + $p.G + $p.B) / 3 * 0.85)
|
||||
if ($v -lt 0) { $v = 0 }
|
||||
$dst.SetPixel($x, $y, [System.Drawing.Color]::FromArgb($p.A, $v, $v, [Math]::Min(255, $v + 6)))
|
||||
}
|
||||
}
|
||||
$out = Join-Path $Dir 'antago-barca-chiara.png'
|
||||
$dst.Save($out, [System.Drawing.Imaging.ImageFormat]::Png)
|
||||
$src.Dispose()
|
||||
$dst.Dispose()
|
||||
Write-Output ("{0}" -f $out)
|
||||
@@ -0,0 +1,46 @@
|
||||
# Disegna la lamiera di un pannello: fondo chiaro sfumato, cornice e viti
|
||||
# agli angoli. Usato per gli sfondi delle plance di esempio.
|
||||
#
|
||||
# powershell -File genera-sfondo.ps1 -Out ..\immagini\sfondo.png -W 1270 -H 950
|
||||
|
||||
param(
|
||||
[string]$Out = 'D:\dev\Plancia\Console\immagini\antago-sfondo.png',
|
||||
[int]$W = 1270,
|
||||
[int]$H = 950
|
||||
)
|
||||
|
||||
Add-Type -AssemblyName System.Drawing
|
||||
|
||||
$bmp = New-Object System.Drawing.Bitmap($W, $H, [System.Drawing.Imaging.PixelFormat]::Format32bppArgb)
|
||||
$g = [System.Drawing.Graphics]::FromImage($bmp)
|
||||
$g.SmoothingMode = 'AntiAlias'
|
||||
|
||||
# Lamiera verniciata: bianco sporco con una leggerissima ombreggiatura verticale.
|
||||
$rect = New-Object System.Drawing.Rectangle(0, 0, $W, $H)
|
||||
$c1 = [System.Drawing.Color]::FromArgb(255, 246, 246, 243)
|
||||
$c2 = [System.Drawing.Color]::FromArgb(255, 226, 227, 224)
|
||||
$br = New-Object System.Drawing.Drawing2D.LinearGradientBrush($rect, $c1, $c2, 90.0)
|
||||
$g.FillRectangle($br, $rect)
|
||||
|
||||
# Cornice del pannello
|
||||
$pen = New-Object System.Drawing.Pen ([System.Drawing.Color]::FromArgb(255, 120, 122, 120)), 3
|
||||
$g.DrawRectangle($pen, 6, 6, ($W - 13), ($H - 13))
|
||||
$pen2 = New-Object System.Drawing.Pen ([System.Drawing.Color]::FromArgb(90, 255, 255, 255)), 1
|
||||
$g.DrawRectangle($pen2, 10, 10, ($W - 21), ($H - 21))
|
||||
|
||||
# Viti agli angoli
|
||||
$screwFill = New-Object System.Drawing.SolidBrush ([System.Drawing.Color]::FromArgb(255, 176, 178, 176))
|
||||
$screwEdge = New-Object System.Drawing.Pen ([System.Drawing.Color]::FromArgb(255, 110, 112, 110)), 2
|
||||
$slot = New-Object System.Drawing.Pen ([System.Drawing.Color]::FromArgb(255, 90, 92, 90)), 3
|
||||
foreach ($p in @(@(26, 26), @(($W - 46), 26), @(26, ($H - 46)), @(($W - 46), ($H - 46)))) {
|
||||
$x = $p[0]
|
||||
$y = $p[1]
|
||||
$g.FillEllipse($screwFill, $x, $y, 20, 20)
|
||||
$g.DrawEllipse($screwEdge, $x, $y, 20, 20)
|
||||
$g.DrawLine($slot, ($x + 4), ($y + 14), ($x + 16), ($y + 6))
|
||||
}
|
||||
|
||||
$g.Dispose()
|
||||
$bmp.Save($Out, [System.Drawing.Imaging.ImageFormat]::Png)
|
||||
$bmp.Dispose()
|
||||
Write-Output ("{0} {1}x{2}" -f $Out, $W, $H)
|
||||
@@ -0,0 +1,129 @@
|
||||
program monitorio;
|
||||
|
||||
{
|
||||
Mostra in tempo reale gli ingressi digitali di un modulo IO.
|
||||
|
||||
Serve al banco, quando si collega un morsetto e si vuole vedere subito quale
|
||||
canale si muove. Stampa una riga solo quando qualcosa cambia, cosi' la
|
||||
finestra non scorre da sola e la cronologia resta leggibile.
|
||||
|
||||
Uso: monitorio [porta] [baud] [slave] [canali] [cicli]
|
||||
cicli = quante letture fare prima di uscire; 0 o assente = per sempre
|
||||
Es.: monitorio COM7 9600 2 8
|
||||
|
||||
Si chiude con Ctrl+C.
|
||||
}
|
||||
|
||||
{$APPTYPE CONSOLE}
|
||||
|
||||
uses
|
||||
System.SysUtils,
|
||||
uModbusRTU in '..\..\uModbusRTU.pas';
|
||||
|
||||
/// "1 0 0 1 . . . ." con i canali spenti resi come punto: gli accesi saltano all'occhio.
|
||||
function Render(const ABits: TArray<Boolean>): string;
|
||||
var
|
||||
I: Integer;
|
||||
begin
|
||||
Result := '';
|
||||
for I := 0 to High(ABits) do
|
||||
if ABits[I] then
|
||||
Result := Result + '[1]'
|
||||
else
|
||||
Result := Result + ' . ';
|
||||
end;
|
||||
|
||||
function Header(ACount: Integer): string;
|
||||
var
|
||||
I: Integer;
|
||||
begin
|
||||
Result := '';
|
||||
for I := 0 to ACount - 1 do
|
||||
Result := Result + Format(' %d ', [I]);
|
||||
end;
|
||||
|
||||
var
|
||||
Port: string;
|
||||
Baud, Slave, Count, Cycles, Done: Integer;
|
||||
Bus: TModbusRTU;
|
||||
Bits, Prev: TArray<Boolean>;
|
||||
First: Boolean;
|
||||
Changed: Boolean;
|
||||
I, Errors: Integer;
|
||||
LastErr: string;
|
||||
|
||||
begin
|
||||
Port := 'COM7'; Baud := 9600; Slave := 2; Count := 8; Cycles := 0; Done := 0;
|
||||
if ParamCount >= 1 then Port := ParamStr(1);
|
||||
if ParamCount >= 2 then Baud := StrToIntDef(ParamStr(2), 9600);
|
||||
if ParamCount >= 3 then Slave := StrToIntDef(ParamStr(3), 2);
|
||||
if ParamCount >= 4 then Count := StrToIntDef(ParamStr(4), 8);
|
||||
if ParamCount >= 5 then Cycles := StrToIntDef(ParamStr(5), 0);
|
||||
|
||||
Writeln(Format('Ingressi dello slave %d su %s a %d baud. Ctrl+C per uscire.',
|
||||
[Slave, Port, Baud]));
|
||||
Writeln;
|
||||
Writeln(' canale: ' + Header(Count));
|
||||
Writeln;
|
||||
|
||||
Bus := TModbusRTU.Create(Port, Baud, 500);
|
||||
try
|
||||
Bus.Connect;
|
||||
First := True;
|
||||
Errors := 0;
|
||||
LastErr := '';
|
||||
SetLength(Prev, 0);
|
||||
|
||||
while True do
|
||||
begin
|
||||
try
|
||||
Bits := Bus.ReadDiscreteInputs(Slave, 0, Count);
|
||||
|
||||
if Errors > 0 then
|
||||
begin
|
||||
Writeln(Format('%s ... ripristinato dopo %d letture fallite',
|
||||
[FormatDateTime('hh:nn:ss', Now), Errors]));
|
||||
Errors := 0;
|
||||
end;
|
||||
|
||||
Changed := First or (Length(Prev) <> Length(Bits));
|
||||
if not Changed then
|
||||
for I := 0 to High(Bits) do
|
||||
if Bits[I] <> Prev[I] then
|
||||
begin
|
||||
Changed := True;
|
||||
Break;
|
||||
end;
|
||||
|
||||
if Changed then
|
||||
begin
|
||||
Writeln(FormatDateTime('hh:nn:ss', Now) + ' ' + Render(Bits));
|
||||
Prev := Copy(Bits);
|
||||
First := False;
|
||||
end;
|
||||
|
||||
except
|
||||
on E: Exception do
|
||||
begin
|
||||
// Un errore ripetuto non deve riempire lo schermo: lo si stampa
|
||||
// la prima volta e poi si tace finche' non cambia.
|
||||
Inc(Errors);
|
||||
if E.Message <> LastErr then
|
||||
begin
|
||||
Writeln(FormatDateTime('hh:nn:ss', Now) + ' ERRORE: ' + E.Message);
|
||||
LastErr := E.Message;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
Sleep(200);
|
||||
|
||||
Inc(Done);
|
||||
if (Cycles > 0) and (Done >= Cycles) then
|
||||
Break;
|
||||
end;
|
||||
|
||||
finally
|
||||
Bus.Free;
|
||||
end;
|
||||
end.
|
||||
@@ -0,0 +1,122 @@
|
||||
program ringtest;
|
||||
|
||||
{
|
||||
Banco di prova per l'aspetto degli elementi: disegna la stessa scena di giorno
|
||||
e di notte, spenta e accesa, e salva l'immagine. Serve a giudicare una
|
||||
modifica ai colori guardandola invece che immaginandola.
|
||||
|
||||
Uso: ringtest [file.bmp]
|
||||
}
|
||||
|
||||
{$APPTYPE CONSOLE}
|
||||
|
||||
uses
|
||||
Winapi.Windows, System.SysUtils, System.Classes,
|
||||
Vcl.Forms, Vcl.Graphics, Vcl.Controls, Vcl.ExtCtrls,
|
||||
uPlanciaElements in '..\uPlanciaElements.pas',
|
||||
uImageLib in '..\uImageLib.pas',
|
||||
uModbusRTU in '..\..\uModbusRTU.pas';
|
||||
|
||||
const
|
||||
PANE_W = 430;
|
||||
PANE_H = 330;
|
||||
|
||||
var
|
||||
Host: TForm;
|
||||
Lib: TImageLibrary;
|
||||
Bmp: TBitmap;
|
||||
OutFile: string;
|
||||
|
||||
/// Un elemento nello stato voluto, dentro il riquadro indicato.
|
||||
procedure Place(APane: TWinControl; AKind: TElementKind; AShape: TKeyShape;
|
||||
ALeft, ATop, AW, AH: Integer; AOn, ANight: Boolean; const ACaption: string;
|
||||
AOffColor: TColor);
|
||||
var
|
||||
Def: TElementDef;
|
||||
El: TPlanciaElement;
|
||||
begin
|
||||
Def := TElementDef.Create(AKind);
|
||||
Def.Shape := AShape;
|
||||
Def.Caption := ACaption;
|
||||
Def.CaptionPos := cpBelow;
|
||||
Def.OffColor := AOffColor;
|
||||
Def.Left := ALeft;
|
||||
Def.Top := ATop;
|
||||
Def.Width := AW;
|
||||
Def.Height := AH;
|
||||
El := TPlanciaElement.CreateElement(Host, Def, Lib);
|
||||
El.Parent := APane;
|
||||
El.Color := APane.Brush.Color;
|
||||
El.NightMode := ANight;
|
||||
El.SyncState(AOn);
|
||||
end;
|
||||
|
||||
/// La stessa scena, cambia solo se e' notte.
|
||||
procedure Scene(APane: TPanel; ANight: Boolean);
|
||||
begin
|
||||
// Tondi: spento e acceso, con lente neutra e lente rossa.
|
||||
Place(APane, ekSwitch, ksRound, 20, 20, 96, 96, False, ANight, 'SPENTO', clNone);
|
||||
Place(APane, ekSwitch, ksRound, 130, 20, 96, 96, True, ANight, 'ACCESO', clNone);
|
||||
Place(APane, ekSwitch, ksRound, 240, 20, 96, 96, False, ANight, 'FIRE', TColor($001818B0));
|
||||
Place(APane, ekSwitch, ksRound, 340, 20, 96, 96, True, ANight, 'FIRE ON', TColor($001818B0));
|
||||
// Squadrati: qui lo stato sta tutto nel bordo.
|
||||
Place(APane, ekButton, ksRect, 20, 150, 110, 44, False, ANight, 'RECT', clNone);
|
||||
Place(APane, ekButton, ksRect, 150, 150, 110, 44, True, ANight, 'RECT ON', clNone);
|
||||
// Spia: questa il colore lo cambia eccome.
|
||||
Place(APane, ekLamp, ksAuto, 290, 150, 70, 60, False, ANight, 'SPIA', clNone);
|
||||
Place(APane, ekLamp, ksAuto, 365, 150, 70, 60, True, ANight, 'SPIA ON', clNone);
|
||||
// Scritta serigrafata: di notte deve passare all'ambra, non sparire.
|
||||
Place(APane, ekLabel, ksAuto, 20, 250, 200, 50, False, ANight, 'ZEBRA', clNone);
|
||||
Place(APane, ekGauge, ksAuto, 250, 225, 100, 95, False, ANight, 'LIVELLO', clNone);
|
||||
end;
|
||||
|
||||
var
|
||||
PaneDay, PaneNight: TPanel;
|
||||
|
||||
begin
|
||||
OutFile := 'ringtest.bmp';
|
||||
if ParamCount >= 1 then
|
||||
OutFile := ParamStr(1);
|
||||
|
||||
Application.Initialize;
|
||||
Lib := TImageLibrary.Create;
|
||||
Host := TForm.Create(nil);
|
||||
try
|
||||
Host.BorderStyle := bsNone;
|
||||
Host.ClientWidth := PANE_W * 2;
|
||||
Host.ClientHeight := PANE_H;
|
||||
|
||||
PaneDay := TPanel.Create(Host);
|
||||
PaneDay.Parent := Host;
|
||||
PaneDay.SetBounds(0, 0, PANE_W, PANE_H);
|
||||
PaneDay.BevelOuter := bvNone;
|
||||
PaneDay.Color := clBtnFace;
|
||||
PaneDay.ParentBackground := False;
|
||||
|
||||
PaneNight := TPanel.Create(Host);
|
||||
PaneNight.Parent := Host;
|
||||
PaneNight.SetBounds(PANE_W, 0, PANE_W, PANE_H);
|
||||
PaneNight.BevelOuter := bvNone;
|
||||
PaneNight.Color := CLR_NIGHT_BG;
|
||||
PaneNight.ParentBackground := False;
|
||||
|
||||
Scene(PaneDay, False);
|
||||
Scene(PaneNight, True);
|
||||
|
||||
Host.Show;
|
||||
Application.ProcessMessages;
|
||||
|
||||
Bmp := TBitmap.Create;
|
||||
try
|
||||
Bmp.SetSize(Host.ClientWidth, Host.ClientHeight);
|
||||
Host.PaintTo(Bmp.Canvas.Handle, 0, 0);
|
||||
Bmp.SaveToFile(OutFile);
|
||||
Writeln(Format('scritto %s (%dx%d)', [OutFile, Bmp.Width, Bmp.Height]));
|
||||
finally
|
||||
Bmp.Free;
|
||||
end;
|
||||
finally
|
||||
Host.Free;
|
||||
Lib.Free;
|
||||
end;
|
||||
end.
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
@@ -0,0 +1,41 @@
|
||||
# Estrae un pezzo di serigrafia dalla foto di un quadro: ritaglia la regione
|
||||
# indicata e rende trasparenti i pixel piu' chiari della soglia, cosi' resta
|
||||
# solo il tratto scuro e si appoggia su qualunque sfondo.
|
||||
#
|
||||
# powershell -File ritaglia-grafica.ps1 -Src foto.png -Out logo.png -X 452 -Y 824 -W 335 -H 112 -Soglia 128
|
||||
|
||||
param(
|
||||
[string]$Src,
|
||||
[string]$Out,
|
||||
[int]$X,
|
||||
[int]$Y,
|
||||
[int]$W,
|
||||
[int]$H,
|
||||
[double]$Soglia = 128.0
|
||||
)
|
||||
|
||||
Add-Type -AssemblyName System.Drawing
|
||||
|
||||
$img = [System.Drawing.Bitmap]::FromFile($Src)
|
||||
$bmp = New-Object System.Drawing.Bitmap($W, $H, [System.Drawing.Imaging.PixelFormat]::Format32bppArgb)
|
||||
$clear = [System.Drawing.Color]::FromArgb(0, 255, 255, 255)
|
||||
|
||||
for ($j = 0; $j -lt $H; $j++) {
|
||||
for ($i = 0; $i -lt $W; $i++) {
|
||||
$c = $img.GetPixel(($X + $i), ($Y + $j))
|
||||
$lum = ([double]$c.R * 0.299) + ([double]$c.G * 0.587) + ([double]$c.B * 0.114)
|
||||
if ($lum -gt $Soglia) {
|
||||
$bmp.SetPixel($i, $j, $clear)
|
||||
}
|
||||
else {
|
||||
# Normalizza il tratto verso il nero, togliendo il colore della luce
|
||||
$v = [int]([Math]::Max(0.0, [Math]::Min(255.0, ($lum * 0.7))))
|
||||
$bmp.SetPixel($i, $j, [System.Drawing.Color]::FromArgb(255, $v, $v, $v))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$bmp.Save($Out, [System.Drawing.Imaging.ImageFormat]::Png)
|
||||
$bmp.Dispose()
|
||||
$img.Dispose()
|
||||
Write-Output ("{0} {1}x{2}" -f $Out, $W, $H)
|
||||
@@ -0,0 +1,23 @@
|
||||
# Ruota un'immagine di 90 gradi. Serve per raddrizzare le foto dei quadri
|
||||
# scattate in verticale.
|
||||
#
|
||||
# powershell -File ruota.ps1 -In foto.png -Out dritta.png -Verso ccw
|
||||
|
||||
param(
|
||||
[string]$In,
|
||||
[string]$Out,
|
||||
[ValidateSet('cw', 'ccw')][string]$Verso = 'ccw'
|
||||
)
|
||||
|
||||
Add-Type -AssemblyName System.Drawing
|
||||
|
||||
$img = [System.Drawing.Bitmap]::FromFile($In)
|
||||
if ($Verso -eq 'cw') {
|
||||
$img.RotateFlip([System.Drawing.RotateFlipType]::Rotate90FlipNone)
|
||||
}
|
||||
else {
|
||||
$img.RotateFlip([System.Drawing.RotateFlipType]::Rotate270FlipNone)
|
||||
}
|
||||
$img.Save($Out, [System.Drawing.Imaging.ImageFormat]::Png)
|
||||
Write-Output ("{0} {1}x{2}" -f $Out, $img.Width, $img.Height)
|
||||
$img.Dispose()
|
||||
@@ -0,0 +1,141 @@
|
||||
program scanbus;
|
||||
|
||||
{
|
||||
Scanner del bus Modbus RTU.
|
||||
|
||||
Interroga gli indirizzi slave uno per uno e riporta chi risponde, con quali
|
||||
funzioni e che dati restituisce. Serve a scoprire l'indirizzo di un modulo
|
||||
appena inserito e a capire da che canale partono i suoi ingressi.
|
||||
|
||||
Uso: scanbus [porta] [baud] [ultimoIndirizzo]
|
||||
Es.: scanbus COM7 9600 16
|
||||
}
|
||||
|
||||
{$APPTYPE CONSOLE}
|
||||
|
||||
uses
|
||||
System.SysUtils,
|
||||
uModbusRTU in '..\..\uModbusRTU.pas';
|
||||
|
||||
const
|
||||
PROBE_TIMEOUT_MS = 200; // basso: un indirizzo morto non deve costare mezzo secondo
|
||||
PROBE_COUNT = 8; // 8 canali: la taglia tipica di questi moduli
|
||||
|
||||
/// Rende leggibili gli 8 bit come "1 0 0 1 ...", con i numeri di canale sopra.
|
||||
function BitsToStr(const ABits: TArray<Boolean>): string;
|
||||
var
|
||||
I: Integer;
|
||||
begin
|
||||
Result := '';
|
||||
for I := 0 to High(ABits) do
|
||||
if ABits[I] then Result := Result + '1 ' else Result := Result + '0 ';
|
||||
end;
|
||||
|
||||
function RegsToStr(const ARegs: TArray<Word>): string;
|
||||
var
|
||||
I: Integer;
|
||||
begin
|
||||
Result := '';
|
||||
for I := 0 to High(ARegs) do
|
||||
Result := Result + IntToStr(ARegs[I]) + ' ';
|
||||
end;
|
||||
|
||||
var
|
||||
Port: string;
|
||||
Baud: Cardinal;
|
||||
LastAddr: Integer;
|
||||
Bus: TModbusRTU;
|
||||
Addr: Integer;
|
||||
Alive: Boolean;
|
||||
Found: Integer;
|
||||
Bits: TArray<Boolean>;
|
||||
Regs: TArray<Word>;
|
||||
|
||||
begin
|
||||
try
|
||||
Port := 'COM7';
|
||||
Baud := 9600;
|
||||
LastAddr := 16;
|
||||
if ParamCount >= 1 then Port := ParamStr(1);
|
||||
if ParamCount >= 2 then Baud := StrToUIntDef(ParamStr(2), 9600);
|
||||
if ParamCount >= 3 then LastAddr := StrToIntDef(ParamStr(3), 16);
|
||||
|
||||
Writeln(Format('Scansione %s a %d baud, indirizzi da 1 a %d.', [Port, Baud, LastAddr]));
|
||||
Writeln('Chiudi PlanciaConsole prima di lanciarlo: la porta si apre una volta sola.');
|
||||
Writeln;
|
||||
|
||||
Bus := TModbusRTU.Create(Port, Baud, PROBE_TIMEOUT_MS);
|
||||
try
|
||||
Bus.Connect;
|
||||
Found := 0;
|
||||
|
||||
for Addr := 1 to LastAddr do
|
||||
begin
|
||||
Write(Format(#13' provo indirizzo %d... ', [Addr]));
|
||||
Alive := False;
|
||||
|
||||
// FC 02 - ingressi digitali: e' cio' che interessa a un modulo IO
|
||||
try
|
||||
Bits := Bus.ReadDiscreteInputs(Addr, 0, PROBE_COUNT);
|
||||
if not Alive then Writeln(#13 + Format('SLAVE %d trovato', [Addr]) + StringOfChar(' ', 20));
|
||||
Alive := True;
|
||||
Writeln(' FC02 ingressi (canali 0..7): ' + BitsToStr(Bits));
|
||||
except
|
||||
on E: Exception do ;
|
||||
end;
|
||||
|
||||
// FC 01 - bobine: le uscite rele'
|
||||
try
|
||||
Bits := Bus.ReadCoils(Addr, 0, PROBE_COUNT);
|
||||
if not Alive then Writeln(#13 + Format('SLAVE %d trovato', [Addr]) + StringOfChar(' ', 20));
|
||||
Alive := True;
|
||||
Writeln(' FC01 bobine (canali 0..7): ' + BitsToStr(Bits));
|
||||
except
|
||||
on E: Exception do ;
|
||||
end;
|
||||
|
||||
if not Alive then
|
||||
Continue; // indirizzo morto: inutile insistere con i registri
|
||||
|
||||
// Solo per chi ha gia' risposto: vale la pena chiedere anche i registri
|
||||
try
|
||||
Regs := Bus.ReadHoldingRegisters(Addr, 0, 4);
|
||||
Writeln(' FC03 holding (reg 0..3): ' + RegsToStr(Regs));
|
||||
except
|
||||
on E: Exception do ;
|
||||
end;
|
||||
|
||||
try
|
||||
Regs := Bus.ReadInputRegisters(Addr, 0, 4);
|
||||
Writeln(' FC04 input reg (reg 0..3): ' + RegsToStr(Regs));
|
||||
except
|
||||
on E: Exception do ;
|
||||
end;
|
||||
|
||||
Writeln;
|
||||
Inc(Found);
|
||||
end;
|
||||
|
||||
Writeln(#13 + StringOfChar(' ', 40));
|
||||
if Found = 0 then
|
||||
begin
|
||||
Writeln('Nessuno slave ha risposto.');
|
||||
Writeln('Controlla: baud del modulo, scambio A/B del RS485, alimentazione,');
|
||||
Writeln('e che PlanciaConsole non tenga gia'' aperta la porta.');
|
||||
end
|
||||
else
|
||||
Writeln(Format('Trovati %d slave.', [Found]));
|
||||
|
||||
finally
|
||||
Bus.Free;
|
||||
end;
|
||||
|
||||
except
|
||||
on E: Exception do
|
||||
Writeln('ERRORE: ' + E.Message);
|
||||
end;
|
||||
|
||||
Writeln;
|
||||
Write('Premi INVIO per chiudere.');
|
||||
Readln;
|
||||
end.
|
||||
Reference in new issue
Block a user