Barometer program : Data Logger function includes Barometer & temperature (BMP180), Humidity & temp. (RHT03), Sunshine (Cds), RTC(M41T62) data. : Logging data saves into EEPROM (AT24C1024) using ring buffer function.
Dependencies: AT24C1024 RHT03 TextLCD BMP180 M41T62
Fork of mbed_blinky by
mon.cpp
00001 /* 00002 * mbed Application program 00003 * Data logging & Monitor program for only LPC1114FN28 00004 * 00005 * Copyright (c) 2010,'14,'20 Kenji Arai / JH1PJL 00006 * http://www7b.biglobe.ne.jp/~kenjia/ 00007 * https://os.mbed.com/users/kenjiArai/ 00008 * Created: May 15th, 2010 00009 * Spareted: June 25th, 2014 mon() & mon_hw() 00010 * Revised: August 8th, 2020 00011 */ 00012 00013 // Include -------------------------------------------------------------------- 00014 #include "mbed.h" 00015 #include "m41t62_rtc.h" // Own lib. / RTC control 00016 #include "AT24C1024.h" // Own lib. / EEPROM control 00017 #include "dt_log.h" 00018 #include "redirect_stdio.h" 00019 00020 // Object --------------------------------------------------------------------- 00021 extern I2C xi2c; // SDA, SCL 00022 extern M41T62 xm41t62; // STmicro RTC(M41T62) 00023 AT24C1024 at24c1024(dp5,dp27); // Atmel 1Mbit EE-PROM 00024 00025 // Definition ----------------------------------------------------------------- 00026 #define GETC(x) getc(x) 00027 #define PUTC(x) putc(x) 00028 #define PRINTF(...) printf(__VA_ARGS__) 00029 #define READABLE(x) readable(x) 00030 00031 // EEPROM 00032 #define EEP_TOP 0x0 00033 00034 // RAM ------------------------------------------------------------------------ 00035 char linebuf[64]; 00036 int buf_size = sizeof(linebuf); 00037 00038 // for EEPROM control 00039 int16_t read_pointer; 00040 00041 typedef struct { 00042 uint16_t head; 00043 uint16_t tail; 00044 } ring_t; 00045 00046 union _inf { 00047 uint8_t buf_pointer[PTR_SIZE]; 00048 ring_t log_inf; 00049 } inf; 00050 00051 typedef struct { 00052 uint32_t time; 00053 uint16_t vcc; 00054 uint16_t baro; 00055 int16_t b_temp; 00056 uint16_t humi; 00057 int16_t h_temp; 00058 uint16_t lux; 00059 } one_log; // 16 bytes total 00060 00061 union _one { 00062 uint8_t bf[PKT_SIZE]; 00063 one_log lg; 00064 } one; 00065 00066 extern float baro; 00067 extern float baro_temp; 00068 extern float cal_vcc; 00069 extern float lux; 00070 extern float humidity; 00071 extern float humidity_temp; 00072 extern float lux; 00073 00074 // ROM / Constant data -------------------------------------------------------- 00075 static const char *const mon_msg = 00076 "Monitor for mbed system, created on " __DATE__ ""; 00077 00078 const char *const log_head = 00079 // $, 2014/6/29,12:43:16,3.293,1004.5,+29.3,45.8,+29.2,1234,* 00080 "$,YYYY/MM/DD,HH:MM:SS,Vcc ,Press ,Temp ,Humi,Temp ,Lux ,*"; 00081 const char *const msg_emty = "Data empty"; 00082 const char *const msg_end = "\r\nreach to end"; 00083 00084 // Function prototypes -------------------------------------------------------- 00085 //extern void mon_hw(void); // ROM& RAM limitation 00086 00087 //------------------------------------------------------------------------------ 00088 // Control Program 00089 //------------------------------------------------------------------------------ 00090 // Put \r\n 00091 void put_rn ( void ) 00092 { 00093 PUTC('\r'); 00094 PUTC('\n'); 00095 } 00096 00097 // Put \r 00098 void put_r ( void ) 00099 { 00100 PUTC('\r'); 00101 } 00102 00103 // Put ", " 00104 void put_lin ( void ) 00105 { 00106 PRINTF(", "); 00107 } 00108 00109 // Put space n 00110 void put_spc( uint8_t n) 00111 { 00112 for(; n > 0; n--) { 00113 PUTC(' '); 00114 } 00115 } 00116 00117 // Change string -> integer 00118 int xatoi (char **str, unsigned long *res) 00119 { 00120 unsigned long val; 00121 unsigned char c, radix, s = 0; 00122 00123 while ((c = **str) == ' ') (*str)++; 00124 if (c == '-') { 00125 s = 1; 00126 c = *(++(*str)); 00127 } 00128 if (c == '0') { 00129 c = *(++(*str)); 00130 if (c <= ' ') { 00131 *res = 0; 00132 return 1; 00133 } 00134 if (c == 'x') { 00135 radix = 16; 00136 c = *(++(*str)); 00137 } else { 00138 if (c == 'b') { 00139 radix = 2; 00140 c = *(++(*str)); 00141 } else { 00142 if ((c >= '0')&&(c <= '9')) { 00143 radix = 8; 00144 } else { 00145 return 0; 00146 } 00147 } 00148 } 00149 } else { 00150 if ((c < '1')||(c > '9')) { 00151 return 0; 00152 } 00153 radix = 10; 00154 } 00155 val = 0; 00156 while (c > ' ') { 00157 if (c >= 'a') c -= 0x20; 00158 c -= '0'; 00159 if (c >= 17) { 00160 c -= 7; 00161 if (c <= 9) return 0; 00162 } 00163 if (c >= radix) return 0; 00164 val = val * radix + c; 00165 c = *(++(*str)); 00166 } 00167 if (s) val = -val; 00168 *res = val; 00169 return 1; 00170 } 00171 00172 //------------------------------------------------------------------------------ 00173 // Data Logging / Save into EEPROM 00174 //------------------------------------------------------------------------------ 00175 /* 00176 head = H, tail =T 00177 state 1: H=1(RING_TOP),T=1(RING_TOP) -> just after Clear command 00178 state 2: H=1,T=n -> n = 2 to RING_TAIL-1 (not filled yet) 00179 state 3: H=1,T=RING_TAIL -> need to check!!!! (just filled) 00180 state 4: H=2,T=1(RING_TOP) -> start ringed state 00181 state 5: H=n,T=n-1 -> n = 2 to RING_TAIL-1 (ringed) 00182 state 6: H=RING_TAIL,T=RING_TAIL-1 -> need to check!!!!! 00183 state 7: H=1(RING_TOP),T=RING_TAIL -> need to check!!!!! 00184 state 8: same as "state 5" 00185 -> Need to check state 3,6,7 00186 */ 00187 // Make one data packet data structure 00188 void dtlog_data_pack(void) 00189 { 00190 struct tm t; 00191 00192 xm41t62.read_rtc_std(&t); 00193 one.lg.time = mktime(&t); 00194 one.lg.vcc = (uint16_t)(cal_vcc * 1000); 00195 one.lg.baro = (uint16_t)(baro * 10); 00196 one.lg.b_temp = (int16_t)(baro_temp * 10); 00197 one.lg.humi = (uint16_t)(humidity * 10); 00198 one.lg.h_temp = (int16_t)(humidity_temp * 10); 00199 one.lg.lux = (uint16_t)lux; 00200 } 00201 00202 // Print one packet as normalized data 00203 void print_one_block_data(void) 00204 { 00205 struct tm *t; 00206 time_t seconds; 00207 uint16_t dt0; 00208 int16_t dt1; 00209 00210 put_rn(); 00211 PUTC( '$' ); 00212 //--- Time 00213 seconds = one.lg.time; 00214 t = localtime(&seconds); 00215 PRINTF(",%04d/%02d/%02d,%02d:%02d:%02d,", 00216 t->tm_year + 1900, t->tm_mon + 1, 00217 t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec); 00218 //--- Vcc 00219 dt0 = one.lg.vcc; 00220 PRINTF("%01d.%03d,", dt0/1000, dt0%1000); 00221 //--- Pressure 00222 dt0 = one.lg.baro; 00223 PRINTF("%04d.%01d,", dt0/10, dt0%10 ); 00224 //--- Temp. 00225 dt1 = one.lg.b_temp; 00226 PRINTF("%+03d.%01d,", dt1/10, abs(dt1)%10 ); 00227 //--- Humidity 00228 dt0 = one.lg.humi; 00229 PRINTF("%02d.%01d,", dt0/10, dt0%10 ); 00230 //--- Temp. 00231 dt1 = one.lg.h_temp; 00232 PRINTF("%+03d.%01d,", dt1/10, abs(dt1)%10 ); 00233 //--- Lux 00234 dt0 = one.lg.lux; 00235 PRINTF("%05d,*", dt0); 00236 } 00237 00238 // Read buffer pointer 00239 static void dtlog_pointer_read(void) 00240 { 00241 uint8_t i; 00242 uint8_t *addr; 00243 00244 /* Read EEPROM and save to buf_pointer[] */ 00245 for ( i = 0; i < PTR_SIZE; i++ ) { 00246 addr = (uint8_t *)(EEP_TOP + i); 00247 inf.buf_pointer[i] = at24c1024.read((int)addr); 00248 ThisThread::sleep_for(2ms); 00249 } 00250 // PRINTF("head %d, Tail %d\r\n", inf.log_inf.head, inf.log_inf.tail); 00251 } 00252 00253 // Write one packet 00254 void dtlog_one_write(void) 00255 { 00256 uint8_t i; 00257 uint8_t *addr; 00258 00259 // Read EEPROM buffer pointer to RAM 00260 for ( i = 0; i < PTR_SIZE; i++ ) { 00261 addr = (uint8_t *)(EEP_TOP + i); 00262 inf.buf_pointer[i] = at24c1024.read((int)addr); 00263 ThisThread::sleep_for(2ms); 00264 } 00265 //PRINTF("head %d, Tail %d\r\n", inf.log_inf.head, inf.log_inf.tail); 00266 // Write data_pack[] into EEPROM 00267 for (i = 0; i < PKT_SIZE; i++) { 00268 addr = (uint8_t *)(EEP_TOP + (inf.log_inf.tail * PTR_SIZE) + i); 00269 at24c1024.write((int)addr, one.bf[i]); 00270 ThisThread::sleep_for(8ms); 00271 } 00272 // Increment buffer pointer in RAM 00273 if (inf.log_inf.head == RING_TOP) { // check state 1,2,3 00274 if (inf.log_inf.tail == RING_TAIL) { // check state 3 00275 inf.log_inf.tail = RING_TOP; // set state 4 00276 inf.log_inf.head = 2; // missing one oldest data 00277 } else { 00278 inf.log_inf.tail++; // set state 2 00279 } 00280 } else { // check state 4,5,6,7 00281 if (inf.log_inf.head == RING_TAIL) { // check state 6 00282 inf.log_inf.head = RING_TOP; // set state 7 00283 inf.log_inf.tail = RING_TAIL; 00284 // check state 4,5 00285 } else if (inf.log_inf.tail == inf.log_inf.head - 1) { 00286 ++inf.log_inf.tail; // continue state 5 00287 ++inf.log_inf.head; 00288 } 00289 } 00290 // Write buffer pointer into EEPROM 00291 for (i = 0; i < PTR_SIZE; i++) { 00292 addr = (uint8_t *)(EEP_TOP + i); 00293 at24c1024.write((int)addr, inf.buf_pointer[i]); 00294 ThisThread::sleep_for(8ms); 00295 } 00296 } 00297 00298 // Read some block from buffer 00299 void dtlog_block_read(int16_t *pt, uint16_t n) 00300 { 00301 uint8_t i; 00302 uint8_t *addr; 00303 uint16_t num; 00304 00305 dtlog_pointer_read(); 00306 if (inf.log_inf.tail == inf.log_inf.head) { // Check pointer 00307 PRINTF(msg_emty); 00308 put_rn(); 00309 return; 00310 } 00311 PRINTF("Head:%d, Tail:%d, Start pointer:%d, Number of data:%d\r\n", 00312 inf.log_inf.head, inf.log_inf.tail, *pt, n); 00313 PRINTF( log_head ); 00314 for (num = 0; num < n; num++) { 00315 /* Read EEPROM and save to data_pack[] */ 00316 for (i = 0; i < PKT_SIZE; i++) { 00317 addr = (uint8_t *)(EEP_TOP + (*pt * PTR_SIZE) + i); 00318 one.bf[i] =at24c1024.read((int)addr); 00319 ThisThread::sleep_for(2ms); 00320 } 00321 print_one_block_data(); 00322 if (READABLE()) { 00323 GETC(); 00324 break; 00325 } 00326 ThisThread::sleep_for(1ms); 00327 if (inf.log_inf.head == RING_TOP) { // check state 1,2,3 00328 *pt += 1; 00329 if (*pt >= inf.log_inf.tail) { // check state 2,3 00330 PRINTF(msg_end); 00331 break; 00332 } 00333 } else { // state 4,5,6,7 00334 if (inf.log_inf.head == RING_TAIL) { // check state 6 00335 if (inf.log_inf.tail == RING_TAIL -1) { // check state 6 00336 if (*pt == RING_TAIL) { // same as :pt += 1 00337 *pt = RING_TOP; 00338 } else { // next read 00339 *pt += 1; 00340 if (*pt >= inf.log_inf.tail) { 00341 PRINTF(msg_end); 00342 break; 00343 } 00344 } 00345 } 00346 // check state 5 00347 } else if (inf.log_inf.tail == inf.log_inf.head - 1) { 00348 *pt += 1; 00349 if (*pt > RING_TAIL) { // same as :pt += 1 00350 *pt = RING_TOP; 00351 } else if (*pt == inf.log_inf.tail) { // reach to end 00352 PRINTF(msg_end); 00353 break; 00354 } 00355 } 00356 } 00357 } 00358 put_rn(); 00359 } 00360 00361 // Clear all buffer 00362 void dtlog_clear_all_buff(void) 00363 { 00364 uint8_t i; 00365 uint8_t *addr; 00366 00367 /* Set initial data */ 00368 inf.log_inf.head = inf.log_inf.tail = RING_TOP; 00369 /* Write buffer pointer */ 00370 for (i = 0; i < PTR_SIZE; i++) { 00371 addr = (uint8_t *)(EEP_TOP + i); 00372 at24c1024.write((int)addr, inf.buf_pointer[i]); 00373 ThisThread::sleep_for(8ms); 00374 } 00375 } 00376 00377 // EEPROM buffer occupation 00378 uint16_t dtlog_buf_occupation(void) 00379 { 00380 uint16_t i = 0; 00381 uint16_t dt = 0; 00382 uint8_t *addr; 00383 00384 // Read EEPROM buffer pointer to RAM 00385 for ( i = 0; i < PTR_SIZE; i++ ) { 00386 addr = (uint8_t *)(EEP_TOP + i); 00387 inf.buf_pointer[i] = at24c1024.read((int)addr); 00388 ThisThread::sleep_for(2ms); 00389 } 00390 // check buffer pointer 00391 if (inf.log_inf.head == inf.log_inf.tail) { 00392 PRINTF(msg_emty); 00393 put_rn(); 00394 return 0; 00395 } 00396 if (inf.log_inf.head == RING_TOP) { // check state 1,2,3 00397 dt = inf.log_inf.tail - inf.log_inf.head; 00398 } else { // state 4,5,6,7 00399 if (inf.log_inf.head == RING_TAIL) { // check state 6 00400 if (inf.log_inf.tail == RING_TAIL - 1) { // check state 6 00401 dt = inf.log_inf.tail - RING_TOP + 1; 00402 } 00403 // check state 4,5 00404 } else if (inf.log_inf.tail == inf.log_inf.head - 1) { 00405 dt = RING_TAIL; 00406 } else { // error 00407 dt = 0; 00408 } 00409 } 00410 return dt; 00411 } 00412 00413 // Read block number 00414 void dtlog_num_of_block(void) 00415 { 00416 uint16_t dt; 00417 00418 dt = dtlog_buf_occupation(); 00419 if (dt == 0) { 00420 PRINTF(msg_emty); 00421 } else { 00422 PRINTF("Number of data = %d", dt); 00423 put_rn(); 00424 dt = (uint16_t)(((uint32_t)dt * 1000 )/ (BLK_NO - 2)); 00425 PRINTF("EEPROM Occupation = %d.%01d%%", dt / 10, dt % 10); 00426 } 00427 put_rn(); 00428 } 00429 00430 //------------------------------------------------------------------------------ 00431 // Monitor 00432 //------------------------------------------------------------------------------ 00433 // Help Massage 00434 void msg_hlp (void) 00435 { 00436 PRINTF(mon_msg); 00437 put_rn(); 00438 PRINTF("d - Data logger"); 00439 put_rn(); 00440 PRINTF("t - Check and set RTC"); 00441 put_rn(); 00442 PRINTF("x - Goto HW monitor"); 00443 put_rn(); 00444 PRINTF("q - Return to main"); 00445 put_rn(); 00446 } 00447 00448 // Get key input data 00449 void get_line (char *buff, int len) 00450 { 00451 char c; 00452 int idx = 0; 00453 00454 for (;;) { 00455 c = GETC(); 00456 // Added by Kenji Arai / JH1PJL May 9th, 2010 00457 if (c == '\r') { 00458 buff[idx++] = c; 00459 break; 00460 } 00461 if ((c == '\b') && idx) { 00462 idx--; 00463 PUTC(c); 00464 PUTC(' '); 00465 PUTC(c); 00466 } 00467 if (((uint8_t)c >= ' ') && (idx < len - 1)) { 00468 buff[idx++] = c; 00469 PUTC(c); 00470 } 00471 } 00472 buff[idx] = 0; 00473 PUTC('\n'); 00474 } 00475 00476 00477 // RTC related subroutines 00478 void chk_and_set_time(char *ptr) 00479 { 00480 unsigned long p1; 00481 struct tm t; 00482 00483 if (xatoi(&ptr, &p1)) { 00484 t.tm_year = (uint8_t)p1 + 100; 00485 PRINTF("Year:%d ",(int)p1); 00486 xatoi( &ptr, &p1 ); 00487 t.tm_mon = (uint8_t)p1 - 1; 00488 PRINTF("Month:%d ",(int)p1); 00489 xatoi( &ptr, &p1 ); 00490 t.tm_mday = (uint8_t)p1; 00491 PRINTF("Day:%d ",(int)p1); 00492 xatoi( &ptr, &p1 ); 00493 t.tm_hour = (uint8_t)p1; 00494 PRINTF("Hour:%d ",(int)p1); 00495 xatoi( &ptr, &p1 ); 00496 t.tm_min = (uint8_t)p1; 00497 PRINTF("Min:%d ",(int)p1); 00498 xatoi( &ptr, &p1 ); 00499 t.tm_sec = (uint8_t)p1; 00500 PRINTF("Sec: %d \r\n",(int)p1); 00501 xm41t62.write_rtc_std(&t); 00502 } 00503 xm41t62.read_rtc_std(&t); 00504 // Show Time with several example 00505 // ex.1 00506 PRINTF("Date: %04d/%02d/%02d, %02d:%02d:%02d\r\n", 00507 t.tm_year + 1900, t.tm_mon + 1, 00508 t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec); 00509 #if 0 00510 time_t seconds; 00511 char buf[40]; 00512 00513 seconds = mktime(&t); 00514 // ex.2 00515 strftime(buf, 40, "%x %X", localtime(&seconds)); 00516 PRINTF("Date: %s\r\n", buf); 00517 // ex.3 00518 strftime(buf, 40, "%I:%M:%S %p (%Y/%m/%d)", localtime(&seconds)); 00519 PRINTF("Date: %s\r\n", buf); 00520 // ex.4 00521 strftime(buf, 40, "%B %d,'%y, %H:%M:%S", localtime(&seconds)); 00522 PRINTF("Date: %s\r\n", buf); 00523 #endif 00524 } 00525 00526 // Data Logger / Check status and Output data 00527 static void data_logger(char *ptr) 00528 { 00529 char c; 00530 unsigned long dt; 00531 uint16_t n; 00532 const char * Msg = "Data Logger Mode, ?[Help]"; 00533 00534 PRINTF("%s", Msg); 00535 put_rn(); 00536 /* Get EEPROM resource */ 00537 dtlog_pointer_read(); 00538 dt = inf.log_inf.head; 00539 while (1) { 00540 /* Get EEPROM resource */ 00541 dtlog_pointer_read(); 00542 PRINTF("DL>"); 00543 ptr = linebuf; 00544 get_line(ptr, buf_size); 00545 switch (*ptr++) { 00546 case 'a' : 00547 put_r(); 00548 read_pointer = inf.log_inf.head; 00549 n = dtlog_buf_occupation(); 00550 dtlog_block_read(&read_pointer, n); 00551 break; 00552 case 'c' : // Clear data 00553 put_r(); 00554 PRINTF("Delete all data?"); 00555 put_rn(); 00556 PRINTF("Enter y/n (n-cancel)"); 00557 put_rn(); 00558 c = GETC(); 00559 PUTC(c); 00560 put_rn(); 00561 if (c == 'y') { 00562 PRINTF("Cleared all logging data"); 00563 dtlog_clear_all_buff(); 00564 } else { 00565 PRINTF("Canceled"); 00566 } 00567 put_rn(); 00568 break; 00569 case 'd' : // d <pointer> [<count>] - Dump buffer 00570 put_r(); 00571 if (xatoi(&ptr, &dt)) { 00572 read_pointer = (uint16_t)dt; 00573 } else { 00574 read_pointer = inf.log_inf.head; 00575 } 00576 if (xatoi(&ptr, &dt)) { 00577 n = (uint8_t)dt; 00578 } else { 00579 n = BLK_SIZE; 00580 } 00581 if (read_pointer == 0) { 00582 read_pointer = 1; 00583 } 00584 dtlog_block_read(&read_pointer, n); 00585 break; 00586 case 0x0d : // CR 00587 put_r(); 00588 dtlog_block_read(&read_pointer, BLK_SIZE); 00589 break; 00590 case 'b' : // Back 00591 put_r(); 00592 read_pointer -= (BLK_SIZE * 2); 00593 if (read_pointer <= 0) { 00594 read_pointer = 1; 00595 } 00596 dtlog_block_read(&read_pointer, n); 00597 break; 00598 case 'n' : 00599 case 's' : // Status 00600 put_r(); 00601 dtlog_num_of_block(); 00602 break; 00603 case 'q' : // exit 00604 linebuf[0] = 0; 00605 return; 00606 case '?' : 00607 put_r(); 00608 PRINTF("d - <pointer> [<count>] Dump one block data"); 00609 put_rn(); 00610 PRINTF("a - Dump all log data"); 00611 put_rn(); 00612 PRINTF("c - Clear log data"); 00613 put_rn(); 00614 PRINTF("s - Logger status"); 00615 put_rn(); 00616 PRINTF("q - Exit DL mode"); 00617 put_rn(); 00618 break; 00619 default: 00620 PUTC('?'); 00621 put_rn(); 00622 break; 00623 } 00624 } 00625 } 00626 00627 // ---------- Program starts here! --------------------------------------------- 00628 int mon(void) 00629 { 00630 char *ptr; 00631 00632 put_rn(); 00633 put_rn(); 00634 PRINTF("%s [Help:'?' key]", mon_msg); 00635 put_rn(); 00636 for (;;) { 00637 put_r(); 00638 PUTC('>'); 00639 ptr = linebuf; 00640 get_line(ptr, sizeof(linebuf)); 00641 switch (*ptr++) { 00642 //------------------------------------------------------------------ 00643 // check and set RTC 00644 //------------------------------------------------------------------ 00645 case 't' : 00646 put_r(); 00647 chk_and_set_time(ptr); 00648 break; 00649 //------------------------------------------------------------------ 00650 // check EEPROM status 00651 //------------------------------------------------------------------ 00652 case 'd' : 00653 put_r(); 00654 data_logger(ptr); 00655 break; 00656 //------------------------------------------------------------------ 00657 // help 00658 //------------------------------------------------------------------ 00659 case '?' : 00660 put_r(); 00661 msg_hlp(); 00662 break; 00663 //------------------------------------------------------------------ 00664 // Go to special command 00665 //------------------------------------------------------------------ 00666 case 'x' : // 00667 PRINTF("->ROM & RAM size limitation. "); 00668 PRINTF("-> Cannot impliment the function\r\n"); 00669 //mon_hw(); 00670 PRINTF("->Came back monitor\r\n"); 00671 break; 00672 //------------------------------------------------------------------ 00673 // Go back to main() 00674 //------------------------------------------------------------------ 00675 case 'q' : // Quit 00676 PRINTF("\rReturn to main\r\n"); 00677 PRINTF("cannot control anymore from here\r\n"); 00678 return 0; 00679 } 00680 } 00681 }
Generated on Tue Jul 12 2022 21:29:41 by 1.7.2