This is a test of the ability to publish code
Dependencies: mbed SpiFlash25 mtsas
main.cpp
00001 #include "mbed.h" 00002 00003 #include "SystemTimeKeeper.h" 00004 #include "RadioManager.h" 00005 #include "StealthPowerSampler.h" 00006 #include "Logo.h" 00007 #include "SampleBacklog.h" 00008 #include "StatusUpdateBacklog.h" 00009 #include "PICManager.h" 00010 #include "RadarSocketManager.h" 00011 #include "SecondTimer.h" 00012 #include "SystemSerialNumber.h" 00013 #include "StatusUpdateManager.h" 00014 #include "SettingsManager.h" 00015 #include "WatchdogManager.h" 00016 00017 #define HOSTNAME_LENGTH 128 00018 00019 // This line controls the regulator's battery charger. 00020 // BC_NCE = 0 enables the battery charger 00021 // BC_NCE = 1 disables the battery charger 00022 DigitalOut bc_nce(PB_2); 00023 00024 // Declare all the firmware subcomponents/objects globally here 00025 SpiFlash25 flash(SPI_MOSI, SPI_MISO, SPI_SCK, SPI_CS1); 00026 SettingsManager settingsManager(flash); 00027 Ticker picTicker; 00028 RadioManager radioManager; 00029 SystemTimeKeeper systemTime; 00030 Serial dbg(USBTX, USBRX); 00031 Ticker main1HzTicker; 00032 StealthPowerSampler intervalSampler; 00033 StealthPowerSampler eventSampler; 00034 StatusUpdateManager updateManager; 00035 SampleBacklog sampleBacklog; 00036 StatusUpdateBacklog statusUpdateBacklog; 00037 RadarSocketManager radarSocketManager(radioManager); 00038 PICManager picManager(&intervalSampler, &eventSampler, &sampleBacklog); 00039 SecondTimer timeSinceSettingsCheck; 00040 00041 00042 void picUpdateServiceCallback() 00043 { 00044 picManager.servicePIC(); 00045 updateManager.service(statusUpdateBacklog); 00046 } 00047 00048 00049 void main1HzCallback() 00050 { 00051 // This gets called exactly once a second and is where we take data samples. 00052 // NOTE: We are in a Ticker callback (ISR) Context, so BE CAREFUL! 00053 systemTime.advance(); 00054 00055 int64_t now = systemTime.uptime(); 00056 00057 // Make sure all samplers know the range of time they've measured even if 00058 // we don't have data for them 00059 intervalSampler.sampleTime(now); 00060 eventSampler.sampleTime(now); 00061 00062 if (intervalSampler.intervalIsComplete()) 00063 { 00064 printf("Interval sample rollover.\r\n"); 00065 00066 if (intervalSampler.isWorthSending()) 00067 { 00068 StealthPowerSample sample; 00069 intervalSampler.populateSample(sample); 00070 sample.m_trigger = StealthPowerSample::TriggerInterval; 00071 sampleBacklog.push(sample); 00072 } 00073 intervalSampler.reset(now); 00074 } 00075 } 00076 00077 00078 const int64_t MinimumValidTime = 1459007487; // 3/26/2016. 00079 00080 void synchronizeTimeToCellular() 00081 { 00082 // Query the radio for the exact wall time 00083 int64_t currentRadioTime = radioManager.getTime(); 00084 00085 // If we got a valid time (after 3/26/2016, and not -1 (error)) 00086 if (currentRadioTime >= MinimumValidTime && currentRadioTime != -1) { 00087 __disable_irq(); 00088 int delta = abs(systemTime.walltime() - currentRadioTime); 00089 // Because the phase of the update Ticker is unaligned with this update, 00090 // leave 1s of slack between the two and only update the system 00091 // time if they get more than 1s apart. 00092 if (delta > 1) { 00093 printf("Resynchronized to wall time = %lld\r\n", currentRadioTime); 00094 systemTime.setWalltime(currentRadioTime); 00095 } 00096 __enable_irq(); 00097 } 00098 } 00099 00100 bool flushQueuesToAPI() 00101 { 00102 // Push all the explicit status-update events 00103 int sentItemsCount = 0; 00104 radar_result_t result = RADAR_OK; 00105 StealthPowerStatusUpdate update; 00106 __disable_irq(); 00107 while (statusUpdateBacklog.peek(update) && update.m_atTimestamp >= MinimumValidTime) { 00108 __enable_irq(); 00109 result = Radar_PublishStatus( 00110 (time_t)update.m_atTimestamp, 00111 update.m_picState, 00112 update.m_picK, 00113 update.m_batteryState, 00114 update.m_error, 00115 update.m_message.c_str(), 00116 update.m_Vsp, 00117 update.m_Voem, 00118 update.m_V3, 00119 update.m_V4, 00120 update.m_A1, 00121 update.m_A2, 00122 update.m_temperature); 00123 00124 __disable_irq(); 00125 if (result == RADAR_OK) { 00126 // Only once it's transmitted do we remove it from queue. 00127 statusUpdateBacklog.shift_if(update); 00128 ++sentItemsCount; 00129 } 00130 if (result != RADAR_OK) { 00131 __enable_irq(); 00132 return false; 00133 } 00134 } 00135 __enable_irq(); 00136 if (sentItemsCount > 0) printf("Transmitted %d event(s).\r\n", sentItemsCount); 00137 00138 // And the interval/trigger samples: 00139 sentItemsCount = 0; 00140 StealthPowerSample sample; 00141 __disable_irq(); 00142 while (sampleBacklog.peek(sample) && sample.m_beginTimestamp >= MinimumValidTime) { 00143 __enable_irq(); 00144 00145 int triggerType = RADAR_TRIGGER_INTERVAL; 00146 if (sample.m_trigger == StealthPowerSample::TriggerDischarge) triggerType = RADAR_TRIGGER_BATTERY_DISCHARGE; 00147 if (sample.m_trigger == StealthPowerSample::TriggerCharge) triggerType = RADAR_TRIGGER_BATTERY_CHARGE; 00148 00149 printf("SENDING SAMPLE, sample.m_Vsp is %f\r\n", sample.m_Vsp); 00150 result = Radar_PublishSample( 00151 triggerType, 00152 (time_t)sample.m_beginTimestamp, 00153 (time_t)sample.m_endTimestamp, 00154 sample.m_Vsp * 1000.0F, 00155 sample.m_Vsp_min * 1000.0F, 00156 sample.m_Vsp_max * 1000.0F, 00157 sample.m_Voem * 1000.0F, 00158 sample.m_Voem_min * 1000.0F, 00159 sample.m_Voem_max * 1000.0F, 00160 sample.m_V3 * 1000.0F, 00161 sample.m_V3_min * 1000.0F, 00162 sample.m_V3_max * 1000.0F, 00163 sample.m_V4 * 1000.0F, 00164 sample.m_V4_min * 1000.0F, 00165 sample.m_V4_max * 1000.0F, 00166 sample.m_A1 * 1000.0F, 00167 sample.m_A1_min * 1000.0F, 00168 sample.m_A1_max * 1000.0F, 00169 sample.m_A2 * 1000.0F, 00170 sample.m_A2_min * 1000.0F, 00171 sample.m_A2_max * 1000.0F, 00172 sample.m_temperatureAvg * 1000.0F, 00173 sample.m_temperatureMin * 1000.0F, 00174 sample.m_temperatureMax * 1000.0F, 00175 sample.m_sampleCount, 00176 sample.m_dischargeCount, 00177 sample.m_chargeCount); 00178 __disable_irq(); 00179 if (result == RADAR_OK) { 00180 // Only once it's transmitted do we remove it from queue. 00181 sampleBacklog.shift_if(sample); 00182 ++sentItemsCount; 00183 } 00184 if (result != RADAR_OK) { 00185 __enable_irq(); 00186 return false; 00187 } 00188 } 00189 __enable_irq(); 00190 if (sentItemsCount > 0) printf("Transmitted %d sample(s).\r\n", sentItemsCount); 00191 00192 return true; 00193 } 00194 00195 00196 bool applySettingsToSubsystems() 00197 { 00198 // Copy the settings from the settings manager into all the subystems that 00199 // should be affected by them. This should be invoked after every 00200 // GetTelemetrySettings() call, or whenever any settings are changed or 00201 // loaded (e.g. at boot). 00202 bool success = true; 00203 00204 success = success && radarSocketManager.setServers( 00205 settingsManager.getHost1(), 00206 settingsManager.getPort1(), 00207 settingsManager.getHost2(), 00208 settingsManager.getPort2()); 00209 00210 success = success && intervalSampler.setSampleInterval( 00211 settingsManager.getSampleIntervalInSeconds()); 00212 00213 success = success && picManager.setThresholds( 00214 settingsManager.getDischargeThreshold(), 00215 settingsManager.getChargeThreshold(), 00216 settingsManager.getCurrentHysteresis(), 00217 settingsManager.getUseFilteredA1ForBatteryState(), 00218 settingsManager.getMinimumEventSampleDuration()); 00219 00220 success = success && updateManager.setSettings( 00221 settingsManager.getStatusUpdateEvictionTime(), 00222 settingsManager.getVoltageFilterLengthInSamples(), 00223 settingsManager.getVspUpdateThreshold(), 00224 settingsManager.getVspJumpThreshold(), 00225 settingsManager.getVoemUpdateThreshold(), 00226 settingsManager.getVoemJumpThreshold(), 00227 settingsManager.getV3UpdateThreshold(), 00228 settingsManager.getV3JumpThreshold(), 00229 settingsManager.getV4UpdateThreshold(), 00230 settingsManager.getV4JumpThreshold(), 00231 settingsManager.getCurrentFilterLengthInSamples(), 00232 settingsManager.getA1UpdateThreshold(), 00233 settingsManager.getA1JumpThreshold(), 00234 settingsManager.getA2UpdateThreshold(), 00235 settingsManager.getA2JumpThreshold(), 00236 settingsManager.getTemperatureFilterLengthInSamples(), 00237 settingsManager.getTemperatureUpdateThreshold(), 00238 settingsManager.getTemperatureJumpThreshold()); 00239 00240 // No need to do anything for settingsCheckInterval--settingsManager 00241 // is the final recipient of that information already. 00242 00243 return success; 00244 } 00245 00246 00247 bool updateSettingsFromServer() 00248 { 00249 int32_t dischargeThreshold = 0; 00250 int32_t chargeThreshold = 0; 00251 int32_t currentHysteresis = 0; 00252 int32_t regularSampleInterval = -1; 00253 int32_t settingsCheckInterval = -1; 00254 hostname_t primaryHost = ""; 00255 uint32_t primaryPort = 0; 00256 hostname_t secondaryHost = ""; 00257 uint32_t secondaryPort = 0; 00258 uint32_t statusUpdateEvictionTime = 0; 00259 uint32_t voltageFilterLengthInSamples = 0; 00260 int32_t vspUpdateThreshold = 0; 00261 int32_t vspJumpThreshold = 0; 00262 int32_t voemUpdateThreshold = 0; 00263 int32_t voemJumpThreshold = 0; 00264 int32_t v3UpdateThreshold = 0; 00265 int32_t v3JumpThreshold = 0; 00266 int32_t v4UpdateThreshold = 0; 00267 int32_t v4JumpThreshold = 0; 00268 uint32_t currentFilterLengthInSamples = 0; 00269 int32_t a1UpdateThreshold = 0; 00270 int32_t a1JumpThreshold = 0; 00271 int32_t a2UpdateThreshold = 0; 00272 int32_t a2JumpThreshold = 0; 00273 uint32_t temperatureFilterLengthInSamples = 0; 00274 int32_t temperatureUpdateThreshold = 0; 00275 int32_t temperatureJumpThreshold = 0; 00276 int32_t useFilteredA1ForBatteryState = 0; 00277 int32_t minimumEventSampleDuration = 0; 00278 00279 00280 radar_result_t result = Radar_GetTelemetrySettings( 00281 &dischargeThreshold, 00282 &chargeThreshold, 00283 ¤tHysteresis, 00284 ®ularSampleInterval, 00285 &settingsCheckInterval, 00286 primaryHost, 00287 &primaryPort, 00288 secondaryHost, 00289 &secondaryPort, 00290 &statusUpdateEvictionTime, 00291 &voltageFilterLengthInSamples, 00292 &vspUpdateThreshold, 00293 &vspJumpThreshold, 00294 &voemUpdateThreshold, 00295 &voemJumpThreshold, 00296 &v3UpdateThreshold, 00297 &v3JumpThreshold, 00298 &v4UpdateThreshold, 00299 &v4JumpThreshold, 00300 ¤tFilterLengthInSamples, 00301 &a1UpdateThreshold, 00302 &a1JumpThreshold, 00303 &a2UpdateThreshold, 00304 &a2JumpThreshold, 00305 &temperatureFilterLengthInSamples, 00306 &temperatureUpdateThreshold, 00307 &temperatureJumpThreshold, 00308 &useFilteredA1ForBatteryState, 00309 &minimumEventSampleDuration); 00310 00311 printf("Radar_GetTelemetrySettings result = %d\r\n", result); // TODO: Cleanup 00312 if (result == RADAR_OK) 00313 { 00314 printf("Retrieved latest settings.\r\n"); 00315 settingsManager.setAll( 00316 primaryHost, 00317 primaryPort, 00318 secondaryHost, 00319 secondaryPort, 00320 dischargeThreshold, 00321 chargeThreshold, 00322 currentHysteresis, 00323 regularSampleInterval, 00324 settingsCheckInterval, 00325 statusUpdateEvictionTime, 00326 voltageFilterLengthInSamples, 00327 vspUpdateThreshold, 00328 vspJumpThreshold, 00329 voemUpdateThreshold, 00330 voemJumpThreshold, 00331 v3UpdateThreshold, 00332 v3JumpThreshold, 00333 v4UpdateThreshold, 00334 v4JumpThreshold, 00335 currentFilterLengthInSamples, 00336 a1UpdateThreshold, 00337 a1JumpThreshold, 00338 a2UpdateThreshold, 00339 a2JumpThreshold, 00340 temperatureFilterLengthInSamples, 00341 temperatureUpdateThreshold, 00342 temperatureJumpThreshold, 00343 useFilteredA1ForBatteryState, 00344 minimumEventSampleDuration); 00345 00346 printf("Latest Settings:\r\n"); 00347 settingsManager.printSettings(); 00348 00349 return applySettingsToSubsystems(); 00350 } 00351 else 00352 { 00353 printf("FAILED to retrieve settings!\r\n"); 00354 } 00355 return false; 00356 } 00357 00358 00359 00360 int main() 00361 { 00362 // Initial boot delay fixes issue with occasional loss of first lines of 00363 // console output. 00364 wait_ms(1000); 00365 00366 // All the demos do this to disable the (modem) battery charger. 00367 bc_nce = 1; 00368 00369 // Radio state management 00370 bool isRadioConnected = false; 00371 bool isCommunicationConnected = false; 00372 bool haveReasonToContactServer = false; 00373 bool everGotSettingsFromServer = false; 00374 00375 // For debugging: 00376 //mts::MTSLog::setLogLevel(mts::MTSLog::TRACE_LEVEL); 00377 00378 // For normal use: 00379 mts::MTSLog::setLogLevel(mts::MTSLog::WARNING_LEVEL); 00380 00381 //********************************* 00382 dbg.baud(115200); 00383 printf("Initializing Serial Console - OK\r\n"); fflush(stdout); 00384 printStealthPowerLogo(); 00385 00386 //********************************* 00387 printf("Initializing System Watchdog Timer - OK\r\n"); fflush(stdout); 00388 WatchdogManager::Initialize(); 00389 00390 //********************************* 00391 printf("Initializing Serial Number: "); fflush(stdout); 00392 printf("%s", getSystemSerialNumber()); 00393 printf(" - OK\r\n"); 00394 00395 //********************************* 00396 printf("Initializing Settings: "); fflush(stdout); 00397 settingsManager.initializeSettings(); 00398 printf("- OK\r\n"); 00399 settingsManager.printSettings(); 00400 00401 //********************************* 00402 printf("Applying Settings: "); fflush(stdout); 00403 if (applySettingsToSubsystems()) 00404 { 00405 printf("- OK\r\n"); 00406 } 00407 else 00408 { 00409 printf("- ERROR\r\n"); 00410 } 00411 00412 //********************************* 00413 printf("Initializing Real Time Clock"); fflush(stdout); 00414 systemTime.reset(); 00415 printf(" - OK\r\n"); 00416 00417 //********************************* 00418 printf("Initializing Sample Engine"); fflush(stdout); 00419 intervalSampler.reset(systemTime.uptime()); 00420 eventSampler.reset(systemTime.uptime()); 00421 main1HzTicker.attach(main1HzCallback, 1.0); 00422 printf(" - OK\r\n"); 00423 00424 //********************************* 00425 printf("Initializing PIC Communications"); fflush(stdout); 00426 00427 if (picManager.initialize()) 00428 printf(" - OK\r\n"); 00429 else 00430 printf(" - FAILED (will keep trying)\r\n"); 00431 // Service the PIC uart rapidly--most of the time there won't be anything to do. 00432 picTicker.attach(picUpdateServiceCallback, 0.1); 00433 00434 //********************************* 00435 // Radio Init is last, because it may block if no signal; don't want to hold off 00436 // sampling engine on a blocking radio call 00437 printf("Initializing Radio"); fflush(stdout); 00438 //wait_us(50); 00439 radioManager.initialize(); 00440 radioManager.connect(); 00441 if (radioManager.hasEverConnected()) 00442 printf(" - OK\r\n"); 00443 else 00444 printf(" - FAILED (but will keep trying)\r\n"); 00445 00446 //********************************* 00447 printf("Entering Main Loop\r\n"); 00448 while(1) 00449 { 00450 // --------------------------------------------------------------------------------------------- 00451 // (0) Feed the pseudo-hardware watchdog ticker loop 00452 WatchdogManager::Feed(); 00453 00454 // --------------------------------------------------------------------------------------------- 00455 // (1) Make sure the radio is connected if it possibly can be. (Not necessarily the socket.) 00456 isRadioConnected = false; 00457 00458 // If connected, ensure time-sync to cellular network wall time. 00459 if (radioManager.isConnected()) { 00460 isRadioConnected = true; 00461 synchronizeTimeToCellular(); 00462 } 00463 else 00464 { 00465 printf("Connecting radio\r\n"); 00466 isRadioConnected = radioManager.connect(); 00467 if (isRadioConnected) 00468 { 00469 printf("Radio connected.\r\n"); 00470 } 00471 else 00472 { 00473 printf("Radio failed to connect.\r\n"); 00474 } 00475 } 00476 00477 // --------------------------------------------------------------------------------------------- 00478 // (2) Then, make sure that if we have a valid wall-time, all samples that were timestamped 00479 // with system uptime are translated to wall-time. 00480 if (systemTime.hasWalltime()) 00481 { 00482 __disable_irq(); 00483 sampleBacklog.retroactivelyTimestampSamples(systemTime.uptime(), systemTime.walltimeDelta()); 00484 statusUpdateBacklog.retroactivelyTimestampSamples(systemTime.uptime(), systemTime.walltimeDelta()); 00485 __enable_irq(); 00486 } 00487 00488 // --------------------------------------------------------------------------------------------- 00489 // (3) Determine if there's a pressing need to contact the server right now--if not, don't waste 00490 // bandwidth connecting and hello'ing a socket we don't have anything to say on. 00491 haveReasonToContactServer = (sampleBacklog.backlog() > 0) || 00492 (statusUpdateBacklog.backlog() > 0) || 00493 (timeSinceSettingsCheck.read() >= settingsManager.getSettingsUpdateIntervalInSeconds()) || 00494 (!everGotSettingsFromServer); 00495 00496 // --------------------------------------------------------------------------------------------- 00497 // (4) Make sure we're connected via TCP to the server if we can be, and need to be, assuming the 00498 // radio is successfully connected. 00499 if (haveReasonToContactServer) 00500 { 00501 // First, if there's any reason to try connecting, that's a reason to start the radio watchdog. 00502 // This basically means "I better have some luck with the radio/server within X amount of time 00503 // or we'll hard reboot the radio." 00504 radioManager.resumeWatchdog(); 00505 00506 isCommunicationConnected = false; 00507 if (isRadioConnected) 00508 { 00509 if (!radarSocketManager.isConnected()) 00510 { 00511 printf("Connecting Stealth Radar API TCP socket\r\n"); 00512 if (radarSocketManager.connect()) { //(re-)connect to configured server 00513 printf("Connected socket; sending Hello()\r\n"); 00514 radar_result_t result = Radar_Hello( 00515 0x00010000, // build 01.0.0 00516 (time_t)systemTime.uptime(), // useful for detecting reboots 00517 (time_t)systemTime.walltime(), 00518 getSystemSerialNumber()); 00519 if (result != RADAR_OK) 00520 { 00521 printf("Error from Radar_Hello, shutting down socket.\r\n"); 00522 radarSocketManager.disconnect(); 00523 isCommunicationConnected = false; 00524 } 00525 else 00526 { 00527 printf("Connected Stealth Radar API TCP socket.\r\n"); 00528 isCommunicationConnected = true; 00529 } 00530 } 00531 else 00532 { 00533 printf("Failed to connect Stealth Radar API TCP socket!\r\n"); 00534 radarSocketManager.disconnect(); 00535 isCommunicationConnected = false; // just being explicit about this 00536 } 00537 } 00538 else 00539 { 00540 isCommunicationConnected = true; 00541 } 00542 } 00543 else 00544 { 00545 printf("Radar socket not connected because radio not connected.\r\n"); 00546 // Radio not connected 00547 radarSocketManager.disconnect(); 00548 isCommunicationConnected = false; 00549 } 00550 } 00551 00552 // --------------------------------------------------------------------------------------------- 00553 // (5) If the communication socket is established, dump as much queued data ASAP to the 00554 // server as possible 00555 if (isCommunicationConnected) 00556 { 00557 if (!flushQueuesToAPI()) 00558 { 00559 printf("Problem transmitting sample/status updates to the server; destroying Radar API TCP Socket.\r\n"); 00560 radarSocketManager.disconnect(); 00561 isCommunicationConnected = false; 00562 } 00563 00564 // Always get settings at the beginning of a connection 00565 if (timeSinceSettingsCheck.read() >= settingsManager.getSettingsUpdateIntervalInSeconds() || !everGotSettingsFromServer) 00566 { 00567 if (updateSettingsFromServer()) 00568 { 00569 // Reset the timer that watches how long it's been since we've asked for settings 00570 timeSinceSettingsCheck.stop(); 00571 timeSinceSettingsCheck.reset(); 00572 timeSinceSettingsCheck.start(); 00573 everGotSettingsFromServer = true; 00574 } 00575 else 00576 { 00577 printf("Problem getting settings from the server.\r\n"); 00578 radarSocketManager.disconnect(); 00579 isCommunicationConnected = false; 00580 } 00581 } 00582 } 00583 00584 // --------------------------------------------------------------------------------------------- 00585 // (6) If we are *not* connected to the server due to radio or tcp problems, *and* there 00586 // is any backlog of samples not sent yet, back them up to flash in case of power loss. 00587 if (!isCommunicationConnected) 00588 { 00589 /*if (sampleBacklog.backlog() > 0) 00590 { 00591 printf("Backing up queued sample data to flash.\r\n"); 00592 } 00593 if (statusUpdateBacklog.backlog() > 0) 00594 { 00595 printf("Backing up queued status-update events to flash.\r\n"); 00596 }*/ 00597 // Backup any wall-timestamped samples to flash since there's no connection right now and 00598 // power could go away at any moment. 00599 00600 // Note -- we dropped this feature due to the complexity of dealing with 64KB sectors 00601 // on the SPI flash, but this is where you'd ideally keep everything backed up. 00602 } 00603 00604 // --------------------------------------------------------------------------------------------- 00605 // (7) Make sure the latest setting are stored in the flash, in duplicate 00606 if (!settingsManager.serviceSettingsInFlash()) 00607 { 00608 printf("Error updating settings in flash!\r\n"); 00609 } 00610 00611 // --------------------------------------------------------------------------------------------- 00612 // (8) Finally, spit out debug state and throttle the main loop rate (too fast seems to aggravate 00613 // the modem, which is already touchy...) 00614 printf("MAIN: t = %d, radio = %d, socket = %d, samplebacklog = %d, updatebacklog = %d, radio wdt = %d\r\n", 00615 (int)SystemTimeKeeper::instance().walltime(), 00616 isRadioConnected, 00617 isCommunicationConnected, 00618 sampleBacklog.backlog(), 00619 statusUpdateBacklog.backlog(), 00620 radioManager.watchdogValue()); 00621 00622 // --------------------------------------------------------------------------------------------- 00623 // (7) Radio watchdog -- if we haven't had success using the radio in 00624 // more than a couple hours, reboot the radio; it may be hosed. 00625 if (!radioManager.serviceWatchdog()) 00626 { 00627 printf("Error handling radio watchdog!\r\n"); 00628 } 00629 00630 wait(2.0); 00631 } 00632 }
Generated on Sat Jul 16 2022 20:02:36 by 1.7.2