Como saber durante o início do sistema quando a hora do sistema se torna correta a partir do NTP

5

Eu tenho um Raspberry Pi executando o Raspbian (derivado do Debian) que está gravando temperaturas em tempo real. Como parte disso, preciso de um relógio com precisão de alguns segundos. Em operação normal quando o servidor está ativo, eu entendo que o Raspberry Pi regularmente se conecta através da rede a um servidor NTP para garantir que o relógio local esteja razoavelmente próximo do horário correto. Esse nível de precisão é bom para minha aplicação. O Raspberry Pi não tem um relógio alimentado por bateria para manter o tempo quando o servidor é desligado ou depowered assim quando ele inicia pela primeira vez a hora do sistema não está correta até que estabeleça uma conexão de internet e obtenha o horário correto via NTP.

Onde eu encontrei um problema é quando há uma queda de energia e o Raspberry Pi está desligado por algum período de tempo sem energia, então volta a funcionar mais tarde (digamos 30 minutos). O Pi inicia, meu aplicativo inicia e começa a registrar a temperatura novamente, mas o relógio no Pi não está correto, então ele registra as temperaturas com o timestamp errado. Parece que de alguma forma, salvou o último tempo conhecido e pega de lá (não redefine a hora da época quando ele reinicia). Eventualmente, quando a LAN em que o Pi está se recuperando e recuperando a conectividade com a Internet, o Pi irá corrigir seu tempo (via NTP), mas antes que isso aconteça, eu tenho timestamps imprecisos que são gravados.

Estou tentando descobrir qual é o melhor curso de ação para resolver esse problema?

Por motivos de manutenção, prefiro não adicionar um relógio complementar com bateria (não quero que ninguém precise substituir uma bateria, pois é essencialmente um dispositivo incorporado, não facilmente acessível ao usuário).

Estou disposto a fazer com que meu aplicativo adie as temperaturas de gravação até eu saber que o tempo foi recuperado com precisão da rede, mas nem sei como detectar esse estado. Alguém sabe como saber quando o tempo foi atualizado a partir de um servidor NTP e agora está correto?

Meu aplicativo é iniciado com a execução de um script na inicialização.

Alguma outra ideia de como resolver este problema?

    
por jfriend00 31.05.2015 / 20:01

7 respostas

0

O que eu decidi que queria na minha solução era uma função que retornaria uma promessa que resolveu quando a hora do sistema estava correta. Eu poderia então chamar essa função na inicialização e nos seus comentários para saber quando começar a gravar as temperaturas novamente. Para fazer isso, decidi usar um ntpClient para obter o tempo exato e compará-lo com a hora do sistema local. Quando os dois estão dentro de uma precisão desejada, posso resolver a promessa. Quando não estão, configuro um temporizador e reavalio a hora, continuando até que, eventualmente, a hora local do sistema se torne precisa o suficiente. Portanto, até agora, isso funcionou muito bem com várias quedas de energia (que é onde o problema com o tempo impreciso foi descoberto inicialmente).

Aqui está o código que usei:

const Promise = require('bluebird');
const ntpClient = Promise.promisifyAll(require('ntp-client'));
const log = require('./log');

function Decay(startT, maxT, decayAmount, decayTimes) {
    // startT is initial delay (e.g. 5 seconds)
    // maxT is the max delay this ever returns (e.g. 5 minutes)
    // decayAmount is how much to decay when a threshold is crossed (e.g. increase by 0.5)
    // decayTimes is how many invocations should trigger a decayAmount (e.g. every 5 times)

    // example: var d = new Decay(5000, 5*60*1000, .5, 5);
    // each 5 seconds, to a max of 5 minutes, getting 50% longer every 5 invocations

    // make sure decayTimes is at least 1 and not negative
    decayTimes = Math.max(decayTimes, 1);
    var num = 0;
    var currentDelay = startT;
    var start = Date.now();

    this.val = function() {
        var elapsed = Date.now() - start;
        // if evenly divisible by decayTimes, then bump the increment
        if (num !== 0 && num % decayTimes === 0) {
            currentDelay = Math.min(Math.round((1 + decayAmount) * currentDelay), maxT);
        }
        ++num;
        return currentDelay;
    };
}

function checkSystemTime(precision) {
    precision = precision || 5000;
    return ntpClient.getNetworkTimeAsync("pool.ntp.org", 123).then(function(ntpTime) {
        return Math.abs(ntpTime.getTime() - Date.now()) <= precision;
    });
}

function waitForAccurateSystemTime(precision, howLong) {
    var start = Date.now();
    // retry starts every 5 seconds, repeats 5 times, then increases by 50% 
    //   up until longest retry time of once every 15 minutes
    var decay = new Decay(5000, 15*60*1000, .5, 5);
    var errCntr = 0;
    var inaccurateCntr = 0;

    function logRetries() {
        // only log anything if there were more than five consecutive errors
        if (errCntr > 5 || inaccurateCntr > 0) {
            log(7, "Time synchronization issue, errCntr = " + errCntr + ", inaccurateCntr = " + inaccurateCntr);
        }
    }

    return new Promise(function(resolve, reject) {

        function check() {
            checkSystemTime(precision).then(function(accurate) {
                if (accurate) {
                    resolve(true);
                } else {
                    ++inaccurateCntr;
                    again();
                }
            }, again);
        }

        function again() {
            ++errCntr;
            if (errCntr == 10) {
                // only log once here that we're in a retry loop on 10th retry
                // final logging will be done later
                log(7, "In retry loop waiting for system time to agree with ntp server time");
            }
            // if we're only supposed to go for a certain amount of time, then check to see
            // if we exceeded that amount of time.  If not, set timer for next decay() value.
            if (!howLong || Date.now() - start <= howLong) {
                setTimeout(check, decay.val());
            } else {
                var err = "timeout waiting for accurate system time";
                log(7, err);
                reject(err);
            }
        }

        check();
    }).then(function(result) {
        logRetries();
        return result;
    }).catch(function(err) {
        logRetries();
        throw err;
    });
}

module.exports = {
    checkSystemTime: checkSystemTime,
    waitForAccurateSystemTime: waitForAccurateSystemTime,
    Decay: Decay
};

E eu uso isso assim:

const validTime = require("./valid-time");
validTime.waitForAccurateSystemTime(2 * 60 * 1000, 0).then({
    // start operation here that requires accurate system time
}).catch({
    // abort process, no accurate system time could be found
});
    
por 11.06.2017 / 18:25
3

Toda vez que você reiniciar o seu Pi (o que leva mais do que alguns segundos), o relógio estará desligado por mais do que o ntp pode compensar alongando / diminuindo o tempo (ou seja, girando, o que só é bom para Corrigindo um relógio que está apenas ligeiramente desligado, como causado por um relógio em tempo real ser lento ou rápido por um segundo ou mais por dia), ntp tem que acertar o relógio.

Então, o que pode ser mais fácil é ter um script que inicie seu primeiro programa de medição de temperatura chamada ntpdate ou equivalente, que define a data ou manda de acordo com a distância do valor recuperado. Por conseguinte, ntpdate não interrompe as coisas se o relógio já estiver definido perto de ser corrigido por ntp , p. se você reiniciar através deste script sem ter uma reinicialização.

    
por 31.05.2015 / 20:19
3

Desde que você esteja usando ntpd (do pacote ntpd ) para manter seu relógio sincronizado, ele pode ser configurado para acelerar o tempo de inicialização, independentemente do deslocamento de tempo. Se você estiver usando algum outro pacote, por favor, informe em sua pergunta.

Por padrão, ntpd apenas saltaria o relógio se fosse menos de 1000 segundos, mas a implementação do Debian fornece o -g para substituir a limitação e permitir a mudança a partir de qualquer deslocamento. (Isso é bom.)

Além disso, o sinalizador -x forçará o giro do tempo em vez de avançar para intervalos de até 600 segundos; você não quer este conjunto. (O padrão do Debian não define este sinalizador, o que é bom.)

Verifique o /etc/default/ntp , que deve ter apenas esta linha configurando os sinalizadores:

NTPD_OPTS='-g'
    
por 31.05.2015 / 20:23
3

Outra opção pode ser analisar a saída de "ntpq -c peers" para observar o stratum se afastar de 16.

    
por 01.06.2015 / 04:19
2

Confira o programa ntp-wait que vem com o NTP. Você o executa, aguarda até que o relógio seja sincronizado e sai. (Ou, eventualmente, desiste e sai com um erro.) Você pode usá-lo para impedir que o script seja iniciado até que o relógio seja sincronizado.

Você também pode executar algo como ntpq -p ou ntpq -c rv e analisar a saída para verificar o status do seu relógio. De fato, ntp-wait é um script Perl curto que faz exatamente isso.

    
por 01.06.2015 / 09:30
1

Seu cliente NTP também é um servidor NTP e pode relatar seu status atual para seus clientes.

Na verdade, estou usando chrony em vez de ntpd em minhas caixas e, quando eu solicito seu status atual, ele diz o seguinte:

[axa@enyo ~]$ chronyc tracking
Reference ID    : p.q.s.t (xxx.yyy.zzz)
Stratum         : 4
Ref time (UTC)  : Sun May 31 22:35:34 2015
System time     : 0.000630264 seconds slow of NTP time
Last offset     : +0.000047504 seconds
RMS offset      : 0.023269517 seconds
Frequency       : 6.462 ppm slow
Residual freq   : -0.023 ppm
Skew            : 0.225 ppm
Root delay      : 0.031594 seconds
Root dispersion : 0.025155 seconds
Update interval : 1035.3 seconds
Leap status     : Normal

No campo "Horário do sistema", seu processo pode determinar se o relógio é suficientemente preciso. Tenho certeza que você poderia ler esses valores diretamente sem ter que analisar a saída de um comando destinado ao uso humano, mas eu não sei os detalhes.

    
por 01.06.2015 / 00:54
1

Você ainda não analisou o problema, e embora o que você está sugerindo funcione se você puder detectar quando ntp é executado, é desnecessariamente complexo.

Você não diz com que frequência registra ou como define o tempo de registro.

Seu sistema economiza tempo (em /etc/fake-hwclock.data ) que deve ser atualizado por hora por uma tarefa cron . O resultado disso é que o tempo registrado sempre será antes do tempo real (no momento da interrupção mais de 0 a 1 hora).

O resultado final disso é que você terá um arquivo de log com horários regulares e um possível "intervalo" durante a falta de energia. Quando ntp entra, corrige a hora, então você terá "gap" para a correção.

É possível que, após uma interrupção, o tempo possa parecer retroceder (se houver entradas de registro depois que cron salvar o tempo).

Tudo o que você precisa fazer é pós-processar o arquivo de log, detectar as lacunas / mudanças de horário e corrigir o arquivo.  Para facilitar as tarefas, eu registraria uma mensagem distinta na inicialização do processo de logon.

Tendo dito tudo isso, eu acabei de obter um RTC (apenas alguns dólares no eBay), que irá manter o tempo por anos usando uma bateria de US $ 1, que é o que eu uso no meu Pi.

    
por 01.06.2015 / 03:56