TNB Library
TnbNmea0183.h
[詳解]
1#pragma once
13#include "TnbSync.h"
14#include "TnbStrVector.h"
15#include "TnbReport.h"
16#include "TnbQueueingReport.h"
17
18
19
20//TNB Library
21namespace TNB
22{
23
24
25
59{
60public:
61
73 {
74 public:
77 {
78 }
79
84 CLineInfo(const CStrVector& vs) : m_astr(vs)
85 {
86 }
87
92 size_t GetSize(void) const
93 {
94 return m_astr.GetSize();
95 }
96
102 LPCTSTR GetName(void) const
103 {
104 return m_astr.IsEmpty() ? _T("") : m_astr[0];
105 }
106
113 LPCTSTR GetString(INDEX index) const
114 {
115 return m_astr.IsInRange(index) ? m_astr[index] : _T("");
116 }
117
124 int GetInt(INDEX index) const
125 {
126 return m_astr.IsInRange(index) ? m_astr[index].ToInt() : 0;
127 }
128
135 double GetDouble(INDEX index) const
136 {
137 return m_astr.IsInRange(index) ? m_astr[index].ToDouble() : 0.0;
138 }
139
145 CStr MakeLineString(bool withCheckSum = true) const
146 {
147 CStr s;
148 loop ( i, m_astr.GetSize() )
149 {
150 if ( i != 0 )
151 {
152 s += _T(",");
153 }
154 s += m_astr[i];
155 }
156 if ( withCheckSum )
157 {
158 BYTE sum = 0;
159 loop ( i, s.GetLength() )
160 {
161 sum ^= s[i];
162 }
163 s += CStr::Fmt(_T("*%02X"), sum);
164 }
165 return _T("$") + s;
166 }
167 private:
168 CStrVector m_astr;
169 };
170
172 struct TGpGga
173 {
174 WORD hour;
175 WORD minute;
176 WORD second;
177 WORD ms;
178 bool isNorth;
179 double latitude;
180 bool isEast;
181 double longitude;
184 double hdop;
185 double height;
187 double dgpsData;
188 WORD dgpsId;
189 };
190
192 struct TGpGsv
193 {
197 WORD snr;
198 };
199
201 struct TGpRmc
202 {
203 bool isValid;
205 bool isNorth;
206 double latitude;
207 bool isEast;
208 double longitude;
209 double speed;
210 double degree;
211 };
212
215 {
221 };
222
236 {
238 virtual ~IListener(void) {}
244 virtual void OnNmeaReceivedLine(const CLineInfo& li) = 0;
250 virtual void OnNmeaError(EErrorCode e, LPARAM lParam) = 0;
251 };
252
253
254 //----------------------
255
256
259 {
260 }
261
267 void SetEnvironment(IReport* pReport, IListener* pListener = NULL)
268 {
269 m_queue.SetEnvironment(pReport, &m_inner);
270 m_inner.m_pListener = pListener;
271 }
272
280 bool GetZdaData(SYSTEMTIME& _tm, DWORD tick = 2000) const
281 {
282 if ( ExistGps() )
283 {
284 EXCLUSIVE(&m_inner.m_dataSync);
285 FILETIME ft1;
286 FILETIME ft2;
287 if ( ::SystemTimeToFileTime(&m_inner.m_gpZda, &ft1) && ::FileTimeToLocalFileTime(&ft1, &ft2) )
288 {
289 ::FileTimeToSystemTime(&ft2, &_tm);
290 DWORD r = ::GetTickCount() - m_inner.m_tickZda;
291 return (r < tick);
292 }
293 }
294 return false;
295 }
296
304 bool GetGgaData(TGpGga& _gga, DWORD tick = 2000) const
305 {
306 if ( ExistGps() )
307 {
308 EXCLUSIVE(&m_inner.m_dataSync);
309 _gga = m_inner.m_gpGga;
310 DWORD r = ::GetTickCount() - m_inner.m_tickGga;
311 return (r < tick);
312 }
313 return false;
314 }
315
323 bool GetGsvData(CVectorT<TGpGsv>& _gsvs, DWORD tick = 2000) const
324 {
325 if ( ExistGps() )
326 {
327 EXCLUSIVE(&m_inner.m_dataSync);
328 _gsvs = m_inner.m_gpGsvs;
329 DWORD r = ::GetTickCount() - m_inner.m_tickGsv;
330 #ifdef _DEBUG
331 if ( r < tick )
332 {
333 loop ( i, _gsvs.GetSize() )
334 {
335 TRACE2("Nmea;GSV[%d] 衛星No=%d, ", i + 1, _gsvs[i].satelliteNumber);
336 TRACE3("仰角=%d, 方位=%d, SNR=%d\n", _gsvs[i].angleOfElevation, _gsvs[i].direction, _gsvs[i].snr);
337 }
338 }
339 #endif
340 return (r < tick);
341 }
342 return false;
343 }
344
352 bool GetRmcData(TGpRmc& _rmc, DWORD tick = 2000) const
353 {
354 if ( ExistGps() )
355 {
356 EXCLUSIVE(&m_inner.m_dataSync);
357 _rmc = m_inner.m_gpRmc;
358 DWORD r = ::GetTickCount() - m_inner.m_tickRmc;
359 return (r < tick);
360 }
361 return false;
362 }
363
369 virtual bool ExistGps(void) const
370 {
371 if ( ! m_queue.IsAlive() )
372 {
373 //開始していない
374 return false;
375 }
376 return m_queue.IsConnect();
377 }
378
379private:
381 struct TInner : CQueueingReport::IListener
382 {
383 DEFPARENTLISTENER(CNmea0183, IParentListener);
384 //-
385 IParentListener* m_pListener;
386 //- 解析用
387 bool m_isAnalying;
388 BYTE m_checkSum;
389 size_t m_checkSize;
390 CStrVector m_queStr;
391 //- 受信情報
392 CSyncSection m_dataSync;
393 SYSTEMTIME m_gpZda;
394 DWORD m_tickZda;
395 TGpGga m_gpGga;
396 DWORD m_tickGga;
397 CVectorT<TGpGsv> m_gpGsvs;
398 CVectorT<TGpGsv> m_gpGsvsTmp;
399 DWORD m_tickGsv;
400 TGpRmc m_gpRmc;
401 DWORD m_tickRmc;
403 TInner(void) : m_isAnalying(false), m_pListener(NULL)
404 {
405 DWORD w = ::GetTickCount();
406 w += 0x80000000;
407 m_tickZda = w;
408 m_tickGga = w;
409 m_tickGsv = w;
410 }
412 void NotifyNmeaReceivedLine(const CLineInfo& li)
413 {
414 if ( STRLIB::Compare(li.GetName(), _T("GPZDA")) == 0 )
415 {
416 //== Time & Date
417 SYSTEMTIME sm;
418 sm.wYear = static_cast<WORD>(li.GetInt(4));
419 sm.wMonth = static_cast<WORD>(li.GetInt(2));
420 sm.wDay = static_cast<WORD>(li.GetInt(3));
421 sm.wDayOfWeek = 0;
422 double tm = li.GetDouble(1);
423 sm.wHour = static_cast<WORD>(int(tm / 10000) % 100);
424 sm.wMinute = static_cast<WORD>(int(tm / 100) % 100);
425 sm.wSecond = static_cast<WORD>(int(tm) % 100);
426 sm.wMilliseconds = static_cast<WORD>(int(tm * 1000) % 1000);
427 EXCLUSIVE(&m_dataSync);
428 m_tickZda = ::GetTickCount();
429 m_gpZda = sm;
430 }
431 else if ( STRLIB::Compare(li.GetName(), _T("GPRMC")) == 0 )
432 {
433 //== GNSS Data
434 TGpRmc g;
435 // 測位時刻(UTC)
436 double tm = li.GetDouble(1);
437 g.utc.wHour = static_cast<WORD>(int(tm / 10000) % 100);
438 g.utc.wMinute = static_cast<WORD>(int(tm / 100) % 100);
439 g.utc.wSecond = static_cast<WORD>(int(tm) % 100);
440 g.utc.wMilliseconds = static_cast<WORD>(int(tm * 1000) % 1000);
441 // 有効無効
442 g.isValid = (li.GetString(2)[0] == 'A');
443 // 緯度(北緯 南緯)
444 g.latitude = li.GetDouble(3);
445 g.isNorth = (li.GetString(4)[0] == 'N');
446 // 経度 (東経 西経)
447 g.longitude = li.GetDouble(5);
448 g.isEast = (li.GetString(6)[0] == 'E');
449 // 対地速度(ノット)
450 g.speed = li.GetDouble(7);
451 // 進行方向
452 g.degree = li.GetDouble(8);
453 //
454 int sm = li.GetInt(9);
455 int y = sm % 100;
456 g.utc.wYear = static_cast<WORD>(y + ((y < 50) ? 2000 : 1900));
457 g.utc.wMonth = static_cast<WORD>((sm / 100) % 100);
458 g.utc.wDay = static_cast<WORD>((sm / 10000) % 100);
459 g.utc.wDayOfWeek = 0;
460 //
461 EXCLUSIVE(&m_dataSync);
462 m_tickRmc = ::GetTickCount();
463 m_gpRmc = g;
464 }
465 else if ( STRLIB::Compare(li.GetName(), _T("GPGGA")) == 0 )
466 {
467 //== Global Positioning System Fix Data
468 TGpGga g;
469 // 測位時刻(UTC)
470 double tm = li.GetDouble(1);
471 g.hour = static_cast<WORD>(int(tm / 10000) % 100);
472 g.minute = static_cast<WORD>(int(tm / 100) % 100);
473 g.second = static_cast<WORD>(int(tm) % 100);
474 g.ms = static_cast<WORD>(int(tm * 1000) % 1000);
475 // 緯度(北緯 南緯)
476 g.latitude = li.GetDouble(2);
477 g.isNorth = (li.GetString(3)[0] == 'N');
478 // 経度 (東経 西経)
479 g.longitude = li.GetDouble(4);
480 g.isEast = (li.GetString(5)[0] == 'E');
481 //
482 g.gpsQuality = static_cast<WORD>(li.GetInt(6)); // GPSクオリティ 0=受信不能 1=単独測位 2=DGPS
483 g.satelliteNumber = static_cast<WORD>(li.GetInt(7)); // 受信衛星数
484 g.hdop = li.GetDouble(8); // HDOP
485 g.height = li.GetDouble(9); // 平均海水面からのアンテナ高度(m)
486 if ( li.GetString(10)[0] == 'f' )
487 {
488 //feetだったのでmに変換
489 g.height *= 0.3048;
490 }
491 g.altitudeDifference = li.GetDouble(11);// 平均高度差(m)
492 if ( li.GetString(12)[0] == 'f' )
493 {
494 //feetだったのでmに変換
495 g.altitudeDifference *= 0.3048;
496 }
497 g.dgpsData = li.GetDouble(13); // DGPSデータのエイジ(秒)
498 g.dgpsId = static_cast<WORD>(li.GetInt(14)); // DGPS基準局のID
499 //
500 EXCLUSIVE(&m_dataSync);
501 m_tickGga = ::GetTickCount();
502 m_gpGga = g;
503 }
504 else if ( STRLIB::Compare(li.GetName(), _T("GPGSV")) == 0 )
505 {
506 //== Satellites in View
507 int all = li.GetInt(1); // 全メッセージ数
508 int no = li.GetInt(2); // メッセージ番号
509// int sate = li.GetInt(3); //衛星数
510 EXCLUSIVE(&m_dataSync);
511 if ( no == 1 )
512 {
513 m_gpGsvsTmp.RemoveAll();
514 }
515 loop ( i, 4 )
516 {
517 INDEX j = 4 + i * 4;
518 if ( li.GetSize() <= j + 3 )
519 {
520 break;
521 }
522 TGpGsv g;
523 g.satelliteNumber = static_cast<WORD>(li.GetInt(j));
524 g.angleOfElevation = static_cast<WORD>(li.GetInt(j + 1));
525 g.direction = static_cast<WORD>(li.GetInt(j + 2));
526 g.snr = static_cast<WORD>(li.GetInt(j + 3));
527 m_gpGsvsTmp.Add(g);
528 }
529 if ( all == no )
530 {
531 EXCLUSIVE(&m_dataSync);
532// ASSERT( sate == m_gpGsvs.GetSize() ); //いっちするはず
533 m_tickGsv = ::GetTickCount();
534 m_gpGsvs = m_gpGsvsTmp;
535 }
536 }
537 if ( m_pListener != NULL )
538 {
539 m_pListener->OnNmeaReceivedLine(li);
540 }
541 }
543 void NotifyNmeaError(EErrorCode e, LPARAM lParam)
544 {
545 if ( m_pListener != NULL )
546 {
547 m_pListener->OnNmeaError(e, lParam);
548 }
549 }
556 virtual bool OnReportEvent(const CReportEvent& ev)
557 {
558 ASSERT ( ev.HasEvent() );
559 switch ( ev.GetEvent() )
560 {
562 NotifyNmeaError(EC_Disconnect, 0);
563 break;
565 NotifyNmeaError(EC_Connect, 0);
566 break;
567 case ReportEvent_End:
568 NotifyNmeaError(EC_End, 0);
569 break;
570 }
571 return true;
572 }
579 virtual size_t OnReportData(bool boIsLast, const IConstCollectionT<BYTE>& c)
580 {
581 size_t len = c.GetSize();
582 if ( ! m_isAnalying )
583 {
584 //=== ヘッダ検索中
585 if ( len > 0 )
586 {
587 BYTE b = c.At(0);
588 if ( b == '\n' || b == '\r' || b == 0 )
589 {
590 return 1;
591 }
592 if ( b != '$' )
593 {
594 // ヘッダ文字'$'じゃない
595 for ( size_t i = 1; i < len; i++ )
596 {
597 if ( c.At(i) == '$' )
598 {
599 TRACE1("Nmea;skip %d\n", i);
600 NotifyNmeaError(EC_UnknownDatas, i);
601 return i;
602 }
603 }
604 TRACE1("Nmea;skip %d\n", len);
605 NotifyNmeaError(EC_UnknownDatas, len);
606 return len;
607 }
608 }
609 if ( len < 1 + 1 + 3 )
610 {
611 return 0; // 絶対足りない
612 }
613 m_isAnalying = true;
614 m_checkSum = 0;
615 m_checkSize = 1;
616 m_queStr.RemoveAll();
617 TRACE0("Nmea;found header'$'\n");
618 return 1; // '$' をスキップ
619 }
620 //=== ライン解析中
621 loop ( i, len )
622 {
623 BYTE b = c.At(i);
624 if ( b == '\n' || b == '$' || b == 0 )
625 {
626 // 異常
627 TRACE0("Nmea;found known char\n");
628 m_isAnalying = false;
629 NotifyNmeaError(EC_UnknownDatas, i + 1);
630 return i + 1;
631 }
632 else if ( b == '*' && (len - i) < 3 )
633 {
634 return 0; //足りない
635 }
636 else if ( b == ',' || b == '*' )
637 {
638 // , か * の場合
639 CStr s;
640 loop ( j, i )
641 {
642 s += c.At(j);
643 m_checkSum ^= c.At(j);
644 m_checkSize++;
645 }
646 TRACE2("(%d) %s\n", m_queStr.GetSize(), s);
647 m_queStr.Add(s);
648 if ( b == ',' )
649 {
650 // , の場合
651 m_checkSum ^= b;
652 m_checkSize++;
653 return i + 1;
654 }
655 // * の場合
656 if ( len >= 3 )
657 {
658 int h = STRLIB::HexCharToInt(c.At(i + 1));
659 int l = STRLIB::HexCharToInt(c.At(i + 2));
660 if ( h < 0 || l < 0 )
661 {
662 TRACE1("Nmea;invalid checksum char skip %d\n", m_checkSize + 1);
663 NotifyNmeaError(EC_UnknownDatas, m_checkSize + 1);
664 m_isAnalying = false;
665 return i + 1;
666 }
667 BYTE sum = static_cast<BYTE>(h << 4 | l);
668 if ( m_checkSum == sum )
669 {
670 //発見!
671 TRACE0("Nmea;found valid line!!\n");
672 CLineInfo li(m_queStr);
673 NotifyNmeaReceivedLine(li);
674 }
675 else
676 {
677 TRACE1("Nmea;checksum error!! skip %d\n", m_checkSize + 3);
678 if ( m_pListener != NULL )
679 {
680 m_pListener->OnNmeaError(EC_CheckSumError, MAKELONG(m_checkSum, sum));
681 }
682 }
683 m_isAnalying = false;
684 return i + 3;
685 }
686 return i;
687 }
688 }
689 return 0;
690 }
691 };
692
693 //- 読込み用
694 CQueueingReport m_queue;
695 TInner m_inner;
696 friend class CNmea0183Test;
697};
698
699
700
701}; // TNB
702
#define loop(VAR, CNT)
loop構文.
Definition: TnbDef.h:343
キューイングレポート関係のヘッダ
通信報告関係のヘッダ
文字列情報配列管理関係のヘッダ
同期処理関係のヘッダ
NMEA-0183 のライン情報.
Definition: TnbNmea0183.h:73
size_t GetSize(void) const
[取得] パラメータ数取得.
Definition: TnbNmea0183.h:92
double GetDouble(INDEX index) const
[取得] double型取得.
Definition: TnbNmea0183.h:135
LPCTSTR GetString(INDEX index) const
[取得] 文字型取得.
Definition: TnbNmea0183.h:113
LPCTSTR GetName(void) const
[取得] 名前取得.
Definition: TnbNmea0183.h:102
CLineInfo(void)
コンストラクタ
Definition: TnbNmea0183.h:76
CStr MakeLineString(bool withCheckSum=true) const
[作成] 一行文字列作成
Definition: TnbNmea0183.h:145
CLineInfo(const CStrVector &vs)
コンストラクタ
Definition: TnbNmea0183.h:84
int GetInt(INDEX index) const
[取得] int型取得.
Definition: TnbNmea0183.h:124
NMEA-0183 レポートクラス.
Definition: TnbNmea0183.h:59
virtual bool ExistGps(void) const
[確認] GPS確認.
Definition: TnbNmea0183.h:369
bool GetZdaData(SYSTEMTIME &_tm, DWORD tick=2000) const
[取得] ZDAデータ取得.
Definition: TnbNmea0183.h:280
bool GetGsvData(CVectorT< TGpGsv > &_gsvs, DWORD tick=2000) const
[取得] GSVデータ取得.
Definition: TnbNmea0183.h:323
bool GetRmcData(TGpRmc &_rmc, DWORD tick=2000) const
[取得] RMCデータ取得.
Definition: TnbNmea0183.h:352
bool GetGgaData(TGpGga &_gga, DWORD tick=2000) const
[取得] GGAデータ取得.
Definition: TnbNmea0183.h:304
void SetEnvironment(IReport *pReport, IListener *pListener=NULL)
[設定] 環境設定.
Definition: TnbNmea0183.h:267
@ EC_End
終了。クローズされた。lParam は0。
Definition: TnbNmea0183.h:220
@ EC_UnknownDatas
不明なデータ(SkipData)。lParamは長さ。
Definition: TnbNmea0183.h:216
@ EC_CheckSumError
チェックサムエラー。HIWORD(lParam)は受信SUM、LOWORD(lParam) は計算SUM。
Definition: TnbNmea0183.h:217
@ EC_Disconnect
切断。lParam は0。
Definition: TnbNmea0183.h:219
@ EC_Connect
接続。lParam は0。
Definition: TnbNmea0183.h:218
CNmea0183(void)
コンストラクタ
Definition: TnbNmea0183.h:258
キューイングレポートクラス
void SetEnvironment(IReport *pReport, IListener *pListener)
[設定] 環境設定
bool IsAlive(void) const
[確認] Aliveチェック.
bool IsConnect(void) const
[確認] 接続チェック
通信受信イベント管理クラス
Definition: TnbReport.h:70
bool HasEvent(void) const
[確認] Event(Error)を持っている
Definition: TnbReport.h:130
EReportEvent GetEvent(void) const
[取得] イベントコード取得
Definition: TnbReport.h:157
size_t GetLength(void) const
[取得] 文字列長
Definition: TnbStr.h:518
static CStrT Fmt(const TCHAR *lpszFormat,...)
[作成] 書式付き文字列作成
Definition: TnbStr.h:1206
Section排他管理クラス
Definition: TnbSync.h:125
virtual size_t GetSize(void) const
[取得] サイズ取得
Definition: TnbVector.h:368
virtual bool RemoveAll(void)
[削除] 空化
Definition: TnbVector.h:565
virtual INDEX Add(const TYP &t)
[追加] 要素一つ追加.
Definition: TnbVector.h:383
int Compare(LPCSTR P1, LPCSTR P2, INT_PTR len=-1, DWORD dwCmpFlags=0)
[比較] 文字列比較(ASCII/SJIS用)
Definition: TnbStrLib.h:135
int HexCharToInt(int c)
[変換] HEX文字数値変換
Definition: TnbStrLib.h:492
#define EXCLUSIVE(CLS)
簡易排他制御マクロ.
Definition: TnbSync.h:788
TNB Library
Definition: TnbDoxyTitle.txt:2
@ ReportEvent_End
終了.
Definition: TnbReport.h:31
@ ReportEvent_Connect
接続.
Definition: TnbReport.h:33
@ ReportEvent_Disconnect
切断.
Definition: TnbReport.h:34
ファイルタイム型.
Definition: TnbTime.h:893
システムタイム型.
Definition: TnbTime.h:876
WORD wYear
Definition: TnbTime.h:877
WORD wSecond
Definition: TnbTime.h:883
WORD wMilliseconds
ミリ秒
Definition: TnbTime.h:884
WORD wMonth
Definition: TnbTime.h:878
WORD wHour
Definition: TnbTime.h:881
WORD wDayOfWeek
曜日
Definition: TnbTime.h:879
WORD wDay
Definition: TnbTime.h:880
WORD wMinute
Definition: TnbTime.h:882
NMEA-0183 のリスナーインターフェース.
Definition: TnbNmea0183.h:236
virtual ~IListener(void)
デストラクタ
Definition: TnbNmea0183.h:238
virtual void OnNmeaError(EErrorCode e, LPARAM lParam)=0
[通知] エラー通知.
virtual void OnNmeaReceivedLine(const CLineInfo &li)=0
[通知] 正常ライン取得通知.
Global Positioning System Fix Data
Definition: TnbNmea0183.h:173
WORD dgpsId
DGPS基準局のID
Definition: TnbNmea0183.h:188
WORD minute
測位時刻(UTC) 分
Definition: TnbNmea0183.h:175
double dgpsData
DGPSデータのエイジ(秒)
Definition: TnbNmea0183.h:187
WORD second
測位時刻(UTC) 秒
Definition: TnbNmea0183.h:176
bool isNorth
北緯 南緯
Definition: TnbNmea0183.h:178
double latitude
緯度
Definition: TnbNmea0183.h:179
double height
平均海水面からのアンテナ高度(m)
Definition: TnbNmea0183.h:185
WORD gpsQuality
GPSクオリティ 0=受信不能 1=単独測位 2=DGPS
Definition: TnbNmea0183.h:182
WORD hour
測位時刻(UTC) 時間
Definition: TnbNmea0183.h:174
bool isEast
東経 西経
Definition: TnbNmea0183.h:180
double longitude
経度
Definition: TnbNmea0183.h:181
double altitudeDifference
平均高度差
Definition: TnbNmea0183.h:186
WORD ms
測位時刻(UTC) ミリ秒
Definition: TnbNmea0183.h:177
WORD satelliteNumber
受信衛星数
Definition: TnbNmea0183.h:183
Satellites in View
Definition: TnbNmea0183.h:193
WORD snr
SNR(デシベル)
Definition: TnbNmea0183.h:197
WORD angleOfElevation
仰角
Definition: TnbNmea0183.h:195
WORD satelliteNumber
衛星番号
Definition: TnbNmea0183.h:194
Recommended Minimum Specific GNSS Data
Definition: TnbNmea0183.h:202
bool isNorth
北緯 南緯
Definition: TnbNmea0183.h:205
double speed
対地速度(ノット)
Definition: TnbNmea0183.h:209
double latitude
緯度
Definition: TnbNmea0183.h:206
double degree
進行方向
Definition: TnbNmea0183.h:210
bool isValid
有効無効
Definition: TnbNmea0183.h:203
bool isEast
東経 西経
Definition: TnbNmea0183.h:207
double longitude
経度
Definition: TnbNmea0183.h:208
SYSTEMTIME utc
測位時刻(UTC)
Definition: TnbNmea0183.h:204
キューイングレポートのリスナーインターフェース
bool IsEmpty(void) const
[確認] 要素の有無確認.
virtual const TYP & At(INDEX index) const =0
[取得] 要素の参照取得.
bool IsInRange(INDEX index) const
[確認] INDEXの有効確認.
virtual size_t GetSize(void) const =0
[取得] 要素数取得.
通信アクセスインターフェース
Definition: TnbReport.h:227