NTPd/SNTPdバグレポート 最終更新日:2023年3月2日
2023年2月28日 ふとGPSNTPサーバにアクセスして時刻同期した装置の内、日付も確認出来る装置で、日付表示が2023年03月01日になっている事を発見しました。顕在していたバグの潜在化です。多分日付計算を間違えているようです。デバッグ開始です。
NTPdでは日付を1900年1月1日0時(UTP)を基準とし、64ビットの符号無し固定小数点で扱う事になっています。経過日時の計算を間違えると1日ズレてしまうことになります。詳細はこちらを参照してください。
GPSの時刻解釈を間違えていればそれ以前の問題ですが、GPSの日付解釈は間違っていないことを確認しました。また、時刻自体に問題が無いことも確認しました。
となると、GPSから取得した時刻と基準日1900年1月1日0時(UTP)からの経過時間の計算で日数計算に間違いがあり、今回の問題になったと思われます。ちょうど閏年対策する2月28日〜3月1日の問題です。
3月1日、バグの残っているNTPdに対して時刻同期をしたところ3月2日になってしまいました。1970年1月1日を基準にNTPクライアントからの問合せ日からの日数計算をし、それに1900年1月1日から1970年1月1日までの日数を足して64ビットの符号無し固定小数点に変換して、NTPクライアントに回答しているのですが、この日数計算の閏年加算で間違いがあり、問合せ日時によって1日ズレることが判りました。
更新したスケッチを公開します。
//---------------NTP since 1900/01/01 retouch2020/12/25 bugfix 2023/03/01 // EXCELで2023年3月1日の1970/1/1からの経過日数(days)を計算したところ19417だった。 // 以下のプログラムではdaysの値が19418となり 1日ズレることを確認した // アルゴリズムを変更する事にしました。 static unsigned long int numberOfSecondsSince1900Epoch(uint16_t y, uint8_t m, uint8_t d, uint8_t h, uint8_t mm, uint8_t s,float timezone) { //2036年問題が存在する。unsigned long intの桁溢れが発生 //桁溢れが発生したら受信側が対応する必要がある。サーバ側では64bit化するべきだが動かなくなる。 //timezoneをfloat扱いにした /* int leapAdjustment = 0; if (y >= 1970) { y -= 1970; leapAdjustment = 2; // 1970 年はうるう年ではありませんでした。 } uint16_t days = d; for (uint8_t i = 1; i < m; ++i) days += pgm_read_byte(daysInMonth + i - 1); //経過日数 */ /* if (m > 2 && y % 4 == 0) ++days; */ //if (m > 2 && (y + leapAdjustment) % 4 == 0) ++days; // LM: 弱いけど今のところ大丈夫 3月以降で閏年なら1日足す //days += 365 * y + (y + 3) / 4 - 1; //今回修正するアルゴリズムは、1970年1月1日からの日数計算の見直しです。 //日付、経過月日数、経過年日数(閏年無視)、閏年補正の 和-1 を経過秒数にして返します。 uint16_t days = d; for (uint8_t i = 1; i < m; ++i) days += pgm_read_byte(daysInMonth + i - 1); //経過月日数 days += (y-1970)*365; //経過年日数(閏年無視) if(m>2){ days += (y/4 - y/100 + y/400 -( 1970/4 - 1970/100 + 1970/400)); }else{ --y; days += (y/4 - y/100 + y/400 -( 1970/4 - 1970/100 + 1970/400)); } long offsetTime = long((float(h)- timezone) * 3600.0); return (days-1)*24L*3600L + offsetTime + mm*60L + s + seventyYears; //return days*24L*3600L + offsetTime + mm*60L + s; } |
/* * 2023/3/1 T.Wanibe * v3.10.2の修正点 * NTPの経過日数計算のアルゴリズムを修正しました。明らかなバグです。 * v3.10.1の修正点: * [Return]ボタンを追加しました * v3.10.0の修正点: * WiznetのNICが無反応状態になってしまった場合のreboot対策を追加。時刻表示は出来ているのにpingが通らないと云う状況に陥ることを確認したので * EthernetClass::hardwareStatus()を利用して健康チェックする。 * v3.9.3の修正点: * 稼働時間をブラウザ表示出来るように改良 * v3.9.2の修正点: * 温度・気圧をグラフ表示するように改造 * v3.9.1の修正点: * NTPqueryを処理した回数を内部保持することにしました。ただ、EEPROMに書き込むまではしていません * v3.9.0の修正点: * 温度センサBMP280を追加 * v3.8.2の修正点: * データ抜けの原因追求のための作業 * pageIntroductionの表示をModbus版と同じように変更 * v3.8.1の修正点: * ・チャート表示の為にCDNサーバからChartライブラリをLoadして使っています。 * 最近utils.jsが404NotFoundとなりグラフ表示出来なくなってしまいました。 * http://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.3.0/Chart.bundle.js * http://cdn.rawgit.com/chartjs/Chart.js/master/samples/utils.js * そこで、utils.js(4kB程度)を内部に取り込む修正を実施しました。 * 都合setup3がコールされるとutils.jsを返します。 * v3.7.6の修正点: * ・GPSの位置情報の表記変更 GoogleMapで直接使える数値表記 * v3.7.5の修正点: * ・WDTの時間を8秒(5秒から) に変更 * ・NTPパケットを受けたらLED1(白)を5秒点灯 * v3.7.4の修正点: * ・ntpパケット内の経過時刻表記を修正 * v3.7.3の修正点: * ・isnan()を使用するコードに変更 * v3.7.2の修正点: * ・WDTを組み込みました。 * ソースコードなのかライブラリなのか、数日間動いた時点でMainLoopが止まってしまう様です。しかし、時計はちゃんと進んでいるため割込は動いています。 * これを打開するにはWDTをMainLoopに組み込むべきだと解釈しました * ・WebServerへのアクセスで不用意なURLの場合にきちんと404を出すように見直した。 * v3.7.1の修正点: * setup2画面(ユーティリティ)の充実 * 少数位の時刻の精度見直し * TimeZone追加 * v3.7.0の修正点: * NTPtoolを使って調査したところ、STM32miniGPSNTPサーバは1秒以上時刻ズレが発生することが判った。 * 何度かNTPtoolを実行して判ったこととして少数位の時刻を無視していることが原因と判ったので修正した。 * v3.6.3の修正点: * setupのSubmit実行でマイコンの再起動を実行する様に変更 * v3.6.2の修正点: * :点滅の間隔を1秒に変更 * v3.6.1の修正点: * /setup頁への移行リンク追加 * v3.6の修正点: * @正常運転中アンテナを外したところ時計表示が更新されなかった。バグと思われるので内部時計で更新されるように修正 * AアンテナかGPSの受信が出来ないときのLED挙動を変更 * LED1:アンテナ受信 未受信点灯 受信消灯 * LED2: * BUILTIN_LED:実行中点滅 * NTP Time Serverの時刻表示をTM1637経由で4桁7セグLED版に変更しました。 * アンテナが外れた時にLED2が消灯するようにした。TimeServerとしては内部時計の値を返します。 * 起動時のLED&KEYの表示を変更しました。GPSの受信が曖昧な内は『HELLO』表示とし、受信後に時刻表示するように * 変更しました。 * 新たにGPSモジュールを購入しました。Crosstour CR900向けの3.5mmステレオジャックタイプです。 * こちらからは$GPのセンテンスが流れてきます。ところが、$GPZDAが流れてこないのです。多分設定で限定した * センテンスしか流さないように流さないようにしているものと考えました。 * と云う事は、$GxRMCを解析して日時情報を求めるのが正解なのだと痛感しました。修正します。 * LED2が点灯したら内部時計設定完了です。 * 設定画面がほしいので再度挑戦 * ver2のコードで問題を確認したため修正します。タイムゾンーンを単に+9してしまうとAM9に日付が変わります。 * これはダメです。正しいLocalタイム対応に修正します。※内部時計はLocalTimeに変更 * 内部時計はGMTのまま扱い、LED&KEYに表示するときに対策します。そうしないとタイムサーバとしては問題ありです。 * このコードはGPSタイムサーバを構築したくて検証中のコードです * NMEA0183のフォーマットを極力単純化して時刻取得するようにした。 * まずはNTPサーバを構築して内部時間を返すものを検討 * 当初ヘッダが$GPGGAに対応するようにしていたが、購入したGPSが$GNGGAのコードしか出さなかったので修正した。 * $GNxxxはロシア版GPSであるGLONASSのセンテンスとのようです。 * $ GNGGA、185824.00,2605.0247881、N、08014.3097082、W、4,14,0.7、-24.343、M ,,, 0.59,0402 * 36 * 185824.00 協定世界時(UTC)での時刻。 hhmmss.uu * 2605.0247881 緯度。dddmm.mmmm * N N = 北緯、South = 南緯 * 08014.3097082 経度。dddmm.mmmm * W E = 東経、W = 西経 * 4 位置特定品質 * 14 使用衛星数 * 0.7 水平精度低下率 * -24.343 アンテナの海抜高さ * M [m] * 0.59 * 0402 差動基準地点ID * * 36 チェックサム * GNGGAには日付情報が無いためGNRMCを使うべきとあった。 * $GNRMC,001903.000,A,3430.61495,N,13316.67709,E,0.00,100.54,120220,,,A,V*00 * RMC(時刻、位置、日付)$__RMC,hhmmss.ss,a, ddmm.mm ,a, ddmm.mm ,a,x.x,x.x,mmddyy,a,a,*hh<CR><LF> * $ センテンスの開始 * __RMC アドレスフィールド( 5 文字)、 __ は「 GN 」「 GP 」など * hhmmss.ss 時刻( UTC ) hh 時 mm 分 ss.ss 秒 * a ステータス * A=data valid * V=navigation receiver warming * ddmm.mm 経度 dd 度 mm.mm 分 * a 北緯( N )または南緯( S ) * ddmm.mm 経度 dd 度 mm.mm 分 * a 東経( E )または西経( W ) * x.x 対地速度( knot ) * x.x 対地コース(進路)(度) * mmddyy 日付 yy 年 mm 月 dd 日 * x.x 偏差 * a 偏差の方向 W または E * a モード * A :単独 * D :ディファレンシャルモード * E : Estimated (dead reckoning), * M :マニュアル入力 * S :シミュレーター入力 * N :無効 * *hh チェックサム( $ から * の間) * <CR><LF> センテンスの終了 * センテンス長が固定長ではないようなので一寸パスします。 * 結局GGA起動時のみ$GNZDAもチェックして日付情報を登録します。 * $GNZDA * ZDA(時刻、日付)$__ZDA,x.x,x.x,x.x,*hh<CR><LF> * $ センテンスの開始 * __ZDA アドレスフィールド( 5 文字)、 __ は「 GP 」など * hhmmss.ss 時刻( UTC ) hh 時 mm 分 ss.ss 秒 * dd 日 * mm 月 * yyyy 西暦 * hh 時(ローカルタイム) * mm 分(ローカルタイム) * *hh チェックサム( $ から * の間) * <CR><LF> センテンスの終了 * 最大131072バイトのフラッシュメモリのうち、スケッチが90296バイト(68%)を使っています。 * 最大20480バイトのRAMのうち、グローバル変数が6552バイト(31%)を使っていて、ローカル変数で13928バイト使うことができます。 */ #include <libmaple/iwdg.h> #include <SPI.h> // needed for Arduino versions later than 0018 //#include <Ethernet.h> #include <Ethernet3.h> #include <EthernetUdp3.h> // UDP library from: bjoern@cs.stanford.edu 12/30/2008 //#include <UBLOX.h> //#include <TM1638.h> #include <TM1637.h> #include <Wire.h> //3.9.0で追加 #include <BME280I2C.h> //温湿度 気圧センサ 3.9.0で追加 #include <RTClock.h> #include <EEPROM.h> #include <avr/pgmspace.h> #include <TextFinder.h> //WebSetting // Time Server Port #define ON 1 #define OFF 0 #define NTP_PORT 123 #define debug true //PIN #define W550io_Rst PA8 //NIC_Reset #define SPI1_NSS_PIN PA4 //NIC_ChipSelec #define UpdateInterval 86400 //更新間隔 //#define dataPin PB4 //LED&KEY //#define clockPin PB3 //LED&KEY //#define strobePin PA15 //LED&KEY #define CLK PB3 //pins definitions for TM1637 and can be changed to other ports #define DIO PB4 #define BUILTIN_LED PC13 #define LED1 PB8 #define LED2 PB9 #define SERIAL_RX_BUFFER_SIZE 256 #define HttpPort 80 //HTTP #define DELIMITER "," #define debug 1 #define iwdg_init_ms(N) iwdg_init(IWDG_PRE_256,((N)/8)) //設定時間をmsecとするためにマクロを用意する。 #define vers "GPS NTP STM32miniShield 3.10.2" const char FVirsion[] = "3.10.02"; byte mac[] = {0x00,0x08,0xDC,0x54,0x4D,0xD6}; //Wiznet byte ip[] = {192, 168, 0, 199}; byte dns_server[] = {192, 168, 0, 1}; byte gateway[] = {0, 0, 0, 0}; byte subnet[] = {255, 255, 255, 0}; static const int NTP_PACKET_SIZE = 48; byte packetBuffer[NTP_PACKET_SIZE]; //データを送受信するためのバッファ int8_t TimeDisp[] = {0x00,0x00,0x00,0x00}; unsigned char ClockPoint = 1; unsigned char Update; unsigned long halfsecond = 0; int year = 2020; byte month = 2; byte day = 11; byte hour = 13; byte minute = 10; byte second = 00; byte hundredths = 00; unsigned long gDate, gTime, age; uint32_t timestamp, tempval,microVal,fractions; // LM: GPS message parsing const char EOL = 10; // End-of-line const int MSGLEN = 67; // GNRMCメッセージの長さ、66文字の印刷可能文字+ 改行コード String tmpMsg = ""; String gnrmcMsg = ""; char chkHader[] = "RMC"; //このバージョンから3文字に変更 // LM: Date/time handling String sUTD = ""; // UT Date String sUTC = ""; // UT Time String HTTP_req = ""; // stores the HTTP request const int CENTURY = 2000; // LM: Alternative debug const boolean MYDEBUG = true; char STRBUF[50]; float timezone = 9.0; //TimeZone 日本は+9です。 const uint8_t daysInMonth [] PROGMEM = { 31,28,31,30,31,30,31,31,30,31,30,31 };//const or compiler complains const unsigned long seventyYears = 2208988800UL; // to convert unix time to epoch long gLoopCount = 3; //1時間毎に更新予定 最初は1分後に更新したくて初期値を調整 byte fPhyState; //オブジェクトコンストラクタ EthernetServer webServer(HttpPort); EthernetClient client; //TM1638 LedAndKey(dataPin, clockPin, strobePin); //データピンPB4、クロックピンPB3、およびストローブピンPA15でモジュールを定義する TM1637 tm1637(CLK,DIO); RTClock rtclock (RTCSEL_LSE); //RTC初期化 tm_t tmm,tmm1,tmm2,tmm3,tmm4; EthernetUDP Udp; //イーサネットUDPインスタンス char buffer[128]; char buffer1[128]; // これはすべて切り詰められたHTMLコードです。 エディターでHTMLコードを作成し、バッファに収まるように切り分けます。 // HTMLをすべての場所で切り取ることができますが、\ "部分ではできません。HTML内でsimple"の代わりに\ "を // 使用する必要があります。そうしないと機能しません。 const byte ID = 0x96; bool GpsEnable = false; char buf1[32]; char buf2[32]; char MacAddressStr[20]; char IPAddressStr[20]; char SubnetMaskStr[20]; char GatewayStr[20]; char NTPserverStr[20]; char DateTimeStr[20]; char Status = 'V'; float unit = 2.33E-7; String nmea; unsigned long baseMilis = millis(); unsigned long miliTime; String setenceStr = "$GPRMC"; String StatusStr = "V = 警告"; String DateTime2Str = "2020年12月24日 8時25分59秒.000"; String DateStr; String TimeStr; String latitudeStr = "北緯 35度41.1493分"; String latitudeNSStr; String longitudeStr = "東経 139度45.3994分"; String longitudeEWStr; String VeloStr = "00.0"; String DirStr = "0.0度"; String ModeStr = "A = 自律方式"; String MagDegStr; String MagDevStr; String NavStr; double latitude; double longitude; float gDayBuf[24][6]; //24時間分6要素データに変更 typedef struct{ uint32_t UTC; long LAT; //10000倍した緯度 long LON; //10000倍した経度 int DIR; //100倍した値0〜3600 int SPD; //速度(ノット単位)を100倍したもの }RMC; RMC gRmc; unsigned long gNTPqueryCount = 0; byte ErrorTMP = false; byte ErrorHUM = false; byte ErrorPRS = false; float temp(NAN), hum(NAN), pres(NAN); float gTemp(NAN), gHumi(NAN), gPres(NAN); BME280I2C bme; BME280::TempUnit tempUnit(BME280::TempUnit_Celsius); BME280::PresUnit presUnit(BME280::PresUnit_Pa); unsigned long OperatingTime = 0; //稼働時間 Count2で1秒 //---------------------------------- void LANSetup(){ int idcheck = EEPROM.read(0); Serial.print(F("LocalID = 0x"));Serial.println(idcheck,HEX); if (idcheck == ID){ //idがIDと同じ値の場合、 //これは、このスケッチがシールドを設定するために使用されたことを意味します。 //EERPOMの値を読み取ってシールドを設定します。 for (int i = 0; i < 6; i++){ mac[i] = EEPROM.read(i+1); } for (int i = 0; i < 4; i++){ ip[i] = EEPROM.read(i+7); } for (int i = 0; i < 4; i++){ subnet[i] = EEPROM.read(i+11); } for (int i = 0; i < 4; i++){ gateway[i] = EEPROM.read(i+15); } timezone = float(EEPROM.read(19)) / 10.0; }else{ //idが一致しない場合、初期値を書き込む事にします。 for (int i = 0 ; i < 6; i++){ EEPROM.write(i + 1,mac[i]); } for (int i = 0 ; i < 4; i++){ EEPROM.write(i + 7, ip[i]); } for (int i = 0 ; i < 4; i++){ EEPROM.write(i + 11, subnet[i]); } for (int i = 0 ; i < 4; i++){ EEPROM.write(i + 15, gateway[i]); } EEPROM.write(19,char(timezone * 10)); // IDを既知のビットに設定します。したがって、Arduinoをリセットすると、EEPROM値が使用されます。 EEPROM.write(0, ID); } sprintf(MacAddressStr,"%02x.%02x.%02x.%02x.%02x.%02x",mac[0],mac[1],mac[2],mac[3],mac[4],mac[5]); sprintf(IPAddressStr,"%d.%d.%d.%d",ip[0],ip[1],ip[2],ip[3]); sprintf(SubnetMaskStr,"%d.%d.%d.%d",subnet[0],subnet[1],subnet[2],subnet[3]); sprintf(GatewayStr,"%d.%d.%d.%d",gateway[0],gateway[1],gateway[2],gateway[3]); Ethernet.begin(mac, ip); // Ethernet.begin(mac); //DHCPの場合 // Ethernet.begin(mac, ip, subnet); //SubnetMaskを意識した場合。 // Ethernet.begin(mac, ip, subnet, gateway); //gatewayを意識した場合。 } //-------------------------- void SetWebPage( EthernetClient client){ client.println("HTTP/1.1 200 OK"); client.println("Content-Type: text/html"); client.println(); // client.print(F("<!DOCTYPE HTML PUBLIC \"\">\n<html>\n<HEAD>\n\t<META http-equiv=\"Content-Type\" charset=Shift_JIS\">\n")); client.print(F("\t<META http-equiv=\"Content-Style-Type\">\n\t<TITLE>")); client.print(vers); client.print(F(" Setup Page</TITLE>\n")); client.print(F("\t<SCRIPT>\n\t\tfunction HP(w1){parent.window.open(w1,'_top')}\n\t</SCRIPT>\n</HEAD>\n")); client.print(F("<BODY marginwidth=\"0\" marginheight=\"0\" leftmargin=\"0\" style=\"margin: 0; padding: 0;\">\n<BLOCKQUOTE><BLOCKQUOTE>\n")); client.print(F("<table bgcolor=\"#17A1A5\" border=\"0\" width=\"100%\" cellpadding=\"1\" style=\"font-family:Verdana;color:#ffffff;font-size:12px;\">\n")); client.print(F("<tr><td>")); client.print(vers); client.print(F(" Setup Page</td></tr></table><br>\n")); // client.print(F("<script>\n\tfunction hex2num (s_hex) {\n\t\teval(\"var n_num=0X\" + s_hex);\n\t\treturn n_num;\n\t}\n</script>\n")); client.print(F("<tbody>\n<FORM><input type=\"hidden\" name=\"SBM\" value=\"1\">\n<table>\n<tr><td>MAC:</td><td>")); client.print(F("<input id=\"T1\" type=\"text\" size=\"3\" maxlength=\"2\" name=\"DT1\" value=\"")); client.print(mac[0],HEX); client.print(F("\">.<input id=\"T3\" type=\"text\" size=\"3\" maxlength=\"2\" name=\"DT2\" value=\"")); client.print(mac[1],HEX); client.print(F("\">.<input id=\"T5\" type=\"text\" size=\"3\" maxlength=\"2\" name=\"DT3\" value=\"")); client.print(mac[2],HEX); client.print(F("\">.<input id=\"T7\" type=\"text\" size=\"3\" maxlength=\"2\" name=\"DT4\" value=\"")); client.print(mac[3],HEX); client.print(F("\">.<input id=\"T9\" type=\"text\" size=\"3\" maxlength=\"2\" name=\"DT5\" value=\"")); client.print(mac[4],HEX); client.print(F("\">.<input id=\"T11\" type=\"text\" size=\"3\" maxlength=\"2\" name=\"DT6\" value=\"")); client.print(mac[5],HEX); // client.print(F("\"><input id=\"T2\" type=\"hidden\" name=\"DT1\"><input id=\"T4\" type=\"hidden\" name=\"DT2")); client.print(F("\"><input id=\"T6\" type=\"hidden\" name=\"DT3\"><input id=\"T8\" type=\"hidden\" name=\"DT4")); client.print(F("\"><input id=\"T10\" type=\"hidden\" name=\"DT5\"><input id=\"T12\" type=\"hidden\" name=\"DT6")); client.print(F("\"></td></tr>\n<tr><td>IP:</td><td><input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT7\" value=\"")); client.print(ip[0],DEC); client.print(F("\">.<input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT8\" value=\"")); client.print(ip[1],DEC); client.print(F("\">.<input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT9\" value=\"")); client.print(ip[2],DEC); client.print(F("\">.<input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT10\" value=\"")); client.print(ip[3],DEC); // client.print(F("\"></td></tr>\n<tr><td>MASK: </td><td><input type= \"text\" size=\"3\" maxlength=\"3\" name=\"DT11\" value=\"")); client.print(subnet[0],DEC); client.print(F("\">.<input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT12\" value=\"")); client.print(subnet[1],DEC); client.print(F("\">.<input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT13\" value=\"")); client.print(subnet[2],DEC); client.print(F("\">.<input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT14\" value=\"")); client.print(subnet[3],DEC); // client.print(F("\"></td></tr>\n<tr><td>GW: </td><td><input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT15\" value=\"")); client.print(gateway[0],DEC); client.print(F("\">.<input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT16\" value=\"")); client.print(gateway[1],DEC); client.print(F("\">.<input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT17\" value=\"")); client.print(gateway[2],DEC); client.print(F("\">.<input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT18\" value=\"")); client.print(gateway[3],DEC); // client.print(F("\"></td></tr><tr><td COLSPAN='2'>\n<HR ALIGN=CENTER></td></tr>")); //1行空けたい // client.print(F("<tr><td>TimeZone:</td><td><input type=\"text\" size=\"3\" maxlength=\"5\" name=\"DT19\" value=\"")); client.print(timezone,2); client.print(F("\"></td></tr>\n<tr><td><br></td></tr><tr><td COLSPAN='2'><P ALIGN=RIGHT>")); client.print(F("<INPUT TYPE=\"button\" VALUE=\"RETURN\" onClick=\"HP('http://")); client.print(IPAddressStr); client.print(F("/setup9')\"/><input id=\"button1\"type=\"submit\" value=\"SUBMIT\" ")); // client.print(F("Onclick=\"")); client.print(F("document.getElementById('T2').value = hex2num(document.getElementById('T1').value);\n")); client.print(F("\t\tdocument.getElementById('T4').value = hex2num(document.getElementById('T3').value);\n")); client.print(F("\t\tdocument.getElementById('T6').value = hex2num(document.getElementById('T5').value);\n")); client.print(F("\t\tdocument.getElementById('T8').value = hex2num(document.getElementById('T7').value);\n")); client.print(F("\t\tdocument.getElementById('T10').value = hex2num(document.getElementById('T9').value);\n")); client.print(F("\t\tdocument.getElementById('T12').value = hex2num(document.getElementById('T11').value);\"")); // client.print(F("></td></tr></tbody>\n</table>\n</BLOCKQUOTE></BLOCKQUOTE>\n</form>\n</BODY>\n</html>\n")); } //-------------------------- 2021/8/12 void SetUtilPage( EthernetClient client){ client.println("HTTP/1.1 200 OK"); client.println("Content-Type: text/html"); client.println(); // client.print(F("<!DOCTYPE HTML PUBLIC \"\">\n<html>\n<HEAD>\n\t<META http-equiv=\"Content-Type\" charset=\"UTF-8\">\n")); client.print(F("\t<META http-equiv=\"Content-Style-Type\">\n\t<TITLE>")); client.print(vers); client.print(F(" Utility Page</TITLE>\n")); client.print(F("\t<script type='application/javascript' SRC='http://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.3.0/Chart.bundle.js'></script>\n")); client.print(F("\t<script type='application/javascript' SRC='./setup3'></script>\n")); client.print(F("\t<style>\n\t\tcanvas{\n")); client.print(F("\t\t\t-moz-user-select: none;\n")); client.print(F("\t\t\t-webkit-user-select: none;\n")); client.print(F("\t\t\t-ms-user-select: none;\n\t\t}\n")); client.print(F("\t\thtml { font-family: Helvetica; display: inline-block; margin: 0px auto;}\n")); client.print(F("\t\tbody{margin-top: 50px;} h1 {color: #444444;margin: 50px auto 30px;}\n")); client.print(F("\t\tp {font-size: 24px;color: #444444;margin-bottom: 10px;}\n")); client.print(F("\t</style>\n")); client.print(F("\t<SCRIPT>\n\t\tfunction HP(w1){parent.window.open(w1,'_top')}\n\t</SCRIPT>\n</HEAD>\n")); client.print(F("<BODY marginwidth=\"0\" marginheight=\"0\" leftmargin=\"0\" style=\"margin: 0; padding: 0;\" BGCOLOR=\"#ffffff\">\n")); client.print(F("<FORM>\n<BLOCKQUOTE><BLOCKQUOTE>\n")); client.print(F("<P><table bgcolor=\"#17A1A5\" border=\"0\" width=\"100%\" cellpadding=\"1\" style=\"font-family:Verdana;color:#ffffff;font-size:12px;\" CELLSPACING=\"2\">\n")); client.print(F("<tr><td>")); client.print(vers); client.print(F(" Utility Page</td></tr></table><br>\n")); // client.print(F("<TABLE WIDTH=\"100%\" BORDER=\"0\" CELLSPACING=\"0\" CELLPADDING=\"0\">")); client.print(F("<TR>\n\t<TD WIDTH=\"25%\"> </TD><TD WIDTH=\"20%\"> </TD><TD WIDTH=\"55%\"><P ALIGN=CENTER><INPUT TYPE=\"button\" VALUE=\"RETURN\" onClick=\"HP('http://")); client.print(IPAddressStr); client.print(F("/setup9')\"/></TD>\n")); client.print(F("<TR>\n\t<TD WIDTH=\"25%\">FirmwareVirsion:</TD>\n\t<TD WIDTH=\"20%\"></TD>\n\t<TD WIDTH=\"55%\">")); client.print(FVirsion); client.print(F("</TD>\n</TR>")); client.print(F("<TR>\n\t<TD WIDTH=\"25%\">MacAddress:</TD>\n\t<TD WIDTH=\"20%\"></TD>\n\t<TD WIDTH=\"55%\">")); client.print(MacAddressStr); client.print(F("</TD>\n</TR>")); client.print(F("<TR>\n\t<TD WIDTH=\"25%\">IPAddress:</TD>\n\t<TD WIDTH=\"20%\"></TD>\n\t<TD WIDTH=\"55%\">")); client.print(IPAddressStr); client.print(F("</TD>\n</TR>")); client.print(F("<TR>\n\t<TD WIDTH=\"25%\">SubnetMask:</TD>\n\t<TD WIDTH=\"20%\"></TD>\n\t<TD WIDTH=\"55%\">")); client.print(SubnetMaskStr); client.print(F("</TD>\n</TR>")); client.print(F("<TR>\n\t<TD WIDTH=\"25%\">InternalTemperature:</TD>\n\t<TD WIDTH=\"20%\"></TD>\n\t<TD WIDTH=\"55%\">")); sprintf(buf2,"%.2f[℃]",gTemp); client.print(buf2); client.print(F("</TD>\n</TR>")); client.print(F("<TR>\n\t<TD WIDTH=\"25%\">InternalAirPressure:</TD>\n\t<TD WIDTH=\"20%\"></TD>\n\t<TD WIDTH=\"55%\">")); sprintf(buf2,"%.0f[Pa]",gPres); client.print(buf2); client.print(F("</TD>\n</TR>")); client.print(F("<TR>\n\t<TD WIDTH=\"25%\">NTP受信回数:</TD>\n\t<TD WIDTH=\"20%\"></TD>\n\t<TD WIDTH=\"55%\">")); sprintf(buf2,"%d[回]",gNTPqueryCount); client.print(buf2); client.print(F("</TD>\n</TR>")); client.print(F("<TR>\n\t<TD WIDTH=\"25%\" NOWRAP>Data acquisition time(Local):</TD>\n\t<TD WIDTH=\"20%\"></TD>\n\t<TD WIDTH=\"55%\">")); client.print(DateTimeStr); client.print(F("</TD>\n</TR>")); client.print(F("<TR>\n\t<TD WIDTH=\"25%\" NOWRAP>Operating Time:</TD>\n\t<TD WIDTH=\"20%\"></TD>\n\t<TD WIDTH=\"55%\">")); sprintf(buf2,"%d[sec]",(OperatingTime / 2)); client.print(buf2); client.print(F("</TD>\n</TR></TABLE></P>\n")); client.print(F("<P><center><TABLE WIDTH=\"750\" BORDER=\"1\" CELLSPACING=\"0\" CELLPADDING=\"0\" HEIGHT=\"101\">\n")); client.print(F("\t<CAPTION ALIGN=\"TOP\"><P ALIGN=LEFT>GPS センテンス</CAPTION>")); client.print(F("\t<TR>\n\t\t<TD COLSPAN=\"2\" BGCOLOR=\"#000000\">\n\t\t<BLOCKQUOTE>\n")); client.print(F("\t\t\t<PRE><FONT COLOR=\"#ffffff\" SIZE=\"+1\">")); client.print(nmea); Serial.println(nmea); //$GPRMC,021227.000,A,3459.9060,N,13652.6560,E,0.09,344.80,241220,,,A*6D //$GNRMC,072815.000,V, , , , , , ,241220,,,N,V*27 //RMCは要素数が固定されていないようなので注意が必要 analsysRMC(); client.print(F("</FONT></PRE>\n\t\t</BLOCKQUOTE>")); client.print(F("</TD>\n\t</TR>\n\t<TR>\n\t\t<TD WIDTH=\"150\" HEIGHT=\"20\">ステータス:</TD>\n\t\t<TD WIDTH=\"600\">")); client.print(StatusStr); client.print(F("</TD>\n\t</TR>\n\t<TR>\n\t\t<TD WIDTH=\"150\" HEIGHT=\"20\">日時(UTC):</TD>\n\t\t<TD WIDTH=\"600\">")); client.print(DateTime2Str); client.print(F("</TD>\n\t</TR>\n\t<TR>\n\t\t<TD WIDTH=\"150\" HEIGHT=\"20\">位置情報:緯度</TD>\n\t\t<TD WIDTH=\"600\">")); client.print(latitudeNSStr); client.print(latitudeStr); client.print(F("\t("));client.print(latitude,8);client.print(F(")")); client.print(F("</TD>\n\t</TR>\n\t<TR>\n\t\t<TD WIDTH=\"150\" HEIGHT=\"20\">位置情報:経度</TD>\n\t\t<TD WIDTH=\"600\">")); client.print(longitudeEWStr); client.print(longitudeStr); client.print(F("\t("));client.print(longitude,8);client.print(F(")")); client.print(F("</TD>\n\t</TR>\n\t<TR>\n\t\t<TD WIDTH=\"150\" HEIGHT=\"20\">移動の速度:</TD>\n\t\t<TD WIDTH=\"600\">")); client.print(VeloStr); client.print(F("[knot]</TD>\n\t</TR>\n\t<TR>\n\t\t<TD WIDTH=\"150\" HEIGHT=\"20\">移動の真方位:</TD>\n\t\t<TD WIDTH=\"600\">")); client.print(DirStr); client.print(F("[度]</TD>\n\t</TR>\n\t<TR>\n\t\t<TD WIDTH=\"150\" HEIGHT=\"20\">モード:</TD>\n\t\t<TD WIDTH=\"600\">")); client.print(ModeStr); client.print(F("</TD>\n\t</TR>\n</TABLE></center>\n<CENTER>\n")); client.print(F("\t<div style='width:75%;'><canvas id='canvas' width='750' height='400' style='display: block; width: 750px; height: 400px;'></canvas></div>\n")); client.print(F("</CENTER></BLOCKQUOTE></BLOCKQUOTE>\n</form>\n")); client.print(F("<script>\n")); client.print(F("\t\tvar randomScalingFactor = function() {\n\t\t\treturn 18 + Math.random() * 10;\n\t\t};\n")); client.print(F("\t\tvar data_00 = [")); for(int i = 0; i<24 ;i++){ client.print(isnan(gDayBuf[i][0]) ? String('\0') : String(gDayBuf[i][0],6)); client.print(F(",")); } client.print(F("];\n")); client.print(F("\t\tvar data_01 = [")); for(int i = 0; i<24 ;i++){ client.print(isnan(gDayBuf[i][1]) ? String('\0') : String(gDayBuf[i][1],6)); client.print(F(",")); } client.print(F("];\n")); client.print(F("\t\tvar data_02 = [")); for(int i = 0; i<24 ;i++){ client.print(isnan(gDayBuf[i][2]) ? String('\0') : String(gDayBuf[i][2],2)); client.print(F(",")); } client.print(F("];\n")); client.print(F("\t\tvar data_03 = [")); for(int i = 0; i<24 ;i++){ client.print(isnan(gDayBuf[i][3]) ? String('\0') : String(gDayBuf[i][3],2)); client.print(F(",")); } client.print(F("];\n")); client.print(F("\t\tvar data_04 = [")); for(int i = 0; i<24 ;i++){ client.print(isnan(gDayBuf[i][4]) ? String('\0') : String(gDayBuf[i][4],2)); client.print(F(",")); } client.print(F("];\n")); client.print(F("\t\tvar data_05 = [")); for(int i = 0; i<24 ;i++){ client.print(isnan(gDayBuf[i][5]) ? String('\0') : String(gDayBuf[i][5],0)); client.print(F(",")); } client.print(F("];\n")); client.print(F("\t\tvar config = {")); client.print(F("\t\t\ttype: 'line',\n\t\t\tdata: {\n")); client.print(F("\t\t\t\tlabels: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12' , '13', '14', '15', '16', '17', '18', '19', '20', '21', '22', '23'],\n")); client.print(F("\t\t\t\tdatasets: [{\n")); client.print(F("\t\t\t\t\tlabel: '緯度[度]',\n")); client.print(F("\t\t\t\t\tdata: data_00,\n")); client.print(F("\t\t\t\t\tborderColor: window.chartColors.red,\n")); client.print(F("\t\t\t\t\tbackgroundColor: 'rgba(0, 0, 0, 0)',\n")); client.print(F("\t\t\t\t\tfill: false,\n")); client.print(F("\t\t\t\t\tcubicInterpolationMode: 'monotone',\n")); client.print(F("\t\t\t\t\tyAxisID: 'y1'\n")); client.print(F("\t\t\t\t}, {\n")); client.print(F("\t\t\t\t\tlabel: '経度[度]',\n")); client.print(F("\t\t\t\t\tdata: data_01,\n")); client.print(F("\t\t\t\t\tborderColor: window.chartColors.blue,\n")); client.print(F("\t\t\t\t\tbackgroundColor: 'rgba(0, 0, 0, 0)',\n")); client.print(F("\t\t\t\t\tfill: false,\n")); client.print(F("\t\t\t\t\tyAxisID: 'y2'\n")); client.print(F("\t\t\t\t}, {\n")); client.print(F("\t\t\t\t\tlabel: '速度[knot]',\n")); client.print(F("\t\t\t\t\tdata: data_02,\n")); client.print(F("\t\t\t\t\tborderColor: window.chartColors.orange,\n")); client.print(F("\t\t\t\t\tbackgroundColor: 'rgba(0, 0, 0, 0)',\n")); client.print(F("\t\t\t\t\tfill: false,\n")); client.print(F("\t\t\t\t\tyAxisID: 'y3'\n")); client.print(F("\t\t\t\t}, {\n")); client.print(F("\t\t\t\t\tlabel: '真方位[度]',\n")); client.print(F("\t\t\t\t\tdata: data_03,\n")); client.print(F("\t\t\t\t\tborderColor: window.chartColors.yellow,\n")); client.print(F("\t\t\t\t\tbackgroundColor: 'rgba(0, 0, 0, 0)',\n")); client.print(F("\t\t\t\t\tfill: false,\n")); client.print(F("\t\t\t\t\tyAxisID: 'y4'\n")); client.print(F("\t\t\t\t}, {\n")); client.print(F("\t\t\t\t\tlabel: '温度[℃]',\n")); client.print(F("\t\t\t\t\tdata: data_04,\n")); client.print(F("\t\t\t\t\tborderColor: window.chartColors.grey,\n")); client.print(F("\t\t\t\t\tbackgroundColor: 'rgba(0, 0, 0, 0)',\n")); client.print(F("\t\t\t\t\tfill: false,\n")); client.print(F("\t\t\t\t\tyAxisID: 'y5'\n")); client.print(F("\t\t\t\t}, {\n")); client.print(F("\t\t\t\t\tlabel: '気圧[Pa]',\n")); client.print(F("\t\t\t\t\tdata: data_05,\n")); client.print(F("\t\t\t\t\tborderColor: window.chartColors.green,\n")); client.print(F("\t\t\t\t\tbackgroundColor: 'rgba(0, 0, 0, 0)',\n")); client.print(F("\t\t\t\t\tfill: false,\n")); client.print(F("\t\t\t\t\tyAxisID: 'y6'\n")); client.print(F("\t\t\t\t}]\n\t\t\t},\n")); client.print(F("\t\t\toptions: {\n\t\t\t\tresponsive: true,\n")); client.print(F("\t\t\t\ttitle: {\n\t\t\t\t\tdisplay: true,\n\t\t\t\t\ttext: '今日のGPS位置情報'\n\t\t\t\t},\n")); client.print(F("\t\t\t\tlegend: {\n\t\t\t\t\tdisplay: true\n\t\t\t\t},\n")); client.print(F("\t\t\t\ttooltips: {\n\t\t\t\t\tenabled: true,\n\t\t\t\t\tmode: 'index'\n\t\t\t\t},\n")); client.print(F("\t\t\t\tscales: {\n\t\t\t\t\txAxes: [{\n\t\t\t\t\t\tdisplay: true,\n")); client.print(F("\t\t\t\t\t\tscaleLabel: {\n\t\t\t\t\t\t\tdisplay: true,\n\t\t\t\t\t\t\tlabelString: '時刻 [時]'\n\t\t\t\t\t\t}\n\t\t\t\t\t}],\n")); client.print(F("\t\t\t\t\tyAxes: [{\n\t\t\t\t\t\tid:'y1',\n\t\t\t\t\t\tdisplay: true,\n")); client.print(F("\t\t\t\t\t\tscaleLabel: {\n\t\t\t\t\t\t\tfontColor: window.chartColors.red,\n")); client.print(F("\t\t\t\t\t\t\tdisplay: true,\n\t\t\t\t\t\t\tlabelString: '緯度[度]'\n\t\t\t\t\t\t},\n")); client.print(F("\t\t\t\t\t\tticks: {\n\t\t\t\t\t\t\tfontColor: window.chartColors.red,\n")); client.print(F("\t\t\t\t\t\t\tsuggestedMin: -200,\n\t\t\t\t\t\t\tsuggestedMax: 200\n\t\t\t\t\t\t}\n")); client.print(F("\t\t\t\t\t},{\n\t\t\t\t\t\tid:'y2',\n\t\t\t\t\t\tdisplay: true,\n")); client.print(F("\t\t\t\t\t\tscaleLabel: {\n\t\t\t\t\t\t\tfontColor: window.chartColors.blue,\n")); client.print(F("\t\t\t\t\t\t\tdisplay: true,\n\t\t\t\t\t\t\tlabelString: '経度[度]'\n\t\t\t\t\t\t},\n")); client.print(F("\t\t\t\t\t\tticks: {\n\t\t\t\t\t\t\tfontColor: window.chartColors.blue,\n")); client.print(F("\t\t\t\t\t\t\tsuggestedMin: -200,\n\t\t\t\t\t\t\tsuggestedMax: 200\n\t\t\t\t\t\t}\n")); client.print(F("\t\t\t\t\t},{\n\t\t\t\t\t\tid:'y3',\n\t\t\t\t\t\tdisplay: true,\n")); client.print(F("\t\t\t\t\t\tscaleLabel: {\n\t\t\t\t\t\t\tfontColor: window.chartColors.orange,\n")); client.print(F("\t\t\t\t\t\t\tdisplay: true,\n\t\t\t\t\t\t\tlabelString: '速度[knot]'\n\t\t\t\t\t\t},\n")); client.print(F("\t\t\t\t\t\tticks: {\n\t\t\t\t\t\t\tfontColor: window.chartColors.orange,\n")); client.print(F("\t\t\t\t\t\t\tsuggestedMin: 0,\n\t\t\t\t\t\t\tsuggestedMax: 40\n\t\t\t\t\t\t\t}\n")); client.print(F("\t\t\t\t\t},{\n\t\t\t\t\t\tid:'y4',\n\t\t\t\t\t\tposition: 'right',\n\t\t\t\t\t\tdisplay: true,\n")); client.print(F("\t\t\t\t\t\tscaleLabel: {\n\t\t\t\t\t\t\tfontColor: window.chartColors.yellow,\n")); client.print(F("\t\t\t\t\t\t\tdisplay: true,\n\t\t\t\t\t\t\tlabelString: '真方位[度]'\n\t\t\t\t\t\t},\n")); client.print(F("\t\t\t\t\t\tticks: {\n\t\t\t\t\t\t\tfontColor: window.chartColors.yellow,\n")); client.print(F("\t\t\t\t\t\t\tsuggestedMin: 0,\n\t\t\t\t\t\t\tsuggestedMax: 400\n\t\t\t\t\t\t}\n")); client.print(F("\t\t\t\t\t},{\n\t\t\t\t\t\tid:'y5',\n\t\t\t\t\t\tposition: 'right',\n\t\t\t\t\t\tdisplay: true,\n")); client.print(F("\t\t\t\t\t\tscaleLabel: {\n\t\t\t\t\t\t\tfontColor: window.chartColors.grey,\n")); client.print(F("\t\t\t\t\t\t\tdisplay: true,\n\t\t\t\t\t\t\tlabelString: '気温[℃]'\n\t\t\t\t\t\t},\n")); client.print(F("\t\t\t\t\t\tticks: {\n\t\t\t\t\t\t\tfontColor: window.chartColors.grey,\n")); client.print(F("\t\t\t\t\t\t\tsuggestedMin: 10,\n\t\t\t\t\t\t\tsuggestedMax: 50\n\t\t\t\t\t\t}\n")); client.print(F("\t\t\t\t\t},{\n\t\t\t\t\t\tid:'y6',\n\t\t\t\t\t\tposition: 'right',\n\t\t\t\t\t\tdisplay: true,\n")); client.print(F("\t\t\t\t\t\tscaleLabel: {\n\t\t\t\t\t\t\tfontColor: window.chartColors.green,\n")); client.print(F("\t\t\t\t\t\t\tdisplay: true,\n\t\t\t\t\t\t\tlabelString: '気圧[Pa]'\n\t\t\t\t\t\t},\n")); client.print(F("\t\t\t\t\t\tticks: {\n\t\t\t\t\t\t\tfontColor: window.chartColors.green,\n")); client.print(F("\t\t\t\t\t\t\tsuggestedMin: 80000,\n\t\t\t\t\t\t\tsuggestedMax: 120000\n\t\t\t\t\t\t}\n\t\t\t\t\t}]\n")); client.print(F("\t\t\t\t}\n\t\t\t}\n\t\t};\n")); client.print(F("\t\twindow.onload = function() {\n\t\t\tvar ctx = document.getElementById('canvas').getContext('2d');\n")); client.print(F("\t\t\twindow.myLine = new Chart(ctx, config);\n\t\t};\n")); client.print(F("</script>\n")); client.print(F("</BODY></html>")); } //-------------------------- void checkWebPage( EthernetClient client) { Serial.println(F("new webClient")); boolean restart = false; if (client) { TextFinder finder(client ); while (client.connected()) { //digitalWrite(LED, HIGH); if (client.available()) { //この部分はすべてのテキスト検索を行います。 if( finder.find("GET /") ) { // 「setup」とい語が見つかった場合は、さらに探してください。 // その単語が見つからない場合は、検索を停止して先に進みます。 // これにより、後でスケッチに独自のWebページを配置できます。 if (finder.findUntil("setup", "\n\r")){ long cord = finder.getValue(); sprintf(buf1,"setupIn=%d",cord); Serial.println(buf1); switch(cord){ case 1: // 「SBM」という単語が見つかった場合は、さらに探し続けます。 // その言葉が見つからない場合は、探して停止します。 // SUBMITボタンが押されていない、何も押されていないことを意味します // セットアップページが構築されている場所に移動し、クライアントのブラウザに表示します。 if (finder.findUntil("SBM", "\n\r")){ byte SET = finder.getValue(); // これで、「DT」という文字を探している間に、「DT」の後ろにあるすべての数字を覚えて、 // 値を一致させて、mac、ip、subnet、およびgatewayに入れる必要があります。 while(finder.findUntil("DT", "\n\r")){ int val = finder.getValue(); // 「DT」のvalが1?6の場合、対応する値はMAC値でなければなりません。 if(val >= 1 && val <= 6) { mac[val - 1] = finder.getValue(); } // 「DT」のvalが7?10の場合、対応する値はIP値でなければなりません。 if(val >= 7 && val <= 10) { ip[val - 7] = finder.getValue(); } // 「DT」のvalが11?14の場合、対応する値はMASK値でなければなりません。 if(val >= 11 && val <= 14) { subnet[val - 11] = finder.getValue(); } // 「DT」のvalが15?18の場合、対応する値はGW値でなければなりません。 if(val >= 15 && val <= 18) { gateway[val - 15] = finder.getValue(); } //「DT」のvalが19の場合、対応する値はtimezoneです。 timezone = finder.getValue(); } // すべてのデータを取得したので、EEPROMに保存できます for (int i = 0 ; i < 6; i++){ EEPROM.write(i + 1,mac[i]); } for (int i = 0 ; i < 4; i++){ EEPROM.write(i + 7, ip[i]); } for (int i = 0 ; i < 4; i++){ EEPROM.write(i + 11, subnet[i]); } for (int i = 0 ; i < 4; i++){ EEPROM.write(i + 15, gateway[i]); } EEPROM.write(19, char(timezone * 10)); // IDを既知のビットに設定します。したがって、Arduinoをリセットすると、EEPROM値が使用されます。 EEPROM.write(0, ID); sprintf(MacAddressStr,"%02x.%02x.%02x.%02x.%02x.%02x",mac[0],mac[1],mac[2],mac[3],mac[4],mac[5]); sprintf(IPAddressStr,"%d.%d.%d.%d",ip[0],ip[1],ip[2],ip[3]); sprintf(SubnetMaskStr,"%d.%d.%d.%d",subnet[0],subnet[1],subnet[2],subnet[3]); sprintf(GatewayStr,"%d.%d.%d.%d",gateway[0],gateway[1],gateway[2],gateway[3]); // すべてのデータがEEPROMに書き込まれている場合、arduinoをリセットする必要があります。 //ハードウェアリセットボタンを使用する必要があります。 restart = true; }else{ } // この時点から、セットアップページの構築を開始し、クライアントのブラウザーに表示できます。 SetWebPage(client); break; case 2: // 新規にユーティリティ頁を作りました。 Serial.println(F("utilIn")); rtclock.getTime(tmm4); sprintf(DateTimeStr,"%04u/%02u/%02u %02u:%02u:%02u",tmm4.year+1970, tmm4.month, tmm4.day,tmm4.hour, tmm4.minute, tmm4.second); SetUtilPage(client); break; case 3: //utils.jsが要求された。 Serial.println(F("utils.js")); LoadUtilsJS(client); break; case 9: Serial.println(F("Home")); pageIntroduction(client); break; default: PrintResponse404(client ); break; } }else{ pageIntroduction(client); } } break; } PrintResponse404(client ); } delay(1); client.stop(); if(restart) nvic_sys_reset(); } } //---------------------------- void pageIntroduction( EthernetClient client){ client.println(F("HTTP/1.1 200 OK")); client.println(F("Content-Type: text/html")); client.println(); // put your own html from here on sprintf(buf1,"%d.%d.%d.%d",ip[0],ip[1],ip[2],ip[3]); client.println(F("<HTML>\n<HEAD>\n\t<META HTTP-EQUIV=\"Content-Type\" CONTENT=\"text/html;CHARSET=UTF-8\">\n<TITLE>GPSNTP メニュー</TITLE>\n</HEAD>\n")); client.println(F("<BODY BGCOLOR='#ffffff'>\n<H2><CENTER><FONT COLOR='#00ff00'>GPSNTP メニュー</FONT></CENTER></H2>\n")); client.println(F("<P><CENTER><TABLE WIDTH='300' BORDER='0' CELLSPACING='0' CELLPADDING='0'>\n\t<TR>\n\t\t<TD WIDTH='100%'>\n\t\t<OL>\n")); sprintf(buffer,"\t\t\t<LI><FONT SIZE='+1'><A HREF=\"http://%s/setup1\">設定メニュー</A>\n",buf1); client.println(buffer); sprintf(buffer,"\t\t\t<LI><A HREF=\"http://%s/setup2\">GPSセンテンス表示</A></FONT>\n",buf1); client.println(buffer); client.print(F("\t\t</OL>\n\t\t</TD>\n\t</TR>\n</TABLE></CENTER>\n</BODY>\n</HTML>\n")); client.stop(); } //-------------------------- void PrintResponse404( EthernetClient client ) { client.println(F("HTTP/1.1 404 Not Found")); client.println(F("Content-Type: text/html")); client.println(F("Connnection: close")); client.println(); client.stop(); } //-------------------------- void LoadUtilsJS( EthernetClient client ) { client.println("HTTP/1.1 200 OK"); client.println("Content-Type: text/html"); client.println(); // client.println("'use strict';\n"); // client.println("window.chartColors = {"); client.println("\tred: 'rgb(255, 99, 132)',"); client.println("\torange: 'rgb(255, 159, 64)',"); client.println("\tyellow: 'rgb(255, 205, 86)',"); client.println("\tgreen: 'rgb(75, 192, 192)',"); client.println("\tblue: 'rgb(54, 162, 235)',"); client.println("\tpurple: 'rgb(153, 102, 255)',"); client.println("\tgrey: 'rgb(201, 203, 207)'"); client.println("};\n"); // client.println("(function(global) {"); client.println("\tvar MONTHS = ["); client.println("\t\t'January',"); client.println("\t\t'February',"); client.println("\t\t'March',"); client.println("\t\t'April',"); client.println("\t\t'May',"); client.println("\t\t'June',"); client.println("\t\t'July',"); client.println("\t\t'August',"); client.println("\t\t'September',"); client.println("\t\t'October',"); client.println("\t\t'November',"); client.println("\t\t'December'"); client.println("\t];\n"); // client.println("\tvar COLORS = ["); client.println("\t\t'#4dc9f6',"); client.println("\t\t'#f67019',"); client.println("\t\t'#f53794',"); client.println("\t\t'#537bc4',"); client.println("\t\t'#acc236',"); client.println("\t\t'#166a8f',"); client.println("\t\t'#00a950',"); client.println("\t\t'#58595b',"); client.println("\t\t'#8549ba'"); client.println("\t];\n"); // client.println("\tvar Samples = global.Samples || (global.Samples = {});"); client.println("\tvar Color = Chart.helpers.color;\n"); // client.println("\tfunction applyDefaultNumbers(config) {"); client.println("\t\tvar cfg = config || {};"); client.println("\t\tcfg.min = cfg.min || 0;"); client.println("\t\tcfg.max = cfg.max || 1;"); client.println("\t\tcfg.from = cfg.from || [];"); client.println("\t\tcfg.count = cfg.count || 8;"); client.println("\t\tcfg.decimals = cfg.decimals || 8;"); client.println("\t\tcfg.continuity = cfg.continuity || 1;\n"); // client.println("\t\treturn cfg;"); client.println("\t}\n"); // client.println("\tSamples.utils = {"); client.println("\t\t// Adapted from http://indiegamr.com/generate-repeatable-random-numbers-in-js/"); client.println("\t\tsrand: function(seed) {"); client.println("\t\t\tthis._seed = seed;"); client.println("\t\t},\n"); // client.println("\t\trand: function(min, max) {"); client.println("\t\t\tvar seed = this._seed;"); client.println("\t\t\tmin = min === undefined ? 0 : min;"); client.println("\t\t\tmax = max === undefined ? 1 : max;"); client.println("\t\t\tthis._seed = (seed * 9301 + 49297) % 233280;"); client.println("\t\t\treturn min + (this._seed / 233280) * (max - min);"); client.println("\t\t},\n"); // client.println("\t\tnumbers: function(config) {"); client.println("\t\t\tvar cfg = applyDefaultNumbers(config);"); client.println("\t\t\tvar dfactor = Math.pow(10, cfg.decimals) || 0;"); client.println("\t\t\tvar data = [];"); client.println("\t\t\tvar i, value;\n"); // client.println("\t\t\tfor (i = 0; i < cfg.count; ++i) {"); client.println("\t\t\t\tvalue = (cfg.from[i] || 0) + this.rand(cfg.min, cfg.max);"); client.println("\t\t\t\tif (this.rand() <= cfg.continuity) {"); client.println("\t\t\t\t\tdata.push(Math.round(dfactor * value) / dfactor);"); client.println("\t\t\t\t} else {"); client.println("\t\t\t\t\tdata.push(null);"); client.println("\t\t\t\t}"); client.println("\t\t\t}\n"); // client.println("\t\t\treturn data;"); client.println("\t\t},\n"); // client.println("\t\tlabels: function(config) {"); client.println("\t\t\tvar cfg = config || {};"); client.println("\t\t\tvar min = cfg.min || 0;"); client.println("\t\t\tvar max = cfg.max || 100;"); client.println("\t\t\tvar count = cfg.count || 8;"); client.println("\t\t\tvar step = (max - min) / count;"); client.println("\t\t\tvar decimals = cfg.decimals || 8;"); client.println("\t\t\tvar dfactor = Math.pow(10, decimals) || 0;"); client.println("\t\t\tvar prefix = cfg.prefix || '';"); client.println("\t\t\tvar values = [];"); client.println("\t\t\tvar i;\n"); // client.println("\t\t\tfor (i = min; i < max; i += step) {"); client.println("\t\t\t\tvalues.push(prefix + Math.round(dfactor * i) / dfactor);"); client.println("\t\t\t}\n"); // client.println("\t\t\treturn values;"); client.println("\t\t},\n"); // client.println("\t\tmonths: function(config) {"); client.println("\t\t\tvar cfg = config || {};"); client.println("\t\t\tvar count = cfg.count || 12;"); client.println("\t\t\tvar section = cfg.section;"); client.println("\t\t\tvar values = [];"); client.println("\t\t\tvar i, value;\n"); // client.println("\t\t\tfor (i = 0; i < count; ++i) {"); client.println("\t\t\t\tvalue = MONTHS[Math.ceil(i) % 12];"); client.println("\t\t\t\tvalues.push(value.substring(0, section));"); client.println("\t\t\t}\n"); // client.println("\t\t\treturn values;"); client.println("\t\t},\n"); // client.println("\t\tcolor: function(index) {"); client.println("\t\t\treturn COLORS[index % COLORS.length];"); client.println("\t\t},\n"); // client.println("\t\ttransparentize: function(color, opacity) {"); client.println("\t\t\tvar alpha = opacity === undefined ? 0.5 : 1 - opacity;"); client.println("\t\t\treturn Color(color).alpha(alpha).rgbString();"); client.println("\t\t}"); client.println("\t};\n"); // client.println("\t// DEPRECATED"); client.println("\twindow.randomScalingFactor = function() {"); client.println("\t\treturn Math.round(Samples.utils.rand(-100, 100));"); client.println("\t};\n"); // client.println("\t// INITIALIZATION\n"); // client.println("\tSamples.utils.srand(Date.now());\n"); // client.println("\t// Google Analytics"); client.println("\t/* eslint-disable */"); client.println("\tif (document.location.hostname.match(/^(www\\.)?chartjs\\.org$/)) {"); client.println("\t\t(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){"); client.println("\t\t(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),"); client.println("\t\tm=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)"); client.println("\t\t})(window,document,'script','//www.google-analytics.com/analytics.js','ga');"); client.println("\t\tga('create', 'UA-28909194-3', 'auto');"); client.println("\t\tga('send', 'pageview');"); client.println("\t}"); client.println("\t/* eslint-enable */\n"); // client.println("}(this));"); } //--------------------- bool processNTP() { //利用可能なデータがある場合は、パケットを読み取ります //https://blog.goo.ne.jp/hiro239415/e/c426a545863921a13a9d6a70b7ae4484 //NTP_Packet (48Byte) bool req = false; int packetSize = Udp.parsePacket(); if(packetSize){ digitalWrite(BUILTIN_LED,HIGH); //要求があったときにBuiltInLEDを点灯 digitalWrite(LED1, HIGH); miliTime = millis() - baseMilis; fractions = uint32_t(float(miliTime % 1000) / unit); sprintf(buf1,"■%d\t%d\n",packetSize,fractions); Serial.print(buf1); Udp.read(packetBuffer,NTP_PACKET_SIZE); IPAddress Remote = Udp.remoteIP(); int PortNum = Udp.remotePort(); //受信タイムスタンプ = サーバが受け取った時刻を予めセットしておく rtclock.getTime(tmm); timestamp = numberOfSecondsSince1900Epoch(tmm.year+1970,tmm.month,tmm.day,tmm.hour,tmm.minute,tmm.second,timezone); miliTime = millis() - baseMilis; fractions = uint32_t(float(miliTime % 1000) / unit); tempval = timestamp; packetBuffer[32] = (tempval >> 24) & 0XFF; tempval = timestamp; packetBuffer[33] = (tempval >> 16) & 0xFF; tempval = timestamp; packetBuffer[34] = (tempval >> 8) & 0xFF; tempval = timestamp; packetBuffer[35] = (tempval) & 0xFF; // microVal = fractions; packetBuffer[36] = (microVal >> 24) & 0xFF; microVal = fractions; packetBuffer[37] = (microVal >> 16) & 0xFF; microVal = fractions; packetBuffer[38] = (microVal >> 8) & 0xFF; microVal = fractions; packetBuffer[39] = microVal & 0xFF; //if (!getGPSdata()) return; //ヘッダーセット packetBuffer[0] = 0b00100100; // 閏秒警告なしLI, 4:SNTPサーバ(Version), 4:サーバ(Mode) packetBuffer[1] = 1 ; // 1:一次基準源(GPS等)(stratum packetBuffer[2] = 5 ; // ポーリング64秒デフォルト値 6(64秒)->5(32秒) packetBuffer[3] = 0xF6; // 精度 -3(0xFD)->-10(0xF6)に変更 msec packetBuffer[4] = 0; // ルート遅延 packetBuffer[5] = 0; packetBuffer[6] = 8; packetBuffer[7] = 0; packetBuffer[8] = 0; // ルート分散 packetBuffer[9] = 0; packetBuffer[10] = 0xC; packetBuffer[11] = 0; //gps.crack_datetime(&year, &month, &day, &hour, &minute, &second, &hundredths, &age); //crack(sUTD, sUTC); //timestamp = numberOfSecondsSince1900Epoch(year,month,day,hour,minute,second); //時刻の再取得 rtclock.getTime(tmm); timestamp = numberOfSecondsSince1900Epoch(tmm.year+1970,tmm.month,tmm.day,tmm.hour,tmm.minute,tmm.second,timezone); #if debug Serial.print(timestamp);Serial.print(F(":Time synchronization Request received\n")); //print_date(gps); #endif tempval = timestamp; microVal = fractions; packetBuffer[12] = 71; //"G"; ReferenceID packetBuffer[13] = 80; //"P"; ReferenceID packetBuffer[14] = 83; //"S"; ReferenceID packetBuffer[15] = 0; //"0"; ReferenceID // リファレンス時刻をセット packetBuffer[16] = (tempval >> 24) & 0XFF; tempval = timestamp; packetBuffer[17] = (tempval >> 16) & 0xFF; tempval = timestamp; packetBuffer[18] = (tempval >> 8) & 0xFF; tempval = timestamp; packetBuffer[19] = (tempval) & 0xFF; packetBuffer[20] = (microVal >> 24) & 0xFF; microVal = fractions; packetBuffer[21] = (microVal >> 16) & 0xFF; microVal = fractions; packetBuffer[22] = (microVal >> 8) & 0xFF; microVal = fractions; packetBuffer[23] = microVal & 0xFF; //オリジナル時間をコピー packetBuffer[24] = packetBuffer[40]; packetBuffer[25] = packetBuffer[41]; packetBuffer[26] = packetBuffer[42]; packetBuffer[27] = packetBuffer[43]; packetBuffer[28] = packetBuffer[44]; packetBuffer[29] = packetBuffer[45]; packetBuffer[30] = packetBuffer[46]; packetBuffer[31] = packetBuffer[47]; //送信時刻をセット rtclock.getTime(tmm); timestamp = numberOfSecondsSince1900Epoch(tmm.year+1970,tmm.month,tmm.day,tmm.hour,tmm.minute,tmm.second,timezone); miliTime = millis() - baseMilis; fractions = uint32_t(float(miliTime % 1000) / unit); tempval = timestamp; packetBuffer[40] = (tempval >> 24) & 0XFF; tempval = timestamp; packetBuffer[41] = (tempval >> 16) & 0xFF; tempval = timestamp; packetBuffer[42] = (tempval >> 8) & 0xFF; tempval = timestamp; packetBuffer[43] = (tempval) & 0xFF; //transmit_timestamp_fractions microVal = fractions; packetBuffer[44] = (microVal >> 24) & 0xFF; microVal = fractions; packetBuffer[45] = (microVal >> 16) & 0xFF; microVal = fractions; packetBuffer[46] = (microVal >> 8) & 0xFF; microVal = fractions; packetBuffer[47] = microVal & 0xFF; // Reply to the IP address and port that sent the NTP request Udp.beginPacket(Remote, PortNum); Udp.write(packetBuffer,NTP_PACKET_SIZE); Udp.endPacket(); digitalWrite(BUILTIN_LED,LOW); //処理完了で消灯 sprintf(buf1,"●%d\t%d\t%d\t%d\n",packetBuffer[20],packetBuffer[21],packetBuffer[22],packetBuffer[23]); Serial.print(buf1); req = true; gNTPqueryCount++; } return req; } //---------------NTP since 1900/01/01 retouch2020/12/25 bugfix 2023/03/01 // EXCELで2023年3月1日の1970/1/1からの経過日数(days)を計算したところ19417だった。 // 以下のプログラムではdaysの値が19418となり 1日ズレることを確認した // アルゴリズムを変更する事にしました。 static unsigned long int numberOfSecondsSince1900Epoch(uint16_t y, uint8_t m, uint8_t d, uint8_t h, uint8_t mm, uint8_t s,float timezone) { //2036年問題が存在する。unsigned long intの桁溢れが発生 //桁溢れが発生したら受信側が対応する必要がある。サーバ側では64bit化するべきだが動かなくなる。 //timezoneをfloat扱いにした /* int leapAdjustment = 0; if (y >= 1970) { y -= 1970; leapAdjustment = 2; // 1970 年はうるう年ではありませんでした。 } uint16_t days = d; for (uint8_t i = 1; i < m; ++i) days += pgm_read_byte(daysInMonth + i - 1); //経過日数 */ /* if (m > 2 && y % 4 == 0) ++days; */ //if (m > 2 && (y + leapAdjustment) % 4 == 0) ++days; // LM: 弱いけど今のところ大丈夫 3月以降で閏年なら1日足す //days += 365 * y + (y + 3) / 4 - 1; //今回修正するアルゴリズムは、1970年1月1日からの日数計算飲み直しです。 //日付、経過月日数、経過年日数(閏年無視)、閏年補正の 和-1 を経過に数押します。 uint16_t days = d; for (uint8_t i = 1; i < m; ++i) days += pgm_read_byte(daysInMonth + i - 1); //経過月日数 days += (y-1970)*365; //経過年日数(閏年無視) if(m>2){ days += (y/4 - y/100 + y/400 -( 1970/4 - 1970/100 + 1970/400)); }else{ --y; days += (y/4 - y/100 + y/400 -( 1970/4 - 1970/100 + 1970/400)); } long offsetTime = long((float(h)- timezone) * 3600.0); return (days-1)*24L*3600L + offsetTime + mm*60L + s + seventyYears; //return days*24L*3600L + offsetTime + mm*60L + s; } //------------------ bool readSensor(void){ if (Serial1.available()) { Serial1.readStringUntil('\n'); //読み捨てて先頭から読めるようにする nmea = Serial1.readStringUntil('\n'); #if debug Serial.println(nmea); #endif //$GNRMC,022757.000,A,3430.62587,N,13316.66175,E,0.63,144.24,231220,,,A,V*02 String nmeames = nmea.substring(3,6); // $GP*** int count = 0; while(!nmeames.equals(chkHader)){ nmea = Serial1.readStringUntil('\n'); nmeames = nmea.substring(3,6); // $GP*** digitalWrite(LED2, count++%2); #if debug Serial.print(nmea);Serial.print(F("\t"));Serial.println(nmeames); #endif if(count>30){ digitalWrite(LED1, HIGH); break; } } int nextPos = 0; if ( nmeames.equals(chkHader)){ // get UTC nextPos = nmea.indexOf(DELIMITER);//int indexOf(char ch, unsigned int fromIndex) const; String shh = nmea.substring(nextPos+1,nextPos+3); String smm = nmea.substring(nextPos+3,nextPos+5); String sss = nmea.substring(nextPos+5,nextPos+7); nextPos = nmea.indexOf(DELIMITER,nextPos+1); //skip UTC nextPos = nmea.indexOf(DELIMITER,nextPos+1); //skip Status nextPos = nmea.indexOf(DELIMITER,nextPos+1); //skip 緯度 nextPos = nmea.indexOf(DELIMITER,nextPos+1); //skip 北緯か南緯か nextPos = nmea.indexOf(DELIMITER,nextPos+1); //skip 経度 nextPos = nmea.indexOf(DELIMITER,nextPos+1); //skip 東経か西経か nextPos = nmea.indexOf(DELIMITER,nextPos+1); //skip 地表における移動の速度[knot] nextPos = nmea.indexOf(DELIMITER,nextPos+1); //skip 地表における移動の真方位[deg] String sdd = nmea.substring(nextPos+1,nextPos+3); String smn = nmea.substring(nextPos+3,nextPos+5); String syy = nmea.substring(nextPos+5,nextPos+7); Serial.println("20" + syy + smn + sdd + shh + smm + sss); tmm.year = syy.toInt() + 2000 - 1970; //Serial.println(tmm.year); if ((tmm.year < 31) || (tmm.year > 100)){ //2001~2079をサポート return false; }else{ tmm.hour = shh.toInt(); hour = tmm.hour; tmm.minute = smm.toInt(); minute = tmm.minute; tmm.second = sss.toInt(); second = tmm.second; tmm.day = sdd.toInt(); day = tmm.day -1; tmm.month = smn.toInt(); month = tmm.month; year = syy.toInt() + 2000; //digitalWrite(LED1, LOW); digitalWrite(LED2, HIGH); return true; } }else{ //digitalWrite(LED1, HIGH); return false; } } } //------------------GPSにアクセスし、取得に成功したら内部時間を更新 bool ReadGPS(){ bool valid = false; if(readSensor()) { if(gLoopCount>60 | !GpsEnable){ rtclock.setTime(tmm); // 時刻の設定 一時間毎に更新 time_t Now = rtclock.getTime() + int(timezone * 3600.0); rtclock.setTime(Now); baseMilis = millis(); //20201224追加 gLoopCount = 1; digitalWrite(LED2, HIGH); GpsEnable = true; } hundredths = 0; // LM: GPS time is always acquired on the second (not used) age = 0; // Not used in this adaptation valid = true; }else{ delay(100); if(gLoopCount%60) Serial.print(F(".")); else{ Serial.println(F(".")); GpsEnable = false; } } if(gLoopCount > 0x7FFFFFF0) gLoopCount = 1; return valid; } //----------------- 割込CallBack void TimeUpdate(void){ //500msec毎に呼ばれる OperatingTime++; //3.9.3で追加 rtclock.getTime(tmm1); //バッファを分ける必要がある if(ClockPoint) tm1637.point(POINT_ON); else tm1637.point(POINT_OFF); TimeDisp[0] = tmm1.hour / 10; TimeDisp[1] = tmm1.hour % 10; TimeDisp[2] = tmm1.minute / 10; TimeDisp[3] = tmm1.minute % 10; ClockPoint = (~ClockPoint) & 0x01; tm1637.display(TimeDisp); } //----------------- int getHourTime(void){ rtclock.getTime(tmm3); return tmm3.hour; } //----------------- RMC文字列を解析してクラスタRMCにセットする void analsysRMC(void){ //$GPRMC,021227.000,A,3459.9060,N,13652.6560,E,0.09,344.80,241220,,,A*6D //$GNRMC,072815.000,V, , , , , , ,241220,,,N,V*27 //RMCは要素数が固定されていないようなので注意が必要 int stPosi = 0; int endPosi; char State; //センテンス endPosi = nmea.indexOf(',',stPosi); if(!(endPosi<0)){ setenceStr = nmea.substring(stPosi,endPosi); Serial.print(F("setenceStr="));Serial.println(setenceStr); } stPosi = endPosi+1; //測位時刻(UTC時分秒) UTC時刻:09時24分03秒307 endPosi = nmea.indexOf(',',stPosi); Serial.print(F("endPosi="));Serial.println(endPosi); if(!(endPosi<0)){ TimeStr = nmea.substring(stPosi,endPosi); Serial.print(F("TimeStr="));Serial.println(TimeStr); } stPosi = endPosi+1; //測位状態 A:単独測位中またはDGPS測位中 V:未測位 単独測位中/DGPS測位中 endPosi = nmea.indexOf(',',stPosi); if(!(endPosi<0)){ StatusStr = nmea.substring(stPosi,endPosi); Serial.print(F("StatusStr="));Serial.println(StatusStr); } stPosi = endPosi+1; //緯度。dddmm.mmmm (10進) 緯度 43.07328 endPosi = nmea.indexOf(',',stPosi); if(!(endPosi<0)){ latitudeStr = nmea.substring(stPosi,endPosi); Serial.print(F("latitudeStr="));Serial.println(latitudeStr); } stPosi = endPosi+1; //N:北緯、S:南緯 北緯 endPosi = nmea.indexOf(',',stPosi); if(!(endPosi<0)){ latitudeNSStr = nmea.substring(stPosi,endPosi); Serial.print(F("latitudeNSStr="));Serial.println(latitudeNSStr); } stPosi = endPosi+1; //経度。dddmm.mmmm (10進) 経度 141.270980 endPosi = nmea.indexOf(',',stPosi); if(!(endPosi<0)){ longitudeStr = nmea.substring(stPosi,endPosi); Serial.print(F("longitudeStr="));Serial.println(longitudeStr); } stPosi = endPosi+1; //E:東経、W:西経 東経 endPosi = nmea.indexOf(',',stPosi); if(!(endPosi<0)){ longitudeEWStr = nmea.substring(stPosi,endPosi); Serial.print(F("longitudeEWStr="));Serial.println(longitudeEWStr); } stPosi = endPosi+1; //速度(000.0〜270.0、単位:ノット) 速度 0 Knot endPosi = nmea.indexOf(',',stPosi); if(!(endPosi<0)){ VeloStr = nmea.substring(stPosi,endPosi); Serial.print(F("VeloStr="));Serial.println(VeloStr); } stPosi = endPosi+1; //真方位による進行方向(000.0〜359.9、単位:度) 240.3度 endPosi = nmea.indexOf(',',stPosi); if(!(endPosi<0)){ DirStr = nmea.substring(stPosi,endPosi); Serial.print(F("DirStr="));Serial.println(DirStr); } stPosi = endPosi+1; //ddmmyy 日付(日、月、年) 日付 2017年7月15日 endPosi = nmea.indexOf(',',stPosi); if(!(endPosi<0)){ DateStr = nmea.substring(stPosi,endPosi); Serial.print(F("DateStr="));Serial.println(DateStr); } stPosi = endPosi+1; //磁気偏差(000.0〜180.0、単位:度) 0(なし) endPosi = nmea.indexOf(',',stPosi); if(!(endPosi<0)){ MagDegStr = nmea.substring(stPosi,endPosi); Serial.print(F("MagDegStr="));Serial.println(MagDegStr); } stPosi = endPosi+1; //磁気偏差 E:磁気偏差が東より W:磁気偏差が西より なし endPosi = nmea.indexOf(',',stPosi); if(!(endPosi<0)){ MagDevStr = nmea.substring(stPosi,endPosi); Serial.print(F("MagDevStr="));Serial.println(MagDevStr); } stPosi = endPosi+1; //測位mode N:未測位 A:単独測位 D:DGPS測位 endPosi = nmea.indexOf(',',stPosi); if(!(endPosi<0)){ ModeStr = nmea.substring(stPosi,endPosi); Serial.print(F("ModeStr="));Serial.println(ModeStr); }else{ ModeStr = nmea.substring(stPosi,stPosi+1); Serial.print(F("ModeStr="));Serial.println(ModeStr); } stPosi = endPosi+1; //ナビゲーション状態 S:Safe C:Caution U:Unsafe V:無効(RAIM 機能 OFF) endPosi = nmea.indexOf(',',stPosi); if(!(endPosi<0)){ NavStr = nmea.substring(stPosi,endPosi); Serial.print(F("NavStr="));Serial.println(NavStr); }else{ NavStr = nmea.substring(stPosi,stPosi+1); Serial.print(F("NavStr="));Serial.println(NavStr); } Status = StatusStr.charAt(0); switch(Status){ case 'A': StatusStr = "A = 有効"; digitalWrite(LED1, LOW); break; case 'V': StatusStr = "V = 警告"; digitalWrite(LED1, HIGH); break; default: StatusStr = ""; digitalWrite(LED1, HIGH); break; } DateTime2Str = "20" + DateStr.substring(4,6) + "年" + DateStr.substring(2,4) + "月" + DateStr.substring(0,2) + "日 " + TimeStr.substring(0,2) + "時" + TimeStr.substring(2,4) + "分" + TimeStr.substring(4,6) + "秒" + TimeStr.substring(7,10); State = latitudeNSStr.charAt(0); switch(State){ case 'N': latitudeNSStr = "北緯 "; break; case 'S': latitudeNSStr = "南緯 "; break; default: latitudeNSStr = ""; break; } stPosi = 0; endPosi = latitudeStr.indexOf('.',stPosi); if(endPosi>0){ String latitudeStr1 = latitudeStr.substring(stPosi,endPosi-2); String latitudeStr2 = latitudeStr.substring(endPosi-2,endPosi); String latitudeStr3 = "." + latitudeStr.substring(endPosi+1); latitude = latitudeStr1.toFloat() + (latitudeStr2.toFloat()/60.0) + (latitudeStr3.toFloat()/60.0); if(State == 'S') latitude = -latitude; latitudeStr = latitudeStr1 + "度" + latitudeStr2 + "分" + latitudeStr3; } State = longitudeEWStr.charAt(0); switch(State){ case 'E': longitudeEWStr = "東経 "; break; case 'W': longitudeEWStr = "西経 "; break; default: longitudeEWStr = ""; break; } stPosi = 0; endPosi = longitudeStr.indexOf('.',stPosi); if(endPosi>0){ String longitudeStr1 = longitudeStr.substring(stPosi,endPosi-2); String longitudeStr2 = longitudeStr.substring(endPosi-2,endPosi); String longitudeStr3 = "." + longitudeStr.substring(endPosi+1); longitude = longitudeStr1.toFloat() + (longitudeStr2.toFloat()/60.0) + (longitudeStr3.toFloat()/60.0); if(State == 'W') longitude = -longitude; longitudeStr = longitudeStr1 + "度" + longitudeStr2 + "分" + longitudeStr3; } State = ModeStr.charAt(0); switch(State){ case 'N': ModeStr = "N = データなし"; break; case 'A': ModeStr = "A = Autonomous(自律方式)"; break; case 'D': ModeStr = "D = Differential(干渉測位方式)"; break; case 'E': ModeStr = "E = Estimated(推定)"; break; case 'M': ModeStr = "M = マニュアル入力"; break; case 'S': ModeStr = "S = シミュレーター入力"; break; default: ModeStr = State; break; } gRmc.UTC = timestamp; gRmc.LAT = long(latitude * 10000.0); gRmc.LON = long(longitude * 10000.0); gRmc.SPD = int(VeloStr.toFloat() * 100.0); gRmc.DIR = int(DirStr.toFloat() * 100.0); } //------------------ bool ReadBME280(void){ bme.read(pres, temp, hum, tempUnit, presUnit); //温度をセット if(ErrorTMP == false) gTemp = temp; //湿度をセット if(ErrorHUM == false) gHumi = hum; //圧力をセット if(ErrorPRS == false) gPres = pres; return true; } //------------------ void setup() { Serial.begin(115200); //シリアル通信を開ます。 delay(1000); Serial.println(F("Serial interface started")); pinMode(BUILTIN_LED,OUTPUT); pinMode(LED1,OUTPUT);digitalWrite(LED1, HIGH); pinMode(LED2,OUTPUT);digitalWrite(LED2, LOW); digitalWrite(BUILTIN_LED,LOW); //LED消灯 //TM1637 init Timmer Setup tm1637.set(); tm1637.init(); Timer1.pause(); // Timer1.setPrescaleFactor(7200); // 72MHz100uSEC Timer1.setOverflow(5000); // 500mSEC Timer1.attachInterrupt(TIMER_UPDATE_INTERRUPT,TimeUpdate); // 割り込みサーブルーチンの宣言:TimeUpdate Timer1.setCount(0); // 0 Timer1.refresh(); Timer1.resume(); // start Ethernet and UDP:(Ethernet3API) Ethernet.setCsPin(SPI1_NSS_PIN); Ethernet.setRstPin(W550io_Rst); // pinMode(W550io_Rst, OUTPUT); digitalWrite(W550io_Rst, LOW); delay(10); digitalWrite(W550io_Rst, HIGH); Serial.print(F("NIC_Reset\n")); LANSetup(); //Ethernet.begin(mac,ip); Serial.println(F("Ethernet interface started")); Serial.print(F("server is at "));Serial.println(Ethernet.localIP()); webServer.begin(); Udp.begin(NTP_PORT); // //I2Cは接続されているかどうかチェックしてから対応することにした。 Wire.begin(); Serial.print(F("I2C_Start\n")); //BME280初期化 BMP280の対策 while(!bme.begin()) { Serial.println(F("Could not find BME280 sensor!")); delay(1000); } // bme.chipID(); // Deprecated. See chipModel(). switch(bme.chipModel()){ case BME280::ChipModel_BME280: Serial.println(F("Found BME280 sensor! Success.")); break; case BME280::ChipModel_BMP280: Serial.println(F("Found BMP280 sensor! No Humidity available.")); ErrorHUM = true; break; default: Serial.println(F("Found UNKNOWN sensor! Error!")); ErrorTMP = true; ErrorHUM = true; ErrorPRS = true; } //BME280計測 計測値はグローバルにセット ReadBME280(); // #if debug Serial.print("Version:");Serial.println(vers); #endif //GPS受信機との通信を開始する Serial1.begin(9600); //9600bps固定の場合 bool valid = ReadGPS(); //GPS取得に成功したら内部時計を更新しています。 if(Serial) Serial.println(F("ReadGPS")); rtclock.getTime(tmm); if(Serial) Serial.println(F("GetTime")); for(int i = 0;i<24;i++){ for(int j = 0;j<6;j++){ //3.9.2で6要素に変更 gDayBuf[i][j] = NAN; } } //sprintf(STRBUF,"%02u%02u%02u",tmm.hour,tmm.minute,tmm.second); //LedAndKey.setDisplayToString(STRBUF,0,2); //LedAndKey.setDisplayToString(" HELLO ",0,0); iwdg_init_ms(30000); //ウォッチドッグをオンにして、オーバーフロー時間を設定します。 //3.8.2で30000<−8000変更 Serial.println(F("EndSetUp")); } //-------------------- int hCount = 0; int preSet = -1; void loop() { //このLoopはscantime=1秒程度です。そのため5秒経っても戻らなかったらリセットすることにします。 iwdg_feed(); //ウォッチドッグタイマーをリセットするには、ドッグ操作をフィードします fPhyState = Ethernet.phyState(); if(Serial) Serial.println(fPhyState); if(!fPhyState){ nvic_sys_reset(); //再起動の実行 } //参考にしたコードはNTPdは常に動作させ、クライアントから時刻同期要求が有ったら //GPSにアクセスするというモノでした。 //この修正版は、NTPdは常に動作させ、要求があったらBluePillの内部クロックを返します。 //また、NTPdの周回待ちにGPSにアクセスし、値を取得したら内部時間を更新します。 //delay(500); bool valid = ReadGPS(); ReadBME280(); //delay(500); client = webServer.available(); //HTTP要求確認 if(client) checkWebPage(client); client.stop(); // コネクションを閉じる。 bool req = processNTP(); //NTP要求確認 if(req){ hCount = 0; //digitalWrite(LED1, HIGH); //processNTP()内で処理 } int h = getHourTime(); //返値0-23 if((Status == 'A') && (preSet != h)){ //1時間おきに時刻を確認し、データをメモリに確保 analsysRMC(); preSet = h; gDayBuf[h][0] = float(gRmc.LAT) / 10000.0; gDayBuf[h][1] = float(gRmc.LON) / 10000.0; gDayBuf[h][2] = float(gRmc.SPD) / 100.0; gDayBuf[h][3] = float(gRmc.DIR) / 100.0; gDayBuf[h][4] = gTemp; gDayBuf[h][5] = gPres; //次回分のデータをNULL化 h = (h+1) % 24; gDayBuf[h][0] = NAN; gDayBuf[h][1] = NAN; gDayBuf[h][2] = NAN; gDayBuf[h][3] = NAN; gDayBuf[h][4] = NAN; gDayBuf[h][5] = NAN; } gLoopCount++; if((hCount++ > 10) && !req && valid){ digitalWrite(LED1, LOW); hCount = 0; } } |
オブジェクト自体はベクター殿のストレージをお借りしています。
https://www.vector.co.jp/vpack/browse/person/an051501.html
免責事項
本ソフトウエアは、あなたに対して何も保証しません。本ソフトウエアの関係者(他の利用者も含む)は、あなたに対して一切責任を負いません。
あなたが、本ソフトウエアを利用(コンパイル後の再利用など全てを含む)する場合は、自己責任で行う必要があります。本ソフトウエアの著作権はToolsBoxに帰属します。
本ソフトウエアをご利用の結果生じた損害について、ToolsBoxは一切責任を負いません。
ToolsBoxはコンテンツとして提供する全ての文章、画像等について、内容の合法性・正確性・安全性等、において最善の注意をし、作成していますが、保証するものではありません。
ToolsBoxはリンクをしている外部サイトについては、何ら保証しません。
ToolsBoxは事前の予告無く、本ソフトウエアの開発・提供を中止する可能性があります。
商標・登録商標
Microsoft、Windows、WindowsNTは米国Microsoft Corporationの米国およびその他の国における登録商標です。
Windows Vista、Windows XPは、米国Microsoft Corporation.の商品名称です。
LabVIEW、National Instruments、NI、ni.comはNational Instrumentsの登録商標です。
I2Cは、NXP Semiconductors社の登録商標です。
その他の企業名ならびに製品名は、それぞれの会社の商標もしくは登録商標です。
すべての商標および登録商標は、それぞれの所有者に帰属します。