diff --git a/README.md b/README.md index e3e5125..5975771 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ Ridgebotics 2025 scouting app in Android #### Functionality: - Add more types of data fields. - Test the scouting app +- Write docs ## In Progress: #### Scouting: @@ -19,7 +20,6 @@ Ridgebotics 2025 scouting app in Android #### Functionality: - Make server software to allow for easy sync over wifi - FTP - Deploy to F-Droid -- ## Done: #### Scouting: diff --git a/app/src/main/java/com/ridgebotics/ridgescout/MainActivity.java b/app/src/main/java/com/ridgebotics/ridgescout/MainActivity.java index 8288179..5ccfb69 100644 --- a/app/src/main/java/com/ridgebotics/ridgescout/MainActivity.java +++ b/app/src/main/java/com/ridgebotics/ridgescout/MainActivity.java @@ -5,6 +5,7 @@ import android.content.Intent; import android.os.Bundle; import android.view.MenuItem; +import com.ridgebotics.ridgescout.databinding.ActivityMainBinding; import com.ridgebotics.ridgescout.scoutingData.fields; import com.ridgebotics.ridgescout.utility.SentimentAnalysis; import com.ridgebotics.ridgescout.utility.AlertManager; @@ -19,7 +20,6 @@ import androidx.navigation.Navigation; import androidx.navigation.ui.AppBarConfiguration; import androidx.navigation.ui.NavigationUI; -import com.ridgebotics.ridgescout.databinding.ActivityMainBinding; import com.ridgebotics.ridgescout.utility.settingsManager; import com.google.android.material.navigation.NavigationBarView; @@ -43,10 +43,6 @@ public class MainActivity extends AppCompatActivity { settingsManager.prefs = this.getSharedPreferences( "com.ridgebotics.ridgescout", Context.MODE_PRIVATE); -// latestSettings.test(); - -// latestSettings.update(); - if(!fileEditor.fileExist(fields.matchFieldsFilename)){ fields.save(fields.matchFieldsFilename, fields.default_match_fields); } @@ -73,30 +69,6 @@ public class MainActivity extends AppCompatActivity { navView = findViewById(R.id.nav_view); -// appBarConfiguration = new AppBarConfiguration.Builder( -// R.id.navigation_scouting, -// R.id.navigation_match_scouting, -// R.id.navigation_team_selector, -// R.id.navigation_pit_scouting, -// -// R.id.navigation_data, -// R.id.navigation_data_status, -// R.id.navigation_data_teams, -// R.id.navigation_data_compile, -// R.id.navigation_data_fields_chooser, -// R.id.navigation_data_fields, -// -// R.id.navigation_transfer, -// R.id.navigation_file_selector, -// R.id.navigation_transfer_selector, -// R.id.navigation_code_generator, -// R.id.navigation_code_scanner, -// R.id.navigation_bluetooth_sender, -// R.id.navigation_bluetooth_receiver, -// R.id.navigation_tba, -// -// R.id.navigation_settings) -// .build(); appBarConfiguration = new AppBarConfiguration.Builder( R.id.navigation_scouting, diff --git a/app/src/main/java/com/ridgebotics/ridgescout/types/frcEvent.java b/app/src/main/java/com/ridgebotics/ridgescout/types/frcEvent.java index 5c7c8ac..28c890a 100644 --- a/app/src/main/java/com/ridgebotics/ridgescout/types/frcEvent.java +++ b/app/src/main/java/com/ridgebotics/ridgescout/types/frcEvent.java @@ -2,6 +2,9 @@ package com.ridgebotics.ridgescout.types; import static com.ridgebotics.ridgescout.utility.DataManager.event; +import android.widget.TableRow; +import android.widget.TextView; + import androidx.annotation.NonNull; import com.ridgebotics.ridgescout.utility.AlertManager; import com.ridgebotics.ridgescout.utility.BuiltByteParser; @@ -113,4 +116,29 @@ public class frcEvent { else return maxMatch; } + +// public + + // Returns the soonest match that there will be all the possible upcoming data on other teams + public void getReportMatches(int ourTeamNum){ + frcMatch[] teamMatches = event.getTeamMatches(ourTeamNum); + + for(int i = 0; i < teamMatches.length; i++){ + int maxMatch = -1; + for(int a = 0; a < 6; a++){ + int teamNum; + if(a < 3) + teamNum = teamMatches[i].redAlliance[a]; + else + teamNum = teamMatches[i].blueAlliance[a-3]; + + if(teamNum == ourTeamNum) + continue; + + int matchNum = event.getMostRecentTeamMatch(teamNum, teamMatches[i].matchIndex); + if(maxMatch < matchNum) + maxMatch = matchNum; + } + } + } } diff --git a/app/src/main/java/com/ridgebotics/ridgescout/types/frcMatch.java b/app/src/main/java/com/ridgebotics/ridgescout/types/frcMatch.java index 6726e31..56af0ee 100644 --- a/app/src/main/java/com/ridgebotics/ridgescout/types/frcMatch.java +++ b/app/src/main/java/com/ridgebotics/ridgescout/types/frcMatch.java @@ -60,4 +60,14 @@ public class frcMatch { return "frcMatch Num: " + matchIndex + ", Blue: " + Arrays.toString(blueAlliance) + ", Red: " + Arrays.toString(redAlliance); } + public String getTeamAlliance(int teamNum){ + if(redAlliance[0] == teamNum) return "red-1"; + if(redAlliance[1] == teamNum) return "red-2"; + if(redAlliance[2] == teamNum) return "red-3"; + if(blueAlliance[0] == teamNum) return "blue-1"; + if(blueAlliance[1] == teamNum) return "blue-2"; + if(blueAlliance[2] == teamNum) return "blue-3"; + return ""; + } + } \ No newline at end of file diff --git a/app/src/main/java/com/ridgebotics/ridgescout/ui/data/DataFragment.java b/app/src/main/java/com/ridgebotics/ridgescout/ui/data/DataFragment.java index 301df77..84805ba 100644 --- a/app/src/main/java/com/ridgebotics/ridgescout/ui/data/DataFragment.java +++ b/app/src/main/java/com/ridgebotics/ridgescout/ui/data/DataFragment.java @@ -71,7 +71,7 @@ public class DataFragment extends Fragment { }); binding.reportButton.setOnClickListener(v -> { - findNavController(this).navigate(R.id.action_navigation_data_to_navigation_data_report); + findNavController(this).navigate(R.id.action_navigation_data_to_navigation_data_report_selector); }); return root; diff --git a/app/src/main/java/com/ridgebotics/ridgescout/ui/data/ReportFragment.java b/app/src/main/java/com/ridgebotics/ridgescout/ui/data/ReportFragment.java index 70bea37..a55022f 100644 --- a/app/src/main/java/com/ridgebotics/ridgescout/ui/data/ReportFragment.java +++ b/app/src/main/java/com/ridgebotics/ridgescout/ui/data/ReportFragment.java @@ -1,9 +1,6 @@ package com.ridgebotics.ridgescout.ui.data; -import static com.ridgebotics.ridgescout.utility.DataManager.evcode; import static com.ridgebotics.ridgescout.utility.DataManager.event; -import static com.ridgebotics.ridgescout.utility.DataManager.match_latest_values; -import static com.ridgebotics.ridgescout.utility.DataManager.pit_latest_values; import android.os.Bundle; import android.view.LayoutInflater; @@ -17,110 +14,57 @@ import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; import com.ridgebotics.ridgescout.databinding.FragmentDataReportBinding; -import com.ridgebotics.ridgescout.scoutingData.ScoutingDataWriter; -import com.ridgebotics.ridgescout.types.data.dataType; import com.ridgebotics.ridgescout.types.frcMatch; -import com.ridgebotics.ridgescout.types.input.dropdownType; -import com.ridgebotics.ridgescout.types.input.inputType; -import com.ridgebotics.ridgescout.types.input.sliderType; import com.ridgebotics.ridgescout.utility.DataManager; -import com.ridgebotics.ridgescout.utility.fileEditor; import com.ridgebotics.ridgescout.utility.ollama.OllamaClient; import com.ridgebotics.ridgescout.utility.ollama.PromptCreator; import com.ridgebotics.ridgescout.utility.settingsManager; -import java.util.ArrayList; -import java.util.List; -import java.util.stream.IntStream; - public class ReportFragment extends Fragment { FragmentDataReportBinding binding; + private static frcMatch match; + public static void setMatch(frcMatch m){ + match = m; + } + + private final int ourTeamNum = settingsManager.getTeamNum(); + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { binding = FragmentDataReportBinding.inflate(inflater, container, false); + binding.teamNumber.setText(String.valueOf(ourTeamNum)); + + binding.AyEyeBox.setVisibility(View.GONE); + DataManager.reload_event(); DataManager.reload_pit_fields(); DataManager.reload_match_fields(); - getReportMatches(); - AIDataOverview(); + binding.AyEyeButton.setText("Create Prompt"); + binding.AyEyeButton.setOnClickListener(a ->{ + getPrompt(); + binding.AyEyeButton.setText("Generate Overview"); + binding.AyEyeButton.setOnClickListener(b ->{ + AIDataOverview(); + binding.AyEyeButton.setVisibility(View.GONE); + }); + }); return binding.getRoot(); } -// -// public frcMatch[] getTeamMatches(int teamNum){ -// List teamMatches = new ArrayList<>(); -// for(int i = 0; i < event.matches.size(); i++){ -// frcMatch match = event.matches.get(i); -// boolean isTeamMatch = false; -// isTeamMatch = IntStream.of(match.redAlliance).anyMatch(x -> x == teamNum); -// isTeamMatch = isTeamMatch || IntStream.of(match.blueAlliance).anyMatch(x -> x == teamNum); -// if(isTeamMatch) -// teamMatches.add(match); -// } -// return teamMatches.toArray(new frcMatch[0]); -// } - - public void getReportMatches(){ -// String out = ""; - int ourTeamNum = settingsManager.getTeamNum(); - frcMatch[] teamMatches = event.getTeamMatches(ourTeamNum); - - TableRow tr = new TableRow(getContext()); - - TextView tv = new TextView(getContext()); - tv.setText("Team match"); - tr.addView(tv); - - tv = new TextView(getContext()); - tv.setText("Most informed match"); - tr.addView(tv); - - binding.teamMatchesTable.addView(tr); - binding.teamMatchesTable.setStretchAllColumns(true); - - for(int i = 0; i < teamMatches.length; i++){ - tr = new TableRow(getContext()); - - tv = new TextView(getContext()); - tv.setText(String.valueOf(teamMatches[i].matchIndex)); - tr.addView(tv); - - int maxMatch = -1; - for(int a = 0; a < 6; a++){ - int teamNum = 0; - if(a < 3) - teamNum = teamMatches[i].redAlliance[a]; - else - teamNum = teamMatches[i].blueAlliance[a-3]; - - if(teamNum == ourTeamNum) - continue; - - int matchNum = event.getMostRecentTeamMatch(teamNum, teamMatches[i].matchIndex); - if(maxMatch < matchNum) - maxMatch = matchNum; - } - - - tv = new TextView(getContext()); - tv.setText(String.valueOf(maxMatch)); - tr.addView(tv); - - binding.teamMatchesTable.addView(tr); - } -// AlertManager.error(out); + private void getPrompt(){ + binding.AyEyeBox.setVisibility(View.VISIBLE); + String prompt = PromptCreator.genMatchPrompt(0); + binding.AyEyeBox.setText(prompt); } private void AIDataOverview(){ - String prompt = PromptCreator.genMatchPrompt(0); - -// System.out.println(prompt); - + String prompt = binding.AyEyeBox.getText().toString(); + binding.AyEyeBox.setText(""); OllamaClient.run(prompt, new OllamaClient.ollamaListener() { @Override public void onResponse(String response) { diff --git a/app/src/main/java/com/ridgebotics/ridgescout/ui/data/ReportSelectorFragment.java b/app/src/main/java/com/ridgebotics/ridgescout/ui/data/ReportSelectorFragment.java new file mode 100644 index 0000000..2a95899 --- /dev/null +++ b/app/src/main/java/com/ridgebotics/ridgescout/ui/data/ReportSelectorFragment.java @@ -0,0 +1,82 @@ +package com.ridgebotics.ridgescout.ui.data; + +import static androidx.navigation.fragment.FragmentKt.findNavController; +import static com.ridgebotics.ridgescout.utility.DataManager.evcode; +import static com.ridgebotics.ridgescout.utility.DataManager.event; + +import android.annotation.SuppressLint; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.TableLayout; +import android.widget.TableRow; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; + +import com.ridgebotics.ridgescout.R; +import com.ridgebotics.ridgescout.databinding.FragmentDataReportSelectorBinding; +import com.ridgebotics.ridgescout.types.frcMatch; +import com.ridgebotics.ridgescout.utility.AlertManager; +import com.ridgebotics.ridgescout.utility.DataManager; +import com.ridgebotics.ridgescout.utility.settingsManager; + +public class ReportSelectorFragment extends Fragment { + FragmentDataReportSelectorBinding binding; + + private final int teamNum = settingsManager.getTeamNum(); + + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + binding = FragmentDataReportSelectorBinding.inflate(inflater, container, false); + binding.matchTable.setStretchAllColumns(true); + + DataManager.reload_event(); + frcMatch[] teamMatches = event.getTeamMatches(teamNum); + + if(teamMatches.length == 0){ + AlertManager.error("Team number " + teamNum + " could not be found in event " + evcode); + findNavController(this).navigate(R.id.action_navigation_data_report_selector_to_navigation_data); + } + + for(int i = 0; i < teamMatches.length; i++){ + addTableRow(teamMatches[i]); + } + + + return binding.getRoot(); + } + + @SuppressLint("SetTextI18n") + private void addTableRow(frcMatch match){ + TableRow tr = new TableRow(getContext()); + TableLayout.LayoutParams rowParams = new TableLayout.LayoutParams( + FrameLayout.LayoutParams.WRAP_CONTENT, + FrameLayout.LayoutParams.WRAP_CONTENT + ); + rowParams.setMargins(20,20,20,20); + tr.setLayoutParams(rowParams); + tr.setPadding(20,20,20,20); + tr.setBackgroundColor(0x5000ff00); + binding.matchTable.addView(tr); + + TextView tv = new TextView(getContext()); + tv.setText("Match " + match.matchIndex); + tv.setTextAlignment(View.TEXT_ALIGNMENT_CENTER); + tr.addView(tv); + + tv = new TextView(getContext()); + tv.setText("Pos " + match.getTeamAlliance(teamNum)); + tv.setTextAlignment(View.TEXT_ALIGNMENT_CENTER); + tr.addView(tv); + + tr.setOnClickListener(v -> { + ReportFragment.setMatch(match); + findNavController(this).navigate(R.id.action_navigation_data_report_selector_to_navigation_data_report); + }); + } +} diff --git a/app/src/main/java/com/ridgebotics/ridgescout/utility/ollama/PromptCreator.java b/app/src/main/java/com/ridgebotics/ridgescout/utility/ollama/PromptCreator.java index f1eb0de..420f096 100644 --- a/app/src/main/java/com/ridgebotics/ridgescout/utility/ollama/PromptCreator.java +++ b/app/src/main/java/com/ridgebotics/ridgescout/utility/ollama/PromptCreator.java @@ -7,6 +7,8 @@ import static com.ridgebotics.ridgescout.utility.DataManager.pit_latest_values; import com.ridgebotics.ridgescout.scoutingData.ScoutingDataWriter; import com.ridgebotics.ridgescout.types.data.dataType; +import com.ridgebotics.ridgescout.types.data.intType; +import com.ridgebotics.ridgescout.types.data.stringType; import com.ridgebotics.ridgescout.types.frcMatch; import com.ridgebotics.ridgescout.types.input.dropdownType; import com.ridgebotics.ridgescout.types.input.inputType; @@ -19,7 +21,7 @@ public class PromptCreator { String summary = field.name + ": "; switch (field.getInputType()){ case DROPDOWN: - summary += "A the index of a dropdown with the possible options: [" +String.join(", ", ((dropdownType) field).text_options) + "]"; + summary += "The index of a dropdown with the possible options: [" +String.join(", ", ((dropdownType) field).text_options) + "]"; break; case SLIDER: sliderType slider = (sliderType) field; @@ -36,17 +38,14 @@ public class PromptCreator { } public static String genMatchPrompt(int matchIndex){ - String prompt = "Below is a list of data collected from an FRC match. Generate a qualitative and concise summary of the teams listed in the data collected.\n\n"; + String prompt = "Below is a list of data collected from an FRC match. Generate a qualitative and concise summary of the teams listed, using both numerical and textual data collected in the summary. Additionally, rank the teams in order of their performance.\n\n"; frcMatch curmatch = event.matches.get(matchIndex); - prompt += "## Pit scouting\n"; - prompt += "This is a list of the different fields that are present in the pit scouting data:\n"; + prompt += "## Pit scouting\n"; prompt += "This is a list of the different fields that are present in the pit scouting data:\n"; - - prompt += "1) Team number\n"; for(int i = 0; i < pit_latest_values.length; i++){ - prompt += (i+2) + ") " + fieldSummary(pit_latest_values[i]) + "\n"; + prompt += (i+1) + ") " + fieldSummary(pit_latest_values[i]) + "\n"; } prompt += ("\nData:\n"); @@ -57,28 +56,37 @@ public class PromptCreator { else teamNum = curmatch.blueAlliance[a-3]; - prompt += teamNum + ","; + prompt += "\nTeam " + teamNum + " pit scout data:\n"; String filename = evcode+"-"+teamNum+".pitscoutdata"; - if(!fileEditor.fileExist(filename)){ - prompt += ("null,".repeat(pit_latest_values.length)); - }else{ - ScoutingDataWriter.ParsedScoutingDataResult psdr = ScoutingDataWriter.load(filename, DataManager.pit_values, DataManager.pit_transferValues); - dataType[] types = psdr.data.array; - for(int i = 0; i < types.length; i++) { - prompt += (types[i].get() + ","); + + if (!fileEditor.fileExist(filename)) continue; + + ScoutingDataWriter.ParsedScoutingDataResult psdr = ScoutingDataWriter.load(filename, DataManager.pit_values, DataManager.pit_transferValues); + dataType[] types = psdr.data.array; + for(int i = 0; i < types.length; i++) { + boolean isNull = true; + switch (types[i].getValueType()){ + case NUM: + isNull = intType.isNull((int) types[i].get()); + break; + case STRING: + isNull = stringType.isNull((String) types[i].get()); + break; + } + if(isNull){ + prompt += match_latest_values[i].name + ": null\n"; + }else{ + prompt += match_latest_values[i].name + ": " + types[i].get() + "\n"; } } - prompt += "\n"; } - prompt += "\n## Match scouting\n"; prompt += "This is a list of the different fields that are present in the match scouting data:\n"; - prompt += "1) Match number\n"; for(int i = 0; i < match_latest_values.length; i++){ - prompt += (i+2) + ") " + fieldSummary(match_latest_values[i]) + "\n"; + prompt += (i+1) + ") " + fieldSummary(match_latest_values[i]) + "\n"; } prompt += ("\nData:\n"); @@ -90,7 +98,7 @@ public class PromptCreator { else teamNum = curmatch.blueAlliance[a-3]; - prompt += "Team " + teamNum + " Match scout data:\n"; + prompt += "\nTeam " + teamNum + " Match scout data for match " + curmatch.matchIndex +":\n"; frcMatch[] matchNums = event.getTeamMatches(teamNum); @@ -112,22 +120,31 @@ public class PromptCreator { alliancePos = c-2; break; } - } - String filename = evcode + "-" + match.matchIndex + "-" + alliance + "-" + alliancePos + "-" + teamNum + ".matchscoutdata"; if (!fileEditor.fileExist(filename)) continue; ScoutingDataWriter.ParsedScoutingDataResult psdr = ScoutingDataWriter.load(filename, DataManager.match_values, DataManager.match_transferValues); dataType[] types = psdr.data.array; - prompt += match.matchIndex + ","; for (int i = 0; i < types.length; i++) { - prompt += (types[i].get() + ","); + boolean isNull = true; + switch (types[i].getValueType()){ + case NUM: + isNull = intType.isNull((int) types[i].get()); + break; + case STRING: + isNull = stringType.isNull((String) types[i].get()); + break; + } + if(isNull){ + prompt += match_latest_values[i].name + ": null\n"; + }else{ + prompt += match_latest_values[i].name + ": " + types[i].get() + "\n"; + } } prompt += "\n"; - } } return prompt; diff --git a/app/src/main/res/layout/fragment_data_report.xml b/app/src/main/res/layout/fragment_data_report.xml index 8d22a96..52feb5e 100644 --- a/app/src/main/res/layout/fragment_data_report.xml +++ b/app/src/main/res/layout/fragment_data_report.xml @@ -1,44 +1,59 @@ - - - - - + android:layout_height="match_parent"> + + + + +