Add RangeTest examples
This commit is contained in:
parent
3cbd159143
commit
add5e29208
|
|
@ -0,0 +1,193 @@
|
|||
//*********************************************************************************************
|
||||
// RangeTest_Gateway (to be used with an RFGateway or similar Moteino)
|
||||
// Adapted from Gateway firmware sketch
|
||||
// Acts as a listener for testing RF performance, in tandem with a RangeTest_Transmitter
|
||||
//*********************************************************************************************
|
||||
// Copyright Felix Rusu 2021, http://www.LowPowerLab.com/contact
|
||||
//*********************************************************************************************
|
||||
#include <RFM69.h> //https://www.github.com/lowpowerlab/rfm69
|
||||
#include <RFM69_ATC.h> //https://www.github.com/lowpowerlab/rfm69
|
||||
#include <U8g2lib.h> //https://github.com/olikraus/u8g2/wiki/u8g2reference fonts:https://github.com/olikraus/u8g2/wiki/fntlistall
|
||||
#include <Wire.h> //i2c scanner: https://playground.arduino.cc/Main/I2cScanner
|
||||
#include <PString.h> //easy string manipulator: http://arduiniana.org/libraries/pstring/
|
||||
//*********************************************************************************************
|
||||
// *********** IMPORTANT SETTINGS - YOU MUST CHANGE/ONFIGURE TO FIT YOUR HARDWARE *************
|
||||
//*********************************************************************************************
|
||||
#define NODEID 1 //unique for each node on same network
|
||||
#define NETWORKID 100 //the same on all nodes that talk to each other
|
||||
#define FREQUENCY RF69_915MHZ //others: RF69_433MHZ, RF69_868MHZ, match to radio variant on your Moteino/board
|
||||
#define ENCRYPTKEY "sampleEncryptKey"
|
||||
#define IS_RFM69HW_HCW //assumes RFM69 HCW/HW, remove if you have RFM69 W/CW
|
||||
#define ENABLE_ATC //comment out to disable AUTO TRANSMISSION CONTROL (ie. always transmit at max power)
|
||||
//*********************************************************************************************
|
||||
#define SERIAL_BAUD 500000 //this works with 0% error on all LowPowerLab boards, for SAMD it is irrelevant
|
||||
#define LED LED_BUILTIN
|
||||
#define BUZZER 6
|
||||
//***********************************************************************************************************
|
||||
#define OLED_BAUD 1600000 //fast i2c clock
|
||||
#define OLED_ADDRESS 0x3C //i2c address on most small OLEDs
|
||||
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);
|
||||
//***********************************************************************************************************
|
||||
#define LED_HIGH digitalWrite(LED, HIGH)
|
||||
#define LED_LOW digitalWrite(LED, LOW)
|
||||
|
||||
#ifdef ENABLE_ATC
|
||||
RFM69_ATC radio;
|
||||
#else
|
||||
RFM69 radio;
|
||||
#endif
|
||||
|
||||
#ifdef SERIAL_BAUD
|
||||
#define DEBUG(input) Serial.print(input)
|
||||
#define DEBUGln(input) Serial.println(input)
|
||||
#define DEBUGHex(input) Serial.print(input, HEX)
|
||||
#define DEBUGFlush() Serial.flush()
|
||||
#else
|
||||
#define DEBUG(input)
|
||||
#define DEBUGln(input)
|
||||
#define DEBUGHex(input)
|
||||
#define DEBUGFlush()
|
||||
#endif
|
||||
|
||||
#define MSG_MAX_LEN 30 //don't need more for testing purposes
|
||||
#define HISTORY_LEN 9 //hold this many past messages - to display on OLED at small font size
|
||||
|
||||
byte OLEDfound=false;
|
||||
char buff[61];
|
||||
PString Pbuff(buff, sizeof(buff)); //easy string manipulator
|
||||
byte ackCount=0;
|
||||
byte lastMessageIndex = HISTORY_LEN;
|
||||
byte historyLength = 0;
|
||||
uint16_t senderID;
|
||||
int rxRSSI;
|
||||
|
||||
typedef struct {
|
||||
char msg[MSG_MAX_LEN];
|
||||
int rssi;
|
||||
uint16_t from;
|
||||
byte pinged;
|
||||
byte pingedOK;
|
||||
} Message;
|
||||
Message * messageHistory = new Message[HISTORY_LEN];
|
||||
|
||||
void setup() {
|
||||
pinMode(BUZZER, OUTPUT);
|
||||
pinMode(LED, OUTPUT);
|
||||
Serial.begin(SERIAL_BAUD);
|
||||
radio.initialize(FREQUENCY,NODEID,NETWORKID);
|
||||
#ifdef IS_RFM69HW_HCW
|
||||
radio.setHighPower();
|
||||
#endif
|
||||
radio.encrypt(ENCRYPTKEY);
|
||||
DEBUG(F("Listening at ")); DEBUG(radio.getFrequency()); DEBUGln(F(" Hz.."));
|
||||
DEBUGln(buff);
|
||||
|
||||
Pbuff = F("Listening at ");
|
||||
Pbuff.print(radio.getFrequency());
|
||||
Pbuff.print(F("Hz..."));
|
||||
DEBUGln(buff);
|
||||
|
||||
delay(1000); //Wire apparently needs 50ms
|
||||
Wire.begin();
|
||||
Wire.beginTransmission(OLED_ADDRESS);
|
||||
byte error = Wire.endTransmission();
|
||||
if (error == 0) {
|
||||
DEBUG(F("OLED FOUND at 0x")); DEBUGln(OLED_ADDRESS);
|
||||
u8g2.begin();
|
||||
u8g2.setDisplayRotation(U8G2_R2); //if required (inside/custom mount?)
|
||||
u8g2.setBusClock(OLED_BAUD);
|
||||
OLEDfound = true;
|
||||
} else {
|
||||
DEBUGln(F("NO OLED found..."));
|
||||
}
|
||||
|
||||
DEBUGFlush();
|
||||
u8g2.clearBuffer();
|
||||
u8g2.setFont(u8g2_font_8x13B_tf);
|
||||
u8g2.setCursor(0,10); u8g2.print("OLED PKT CAPTURE");
|
||||
u8g2.sendBuffer();
|
||||
delay(1000);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
if (radio.receiveDone()) {
|
||||
LED_HIGH;
|
||||
senderID = radio.SENDERID;
|
||||
rxRSSI = radio.RSSI;
|
||||
|
||||
DEBUG('[');DEBUG(senderID);DEBUG(F("] "));
|
||||
DEBUG((char*)radio.DATA);
|
||||
DEBUG(F(" [RX_RSSI:"));DEBUG(rxRSSI);DEBUG(F("]"));
|
||||
|
||||
Pbuff = "";
|
||||
Pbuff.print(radio.SENDERID);
|
||||
Pbuff.print(' ');
|
||||
Pbuff.print((char*)radio.DATA);
|
||||
Pbuff.print(' ');
|
||||
Pbuff.print(radio.RSSI);
|
||||
|
||||
if (radio.ACKRequested()) {
|
||||
radio.sendACK();
|
||||
|
||||
if (++ackCount%3==0) {
|
||||
DEBUG(F(" Pinging ["));
|
||||
DEBUG(senderID);
|
||||
DEBUG(F("]:"));
|
||||
delay(3); //need this when sending right after reception .. ?
|
||||
//if (radio.sendWithRetry(senderID, "ACK TEST", 8, 0)) { // 0 = only 1 attempt, no retries
|
||||
if (radio.sendWithRetry(senderID, "ACK TEST", 8)) {
|
||||
DEBUG(F("OK!"));
|
||||
Pbuff.print(" OK");
|
||||
} else {
|
||||
DEBUG(F("nothing"));
|
||||
Pbuff.print(" NOK");
|
||||
}
|
||||
}
|
||||
}
|
||||
DEBUGln();
|
||||
saveToHistory(buff);
|
||||
DEBUGln();
|
||||
if (OLEDfound) draw();
|
||||
DEBUGln();
|
||||
LED_LOW;
|
||||
}
|
||||
|
||||
//LowPower.powerDown(SLEEP_8S, ADC_OFF, BOD_OFF);
|
||||
}
|
||||
|
||||
void draw() {
|
||||
u8g2.clearBuffer();
|
||||
u8g2.setFont(u8g2_font_tom_thumb_4x6_tf);
|
||||
|
||||
byte offset=0;
|
||||
for (byte i=lastMessageIndex+1; i<historyLength; i++) {
|
||||
offset = 7 + (i-lastMessageIndex-1)*7;
|
||||
u8g2.setCursor(0, offset); u8g2.print(messageHistory[i].msg);
|
||||
//DEBUG(F("L1_i=")); DEBUG(i); DEBUG(F("_offset=")); DEBUGln(offset);
|
||||
}
|
||||
offset+=7;
|
||||
for (byte i=0; i<=lastMessageIndex; i++) {
|
||||
u8g2.setCursor(0, offset + i*7); u8g2.print(messageHistory[i].msg);
|
||||
//DEBUG(F("L2_i=")); DEBUG(i); DEBUG(F("_offset=")); DEBUG(offset); DEBUG(F("_x=")); DEBUGln(offset + i*7);
|
||||
}
|
||||
|
||||
u8g2.sendBuffer();
|
||||
}
|
||||
|
||||
void saveToHistory(char * msg) {
|
||||
byte length = strlen(msg);
|
||||
if (length == 0) return;
|
||||
|
||||
if (historyLength < HISTORY_LEN) historyLength++;
|
||||
if (lastMessageIndex >= HISTORY_LEN-1) //reset pointed in circular buffer/array
|
||||
lastMessageIndex = 0;
|
||||
else
|
||||
lastMessageIndex++;
|
||||
|
||||
strcpy(messageHistory[lastMessageIndex].msg, msg);
|
||||
DEBUG(F("HIST ADDED ["));
|
||||
DEBUG(lastMessageIndex);
|
||||
DEBUG(F("]["));
|
||||
DEBUG((char*)messageHistory[lastMessageIndex].msg);
|
||||
DEBUG(F("]"));
|
||||
}
|
||||
|
|
@ -0,0 +1,259 @@
|
|||
//*********************************************************************************************
|
||||
// RangeTest_Transmitter (to be used with a MotionMote or similar Moteino)
|
||||
// Adapted from MotionMote firmware sketch
|
||||
// Acts as a continuous transmitter for testing RF performance, in tandem with a RangeTest_Gateway
|
||||
//*********************************************************************************************
|
||||
// Copyright Felix Rusu 2021, http://www.LowPowerLab.com/contact
|
||||
//*********************************************************************************************
|
||||
#include <RFM69.h> //https://www.github.com/lowpowerlab/rfm69
|
||||
#include <RFM69_ATC.h>//https://www.github.com/lowpowerlab/rfm69
|
||||
#include <LowPower.h> //https://github.com/lowpowerlab/lowpower
|
||||
#include <SPIFlash.h> //https://www.github.com/lowpowerlab/spiflash
|
||||
//*********************************************************************************************
|
||||
//************ IMPORTANT SETTINGS - YOU MUST CHANGE/CONFIGURE TO FIT YOUR HARDWARE ************
|
||||
//*********************************************************************************************
|
||||
#define NODEID 2 //unique for each node on same network
|
||||
#define NETWORKID 100 //the same on all nodes that talk to each other
|
||||
#define GATEWAYID 1
|
||||
#define FREQUENCY RF69_915MHZ //others: RF69_433MHZ, RF69_868MHZ, match to radio variant on your Moteino/board
|
||||
#define ENCRYPTKEY "sampleEncryptKey" //exactly the same 16 characters/bytes on all nodes!
|
||||
#define IS_RFM69HW_HCW //assumes RFM69 HCW/HW, remove if you have RFM69 W/CW
|
||||
#define ENABLE_ATC //comment out to disable AUTO TRANSMISSION CONTROL (ie. always transmit at max power)
|
||||
#define ATC_RSSI -90 //noise floor is at -100dB, keep above by a margin of 10db
|
||||
//*********************************************************************************************
|
||||
//************ GPIO & MISC ********************************************************************
|
||||
//*********************************************************************************************
|
||||
#define ACK_TIME 30 // max # of ms to wait for an ack
|
||||
#define ONBOARDLED 9 // MotionMote onboard LED on D9
|
||||
#define PIR_POWER 7 //PIR is powered from D7
|
||||
#define RFM_RST A0 //used to reset the radio module at power up to ensure a clean start
|
||||
#define MOTION_PIN 3 // D3
|
||||
#define MOTION_IRQ 1 // hardware interrupt 1 (D3) - where motion sensors OUTput is connected, this will generate an interrupt every time there is MOTION
|
||||
#define LED_PWR 6 // separate MotionMote LED powered from D6
|
||||
#define LED_GND 5 // separate MotionMote LED ground on D5
|
||||
#define LED_HIGH digitalWrite(LED_PWR, HIGH)
|
||||
#define LED_LOW digitalWrite(LED_PWR, LOW)
|
||||
#define DUPLICATE_INTERVAL 10000 //avoid duplicates in 55second intervals (ie mailman sometimes spends 30+ seconds at mailbox)
|
||||
#define BATT_INTERVAL 4000 // read and report battery voltage every this many ms (approx)
|
||||
#define INTERNAL_AREF_V 1100 //=1.1v internal bandgap. can be adjusted to more or less to be more accurate
|
||||
//*********************************************************************************************
|
||||
#define DEBUG_EN //comment this out when deploying to an installed SM to save a few KB of sketch size
|
||||
#ifdef DEBUG_EN
|
||||
#define SERIAL_BAUD 500000
|
||||
#define DEBUG(input) Serial.print(input)
|
||||
#define DEBUGln(input) Serial.println(input)
|
||||
#define DEBUGHEX(input, param) Serial.print(input, param)
|
||||
#define DEBUGFlush() Serial.flush()
|
||||
#else
|
||||
#define DEBUG(input)
|
||||
#define DEBUGln(input)
|
||||
#define DEBUGHEX(input, param)
|
||||
#define DEBUGFlush();
|
||||
#endif
|
||||
//*********************************************************************************************
|
||||
#ifdef ENABLE_ATC
|
||||
RFM69_ATC radio;
|
||||
#else
|
||||
RFM69 radio;
|
||||
#endif
|
||||
SPIFlash flash(SS_FLASHMEM, 0xEF30); //EF30 for 4mbit Windbond chip (W25X40CL)
|
||||
|
||||
volatile boolean motionDetected=false;
|
||||
float batteryVolts = 5;
|
||||
float temp = 0;
|
||||
char BATstr[10]; //longest battery voltage reading message = 9chars
|
||||
char TEMPstr[10];
|
||||
char sendBuf[32];
|
||||
void motionIRQ(void);
|
||||
void checkBattery(void);
|
||||
|
||||
void setup() {
|
||||
#ifdef DEBUG_EN
|
||||
Serial.begin(SERIAL_BAUD);
|
||||
#endif
|
||||
|
||||
if (flash.initialize()) {
|
||||
DEBUGln(F("SPI Flash Init OK!"));
|
||||
flash.sleep(); //safe to call because initialize() wakes it up
|
||||
}
|
||||
else DEBUGln(F("SPI Flash MEM FAIL!"));
|
||||
|
||||
pinMode(MOTION_PIN, INPUT);
|
||||
pinMode(ONBOARDLED, OUTPUT);
|
||||
pinMode(PIR_POWER, OUTPUT);
|
||||
pinMode(RFM_RST, OUTPUT);
|
||||
pinMode(LED_PWR, OUTPUT);
|
||||
pinMode(LED_GND, OUTPUT);
|
||||
digitalWrite(PIR_POWER, HIGH);
|
||||
digitalWrite(RFM_RST, LOW);
|
||||
attachInterrupt(MOTION_IRQ, motionIRQ, RISING);
|
||||
digitalWrite(RFM_RST, HIGH); delay(100); digitalWrite(RFM_RST, LOW);
|
||||
|
||||
radio.initialize(FREQUENCY,NODEID,NETWORKID);
|
||||
#ifdef IS_RFM69HW_HCW
|
||||
radio.setHighPower();
|
||||
#endif
|
||||
radio.encrypt(ENCRYPTKEY);
|
||||
|
||||
//Auto Transmission Control - dials down transmit power to save battery (-100 is the noise floor, -90 is still pretty good)
|
||||
//For indoor nodes that are pretty static and at pretty stable temperatures (like a MotionMote) -90dBm is quite safe
|
||||
//For more variable nodes that can expect to move or experience larger temp drifts a lower margin like -70 to -80 would probably be better
|
||||
//Always test your ATC mote in the edge cases in your own environment to ensure ATC will perform as you expect
|
||||
#ifdef ENABLE_ATC
|
||||
radio.enableAutoPower(ATC_RSSI);
|
||||
DEBUGln("RFM69_ATC Enabled (Auto Transmission Control)\n");
|
||||
#endif
|
||||
|
||||
DEBUG("Transmitting at "); DEBUG(radio.getFrequency()); DEBUGln(" Hz..");
|
||||
|
||||
radio.sendWithRetry(GATEWAYID, "START", 5);
|
||||
}
|
||||
|
||||
void motionIRQ() {
|
||||
motionDetected=true;
|
||||
DEBUGln("IRQ");
|
||||
}
|
||||
|
||||
uint32_t time=0, now=0, MLO=0, BLO=0;
|
||||
byte motionRecentlyCycles=0;
|
||||
byte ackCount=0;
|
||||
byte packetCounter=0;
|
||||
void loop() {
|
||||
now = millis();
|
||||
checkBattery();
|
||||
//DEBUG("Slept: ");DEBUG(now-lastSleepTime);DEBUGln("ms");
|
||||
|
||||
if (motionDetected && (time-MLO > DUPLICATE_INTERVAL)) {
|
||||
packetCounter++;
|
||||
LED_HIGH; //digitalWrite(LED, HIGH);
|
||||
MLO = BLO = time; //save timestamp of event
|
||||
sprintf(sendBuf, "%d MOTION X:%d", packetCounter, radio.getPowerLevel());
|
||||
DEBUG(sendBuf);
|
||||
|
||||
if (radio.sendWithRetry(GATEWAYID, sendBuf, strlen(sendBuf))) {
|
||||
DEBUG("..OK! RSSI:");
|
||||
DEBUG(radio.RSSI);
|
||||
listen_a_little();
|
||||
} else DEBUG("..NOK..");
|
||||
|
||||
radio.sleep();
|
||||
LED_LOW;
|
||||
} else if (time-BLO > BATT_INTERVAL) {
|
||||
packetCounter++;
|
||||
sprintf(sendBuf, "%d V:%s F:%s X:%d", packetCounter, BATstr, TEMPstr, radio.getPowerLevel());
|
||||
BLO = time;
|
||||
|
||||
DEBUG(sendBuf);
|
||||
if (radio.sendWithRetry(GATEWAYID, sendBuf, strlen(sendBuf))) {
|
||||
DEBUG("..OK!");
|
||||
listen_a_little();
|
||||
} else DEBUG("..NOK..");
|
||||
}
|
||||
DEBUGln(); DEBUGFlush();
|
||||
|
||||
//while motion recently happened sleep for small slots of time to better approximate last motion event
|
||||
//this helps with debouncing a "MOTION" event more accurately for sensors that fire the IRQ very rapidly (ie panasonic sensors)
|
||||
if (motionDetected || motionRecentlyCycles>0) {
|
||||
if (motionDetected) motionRecentlyCycles=8;
|
||||
else motionRecentlyCycles--;
|
||||
motionDetected=false; //do NOT move this after the SLEEP line below or motion will never be detected
|
||||
time = time + 250 + millis()-now; //correct millis() resonator drift, may need to be tweaked to be accurate
|
||||
radio.sleep();
|
||||
LowPower.powerDown(SLEEP_250MS, ADC_OFF, BOD_OFF);
|
||||
DEBUGln("WAKEUP250ms");
|
||||
} else {
|
||||
time = time + 4000 + millis()-now /*+ 480*/; //correct millis() resonator drift, may need to be tweaked to be accurate
|
||||
radio.sleep();
|
||||
LowPower.powerDown(SLEEP_4S, ADC_OFF, BOD_OFF); //watchdog sleep uses extra ~4uA!
|
||||
//sleep(4000);
|
||||
DEBUGln("WAKEUP4s");
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t BLR=0;
|
||||
void checkBattery()
|
||||
{
|
||||
if (time-BLR > 3) //only read battery every 30s or so
|
||||
{
|
||||
BLR = time;
|
||||
long vavg = 0;
|
||||
temp = 0;
|
||||
ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1); // Ref to Vcc. Measure internal 1.1V ref
|
||||
for (int j = 0; j < 10; j++)
|
||||
{ // Read a few times to get ADC to settle
|
||||
ADCSRA |= _BV(ADSC); // Start conversion
|
||||
temp += radio.readTemperature(-1); // Temperature. -1 = user cal factor, adjust for correct ambient
|
||||
while (bit_is_set(ADCSRA,ADSC)); // measuring
|
||||
if (j > 4) { // Skip the first 5 Vcc readings, take the next 5
|
||||
vavg = vavg + (((INTERNAL_AREF_V * 1024L) / ADC) + 5L);
|
||||
}
|
||||
}
|
||||
batteryVolts = (vavg/5.0)/1000.0;
|
||||
temp /= 10;
|
||||
dtostrf(temp, 2,0, TEMPstr);
|
||||
dtostrf(batteryVolts, 3,1, BATstr); //update the BATStr which gets sent every BATT_CYCLES or along with the MOTION message
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void sleep(uint32_t sleepTime) {
|
||||
DEBUGFlush();
|
||||
if (sleepTime < 262) { //sleeps just the MCU, using WDT (radio is not touched)
|
||||
LowPower.longPowerDown(sleepTime);
|
||||
} else { //sleeps MCU using the radio timer - should not be used if radio needs to be in RX mode!
|
||||
uint32_t freq = radio.getFrequency();
|
||||
uint32_t remainingSleepTime = sleepTime;
|
||||
|
||||
while (remainingSleepTime) { //split into sleep(60s) calls if > 60s sleep
|
||||
if (remainingSleepTime > 65500) {
|
||||
sleepTime = 65500;
|
||||
remainingSleepTime -= 65500;
|
||||
} else {
|
||||
sleepTime = remainingSleepTime;
|
||||
remainingSleepTime = 0;
|
||||
}
|
||||
|
||||
if (sleepTime%262 && sleepTime > 262*2) {
|
||||
DEBUG("Sleep "); DEBUGln(sleepTime-sleepTime%262-262); DEBUGFlush();
|
||||
listenModeSleep(sleepTime-sleepTime%262-262);
|
||||
DEBUG("Sleep "); DEBUGln(sleepTime%262 + 262); DEBUGFlush();
|
||||
listenModeSleep(sleepTime%262 + 262);
|
||||
} else {
|
||||
DEBUG("Sleep "); DEBUGln(sleepTime); DEBUGFlush();
|
||||
listenModeSleep(sleepTime);
|
||||
}
|
||||
|
||||
//WAKEUP happens here (must reinit!)
|
||||
radio.RFM69::initialize(FREQUENCY,NODEID,NETWORKID); //call base init!
|
||||
#ifdef ENCRYPTKEY
|
||||
radio.encrypt(ENCRYPTKEY);
|
||||
#endif
|
||||
radio.setFrequency(freq);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void listenModeSleep(uint16_t millisInterval) {
|
||||
radio.listenModeSleep(millisInterval);
|
||||
LowPower.powerDown( SLEEP_FOREVER, ADC_OFF, BOD_OFF );
|
||||
LowPower.powerDown( SLEEP_FOREVER, ADC_OFF, BOD_OFF );
|
||||
LowPower.powerDown( SLEEP_FOREVER, ADC_OFF, BOD_OFF );
|
||||
radio.endListenModeSleep();
|
||||
}
|
||||
|
||||
void listen_a_little() {
|
||||
radio.receiveDone(); //radio RX!
|
||||
LowPower.powerDown(SLEEP_60MS, ADC_OFF, BOD_OFF);
|
||||
|
||||
if (radio.receiveDone()) {
|
||||
DEBUG(" [");DEBUG(radio.SENDERID);DEBUG("] ");
|
||||
DEBUG((char*)radio.DATA);
|
||||
DEBUG("[RX_RSSI:");DEBUG(radio.RSSI);DEBUG("]");
|
||||
|
||||
if (radio.ACKRequested()) {
|
||||
radio.sendACK();
|
||||
DEBUG("[ACK sent]");
|
||||
}
|
||||
time += 10;
|
||||
} else time += 60;
|
||||
}
|
||||
Loading…
Reference in New Issue