/[H9]/trunk/FlisServer/FlisServerDlg.cpp
ViewVC logotype

Contents of /trunk/FlisServer/FlisServerDlg.cpp

Parent Directory Parent Directory | Revision Log Revision Log


Revision 132 - (show annotations) (download)
Mon Dec 3 11:46:03 2007 UTC (16 years, 5 months ago) by kevin
File size: 17841 byte(s)
server software rdy for review
1 // FlisServerDlg.cpp : implementation file
2 //
3
4 #include "stdafx.h"
5 #include "FlisServer.h"
6 #include "FlisServerDlg.h"
7 #include <vector>
8 #include ".\flisserverdlg.h"
9
10 #ifdef _DEBUG
11 #define new DEBUG_NEW
12 #endif
13
14
15 // CAboutDlg dialog used for App About
16
17 class CAboutDlg : public CDialog
18 {
19 public:
20 CAboutDlg();
21
22 // Dialog Data
23 enum { IDD = IDD_ABOUTBOX };
24
25 protected:
26 virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support
27
28 // Implementation
29 protected:
30 DECLARE_MESSAGE_MAP()
31 };
32
33 CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD)
34 {
35 }
36
37 void CAboutDlg::DoDataExchange(CDataExchange* pDX)
38 {
39 CDialog::DoDataExchange(pDX);
40 }
41
42 BEGIN_MESSAGE_MAP(CAboutDlg, CDialog)
43 END_MESSAGE_MAP()
44
45
46 // CFlisServerDlg dialog
47
48
49
50 CFlisServerDlg::CFlisServerDlg(CWnd* pParent /*=NULL*/)
51 : CDialog(CFlisServerDlg::IDD, pParent)
52 {
53 m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
54 }
55
56 void CFlisServerDlg::DoDataExchange(CDataExchange* pDX)
57 {
58 CDialog::DoDataExchange(pDX);
59 DDX_Control(pDX, IDC_Textwindow, m_Textwindow);
60 }
61
62 BEGIN_MESSAGE_MAP(CFlisServerDlg, CDialog)
63 ON_WM_SYSCOMMAND()
64 ON_WM_PAINT()
65 ON_WM_QUERYDRAGICON()
66 ON_MESSAGE(UWM_MYMESSAGE, OnShowString)
67 //}}AFX_MSG_MAP
68 ON_BN_CLICKED(IDCLOSE, OnBnClickedClose)
69 ON_BN_CLICKED(IDC_GSMPIN, OnBnClickedGsmpin)
70 ON_BN_CLICKED(IDC_Start, OnBnClickedStart)
71 END_MESSAGE_MAP()
72
73
74 // CFlisServerDlg message handlers
75
76 BOOL CFlisServerDlg::OnInitDialog()
77 {
78 CDialog::OnInitDialog();
79
80 // Add "About..." menu item to system menu.
81
82 // IDM_ABOUTBOX must be in the system command range.
83 ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
84 ASSERT(IDM_ABOUTBOX < 0xF000);
85
86 CMenu* pSysMenu = GetSystemMenu(FALSE);
87 if (pSysMenu != NULL)
88 {
89 CString strAboutMenu;
90 strAboutMenu.LoadString(IDS_ABOUTBOX);
91 if (!strAboutMenu.IsEmpty())
92 {
93 pSysMenu->AppendMenu(MF_SEPARATOR);
94 pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
95 }
96 }
97
98 // Set the icon for this dialog. The framework does this automatically
99 // when the application's main window is not a dialog
100 SetIcon(m_hIcon, TRUE); // Set big icon
101 SetIcon(m_hIcon, FALSE); // Set small icon
102
103 // TODO: Add extra initialization here
104 StartSerial();
105 DBConnect();
106 ResetSms = 0;
107
108 return TRUE; // return TRUE unless you set the focus to a control
109 }
110
111 void CFlisServerDlg::OnSysCommand(UINT nID, LPARAM lParam)
112 {
113 if ((nID & 0xFFF0) == IDM_ABOUTBOX)
114 {
115 CAboutDlg dlgAbout;
116 dlgAbout.DoModal();
117 }
118 else
119 {
120 CDialog::OnSysCommand(nID, lParam);
121 }
122 }
123
124 // If you add a minimize button to your dialog, you will need the code below
125 // to draw the icon. For MFC applications using the document/view model,
126 // this is automatically done for you by the framework.
127
128 void CFlisServerDlg::OnPaint()
129 {
130 if (IsIconic())
131 {
132 CPaintDC dc(this); // device context for painting
133
134 SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);
135
136 // Center icon in client rectangle
137 int cxIcon = GetSystemMetrics(SM_CXICON);
138 int cyIcon = GetSystemMetrics(SM_CYICON);
139 CRect rect;
140 GetClientRect(&rect);
141 int x = (rect.Width() - cxIcon + 1) / 2;
142 int y = (rect.Height() - cyIcon + 1) / 2;
143
144 // Draw the icon
145 dc.DrawIcon(x, y, m_hIcon);
146 }
147 else
148 {
149 CDialog::OnPaint();
150 }
151 }
152
153 // The system calls this function to obtain the cursor to display while the user drags
154 // the minimized window.
155 HCURSOR CFlisServerDlg::OnQueryDragIcon()
156 {
157 return static_cast<HCURSOR>(m_hIcon);
158 }
159 int CFlisServerDlg::StartSerial(void)
160 {
161 int Baud;
162 CString SerialPort = "COM3";
163
164 Baud = 1200;
165 if( Serial.isOpen() )
166 {
167 Serial.close();
168 Serial.open( SerialPort, Baud );
169 }
170 else
171 {
172 Serial.open( SerialPort, Baud );
173 }
174
175
176 return 0;
177 }
178 std::vector<unsigned char> CFlisServerDlg::readFrame()
179 {
180 std::vector<unsigned char> buf;
181 while(Serial.getComstat().cbInQue > 0)
182 {
183 unsigned char data = Serial.readByte();
184
185 buf.push_back(data);
186 }
187 return buf;
188 }
189
190 void CFlisServerDlg::writeFrame(std::vector<unsigned char> data)
191 {
192 for (int i=0; i<data.size(); i++)
193 {
194 Serial.writeByte( data[i] );
195 Sleep(5);
196 }
197 Serial.writeByte(0x0D);
198 Sleep(100);
199
200 }
201 void CFlisServerDlg::SetPin()
202 {
203 CString tekst;
204 std::vector<unsigned char> data;
205 data.push_back('a');
206 data.push_back('t');
207 data.push_back('+');
208 data.push_back('c');
209 data.push_back('p');
210 data.push_back('i');
211 data.push_back('n');
212 data.push_back('=');
213 data.push_back('2');
214 data.push_back('5');
215 data.push_back('9');
216 data.push_back('5');
217
218 writeFrame(data);
219 }
220 void CFlisServerDlg::SendSmsData(std::vector<unsigned char> data)
221 {
222
223 for (int i=0; i<data.size(); i++)
224 {
225 Serial.writeByte( data[i] );
226 Sleep(5);
227 }
228 Serial.writeByte(0x1A);
229 Sleep(3000);
230 if(Serial.getComstat().cbInQue > 0)
231 {
232 CString tekst;
233 std::vector<unsigned char> answer = readFrame();
234 Sleep(50);
235 char array1[150];
236 int i;
237 for (int i=0; i<answer.size(); i++)
238 {
239 array1[i] = answer[i];
240 }
241
242 for (int i=0; i<answer.size(); i++)
243 {
244 if ((array1[i] != 0x0A) && (array1[i] != 0x0D))
245 {
246 tekst.AppendChar(array1[i]);
247 }
248 }
249 m_Textwindow.SetWindowText(tekst);
250 }
251
252 }
253 void CFlisServerDlg::SendSmsHead(std::vector<unsigned char> data)
254 {
255 vector<unsigned char> atcommand;
256 atcommand.push_back('a');
257 atcommand.push_back('t');
258 atcommand.push_back('+');
259 atcommand.push_back('c');
260 atcommand.push_back('m');
261 atcommand.push_back('g');
262 atcommand.push_back('s');
263 atcommand.push_back('=');
264 atcommand.push_back('"');
265 int s = (atcommand.size() -1 );
266
267 for (int i=0; i<(atcommand.size()); i++)
268 {
269 Serial.writeByte( atcommand[i] );
270 Sleep(5);
271 }
272
273 for (int i=0; i<data.size(); i++)
274 {
275 Serial.writeByte( data[i] );
276 Sleep(5);
277 }
278 Serial.writeByte(atcommand[s]);
279 Serial.writeByte(0x0D);
280 Sleep(250);
281 }
282 void CFlisServerDlg::DBConnect()
283 {
284 CString dsn;
285 //dsn.Format("ODBC;Description=asd;DRIVER=PostgreSQL ANSI;SERVER=t-hoerup.dk; uid=serrenab;password=furnacemonitor;database=flisfyr;sslmode=prefer"); //Torben
286 dsn.Format("ODBC;Description=asd;DRIVER=PostgreSQL ANSI;SERVER=192.168.134.132; uid=serrenab;password=furnacemonitor;database=flisfyr;sslmode=prefer"); //skole server
287 db.OpenEx(dsn, CDatabase::noOdbcDialog);
288 }
289 vector<Commands> CFlisServerDlg::DBRead(void)
290 {
291 vector<Commands> buffer;
292
293 CString SQL, IDnr, CommandID, InstallationsID;
294 SQL = "select id,date_trunc('second', created) as created,executed,commandid,installationid from command WHERE executed IS NULL;";
295 CRecordset rs(&db);
296 rs.Open(AFX_DB_USE_DEFAULT_TYPE, SQL);
297 if (rs.GetRecordCount()>0)
298 {
299 rs.MoveFirst();
300 while(!rs.IsEOF())
301 {
302 Commands Mycom;
303 rs.GetFieldValue((short)0, IDnr);
304 rs.GetFieldValue(3, CommandID);
305 rs.GetFieldValue(4, InstallationsID);
306
307 Mycom.IDnr = IDnr;
308 Mycom.CommandID = CommandID;
309 Mycom.InstallationsID = InstallationsID;
310
311 buffer.push_back(Mycom);
312 rs.MoveNext();
313 }
314 }
315 rs.Close();
316 return buffer;
317 }
318 void CFlisServerDlg::ReadSms()
319 {
320 CString tekst, oldtekst;
321 Sleep(950);
322 if(Serial.getComstat().cbInQue > 0)
323 {
324 std::vector<unsigned char> answer = readFrame();
325 Sleep(50);
326 char array1[250];
327 int i;
328 for (int i=0; i<answer.size(); i++)
329 {
330 array1[i] = answer[i];
331 }
332
333 for (int i=0; i<answer.size(); i++)
334 {
335 if ((array1[i] != 0x0A) && (array1[i] != 0x0D))
336 {
337 tekst.AppendChar(array1[i]);
338 }
339 }
340
341 m_Textwindow.GetWindowText(oldtekst);
342 oldtekst.Append("\r\n");
343 oldtekst.Append(tekst);
344 m_Textwindow.SetWindowText(oldtekst);
345 SmsSplit(tekst);
346 }
347 }
348 void CFlisServerDlg::SmsSplit(CString data)
349 {
350 CString FyrData, TlfNr, SmsCount, Temper, Flamme, Flis, FremFejl, PowerFail, oldtekst;
351 char CharData[150];
352 strcpy(CharData,data);
353
354 int s=24;
355 for (int i=0; i<=7; i++)
356 {
357 TlfNr.AppendChar(CharData[s]);
358 s++;
359 }
360
361 for (int s=57; s<=(data.GetLength()-3); s++)
362 {
363 FyrData.AppendChar(CharData[s]);
364 }
365 FyrData.Append(":");
366
367 SmsCount = Splitter(FyrData);
368 Temper = Splitter(FyrData);
369 Flamme = Splitter(FyrData);
370 Flis = Splitter(FyrData);
371 FremFejl = Splitter(FyrData);
372 PowerFail = Splitter(FyrData);
373
374 CString SQL, Textwindow, InstallNR;
375 SQL.Format("select ID from installation where installationphonenr=%s",TlfNr);
376
377 CRecordset rs(&db);
378 rs.Open(AFX_DB_USE_DEFAULT_TYPE, SQL);
379 if (rs.GetRecordCount()>0)
380 {
381 rs.MoveFirst();
382 rs.GetFieldValue((short)0,InstallNR);
383 }
384 rs.Close();
385
386 SQL.Format("insert into logtable (logtime,temperature,flamedetector,solidfuelempty,conveyorerror,powerfailure,messagenr,installationnr) Values (now(),%s,'%s','%s','%s','%s',%s,%s)",Temper, Flamme, Flis, FremFejl, PowerFail, SmsCount,InstallNR);
387 db.ExecuteSQL(SQL);
388
389 m_Textwindow.GetWindowText(Textwindow);
390 Textwindow.Append("\r\n");
391 Textwindow.Append("Sms added to Log");
392 m_Textwindow.SetWindowText(Textwindow);
393 Sleep(150);
394
395 ///////////////////////////////////////////////////////////////////////////////////////////////////////
396 ///////////////////// Her skal sendes data til databasen //////////////////////////////////////////////
397 }
398 CString CFlisServerDlg::Splitter(CString& fyrdata)
399 {
400 CString Output;
401
402 int pos = fyrdata.Find(':',0);
403 if (pos != -1)
404 {
405 Output = fyrdata.Left(pos);
406 fyrdata = fyrdata.Right( fyrdata.GetLength() - pos -1);
407 }
408 return Output;
409 }
410 void CFlisServerDlg::OnBnClickedClose()
411 {
412 // TODO: Add your control notification handler code here
413 continueThread = 0;
414 DeleteSms();
415
416 Sleep(500);
417 if( Serial.isOpen() )
418 {
419 Serial.close();
420 }
421
422 if(db.IsOpen())
423 {
424 db.Close();
425 }
426
427 OnOK();
428
429 }
430
431 void CFlisServerDlg::OnBnClickedGsmpin()
432 {
433 // TODO: Add your control notification handler code here
434 m_Textwindow.SetWindowText("Indsætter Pinkode, efterfuldt af 60sec pause");
435 UpdateWindow();
436 SetPin();
437 Sleep(50000);
438
439 while (Serial.getComstat().cbInQue > 0)
440 {
441 Serial.readByte(); //Flush the incoming queue
442 }
443
444 OnBnClickedStart();
445 }
446 void CFlisServerDlg::DeleteSms()
447 {
448 vector<unsigned char> atcommand;
449 atcommand.push_back('a');
450 atcommand.push_back('t');
451 atcommand.push_back('+');
452 atcommand.push_back('c');
453 atcommand.push_back('m');
454 atcommand.push_back('g');
455 atcommand.push_back('d');
456 atcommand.push_back('=');
457 atcommand.push_back('1');
458 atcommand.push_back(',');
459 atcommand.push_back('3');
460
461 writeFrame(atcommand);
462 Sleep(500);
463 }
464 LRESULT CFlisServerDlg::OnShowString(WPARAM wParam, LPARAM lParam)
465 {
466 CString *s = (CString*) lParam;
467 GetDlgItem(IDC_Textwindow)->SetWindowText(*s);
468
469 delete s;
470 return 0;
471
472 }UINT threadWrapper(LPVOID thread)
473 {
474 CFlisServerDlg *t = (CFlisServerDlg*) thread;
475 t->runthread();
476 return 0;
477 }
478
479 void CFlisServerDlg::startthread()
480 {
481 AfxBeginThread(threadWrapper, (LPVOID) this);
482 }
483
484 void CFlisServerDlg::runthread()
485 {
486 while (continueThread != 0)
487 {
488 Reader();
489 }
490 }
491 void CFlisServerDlg::Reader()
492 {
493 if(Serial.getComstat().cbInQue > 0)
494 {
495 Sleep(250);
496 std::vector<unsigned char> answer = readFrame();
497 Sleep(500);
498 CString tekst, oldtekst;
499 int lol;
500 char array1[250];
501 int i;
502 for (int i=0; i<answer.size(); i++)
503 {
504 array1[i] = answer[i];
505 }
506
507 for (int i=0; i<answer.size(); i++)
508 {
509 if ((array1[i] != 0x0A) && (array1[i] != 0x0D))
510 {
511 tekst.AppendChar(array1[i]);
512 }
513 }
514 tekst.Append(":");
515
516 CString command;
517 bool plus;
518 int pos = tekst.Find('+',0);
519 if (pos != -1)
520 {
521 plus = true;
522 tekst = tekst.Right( tekst.GetLength() - pos -1);
523 pos = tekst.Find(':');
524 command = tekst.Left(pos);
525 tekst = tekst.Right( tekst.GetLength() - pos -1);
526 }
527
528
529 if(tekst == "OK")
530 {
531 m_Textwindow.GetWindowText(oldtekst);
532 oldtekst.Append("\r\n");
533 oldtekst.Append("OK tekst modtaget");
534 m_Textwindow.SetWindowText(oldtekst);
535 }
536 else if (tekst == "error")
537 {
538 m_Textwindow.GetWindowText(oldtekst);
539 oldtekst.Append("\r\n");
540 oldtekst.Append("error tekst");
541 oldtekst.Append(tekst);
542 m_Textwindow.SetWindowText(oldtekst);
543 }
544 else if (plus = true)
545 {
546 if (command = "cmti")
547 {
548 CString smscount, oldteskst;
549 int pos = tekst.Find(',',0);
550 if (pos != -1)
551 {
552 smscount = tekst.Right( tekst.GetLength() - pos -1);
553 smscount.Remove(':');
554 ResetSms++;
555 }
556 ////////////////Read sms ting//////////////////
557 std::vector<unsigned char> data;
558 data.push_back('a');
559 data.push_back('t');
560 data.push_back('+');
561 data.push_back('c');
562 data.push_back('m');
563 data.push_back('g');
564 data.push_back('r');
565 data.push_back('=');
566
567 for (int i=0; i< smscount.GetLength(); i++)
568 {
569 data.push_back(smscount[i]);
570 }
571 m_Textwindow.GetWindowText(oldteskst);
572 oldteskst.Append("\r\n");
573 for (int i=0; i<data.size();i++)
574 {
575 oldteskst.AppendChar(data[i]);
576 }
577 m_Textwindow.SetWindowText(oldteskst);
578 writeFrame(data);
579 Sleep(200);
580 ReadSms();
581 ResetSms++;
582
583 }
584 else if(command = "wind")
585 {
586 m_Textwindow.GetWindowText(oldtekst);
587 oldtekst.Append("\r\n");
588 oldtekst.Append("Først lidt tekst på næste linie\r\n");
589 oldtekst.Append(tekst);
590 m_Textwindow.SetWindowText(oldtekst);
591 }
592 }
593 else
594 {
595 m_Textwindow.GetWindowText(oldtekst);
596 oldtekst.Append("\r\n");
597 oldtekst.Append("Anden tekst end forventet");
598 oldtekst.Append("\r\n");
599 oldtekst.Append(tekst);
600 m_Textwindow.SetWindowText(oldtekst);
601 }
602 if(ResetSms == 20)
603 {
604 DeleteSms();
605 m_Textwindow.GetWindowText(oldtekst);
606 oldtekst.Append("\r\n");
607 oldtekst.Append("Slettet Sendte og læste beskeder da vi nåede grænsen.");
608 m_Textwindow.SetWindowText(oldtekst);
609 }
610
611 }
612 if (continueThread = 1)
613 {
614
615 CString testdata, dataframe,testprint, sIDnr, sCommandID, sInstallationsID, sImei;
616 int commandtest = 0;
617 int iAll = 1;
618 std::vector<Commands> data;
619 data = DBRead();
620 for (int i=0; i<data.size(); i++)
621 {
622 testdata.Append(data[i].IDnr);
623 testdata.Append(":");
624 testdata.Append(data[i].CommandID);
625 testdata.Append(":");
626 commandtest = atoi(data[i].CommandID);
627
628 if(commandtest > 1)
629 {
630 testdata.Append(data[i].InstallationsID);
631 testdata.Append(":");
632 }
633 }
634 sIDnr = Splitter(testdata);
635 sCommandID = Splitter(testdata);
636 sInstallationsID = "0";
637 if (commandtest > 1)
638 {
639 sInstallationsID = Splitter(testdata);
640 iAll = 0;
641 }
642
643 if (sIDnr.GetLength() > 0)
644 {
645 DBReadData(sIDnr,sCommandID,sInstallationsID);
646 }
647 Sleep(500);
648 }
649 }
650 void CFlisServerDlg::DBReadData(CString IDnr,CString CommandID,CString InstallationsID)
651 {
652 CString ServerTlfNr;
653 int i = 0;
654 ServerTlfNr = "29860132";
655
656
657 std::vector<Installation> inst;
658 inst = DBReadPhone(InstallationsID);
659 while (i < inst.size())
660 {
661 CString TlfNr, Imei, updaterate;
662 TlfNr.Empty();
663 Imei.Empty();
664 updaterate.Empty();
665
666 CString dataen = inst[i].InstPhoneNr;
667 TlfNr.Append(dataen);
668 CString Imeidata = inst[i].Imei;
669 Imei.Append(Imeidata);
670 CString updaterat = inst[i].Updaterate;
671 updaterate.Append(updaterat);
672 i++;
673
674 vector<unsigned char> tlfnr;
675 for (int i=0; i<TlfNr.GetLength(); i++)
676 {
677 tlfnr.push_back(TlfNr[i]);
678 }
679
680 int calcimei;
681 __int64 buf;
682
683 buf = atof(Imei);
684
685 calcimei = tversum(buf);
686
687 Imei.Format("%d",calcimei);
688
689 SendSmsHead(tlfnr);
690 Sleep(250);
691
692 vector<unsigned char> smsdata;
693
694 for (int i=0; i<Imei.GetLength(); i++)
695 {
696 smsdata.push_back(Imei[i]);
697 }
698 smsdata.push_back(':');
699 for (int i=0; i<ServerTlfNr.GetLength(); i++)
700 {
701 smsdata.push_back(ServerTlfNr[i]);
702 }
703 smsdata.push_back(':');
704 for (int i=0; i<updaterate.GetLength(); i++)
705 {
706 smsdata.push_back(updaterate[i]);
707 }
708
709 SendSmsData(smsdata);
710 Sleep(500);
711
712 }
713 CString SQL, Textwindow;
714 SQL.Format("update command set executed=now() where id=%s",IDnr);
715 db.ExecuteSQL(SQL);
716 m_Textwindow.GetWindowText(Textwindow);
717 Textwindow.Append("\r\n");
718 Textwindow.Append("Command executed");
719 m_Textwindow.SetWindowText(Textwindow);
720 Sleep(150);
721
722 }
723 vector<Installation> CFlisServerDlg::DBReadPhone(CString sInstallationsID)
724 {
725 vector<Installation> buffer;
726
727 CString SQL, phonenr, imei, updaterate;
728 int installernull;
729 Installation Myinst;
730 installernull = atoi(sInstallationsID);
731 if (installernull > 1)
732 {
733 SQL.Format("select installationphonenr, imei, updaterate from installation WHERE id = %s", sInstallationsID);
734 }
735 else if(installernull < 2)
736 {
737 SQL.Format("select installationphonenr, imei, updaterate from installation");
738 }
739 CRecordset rs(&db);
740 rs.Open(AFX_DB_USE_DEFAULT_TYPE, SQL);
741 if (rs.GetRecordCount()>0)
742 {
743 rs.MoveFirst();
744 while(!rs.IsEOF())
745 {
746
747 rs.GetFieldValue((short)0,phonenr);
748 rs.GetFieldValue(1,imei);
749 rs.GetFieldValue(2,updaterate);
750
751 Myinst.InstPhoneNr = phonenr;
752 Myinst.Imei = imei;
753 Myinst.Updaterate = updaterate;
754
755 buffer.push_back(Myinst);
756 rs.MoveNext();
757 }
758 }
759 rs.Close();
760 return buffer;
761 }
762 int CFlisServerDlg::tversum(__int64 input)
763 {
764 int sum = 0;
765 while (input > 0)
766 {
767 sum += (input %10);
768 input /= 10;
769 }
770 return sum;
771 }
772 void CFlisServerDlg::OnBnClickedStart()
773 {
774 // TODO: Add your control notification handler code here
775
776 continueThread = 1;
777 m_Textwindow.SetWindowText("Started");
778 AfxBeginThread(threadWrapper,AfxGetMainWnd());
779 }

  ViewVC Help
Powered by ViewVC 1.1.20