for while if sono... padelle, certamente bisogna conoscere come si usano, ma come maneggiarle per arrivare a un certo risultato è un altro paio di maniche, e riguarda appunto la logica.
Provare a usare più o meno a tentativi le diverse padelle non porta ad alcun risultato se prima non è definita la procedura che si vuole seguire.
In particolare dalla descrizione secondo me emergono almeno sei stati/situazioni in cui il sistema si può trovare, e questi devono essere ben chiari ed esplicitati prima di pensare di buttare giù una sola istruzione:

NOTA: i numeri degli stati non indicano alcuna sequenza (che è quella indicata dalle frecce) ma sono solo nomi per identificarli.
Già solo con questa schematizzazione (che descrive bene l'ordine temporale in cui avvengono o ci si aspettano le cose) si nota che il LED deve blinkare negli stati 1 e 4, e che l'uscita deve essere attiva negli stati 1, 2 e 5. Questa è già una buona base di partenza e semplifica tantissime cose (e non stiamo ancora parlando di istruzioni).
Ad ogni ciclo di esecuzione del programma (che avviene nella funzione loop) va gestito lo stato attualmente "valido", che banalmente si può identificare con il valore di una variabile 'fase':
// gestione stato attivo
if (0 == fase)
{
...
}
else if (1 == fase)
{
...
}
else if (2 == fase)
{
...
}
else if (3 == fase)
{
...
}
else if (4 == fase)
{
...
}
else if (5 == fase)
{
...
}
Dopo questa struttura di selezione in cui verranno valutati gli eventi attesi, si possono scrivere le gestioni del blink e dell'uscita, che in ogni momento dipendono solo dallo stato attivo. Ad esempio:
// gestione blink
if (1 == fase || 4 == fase)
digitalWrite(LED_PIN, (millis() % 500) < 250);
else
digitalWrite(LED_PIN, ...livello off...);
// aggiornamento uscita
if (1 == fase || 2 == fase || 5 == fase)
digitalWrite(USCITA_PIN, ...livello on...);
else
digitalWrite(USCITA_PIN, ...livello off...);
Già da qui si vede che con questa impostazione tutto il ragionamento di un for per il blink dentro cui aspettarsi il pulsante ecc ecc è inutilmente complicato.
È sufficiente in ogni stato occuparsi solo di riconoscere gli eventi che gli interessano, fare quello che eventualmente serve fare quando sono riconosciuti, e impostare un nuovo valore nella variabile 'fase' se si deve cambiare stato:
if (0 == fase)
{
if (chiaveInserita())
{
resetTimer(); // salvo tempo istante di inserimento
fase = 4; // stato valutato al prossimo ciclo di loop
}
}
Si possono poi scrivere le piccole funzioncine di servizio per svolgere i compiti hardware di base, ad esempio:
bool chiaveInserita() {
return digitalRead(PIN_CHIAVE) == ...livello premuto...;
}
void resetTempo() {
tempoInizioConteggio = millis();
}
bool timeout(uint32_t delta) {
return millis() - tempoInizioConteggio >= delta;
}
Tutto questo è tremendamente più semplice di quanto stavi iniziando a pensare con cicli innestati ecc, permette di scrivere praticamente tutta la logica in italiano con fasi, e passaggi da una fase all'altra, ben espliciti, ed è un metodo generale e universale per affrontare qualsiasi compito di automazione. Come effetto collaterale permette anche di scrivere N processi paralleli indipendenti che vengono eseguiti "contemporaneamente" (a livello di processo, perché poi fisicamente la CPU esegue una sola istruzione per volta).