From 8189c1a995865fff7d787a03c15a74e8f2f12eb4 Mon Sep 17 00:00:00 2001 From: voroshkov Date: Tue, 20 Feb 2018 22:20:44 +0300 Subject: [PATCH 1/9] add api for race director software --- .../chorus_rf_laptimer/chorus_rf_laptimer.ino | 34 ++++++++++++++++--- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/Arduino/chorus_rf_laptimer/chorus_rf_laptimer.ino b/Arduino/chorus_rf_laptimer/chorus_rf_laptimer.ino index b8baec1..14592e2 100644 --- a/Arduino/chorus_rf_laptimer/chorus_rf_laptimer.ino +++ b/Arduino/chorus_rf_laptimer/chorus_rf_laptimer.ino @@ -38,7 +38,7 @@ TODO: there's possible optimization in send queue: remove already existing items from queue */ -#define API_VERSION 2 // version number to be increased with each API change (int16) +#define API_VERSION 3 // version number to be increased with each API change (int16) // #define DEBUG @@ -67,6 +67,7 @@ const uint16_t musicNotes[] PROGMEM = { 523, 587, 659, 698, 784, 880, 988, 1046 // input control byte constants #define CONTROL_START_RACE 'R' +#define CONTROL_START_RACE_ABS 'P' // race with absolute laps timing #define CONTROL_END_RACE 'r' #define CONTROL_DEC_MIN_LAP 'm' #define CONTROL_INC_MIN_LAP 'M' @@ -177,6 +178,7 @@ uint8_t isSendQueueFull = 0; //----- Lap timings-------------------------------- uint32_t lastMilliseconds = 0; +uint32_t raceStartTime = 0; #define MIN_MIN_LAP_TIME 1 //seconds #define MAX_MIN_LAP_TIME 60 //seconds uint8_t minLapTime = 5; //seconds @@ -196,6 +198,7 @@ uint8_t allowEdgeGeneration = 0; uint8_t channelIndex = 0; uint8_t bandIndex = 0; uint8_t isRaceStarted = 0; +uint8_t raceType = 0; // type 1: lap times are counted as absolute for each lap; type 2: lap times are relative to the race start (sum of previous absolute lap times); uint8_t isSoundEnabled = 1; uint8_t isConfigured = 0; //changes to 1 if any input changes the state of the device. it will mean that externally stored preferences should not be applied uint8_t rssiMonitor = 0; @@ -276,7 +279,7 @@ void loop() { gen_rising_edge(pinRaspiInt); //generate interrupt for EasyRaceLapTimer software uint32_t now = millis(); - uint32_t diff = now - lastMilliseconds; + uint32_t diff = now - lastMilliseconds; //time diff with the last lap (or with the race start) if (timeCalibrationConst) { diff = diff + (int32_t)diff/timeCalibrationConst; } @@ -284,7 +287,17 @@ void loop() { if (diff > minLapTime*1000 || (shouldSkipFirstLap && newLapIndex == 0)) { // if minLapTime haven't passed since last lap, then it's probably false alarm digitalLow(led); if (newLapIndex < MAX_LAPS-1) { // log time only if there are slots available - lapTimes[newLapIndex] = diff; + if (raceType == 1) { + // for the raceType 1 count time spent for each lap + lapTimes[newLapIndex] = diff; + } else { + // for the raceType 2 count times relative to the race start (ever-growing with each new lap within the race) + uint32_t diffStart = now - raceStartTime; + if (timeCalibrationConst) { + diffStart = diffStart + (int32_t)diffStart/timeCalibrationConst; + } + lapTimes[newLapIndex] = diffStart; + } newLapIndex++; lastLapsNotSent++; addToSendQueue(SEND_LAST_LAPTIMES); @@ -585,12 +598,25 @@ void handleSerialControlInput(uint8_t *controlData, uint8_t length) { } else { switch (controlByte) { case CONTROL_START_RACE: // start race - lastMilliseconds = millis(); + raceStartTime = millis(); + lastMilliseconds = raceStartTime; DEBUG_CODE( digitalHigh(serialTimerPin); ); newLapIndex = 0; isRaceStarted = 1; + raceType = 1; + allowEdgeGeneration = 0; + playStartRaceTones(); + addToSendQueue(SEND_RACE_STATE); + isConfigured = 1; + break; + case CONTROL_START_RACE_ABS: // start race with absolute laps timing + raceStartTime = millis(); + lastMilliseconds = raceStartTime; + newLapIndex = 0; + isRaceStarted = 1; + raceType = 2; allowEdgeGeneration = 0; playStartRaceTones(); addToSendQueue(SEND_RACE_STATE); From 3f07f4452ee2ed3fcd56b476a11ffca635316043 Mon Sep 17 00:00:00 2001 From: voroshkov Date: Thu, 26 Apr 2018 00:10:36 +0300 Subject: [PATCH 2/9] change threshold setup algorithm --- .../chorus_rf_laptimer/chorus_rf_laptimer.ino | 149 +++++++++++++++--- Arduino/chorus_rf_laptimer/pinAssignments.h | 2 +- Arduino/chorus_rf_laptimer/sounds.h | 29 ++++ 3 files changed, 159 insertions(+), 21 deletions(-) diff --git a/Arduino/chorus_rf_laptimer/chorus_rf_laptimer.ino b/Arduino/chorus_rf_laptimer/chorus_rf_laptimer.ino index 14592e2..71c9eda 100644 --- a/Arduino/chorus_rf_laptimer/chorus_rf_laptimer.ino +++ b/Arduino/chorus_rf_laptimer/chorus_rf_laptimer.ino @@ -143,7 +143,7 @@ const uint16_t musicNotes[] PROGMEM = { 523, 587, 659, 698, 784, 880, 988, 1046 #define SEND_ALL_DEVICE_STATE 255 //----- RSSI -------------------------------------- -#define FILTER_ITERATIONS 5 +#define FILTER_ITERATIONS 0 // software filtering iterations; set 0 - if filtered in hardware; set 5 - if not uint16_t rssiArr[FILTER_ITERATIONS + 1]; uint16_t rssiThreshold = 190; uint16_t rssi; @@ -158,6 +158,9 @@ uint16_t rssiThresholdArray[THRESHOLD_ARRAY_SIZE]; #define MIN_RSSI_MONITOR_DELAY_CYCLES 10 //each 1ms, if cycle takes 100us, to prevent loss of communication uint16_t rssiMonitorDelayCycles = DEFAULT_RSSI_MONITOR_DELAY_CYCLES; +#define RSSI_SETUP_INITIALIZE 0 +#define RSSI_SETUP_NEXT_STEP 1 + //----- Voltage monitoring ------------------------- #define VOLTAGE_READS 3 //get average of VOLTAGE_READS readings @@ -212,6 +215,7 @@ uint8_t shouldSendSingleItem = 0; uint8_t lastLapsNotSent = 0; uint16_t rssiMonitorDelayExpiration = 0; uint16_t frequency = 0; +uint8_t isSettingThreshold = 0; //----- read/write bufs --------------------------- #define READ_BUFFER_SIZE 20 @@ -231,9 +235,9 @@ uint8_t proxyBufDataSize = 0; // ---------------------------------------------------------------------------- void setup() { - // initialize digital pin 13 LED as an output. - pinMode(led, OUTPUT); - digitalHigh(led); + // initialize led pin as output. + pinMode(ledPin, OUTPUT); + digitalHigh(ledPin); // init buzzer pin pinMode(buzzerPin, OUTPUT); @@ -253,8 +257,8 @@ void setup() { initFastADC(); - // Setup Done - Turn Status LED off. - digitalLow(led); + // Setup Done - Turn Status ledPin off. + digitalLow(ledPin); DEBUG_CODE( pinMode(serialTimerPin, OUTPUT); @@ -285,7 +289,7 @@ void loop() { } if (isRaceStarted) { // if we're within the race, then log lap time if (diff > minLapTime*1000 || (shouldSkipFirstLap && newLapIndex == 0)) { // if minLapTime haven't passed since last lap, then it's probably false alarm - digitalLow(led); + // digitalLow(ledPin); if (newLapIndex < MAX_LAPS-1) { // log time only if there are slots available if (raceType == 1) { // for the raceType 1 count time spent for each lap @@ -313,7 +317,7 @@ void loop() { } else { allowEdgeGeneration = 1; // we're below the threshold, be ready to catch another case - digitalHigh(led); + // digitalHigh(ledPin); } } @@ -473,6 +477,10 @@ void loop() { } } + if (isSettingThreshold) { + setupThreshold(RSSI_SETUP_NEXT_STEP); + } + if (isSoundEnabled && playSound) { if (playStartTime == 0) { tone(buzzerPin,curToneSeq[curToneIndex]); @@ -693,8 +701,7 @@ void handleSerialControlInput(uint8_t *controlData, uint8_t length) { break; case CONTROL_SET_THRESHOLD: // set threshold setOrDropThreshold(); - addToSendQueue(SEND_THRESHOLD); - isConfigured = 1; + break; case CONTROL_SET_SOUND: // set sound isSoundEnabled = !isSoundEnabled; @@ -744,6 +751,10 @@ void readSerialDataChunk () { uint8_t availBytes = Serial.available(); if (availBytes) { + if (availBytes > READ_BUFFER_SIZE) { + digitalHigh(ledPin); + } + uint8_t freeBufBytes = READ_BUFFER_SIZE - readBufFilledBytes; //reset buffer if we couldn't find delimiter in its contents in prev step @@ -893,22 +904,120 @@ void decThreshold() { // ---------------------------------------------------------------------------- void setOrDropThreshold() { if (rssiThreshold == 0) { - uint16_t median; - for(uint8_t i=0; i < THRESHOLD_ARRAY_SIZE; i++) { - rssiThresholdArray[i] = getFilteredRSSI(); - } - sortArray(rssiThresholdArray, THRESHOLD_ARRAY_SIZE); - median = getMedian(rssiThresholdArray, THRESHOLD_ARRAY_SIZE); - if (median > MAGIC_THRESHOLD_REDUCE_CONSTANT) { - rssiThreshold = median - MAGIC_THRESHOLD_REDUCE_CONSTANT; - playSetThresholdTones(); - } + // uint16_t median; + // for(uint8_t i=0; i < THRESHOLD_ARRAY_SIZE; i++) { + // rssiThresholdArray[i] = getFilteredRSSI(); + // } + // sortArray(rssiThresholdArray, THRESHOLD_ARRAY_SIZE); + // median = getMedian(rssiThresholdArray, THRESHOLD_ARRAY_SIZE); + // if (median > MAGIC_THRESHOLD_REDUCE_CONSTANT) { + // rssiThreshold = median - MAGIC_THRESHOLD_REDUCE_CONSTANT; + // playSetThresholdTones(); + // } + isSettingThreshold = 1; + setupThreshold(RSSI_SETUP_INITIALIZE); } else { rssiThreshold = 0; + isSettingThreshold = 0; + isConfigured = 1; + addToSendQueue(SEND_THRESHOLD); playClearThresholdTones(); } } + +// ---------------------------------------------------------------------------- +void setupThreshold(uint8_t phase) { + // this process assumes the following: + // 1. before the process all VTXs are turned ON, but are distant from the Chorus device, so that Chorus sees the "background" rssi values only + // 2. once the setup process is initiated by Chorus operator, all pilots walk towards the Chorus device + // 3. setup process starts tracking top rssi values + // 4. as pilots come closer, rssi should rise above the value defined by RISE_RSSI_THRESHOLD_PERCENT + // 5. after that setup expects rssi to fall from the reached top, down by FALL_RSSI_THRESHOLD_PERCENT + // 6. after the rssi falls, the top recorded value (decreased by TOP_RSSI_DECREASE_PERCENT) is set as a threshold + + // time constant for accumulation filter: higher value => more delay + // value of 20 should give about 100 readings before value reaches the settled rssi + #define ACCUMULATION_TIME_CONSTANT 100 + #define MILLIS_BETWEEN_ACCU_READS 10 // artificial delay between rssi reads to slow down the accumulation + #define TOP_RSSI_DECREASE_PERCENT 5 // decrease top value by this percent using diff between low and high as a base + #define RISE_RSSI_THRESHOLD_PERCENT 20 // rssi value should pass this percentage above low value to continue finding the peak and further fall down of rssi + #define FALL_RSSI_THRESHOLD_PERCENT 50 // rssi should fall below this percentage of diff between high and low to finalize setup of the threshold + + static uint8_t rssiSetupPhase; + static uint16_t rssiLow; + static uint16_t rssiHigh; + static uint16_t rssiHighEnoughForMonitoring; + static uint32_t accumulatedShiftedRssi; // accumulates rssi slowly; contains multiplied rssi value for better accuracy + static uint32_t lastRssiAccumulationTime; + + if (!isSettingThreshold) return; // just for safety, normally it's controlled outside + + if (phase == RSSI_SETUP_INITIALIZE) { + // initialization step + playThresholdSetupStartTones(); + rssiThreshold = 0xFFFF; // just to make it clearable by setThreshold function + isSettingThreshold = 1; + rssiSetupPhase = 0; + rssiLow = rssi; + rssiHigh = rssi; + accumulatedShiftedRssi = rssi * ACCUMULATION_TIME_CONSTANT; // multiply to prevent loss in accuracy + rssiHighEnoughForMonitoring = rssiLow + rssiLow * RISE_RSSI_THRESHOLD_PERCENT / 100; + lastRssiAccumulationTime = millis(); + } else { + // active phase step (searching for high value and fall down) + if (rssiSetupPhase == 0) { + // in this phase of the setup we are tracking rssi growth until it reaches the predefined percentage from low + + // searching for peak + if (rssi > rssiHigh) { + rssiHigh = rssi; + } + + // since filter runs too fast, we have to introduce a delay between subsequent readings of filter values + uint32_t curTime = millis(); + if ((curTime - lastRssiAccumulationTime) > MILLIS_BETWEEN_ACCU_READS) { + lastRssiAccumulationTime = curTime; + // this is actually a filter with a delay determined by ACCUMULATION_TIME_CONSTANT + accumulatedShiftedRssi = rssi + (accumulatedShiftedRssi * (ACCUMULATION_TIME_CONSTANT - 1) / ACCUMULATION_TIME_CONSTANT ); + } + + uint16_t accumulatedRssi = accumulatedShiftedRssi / ACCUMULATION_TIME_CONSTANT; // find actual rssi from multiplied value + + if (accumulatedRssi > rssiHighEnoughForMonitoring) { + rssiSetupPhase = 1; + accumulatedShiftedRssi = rssiHigh * ACCUMULATION_TIME_CONSTANT; + playThresholdSetupMiddleTones(); + } + } else { + // in this phase of the setup we are tracking highest rssi and expect it to fall back down so that we know that the process is complete + + if (rssi > rssiHigh) { + rssiHigh = rssi; + accumulatedShiftedRssi = rssiHigh * ACCUMULATION_TIME_CONSTANT; // set to highest found rssi + } + + // since filter runs too fast, we have to introduce a delay between subsequent readings of filter values + uint32_t curTime = millis(); + if ((curTime - lastRssiAccumulationTime) > MILLIS_BETWEEN_ACCU_READS) { + lastRssiAccumulationTime = curTime; + // this is actually a filter with a delay determined by ACCUMULATION_TIME_CONSTANT + accumulatedShiftedRssi = rssi + (accumulatedShiftedRssi * (ACCUMULATION_TIME_CONSTANT - 1) / ACCUMULATION_TIME_CONSTANT ); + } + uint16_t accumulatedRssi = accumulatedShiftedRssi / ACCUMULATION_TIME_CONSTANT; + + uint16_t rssiLowEnoughForSetup = rssiHigh - (rssiHigh - rssiLow) * FALL_RSSI_THRESHOLD_PERCENT / 100; + if (accumulatedRssi < rssiLowEnoughForSetup) { + rssiThreshold = rssiHigh - ((rssiHigh - rssiLow) * TOP_RSSI_DECREASE_PERCENT) / 100; + isSettingThreshold = 0; + isConfigured = 1; + playThresholdSetupDoneTones(); + addToSendQueue(SEND_THRESHOLD); + } + } + } +} + // ---------------------------------------------------------------------------- void setThresholdValue(uint16_t threshold) { rssiThreshold = threshold; diff --git a/Arduino/chorus_rf_laptimer/pinAssignments.h b/Arduino/chorus_rf_laptimer/pinAssignments.h index 451f835..faf377c 100644 --- a/Arduino/chorus_rf_laptimer/pinAssignments.h +++ b/Arduino/chorus_rf_laptimer/pinAssignments.h @@ -29,7 +29,7 @@ SOFTWARE. // Pin definitions -#define led 13 +#define ledPin 13 #define spiDataPin 10 #define slaveSelectPin 11 #define spiClockPin 12 diff --git a/Arduino/chorus_rf_laptimer/sounds.h b/Arduino/chorus_rf_laptimer/sounds.h index 72c5744..37d8808 100644 --- a/Arduino/chorus_rf_laptimer/sounds.h +++ b/Arduino/chorus_rf_laptimer/sounds.h @@ -39,6 +39,7 @@ uint8_t lastToneSeqIndex; //----- buzzer tone sequences ------------------------------ // pairs of [frequency(Hz), duration(ms), ...] +// NOTES: 523, 587, 659, 698, 784, 880, 988, 1046 #define TONE_SEQ_DETECT_LEN 8 uint16_t toneSeq_Detect [TONE_SEQ_DETECT_LEN] = { 950, 50, 1050, 50, 1250, 50, 1350, 50 }; @@ -58,6 +59,16 @@ uint16_t toneSeq_StartRace [TONE_SEQ_START_RACE_LEN] = { 1500, 700 }; #define TONE_SEQ_END_RACE_LEN 10 uint16_t toneSeq_EndRace [TONE_SEQ_END_RACE_LEN] = { 1500, 120, 0, 30, 1500, 120, 0, 30, 1500, 120 }; +#define TONE_SEQ_SETUP_THRESHOLD_START 10 +uint16_t toneSeq_SetupThresholdStart [TONE_SEQ_SETUP_THRESHOLD_START] = { 659, 100, 0, 20, 880, 100, 0, 20, 659, 100 }; + +#define TONE_SEQ_SETUP_THRESHOLD_MIDDLE 10 +uint16_t toneSeq_SetupThresholdMiddle [TONE_SEQ_SETUP_THRESHOLD_MIDDLE] = { 659, 150, 0, 30, 784, 150, 0, 30, 880, 150 }; + +#define TONE_SEQ_SETUP_THRESHOLD_DONE 14 +uint16_t toneSeq_SetupThresholdDone [TONE_SEQ_SETUP_THRESHOLD_DONE] = { 880, 150, 0, 30, 784, 150, 0, 30, 659, 150, 0, 30, 523, 500}; + + // ---------------------------------------------------------------------------- void startPlayingTones() { curToneIndex = 0; @@ -100,4 +111,22 @@ void playEndRaceTones() { curToneSeq = toneSeq_EndRace; lastToneSeqIndex = TONE_SEQ_END_RACE_LEN - 1; startPlayingTones(); +} +// ---------------------------------------------------------------------------- +void playThresholdSetupStartTones() { + curToneSeq = toneSeq_SetupThresholdStart; + lastToneSeqIndex = TONE_SEQ_SETUP_THRESHOLD_START - 1; + startPlayingTones(); +} +// ---------------------------------------------------------------------------- +void playThresholdSetupMiddleTones() { + curToneSeq = toneSeq_SetupThresholdMiddle; + lastToneSeqIndex = TONE_SEQ_SETUP_THRESHOLD_MIDDLE - 1; + startPlayingTones(); +} +// ---------------------------------------------------------------------------- +void playThresholdSetupDoneTones() { + curToneSeq = toneSeq_SetupThresholdDone; + lastToneSeqIndex = TONE_SEQ_SETUP_THRESHOLD_DONE - 1; + startPlayingTones(); } \ No newline at end of file From fb7f66588d41549eb4cf311811e07b56997ce752 Mon Sep 17 00:00:00 2001 From: voroshkov Date: Mon, 7 May 2018 12:57:03 +0300 Subject: [PATCH 3/9] update threshold setting algorithm: use slowRssi for peaks, revise constants to make more error-proof --- .../chorus_rf_laptimer/chorus_rf_laptimer.ino | 48 +++++++++++++------ Arduino/chorus_rf_laptimer/pinAssignments.h | 3 +- 2 files changed, 34 insertions(+), 17 deletions(-) diff --git a/Arduino/chorus_rf_laptimer/chorus_rf_laptimer.ino b/Arduino/chorus_rf_laptimer/chorus_rf_laptimer.ino index 71c9eda..76f667a 100644 --- a/Arduino/chorus_rf_laptimer/chorus_rf_laptimer.ino +++ b/Arduino/chorus_rf_laptimer/chorus_rf_laptimer.ino @@ -147,6 +147,7 @@ const uint16_t musicNotes[] PROGMEM = { 523, 587, 659, 698, 784, 880, 988, 1046 uint16_t rssiArr[FILTER_ITERATIONS + 1]; uint16_t rssiThreshold = 190; uint16_t rssi; +uint16_t slowRssi; #define RSSI_MAX 1024 #define RSSI_MIN 0 @@ -264,7 +265,7 @@ void setup() { pinMode(serialTimerPin, OUTPUT); pinMode(loopTimerPin, OUTPUT); pinMode(bufferBusyPin, OUTPUT); - pinMode(sequencePin, OUTPUT); + pinMode(dbgPin, OUTPUT); ); } // ---------------------------------------------------------------------------- @@ -273,14 +274,20 @@ void loop() { digitalToggle(loopTimerPin); ); - rssi = getFilteredRSSI(); + // TODO: revise if additional filtering is really needed + // TODO: if needed, then maybe use the same algorithm, as getSlowChangingRSSI to avoid decrease of values? + rssi = getFilteredRSSI(); // actually it doesn't filter + + if (!isRaceStarted) { // no need to get slowRssi during race time because it's used only in threshold setting, which is already set by the race time + slowRssi = getSlowChangingRSSI(); // filter RSSI + } // check rssi threshold to identify when drone finishes the lap if (rssiThreshold > 0) { // threshold = 0 means that we don't check rssi values if(rssi > rssiThreshold) { // rssi above the threshold - drone is near if (allowEdgeGeneration) { // we haven't fired event for this drone proximity case yet allowEdgeGeneration = 0; - gen_rising_edge(pinRaspiInt); //generate interrupt for EasyRaceLapTimer software + // gen_rising_edge(pinRaspiInt); //generate interrupt for EasyRaceLapTimer software uint32_t now = millis(); uint32_t diff = now - lastMilliseconds; //time diff with the last lap (or with the race start) @@ -938,10 +945,11 @@ void setupThreshold(uint8_t phase) { // time constant for accumulation filter: higher value => more delay // value of 20 should give about 100 readings before value reaches the settled rssi - #define ACCUMULATION_TIME_CONSTANT 100 + // don't make it bigger than 2000 to avoid overflow of accumulatedShiftedRssi + #define ACCUMULATION_TIME_CONSTANT 150 #define MILLIS_BETWEEN_ACCU_READS 10 // artificial delay between rssi reads to slow down the accumulation - #define TOP_RSSI_DECREASE_PERCENT 5 // decrease top value by this percent using diff between low and high as a base - #define RISE_RSSI_THRESHOLD_PERCENT 20 // rssi value should pass this percentage above low value to continue finding the peak and further fall down of rssi + #define TOP_RSSI_DECREASE_PERCENT 10 // decrease top value by this percent using diff between low and high as a base + #define RISE_RSSI_THRESHOLD_PERCENT 25 // rssi value should pass this percentage above low value to continue finding the peak and further fall down of rssi #define FALL_RSSI_THRESHOLD_PERCENT 50 // rssi should fall below this percentage of diff between high and low to finalize setup of the threshold static uint8_t rssiSetupPhase; @@ -959,9 +967,9 @@ void setupThreshold(uint8_t phase) { rssiThreshold = 0xFFFF; // just to make it clearable by setThreshold function isSettingThreshold = 1; rssiSetupPhase = 0; - rssiLow = rssi; - rssiHigh = rssi; - accumulatedShiftedRssi = rssi * ACCUMULATION_TIME_CONSTANT; // multiply to prevent loss in accuracy + rssiLow = slowRssi; // using slowRssi to avoid catching random current rssi + rssiHigh = rssiLow; + accumulatedShiftedRssi = rssiLow * ACCUMULATION_TIME_CONSTANT; // multiply to prevent loss in accuracy rssiHighEnoughForMonitoring = rssiLow + rssiLow * RISE_RSSI_THRESHOLD_PERCENT / 100; lastRssiAccumulationTime = millis(); } else { @@ -969,9 +977,9 @@ void setupThreshold(uint8_t phase) { if (rssiSetupPhase == 0) { // in this phase of the setup we are tracking rssi growth until it reaches the predefined percentage from low - // searching for peak - if (rssi > rssiHigh) { - rssiHigh = rssi; + // searching for peak; using slowRssi to avoid catching sudden random peaks + if (slowRssi > rssiHigh) { + rssiHigh = slowRssi; } // since filter runs too fast, we have to introduce a delay between subsequent readings of filter values @@ -979,7 +987,7 @@ void setupThreshold(uint8_t phase) { if ((curTime - lastRssiAccumulationTime) > MILLIS_BETWEEN_ACCU_READS) { lastRssiAccumulationTime = curTime; // this is actually a filter with a delay determined by ACCUMULATION_TIME_CONSTANT - accumulatedShiftedRssi = rssi + (accumulatedShiftedRssi * (ACCUMULATION_TIME_CONSTANT - 1) / ACCUMULATION_TIME_CONSTANT ); + accumulatedShiftedRssi = rssi + (accumulatedShiftedRssi * (ACCUMULATION_TIME_CONSTANT - 1) / ACCUMULATION_TIME_CONSTANT); } uint16_t accumulatedRssi = accumulatedShiftedRssi / ACCUMULATION_TIME_CONSTANT; // find actual rssi from multiplied value @@ -992,8 +1000,9 @@ void setupThreshold(uint8_t phase) { } else { // in this phase of the setup we are tracking highest rssi and expect it to fall back down so that we know that the process is complete - if (rssi > rssiHigh) { - rssiHigh = rssi; + // continue searching for peak; using slowRssi to avoid catching sudden random peaks + if (slowRssi > rssiHigh) { + rssiHigh = slowRssi; accumulatedShiftedRssi = rssiHigh * ACCUMULATION_TIME_CONSTANT; // set to highest found rssi } @@ -1045,6 +1054,15 @@ uint16_t getFilteredRSSI() { return rssiArr[FILTER_ITERATIONS]; } // ---------------------------------------------------------------------------- +// this is just a digital filter function +uint16_t getSlowChangingRSSI() { + #define TIME_DELAY_CONSTANT 1000 // this is a filtering constant that regulates both depth of filtering and delay at the same time + static uint32_t slowRssiMultiplied; + + slowRssiMultiplied = rssi + (slowRssiMultiplied * (TIME_DELAY_CONSTANT - 1) / TIME_DELAY_CONSTANT ); + return slowRssiMultiplied / TIME_DELAY_CONSTANT; +} +// ---------------------------------------------------------------------------- void sortArray(uint16_t a[], uint16_t size) { for(uint16_t i=0; i<(size-1); i++) { for(uint16_t j=0; j<(size-(i+1)); j++) { diff --git a/Arduino/chorus_rf_laptimer/pinAssignments.h b/Arduino/chorus_rf_laptimer/pinAssignments.h index faf377c..6343f93 100644 --- a/Arduino/chorus_rf_laptimer/pinAssignments.h +++ b/Arduino/chorus_rf_laptimer/pinAssignments.h @@ -42,6 +42,5 @@ SOFTWARE. #define loopTimerPin 4 #define serialTimerPin 3 #define bufferBusyPin 7 - #define sequencePin 6 - + #define dbgPin 6 #endif From ec118d95c35221e9d256c7b2e027697bfe85cef6 Mon Sep 17 00:00:00 2001 From: voroshkov Date: Mon, 14 May 2018 12:51:07 +0300 Subject: [PATCH 4/9] stop setting rssi when race starts --- Arduino/chorus_rf_laptimer/chorus_rf_laptimer.ino | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Arduino/chorus_rf_laptimer/chorus_rf_laptimer.ino b/Arduino/chorus_rf_laptimer/chorus_rf_laptimer.ino index 76f667a..2b6c28b 100644 --- a/Arduino/chorus_rf_laptimer/chorus_rf_laptimer.ino +++ b/Arduino/chorus_rf_laptimer/chorus_rf_laptimer.ino @@ -620,6 +620,7 @@ void handleSerialControlInput(uint8_t *controlData, uint8_t length) { ); newLapIndex = 0; isRaceStarted = 1; + isSettingThreshold = 0; // safety measure: stop setting threshold upon race start to avoid losing time there raceType = 1; allowEdgeGeneration = 0; playStartRaceTones(); @@ -631,6 +632,7 @@ void handleSerialControlInput(uint8_t *controlData, uint8_t length) { lastMilliseconds = raceStartTime; newLapIndex = 0; isRaceStarted = 1; + isSettingThreshold = 0; // safety measure: stop setting threshold upon race start to avoid losing time there raceType = 2; allowEdgeGeneration = 0; playStartRaceTones(); From 56c9c45add221634a9c3aaf19727c2d9600ac1a9 Mon Sep 17 00:00:00 2001 From: voroshkov Date: Tue, 22 May 2018 23:00:31 +0300 Subject: [PATCH 5/9] reworks the API command set --- Arduino/chorus_rf_laptimer/channels.h | 2 +- .../chorus_rf_laptimer/chorus_rf_laptimer.ino | 623 ++++++++---------- Arduino/chorus_rf_laptimer/sounds.h | 10 +- 3 files changed, 274 insertions(+), 361 deletions(-) diff --git a/Arduino/chorus_rf_laptimer/channels.h b/Arduino/chorus_rf_laptimer/channels.h index 0231f4e..442fa2b 100644 --- a/Arduino/chorus_rf_laptimer/channels.h +++ b/Arduino/chorus_rf_laptimer/channels.h @@ -39,7 +39,7 @@ const uint16_t channelTable[] PROGMEM = { 0x2906, 0x2910, 0x291A, 0x2984, 0x298E, 0x2998, 0x2A02, 0x2A0C, // Band F / Airwave 0x2609, 0x261C, 0x268E, 0x2701, 0x2713, 0x2786, 0x2798, 0x280B, // Band D / 5.3 0x248f, 0x2499, 0x2503, 0x250d, 0x2909, 0x2913, 0x291d, 0x2987, //connex - 0x2991, 0x299b, 0x299b, 0x299b, 0x299b, 0x299b, 0x299b, 0x299b //even more connex, last 6 unused!!! + 0x2991, 0x299b, 0x299b, 0x299b, 0x299b, 0x299b, 0x299b, 0x299b //even more connex, last 6 unused!!! }; // Channels' MHz Values. Just for reference. Not used in code. diff --git a/Arduino/chorus_rf_laptimer/chorus_rf_laptimer.ino b/Arduino/chorus_rf_laptimer/chorus_rf_laptimer.ino index 2b6c28b..2888ed4 100644 --- a/Arduino/chorus_rf_laptimer/chorus_rf_laptimer.ino +++ b/Arduino/chorus_rf_laptimer/chorus_rf_laptimer.ino @@ -29,16 +29,16 @@ SOFTWARE. /* TODO: Resolve problem: - when CONTROL_DATA_REQUEST is being processed, i.e. all data items are sent in sequence, + when CONTROL_GET_ALL_DATA is being processed, i.e. all data items are sent in sequence, and either external or external logic decides to send an individual item, - then the latter just breaks execution of CONTROL_DATA_REQUEST and data is not sent completely + then the latter just breaks execution of CONTROL_GET_ALL_DATA and data is not sent completely This behavior should not occur in case of normal send queue processing. TODO: there's possible optimization in send queue: remove already existing items from queue */ -#define API_VERSION 3 // version number to be increased with each API change (int16) +#define API_VERSION 4 // version number to be increased with each API change (int16) // #define DEBUG @@ -65,82 +65,96 @@ const uint16_t musicNotes[] PROGMEM = { 523, 587, 659, 698, 784, 880, 988, 1046 // this means that we can theoretically have 1ms timing accuracy :) #define RSSI_READS 5 // 5 should give about 10 000 readings per second +// API in brief (sorted alphabetically): +// Req Resp Description +// 1 1 first lap counts (opposite to prev API) +// B B band +// C C channel +// F F freq +// H H threshold setup mode +// L lap (response only) +// I I rssi monitor interval (0 = off) +// J J time adjustment constant +// M M min lap time +// R R race mode +// S S device sounds state +// T T threshold +// get only: +// # # api version # +// a ... all device state +// r r rssi value +// t t time in milliseconds +// v v voltage +// x end of sequence sign (response to "a") +// y y is module configured (response to "a") + // input control byte constants -#define CONTROL_START_RACE 'R' -#define CONTROL_START_RACE_ABS 'P' // race with absolute laps timing -#define CONTROL_END_RACE 'r' -#define CONTROL_DEC_MIN_LAP 'm' -#define CONTROL_INC_MIN_LAP 'M' -#define CONTROL_DEC_CHANNEL 'c' -#define CONTROL_INC_CHANNEL 'C' -#define CONTROL_DEC_THRESHOLD 't' -#define CONTROL_INC_THRESHOLD 'T' -#define CONTROL_SET_THRESHOLD 'S' // it either triggers or sets an uint16 depending on command length -#define CONTROL_SET_SOUND 'D' -#define CONTROL_DATA_REQUEST 'A' -#define CONTROL_INC_BAND 'B' -#define CONTROL_DEC_BAND 'b' -#define CONTROL_START_CALIBRATE 'I' -#define CONTROL_END_CALIBRATE 'i' -#define CONTROL_MONITOR_ON 'V' -#define CONTROL_MONITOR_OFF 'v' -#define CONTROL_SET_SKIP_LAP0 'F' -#define CONTROL_GET_VOLTAGE 'Y' -#define CONTROL_GET_RSSI 'E' -#define CONTROL_GET_API_VERSION '#' -// input control byte constants for uint8 "set value" commands -#define CONTROL_SET_MIN_LAP 'L' -#define CONTROL_SET_CHANNEL 'H' -#define CONTROL_SET_BAND 'N' -// input control byte constants for uint16 "set value" commands -#define CONTROL_SET_FREQUENCY 'Q' -#define CONTROL_SET_MON_DELAY 'd' +// get/set: +#define CONTROL_WAIT_FIRST_LAP '1' +#define CONTROL_BAND 'B' +#define CONTROL_CHANNEL 'C' +#define CONTROL_FREQUENCY 'F' +#define CONTROL_THRESHOLD_SETUP 'H' +#define CONTROL_RSSI_MON_INTERVAL 'I' +#define CONTROL_TIME_ADJUSTMENT 'J' +#define CONTROL_RACE_MODE 'R' +#define CONTROL_MIN_LAP_TIME 'M' +#define CONTROL_SOUND 'S' +#define CONTROL_THRESHOLD 'T' +// get only: +#define CONTROL_GET_API_VERSION '#' +#define CONTROL_GET_ALL_DATA 'a' +#define CONTROL_GET_RSSI 'r' +#define CONTROL_GET_TIME 't' +#define CONTROL_GET_VOLTAGE 'v' +#define CONTROL_GET_IS_CONFIGURED 'y' // output id byte constants -#define RESPONSE_CHANNEL 'C' -#define RESPONSE_RACE_STATE 'R' -#define RESPONSE_MIN_LAP_TIME 'M' -#define RESPONSE_THRESHOLD 'T' -#define RESPONSE_CURRENT_RSSI 'S' -#define RESPONSE_LAPTIME 'L' -#define RESPONSE_SOUND_STATE 'D' -#define RESPONSE_BAND 'B' -#define RESPONSE_CALIBR_TIME 'I' -#define RESPONSE_CALIBR_STATE 'i' -#define RESPONSE_MONITOR_STATE 'V' -#define RESPONSE_LAP0_STATE 'F' -#define RESPONSE_END_SEQUENCE 'X' -#define RESPONSE_IS_CONFIGURED 'P' -#define RESPONSE_VOLTAGE 'Y' -#define RESPONSE_FREQUENCY 'Q' -#define RESPONSE_MONITOR_DELAY 'd' -#define RESPONSE_API_VERSION '#' +#define RESPONSE_WAIT_FIRST_LAP '1' +#define RESPONSE_BAND 'B' +#define RESPONSE_CHANNEL 'C' +#define RESPONSE_FREQUENCY 'F' +#define RESPONSE_THRESHOLD_SETUP 'H' +#define RESPONSE_RSSI_MON_INTERVAL 'I' +#define RESPONSE_TIME_ADJUSTMENT 'J' +#define RESPONSE_LAPTIME 'L' +#define RESPONSE_RACE_MODE 'R' +#define RESPONSE_MIN_LAP_TIME 'M' +#define RESPONSE_SOUND 'S' +#define RESPONSE_THRESHOLD 'T' + +#define RESPONSE_API_VERSION '#' +#define RESPONSE_RSSI 'r' +#define RESPONSE_TIME 't' +#define RESPONSE_VOLTAGE 'v' +#define RESPONSE_END_SEQUENCE 'x' +#define RESPONSE_IS_CONFIGURED 'y' // send item byte constants // Must correspond to sequence of numbers used in "send data" switch statement // Subsequent items starting from 0 participate in "send all data" response -#define SEND_CHANNEL 0 -#define SEND_RACE_STATE 1 -#define SEND_MIN_LAP_TIME 2 -#define SEND_THRESHOLD 3 -#define SEND_ALL_LAPTIMES 4 -#define SEND_SOUND_STATE 5 -#define SEND_BAND 6 -#define SEND_CALIBR_STATE 7 -#define SEND_MONITOR_STATE 8 -#define SEND_LAP0_STATE 9 -#define SEND_IS_CONFIGURED 10 -#define SEND_FREQUENCY 11 -#define SEND_MONITOR_DELAY 12 -#define SEND_API_VERSION 13 -#define SEND_END_SEQUENCE 14 +#define SEND_CHANNEL 0 +#define SEND_RACE_MODE 1 +#define SEND_MIN_LAP_TIME 2 +#define SEND_THRESHOLD 3 +#define SEND_ALL_LAPTIMES 4 +#define SEND_SOUND_STATE 5 +#define SEND_BAND 6 +#define SEND_LAP0_STATE 7 +#define SEND_IS_CONFIGURED 8 +#define SEND_FREQUENCY 9 +#define SEND_MON_INTERVAL 10 +#define SEND_TIME_ADJUSTMENT 11 +#define SEND_API_VERSION 12 +#define SEND_END_SEQUENCE 13 // following items don't participate in "send all items" response -#define SEND_LAST_LAPTIMES 100 -#define SEND_CALIBR_TIME 101 -#define SEND_CURRENT_RSSI 102 -#define SEND_VOLTAGE 103 +#define SEND_LAST_LAPTIMES 100 +#define SEND_TIME 101 +#define SEND_CURRENT_RSSI 102 +#define SEND_VOLTAGE 103 +#define SEND_THRESHOLD_SETUP_MODE 104 // special item that sends all subsequent items from 0 (see above) -#define SEND_ALL_DEVICE_STATE 255 +#define SEND_ALL_DEVICE_STATE 255 //----- RSSI -------------------------------------- #define FILTER_ITERATIONS 0 // software filtering iterations; set 0 - if filtered in hardware; set 5 - if not @@ -155,9 +169,9 @@ uint16_t slowRssi; #define THRESHOLD_ARRAY_SIZE 100 uint16_t rssiThresholdArray[THRESHOLD_ARRAY_SIZE]; -#define DEFAULT_RSSI_MONITOR_DELAY_CYCLES 1000 //each 100ms, if cycle takes 100us -#define MIN_RSSI_MONITOR_DELAY_CYCLES 10 //each 1ms, if cycle takes 100us, to prevent loss of communication -uint16_t rssiMonitorDelayCycles = DEFAULT_RSSI_MONITOR_DELAY_CYCLES; +#define MIN_RSSI_MONITOR_INTERVAL 1 // in milliseconds +uint16_t rssiMonitorInterval = 0; // zero means the RSSI monitor is OFF +uint32_t lastRssiMonitorReading = 0; // millis when rssi monitor value was last read #define RSSI_SETUP_INITIALIZE 0 #define RSSI_SETUP_NEXT_STEP 1 @@ -171,8 +185,6 @@ uint16_t rssiMonitorDelayCycles = DEFAULT_RSSI_MONITOR_DELAY_CYCLES; // entire Chorus device will produce a single voltage response. #define VOLTAGE_ZERO_THRESHOLD 100 -uint16_t voltage = 0; - //----- Send Queue --------------------------------- #define SEND_QUEUE_MAXLEN 20 uint8_t sendQueue[SEND_QUEUE_MAXLEN]; @@ -184,39 +196,38 @@ uint8_t isSendQueueFull = 0; uint32_t lastMilliseconds = 0; uint32_t raceStartTime = 0; #define MIN_MIN_LAP_TIME 1 //seconds -#define MAX_MIN_LAP_TIME 60 //seconds -uint8_t minLapTime = 5; //seconds +#define MAX_MIN_LAP_TIME 120 //seconds +uint8_t minLapTime = 1; //seconds #define MAX_LAPS 100 uint32_t lapTimes[MAX_LAPS]; -//----- Calibration ------------------------------- -uint8_t isCalibrated = 0; -uint32_t calibrationMilliseconds = 0; - -// Usage of signed int calibration constant: -// calibratedMs = readMs + readMs/timeCalibrationConst -int32_t timeCalibrationConst = 0; +//----- Time Adjustment (for accuracy) ------------ +#define INFINITE_TIME_ADJUSTMENT 0x7FFFFFFFF // max positive 32 bit signed number +// Usage of signed int time adjustment constant inside this firmware: +// * calibratedMs = readMs + readMs/timeAdjustment +// Usage of signed int time adjustment constant from outside: +// * set to zero means time adjustment procedure was not performed for this node +// * set to INFINITE_TIME_ADJUSTMENT, means time adjustment was performed, but no need to adjust +int32_t timeAdjustment = 0; //----- other globals------------------------------ -uint8_t allowEdgeGeneration = 0; +uint8_t allowLapGeneration = 0; uint8_t channelIndex = 0; uint8_t bandIndex = 0; -uint8_t isRaceStarted = 0; -uint8_t raceType = 0; // type 1: lap times are counted as absolute for each lap; type 2: lap times are relative to the race start (sum of previous absolute lap times); +uint8_t raceMode = 0; // 0: race mode is off; 1: lap times are counted relative to last lap end; 2: lap times are relative to the race start (sum of all previous lap times); uint8_t isSoundEnabled = 1; uint8_t isConfigured = 0; //changes to 1 if any input changes the state of the device. it will mean that externally stored preferences should not be applied -uint8_t rssiMonitor = 0; uint8_t newLapIndex = 0; -uint8_t shouldSkipFirstLap = 1; //start table is before the laptimer, so first lap is not a full-fledged lap (i.e. don't respect min-lap-time for the very first lap) +uint8_t shouldWaitForFirstLap = 0; // 0 means start table is before the laptimer, so first lap is not a full-fledged lap (i.e. don't respect min-lap-time for the very first lap) uint8_t isSendingData = 0; uint8_t sendStage = 0; uint8_t sendLapTimesIndex = 0; uint8_t sendLastLapIndex = 0; uint8_t shouldSendSingleItem = 0; uint8_t lastLapsNotSent = 0; -uint16_t rssiMonitorDelayExpiration = 0; +uint8_t thresholdSetupMode = 0; uint16_t frequency = 0; -uint8_t isSettingThreshold = 0; +uint32_t millisUponRequest = 0; //----- read/write bufs --------------------------- #define READ_BUFFER_SIZE 20 @@ -278,34 +289,33 @@ void loop() { // TODO: if needed, then maybe use the same algorithm, as getSlowChangingRSSI to avoid decrease of values? rssi = getFilteredRSSI(); // actually it doesn't filter - if (!isRaceStarted) { // no need to get slowRssi during race time because it's used only in threshold setting, which is already set by the race time + if (!raceMode) { // no need to get slowRssi during race time because it's used only in threshold setting, which is already set by the race time slowRssi = getSlowChangingRSSI(); // filter RSSI } // check rssi threshold to identify when drone finishes the lap if (rssiThreshold > 0) { // threshold = 0 means that we don't check rssi values if(rssi > rssiThreshold) { // rssi above the threshold - drone is near - if (allowEdgeGeneration) { // we haven't fired event for this drone proximity case yet - allowEdgeGeneration = 0; - // gen_rising_edge(pinRaspiInt); //generate interrupt for EasyRaceLapTimer software + if (allowLapGeneration) { // we haven't fired event for this drone proximity case yet + allowLapGeneration = 0; uint32_t now = millis(); uint32_t diff = now - lastMilliseconds; //time diff with the last lap (or with the race start) - if (timeCalibrationConst) { - diff = diff + (int32_t)diff/timeCalibrationConst; + if (timeAdjustment != 0 && timeAdjustment != INFINITE_TIME_ADJUSTMENT) { + diff = diff + (int32_t)diff/timeAdjustment; } - if (isRaceStarted) { // if we're within the race, then log lap time - if (diff > minLapTime*1000 || (shouldSkipFirstLap && newLapIndex == 0)) { // if minLapTime haven't passed since last lap, then it's probably false alarm + if (raceMode) { // if we're within the race, then log lap time + if (diff > minLapTime*1000 || (!shouldWaitForFirstLap && newLapIndex == 0)) { // if minLapTime haven't passed since last lap, then it's probably false alarm // digitalLow(ledPin); if (newLapIndex < MAX_LAPS-1) { // log time only if there are slots available - if (raceType == 1) { - // for the raceType 1 count time spent for each lap + if (raceMode == 1) { + // for the raceMode 1 count time spent for each lap lapTimes[newLapIndex] = diff; } else { - // for the raceType 2 count times relative to the race start (ever-growing with each new lap within the race) + // for the raceMode 2 count times relative to the race start (ever-growing with each new lap within the race) uint32_t diffStart = now - raceStartTime; - if (timeCalibrationConst) { - diffStart = diffStart + (int32_t)diffStart/timeCalibrationConst; + if (timeAdjustment != 0 && timeAdjustment != INFINITE_TIME_ADJUSTMENT) { + diffStart = diffStart + (int32_t)diffStart/timeAdjustment; } lapTimes[newLapIndex] = diffStart; } @@ -323,7 +333,7 @@ void loop() { } } else { - allowEdgeGeneration = 1; // we're below the threshold, be ready to catch another case + allowLapGeneration = 1; // we're below the threshold, be ready to catch another case // digitalHigh(ledPin); } } @@ -341,8 +351,8 @@ void loop() { onItemSent(); } break; - case 1: // SEND_RACE_STATE - if (send4BitsToSerial(RESPONSE_RACE_STATE, isRaceStarted)) { + case 1: // SEND_RACE_MODE + if (send4BitsToSerial(RESPONSE_RACE_MODE, raceMode)) { DEBUG_CODE( digitalLow(serialTimerPin); ); @@ -370,7 +380,7 @@ void loop() { } break; case 5: // SEND_SOUND_STATE - if (send4BitsToSerial(RESPONSE_SOUND_STATE, isSoundEnabled)) { + if (send4BitsToSerial(RESPONSE_SOUND, isSoundEnabled)) { onItemSent(); } break; @@ -379,44 +389,39 @@ void loop() { onItemSent(); } break; - case 7: // SEND_CALIBR_STATE - if (send4BitsToSerial(RESPONSE_CALIBR_STATE, isCalibrated)) { + case 7: // SEND_LAP0_STATE + if (send4BitsToSerial(RESPONSE_WAIT_FIRST_LAP, shouldWaitForFirstLap)) { onItemSent(); } break; - case 8: // SEND_MONITOR_STATE - if (send4BitsToSerial(RESPONSE_MONITOR_STATE, rssiMonitor)) { - onItemSent(); - } - break; - case 9: // SEND_LAP0_STATE - if (send4BitsToSerial(RESPONSE_LAP0_STATE, shouldSkipFirstLap)) { + case 8: // SEND_IS_CONFIGURED + if (send4BitsToSerial(RESPONSE_IS_CONFIGURED, isConfigured)) { onItemSent(); } break; - case 10: // SEND_IS_CONFIGURED - if (send4BitsToSerial(RESPONSE_IS_CONFIGURED, isConfigured)) { + case 9: // SEND_FREQUENCY + if (sendIntToSerial(RESPONSE_FREQUENCY, frequency)) { onItemSent(); } break; - case 11: // SEND_FREQUENCY - if (sendIntToSerial(RESPONSE_FREQUENCY, frequency)) { + case 10: // SEND_MON_INTERVAL + if (sendIntToSerial(RESPONSE_RSSI_MON_INTERVAL, rssiMonitorInterval)) { onItemSent(); } break; - case 12: // SEND_MONITOR_DELAY - if (sendIntToSerial(RESPONSE_MONITOR_DELAY, rssiMonitorDelayCycles)) { + case 11: // SEND_TIME_ADJUSTMENT + if (sendLongToSerial(RESPONSE_TIME_ADJUSTMENT, timeAdjustment)) { onItemSent(); } break; - case 13: // SEND_API_VERSION + case 12: // SEND_API_VERSION if (sendIntToSerial(RESPONSE_API_VERSION, API_VERSION)) { onItemSent(); } break; - // Below is a termination case, to notify that data for CONTROL_DATA_REQUEST is over. + // Below is a termination case, to notify that data for CONTROL_GET_ALL_DATA is over. // Must be the last item in the sequence! - case 14: // SEND_END_SEQUENCE + case 13: // SEND_END_SEQUENCE if (send4BitsToSerial(RESPONSE_END_SEQUENCE, 1)) { onItemSent(); isSendingData = 0; @@ -426,7 +431,7 @@ void loop() { //-------------------------------------------------------------------------------------- // Here is the gap in sequence between items that are sent as response to - // "CONTROL_DATA_REQUEST" and items that are only sent individually + // "CONTROL_GET_ALL_DATA" and items that are only sent individually //-------------------------------------------------------------------------------------- case 100: // SEND_LAST_LAPTIMES @@ -441,16 +446,18 @@ void loop() { } break; case 101: // SEND_CALIBR_TIME - if (sendLongToSerial(RESPONSE_CALIBR_TIME, calibrationMilliseconds)) { + if (sendLongToSerial(RESPONSE_TIME, millisUponRequest)) { onItemSent(); } break; case 102: // SEND_CURRENT_RSSI - if (sendIntToSerial(RESPONSE_CURRENT_RSSI, rssi)) { + if (sendIntToSerial(RESPONSE_RSSI, rssi)) { onItemSent(); } break; case 103: // SEND_VOLTAGE + uint16_t voltage; + voltage = readVoltage(); if (voltage > VOLTAGE_ZERO_THRESHOLD) { if (sendIntToSerial(RESPONSE_VOLTAGE, voltage)) { onItemSent(); @@ -459,6 +466,11 @@ void loop() { onItemSent(); } break; + case 104: // SEND_THRESHOLD_SETUP_MODE + if (send4BitsToSerial(RESPONSE_THRESHOLD_SETUP, thresholdSetupMode)) { + onItemSent(); + } + break; default: isSendingData = 0; DEBUG_CODE( @@ -476,15 +488,15 @@ void loop() { } } - if (rssiMonitor) { - rssiMonitorDelayExpiration++; - if (rssiMonitorDelayExpiration >= rssiMonitorDelayCycles) { + if (rssiMonitorInterval) { + uint32_t ms = millis(); + if (ms - lastRssiMonitorReading >= rssiMonitorInterval) { addToSendQueue(SEND_CURRENT_RSSI); - rssiMonitorDelayExpiration = 0; + lastRssiMonitorReading = ms; } } - if (isSettingThreshold) { + if (thresholdSetupMode) { setupThreshold(RSSI_SETUP_NEXT_STEP); } @@ -569,17 +581,22 @@ void handleSerialControlInput(uint8_t *controlData, uint8_t length) { uint8_t controlByte = controlData[0]; uint8_t valueToSet; - if (length > 3) { + if (length > 3) { // set value commands switch(controlByte) { - case CONTROL_SET_CHANNEL: + case CONTROL_RACE_MODE: valueToSet = TO_BYTE(controlData[1]); - setChannel(valueToSet); + setRaceMode(valueToSet); + addToSendQueue(SEND_RACE_MODE); + isConfigured = 1; + break; + case CONTROL_WAIT_FIRST_LAP: + valueToSet = TO_BYTE(controlData[1]); + shouldWaitForFirstLap = valueToSet; playClickTones(); - addToSendQueue(SEND_CHANNEL); - addToSendQueue(SEND_FREQUENCY); + addToSendQueue(SEND_LAP0_STATE); isConfigured = 1; break; - case CONTROL_SET_BAND: + case CONTROL_BAND: valueToSet = TO_BYTE(controlData[1]); setBand(valueToSet); playClickTones(); @@ -587,169 +604,120 @@ void handleSerialControlInput(uint8_t *controlData, uint8_t length) { addToSendQueue(SEND_FREQUENCY); isConfigured = 1; break; - case CONTROL_SET_MIN_LAP: - valueToSet = HEX_TO_BYTE(controlData[1], controlData[2]); - setMinLap(valueToSet); + case CONTROL_CHANNEL: + valueToSet = TO_BYTE(controlData[1]); + setChannel(valueToSet); playClickTones(); - addToSendQueue(SEND_MIN_LAP_TIME); - isConfigured = 1; - break; - case CONTROL_SET_THRESHOLD: // set threshold - setThresholdValue(HEX_TO_UINT16(&controlData[1])); - addToSendQueue(SEND_THRESHOLD); + addToSendQueue(SEND_CHANNEL); + addToSendQueue(SEND_FREQUENCY); isConfigured = 1; break; - case CONTROL_SET_FREQUENCY: // set frequency + case CONTROL_FREQUENCY: frequency = setModuleFrequency(HEX_TO_UINT16(&controlData[1])); addToSendQueue(SEND_FREQUENCY); isConfigured = 1; break; - case CONTROL_SET_MON_DELAY: // set frequency - rssiMonitorDelayCycles = setRssiMonitorDelay(HEX_TO_UINT16(&controlData[1])); - addToSendQueue(SEND_MONITOR_DELAY); - isConfigured = 1; - break; - } - } else { - switch (controlByte) { - case CONTROL_START_RACE: // start race - raceStartTime = millis(); - lastMilliseconds = raceStartTime; - DEBUG_CODE( - digitalHigh(serialTimerPin); - ); - newLapIndex = 0; - isRaceStarted = 1; - isSettingThreshold = 0; // safety measure: stop setting threshold upon race start to avoid losing time there - raceType = 1; - allowEdgeGeneration = 0; - playStartRaceTones(); - addToSendQueue(SEND_RACE_STATE); - isConfigured = 1; - break; - case CONTROL_START_RACE_ABS: // start race with absolute laps timing - raceStartTime = millis(); - lastMilliseconds = raceStartTime; - newLapIndex = 0; - isRaceStarted = 1; - isSettingThreshold = 0; // safety measure: stop setting threshold upon race start to avoid losing time there - raceType = 2; - allowEdgeGeneration = 0; - playStartRaceTones(); - addToSendQueue(SEND_RACE_STATE); - isConfigured = 1; - break; - case CONTROL_END_CALIBRATE: // end calibration - calibrationMilliseconds = millis() - calibrationMilliseconds; - addToSendQueue(SEND_CALIBR_TIME); - break; - case CONTROL_START_CALIBRATE: // start calibration - calibrationMilliseconds = millis(); - break; - case CONTROL_END_RACE: // end race - isRaceStarted = 0; - newLapIndex = 0; - playEndRaceTones(); - addToSendQueue(SEND_RACE_STATE); + case CONTROL_RSSI_MON_INTERVAL: + rssiMonitorInterval = setRssiMonitorInterval(HEX_TO_UINT16(&controlData[1])); + lastRssiMonitorReading = 0; + addToSendQueue(SEND_MON_INTERVAL); isConfigured = 1; break; - case CONTROL_GET_RSSI: // get current RSSI value - addToSendQueue(SEND_CURRENT_RSSI); - break; - case CONTROL_DEC_MIN_LAP: // decrease minLapTime - decMinLap(); + case CONTROL_MIN_LAP_TIME: + valueToSet = HEX_TO_BYTE(controlData[1], controlData[2]); + setMinLap(valueToSet); playClickTones(); addToSendQueue(SEND_MIN_LAP_TIME); isConfigured = 1; break; - case CONTROL_INC_MIN_LAP: // increase minLapTime - incMinLap(); + case CONTROL_SOUND: + valueToSet = TO_BYTE(controlData[1]); + isSoundEnabled = valueToSet; + if (!isSoundEnabled) { + noTone(buzzerPin); + } + addToSendQueue(SEND_SOUND_STATE); playClickTones(); - addToSendQueue(SEND_MIN_LAP_TIME); isConfigured = 1; break; - case CONTROL_DEC_CHANNEL: // decrease channel - decChannel(); - playClickTones(); - addToSendQueue(SEND_CHANNEL); - addToSendQueue(SEND_FREQUENCY); + case CONTROL_THRESHOLD: + setThresholdValue(HEX_TO_UINT16(&controlData[1])); + addToSendQueue(SEND_THRESHOLD); isConfigured = 1; break; - case CONTROL_INC_CHANNEL: // increase channel - incChannel(); - playClickTones(); - addToSendQueue(SEND_CHANNEL); - addToSendQueue(SEND_FREQUENCY); + case CONTROL_TIME_ADJUSTMENT: + timeAdjustment = HEX_TO_SIGNED_LONG(&controlData[1]); + addToSendQueue(SEND_TIME_ADJUSTMENT); isConfigured = 1; break; - case CONTROL_DEC_BAND: // decrease band - decBand(); - playClickTones(); - addToSendQueue(SEND_BAND); - addToSendQueue(SEND_FREQUENCY); - isConfigured = 1; + case CONTROL_THRESHOLD_SETUP: // setup threshold using sophisticated algorithm + valueToSet = TO_BYTE(controlData[1]); + thresholdSetupMode = valueToSet; + if (raceMode) { // don't run threshold setup in race mode because we don't calculate slowRssi in race mode, but it's needed for setup threshold algorithm + thresholdSetupMode = 0; + } + if (thresholdSetupMode) { + setupThreshold(RSSI_SETUP_INITIALIZE); + } else { + playThresholdSetupStopTones(); + } + addToSendQueue(SEND_THRESHOLD_SETUP_MODE); break; - case CONTROL_INC_BAND: // increase channel - incBand(); - playClickTones(); + } + } else { // get value and other instructions + switch (controlByte) { + case CONTROL_GET_TIME: + millisUponRequest = millis(); + addToSendQueue(SEND_TIME); + break; + case CONTROL_WAIT_FIRST_LAP: + addToSendQueue(SEND_LAP0_STATE); + break; + case CONTROL_BAND: addToSendQueue(SEND_BAND); + break; + case CONTROL_CHANNEL: + addToSendQueue(SEND_CHANNEL); + break; + case CONTROL_FREQUENCY: addToSendQueue(SEND_FREQUENCY); - isConfigured = 1; break; - case CONTROL_DEC_THRESHOLD: // decrease threshold - decThreshold(); - playClickTones(); - addToSendQueue(SEND_THRESHOLD); - isConfigured = 1; + case CONTROL_RSSI_MON_INTERVAL: + addToSendQueue(SEND_MON_INTERVAL); break; - case CONTROL_INC_THRESHOLD: // increase threshold - incThreshold(); - playClickTones(); - addToSendQueue(SEND_THRESHOLD); - isConfigured = 1; + case CONTROL_RACE_MODE: + addToSendQueue(SEND_RACE_MODE); break; - case CONTROL_SET_THRESHOLD: // set threshold - setOrDropThreshold(); - + case CONTROL_MIN_LAP_TIME: + addToSendQueue(SEND_MIN_LAP_TIME); break; - case CONTROL_SET_SOUND: // set sound - isSoundEnabled = !isSoundEnabled; - if (!isSoundEnabled) { - noTone(buzzerPin); - } + case CONTROL_SOUND: addToSendQueue(SEND_SOUND_STATE); - playClickTones(); - isConfigured = 1; break; - case CONTROL_MONITOR_ON: // start RSSI monitor - rssiMonitor = 1; - rssiMonitorDelayExpiration = 0; - addToSendQueue(SEND_MONITOR_STATE); - // don't play tones here because it suppresses race tone when used simultaneously - // playClickTones(); - break; - case CONTROL_MONITOR_OFF: // stop RSSI monitor - rssiMonitor = 0; - addToSendQueue(SEND_MONITOR_STATE); - // don't play tones here because it suppresses race tone when used simultaneously - // playClickTones(); + case CONTROL_THRESHOLD: + addToSendQueue(SEND_THRESHOLD); break; - case CONTROL_SET_SKIP_LAP0: // set valid first lap - shouldSkipFirstLap = !shouldSkipFirstLap; - addToSendQueue(SEND_LAP0_STATE); - playClickTones(); - isConfigured = 1; + case CONTROL_GET_RSSI: // get current RSSI value + addToSendQueue(SEND_CURRENT_RSSI); break; case CONTROL_GET_VOLTAGE: //get battery voltage - voltage = readVoltage(); addToSendQueue(SEND_VOLTAGE); break; - case CONTROL_DATA_REQUEST: // request all data + case CONTROL_GET_ALL_DATA: // request all data addToSendQueue(SEND_ALL_DEVICE_STATE); break; case CONTROL_GET_API_VERSION: //get API version addToSendQueue(SEND_API_VERSION); break; + case CONTROL_TIME_ADJUSTMENT: + addToSendQueue(SEND_TIME_ADJUSTMENT); + break; + case CONTROL_THRESHOLD_SETUP: // get state of threshold setup process + addToSendQueue(SEND_THRESHOLD_SETUP_MODE); + break; + case CONTROL_GET_IS_CONFIGURED: + addToSendQueue(SEND_IS_CONFIGURED); + break; } } } @@ -792,7 +760,7 @@ void readSerialDataChunk () { //if delimiter found then process the command or send it further if (foundIdx < READ_BUFFER_SIZE) { switch (readBuf[0]) { - case 'R': //read data from module + case 'R': //request to module (command or get/set value) if (readBuf[1] == MODULE_ID_HEX) { //process input targeted for this device handleSerialControlInput(&readBuf[2], foundIdx); @@ -803,15 +771,6 @@ void readSerialDataChunk () { handleSerialControlInput(&readBuf[2], foundIdx); } break; - case 'C': //read calibration data for current module - if (readBuf[1] == MODULE_ID_HEX) { - timeCalibrationConst = HEX_TO_SIGNED_LONG(&readBuf[2]); - isCalibrated = 1; - shouldPassMsgFurther = 0; - addToSendQueue(SEND_CALIBR_STATE); - isConfigured = 1; - } - break; case 'N': //enumerate modules //process auto-enumeration request (save current number and pass next number further) MODULE_ID_HEX = readBuf[1]; @@ -839,15 +798,21 @@ void sendProxyDataChunk () { } } // ---------------------------------------------------------------------------- -void decMinLap() { - if (minLapTime > MIN_MIN_LAP_TIME) { - minLapTime--; - } -} -// ---------------------------------------------------------------------------- -void incMinLap() { - if (minLapTime < MAX_MIN_LAP_TIME) { - minLapTime++; +void setRaceMode(uint8_t mode) { + if (mode == 0) { // stop race + raceMode = 0; + newLapIndex = 0; + playEndRaceTones(); + } else { // start race in specified mode + raceMode = mode; + raceStartTime = millis(); + lastMilliseconds = raceStartTime; + newLapIndex = 0; + if (thresholdSetupMode) { + thresholdSetupMode = 0; // safety measure: stop setting threshold upon race start to avoid losing time there + addToSendQueue(SEND_THRESHOLD_SETUP_MODE); + } + playStartRaceTones(); } } // ---------------------------------------------------------------------------- @@ -857,20 +822,6 @@ void setMinLap(uint8_t mlt) { } } // ---------------------------------------------------------------------------- -void incChannel() { - if (channelIndex < 7) { - channelIndex++; - } - frequency = setModuleChannel(channelIndex, bandIndex); -} -// ---------------------------------------------------------------------------- -void decChannel() { - if (channelIndex > 0) { - channelIndex--; - } - frequency = setModuleChannel(channelIndex, bandIndex); -} -// ---------------------------------------------------------------------------- void setChannel(uint8_t channel) { if (channel >= 0 && channel <= 7) { channelIndex = channel; @@ -878,63 +829,12 @@ void setChannel(uint8_t channel) { } } // ---------------------------------------------------------------------------- -void incBand() { - if (bandIndex < MAX_BAND) { - bandIndex++; - } - frequency = setModuleChannel(channelIndex, bandIndex); -} -// ---------------------------------------------------------------------------- -void decBand() { - if (bandIndex > 0) { - bandIndex--; - } - frequency = setModuleChannel(channelIndex, bandIndex); -} -// ---------------------------------------------------------------------------- void setBand(uint8_t band) { if (band >= 0 && band <= MAX_BAND) { bandIndex = band; frequency = setModuleChannel(channelIndex, bandIndex); } } -// ---------------------------------------------------------------------------- -void incThreshold() { - if (rssiThreshold < RSSI_MAX) { - rssiThreshold++; - } -} -// ---------------------------------------------------------------------------- -void decThreshold() { - if (rssiThreshold > RSSI_MIN) { - rssiThreshold--; - } -} -// ---------------------------------------------------------------------------- -void setOrDropThreshold() { - if (rssiThreshold == 0) { - // uint16_t median; - // for(uint8_t i=0; i < THRESHOLD_ARRAY_SIZE; i++) { - // rssiThresholdArray[i] = getFilteredRSSI(); - // } - // sortArray(rssiThresholdArray, THRESHOLD_ARRAY_SIZE); - // median = getMedian(rssiThresholdArray, THRESHOLD_ARRAY_SIZE); - // if (median > MAGIC_THRESHOLD_REDUCE_CONSTANT) { - // rssiThreshold = median - MAGIC_THRESHOLD_REDUCE_CONSTANT; - // playSetThresholdTones(); - // } - isSettingThreshold = 1; - setupThreshold(RSSI_SETUP_INITIALIZE); - } - else { - rssiThreshold = 0; - isSettingThreshold = 0; - isConfigured = 1; - addToSendQueue(SEND_THRESHOLD); - playClearThresholdTones(); - } -} - // ---------------------------------------------------------------------------- void setupThreshold(uint8_t phase) { // this process assumes the following: @@ -954,21 +854,18 @@ void setupThreshold(uint8_t phase) { #define RISE_RSSI_THRESHOLD_PERCENT 25 // rssi value should pass this percentage above low value to continue finding the peak and further fall down of rssi #define FALL_RSSI_THRESHOLD_PERCENT 50 // rssi should fall below this percentage of diff between high and low to finalize setup of the threshold - static uint8_t rssiSetupPhase; static uint16_t rssiLow; static uint16_t rssiHigh; static uint16_t rssiHighEnoughForMonitoring; static uint32_t accumulatedShiftedRssi; // accumulates rssi slowly; contains multiplied rssi value for better accuracy static uint32_t lastRssiAccumulationTime; - if (!isSettingThreshold) return; // just for safety, normally it's controlled outside + if (!thresholdSetupMode) return; // just for safety, normally it's controlled outside if (phase == RSSI_SETUP_INITIALIZE) { // initialization step playThresholdSetupStartTones(); - rssiThreshold = 0xFFFF; // just to make it clearable by setThreshold function - isSettingThreshold = 1; - rssiSetupPhase = 0; + thresholdSetupMode = 1; rssiLow = slowRssi; // using slowRssi to avoid catching random current rssi rssiHigh = rssiLow; accumulatedShiftedRssi = rssiLow * ACCUMULATION_TIME_CONSTANT; // multiply to prevent loss in accuracy @@ -976,7 +873,7 @@ void setupThreshold(uint8_t phase) { lastRssiAccumulationTime = millis(); } else { // active phase step (searching for high value and fall down) - if (rssiSetupPhase == 0) { + if (thresholdSetupMode == 1) { // in this phase of the setup we are tracking rssi growth until it reaches the predefined percentage from low // searching for peak; using slowRssi to avoid catching sudden random peaks @@ -995,9 +892,10 @@ void setupThreshold(uint8_t phase) { uint16_t accumulatedRssi = accumulatedShiftedRssi / ACCUMULATION_TIME_CONSTANT; // find actual rssi from multiplied value if (accumulatedRssi > rssiHighEnoughForMonitoring) { - rssiSetupPhase = 1; + thresholdSetupMode = 2; accumulatedShiftedRssi = rssiHigh * ACCUMULATION_TIME_CONSTANT; playThresholdSetupMiddleTones(); + addToSendQueue(SEND_THRESHOLD_SETUP_MODE); } } else { // in this phase of the setup we are tracking highest rssi and expect it to fall back down so that we know that the process is complete @@ -1020,9 +918,10 @@ void setupThreshold(uint8_t phase) { uint16_t rssiLowEnoughForSetup = rssiHigh - (rssiHigh - rssiLow) * FALL_RSSI_THRESHOLD_PERCENT / 100; if (accumulatedRssi < rssiLowEnoughForSetup) { rssiThreshold = rssiHigh - ((rssiHigh - rssiLow) * TOP_RSSI_DECREASE_PERCENT) / 100; - isSettingThreshold = 0; + thresholdSetupMode = 0; isConfigured = 1; playThresholdSetupDoneTones(); + addToSendQueue(SEND_THRESHOLD_SETUP_MODE); addToSendQueue(SEND_THRESHOLD); } } @@ -1031,16 +930,22 @@ void setupThreshold(uint8_t phase) { // ---------------------------------------------------------------------------- void setThresholdValue(uint16_t threshold) { + // stop the "setting threshold algorithm" to avoid overwriting the explicitly set value + if (thresholdSetupMode) { + thresholdSetupMode = 0; + addToSendQueue(SEND_THRESHOLD_SETUP_MODE); + } rssiThreshold = threshold; if (threshold != 0) { - playSetThresholdTones(); + playClickTones(); } else { playClearThresholdTones(); } } // ---------------------------------------------------------------------------- -uint16_t setRssiMonitorDelay(uint16_t delay) { - return delay < MIN_RSSI_MONITOR_DELAY_CYCLES ? MIN_RSSI_MONITOR_DELAY_CYCLES : delay; +uint16_t setRssiMonitorInterval(uint16_t interval) { + // valid values are: zero and others above MIN_RSSI_MONITOR_INTERVAL + return (interval > 0 && interval < MIN_RSSI_MONITOR_INTERVAL) ? MIN_RSSI_MONITOR_INTERVAL : interval; } // ---------------------------------------------------------------------------- uint16_t getFilteredRSSI() { diff --git a/Arduino/chorus_rf_laptimer/sounds.h b/Arduino/chorus_rf_laptimer/sounds.h index 37d8808..e585314 100644 --- a/Arduino/chorus_rf_laptimer/sounds.h +++ b/Arduino/chorus_rf_laptimer/sounds.h @@ -60,7 +60,10 @@ uint16_t toneSeq_StartRace [TONE_SEQ_START_RACE_LEN] = { 1500, 700 }; uint16_t toneSeq_EndRace [TONE_SEQ_END_RACE_LEN] = { 1500, 120, 0, 30, 1500, 120, 0, 30, 1500, 120 }; #define TONE_SEQ_SETUP_THRESHOLD_START 10 -uint16_t toneSeq_SetupThresholdStart [TONE_SEQ_SETUP_THRESHOLD_START] = { 659, 100, 0, 20, 880, 100, 0, 20, 659, 100 }; +uint16_t toneSeq_SetupThresholdStart [TONE_SEQ_SETUP_THRESHOLD_START] = { 659, 100, 0, 20, 659, 100, 0, 20, 880, 100 }; + +#define TONE_SEQ_SETUP_THRESHOLD_STOP 10 +uint16_t toneSeq_SetupThresholdStop [TONE_SEQ_SETUP_THRESHOLD_STOP] = { 880, 100, 0, 20, 880, 100, 0, 20, 659, 100 }; #define TONE_SEQ_SETUP_THRESHOLD_MIDDLE 10 uint16_t toneSeq_SetupThresholdMiddle [TONE_SEQ_SETUP_THRESHOLD_MIDDLE] = { 659, 150, 0, 30, 784, 150, 0, 30, 880, 150 }; @@ -119,6 +122,11 @@ void playThresholdSetupStartTones() { startPlayingTones(); } // ---------------------------------------------------------------------------- +void playThresholdSetupStopTones() { + curToneSeq = toneSeq_SetupThresholdStop; + lastToneSeqIndex = TONE_SEQ_SETUP_THRESHOLD_STOP - 1; + startPlayingTones(); +}// ---------------------------------------------------------------------------- void playThresholdSetupMiddleTones() { curToneSeq = toneSeq_SetupThresholdMiddle; lastToneSeqIndex = TONE_SEQ_SETUP_THRESHOLD_MIDDLE - 1; From 446b9fa77f956930b76c1864b5f575ab92fa4417 Mon Sep 17 00:00:00 2001 From: voroshkov Date: Wed, 23 May 2018 23:18:54 +0300 Subject: [PATCH 6/9] update API doc with v4 commands --- docs/ChorusAPI.xlsx | Bin 13416 -> 13424 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/docs/ChorusAPI.xlsx b/docs/ChorusAPI.xlsx index 907be661b260cbea0562e33a13a52126970a394a..250738120256c65d858bff82502ca1f963169066 100644 GIT binary patch delta 5691 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 L2}1z%pVEH;{h_4j delta 5694 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 From 57cdcf9a65e0ed2affc7d0c9fa628b133f22e385 Mon Sep 17 00:00:00 2001 From: voroshkov Date: Thu, 24 May 2018 17:55:08 +0300 Subject: [PATCH 7/9] add more reports to bulk state response (for consistency) --- .../chorus_rf_laptimer/chorus_rf_laptimer.ino | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/Arduino/chorus_rf_laptimer/chorus_rf_laptimer.ino b/Arduino/chorus_rf_laptimer/chorus_rf_laptimer.ino index 2888ed4..6094625 100644 --- a/Arduino/chorus_rf_laptimer/chorus_rf_laptimer.ino +++ b/Arduino/chorus_rf_laptimer/chorus_rf_laptimer.ino @@ -146,13 +146,13 @@ const uint16_t musicNotes[] PROGMEM = { 523, 587, 659, 698, 784, 880, 988, 1046 #define SEND_MON_INTERVAL 10 #define SEND_TIME_ADJUSTMENT 11 #define SEND_API_VERSION 12 -#define SEND_END_SEQUENCE 13 +#define SEND_VOLTAGE 13 +#define SEND_THRESHOLD_SETUP_MODE 14 +#define SEND_END_SEQUENCE 15 // following items don't participate in "send all items" response #define SEND_LAST_LAPTIMES 100 #define SEND_TIME 101 #define SEND_CURRENT_RSSI 102 -#define SEND_VOLTAGE 103 -#define SEND_THRESHOLD_SETUP_MODE 104 // special item that sends all subsequent items from 0 (see above) #define SEND_ALL_DEVICE_STATE 255 @@ -419,9 +419,25 @@ void loop() { onItemSent(); } break; + case 13: // SEND_VOLTAGE + uint16_t voltage; + voltage = readVoltage(); + if (voltage > VOLTAGE_ZERO_THRESHOLD) { + if (sendIntToSerial(RESPONSE_VOLTAGE, voltage)) { + onItemSent(); + } + } else { + onItemSent(); + } + break; + case 14: // SEND_THRESHOLD_SETUP_MODE + if (send4BitsToSerial(RESPONSE_THRESHOLD_SETUP, thresholdSetupMode)) { + onItemSent(); + } + break; // Below is a termination case, to notify that data for CONTROL_GET_ALL_DATA is over. // Must be the last item in the sequence! - case 13: // SEND_END_SEQUENCE + case 15: // SEND_END_SEQUENCE if (send4BitsToSerial(RESPONSE_END_SEQUENCE, 1)) { onItemSent(); isSendingData = 0; @@ -445,7 +461,7 @@ void loop() { onItemSent(); } break; - case 101: // SEND_CALIBR_TIME + case 101: // SEND_CURRENT_TIME if (sendLongToSerial(RESPONSE_TIME, millisUponRequest)) { onItemSent(); } @@ -455,22 +471,6 @@ void loop() { onItemSent(); } break; - case 103: // SEND_VOLTAGE - uint16_t voltage; - voltage = readVoltage(); - if (voltage > VOLTAGE_ZERO_THRESHOLD) { - if (sendIntToSerial(RESPONSE_VOLTAGE, voltage)) { - onItemSent(); - } - } else { - onItemSent(); - } - break; - case 104: // SEND_THRESHOLD_SETUP_MODE - if (send4BitsToSerial(RESPONSE_THRESHOLD_SETUP, thresholdSetupMode)) { - onItemSent(); - } - break; default: isSendingData = 0; DEBUG_CODE( From 1c30ae33dbd5bed4f986f27cbd18b66070a83940 Mon Sep 17 00:00:00 2001 From: voroshkov Date: Thu, 24 May 2018 22:01:40 +0300 Subject: [PATCH 8/9] use API v4; indicate threshold setup according to new algorithm; add Spanish localization --- Android/ChorusRFLaptimer/app/build.gradle | 6 +- .../chorus_laptimer/AppPreferences.java | 12 +- .../chorus_laptimer/AppState.java | 119 ++++++++++++++++-- .../chorus_laptimer/ChannelsListAdapter.java | 20 ++- .../chorus_laptimer/DataAction.java | 2 + .../chorus_laptimer/DeviceState.java | 3 + .../chorus_laptimer/MainActivity.java | 25 ++++ .../PilotsRssiListAdapter.java | 37 +++++- .../chorus_laptimer/PilotsSetupFragment.java | 1 + .../chorus_laptimer/RaceResultFragment.java | 22 ++-- .../chorus_laptimer/RaceSetupFragment.java | 15 ++- .../chorus_laptimer/TextSpeaker.java | 1 + .../chorus_laptimer/Utils.java | 48 ++++--- .../src/main/res/layout/pilot_rssi_group.xml | 10 ++ .../app/src/main/res/values-de/strings.xml | 8 +- .../app/src/main/res/values-es/strings.xml | 6 +- .../app/src/main/res/values-ru/strings.xml | 6 +- .../app/src/main/res/values/strings.xml | 8 +- Android/ChorusRFLaptimer/build.gradle | 2 +- .../gradle/wrapper/gradle-wrapper.properties | 4 +- 20 files changed, 289 insertions(+), 66 deletions(-) diff --git a/Android/ChorusRFLaptimer/app/build.gradle b/Android/ChorusRFLaptimer/app/build.gradle index 4edae1f..dd32e0e 100644 --- a/Android/ChorusRFLaptimer/app/build.gradle +++ b/Android/ChorusRFLaptimer/app/build.gradle @@ -2,13 +2,13 @@ apply plugin: 'com.android.application' android { compileSdkVersion 25 - buildToolsVersion '25.0.2' + buildToolsVersion '26.0.2' defaultConfig { applicationId "app.andrey_voroshkov.chorus_laptimer" minSdkVersion 16 targetSdkVersion 25 - versionCode 17 - versionName "0.7.5" + versionCode 18 + versionName "0.7.6" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" archivesBaseName = "ChorusRFLaptimer" } diff --git a/Android/ChorusRFLaptimer/app/src/main/java/app/andrey_voroshkov/chorus_laptimer/AppPreferences.java b/Android/ChorusRFLaptimer/app/src/main/java/app/andrey_voroshkov/chorus_laptimer/AppPreferences.java index 9563b7d..710e470 100644 --- a/Android/ChorusRFLaptimer/app/src/main/java/app/andrey_voroshkov/chorus_laptimer/AppPreferences.java +++ b/Android/ChorusRFLaptimer/app/src/main/java/app/andrey_voroshkov/chorus_laptimer/AppPreferences.java @@ -207,19 +207,19 @@ public static void applyDeviceDependentPreferences() { boolean prefSkipFirstLap = app.preferences.getBoolean(SKIP_FIRST_LAP, true); if (app.shouldSkipFirstLap != prefSkipFirstLap) { app.changeSkipFirstLap(prefSkipFirstLap); - app.sendBtCommand("R*F"); + app.sendBtCommand("R*1" + (prefSkipFirstLap ? "0" : "1")); } boolean prefEnableDeviceSounds = app.preferences.getBoolean(ENABLE_DEVICE_SOUNDS, true); if (app.isDeviceSoundEnabled != prefEnableDeviceSounds) { app.changeDeviceSoundState(prefEnableDeviceSounds); - app.sendBtCommand("R*D"); + app.sendBtCommand("R*S" + (prefEnableDeviceSounds ? "1" : "0")); } if (app.raceState != null) { int prefMLT = app.preferences.getInt(MIN_LAP_TIME, 3); app.changeRaceMinLapTime(prefMLT); - app.sendBtCommand("R*L" + String.format("%02X", prefMLT)); + app.sendBtCommand("R*M" + String.format("%02X", prefMLT)); } if (app.deviceStates != null) { @@ -233,7 +233,7 @@ public static void applyDeviceDependentPreferences() { if (i < bandsCount) { int prefBand = Integer.parseInt(bandsArray[i]); app.changeDeviceBand(i, prefBand); - app.sendBtCommand("R" + String.format("%X", i) + "N" + String.format("%X", prefBand)); + app.sendBtCommand("R" + String.format("%X", i) + "B" + String.format("%X", prefBand)); } } } @@ -248,7 +248,7 @@ public static void applyDeviceDependentPreferences() { if (i < channelsCount) { int prefChannel = Integer.parseInt(channelsArray[i]); app.changeDeviceChannel(i, prefChannel); - app.sendBtCommand("R" + String.format("%X", i) + "H" + String.format("%X", prefChannel)); + app.sendBtCommand("R" + String.format("%X", i) + "C" + String.format("%X", prefChannel)); } } } @@ -263,7 +263,7 @@ public static void applyDeviceDependentPreferences() { if (i < thresholdsCount) { int prefThreshold = Integer.parseInt(thresholdsArray[i]); app.changeDeviceThreshold(i, prefThreshold); - app.sendBtCommand("R" + String.format("%X", i) + "S" + String.format("%04X", prefThreshold)); + app.sendBtCommand("R" + String.format("%X", i) + "T" + String.format("%04X", prefThreshold)); } } } diff --git a/Android/ChorusRFLaptimer/app/src/main/java/app/andrey_voroshkov/chorus_laptimer/AppState.java b/Android/ChorusRFLaptimer/app/src/main/java/app/andrey_voroshkov/chorus_laptimer/AppState.java index 6507534..889b187 100644 --- a/Android/ChorusRFLaptimer/app/src/main/java/app/andrey_voroshkov/chorus_laptimer/AppState.java +++ b/Android/ChorusRFLaptimer/app/src/main/java/app/andrey_voroshkov/chorus_laptimer/AppState.java @@ -6,6 +6,7 @@ import android.os.Handler; import android.os.Message; import android.support.annotation.StringRes; +import android.text.TextUtils; import java.util.ArrayList; @@ -17,6 +18,8 @@ public class AppState { public static final byte DELIMITER = '\n'; + public static final int SUPPORTED_API_VERSION = 4; + public static final int MIN_RSSI = 80; public static final int MAX_RSSI = 315; public static final int RSSI_SPAN = MAX_RSSI - MIN_RSSI; @@ -24,6 +27,8 @@ public class AppState { public static final String bandNames [] = {"R", "A", "B", "E", "F", "D", "Connex1", "Connex2"}; public static final int DEFAULT_MIN_LAP_TIME = 5; public static final int DEFAULT_LAPS_TO_GO = 3; + public static final int NO_API_VERSION = -1; + public static final int NO_TIME_ADJUSTMENT = 0x7FFFFFF; //tone sounds and durations (race start, lap count, etc) public static final int TONE_PREPARE = ToneGenerator.TONE_DTMF_1; @@ -33,6 +38,7 @@ public class AppState { public static final int TONE_LAP = ToneGenerator.TONE_DTMF_S; public static final int DURATION_LAP = 400; public static final int MIN_TIME_BEFORE_RACE_TO_SPEAK = 5; //seconds, don't speak "Prepare" message if less time is set + public static final int START_BEEPS_COUNT = 4; //number of beeps in start beeps sequence. should be within 1 - 16 //voltage measuring constants public static final int BATTERY_CHECK_INTERVAL = 10000; // 10 seconds @@ -76,6 +82,8 @@ public static AppState getInstance() { public int batteryAdjustmentConst = 1; public boolean isLiPoMonitorEnabled = true; public boolean isConnected = false; + public int calibrationActualTime = 10000; + public boolean didWrongApiEventFire = false; private ArrayList deviceTransmissionStates; private ArrayList mListeners; @@ -94,7 +102,7 @@ private AppState() { public void handleMessage(Message msg) { AppState app = AppState.getInstance(); if (app.isConnected && app.isLiPoMonitorEnabled && app.raceState != null && !app.raceState.isStarted) { - AppState.getInstance().sendBtCommand("R*Y"); + AppState.getInstance().sendBtCommand("R*v"); } sendEmptyMessageDelayed(0, BATTERY_CHECK_INTERVAL); } @@ -324,6 +332,13 @@ public String getBandText(int deviceId) { } } + public int getThresholdSetupState(int deviceId) { + if (deviceStates == null) return 0; + if (deviceStates.size() <= deviceId) return 0; + int thresholdSetupState = deviceStates.get(deviceId).thresholdSetupState; + return thresholdSetupState; + } + // Channels to send to the SPI registers private static int channelTable[] = { // Channel 1 - 8 @@ -408,6 +423,7 @@ public int getEnabledPilotsCount() { //--------------------------------------------------------------------- public void sendBtCommand(String cmd) { if (conn == null) return; +// Log.i("SEND BT COMMAND", cmd); conn.send(cmd + (char)DELIMITER); } @@ -426,7 +442,7 @@ public void onDisconnected() { } public void onBeforeDisconnect() { - sendBtCommand("R*v"); // turn off RSSI monitoring before disconnect + sendBtCommand("R*I0000"); // turn off RSSI monitoring before disconnect suspendBatteryMonitoringHandlers(); } //--------------------------------------------------------------------- @@ -455,7 +471,10 @@ public void setNumberOfDevices(int n) { emitEvent(DataAction.NDevices); resetDeviceTransmissionStates(); - sendBtCommand("R*A"); + clearApiVersions(); + clearWrongApiWarningCounter(); + sendBtCommand("R*#"); + sendBtCommand("R*a"); } public void resetDeviceTransmissionStates() { @@ -463,6 +482,43 @@ public void resetDeviceTransmissionStates() { deviceTransmissionStates.set(i, false); } } + public void checkApiVersion(int deviceId, int version) { + DeviceState currentState = deviceStates.get(deviceId); + if (currentState == null) { + return; + } + currentState.apiVersion = version; + + boolean doHaveAllVersions = true; + for (DeviceState ds: deviceStates) { + int ver = ds.apiVersion; + if (ver == NO_API_VERSION) { + doHaveAllVersions = false; + break; + } + } + if (doHaveAllVersions) { + if (!getModulesWithWrongApiVersion().equals("") && !didWrongApiEventFire) { + emitEvent(DataAction.WrongApiVersion); + didWrongApiEventFire = true; + } + } + } + + public String getModulesWithWrongApiVersion() { + boolean isAnyWrong = false; + ArrayList wrongsList = new ArrayList<>(); + int count = deviceStates.size(); + for(int i = 0; i < count; i++) { + DeviceState ds = deviceStates.get(i); + if (ds.apiVersion < SUPPORTED_API_VERSION) { + isAnyWrong = true; + wrongsList.add(Integer.toString(i + 1)); + } + } + if (!isAnyWrong) return ""; + return TextUtils.join(", ", wrongsList); + } public void changeDeviceChannel(int deviceId, int channel) { DeviceState currentState = deviceStates.get(deviceId); @@ -670,6 +726,18 @@ public void addLapResult(int deviceId, int lapNumber, int lapTime) { } } } + public void clearOldCalibrationTimes() { + calibrationActualTime = 0; + for (DeviceState ds: deviceStates) { + ds.isCalibrated = false; + ds.calibrationTime = 0; + ds.deviceTime = 0; + } + } + + public void setCalibrationActualTime(int calibrationActualTime) { + this.calibrationActualTime = calibrationActualTime; + } public void changeCalibration(int deviceId, boolean isCalibrated) { if (deviceId >= numberOfDevices) { @@ -683,7 +751,7 @@ public void changeCalibration(int deviceId, boolean isCalibrated) { emitEvent(DataAction.DeviceCalibrationStatus); } - public void changeDeviceCalibrationTime(int deviceId, int calibrationTime) { + public void changeDeviceCalibrationTime(int deviceId, long deviceTime) { if (deviceId >= numberOfDevices) { return; } @@ -691,7 +759,15 @@ public void changeDeviceCalibrationTime(int deviceId, int calibrationTime) { if (currentState == null) { return; } + + if (currentState.deviceTime == 0) { + currentState.deviceTime = deviceTime; // first read? just store current time into device state + return; + } + + int calibrationTime = (int)(deviceTime - currentState.deviceTime); // find diff between this time and previous read currentState.calibrationTime = calibrationTime; + currentState.deviceTime = 0; // just drop prev read to avoid possible mistakes in future boolean doHaveAllTimes = true; for (DeviceState ds: deviceStates) { int time = ds.calibrationTime; @@ -706,16 +782,18 @@ public void changeDeviceCalibrationTime(int deviceId, int calibrationTime) { } public void calculateAndSendCalibrationValues() { - int baseTime = CALIBRATION_TIME_MS; + int baseTime = calibrationActualTime; for (int i = 0; i < numberOfDevices; i++) { int time = deviceStates.get(i).calibrationTime; + int diff = baseTime - time; - int calibrationValue = 0; + + int calibrationValue = NO_TIME_ADJUSTMENT; // predefined const if (diff != 0) { calibrationValue = baseTime/diff; } deviceStates.get(i).calibrationValue = calibrationValue; - sendBtCommand("C" + String.format("%X", i) + String.format("%08X", calibrationValue)); + sendBtCommand("R" + String.format("%X", i) + "J" + String.format("%08X", calibrationValue)); } emitEvent(DataAction.DeviceCalibrationValue); } @@ -732,6 +810,18 @@ public void changeRssiMonitorState(boolean isMonitorOn) { emitEvent(DataAction.RSSImonitorState); } + public void changeThresholdSetupState(int deviceId, int thrSetupState) { + if (deviceId >= numberOfDevices) { + return; + } + DeviceState currentState = deviceStates.get(deviceId); + if (currentState == null) { + return; + } + currentState.thresholdSetupState = thrSetupState; + emitEvent(DataAction.ThresholdSetupState); + } + //use to determine if all devices reported their state after connection public boolean isDevicesInitializationOver() { int count = deviceTransmissionStates.size(); @@ -755,10 +845,10 @@ public void receivedEndOfSequence(int deviceId) { return; } if (raceState.isStarted && isRssiMonitorOn) { - sendBtCommand("R*v"); // turn RSSI Monitoring off + sendBtCommand("R*I0000"); // turn RSSI Monitoring off } if (!raceState.isStarted && !isRssiMonitorOn) { - sendBtCommand("R*V"); // turn RSSI Monitoring on + sendBtCommand("R*I0064"); // turn RSSI Monitoring on } //also decide to apply preferences after all states are received AppPreferences.applyInAppPreferences(); @@ -829,6 +919,17 @@ public void clearVoltage() { recalculateVoltage(); } + public void clearWrongApiWarningCounter() { + didWrongApiEventFire = false; + } + + public void clearApiVersions() { + int count = deviceStates.size(); + for(int i = 0; i < count; i++) { + deviceStates.get(i).apiVersion = NO_API_VERSION; + } + } + public void changeAdjustmentConst(int adjConst) { if (!isLiPoMonitorEnabled) { return; diff --git a/Android/ChorusRFLaptimer/app/src/main/java/app/andrey_voroshkov/chorus_laptimer/ChannelsListAdapter.java b/Android/ChorusRFLaptimer/app/src/main/java/app/andrey_voroshkov/chorus_laptimer/ChannelsListAdapter.java index b67ffff..69ee6d4 100644 --- a/Android/ChorusRFLaptimer/app/src/main/java/app/andrey_voroshkov/chorus_laptimer/ChannelsListAdapter.java +++ b/Android/ChorusRFLaptimer/app/src/main/java/app/andrey_voroshkov/chorus_laptimer/ChannelsListAdapter.java @@ -93,28 +93,40 @@ public View getView(final int position, View convertView, ViewGroup parent) { viewHolder.btnDecCh.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - AppState.getInstance().sendBtCommand("R" + deviceId + "c"); + int newChannel = AppState.getInstance().deviceStates.get(position).channel - 1; + if (newChannel >= 0) { + AppState.getInstance().sendBtCommand("R" + deviceId + "C" + String.format("%X", newChannel)); + } } }); viewHolder.btnIncCh.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - AppState.getInstance().sendBtCommand("R" + deviceId + "C"); + int newChannel = AppState.getInstance().deviceStates.get(position).channel + 1; + if (newChannel <= 7) { + AppState.getInstance().sendBtCommand("R" + deviceId + "C" + String.format("%X", newChannel)); + } } }); viewHolder.btnDecBand.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - AppState.getInstance().sendBtCommand("R" + deviceId + "b"); + int newBand = AppState.getInstance().deviceStates.get(position).band - 1; + if (newBand >= 0) { + AppState.getInstance().sendBtCommand("R" + deviceId + "B" + String.format("%X", newBand)); + } } }); viewHolder.btnIncBand.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - AppState.getInstance().sendBtCommand("R" + deviceId + "B"); + int newBand = AppState.getInstance().deviceStates.get(position).band + 1; + if (newBand <= 6) { + AppState.getInstance().sendBtCommand("R" + deviceId + "B" + String.format("%X", newBand)); + } } }); diff --git a/Android/ChorusRFLaptimer/app/src/main/java/app/andrey_voroshkov/chorus_laptimer/DataAction.java b/Android/ChorusRFLaptimer/app/src/main/java/app/andrey_voroshkov/chorus_laptimer/DataAction.java index 1275fe4..cf3e4d6 100644 --- a/Android/ChorusRFLaptimer/app/src/main/java/app/andrey_voroshkov/chorus_laptimer/DataAction.java +++ b/Android/ChorusRFLaptimer/app/src/main/java/app/andrey_voroshkov/chorus_laptimer/DataAction.java @@ -30,5 +30,7 @@ public enum DataAction { BatteryVoltage, VoltageAdjustmentConst, LiPoMonitorEnable, + WrongApiVersion, + ThresholdSetupState, Disconnect } diff --git a/Android/ChorusRFLaptimer/app/src/main/java/app/andrey_voroshkov/chorus_laptimer/DeviceState.java b/Android/ChorusRFLaptimer/app/src/main/java/app/andrey_voroshkov/chorus_laptimer/DeviceState.java index b7b1eb9..596fc96 100644 --- a/Android/ChorusRFLaptimer/app/src/main/java/app/andrey_voroshkov/chorus_laptimer/DeviceState.java +++ b/Android/ChorusRFLaptimer/app/src/main/java/app/andrey_voroshkov/chorus_laptimer/DeviceState.java @@ -14,6 +14,9 @@ public class DeviceState { public int calibrationValue; public int currentRSSI; public boolean isEnabled; + public long deviceTime; + public int thresholdSetupState; + public int apiVersion; DeviceState() { isCalibrated = false; diff --git a/Android/ChorusRFLaptimer/app/src/main/java/app/andrey_voroshkov/chorus_laptimer/MainActivity.java b/Android/ChorusRFLaptimer/app/src/main/java/app/andrey_voroshkov/chorus_laptimer/MainActivity.java index 6acfd1c..62f6a78 100644 --- a/Android/ChorusRFLaptimer/app/src/main/java/app/andrey_voroshkov/chorus_laptimer/MainActivity.java +++ b/Android/ChorusRFLaptimer/app/src/main/java/app/andrey_voroshkov/chorus_laptimer/MainActivity.java @@ -2,6 +2,8 @@ import android.Manifest; import android.app.Activity; +import android.app.AlertDialog; +import android.content.DialogInterface; import android.content.Intent; import android.net.DhcpInfo; import android.net.wifi.WifiInfo; @@ -191,12 +193,35 @@ protected void onCreate(Bundle savedInstanceState) { AppState.getInstance().preferences = getPreferences(MODE_PRIVATE); AppPreferences.applyAll(); + AppState.getInstance().addListener(new IDataListener() { + @Override + public void onDataChange(DataAction dataItemName) { + switch (dataItemName) { + case WrongApiVersion: + MainActivity.this.showWrongApiDialog(); + } + } + }); //Ensure permissions permissions before any disk IO ensurePermissions(); //this will cleanup csv reports after 2 weeks (14 days) cleanUpCSVReports(); } + public void showWrongApiDialog () { + String modulesWithWrongApi = AppState.getInstance().getModulesWithWrongApiVersion(); + new AlertDialog.Builder(MainActivity.this) + .setTitle(getResources().getString(R.string.api_err_title)) + .setMessage(getResources().getString(R.string.api_err_message, modulesWithWrongApi, AppState.SUPPORTED_API_VERSION)) + .setCancelable(false) + .setPositiveButton(getResources().getString(R.string.api_err_button), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + AppState.getInstance().conn.disconnect(); + } + }).show(); + } + public void onDestroy() { super.onDestroy(); bt.stopService(); diff --git a/Android/ChorusRFLaptimer/app/src/main/java/app/andrey_voroshkov/chorus_laptimer/PilotsRssiListAdapter.java b/Android/ChorusRFLaptimer/app/src/main/java/app/andrey_voroshkov/chorus_laptimer/PilotsRssiListAdapter.java index fb58e54..a9f0a38 100644 --- a/Android/ChorusRFLaptimer/app/src/main/java/app/andrey_voroshkov/chorus_laptimer/PilotsRssiListAdapter.java +++ b/Android/ChorusRFLaptimer/app/src/main/java/app/andrey_voroshkov/chorus_laptimer/PilotsRssiListAdapter.java @@ -1,6 +1,8 @@ package app.andrey_voroshkov.chorus_laptimer; import android.content.Context; +import android.graphics.PorterDuff; +import android.support.v4.content.ContextCompat; import android.text.Editable; import android.text.TextWatcher; import android.view.LayoutInflater; @@ -37,6 +39,7 @@ static class ViewHolder { EditText edPilotName; TextView txtChannelLabel; TextView txtThresh; + ProgressBar spinThresholdSetup; Button btnDecThr; Button btnIncThr; Button btnSetThr; @@ -89,6 +92,7 @@ public View getView(final int position, View convertView, ViewGroup parent) { viewHolder.edPilotName = (EditText) convertView.findViewById(R.id.editPilotName); viewHolder.txtChannelLabel = (TextView) convertView.findViewById(R.id.txtChannelLabel); viewHolder.txtThresh = (TextView) convertView.findViewById(R.id.txtThreshold); + viewHolder.spinThresholdSetup = (ProgressBar) convertView.findViewById(R.id.spinThresholdSetup); viewHolder.btnDecThr = (Button) convertView.findViewById(R.id.btnDecThresh); viewHolder.btnIncThr = (Button) convertView.findViewById(R.id.btnIncThresh); viewHolder.btnSetThr = (Button) convertView.findViewById(R.id.btnCapture); @@ -122,6 +126,20 @@ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { String ch = AppState.getInstance().getChannelText(position); String band = AppState.getInstance().getBandText(position); Boolean isEnabled = AppState.getInstance().getIsPilotEnabled(position); + int thresholdSetupState = AppState.getInstance().getThresholdSetupState(position); + + if (thresholdSetupState > 0) { + //showThresholdSetupProgress; + viewHolder.spinThresholdSetup.setVisibility(View.VISIBLE); + viewHolder.txtThresh.setVisibility(View.GONE); + int colorId = (thresholdSetupState == 1) ? R.color.colorWarn : R.color.colorPrimary; + int color = ContextCompat.getColor(mContext, colorId); + viewHolder.spinThresholdSetup.getIndeterminateDrawable().setColorFilter(color, PorterDuff.Mode.SRC_IN); + } else { + //hideThresholdSetupProgress; + viewHolder.spinThresholdSetup.setVisibility(View.GONE); + viewHolder.txtThresh.setVisibility(View.VISIBLE); + } String mhzString = mContext.getString(R.string.mhz_string); String freq = AppState.getInstance().getFrequencyText(position) + " " + mhzString; @@ -145,7 +163,11 @@ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { viewHolder.btnDecThr.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - AppState.getInstance().sendBtCommand("R" + deviceId + "t"); + int newThresh = ds.threshold - 1; + if (newThresh < 0) { + newThresh = 0; + } + AppState.getInstance().sendBtCommand("R" + deviceId + "T" + String.format("%04X", newThresh)); } }); @@ -156,7 +178,7 @@ public boolean onLongClick(View v) { if (newThresh < 0) { newThresh = 0; } - AppState.getInstance().sendBtCommand("R" + deviceId + "S" + String.format("%04X", newThresh)); + AppState.getInstance().sendBtCommand("R" + deviceId + "T" + String.format("%04X", newThresh)); return true; } }); @@ -164,7 +186,8 @@ public boolean onLongClick(View v) { viewHolder.btnIncThr.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - AppState.getInstance().sendBtCommand("R" + deviceId + "T"); + int newThresh = ds.threshold + 1; + AppState.getInstance().sendBtCommand("R" + deviceId + "T" + String.format("%04X", newThresh)); } }); @@ -175,7 +198,7 @@ public boolean onLongClick(View v) { if (newThresh > AppState.MAX_RSSI) { newThresh = AppState.MAX_RSSI; } - AppState.getInstance().sendBtCommand("R" + deviceId + "S" + String.format("%04X", newThresh)); + AppState.getInstance().sendBtCommand("R" + deviceId + "T" + String.format("%04X", newThresh)); return true; } }); @@ -183,7 +206,11 @@ public boolean onLongClick(View v) { viewHolder.btnSetThr.setOnLongClickListener(new View.OnLongClickListener() { @Override public boolean onLongClick(View v) { - AppState.getInstance().sendBtCommand("R" + deviceId + "S"); + if (ds.threshold == 0) { + AppState.getInstance().sendBtCommand("R" + deviceId + "H1"); + } else { + AppState.getInstance().sendBtCommand("R" + deviceId + "T0000"); + } return false; } }); diff --git a/Android/ChorusRFLaptimer/app/src/main/java/app/andrey_voroshkov/chorus_laptimer/PilotsSetupFragment.java b/Android/ChorusRFLaptimer/app/src/main/java/app/andrey_voroshkov/chorus_laptimer/PilotsSetupFragment.java index 1f2803c..b047014 100644 --- a/Android/ChorusRFLaptimer/app/src/main/java/app/andrey_voroshkov/chorus_laptimer/PilotsSetupFragment.java +++ b/Android/ChorusRFLaptimer/app/src/main/java/app/andrey_voroshkov/chorus_laptimer/PilotsSetupFragment.java @@ -60,6 +60,7 @@ public void onDataChange(DataAction dataItemName) { case DeviceBand: case DeviceThreshold: case PilotEnabledDisabled: + case ThresholdSetupState: updateResults(); break; case DeviceRSSI: diff --git a/Android/ChorusRFLaptimer/app/src/main/java/app/andrey_voroshkov/chorus_laptimer/RaceResultFragment.java b/Android/ChorusRFLaptimer/app/src/main/java/app/andrey_voroshkov/chorus_laptimer/RaceResultFragment.java index 173db5c..ac3dfdd 100644 --- a/Android/ChorusRFLaptimer/app/src/main/java/app/andrey_voroshkov/chorus_laptimer/RaceResultFragment.java +++ b/Android/ChorusRFLaptimer/app/src/main/java/app/andrey_voroshkov/chorus_laptimer/RaceResultFragment.java @@ -4,6 +4,7 @@ import android.os.Bundle; import android.os.Handler; import android.os.Message; +import android.os.SystemClock; import android.support.v4.app.Fragment; import android.view.LayoutInflater; import android.view.View; @@ -42,11 +43,12 @@ public void handleMessage(Message msg) { if (counter == 0) { //last beep AppState.getInstance().playTone(AppState.TONE_GO, AppState.DURATION_GO); - AppState.getInstance().sendBtCommand("R*R"); + AppState.getInstance().sendBtCommand("R*R1"); } else { this.sendEmptyMessageDelayed(counter - 1, 1000); //first 3 beeps - if (counter < 4) { + if (counter < AppState.START_BEEPS_COUNT) { + AppState.getInstance().sendBtCommand("T"+ String.format("%01X", counter)); AppState.getInstance().playTone(AppState.TONE_PREPARE, AppState.DURATION_PREPARE); } } @@ -125,6 +127,8 @@ public void onClick(View v) { pd.setCancelable(false); pd.setProgressNumberFormat(null); pd.show(); + AppState.getInstance().clearOldCalibrationTimes(); + final long msStart = System.currentTimeMillis(); final Handler h = new Handler() { public void handleMessage(Message msg) { switch(msg.what) { @@ -135,13 +139,15 @@ public void handleMessage(Message msg) { } break; case 1: - AppState.getInstance().sendBtCommand("R*i"); + long msEnd = System.currentTimeMillis(); + AppState.getInstance().sendBtCommand("R*t"); + AppState.getInstance().setCalibrationActualTime((int)(msEnd - msStart)); pd.dismiss(); break; } } }; - AppState.getInstance().sendBtCommand("R*I"); + AppState.getInstance().sendBtCommand("R*t"); h.sendEmptyMessage(0); h.sendEmptyMessageDelayed(1, AppState.CALIBRATION_TIME_MS); } @@ -153,10 +159,11 @@ public void onClick(View v) { boolean isStarted = AppState.getInstance().raceState.isStarted; if (!isStarted && !mIsStartingRace) { //stop rssi monitoring first, then start race - AppState.getInstance().sendBtCommand("R*v"); + AppState.getInstance().sendBtCommand("R*I0000"); Button btnRace = (Button) rootView.findViewById(R.id.btnStartRace); btnRace.setText(R.string.starting_race); + AppState.getInstance().sendBtCommand("TP"); mIsStartingRace = true; int timeBeforeRace = AppState.getInstance().timeToPrepareForRace; if (timeBeforeRace >= AppState.MIN_TIME_BEFORE_RACE_TO_SPEAK) @@ -174,12 +181,13 @@ public boolean onLongClick(View v) { boolean isStarted = AppState.getInstance().raceState.isStarted; if (isStarted) { //stop race and start RSSI monitoring - AppState.getInstance().sendBtCommand("R*r"); - AppState.getInstance().sendBtCommand("R*V"); + AppState.getInstance().sendBtCommand("R*R0"); + AppState.getInstance().sendBtCommand("R*I0064"); return true; } else if (mIsStartingRace) { //TODO: move mIsStartingRace flag into appState, use updateButtons to update button captions mIsStartingRace = false; + AppState.getInstance().sendBtCommand("R*R0"); // send end race (workaround for the led gate to switch to no-race mode) mRaceStartingHandler.removeCallbacksAndMessages(null); updateButtons(rootView); return true; diff --git a/Android/ChorusRFLaptimer/app/src/main/java/app/andrey_voroshkov/chorus_laptimer/RaceSetupFragment.java b/Android/ChorusRFLaptimer/app/src/main/java/app/andrey_voroshkov/chorus_laptimer/RaceSetupFragment.java index c636fb4..7609118 100644 --- a/Android/ChorusRFLaptimer/app/src/main/java/app/andrey_voroshkov/chorus_laptimer/RaceSetupFragment.java +++ b/Android/ChorusRFLaptimer/app/src/main/java/app/andrey_voroshkov/chorus_laptimer/RaceSetupFragment.java @@ -129,14 +129,19 @@ public void onClick(View v) { btnDecMLT.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - AppState.getInstance().sendBtCommand("R*m"); + int mlt = AppState.getInstance().raceState.minLapTime; + if (mlt > 0) { + mlt--; + } + AppState.getInstance().sendBtCommand("R*M" + String.format("%02X", mlt)); } }); btnIncMLT.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - AppState.getInstance().sendBtCommand("R*M"); + int mlt = AppState.getInstance().raceState.minLapTime + 1; + AppState.getInstance().sendBtCommand("R*M" + String.format("%02X", mlt)); } }); @@ -175,7 +180,8 @@ public void onClick(View v) { chkDeviceSoundEnabled.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - AppState.getInstance().sendBtCommand("R*D"); + boolean isSoundEnabled = AppState.getInstance().isDeviceSoundEnabled; + AppState.getInstance().sendBtCommand("R*S" + (isSoundEnabled ? "0" : "1")); } }); @@ -189,7 +195,8 @@ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { chkSkipFirstLap.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - AppState.getInstance().sendBtCommand("R*F"); + boolean shouldSkip = AppState.getInstance().shouldSkipFirstLap; + AppState.getInstance().sendBtCommand("R*1" + (shouldSkip ? "1" : "0")); } }); diff --git a/Android/ChorusRFLaptimer/app/src/main/java/app/andrey_voroshkov/chorus_laptimer/TextSpeaker.java b/Android/ChorusRFLaptimer/app/src/main/java/app/andrey_voroshkov/chorus_laptimer/TextSpeaker.java index f9836d0..0a90af0 100644 --- a/Android/ChorusRFLaptimer/app/src/main/java/app/andrey_voroshkov/chorus_laptimer/TextSpeaker.java +++ b/Android/ChorusRFLaptimer/app/src/main/java/app/andrey_voroshkov/chorus_laptimer/TextSpeaker.java @@ -25,6 +25,7 @@ public class TextSpeaker implements TextToSpeech.OnInitListener { SUPPORTED_LOCALES.put("ru", new Locale("ru")); SUPPORTED_LOCALES.put("es", new Locale("es")); SUPPORTED_LOCALES.put("de", new Locale("de")); + SUPPORTED_LOCALES.put("it", new Locale("it")); } private TextToSpeech tts; diff --git a/Android/ChorusRFLaptimer/app/src/main/java/app/andrey_voroshkov/chorus_laptimer/Utils.java b/Android/ChorusRFLaptimer/app/src/main/java/app/andrey_voroshkov/chorus_laptimer/Utils.java index 955f3ce..77f5001 100644 --- a/Android/ChorusRFLaptimer/app/src/main/java/app/andrey_voroshkov/chorus_laptimer/Utils.java +++ b/Android/ChorusRFLaptimer/app/src/main/java/app/andrey_voroshkov/chorus_laptimer/Utils.java @@ -77,20 +77,20 @@ public static String btDataChunkParser(String chunk) { result.append("Threshold: ").append(threshold); AppState.getInstance().changeDeviceThreshold(moduleId, threshold); break; - case 'S': + case 'r': int rssi = Integer.parseInt(chunk.substring(3,7), 16); result.append("RSSI: ").append(rssi); AppState.getInstance().changeDeviceRSSI(moduleId, rssi); break; - case 'D': + case 'S': int soundState = Integer.parseInt(chunk.substring(3,4)); result.append("Sound: ").append(soundState != 0 ? "enabled" : "disabled"); AppState.getInstance().changeDeviceSoundState(soundState!=0); break; - case 'F': - int shouldSkipFirstLap = Integer.parseInt(chunk.substring(3,4)); - result.append("Skip First Lap: ").append(shouldSkipFirstLap != 0 ? "enabled" : "disabled"); - AppState.getInstance().changeSkipFirstLap(shouldSkipFirstLap!=0); + case '1': + int shouldWaitFirstLap = Integer.parseInt(chunk.substring(3,4)); + result.append("Wait First Min Lap: ").append(shouldWaitFirstLap != 0 ? "enabled" : "disabled"); + AppState.getInstance().changeSkipFirstLap(shouldWaitFirstLap != 1); break; case 'L': int lapNo = Integer.parseInt(chunk.substring(3,5), 16); @@ -103,35 +103,45 @@ public static String btDataChunkParser(String chunk) { result.append("Band #").append(band); AppState.getInstance().changeDeviceBand(moduleId, band); break; - case 'i': - int isCalibrated = Integer.parseInt(chunk.substring(3,4), 16); - result.append("Calibration: ").append(isCalibrated!= 0 ? "done" : "not performed"); - AppState.getInstance().changeCalibration(moduleId, (isCalibrated!=0)); + case 'J': + int adjustment = (int)Long.parseLong(chunk.substring(3,11), 16); + result.append("TimeAdjustment: ").append(adjustment!= 0 ? "done" : "not performed"); + boolean isCalibrated = adjustment != 0; + AppState.getInstance().changeCalibration(moduleId, isCalibrated); break; - case 'I': - int calibrationTime = Integer.parseInt(chunk.substring(3,11), 16); - result.append("Calibration time: ").append( calibrationTime); - AppState.getInstance().changeDeviceCalibrationTime(moduleId, calibrationTime); + case 't': + long deviceTime = Long.parseLong(chunk.substring(3,11), 16); + result.append("device time: ").append( deviceTime); + AppState.getInstance().changeDeviceCalibrationTime(moduleId, deviceTime); break; - case 'V': - int isMonitorOn = Integer.parseInt(chunk.substring(3,4), 16); + case 'I': + int isMonitorOn = Integer.parseInt(chunk.substring(3,7), 16); result.append("RSSI Monitor: ").append(isMonitorOn!= 0 ? "on" : "off"); AppState.getInstance().changeRssiMonitorState(isMonitorOn!=0); break; - case 'X': + case 'H': + int thresholdSetupState = Integer.parseInt(chunk.substring(3,4), 16); + result.append("Threshold setup state: ").append(thresholdSetupState); + AppState.getInstance().changeThresholdSetupState(moduleId, thresholdSetupState); + break; + case 'x': result.append("EndOfSequence. Device# ").append(moduleId); AppState.getInstance().receivedEndOfSequence(moduleId); break; - case 'P': + case 'y': int isDeviceConfigured = Integer.parseInt(chunk.substring(3,4), 16); result.append("Device is configured: ").append(isDeviceConfigured!= 0 ? "yes" : "no"); AppState.getInstance().changeDeviceConfigStatus(moduleId, (isDeviceConfigured!=0)); break; - case 'Y': + case 'v': int voltageReading = Integer.parseInt(chunk.substring(3,7), 16); result.append("Voltage(abstract): ").append( voltageReading); AppState.getInstance().changeVoltage(voltageReading); break; + case '#': + int apiVersion = Integer.parseInt(chunk.substring(3,7), 16); + AppState.getInstance().checkApiVersion(moduleId, apiVersion); + break; } } else if (dest == 'R') { diff --git a/Android/ChorusRFLaptimer/app/src/main/res/layout/pilot_rssi_group.xml b/Android/ChorusRFLaptimer/app/src/main/res/layout/pilot_rssi_group.xml index 476ba8d..b6373b9 100644 --- a/Android/ChorusRFLaptimer/app/src/main/res/layout/pilot_rssi_group.xml +++ b/Android/ChorusRFLaptimer/app/src/main/res/layout/pilot_rssi_group.xml @@ -97,6 +97,16 @@ android:id="@+id/btnDecThresh" android:textStyle="normal|bold" /> + + zurücksetzen setzen Zeitmesser Kalibrieren - Gerät Nr: + Gerät Nr%d %d sec. Kanal: %1$s%2$s (%3$s) kalibriere Zeitmesser.... @@ -32,6 +32,7 @@ Geräte Töne Ansage Rundenzeit Ansage Nachrichten + Ansage nur in English Kanal Nr. Frequenzband + @@ -72,7 +73,10 @@ nicht Verbunden Verbunden zu %s Verbindung getrennt - Ansage nur in English + + Kompatibilitätsfehler + Das Chorusmodul %1$s ist mit einer veralteten Firmware beschrieben.\n\nBitte spielen Sie die Firmware mit API-Version %2$d oder höher auf das Modul und versuchen Sie es erneut. + Verbindung trennen Starte Rennen (%1$d Pilot) diff --git a/Android/ChorusRFLaptimer/app/src/main/res/values-es/strings.xml b/Android/ChorusRFLaptimer/app/src/main/res/values-es/strings.xml index 9188077..461e8a8 100644 --- a/Android/ChorusRFLaptimer/app/src/main/res/values-es/strings.xml +++ b/Android/ChorusRFLaptimer/app/src/main/res/values-es/strings.xml @@ -33,6 +33,7 @@ Activar Buzzer Chorus Escuchar Tiempo de Vueltas Escuchar Mensajes + Speak in English Canal # RF Band + @@ -73,7 +74,10 @@ No conectado Conectado a %s Desconectado - Speak in English + + Compatibility Error + Chorus node(s) # %1$s are flashed with outdated firmware.\n\nPlease flash firmware with API version %2$d or higher and try again. + Disconnect Inicio de Carrera (%1$d pilot) diff --git a/Android/ChorusRFLaptimer/app/src/main/res/values-ru/strings.xml b/Android/ChorusRFLaptimer/app/src/main/res/values-ru/strings.xml index a37db47..e3e20f0 100644 --- a/Android/ChorusRFLaptimer/app/src/main/res/values-ru/strings.xml +++ b/Android/ChorusRFLaptimer/app/src/main/res/values-ru/strings.xml @@ -31,6 +31,7 @@ Звук на устройстве Говорить время кругов Говорить оповещения + Говорить на английском Канал № Диапазон + @@ -71,7 +72,10 @@ Не подключено Подключено к %s Соединение прервано - Говорить на английском + + Ошибка совместимости + Прошивка модулей № %1$s устарела.\n\nПожалуйста установите прошивку с версией API не ниже %2$d и попробуйте подключиться снова. + Отключиться Начать гонку (%1$d пилот) diff --git a/Android/ChorusRFLaptimer/app/src/main/res/values/strings.xml b/Android/ChorusRFLaptimer/app/src/main/res/values/strings.xml index 2f2c59b..335d60e 100644 --- a/Android/ChorusRFLaptimer/app/src/main/res/values/strings.xml +++ b/Android/ChorusRFLaptimer/app/src/main/res/values/strings.xml @@ -31,6 +31,7 @@ Enable Device Sounds Speak Lap Times Speak Messages + Speak in English Channel # RF Band + @@ -56,7 +57,7 @@ Lap %1$d. %1$s finished. %1$s already finished. - s. + seconds Report saved as: %s @@ -71,7 +72,10 @@ Not connected Connected to %s Disconnected - Speak in English + + Compatibility Error + Chorus node(s) # %1$s are flashed with outdated firmware.\n\nPlease flash firmware with API version %2$d or higher and try again. + Disconnect Start Race (%1$d pilot) diff --git a/Android/ChorusRFLaptimer/build.gradle b/Android/ChorusRFLaptimer/build.gradle index 6514a0b..7467f53 100644 --- a/Android/ChorusRFLaptimer/build.gradle +++ b/Android/ChorusRFLaptimer/build.gradle @@ -5,7 +5,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.3.3' + classpath 'com.android.tools.build:gradle:3.0.1' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/Android/ChorusRFLaptimer/gradle/wrapper/gradle-wrapper.properties b/Android/ChorusRFLaptimer/gradle/wrapper/gradle-wrapper.properties index 8a0d3c9..e8cf5e7 100644 --- a/Android/ChorusRFLaptimer/gradle/wrapper/gradle-wrapper.properties +++ b/Android/ChorusRFLaptimer/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Wed Mar 15 21:18:32 MSK 2017 +#Tue Apr 03 17:35:17 MSK 2018 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip From 5b0f7c195c35a5348b64cbb73f1ce67b5a48ae2a Mon Sep 17 00:00:00 2001 From: voroshkov Date: Thu, 24 May 2018 22:04:09 +0300 Subject: [PATCH 9/9] add Spanish localization --- .../app/src/main/res/values-it/strings.xml | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 Android/ChorusRFLaptimer/app/src/main/res/values-it/strings.xml diff --git a/Android/ChorusRFLaptimer/app/src/main/res/values-it/strings.xml b/Android/ChorusRFLaptimer/app/src/main/res/values-it/strings.xml new file mode 100644 index 0000000..2c83500 --- /dev/null +++ b/Android/ChorusRFLaptimer/app/src/main/res/values-it/strings.xml @@ -0,0 +1,79 @@ + + Chorus RF Laptimer + + + Connessione via Bluetooth + Connessione via WiFi + Disconnetti + + + Gara + Piloti + Freq + Impostazioni + + + Nome Pilota + Soglia RSSI + Pulisci + Imposta + Calibrazione Timers + Dispositivo #%d + %d sec. + Canale: %1$s%2$s (%3$s) + Calibro i timers... + Lipo Monitor + Correggi voltaggio + Numero di Giri + Tempo Minimo per Giro + Salta Il Primo Giro + Tempo Prima Della Partenza + Abilita Suono Buzzer + Annuncia tempi sul Giro + Abilita Messaggi Vocali + Annunci in Inglese + Canale # + Banda RF + + + - + MHz + + + Partenza... + Ferma Gara(Premi a lungo) + Inizia Gara + Prepara i piloti per iniziare la Gara + Imposta tutte le soglie prima della Partenza + Miglior giro: + Miglior posizione del giro: %s + Giro # %1$d: %2$s + Ultimo giro: + Tempo totale: + Posizione: + TERMINATA + %1$s (%2$s%3$s) Giri: %4$s %5$s + Gara terminata + Partenza tra %d secondi + Giro %1$d. + %1$s terminato. + %1$s già terminato. + secondi + + + Report salvato come: %s + Errore creando il report. Controlla ci sia spazio sufficiente nel dispositivo. + + + Livello batteria critico + Livello batteria basso + + + Connesso + Non connesso + Connessione a %s + Disconnesso + + Compatibility Error + Chorus node(s) # %1$s are flashed with outdated firmware.\n\nPlease flash firmware with API version %2$d or higher and try again. + Disconnect + \ No newline at end of file