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,195 @@
|
||||
unit uAlarmSound;
|
||||
|
||||
{
|
||||
Suoni di allarme delle spie.
|
||||
|
||||
Usa MCI (mciSendString), che su Windows suona MP3 e WAV senza librerie
|
||||
esterne e senza mettere un componente sul form. La riproduzione non blocca:
|
||||
"play" torna subito e il suono continua per conto suo, quindi una plancia
|
||||
che suona resta comandabile.
|
||||
|
||||
Ogni file suona su un proprio alias, aperto la prima volta e tenuto aperto:
|
||||
riaprirlo a ogni allarme costerebbe qualche decimo di secondo, e un cicalino
|
||||
deve partire nell'istante in cui la spia si accende. Due allarmi diversi si
|
||||
sovrappongono; lo stesso allarme che si ripete riparte da capo.
|
||||
}
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
Winapi.Windows, Winapi.MMSystem, System.SysUtils, System.Classes,
|
||||
System.Generics.Collections;
|
||||
|
||||
type
|
||||
TAlarmPlayer = class
|
||||
private
|
||||
/// Percorso del file -> alias MCI aperto per quel file.
|
||||
FOpen: TDictionary<string, string>;
|
||||
FNextAlias: Integer;
|
||||
FLastError: string;
|
||||
function Command(const ACommand: string): Boolean;
|
||||
/// Comando MCI che torna una risposta ('status ... mode' e simili).
|
||||
function Query(const ACommand: string): string;
|
||||
function AliasFor(const AFileName: string): string;
|
||||
public
|
||||
constructor Create;
|
||||
destructor Destroy; override;
|
||||
/// Suona il file, ripartendo da capo se sta gia' suonando. False se non
|
||||
/// si e' potuto suonare; il motivo sta in LastError.
|
||||
/// Con ALoop il suono riparte da solo quando finisce, finche' non lo si
|
||||
/// ferma: un allarme deve continuare a chiamare, non suonare una volta.
|
||||
function Play(const AFileName: string; ALoop: Boolean = False): Boolean;
|
||||
/// Vero se quel file sta suonando in questo momento. Serve a far
|
||||
/// ricominciare l'allarme sui dispositivi che non sanno ripetere da soli.
|
||||
function IsPlaying(const AFileName: string): Boolean;
|
||||
/// Ferma un suono solo, lasciando suonare gli altri: serve quando una
|
||||
/// spia si spegne mentre altri allarmi sono ancora attivi.
|
||||
procedure Stop(const AFileName: string);
|
||||
/// Zittisce tutto: si usa lasciando la plancia o chiudendo.
|
||||
procedure StopAll;
|
||||
property LastError: string read FLastError;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
const
|
||||
/// Oltre questo numero di suoni diversi aperti, il piu' vecchio si chiude:
|
||||
/// una plancia con decine di allarmi non deve tenere aperti decine di
|
||||
/// dispositivi MCI.
|
||||
MAX_OPEN = 8;
|
||||
|
||||
{ TAlarmPlayer }
|
||||
|
||||
constructor TAlarmPlayer.Create;
|
||||
begin
|
||||
inherited Create;
|
||||
FOpen := TDictionary<string, string>.Create;
|
||||
end;
|
||||
|
||||
destructor TAlarmPlayer.Destroy;
|
||||
begin
|
||||
StopAll;
|
||||
FOpen.Free;
|
||||
inherited;
|
||||
end;
|
||||
|
||||
function TAlarmPlayer.Command(const ACommand: string): Boolean;
|
||||
var
|
||||
Err: MCIERROR;
|
||||
Buf: array[0..255] of Char;
|
||||
begin
|
||||
Err := mciSendString(PChar(ACommand), nil, 0, 0);
|
||||
Result := Err = 0;
|
||||
if Result then
|
||||
Exit;
|
||||
if mciGetErrorString(Err, Buf, Length(Buf)) then
|
||||
FLastError := Buf
|
||||
else
|
||||
FLastError := Format('errore MCI %d', [Err]);
|
||||
end;
|
||||
|
||||
function TAlarmPlayer.AliasFor(const AFileName: string): string;
|
||||
var
|
||||
Key: string;
|
||||
Old: TPair<string, string>;
|
||||
begin
|
||||
Key := LowerCase(AFileName);
|
||||
if FOpen.TryGetValue(Key, Result) then
|
||||
Exit;
|
||||
|
||||
if FOpen.Count >= MAX_OPEN then
|
||||
for Old in FOpen do
|
||||
begin
|
||||
Command('close ' + Old.Value);
|
||||
FOpen.Remove(Old.Key);
|
||||
Break;
|
||||
end;
|
||||
|
||||
Inc(FNextAlias);
|
||||
Result := Format('plancia_all%d', [FNextAlias]);
|
||||
// Il tipo va detto: senza, MCI sceglie dall'estensione e sui .mp3 di certe
|
||||
// installazioni sbaglia dispositivo.
|
||||
if not Command(Format('open "%s" type mpegvideo alias %s',
|
||||
[AFileName, Result])) then
|
||||
if not Command(Format('open "%s" alias %s', [AFileName, Result])) then
|
||||
Exit('');
|
||||
FOpen.Add(Key, Result);
|
||||
end;
|
||||
|
||||
function TAlarmPlayer.Query(const ACommand: string): string;
|
||||
var
|
||||
Buf: array[0..127] of Char;
|
||||
begin
|
||||
if mciSendString(PChar(ACommand), Buf, Length(Buf), 0) = 0 then
|
||||
Result := Buf
|
||||
else
|
||||
Result := '';
|
||||
end;
|
||||
|
||||
function TAlarmPlayer.IsPlaying(const AFileName: string): Boolean;
|
||||
var
|
||||
Alias: string;
|
||||
begin
|
||||
Result := FOpen.TryGetValue(LowerCase(AFileName), Alias) and
|
||||
SameText(Query('status ' + Alias + ' mode'), 'playing');
|
||||
end;
|
||||
|
||||
function TAlarmPlayer.Play(const AFileName: string; ALoop: Boolean): Boolean;
|
||||
var
|
||||
Alias: string;
|
||||
begin
|
||||
FLastError := '';
|
||||
if AFileName = '' then
|
||||
Exit(False);
|
||||
if not FileExists(AFileName) then
|
||||
begin
|
||||
FLastError := 'file non trovato';
|
||||
Exit(False);
|
||||
end;
|
||||
|
||||
Alias := AliasFor(AFileName);
|
||||
if Alias = '' then
|
||||
Exit(False);
|
||||
// "from 0" fa ripartire da capo un allarme che sta gia' suonando: meglio
|
||||
// risentirlo dall'inizio che non sentire niente. Con "repeat" il
|
||||
// dispositivo ripete da solo; non tutti lo accettano, e a quelli si rimedia
|
||||
// facendolo ripartire da fuori quando risulta fermo (vedi IsPlaying).
|
||||
Result := False;
|
||||
if ALoop then
|
||||
Result := Command(Format('play %s from 0 repeat', [Alias]));
|
||||
if not Result then
|
||||
Result := Command(Format('play %s from 0', [Alias]));
|
||||
if not Result then
|
||||
begin
|
||||
// Il dispositivo puo' essere morto (cuffie staccate): si riapre da capo.
|
||||
Command('close ' + Alias);
|
||||
FOpen.Remove(LowerCase(AFileName));
|
||||
Alias := AliasFor(AFileName);
|
||||
if Alias <> '' then
|
||||
Result := Command(Format('play %s from 0', [Alias]));
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TAlarmPlayer.Stop(const AFileName: string);
|
||||
var
|
||||
Alias: string;
|
||||
begin
|
||||
// Il dispositivo resta aperto: se l'allarme torna, riparte senza il ritardo
|
||||
// dell'apertura.
|
||||
if FOpen.TryGetValue(LowerCase(AFileName), Alias) then
|
||||
Command('stop ' + Alias);
|
||||
end;
|
||||
|
||||
procedure TAlarmPlayer.StopAll;
|
||||
var
|
||||
Alias: string;
|
||||
begin
|
||||
for Alias in FOpen.Values do
|
||||
begin
|
||||
Command('stop ' + Alias);
|
||||
Command('close ' + Alias);
|
||||
end;
|
||||
FOpen.Clear;
|
||||
end;
|
||||
|
||||
end.
|
||||
Reference in new issue
Block a user