From f1cbb0b172ed562b2315dc0df65bee45f440fe7b Mon Sep 17 00:00:00 2001 From: Cyberarm Date: Thu, 14 Sep 2023 20:21:28 -0500 Subject: [PATCH] Clean slate with TAC, CyberarmEngineV2, and stubbed structure. --- .../internal/FtcRobotControllerActivity.java | 8 + TeamCode/build.gradle | 1 + .../drivers/EncoderAdafruitP4991.java | 87 ++++ .../cyberarm/drivers/IMUAdafruitBNO085.java | 104 ++++ .../cyberarm/engine/V2/CyberarmEngine.java | 466 ++++++++++++++++++ .../dev/cyberarm/engine/V2/CyberarmState.java | 235 +++++++++ .../cyberarm/engine/V2/GamepadChecker.java | 65 +++ .../java/dev/cyberarm/engine/V2/README.txt | 28 ++ .../org/firstinspires/ftc/teamcode/readme.md | 131 ----- .../CenterStage/Common/PrototypeRobot.java | 14 + .../Engines/PrototypeRobotEngine.java | 18 + .../States/PrototypeRobotDrivetrainState.java | 15 + .../java/org/timecrafters/Library/Robot.java | 5 + .../library/TimeCraftersConfiguration.java | 150 ++++++ .../library/backend/Config.java | 44 ++ .../library/backend/Settings.java | 12 + .../library/backend/TAC.java | 14 + .../library/backend/config/Action.java | 18 + .../library/backend/config/Configuration.java | 20 + .../library/backend/config/Group.java | 27 + .../library/backend/config/Presets.java | 22 + .../library/backend/config/Variable.java | 92 ++++ .../serializers/ActionDeserializer.java | 33 ++ .../library/serializers/ActionSerializer.java | 27 + .../serializers/ConfigDeserializer.java | 34 ++ .../library/serializers/ConfigSerializer.java | 32 ++ .../ConfigurationDeserializer.java | 36 ++ .../serializers/ConfigurationSerializer.java | 29 ++ .../serializers/GroupDeserializer.java | 33 ++ .../library/serializers/GroupSerializer.java | 26 + .../serializers/PresetsDeserializer.java | 33 ++ .../serializers/PresetsSerializer.java | 25 + .../serializers/SettingsDeserializer.java | 25 + .../serializers/SettingsSerializer.java | 30 ++ .../serializers/VariableDeserializer.java | 28 ++ .../serializers/VariableSerializer.java | 23 + .../tacnet_management/HaltTACNETService.java | 26 + .../tacnet_management/StartTACNETService.java | 31 ++ 38 files changed, 1916 insertions(+), 131 deletions(-) create mode 100644 TeamCode/src/main/java/dev/cyberarm/drivers/EncoderAdafruitP4991.java create mode 100644 TeamCode/src/main/java/dev/cyberarm/drivers/IMUAdafruitBNO085.java create mode 100644 TeamCode/src/main/java/dev/cyberarm/engine/V2/CyberarmEngine.java create mode 100644 TeamCode/src/main/java/dev/cyberarm/engine/V2/CyberarmState.java create mode 100644 TeamCode/src/main/java/dev/cyberarm/engine/V2/GamepadChecker.java create mode 100644 TeamCode/src/main/java/dev/cyberarm/engine/V2/README.txt delete mode 100644 TeamCode/src/main/java/org/firstinspires/ftc/teamcode/readme.md create mode 100644 TeamCode/src/main/java/org/timecrafters/CenterStage/Common/PrototypeRobot.java create mode 100644 TeamCode/src/main/java/org/timecrafters/CenterStage/Engines/PrototypeRobotEngine.java create mode 100644 TeamCode/src/main/java/org/timecrafters/CenterStage/States/PrototypeRobotDrivetrainState.java create mode 100644 TeamCode/src/main/java/org/timecrafters/Library/Robot.java create mode 100644 TeamCode/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/library/TimeCraftersConfiguration.java create mode 100644 TeamCode/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/library/backend/Config.java create mode 100644 TeamCode/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/library/backend/Settings.java create mode 100644 TeamCode/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/library/backend/TAC.java create mode 100644 TeamCode/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/library/backend/config/Action.java create mode 100644 TeamCode/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/library/backend/config/Configuration.java create mode 100644 TeamCode/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/library/backend/config/Group.java create mode 100644 TeamCode/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/library/backend/config/Presets.java create mode 100644 TeamCode/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/library/backend/config/Variable.java create mode 100644 TeamCode/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/library/serializers/ActionDeserializer.java create mode 100644 TeamCode/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/library/serializers/ActionSerializer.java create mode 100644 TeamCode/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/library/serializers/ConfigDeserializer.java create mode 100644 TeamCode/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/library/serializers/ConfigSerializer.java create mode 100644 TeamCode/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/library/serializers/ConfigurationDeserializer.java create mode 100644 TeamCode/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/library/serializers/ConfigurationSerializer.java create mode 100644 TeamCode/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/library/serializers/GroupDeserializer.java create mode 100644 TeamCode/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/library/serializers/GroupSerializer.java create mode 100644 TeamCode/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/library/serializers/PresetsDeserializer.java create mode 100644 TeamCode/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/library/serializers/PresetsSerializer.java create mode 100644 TeamCode/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/library/serializers/SettingsDeserializer.java create mode 100644 TeamCode/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/library/serializers/SettingsSerializer.java create mode 100644 TeamCode/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/library/serializers/VariableDeserializer.java create mode 100644 TeamCode/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/library/serializers/VariableSerializer.java create mode 100644 TeamCode/src/main/java/org/timecrafters/tacnet_management/HaltTACNETService.java create mode 100644 TeamCode/src/main/java/org/timecrafters/tacnet_management/StartTACNETService.java diff --git a/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/internal/FtcRobotControllerActivity.java b/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/internal/FtcRobotControllerActivity.java index 3f1f77c..3f1440e 100644 --- a/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/internal/FtcRobotControllerActivity.java +++ b/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/internal/FtcRobotControllerActivity.java @@ -183,6 +183,12 @@ public class FtcRobotControllerActivity extends Activity private WifiDirectChannelChanger wifiDirectChannelChanger; + + private static FtcRobotControllerActivity appActivity; + public static FtcRobotControllerActivity getAppActivity() { + return appActivity; + } + protected class RobotRestarter implements Restarter { public void requestRestart() { @@ -267,6 +273,8 @@ public class FtcRobotControllerActivity extends Activity @Override protected void onCreate(Bundle savedInstanceState) { + appActivity = this; + super.onCreate(savedInstanceState); if (enforcePermissionValidator()) { diff --git a/TeamCode/build.gradle b/TeamCode/build.gradle index 436ce67..90fd529 100644 --- a/TeamCode/build.gradle +++ b/TeamCode/build.gradle @@ -26,4 +26,5 @@ android { dependencies { implementation project(':FtcRobotController') annotationProcessor files('lib/OpModeAnnotationProcessor.jar') + implementation 'org.ftclib.ftclib:core:2.1.1' } diff --git a/TeamCode/src/main/java/dev/cyberarm/drivers/EncoderAdafruitP4991.java b/TeamCode/src/main/java/dev/cyberarm/drivers/EncoderAdafruitP4991.java new file mode 100644 index 0000000..7b0955b --- /dev/null +++ b/TeamCode/src/main/java/dev/cyberarm/drivers/EncoderAdafruitP4991.java @@ -0,0 +1,87 @@ +package dev.cyberarm.drivers; + +import com.qualcomm.robotcore.hardware.I2cAddr; +import com.qualcomm.robotcore.hardware.I2cDeviceSynch; +import com.qualcomm.robotcore.hardware.I2cDeviceSynchDevice; +import com.qualcomm.robotcore.hardware.configuration.annotations.DeviceProperties; +import com.qualcomm.robotcore.hardware.configuration.annotations.I2cDeviceType; +import com.qualcomm.robotcore.util.RobotLog; + +import java.util.Arrays; + +@I2cDeviceType +@DeviceProperties(name = "Adafruit P4991 Rotary Encoder QT", xmlTag = "Adafruit_Encoder_P4991") +public class EncoderAdafruitP4991 extends I2cDeviceSynchDevice { + final Object i2cLock = new Object(); + I2cAddr ADDRESS_I2C_DEFAULT = I2cAddr.create7bit(0x36); + private int operation = Register.SEESAW_ENCODER_BASE.bVal; + + public enum Register { + SEESAW_ENCODER_BASE(0x11), + + SEESAW_ENCODER_STATUS(0x00), + SEESAW_ENCODER_INTEN_SET(0x10), + SEESAW_ENCODER_INTEN_CLR(0x20), + SEESAW_ENCODER_POSITION(0x30), + SEESAW_ENCODER_DELTA(0x40); + + public final int bVal; + + Register(int bVal) + { + this.bVal = bVal; + } + } + + public EncoderAdafruitP4991(I2cDeviceSynch i2cDeviceSynch) { + // SEE: https://learn.adafruit.com/adafruit-seesaw-atsamd09-breakout?view=all#i2c-transactions-2937115 + + super(i2cDeviceSynch, true); + + super.registerArmingStateCallback(false); + this.deviceClient.engage(); + } + + @Override + protected boolean doInitialize() { + deviceClient.setI2cAddress(ADDRESS_I2C_DEFAULT); + + return deviceClient.isArmed(); + } + + @Override + public Manufacturer getManufacturer() { + return Manufacturer.Adafruit; + } + + @Override + public String getDeviceName() { + return "Adafruit P4991 Rotary Encoder QT"; + } + + public synchronized int getCurrentPosition() { +// ByteBuffer buf = ByteBuffer.allocate(4); +// buf.putShort((short) Register.SEESAW_ENCODER_BASE.bVal); +// buf.putShort((short) Register.SEESAW_ENCODER_POSITION.bVal); + + deviceClient.write8(0x11); + deviceClient.write8(0x30); + + delay(100); + + RobotLog.vv("Encoder", Arrays.toString(deviceClient.read(4))); + return 0; +// TimestampedData data = deviceClient.readTimeStamped(4); +// RobotLog.vv("ENCODER", "Operation: " + operation + ", Timestamp: " + data.nanoTime + ", Data: " + Arrays.toString(data.data)); +// return TypeConversion.byteArrayToInt(data.data); + } + + private void delay(long ms) { + try { + wait(ms); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + +} \ No newline at end of file diff --git a/TeamCode/src/main/java/dev/cyberarm/drivers/IMUAdafruitBNO085.java b/TeamCode/src/main/java/dev/cyberarm/drivers/IMUAdafruitBNO085.java new file mode 100644 index 0000000..d028359 --- /dev/null +++ b/TeamCode/src/main/java/dev/cyberarm/drivers/IMUAdafruitBNO085.java @@ -0,0 +1,104 @@ +package dev.cyberarm.drivers; + +import com.qualcomm.robotcore.hardware.I2cAddr; +import com.qualcomm.robotcore.hardware.I2cDeviceSynch; +import com.qualcomm.robotcore.hardware.I2cDeviceSynchDevice; +import com.qualcomm.robotcore.hardware.IMU; + +import org.firstinspires.ftc.robotcore.external.navigation.AngleUnit; +import org.firstinspires.ftc.robotcore.external.navigation.AngularVelocity; +import org.firstinspires.ftc.robotcore.external.navigation.AxesOrder; +import org.firstinspires.ftc.robotcore.external.navigation.AxesReference; +import org.firstinspires.ftc.robotcore.external.navigation.Orientation; +import org.firstinspires.ftc.robotcore.external.navigation.Quaternion; +import org.firstinspires.ftc.robotcore.external.navigation.YawPitchRollAngles; + +public class IMUAdafruitBNO085 extends I2cDeviceSynchDevice implements IMU { + + private final Object i2cLock = new Object(); + private final I2cAddr DEFAULT_I2C_ADDRESS = new I2cAddr(0x4A); + private enum Register { + PRODUCT_ID(0x00); + + private final int address; + + Register(int address) { + this.address = address; + } + } + + + protected IMUAdafruitBNO085(I2cDeviceSynch i2cDeviceSynch, boolean deviceClientIsOwned) { + super(i2cDeviceSynch, deviceClientIsOwned); + + this.deviceClient.setI2cAddress(DEFAULT_I2C_ADDRESS); + } + + @Override + public boolean initialize(Parameters parameters) { + return false; + } + + @Override + public void resetYaw() { + } + + @Override + public YawPitchRollAngles getRobotYawPitchRollAngles() { + return null; + } + + @Override + public Orientation getRobotOrientation(AxesReference reference, AxesOrder order, AngleUnit angleUnit) { + return null; + } + + @Override + public Quaternion getRobotOrientationAsQuaternion() { + return null; + } + + @Override + public AngularVelocity getRobotAngularVelocity(AngleUnit angleUnit) { + return null; + } + + @Override + public Manufacturer getManufacturer() { + return Manufacturer.Adafruit; + } + + @Override + public String getDeviceName() { + return null; + } + + @Override + public String getConnectionInfo() { + return null; + } + + @Override + public int getVersion() { + return 0; + } + + @Override + protected boolean doInitialize() { + synchronized (i2cLock) { + deviceClient.read(32); + } + + return false; + } + + @Override + public void resetDeviceConfigurationForOpMode() { + + } + + @Override + public void close() { + + } +} diff --git a/TeamCode/src/main/java/dev/cyberarm/engine/V2/CyberarmEngine.java b/TeamCode/src/main/java/dev/cyberarm/engine/V2/CyberarmEngine.java new file mode 100644 index 0000000..a515e48 --- /dev/null +++ b/TeamCode/src/main/java/dev/cyberarm/engine/V2/CyberarmEngine.java @@ -0,0 +1,466 @@ +package dev.cyberarm.engine.V2; + +import android.util.Log; + +import com.qualcomm.robotcore.eventloop.opmode.OpMode; +import com.qualcomm.robotcore.hardware.Gamepad; + +import org.timecrafters.TimeCraftersConfigurationTool.library.TimeCraftersConfiguration; +import org.timecrafters.TimeCraftersConfigurationTool.library.backend.config.Action; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; + +/** + * CyberarmEngine Version 3.0 | December 31st 2022 + * After a few years of use, it's safe to say this implementation is stable and reasonably feature complete. + * * Added support for background tasks that run unqueued for the duration of the op mode unless stopped. + * * Added thread-safe 'blackboard' for storing bits that need to be easily shared between states/tasks. + * + * CyberarmEngine Version 2.0 | October 26th 2018 + * AN Experimental reimplementation of GoldfishPi's original Engine system. + * Designed to be easily maintainable, extensible, and easy to understand. + */ +public abstract class CyberarmEngine extends OpMode { + + public static CyberarmEngine instance; + //Array To Hold States + final private CopyOnWriteArrayList cyberarmStates = new CopyOnWriteArrayList<>(); + // Array to Hold Tasks + final private CopyOnWriteArrayList backgroundTasks = new CopyOnWriteArrayList<>(); + // HashMap to store data for States and Tasks + final private ConcurrentHashMap blackboard = new ConcurrentHashMap<>(); + private int activeStateIndex = 0; // Index of currently running state + private boolean isRunning; // Whether engine is running or not + + private final static String TAG = "PROGRAM.ENGINE"; + public boolean showStateChildrenListInTelemetry = false; + + private GamepadChecker gamepadCheckerGamepad1, gamepadCheckerGamepad2; + + /** + * Called when INIT button on Driver Station is pushed + * ENSURE to call super.init() if you override this method + */ + public void init() { + CyberarmEngine.instance = this; + isRunning = false; + gamepadCheckerGamepad1 = new GamepadChecker(this, gamepad1); + gamepadCheckerGamepad2 = new GamepadChecker(this, gamepad2); + + setup(); + + isRunning = true; + + for (CyberarmState state: cyberarmStates) { + initState(state); + } + + // Background tasks + for (CyberarmState task : backgroundTasks) { + initState(task); + } + } + + /** + * Setup states for engine to use + * For example: + *
+   * {@code
+   *   public void setup() {
+   *     addState(new TestState());
+   *     addState(new AnotherState(100, 500));
+   *   }
+   * }
+   * 
+ */ + public abstract void setup(); + + /** + * Called when START button on Driver Station is pushed + * ENSURE to call super.start() if you override this method + */ + public void start() { + if (cyberarmStates.size() > 0) { + runState(cyberarmStates.get(0)); + } + + // Background tasks + for (CyberarmState task : backgroundTasks) { + runState(task); + } + } + + /** + * Engine main loop + * ENSURE to call super.loop() if you override this method + */ + public void loop() { + CyberarmState state; + + // Try to set state to the current state, if it fails assume that there are no states to run + try { + state = cyberarmStates.get(activeStateIndex); + } catch(IndexOutOfBoundsException e) { + // The engine is now out of states. + stop(); + + telemetry.addLine("" + this.getClass().getSimpleName() + " is out of states to run!"); + telemetry.addLine(); + return; + } + + // Add telemetry to show currently running state + telemetry.addLine( + "Running state: " +state.getClass().getSimpleName() + ". State: " + + (activeStateIndex + 1) + " of " + (cyberarmStates.size()) + + " (" + activeStateIndex + "/" + (cyberarmStates.size() - 1) + ")"); + + if (showStateChildrenListInTelemetry && state.hasChildren()) { + for(CyberarmState child: state.children) { + telemetry.addLine(" Child: " + child.getClass().getSimpleName() + " [" + child.children.size() + "] grandchildren"); + } + } + telemetry.addLine(); + + if (state.getHasFinished() && state.childrenHaveFinished()) { + activeStateIndex++; + + try { + state = cyberarmStates.get(activeStateIndex); + runState(state); + } catch(IndexOutOfBoundsException e) { /* loop will handle this in a few milliseconds */ } + + } else { + stateTelemetry(state); + + // Background tasks + for (CyberarmState task : backgroundTasks) { + stateTelemetry(task); + } + } + + gamepadCheckerGamepad1.update(); + gamepadCheckerGamepad2.update(); + } + + /** + * Stops every known state + */ + @Override + public void stop() { + for (CyberarmState state: cyberarmStates) { + stopState(state); + } + + // Background tasks + for (CyberarmState task : backgroundTasks) { + stopState(task); + } + } + + /** + * Recursively calls telemetry() on states + * @param state State to get telemetry + */ + private void stateTelemetry(CyberarmState state) { + if (!state.getHasFinished()) { + state.telemetry(); + } + + for(CyberarmState childState : state.children) { + if (!childState.getHasFinished()) { + stateTelemetry(childState); + } + } + } + + /** + * Called when INIT button on Driver Station is pressed + * Recursively initiates states + * @param state State to initiate + */ + private void initState(CyberarmState state) { + state.init(); + + for(CyberarmState childState : state.children) { + initState(childState); + } + } + + /** + * Called when programs ends or STOP button on Driver Station is pressed + * Recursively stop states + * @param state State to stop + */ + public void stopState(CyberarmState state) { + state.setHasFinished(true); + state.stop(); + + for(CyberarmState childState : state.children) { + stopState(childState); + } + } + + /** + * Recursively start up states + * @param state State to run + */ + protected void runState(CyberarmState state) { + final CyberarmState finalState = state; +// if (state.isRunning()) { return; } // Assume that we have already started running this state + + new Thread(() -> { + finalState.prestart(); + finalState.start(); + finalState.startTime = System.currentTimeMillis(); + finalState.run(); + }).start(); + + for (CyberarmState kid : state.children) { + runState(kid); + } + } + + /** + * Add state to queue, will call init() on state if engine is running + * @param state State to add to queue + */ + public CyberarmState addState(CyberarmState state) { + Log.i(TAG, "Adding cyberarmState "+ state.getClass()); + cyberarmStates.add(state); + + if (isRunning()) { initState(state); } + + return state; + } + + /** + * Inserts state after the query state plus an offset to ensure logical insertion + * @param query State to add state after + * @param state State to be inserted + * @return CyberarmState + */ + public CyberarmState insertState(CyberarmState query, CyberarmState state) { + int index = cyberarmStates.indexOf(query) + query.insertOffset; + Log.i(TAG, "Adding cyberarmState "+ state.getClass()); + + cyberarmStates.add(index, state); + query.insertOffset++; + + if (isRunning()) { initState(state); } + + return state; + } + + /** + * Adds state to the most recently added top level state as a parallel state + * @param state State to add to last top level state + * @return CyberarmState + */ + public CyberarmState addParallelStateToLastState(CyberarmState state) { + CyberarmState parentState = cyberarmStates.get(cyberarmStates.size() - 1); + + Log.i(TAG, "Adding parallel cyberarmState "+ state.getClass() + " to parent state " + parentState.getClass()); + + parentState.addParallelState((state)); + + return state; + } + + /** + * Adds state as a background task that is run until the opmode stops + * background tasks are not queued, they are all started at once. + * @param state State to add to list + * @return CyberarmState + */ + public CyberarmState addTask(CyberarmState state) { + Log.i(TAG, "Adding task cyberarmState "+ state.getClass()); + + backgroundTasks.add(state); + + if (isRunning()) { + initState(state); + runState(state); + } + + return state; + } + + /** + * Retrieve value from blackboard + * @param key String to use to look up value + * @return Returns T of stored Object + */ + public T blackboardGet(String key) { + return (T) blackboard.get(key); + } + + public String blackboardGetString(String key) { + return (String) blackboard.get(key); + } + + public int blackboardGetInt(String key) { + return (int) blackboard.get(key); + } + + public long blackboardGetLong(String key) { + return (long) blackboard.get(key); + } + + public float blackboardGetFloat(String key) { + return (float) blackboard.get(key); + } + + public double blackboardGetDouble(String key) { + return (double) blackboard.get(key); + } + + public boolean blackboardGetBoolean(String key) { + return (boolean) blackboard.get(key); + } + + /** + * Set value of key to value + * @param key String + * @param value Object + * @return Returns T + */ + + public T blackboardSet(String key, T value) { + blackboard.put(key, value); + + return (T) value; + } + + /** + * Remove value from blackboard + * @param key String + * @param value Object + * @return Returns T + */ + public T blackboardRemove(String key, T value) { + blackboard.remove(key); + + return (T) value; + } + + private void buttonDownForStates(CyberarmState state, Gamepad gamepad, String button) { + state.buttonDown(gamepad, button); + + for (CyberarmState child : state.children) { + child.buttonDown(gamepad, button); + } + } + + private void buttonUpForStates(CyberarmState state, Gamepad gamepad, String button) { + state.buttonUp(gamepad, button); + + for (CyberarmState child : state.children) { + child.buttonUp(gamepad, button); + } + } + + /** + * Called by GamepadChecker when it detects that a gamepad button has been pressed + * @param gamepad Gamepad + * @param button String + */ + protected void buttonDown(Gamepad gamepad, String button) { + try { + buttonDownForStates(cyberarmStates.get(activeStateIndex), gamepad, button); + } catch(IndexOutOfBoundsException e){ + /* loop will handle this in a few milliseconds */ + } + } + + /** + * Called by GamepadChecker when it detects that a gamepad button has been released + * @param gamepad Gamepad + * @param button String + */ + protected void buttonUp(Gamepad gamepad, String button) { + try { + buttonUpForStates(cyberarmStates.get(activeStateIndex), gamepad, button); + } catch(IndexOutOfBoundsException e){ + /* loop will handle this in a few milliseconds */ + } + } + + /** + * This will return false while Engine.setup() is executing, and be true after. + * @return Whether the engine main loop is running + */ + public boolean isRunning() { + return isRunning; + } + + /** + * + * @return The index used to lookup the current state from cyberarmStates + */ + public int getActiveStateIndex() { + return activeStateIndex; + } + + /** + * Automatically populates states from a TimeCraftersConfiguration Group actions + * requires action comments to start with an @ character followed by the class name + * state must have a construction that takes 3 arguments: object, groupName, and actionName + * @param configuration TimeCraftersConfiguration + * @param packageName Package name where states are defined + * @param object Object to pass as first argument to states constructor + * @param objectClass Class to cast object to + * @param groupName Group name + */ + public void setupFromConfig(TimeCraftersConfiguration configuration, String packageName, Object object, Class objectClass, String groupName) { + CyberarmState lastState = null; + String lastActionName = null; + String[] lastActionNameSplit = new String[0]; + + for (Action action : configuration.group(groupName).getActions()) { + if (!action.enabled) { + continue; + } + + String className = null; + + if (action.comment.startsWith("@")) { + String[] split = action.comment.split("@"); + className = split[1].split("[ \\-]")[0]; + } else { + throw(new RuntimeException("setupFromConfig: Action \"" + action.name + "\" in group \"" + groupName + "\" is missing magic @ in comment.")); + } + + Class klass = null; + try { + klass = Class.forName("" + packageName + "." + className); + if (klass != null) { + String[] actionNameSplit = action.name.split("-"); + Constructor constructor = klass.getConstructor(objectClass, String.class, String.class); + CyberarmState state = (CyberarmState) constructor.newInstance(objectClass.cast(object), groupName, action.name); + + if (lastState != null && lastActionNameSplit.length == 2 && actionNameSplit.length == 2 && actionNameSplit[0].equals(lastActionNameSplit[0])) + { + lastState.addParallelState(state); + } else { + addState(state); + lastState = state; + } + + lastActionName = action.name; + lastActionNameSplit = lastActionName.split("-"); + } + } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InstantiationException | InvocationTargetException e) { + e.printStackTrace(); + + RuntimeException exception = new RuntimeException(e.getMessage(), e.getCause()); + exception.setStackTrace(e.getStackTrace()); + + throw(exception); + } + } + } +} diff --git a/TeamCode/src/main/java/dev/cyberarm/engine/V2/CyberarmState.java b/TeamCode/src/main/java/dev/cyberarm/engine/V2/CyberarmState.java new file mode 100644 index 0000000..d0a867c --- /dev/null +++ b/TeamCode/src/main/java/dev/cyberarm/engine/V2/CyberarmState.java @@ -0,0 +1,235 @@ +package dev.cyberarm.engine.V2; + +import android.util.Log; + +import com.qualcomm.robotcore.hardware.Gamepad; + +import java.util.concurrent.CopyOnWriteArrayList; + +/** + * A State for use with CyberarmEngineV2 + */ +public abstract class CyberarmState implements Runnable { + + private volatile boolean isRunning, hasFinished; + public static String TAG = "PROGRAM.STATE"; + public CyberarmEngine engine = CyberarmEngine.instance; + public CopyOnWriteArrayList children = new CopyOnWriteArrayList<>(); + public long startTime = 0; + public int insertOffset = 1; + + /** + * Called when INIT button on Driver Station is pushed + */ + public void init() { + } + + /** + * Called just before start to ensure state is in correct state + */ + protected void prestart() { + isRunning = true; + } + + /** + * Called when state has begin to run + */ + public void start() { + } + + /** + * Called while State is running + */ + public abstract void exec(); + + /** + * State's main loop, calls exec() until hasFinished is true + * DO NO OVERRIDE + */ + @Override + public void run() { + while (!hasFinished) { + exec(); + } + isRunning = false; + } + + /** + * Place telemetry calls in here instead of inside exec() to have them displayed correctly on the Driver Station + * (States update thousands of times per second, resulting in missing or weirdly formatted telemetry if telemetry is added in exec()) + */ + public void telemetry() { + } + + /** + * Called when Engine is finished + */ + public void stop() { + } + + /** + * Called when GamepadChecker detects that a gamepad button has been pressed + * @param gamepad Gamepad + * @param button String + */ + public void buttonDown(Gamepad gamepad, String button) { + } + + + /** + * Called when GamepadChecker detects that a gamepad button has been released + * @param gamepad Gamepad + * @param button String + */ + public void buttonUp(Gamepad gamepad, String button) { + } + + /** + * Add a state which runs in parallel with this one + */ + public CyberarmState addParallelState(CyberarmState state) { + Log.i(TAG, "Adding " + state.getClass() + " to " + this.getClass()); + children.add(state); + + if (isRunning()) { + state.init(); + engine.runState(state); + Log.i(TAG, "Started " + state.getClass() + " in " + this.getClass()); + } + + return state; + } + + /** + * Add a state to engine which will run after this one finishes + */ + public CyberarmState addState(CyberarmState state) { + engine.insertState(this, state); + + return state; + } + + /** + * Returns whether or not state has children + * @return True if state has children, false otherwise + */ + public boolean hasChildren() { + return (children.size() > 0); + } + + /** + * Have all of the states children finished running themselves? + * @return Whether or not all children have finished running + */ + public boolean childrenHaveFinished() { + return childrenHaveFinished(children); + } + + /** + * Have all of the states children finished running themselves? + * @param kids CopyOnWriteArrayList of children to check for hasFinished() + * @return Whether or not all children have finished running + */ + public boolean childrenHaveFinished(CopyOnWriteArrayList kids) { + boolean allDone = true; + + for (CyberarmState state : kids) { + if (!state.hasFinished) { + allDone = false; + break; + } else { + if (!state.childrenHaveFinished()) { + allDone = false; + break; + } + } + } + + return allDone; + } + + /** + * + * @return The number of milliseconds this state has been running for + */ + public double runTime() { + return (System.currentTimeMillis() - startTime); + } + + /** + * Set whether state has finished or not + * @param value boolean + */ + public void setHasFinished(boolean value) { + hasFinished = value; + } + + /** + * + * @return Get value of hasFinished + */ + public boolean getHasFinished() { + return hasFinished; + } + + /** + * + * @return Get value of isRunning + */ + public boolean isRunning() { + return isRunning; + } + + /** + * + * @param timems How long to sleep in milliseconds + */ + public void sleep(long timems) { + try { + Thread.sleep(timems); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + /** + * + * @param width How many characters wide to be + * @param percentCompleted Number between 0.0 and 100.0 + * @param bar What character to draw the completion bar with + * @param padding What character to draw non-completed bar with + * @return A string + */ + public String progressBar(int width, double percentCompleted, String bar, String padding) { + String percentCompletedString = "" + Math.round(percentCompleted) + "%"; + double activeWidth = (width - 2) - percentCompletedString.length(); + + String string = "["; + double completed = (percentCompleted / 100.0) * activeWidth; + + for (int i = 0; i <= ((int) activeWidth); i++) { + if (i == ((int) activeWidth) / 2) { + string += percentCompletedString; + } else { + if (i <= (int) completed && (int) completed > 0) { + string += bar; + } else { + string += padding; + } + } + } + + string += "]"; + return string; + } + + /** + * + * @param width How many characters wide to be + * @param percentCompleted Number between 0.0 and 100.0 + * @return A string + */ + public String progressBar(int width, double percentCompleted) { + return progressBar(width, percentCompleted, "=", " "); + } +} \ No newline at end of file diff --git a/TeamCode/src/main/java/dev/cyberarm/engine/V2/GamepadChecker.java b/TeamCode/src/main/java/dev/cyberarm/engine/V2/GamepadChecker.java new file mode 100644 index 0000000..2f43acd --- /dev/null +++ b/TeamCode/src/main/java/dev/cyberarm/engine/V2/GamepadChecker.java @@ -0,0 +1,65 @@ +package dev.cyberarm.engine.V2; + +import android.util.Log; + +import com.qualcomm.robotcore.hardware.Gamepad; + +import java.lang.reflect.Field; +import java.util.HashMap; + +public class GamepadChecker { + private final String TAG = "GamepadChecker"; + private final CyberarmEngine engine; + private final Gamepad gamepad; + private final HashMap buttons = new HashMap<>(); + + public GamepadChecker(CyberarmEngine engine, Gamepad gamepad) { + this.engine = engine; + this.gamepad = gamepad; + + buttons.put("a", false); + buttons.put("b", false); + buttons.put("x", false); + buttons.put("y", false); + + buttons.put("start", false); + buttons.put("guide", false); + buttons.put("back", false); + + buttons.put("left_bumper", false); + buttons.put("right_bumper", false); + + buttons.put("left_stick_button", false); + buttons.put("right_stick_button", false); + + buttons.put("dpad_left", false); + buttons.put("dpad_right", false); + buttons.put("dpad_up", false); + buttons.put("dpad_down", false); + } + + public void update() { + for (String btn : buttons.keySet()) { + try { + Field field = gamepad.getClass().getDeclaredField(btn); + boolean button = field.getBoolean(gamepad); + + if (button) { + if (!buttons.get(btn)) { + engine.buttonDown(gamepad, btn); + } + + buttons.put(btn, true); + } else { + if (buttons.get(btn)) { + engine.buttonUp(gamepad, btn); + } + + buttons.put(btn, false); + } + } catch (NoSuchFieldException|IllegalAccessException e) { + e.printStackTrace(); + } + } + } +} diff --git a/TeamCode/src/main/java/dev/cyberarm/engine/V2/README.txt b/TeamCode/src/main/java/dev/cyberarm/engine/V2/README.txt new file mode 100644 index 0000000..7632dc5 --- /dev/null +++ b/TeamCode/src/main/java/dev/cyberarm/engine/V2/README.txt @@ -0,0 +1,28 @@ +CyberarmEngine V2 Architecture + +Engine + -> [States] + -> [ParallelStates] + +Start with an Engine and override setup(): + +public class Engine extends CyberarmEngineV2 { + public void setup() { + addState(new State(arguments)); + } +} + +NOTE: states do not need to be passed the instance of Engine as they have a field 'cyberarmEngine' + which is set to CyberarmEngineV2.instance when they are created. + +States can have 'children' which are also States, which run in parallel with their parent. There is +no fixed limit to how many grandchildren can exist (Children themselves can have children.): + +public class State extends CyberarmEngineStateV2 { + public init() { + addParallelState(new ParallelState(arguments)); + } + + public exec() { + } +} \ No newline at end of file diff --git a/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/readme.md b/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/readme.md deleted file mode 100644 index 4d1da42..0000000 --- a/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/readme.md +++ /dev/null @@ -1,131 +0,0 @@ -## TeamCode Module - -Welcome! - -This module, TeamCode, is the place where you will write/paste the code for your team's -robot controller App. This module is currently empty (a clean slate) but the -process for adding OpModes is straightforward. - -## Creating your own OpModes - -The easiest way to create your own OpMode is to copy a Sample OpMode and make it your own. - -Sample opmodes exist in the FtcRobotController module. -To locate these samples, find the FtcRobotController module in the "Project/Android" tab. - -Expand the following tree elements: - FtcRobotController/java/org.firstinspires.ftc.robotcontroller/external/samples - -### Naming of Samples - -To gain a better understanding of how the samples are organized, and how to interpret the -naming system, it will help to understand the conventions that were used during their creation. - -These conventions are described (in detail) in the sample_conventions.md file in this folder. - -To summarize: A range of different samples classes will reside in the java/external/samples. -The class names will follow a naming convention which indicates the purpose of each class. -The prefix of the name will be one of the following: - -Basic: This is a minimally functional OpMode used to illustrate the skeleton/structure - of a particular style of OpMode. These are bare bones examples. - -Sensor: This is a Sample OpMode that shows how to use a specific sensor. - It is not intended to drive a functioning robot, it is simply showing the minimal code - required to read and display the sensor values. - -Robot: This is a Sample OpMode that assumes a simple two-motor (differential) drive base. - It may be used to provide a common baseline driving OpMode, or - to demonstrate how a particular sensor or concept can be used to navigate. - -Concept: This is a sample OpMode that illustrates performing a specific function or concept. - These may be complex, but their operation should be explained clearly in the comments, - or the comments should reference an external doc, guide or tutorial. - Each OpMode should try to only demonstrate a single concept so they are easy to - locate based on their name. These OpModes may not produce a drivable robot. - -After the prefix, other conventions will apply: - -* Sensor class names are constructed as: Sensor - Company - Type -* Robot class names are constructed as: Robot - Mode - Action - OpModetype -* Concept class names are constructed as: Concept - Topic - OpModetype - -Once you are familiar with the range of samples available, you can choose one to be the -basis for your own robot. In all cases, the desired sample(s) needs to be copied into -your TeamCode module to be used. - -This is done inside Android Studio directly, using the following steps: - - 1) Locate the desired sample class in the Project/Android tree. - - 2) Right click on the sample class and select "Copy" - - 3) Expand the TeamCode/java folder - - 4) Right click on the org.firstinspires.ftc.teamcode folder and select "Paste" - - 5) You will be prompted for a class name for the copy. - Choose something meaningful based on the purpose of this class. - Start with a capital letter, and remember that there may be more similar classes later. - -Once your copy has been created, you should prepare it for use on your robot. -This is done by adjusting the OpMode's name, and enabling it to be displayed on the -Driver Station's OpMode list. - -Each OpMode sample class begins with several lines of code like the ones shown below: - -``` - @TeleOp(name="Template: Linear OpMode", group="Linear Opmode") - @Disabled -``` - -The name that will appear on the driver station's "opmode list" is defined by the code: - ``name="Template: Linear OpMode"`` -You can change what appears between the quotes to better describe your opmode. -The "group=" portion of the code can be used to help organize your list of OpModes. - -As shown, the current OpMode will NOT appear on the driver station's OpMode list because of the - ``@Disabled`` annotation which has been included. -This line can simply be deleted , or commented out, to make the OpMode visible. - - - -## ADVANCED Multi-Team App management: Cloning the TeamCode Module - -In some situations, you have multiple teams in your club and you want them to all share -a common code organization, with each being able to *see* the others code but each having -their own team module with their own code that they maintain themselves. - -In this situation, you might wish to clone the TeamCode module, once for each of these teams. -Each of the clones would then appear along side each other in the Android Studio module list, -together with the FtcRobotController module (and the original TeamCode module). - -Selective Team phones can then be programmed by selecting the desired Module from the pulldown list -prior to clicking to the green Run arrow. - -Warning: This is not for the inexperienced Software developer. -You will need to be comfortable with File manipulations and managing Android Studio Modules. -These changes are performed OUTSIDE of Android Studios, so close Android Studios before you do this. - -Also.. Make a full project backup before you start this :) - -To clone TeamCode, do the following: - -Note: Some names start with "Team" and others start with "team". This is intentional. - -1) Using your operating system file management tools, copy the whole "TeamCode" - folder to a sibling folder with a corresponding new name, eg: "Team0417". - -2) In the new Team0417 folder, delete the TeamCode.iml file. - -3) the new Team0417 folder, rename the "src/main/java/org/firstinspires/ftc/teamcode" folder - to a matching name with a lowercase 'team' eg: "team0417". - -4) In the new Team0417/src/main folder, edit the "AndroidManifest.xml" file, change the line that contains - package="org.firstinspires.ftc.teamcode" - to be - package="org.firstinspires.ftc.team0417" - -5) Add: include ':Team0417' to the "/settings.gradle" file. - -6) Open up Android Studios and clean out any old files by using the menu to "Build/Clean Project"" \ No newline at end of file diff --git a/TeamCode/src/main/java/org/timecrafters/CenterStage/Common/PrototypeRobot.java b/TeamCode/src/main/java/org/timecrafters/CenterStage/Common/PrototypeRobot.java new file mode 100644 index 0000000..0f8bc05 --- /dev/null +++ b/TeamCode/src/main/java/org/timecrafters/CenterStage/Common/PrototypeRobot.java @@ -0,0 +1,14 @@ +package org.timecrafters.CenterStage.Common; + +import org.timecrafters.Library.Robot; + +public class PrototypeRobot extends Robot { + private String string; + public PrototypeRobot(String string) { + this.string = string; + } + @Override + public void setup() { + System.out.println("Bacon: " + this.string); + } +} diff --git a/TeamCode/src/main/java/org/timecrafters/CenterStage/Engines/PrototypeRobotEngine.java b/TeamCode/src/main/java/org/timecrafters/CenterStage/Engines/PrototypeRobotEngine.java new file mode 100644 index 0000000..f8ef646 --- /dev/null +++ b/TeamCode/src/main/java/org/timecrafters/CenterStage/Engines/PrototypeRobotEngine.java @@ -0,0 +1,18 @@ +package org.timecrafters.CenterStage.Engines; + +import com.qualcomm.robotcore.eventloop.opmode.TeleOp; + +import org.timecrafters.CenterStage.Common.PrototypeRobot; +import org.timecrafters.CenterStage.States.PrototypeRobotDrivetrainState; +import dev.cyberarm.engine.V2.CyberarmEngine; + +@TeleOp(name = "Prototype Robot", group = "PROTOTYPE") +public class PrototypeRobotEngine extends CyberarmEngine { + private PrototypeRobot robot; + @Override + public void setup() { + this.robot = new PrototypeRobot("Hello World"); + + addState(new PrototypeRobotDrivetrainState(robot)); + } +} diff --git a/TeamCode/src/main/java/org/timecrafters/CenterStage/States/PrototypeRobotDrivetrainState.java b/TeamCode/src/main/java/org/timecrafters/CenterStage/States/PrototypeRobotDrivetrainState.java new file mode 100644 index 0000000..50546f2 --- /dev/null +++ b/TeamCode/src/main/java/org/timecrafters/CenterStage/States/PrototypeRobotDrivetrainState.java @@ -0,0 +1,15 @@ +package org.timecrafters.CenterStage.States; + +import org.timecrafters.CenterStage.Common.PrototypeRobot; + +import dev.cyberarm.engine.V2.CyberarmState; + +public class PrototypeRobotDrivetrainState extends CyberarmState { + private PrototypeRobot robot; + public PrototypeRobotDrivetrainState(PrototypeRobot robot) { + this.robot = robot; + } + @Override + public void exec() { + } +} diff --git a/TeamCode/src/main/java/org/timecrafters/Library/Robot.java b/TeamCode/src/main/java/org/timecrafters/Library/Robot.java new file mode 100644 index 0000000..cbed874 --- /dev/null +++ b/TeamCode/src/main/java/org/timecrafters/Library/Robot.java @@ -0,0 +1,5 @@ +package org.timecrafters.Library; + +public abstract class Robot { + public abstract void setup(); +} diff --git a/TeamCode/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/library/TimeCraftersConfiguration.java b/TeamCode/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/library/TimeCraftersConfiguration.java new file mode 100644 index 0000000..5c52a36 --- /dev/null +++ b/TeamCode/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/library/TimeCraftersConfiguration.java @@ -0,0 +1,150 @@ +package org.timecrafters.TimeCraftersConfigurationTool.library; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +import org.timecrafters.TimeCraftersConfigurationTool.library.backend.Config; +import org.timecrafters.TimeCraftersConfigurationTool.library.backend.Settings; +import org.timecrafters.TimeCraftersConfigurationTool.library.backend.TAC; +import org.timecrafters.TimeCraftersConfigurationTool.library.backend.config.Action; +import org.timecrafters.TimeCraftersConfigurationTool.library.backend.config.Configuration; +import org.timecrafters.TimeCraftersConfigurationTool.library.backend.config.Group; +import org.timecrafters.TimeCraftersConfigurationTool.library.backend.config.Presets; +import org.timecrafters.TimeCraftersConfigurationTool.library.backend.config.Variable; +import org.timecrafters.TimeCraftersConfigurationTool.library.serializers.ActionDeserializer; +import org.timecrafters.TimeCraftersConfigurationTool.library.serializers.ActionSerializer; +import org.timecrafters.TimeCraftersConfigurationTool.library.serializers.ConfigDeserializer; +import org.timecrafters.TimeCraftersConfigurationTool.library.serializers.ConfigSerializer; +import org.timecrafters.TimeCraftersConfigurationTool.library.serializers.ConfigurationDeserializer; +import org.timecrafters.TimeCraftersConfigurationTool.library.serializers.ConfigurationSerializer; +import org.timecrafters.TimeCraftersConfigurationTool.library.serializers.GroupDeserializer; +import org.timecrafters.TimeCraftersConfigurationTool.library.serializers.GroupSerializer; +import org.timecrafters.TimeCraftersConfigurationTool.library.serializers.PresetsDeserializer; +import org.timecrafters.TimeCraftersConfigurationTool.library.serializers.PresetsSerializer; +import org.timecrafters.TimeCraftersConfigurationTool.library.serializers.SettingsDeserializer; +import org.timecrafters.TimeCraftersConfigurationTool.library.serializers.SettingsSerializer; +import org.timecrafters.TimeCraftersConfigurationTool.library.serializers.VariableDeserializer; +import org.timecrafters.TimeCraftersConfigurationTool.library.serializers.VariableSerializer; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; + +public class TimeCraftersConfiguration { + private static final String TAG = "TCT|TCConfig"; + private Config config; + + public TimeCraftersConfiguration() { + Settings settings = loadSettings(); + this.config = loadConfig(settings.config); + } + + public TimeCraftersConfiguration(String configName) { + this.config = loadConfig(configName); + } + + public Config getConfig() { + return config; + } + + public Group group(String groupName) { + for (final Group group : config.getGroups()) { + if (group.name.trim().equals(groupName.trim())) { + return group; + } + } + + throw(new RuntimeException("Failed to find a group named:\"" + groupName.trim() + "\" in config \"" + config.getName() + "\"")); + } + + public Action action(String groupName, String actionName) { + final Group group = group(groupName); + + for (Action action : group.getActions()) { + if (action.name.trim().equals(actionName.trim())) { + return action; + } + } + + throw(new RuntimeException("Failed to find an action named:\"" + actionName.trim() + "\" in group \"" + groupName.trim() + "\" in config \"" + config.getName() + "\"")); + } + + public Variable variable(String groupName, String actionName, String variableName) { + final Action action = action(groupName, actionName); + + for (Variable variable : action.getVariables()) { + if (variable.name.trim().equals(variableName.trim())) { + return variable; + } + } + + throw(new RuntimeException("Failed to find a variable named \"" + variableName.trim() + "\" in action:\"" + actionName.trim() + + "\" in group \"" + groupName.trim() + "\" in config \"" + config.getName() + "\"")); + } + + private Settings loadSettings() { + File settingsFile = new File(TAC.SETTINGS_PATH); + + if (!settingsFile.exists()) { + throw( new RuntimeException("Unable to load settings.json, file does not exist!") ); + } + + try { + return gsonForSettings().fromJson(new FileReader(settingsFile), Settings.class); + } catch (FileNotFoundException e) { + throw( new RuntimeException("Unable to load settings.json") ); + } + } + + private Config loadConfig(String name) { + if (name.equals("")) { + throw(new RuntimeException("Cannot load a config with an empty name!")); + } + + String path = TAC.CONFIGS_PATH + File.separator + name + ".json"; + File configFile = new File(path); + + if (configFile.exists() && configFile.isFile()) { + try { + Config config = gsonForConfig().fromJson(new FileReader(configFile), Config.class); + config.setName(name); + + return config; + } catch (FileNotFoundException e) { + e.printStackTrace(); + throw(new RuntimeException("Unable to find a config file named \"" + name + "\"")); + } + } else { + throw(new RuntimeException("Unable to find a config file named \"" + name + "\"")); + } + } + + private Gson gsonForSettings() { + return new GsonBuilder() + .registerTypeAdapter(Settings.class, new SettingsSerializer()) + .registerTypeAdapter(Settings.class, new SettingsDeserializer()) + .create(); + } + + public Gson gsonForConfig() { + return new GsonBuilder() + .registerTypeAdapter(Config.class, new ConfigSerializer()) + .registerTypeAdapter(Config.class, new ConfigDeserializer()) + + .registerTypeAdapter(Configuration.class, new ConfigurationSerializer()) + .registerTypeAdapter(Configuration.class, new ConfigurationDeserializer()) + + .registerTypeAdapter(Group.class, new GroupSerializer()) + .registerTypeAdapter(Group.class, new GroupDeserializer()) + + .registerTypeAdapter(Action.class, new ActionSerializer()) + .registerTypeAdapter(Action.class, new ActionDeserializer()) + + .registerTypeAdapter(Variable.class, new VariableSerializer()) + .registerTypeAdapter(Variable.class, new VariableDeserializer()) + + .registerTypeAdapter(Presets.class, new PresetsSerializer()) + .registerTypeAdapter(Presets.class, new PresetsDeserializer()) + .create(); + } +} diff --git a/TeamCode/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/library/backend/Config.java b/TeamCode/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/library/backend/Config.java new file mode 100644 index 0000000..8054c72 --- /dev/null +++ b/TeamCode/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/library/backend/Config.java @@ -0,0 +1,44 @@ +package org.timecrafters.TimeCraftersConfigurationTool.library.backend; + +import org.timecrafters.TimeCraftersConfigurationTool.library.backend.config.Action; +import org.timecrafters.TimeCraftersConfigurationTool.library.backend.config.Configuration; +import org.timecrafters.TimeCraftersConfigurationTool.library.backend.config.Group; +import org.timecrafters.TimeCraftersConfigurationTool.library.backend.config.Presets; + +import java.util.ArrayList; +import java.util.Date; + +public class Config { + private String name; + private Configuration configuration; + private ArrayList groups; + private Presets presets; + + public Config(String name) { + this.name = name; + this.configuration = new Configuration(new Date(), new Date(), TAC.CONFIG_SPEC_VERSION, 0); + groups = new ArrayList<>(); + presets = new Presets(new ArrayList(), new ArrayList()); + } + + public Config(Configuration configuration, ArrayList groups, Presets presets) { + this.configuration = configuration; + this.groups = groups; + this.presets = presets; + } + + public String getName() { return name; } + public void setName(String name) { this.name = name; } + + public Configuration getConfiguration() { + return configuration; + } + + public Presets getPresets() { + return presets; + } + + public ArrayList getGroups() { + return groups; + } +} diff --git a/TeamCode/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/library/backend/Settings.java b/TeamCode/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/library/backend/Settings.java new file mode 100644 index 0000000..01d3a2d --- /dev/null +++ b/TeamCode/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/library/backend/Settings.java @@ -0,0 +1,12 @@ +package org.timecrafters.TimeCraftersConfigurationTool.library.backend; + +public class Settings { + public String hostname, config; + public int port; + + public Settings(String hostname, int port, String config) { + this.hostname = hostname; + this.port = port; + this.config = config; + } +} diff --git a/TeamCode/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/library/backend/TAC.java b/TeamCode/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/library/backend/TAC.java new file mode 100644 index 0000000..2490f09 --- /dev/null +++ b/TeamCode/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/library/backend/TAC.java @@ -0,0 +1,14 @@ +package org.timecrafters.TimeCraftersConfigurationTool.library.backend; + +import android.os.Environment; + +import java.io.File; + +public class TAC { + // TODO: Update filesystem handling + public static final String ROOT_PATH = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "TimeCrafters_Configuration_Tool", + CONFIGS_PATH = ROOT_PATH + File.separator + "/configs", + SETTINGS_PATH = ROOT_PATH + File.separator + "settings.json"; + + public static final int CONFIG_SPEC_VERSION = 2; +} diff --git a/TeamCode/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/library/backend/config/Action.java b/TeamCode/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/library/backend/config/Action.java new file mode 100644 index 0000000..7d3eede --- /dev/null +++ b/TeamCode/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/library/backend/config/Action.java @@ -0,0 +1,18 @@ +package org.timecrafters.TimeCraftersConfigurationTool.library.backend.config; + +import java.util.ArrayList; + +public class Action { + public String name, comment; + public boolean enabled; + private ArrayList variables; + + public Action(String name, String comment, boolean enabled, ArrayList variables) { + this.name = name; + this.comment = comment; + this.enabled = enabled; + this.variables = variables; + } + + public ArrayList getVariables() { return variables; } +} diff --git a/TeamCode/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/library/backend/config/Configuration.java b/TeamCode/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/library/backend/config/Configuration.java new file mode 100644 index 0000000..547481e --- /dev/null +++ b/TeamCode/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/library/backend/config/Configuration.java @@ -0,0 +1,20 @@ +package org.timecrafters.TimeCraftersConfigurationTool.library.backend.config; + +import java.util.Date; + +public class Configuration { + public static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss Z"; + + public Date createdAt, updatedAt; + private int specVersion; + public int revision; + + public Configuration(Date createdAt, Date updatedAt, int specVersion, int revision) { + this.createdAt = createdAt; + this.updatedAt = updatedAt; + this.specVersion = specVersion; + this.revision = revision; + } + + public int getSpecVersion() { return specVersion; } +} \ No newline at end of file diff --git a/TeamCode/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/library/backend/config/Group.java b/TeamCode/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/library/backend/config/Group.java new file mode 100644 index 0000000..d44bca4 --- /dev/null +++ b/TeamCode/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/library/backend/config/Group.java @@ -0,0 +1,27 @@ +package org.timecrafters.TimeCraftersConfigurationTool.library.backend.config; + +import java.util.ArrayList; + +public class Group { + public String name; + private ArrayList actions; + + public Group(String name, ArrayList actions) { + this.name = name; + this.actions = actions; + } + + public static boolean nameIsUnique(ArrayList groups, String name) { + for (Group group: groups) { + if (group.name.equals(name)) { + return false; + } + } + + return true; + } + + public ArrayList getActions() { + return actions; + } +} diff --git a/TeamCode/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/library/backend/config/Presets.java b/TeamCode/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/library/backend/config/Presets.java new file mode 100644 index 0000000..682dcad --- /dev/null +++ b/TeamCode/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/library/backend/config/Presets.java @@ -0,0 +1,22 @@ +package org.timecrafters.TimeCraftersConfigurationTool.library.backend.config; + + +import java.util.ArrayList; + +public class Presets { + private ArrayList groups; + private ArrayList actions; + + public Presets(ArrayList groups, ArrayList actions) { + this.groups = groups; + this.actions = actions; + } + + public ArrayList getGroups() { + return groups; + } + + public ArrayList getActions() { + return actions; + } +} \ No newline at end of file diff --git a/TeamCode/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/library/backend/config/Variable.java b/TeamCode/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/library/backend/config/Variable.java new file mode 100644 index 0000000..0193c76 --- /dev/null +++ b/TeamCode/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/library/backend/config/Variable.java @@ -0,0 +1,92 @@ +package org.timecrafters.TimeCraftersConfigurationTool.library.backend.config; + +import android.util.Log; + +import java.util.Arrays; + +public class Variable { + public String name; + private String value; + + public Variable(String name, String value) { + this.name = name; + this.value = value; + } + + public String rawValue() { + return value; + } + + public T value() { + return valueOf(value); + } + + public void setValue(String value) { + this.value = value; + } + + @SuppressWarnings("unchecked") + static public T valueOf(String value) { + String[] split = value.split("x", 2); +// Log.d("Variable", "valueOf split: " + Arrays.toString(split)); + + switch (split[0]) { + case "B": { + return (T) Boolean.valueOf(split[(split.length-1)]); + } + case "D": { + return (T) Double.valueOf(split[(split.length-1)]); + } + case "F": { + return (T) Float.valueOf(split[(split.length-1)]); + } + case "I": { + return (T) Integer.valueOf(split[(split.length-1)]); + } + case "L": { + return (T) Long.valueOf(split[(split.length-1)]); + } + case "S": { + String string = ""; + int i = 0; + for(String str : split) { + if (i == 0) { i++; continue; } + + string += str; + } + return (T) string; + } + default: { + return null; + } + } + } + + static public String typeOf(String value) { + String[] split = value.split("x"); + + switch (split[0]) { + case "B": { + return "Boolean"; + } + case "D": { + return "Double"; + } + case "F": { + return "Float"; + } + case "I": { + return "Integer"; + } + case "L": { + return "Long"; + } + case "S": { + return "String"; + } + default: { + return "=!UNKNOWN!="; + } + } + } +} \ No newline at end of file diff --git a/TeamCode/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/library/serializers/ActionDeserializer.java b/TeamCode/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/library/serializers/ActionDeserializer.java new file mode 100644 index 0000000..d2c177e --- /dev/null +++ b/TeamCode/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/library/serializers/ActionDeserializer.java @@ -0,0 +1,33 @@ +package org.timecrafters.TimeCraftersConfigurationTool.library.serializers; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; + +import org.timecrafters.TimeCraftersConfigurationTool.library.backend.config.Action; +import org.timecrafters.TimeCraftersConfigurationTool.library.backend.config.Group; +import org.timecrafters.TimeCraftersConfigurationTool.library.backend.config.Variable; + +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class ActionDeserializer implements JsonDeserializer { + @Override + public Action deserialize(JsonElement json, Type type, JsonDeserializationContext context) throws JsonParseException { + JsonObject jsonObject = json.getAsJsonObject(); + + final String name = jsonObject.get("name").getAsString(); + final String comment = jsonObject.get("comment").getAsString(); + final boolean enabled = jsonObject.get("enabled").getAsBoolean(); + Variable[] variablesArray = context.deserialize(jsonObject.get("variables"), Variable[].class); + + List variablesList = Arrays.asList(variablesArray); + ArrayList variables = new ArrayList<>(variablesList); + + return new Action(name, comment, enabled, variables); + } +} \ No newline at end of file diff --git a/TeamCode/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/library/serializers/ActionSerializer.java b/TeamCode/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/library/serializers/ActionSerializer.java new file mode 100644 index 0000000..5f8e6ed --- /dev/null +++ b/TeamCode/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/library/serializers/ActionSerializer.java @@ -0,0 +1,27 @@ +package org.timecrafters.TimeCraftersConfigurationTool.library.serializers; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; + +import org.timecrafters.TimeCraftersConfigurationTool.library.backend.config.Action; +import org.timecrafters.TimeCraftersConfigurationTool.library.backend.config.Group; +import org.timecrafters.TimeCraftersConfigurationTool.library.backend.config.Variable; + +import java.lang.reflect.Type; + +public class ActionSerializer implements JsonSerializer { + @Override + public JsonElement serialize(Action action, Type type, JsonSerializationContext context) { + JsonObject container = new JsonObject(); + + container.add("name", new JsonPrimitive(action.name)); + container.add("comment", new JsonPrimitive(action.comment)); + container.add("enabled", new JsonPrimitive(action.enabled)); + container.add("variables", context.serialize(action.getVariables().toArray(), Variable[].class)); + + return container; + } +} \ No newline at end of file diff --git a/TeamCode/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/library/serializers/ConfigDeserializer.java b/TeamCode/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/library/serializers/ConfigDeserializer.java new file mode 100644 index 0000000..6766346 --- /dev/null +++ b/TeamCode/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/library/serializers/ConfigDeserializer.java @@ -0,0 +1,34 @@ +package org.timecrafters.TimeCraftersConfigurationTool.library.serializers; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; + +import org.timecrafters.TimeCraftersConfigurationTool.library.backend.Config; +import org.timecrafters.TimeCraftersConfigurationTool.library.backend.config.Configuration; +import org.timecrafters.TimeCraftersConfigurationTool.library.backend.config.Group; +import org.timecrafters.TimeCraftersConfigurationTool.library.backend.config.Presets; + +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class ConfigDeserializer implements JsonDeserializer { + @Override + public Config deserialize(JsonElement json, Type type, JsonDeserializationContext context) throws JsonParseException { + JsonObject jsonObject = json.getAsJsonObject(); + JsonObject data = jsonObject.get("data").getAsJsonObject(); + + Configuration configuration = context.deserialize(jsonObject.get("config"), Configuration.class); + Group[] groupsArray = context.deserialize(data.get("groups"), Group[].class); + List groupsList = Arrays.asList(groupsArray); + ArrayList groups = new ArrayList<>(groupsList); + + Presets presets = context.deserialize(data.get("presets"), Presets.class); + + return new Config(configuration, groups, presets); + } +} \ No newline at end of file diff --git a/TeamCode/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/library/serializers/ConfigSerializer.java b/TeamCode/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/library/serializers/ConfigSerializer.java new file mode 100644 index 0000000..21d4778 --- /dev/null +++ b/TeamCode/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/library/serializers/ConfigSerializer.java @@ -0,0 +1,32 @@ +package org.timecrafters.TimeCraftersConfigurationTool.library.serializers; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; + +import org.timecrafters.TimeCraftersConfigurationTool.library.backend.Config; +import org.timecrafters.TimeCraftersConfigurationTool.library.backend.config.Action; +import org.timecrafters.TimeCraftersConfigurationTool.library.backend.config.Configuration; +import org.timecrafters.TimeCraftersConfigurationTool.library.backend.config.Group; + +import java.lang.reflect.Type; + +public class ConfigSerializer implements JsonSerializer { + @Override + public JsonElement serialize(Config config, Type type, JsonSerializationContext context) { + JsonObject container = new JsonObject(); + JsonObject result = new JsonObject(); + JsonObject presets = new JsonObject(); + container.add("config", context.serialize(config.getConfiguration(), Configuration.class)); + result.add("groups", context.serialize(config.getGroups().toArray(), Group[].class)); + + presets.add("groups", context.serialize(config.getPresets().getGroups().toArray(), Group[].class)); + presets.add("actions", context.serialize(config.getPresets().getActions().toArray(), Action[].class)); + + result.add("presets", presets); + container.add("data", result); + + return container; + } +} \ No newline at end of file diff --git a/TeamCode/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/library/serializers/ConfigurationDeserializer.java b/TeamCode/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/library/serializers/ConfigurationDeserializer.java new file mode 100644 index 0000000..590e1a5 --- /dev/null +++ b/TeamCode/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/library/serializers/ConfigurationDeserializer.java @@ -0,0 +1,36 @@ +package org.timecrafters.TimeCraftersConfigurationTool.library.serializers; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; + +import org.timecrafters.TimeCraftersConfigurationTool.library.backend.config.Configuration; + +import java.lang.reflect.Type; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; + +public class ConfigurationDeserializer implements JsonDeserializer { + @Override + public Configuration deserialize(JsonElement json, Type type, JsonDeserializationContext context) throws JsonParseException { + JsonObject config = json.getAsJsonObject(); + + SimpleDateFormat dateFormat = new SimpleDateFormat(Configuration.DATE_FORMAT); + Date createdAt = new Date(); + Date updatedAt = new Date(); + try { + createdAt = dateFormat.parse(config.get("created_at").getAsString()); + updatedAt = dateFormat.parse(config.get("updated_at").getAsString()); + } catch (ParseException e) { + e.printStackTrace(); + } + + final int spec_version = config.get("spec_version").getAsInt(); + final int revision = config.get("revision").getAsInt(); + + return new Configuration(createdAt, updatedAt, spec_version, revision); + } +} diff --git a/TeamCode/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/library/serializers/ConfigurationSerializer.java b/TeamCode/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/library/serializers/ConfigurationSerializer.java new file mode 100644 index 0000000..8b69916 --- /dev/null +++ b/TeamCode/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/library/serializers/ConfigurationSerializer.java @@ -0,0 +1,29 @@ +package org.timecrafters.TimeCraftersConfigurationTool.library.serializers; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; + +import org.timecrafters.TimeCraftersConfigurationTool.library.backend.config.Configuration; +import org.timecrafters.TimeCraftersConfigurationTool.library.backend.config.Variable; + +import java.lang.reflect.Type; +import java.text.SimpleDateFormat; + +public class ConfigurationSerializer implements JsonSerializer { + @Override + public JsonElement serialize(Configuration configuration, Type type, JsonSerializationContext context) { + JsonObject container = new JsonObject(); + + SimpleDateFormat dateFormat = new SimpleDateFormat(Configuration.DATE_FORMAT); + + container.add("created_at", new JsonPrimitive(dateFormat.format(configuration.createdAt))); + container.add("updated_at", new JsonPrimitive(dateFormat.format(configuration.updatedAt))); + container.add("spec_version", new JsonPrimitive(configuration.getSpecVersion())); + container.add("revision", new JsonPrimitive(configuration.revision)); + + return container; + } +} \ No newline at end of file diff --git a/TeamCode/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/library/serializers/GroupDeserializer.java b/TeamCode/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/library/serializers/GroupDeserializer.java new file mode 100644 index 0000000..01086bc --- /dev/null +++ b/TeamCode/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/library/serializers/GroupDeserializer.java @@ -0,0 +1,33 @@ +package org.timecrafters.TimeCraftersConfigurationTool.library.serializers; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; + +import org.timecrafters.TimeCraftersConfigurationTool.library.backend.Config; +import org.timecrafters.TimeCraftersConfigurationTool.library.backend.config.Action; +import org.timecrafters.TimeCraftersConfigurationTool.library.backend.config.Configuration; +import org.timecrafters.TimeCraftersConfigurationTool.library.backend.config.Group; +import org.timecrafters.TimeCraftersConfigurationTool.library.backend.config.Presets; + +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class GroupDeserializer implements JsonDeserializer { + @Override + public Group deserialize(JsonElement json, Type type, JsonDeserializationContext context) throws JsonParseException { + JsonObject jsonObject = json.getAsJsonObject(); + + final String name = jsonObject.get("name").getAsString(); + Action[] actionsArray = context.deserialize(jsonObject.get("actions"), Action[].class); + + List actionsList = Arrays.asList(actionsArray); + ArrayList actions = new ArrayList<>(actionsList); + + return new Group(name, actions); + } +} \ No newline at end of file diff --git a/TeamCode/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/library/serializers/GroupSerializer.java b/TeamCode/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/library/serializers/GroupSerializer.java new file mode 100644 index 0000000..3d8a5bc --- /dev/null +++ b/TeamCode/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/library/serializers/GroupSerializer.java @@ -0,0 +1,26 @@ +package org.timecrafters.TimeCraftersConfigurationTool.library.serializers; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; + +import org.timecrafters.TimeCraftersConfigurationTool.library.backend.Config; +import org.timecrafters.TimeCraftersConfigurationTool.library.backend.config.Action; +import org.timecrafters.TimeCraftersConfigurationTool.library.backend.config.Configuration; +import org.timecrafters.TimeCraftersConfigurationTool.library.backend.config.Group; + +import java.lang.reflect.Type; + +public class GroupSerializer implements JsonSerializer { + @Override + public JsonElement serialize(Group group, Type type, JsonSerializationContext context) { + JsonObject container = new JsonObject(); + + container.add("name", new JsonPrimitive(group.name)); + container.add("actions", context.serialize(group.getActions().toArray(), Action[].class)); + + return container; + } +} \ No newline at end of file diff --git a/TeamCode/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/library/serializers/PresetsDeserializer.java b/TeamCode/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/library/serializers/PresetsDeserializer.java new file mode 100644 index 0000000..509db3b --- /dev/null +++ b/TeamCode/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/library/serializers/PresetsDeserializer.java @@ -0,0 +1,33 @@ +package org.timecrafters.TimeCraftersConfigurationTool.library.serializers; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; + +import org.timecrafters.TimeCraftersConfigurationTool.library.backend.config.Action; +import org.timecrafters.TimeCraftersConfigurationTool.library.backend.config.Group; +import org.timecrafters.TimeCraftersConfigurationTool.library.backend.config.Presets; + +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class PresetsDeserializer implements JsonDeserializer { + @Override + public Presets deserialize(JsonElement json, Type type, JsonDeserializationContext context) throws JsonParseException { + JsonObject jsonObject = json.getAsJsonObject(); + + Group[] GroupsArray = context.deserialize(jsonObject.get("groups"), Group[].class); + Action[] actionsArray = context.deserialize(jsonObject.get("actions"), Action[].class); + + List groupsList = Arrays.asList(GroupsArray); + ArrayList groups = new ArrayList<>(groupsList); + List actionsList = Arrays.asList(actionsArray); + ArrayList actions = new ArrayList<>(actionsList); + + return new Presets(groups, actions); + } +} \ No newline at end of file diff --git a/TeamCode/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/library/serializers/PresetsSerializer.java b/TeamCode/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/library/serializers/PresetsSerializer.java new file mode 100644 index 0000000..7be89e5 --- /dev/null +++ b/TeamCode/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/library/serializers/PresetsSerializer.java @@ -0,0 +1,25 @@ +package org.timecrafters.TimeCraftersConfigurationTool.library.serializers; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; + +import org.timecrafters.TimeCraftersConfigurationTool.library.backend.config.Action; +import org.timecrafters.TimeCraftersConfigurationTool.library.backend.config.Group; +import org.timecrafters.TimeCraftersConfigurationTool.library.backend.config.Presets; + +import java.lang.reflect.Type; + +public class PresetsSerializer implements JsonSerializer { + @Override + public JsonElement serialize(Presets presets, Type type, JsonSerializationContext context) { + JsonObject container = new JsonObject(); + + container.add("groups", context.serialize(presets.getGroups().toArray(), Group[].class)); + container.add("actions", context.serialize(presets.getActions().toArray(), Action[].class)); + + return container; + } +} \ No newline at end of file diff --git a/TeamCode/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/library/serializers/SettingsDeserializer.java b/TeamCode/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/library/serializers/SettingsDeserializer.java new file mode 100644 index 0000000..e08e84e --- /dev/null +++ b/TeamCode/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/library/serializers/SettingsDeserializer.java @@ -0,0 +1,25 @@ +package org.timecrafters.TimeCraftersConfigurationTool.library.serializers; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; + +import org.timecrafters.TimeCraftersConfigurationTool.library.backend.Settings; + +import java.lang.reflect.Type; + +public class SettingsDeserializer implements JsonDeserializer { + @Override + public Settings deserialize(JsonElement json, Type type, JsonDeserializationContext context) throws JsonParseException { + JsonObject jsonObject = json.getAsJsonObject(); + JsonObject data = jsonObject.get("data").getAsJsonObject(); + + return new Settings( + data.get("hostname").getAsString(), + data.get("port").getAsInt(), + data.get("config").getAsString() + ); + } +} diff --git a/TeamCode/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/library/serializers/SettingsSerializer.java b/TeamCode/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/library/serializers/SettingsSerializer.java new file mode 100644 index 0000000..bd81298 --- /dev/null +++ b/TeamCode/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/library/serializers/SettingsSerializer.java @@ -0,0 +1,30 @@ +package org.timecrafters.TimeCraftersConfigurationTool.library.serializers; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonPrimitive; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; + +import org.timecrafters.TimeCraftersConfigurationTool.library.backend.Settings; + +import java.lang.reflect.Type; + +public class SettingsSerializer implements JsonSerializer { + @Override + public JsonElement serialize(Settings settings, Type type, JsonSerializationContext context) { + JsonObject container = new JsonObject(); + JsonObject result = new JsonObject(); + result.add("hostname", new JsonPrimitive(settings.hostname)); + result.add("port", new JsonPrimitive(settings.port)); + result.add("config", new JsonPrimitive(settings.config)); + + container.add("data", result); + + return container; + } +} + diff --git a/TeamCode/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/library/serializers/VariableDeserializer.java b/TeamCode/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/library/serializers/VariableDeserializer.java new file mode 100644 index 0000000..8251c45 --- /dev/null +++ b/TeamCode/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/library/serializers/VariableDeserializer.java @@ -0,0 +1,28 @@ +package org.timecrafters.TimeCraftersConfigurationTool.library.serializers; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; + +import org.timecrafters.TimeCraftersConfigurationTool.library.backend.config.Action; +import org.timecrafters.TimeCraftersConfigurationTool.library.backend.config.Group; +import org.timecrafters.TimeCraftersConfigurationTool.library.backend.config.Variable; + +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class VariableDeserializer implements JsonDeserializer { + @Override + public Variable deserialize(JsonElement json, Type type, JsonDeserializationContext context) throws JsonParseException { + JsonObject jsonObject = json.getAsJsonObject(); + + final String name = jsonObject.get("name").getAsString(); + final String value = jsonObject.get("value").getAsString(); + + return new Variable(name, value); + } +} \ No newline at end of file diff --git a/TeamCode/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/library/serializers/VariableSerializer.java b/TeamCode/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/library/serializers/VariableSerializer.java new file mode 100644 index 0000000..c5adf49 --- /dev/null +++ b/TeamCode/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/library/serializers/VariableSerializer.java @@ -0,0 +1,23 @@ +package org.timecrafters.TimeCraftersConfigurationTool.library.serializers; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; + +import org.timecrafters.TimeCraftersConfigurationTool.library.backend.config.Variable; + +import java.lang.reflect.Type; + +public class VariableSerializer implements JsonSerializer { + @Override + public JsonElement serialize(Variable variable, Type type, JsonSerializationContext context) { + JsonObject container = new JsonObject(); + + container.add("name", new JsonPrimitive(variable.name)); + container.add("value", new JsonPrimitive(variable.rawValue())); + + return container; + } +} \ No newline at end of file diff --git a/TeamCode/src/main/java/org/timecrafters/tacnet_management/HaltTACNETService.java b/TeamCode/src/main/java/org/timecrafters/tacnet_management/HaltTACNETService.java new file mode 100644 index 0000000..ca0b34a --- /dev/null +++ b/TeamCode/src/main/java/org/timecrafters/tacnet_management/HaltTACNETService.java @@ -0,0 +1,26 @@ +package org.timecrafters.tacnet_management; + +import android.content.Context; +import android.content.Intent; + +import com.qualcomm.robotcore.eventloop.opmode.OpMode; +import com.qualcomm.robotcore.eventloop.opmode.TeleOp; + +import org.firstinspires.ftc.robotcontroller.internal.FtcRobotControllerActivity; + +@TeleOp(name = "Halt TACNET Service", group = "TACNET") +public class HaltTACNETService extends OpMode { + @Override + public void init() { + Context appContext = FtcRobotControllerActivity.getAppActivity().getApplicationContext(); + Intent tacnetIntent = new Intent("org.timecrafters.TimeCraftersConfigurationTool.tacnet.ACTION_START_SERVER"); + tacnetIntent.setPackage("org.timecrafters.TimeCraftersConfigurationTool"); + + appContext.stopService(tacnetIntent); + } + + @Override + public void loop() { + stop(); + } +} diff --git a/TeamCode/src/main/java/org/timecrafters/tacnet_management/StartTACNETService.java b/TeamCode/src/main/java/org/timecrafters/tacnet_management/StartTACNETService.java new file mode 100644 index 0000000..db0cef7 --- /dev/null +++ b/TeamCode/src/main/java/org/timecrafters/tacnet_management/StartTACNETService.java @@ -0,0 +1,31 @@ +package org.timecrafters.tacnet_management; + +import android.content.Context; +import android.content.Intent; +import android.os.Build; + +import com.qualcomm.robotcore.eventloop.opmode.OpMode; +import com.qualcomm.robotcore.eventloop.opmode.TeleOp; + +import org.firstinspires.ftc.robotcontroller.internal.FtcRobotControllerActivity; + +@TeleOp(name = "Start TACNET Service", group = "TACNET") +public class StartTACNETService extends OpMode { + @Override + public void init() { + Context appContext = FtcRobotControllerActivity.getAppActivity().getApplicationContext(); + Intent tacnetIntent = new Intent("org.timecrafters.TimeCraftersConfigurationTool.tacnet.ACTION_START_SERVER"); + tacnetIntent.setPackage("org.timecrafters.TimeCraftersConfigurationTool"); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + appContext.startForegroundService(tacnetIntent); + } else { + appContext.startService(tacnetIntent); + } + } + + @Override + public void loop() { + stop(); + } +}