リアルタイムの温度を記録しているRaspbian(Debian派生)を実行しているRaspberry Piを持っています。その一環として、数秒以内の精度の時計が必要です。サーバーが稼働している通常の操作では、Raspberry Piは定期的にネットワークを介してNTPサーバーに接続し、ローカルクロックが正しい時刻にかなり近いことを確認します。そのレベルの私のアプリケーションでは精度は問題ありません。RaspberryPiには、サーバーのシャットダウン時または電源切断時の時刻を維持するためのバッテリー駆動の時計がないため、最初に起動したとき、インターネット接続を確立して正しい時刻を取得するまで、システム時刻は正しくありません。 NTP経由。
私が問題を発見したのは、停電があり、Raspberry Piが電源なしで一定時間オフになっていて、しばらくしてから電源が回復したとき(たとえば30分)です。 Piが起動し、アプリが起動して温度の記録が再開されますが、Piの時計が正しくないため、間違ったタイムスタンプで温度が記録されます。どういうわけか、最後の既知の時間を保存し、そこから取得したようです(再起動時にエポック時間にリセットされません)。最終的に、Piが接続されているLANが回復し、インターネット接続が回復すると、Piは(NTPを介して)時刻を修正しますが、その前に、記録されるタイムスタンプが不正確になります。
この問題を解決するための最善の行動方針を見つけようとしていますか?
メンテナンス上の理由から、バッテリーでバックアップされたアドオンクロックを追加したくありません(これは本質的に組み込みデバイスであり、ユーザーが簡単にアクセスできないため、バッテリーを交換する必要はありません)。
時刻がネットワークから正確に取得されたことがわかるまで、アプリに温度の記録を延期させたいのですが、その状態を検出する方法すらわかりません。 NTPサーバーから時刻が更新され、正しい時刻がいつ更新されたかを知る方法を知っている人はいますか?
私のアプリは、起動時にスクリプトを実行することで起動します。
この問題を解決する方法について他に何かアイデアはありますか?
私が自分のソリューションで必要だと判断したのは、システム時刻が正確なときに解決されるpromiseを返す関数でした。次に、起動時にその関数を呼び出し、そのフィードバックを使用して、温度の記録をいつ開始するかを知ることができます。そのために、ntpClientを使用して自分で正確な時刻を取得し、ローカルシステムの時刻と比較することにしました。 2つが希望の精度内にある場合、私は約束を解決することができます。そうでない場合は、タイマーを設定して時刻を再確認し、最終的にローカルシステム時刻が十分に正確になるまで続けます。これまでのところ、これはいくつかの停電によってうまく機能しました(これは、不正確な時間の問題が最初に発見された場所です)。
これが私が使ったコードです:
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
};
そして、私はこれを次のように使用します:
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
});
Piを再起動するたびに(数秒以上かかります)、時間の延長/短縮によってntp
が補うことができる時間よりも長く時計がオフになります(つまり、回転、これはリアルタイムクロックが1日1秒程度遅い、または速いなど、わずかにずれている時計を修正する場合、ntp
は時計を設定する必要があります。
したがって、最も簡単なのは、温度測定プログラムを開始するスクリプトで、最初にntpdate
または同等のものを呼び出すことです。このスクリプトは、取得した値の距離に応じて日付またはスルーを設定します。したがって、ntpdate
は、時計がntp
によってすでに正しい値に近く設定されている場合でも、問題を引き起こすことはありません。再起動せずにこのスクリプトを使用して再起動した場合。
ntpd
(ntpd
パッケージから)を使用してクロックの同期を維持している場合は、タイムオフセットに関係なく起動時にクロックをステップするように構成できます。他のパッケージを使用している場合は、質問でアドバイスしてください。
デフォルトでは、ntpd
は1000秒未満の場合にのみクロックをジャンプしますが、Debianの実装は-g
フラグを提供して制限をオーバーライドし、オフセットからのステップ実行を許可します。 (これはいい。)
また、-x
フラグは、最大600秒の間隔でステップするのではなく、時間を強制的にスルーします。あなたはnotこのセットが欲しいです。 (Debianのデフォルトでは、このフラグは設定されていません。これは良いことです。)
/etc/default/ntp
を確認してください。これには、フラグを設定する次の行だけが含まれているはずです。
NTPD_OPTS='-g'
時間が同期される前にロギングプロセスが開始されることを説明するために質問が更新されたため、同期がいつ完了するかを識別するためにntpstat
を使用することをお勧めします。
非同期
ntpstat; printf "\nexit status %s\n" $?
unsynchronised
polling server every 8 s
exit status 1
同期
ntpstat; printf "\nexit status %s\n" $?
synchronised to NTP server (203.0.113.22) at stratum 3
time correct to within 93 ms
polling server every 1024 s
exit status 0
ビジーループの例
until ntpstat; do echo waiting; sleep 30; done; date
ntpstat
がなくインストールできない場合は、おそらくntpq -c sysinfo
から情報を入手できます。
ntpq -c sysinfo
associd=0 status=0615 leap_none, sync_ntp, 1 event, clock_sync,
system peer: server2.contoso.com:123
system peer mode: client
leap indicator: 00
stratum: 3
log2 precision: -20
root delay: 26.453
root dispersion: 28.756
reference ID: 203.0.113.22
reference time: e08863a7.fb7d83bf Thu, May 16 2019 23:33:11.982
system jitter: 3.227792
clock jitter: 1.178
clock wander: 0.012
broadcast delay: -50.000
symm. auth. delay: 0.000
もう1つのオプションは、ntpq -c peers
の出力を解析して、階層が16から離れるのを監視することです。
NTPに付属のntp-wait
プログラムを確認してください。あなたはそれを実行し、あなたの時計が同期されるまで待って、そして終了します。 (または、最終的にあきらめて、エラーで終了します。)これを使用して、クロックが同期されるまでスクリプトが開始されないようにすることができます。
ntpq -p
やntpq -c rv
などを実行し、出力を解析して時計のステータスを確認することもできます。確かに、ntp-wait
はまさにそれを行う短いPerlスクリプトです。
NTPクライアントはNTPサーバーでもあり、現在のステータスをクライアントに報告できます。
私のボックスでは実際にchrony
ではなくntpd
を使用しています。現在のステータスを尋ねると、次のように表示されます。
[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
「システム時間」フィールドから、プロセスはクロックが十分に正確かどうかを判断できます。人間が使用することを目的としたコマンドの出力を解析しなくても、これらの値を直接読み取ることができると確信していますが、詳細はわかりません。
あなたは実際に問題を分析していません、そしてあなたが示唆していることがntp
の実行を検出できればうまくいくでしょうが、それは不必要に複雑です。
ロギングの頻度やロギング時間をどのように設定するかはわかりません。
システムは時間を節約します(/etc/fake-hwclock.data
)cron
タスクによって毎時間更新する必要があります。この結果、記録された時刻は常に実際の時刻よりも前になります(停止時間に0から1時間を加えた時間)。
この結果、ログファイルが定期的に作成され、停電時に「ギャップ」が生じる可能性があります。 ntp
が開始されると、時刻が修正されるため、修正には「ギャップ」があります。
停止後、時間が逆行しているように見える可能性があります(cron
が時間を節約した後にログエントリがある場合)。
あなたがしなければならないのは、ログファイルを後処理し、ギャップ/時間の変化を検出し、ファイルを修正することです。タックを簡単にするために、ログオンプロセスの起動に個別のメッセージを記録します。
それをすべて言っても、私はRTC(eBayで数ドルのみ))を取得します。これは、1ドルのバッテリーを使用して何年も時間を維持します。これは私がPiで使用するものです。
これは非常に便利ですスレッド、実際の既存の問題を強調表示(多くの場合、解決されないか、ひどく解決され、堅牢な方法ではありません)何百万もの「IOT」システムのユーザビリティとメンテナンスコストに影響します(まもなく数十億)。上記のいくつかの素敵なntpヒント、NTPは便利ですが、パーティーに遅れることがあります:
DX=`date +%s.%4N`
DB=`awk -v DX=$DX '{printf "%.0f",DX-$1}' /proc/uptime`
c=$(( $c+1 ))
echo "${DX} DB=$DB $$ $c $MSG"
Marcellの方法:uptimeを引く(/ proc/uptimeの最初のフィールド)NIXの日付から($ DX)=は(DB)の起動時間です(理論的には1だけ変化する可能性があるため、丸めの、しかしそれを見たことがない)。最近記録されたいくつかの例filename = date ... $ DB。$$。$ c.myapp.gpg:
2019-05-16.200713.1558037061.1207.6.myapp.gpg
2019-05-16.201348.1558037061.1207.7.myapp.gpg
NTPが何日ものデータロギング(ネットワークの問題)後に同期するように管理されていても)ログに記録されたデータは安全で、データの表示/分析中に$ DB NTP syncであるため、ジャンプします。NTPが何らかの理由で同期しない場合でも、データはログに記録され、一貫性があります(ただし)その場合、正確な時間オフセットを常に回復できるとは限りません。時間同期は、多くの場合、記録されたデータ(光センサー、または他のシステムによって記録されたイベント/サウンドなど)から確立できます。
注:$$は起動後に異なる可能性があり、$ DBも異なるため、$ cカウンターは継続性を確認します。
可能な限り多くの状況で合理的に機能する堅牢なシステム(ベイビーベイビーは野生の世界)を実装することが私たちの仕事です(ますます多くのシステムがモバイルであり、トンネルで起動したときにネットワークがすぐに利用できる場合とできない場合があることを考慮してください、または他の場所)。
壊れやすいシステムを実装したり、問題を封じ込める代わりにエスカレートさせたりするのは専門的ではありません(1つのサブシステムはうまく機能しません。たとえば、ネットワークまたはNTP=>他のサブシステムもフリーズします=>このようなプラクティスは大規模に適用されますシステムは循環依存関係、信頼できないシステム、またはシステム全体が崩壊する可能性があると考えられる最悪の場合に適切なレシピを作成します。不要な依存関係を追加するのは専門家ではありません(=管理者が問題を解決するなど、他の誰かに任せてください。 tケア)例:ブート直後にネットワークとゲートウェイおよびタイムサーバーが常に稼働し、到達可能であること、およびRTC=数十または数百のデバイスのバッテリの交換)が必要であるもちろん、信頼できるネットワークとNTPシステムは大歓迎です。すべてのガジェットのバッテリーRTCは、通常、利益よりも苦痛です。
一般に、データは(システムを不必要にフリーズするのではなく)可能な限り何らかの方法でログに記録する必要があります。
もちろん、さらなる改善が可能です(/ bin/shを.pyまたは.cまたは...に移植することもできます)。通常、UTCでログインし、TZローカライズ形式で表示することをお勧めします(現地時間は元に戻る可能性があり、問題が発生する場合があります)。 Bashは素晴らしいですが、/ bin/shdashまたはbusyboxShellでうまく機能させるのが簡単な場合は、bash構文を避けるのが親切です... bashの「関数」ノイズワードは避けてください。