From 61ab69002b332dba2ff94ab36376df7ca40bf5ff Mon Sep 17 00:00:00 2001 From: Kevin Date: Thu, 20 Jun 2019 12:59:17 +0200 Subject: [PATCH 1/5] Send a message when a module checkbox gets clicked --- .../chorus_laptimer/PilotsRssiListAdapter.java | 7 +++++++ .../java/app/andrey_voroshkov/chorus_laptimer/Utils.java | 4 ++++ Arduino/chorus_rf_laptimer/chorus_rf_laptimer.ino | 2 ++ 3 files changed, 13 insertions(+) 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 c8bfdc0..67dbc55 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 @@ -123,6 +123,13 @@ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { } }); + viewHolder.isPilotEnabled.setOnClickListener(new CompoundButton.OnClickListener() { + @Override + public void onClick(View v) { + AppState.getInstance().sendBtCommand("R" + deviceId + "A" + (((CompoundButton)v).isChecked() ? 1 : 0)); + } + }); + Boolean isEnabled = AppState.getInstance().getIsPilotEnabled(position); viewHolder.isPilotEnabled.setChecked(isEnabled); 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 55f6170..0c3076d 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 @@ -160,6 +160,10 @@ public static String btDataChunkParser(String chunk) { int apiVersion = Integer.parseInt(chunk.substring(3,7), 16); AppState.getInstance().checkApiVersion(moduleId, apiVersion); break; + case 'A': + int isModuleActive = Integer.parseInt(chunk.substring(3,4), 16); + AppState.getInstance().changeDeviceEnabled(moduleId, (isModuleActive != 0)); + break; } } else if (dest == 'R') { diff --git a/Arduino/chorus_rf_laptimer/chorus_rf_laptimer.ino b/Arduino/chorus_rf_laptimer/chorus_rf_laptimer.ino index ed6ea1e..d451f7a 100644 --- a/Arduino/chorus_rf_laptimer/chorus_rf_laptimer.ino +++ b/Arduino/chorus_rf_laptimer/chorus_rf_laptimer.ino @@ -108,6 +108,7 @@ const uint16_t musicNotes[] = { #define CONTROL_SOUND 'S' #define CONTROL_THRESHOLD 'T' #define CONTROL_EXPERIMENTAL_MODE 'E' +#define CONTROL_MODULE_ACTIVE 'A' // get only: #define CONTROL_GET_API_VERSION '#' #define CONTROL_GET_ALL_DATA 'a' @@ -131,6 +132,7 @@ const uint16_t musicNotes[] = { #define RESPONSE_SOUND 'S' #define RESPONSE_THRESHOLD 'T' #define RESPONSE_EXPERIMENTAL_MODE 'E' +#define RESPONSE_MODULE_ACTIVE 'A' #define RESPONSE_API_VERSION '#' #define RESPONSE_RSSI 'r' From d1916218c09040fa247b9f7fc7b8f5cd2d30484a Mon Sep 17 00:00:00 2001 From: Kevin Date: Wed, 26 Jun 2019 16:02:39 +0200 Subject: [PATCH 2/5] Implement Arduino power down code. --- .../chorus_rf_laptimer/chorus_rf_laptimer.ino | 27 +++++++- Arduino/chorus_rf_laptimer/rx5808spi.h | 61 +++++++++++++++++++ 2 files changed, 86 insertions(+), 2 deletions(-) diff --git a/Arduino/chorus_rf_laptimer/chorus_rf_laptimer.ino b/Arduino/chorus_rf_laptimer/chorus_rf_laptimer.ino index d451f7a..c4e092a 100644 --- a/Arduino/chorus_rf_laptimer/chorus_rf_laptimer.ino +++ b/Arduino/chorus_rf_laptimer/chorus_rf_laptimer.ino @@ -160,7 +160,8 @@ const uint16_t musicNotes[] = { #define SEND_VOLTAGE 13 #define SEND_THRESHOLD_SETUP_MODE 14 #define SEND_EXPERIMENTAL_MODE 15 -#define SEND_END_SEQUENCE 16 +#define SEND_MODULE_ACTIVE 16 +#define SEND_END_SEQUENCE 17 // following items don't participate in "send all items" response #define SEND_LAST_LAPTIMES 100 #define SEND_TIME 101 @@ -282,6 +283,7 @@ uint8_t thresholdSetupMode = 0; uint8_t experimentalMode = 0; uint16_t frequency = 0; uint32_t millisUponRequest = 0; +uint8_t isModuleActive = 1; // 0 means this module is inactive and the VRX is disabled //----- read/write bufs --------------------------- #define READ_BUFFER_SIZE 30 @@ -455,9 +457,13 @@ void loop() { if (send4BitsToSerial(RESPONSE_EXPERIMENTAL_MODE, experimentalMode)) { onItemSent(); } + case 16: // SEND_MODULE_ACTIVE + if (send4BitsToSerial(RESPONSE_MODULE_ACTIVE, isModuleActive)) { + onItemSent(); + } // 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 16: // SEND_END_SEQUENCE + case 17: // SEND_END_SEQUENCE if (send4BitsToSerial(RESPONSE_END_SEQUENCE, 1)) { onItemSent(); isSendingData = 0; @@ -689,6 +695,12 @@ void handleSerialControlInput(uint8_t *controlData, uint8_t length) { addToSendQueue(SEND_EXPERIMENTAL_MODE); isConfigured = 1; break; + case CONTROL_MODULE_ACTIVE: + valueToSet = TO_BYTE(controlData[1]); + setModuleActive(valueToSet); + addToSendQueue(SEND_MODULE_ACTIVE); + isConfigured = 1; + break; } } else { // get value and other instructions switch (controlByte) { @@ -881,6 +893,17 @@ void setExperimentalMode(uint8_t mode) { #endif } // ---------------------------------------------------------------------------- +void setModuleActive(uint8_t active) { + isModuleActive = active; + if(active) { + resetModule(); + // We need to set the freqency again on power up + setModuleFrequency(frequency); + } else { + powerDownModule(); + } +} + 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 diff --git a/Arduino/chorus_rf_laptimer/rx5808spi.h b/Arduino/chorus_rf_laptimer/rx5808spi.h index aec4fcb..ffbf873 100644 --- a/Arduino/chorus_rf_laptimer/rx5808spi.h +++ b/Arduino/chorus_rf_laptimer/rx5808spi.h @@ -75,6 +75,67 @@ void SERIAL_ENABLE_HIGH() { delayMicroseconds(1); } +void powerDownModule() { + cli(); + SERIAL_ENABLE_HIGH(); + SERIAL_ENABLE_LOW(); + + // send 0x0A + SERIAL_SENDBIT0(); + SERIAL_SENDBIT1(); + SERIAL_SENDBIT0(); + SERIAL_SENDBIT1(); + + // write + SERIAL_SENDBIT1(); + + // set all bits to one -> disable all modules + for (uint8_t i = 20; i > 0; i--) { + SERIAL_SENDBIT1(); + } + // Finished clocking data in + SERIAL_ENABLE_HIGH(); + delayMicroseconds(1); + + digitalLow(slaveSelectPin); + digitalLow(spiClockPin); + digitalLow(spiDataPin); + sei(); + + delay(MIN_TUNE_TIME); +} + +void resetModule() { + cli(); + SERIAL_ENABLE_HIGH(); + SERIAL_ENABLE_LOW(); + + // State register + SERIAL_SENDBIT0(); + SERIAL_SENDBIT0(); + SERIAL_SENDBIT0(); + SERIAL_SENDBIT0(); + + // write + SERIAL_SENDBIT1(); + + // set all bits to zero -> reset + for (int i = 20; i > 0; i--) { + SERIAL_SENDBIT0(); + } + + // Finished clocking data in + SERIAL_ENABLE_HIGH(); + delayMicroseconds(1); + + digitalLow(slaveSelectPin); + digitalLow(spiClockPin); + digitalLow(spiDataPin); + sei(); + + delay(MIN_TUNE_TIME); +} + uint16_t setModuleFrequency(uint16_t frequency) { uint8_t i; uint16_t channelData; From 22bf16e14a454877364f7beb26a9f4aba3152acb Mon Sep 17 00:00:00 2001 From: Kevin Date: Thu, 4 Jul 2019 09:36:49 +0200 Subject: [PATCH 3/5] Fix wrong register when powering down features --- Arduino/chorus_rf_laptimer/rx5808spi.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Arduino/chorus_rf_laptimer/rx5808spi.h b/Arduino/chorus_rf_laptimer/rx5808spi.h index f570904..7013540 100644 --- a/Arduino/chorus_rf_laptimer/rx5808spi.h +++ b/Arduino/chorus_rf_laptimer/rx5808spi.h @@ -77,8 +77,9 @@ void PowerDownFeatures(uint32_t features) digitalLow(slaveSelectPin); delayMicroseconds(1); + // send 0x0A SERIAL_SENDBIT0(); - SERIAL_SENDBIT0(); + SERIAL_SENDBIT1(); SERIAL_SENDBIT0(); SERIAL_SENDBIT1(); From 75e6ff743d1e5cafba15163c6f2cd902937659b4 Mon Sep 17 00:00:00 2001 From: Kevin Date: Thu, 4 Jul 2019 09:37:48 +0200 Subject: [PATCH 4/5] Fix resetModule and powerDownModule to compile with the newest changes --- Arduino/chorus_rf_laptimer/rx5808spi.h | 53 ++++++-------------------- 1 file changed, 12 insertions(+), 41 deletions(-) diff --git a/Arduino/chorus_rf_laptimer/rx5808spi.h b/Arduino/chorus_rf_laptimer/rx5808spi.h index 7013540..dd69611 100644 --- a/Arduino/chorus_rf_laptimer/rx5808spi.h +++ b/Arduino/chorus_rf_laptimer/rx5808spi.h @@ -115,45 +115,21 @@ void setupSPIpins() { } void powerDownModule() { - cli(); - SERIAL_ENABLE_HIGH(); - SERIAL_ENABLE_LOW(); - - // send 0x0A - SERIAL_SENDBIT0(); - SERIAL_SENDBIT1(); - SERIAL_SENDBIT0(); - SERIAL_SENDBIT1(); - - // write - SERIAL_SENDBIT1(); - - // set all bits to one -> disable all modules - for (uint8_t i = 20; i > 0; i--) { - SERIAL_SENDBIT1(); - } - // Finished clocking data in - SERIAL_ENABLE_HIGH(); - delayMicroseconds(1); - - digitalLow(slaveSelectPin); - digitalLow(spiClockPin); - digitalLow(spiDataPin); - sei(); - - delay(MIN_TUNE_TIME); + // Power down all features + PowerDownFeatures(PD_PLL1D8 | PD_DIV80 | PD_MIXER | PD_IFABF | PD_REG1D8 | PD_6M5 | PD_AU6M5 | PD_6M | PD_AU6M | PD_SYN | PD_5GVCO | PD_DIV4 | PD_DIV4 | PD_BC | PD_REGIF | PD_REGBS | PD_RSSI_SQUELCH | PD_IFAF | PD_IF_DEMOD | PD_VAMP | PD_VCLAMP); } +// Reset needs to be used to wake the module up if it is completely powered down void resetModule() { - cli(); - SERIAL_ENABLE_HIGH(); - SERIAL_ENABLE_LOW(); + digitalLow(spiClockPin); + digitalLow(slaveSelectPin); + delayMicroseconds(1); - // State register - SERIAL_SENDBIT0(); - SERIAL_SENDBIT0(); - SERIAL_SENDBIT0(); - SERIAL_SENDBIT0(); + // State register 0x0F + SERIAL_SENDBIT1(); + SERIAL_SENDBIT1(); + SERIAL_SENDBIT1(); + SERIAL_SENDBIT1(); // write SERIAL_SENDBIT1(); @@ -164,14 +140,9 @@ void resetModule() { } // Finished clocking data in - SERIAL_ENABLE_HIGH(); + digitalHigh(slaveSelectPin); delayMicroseconds(1); - digitalLow(slaveSelectPin); - digitalLow(spiClockPin); - digitalLow(spiDataPin); - sei(); - delay(MIN_TUNE_TIME); } From 1bce858bee2743183e2e35e8dd55328d5ec07e4f Mon Sep 17 00:00:00 2001 From: voroshkov Date: Thu, 4 Jul 2019 22:26:04 +0300 Subject: [PATCH 5/5] update support for power-down mode in android app; update API version and docs --- Android/ChorusRFLaptimer/app/build.gradle | 4 +-- .../chorus_laptimer/AppPreferences.java | 26 ++++++++++-------- .../chorus_rf_laptimer/chorus_rf_laptimer.ino | 2 +- docs/ChorusAPI.xlsx | Bin 14136 -> 14265 bytes 4 files changed, 17 insertions(+), 15 deletions(-) diff --git a/Android/ChorusRFLaptimer/app/build.gradle b/Android/ChorusRFLaptimer/app/build.gradle index 1330a5e..d4f868d 100644 --- a/Android/ChorusRFLaptimer/app/build.gradle +++ b/Android/ChorusRFLaptimer/app/build.gradle @@ -7,8 +7,8 @@ android { applicationId "app.andrey_voroshkov.chorus_laptimer" minSdkVersion 16 targetSdkVersion 26 - versionCode 24 - versionName "0.7.12" + versionCode 25 + versionName "0.7.14" 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 5ae9993..0ce2507 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 @@ -195,18 +195,7 @@ public static void applyInAppPreferences() { } } - if (app.deviceStates != null) { - String statuses = app.preferences.getString(DEVICE_ENABLED, ""); - if (!statuses.equals("")) { - String[] statusesArray = TextUtils.split(statuses, STRING_ITEMS_DELIMITER); - int statusesCount = statusesArray.length; - for(int i = 0; i < app.deviceStates.size(); i++) { - if (i < statusesCount) { - app.changeDeviceEnabled(i, Boolean.parseBoolean(statusesArray[i])); - } - } - } - } + } public static void applyDeviceDependentPreferences() { @@ -282,6 +271,19 @@ public static void applyDeviceDependentPreferences() { } } + String enabledStatuses = app.preferences.getString(DEVICE_ENABLED, ""); + if (!enabledStatuses.equals("")) { + String[] enabledStatusesArray = TextUtils.split(enabledStatuses, STRING_ITEMS_DELIMITER); + int statusesCount = enabledStatusesArray.length; + for(int i = 0; i < app.deviceStates.size(); i++) { + if (i < statusesCount) { + boolean prefEnabledStatus = Boolean.parseBoolean(enabledStatusesArray[i]); + app.changeDeviceEnabled(i, prefEnabledStatus); + app.sendBtCommand("R" + String.format("%X", i) + "A" + (prefEnabledStatus ? "1" : "0")); + } + } + } + } } diff --git a/Arduino/chorus_rf_laptimer/chorus_rf_laptimer.ino b/Arduino/chorus_rf_laptimer/chorus_rf_laptimer.ino index c4e092a..34b778c 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 5 // version number to be increased with each API change (int16) +#define API_VERSION 6 // version number to be increased with each API change (int16) #define EXPERIMENTAL_FEATURES_AVAILABLE // comment out if current code version doesn't contain any experimental features diff --git a/docs/ChorusAPI.xlsx b/docs/ChorusAPI.xlsx index dc248e2dc2486326f04f47a9cee9ba616df86ad2..be1b3d451464f9b28b21fb6784c4316a504dfdb4 100644 GIT binary patch delta 7113 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