Clean slate with TAC, CyberarmEngineV2, and stubbed structure.

This commit is contained in:
2023-09-14 20:21:28 -05:00
parent f3a5a54f67
commit f1cbb0b172
38 changed files with 1916 additions and 131 deletions

View File

@@ -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()) {

View File

@@ -26,4 +26,5 @@ android {
dependencies {
implementation project(':FtcRobotController')
annotationProcessor files('lib/OpModeAnnotationProcessor.jar')
implementation 'org.ftclib.ftclib:core:2.1.1'
}

View File

@@ -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<I2cDeviceSynch> {
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();
}
}
}

View File

@@ -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<I2cDeviceSynch> 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() {
}
}

View File

@@ -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<CyberarmState> cyberarmStates = new CopyOnWriteArrayList<>();
// Array to Hold Tasks
final private CopyOnWriteArrayList<CyberarmState> backgroundTasks = new CopyOnWriteArrayList<>();
// HashMap to store data for States and Tasks
final private ConcurrentHashMap<String, Object> 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:
* <pre>
* {@code
* public void setup() {
* addState(new TestState());
* addState(new AnotherState(100, 500));
* }
* }
* </pre>
*/
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> 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> 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> 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);
}
}
}
}

View File

@@ -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<CyberarmState> 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<CyberarmState> 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, "=", " ");
}
}

View File

@@ -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<String, Boolean> 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();
}
}
}
}

View File

@@ -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() {
}
}

View File

@@ -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""

View File

@@ -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);
}
}

View File

@@ -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));
}
}

View File

@@ -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() {
}
}

View File

@@ -0,0 +1,5 @@
package org.timecrafters.Library;
public abstract class Robot {
public abstract void setup();
}

View File

@@ -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();
}
}

View File

@@ -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<Group> 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<Group>(), new ArrayList<Action>());
}
public Config(Configuration configuration, ArrayList<Group> 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<Group> getGroups() {
return groups;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}

View File

@@ -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<Variable> variables;
public Action(String name, String comment, boolean enabled, ArrayList<Variable> variables) {
this.name = name;
this.comment = comment;
this.enabled = enabled;
this.variables = variables;
}
public ArrayList<Variable> getVariables() { return variables; }
}

View File

@@ -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; }
}

View File

@@ -0,0 +1,27 @@
package org.timecrafters.TimeCraftersConfigurationTool.library.backend.config;
import java.util.ArrayList;
public class Group {
public String name;
private ArrayList<Action> actions;
public Group(String name, ArrayList<Action> actions) {
this.name = name;
this.actions = actions;
}
public static boolean nameIsUnique(ArrayList<Group> groups, String name) {
for (Group group: groups) {
if (group.name.equals(name)) {
return false;
}
}
return true;
}
public ArrayList<Action> getActions() {
return actions;
}
}

View File

@@ -0,0 +1,22 @@
package org.timecrafters.TimeCraftersConfigurationTool.library.backend.config;
import java.util.ArrayList;
public class Presets {
private ArrayList<Group> groups;
private ArrayList<Action> actions;
public Presets(ArrayList<Group> groups, ArrayList<Action> actions) {
this.groups = groups;
this.actions = actions;
}
public ArrayList<Group> getGroups() {
return groups;
}
public ArrayList<Action> getActions() {
return actions;
}
}

View File

@@ -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> T value() {
return valueOf(value);
}
public void setValue(String value) {
this.value = value;
}
@SuppressWarnings("unchecked")
static public <T> 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!=";
}
}
}
}

View File

@@ -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<Action> {
@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<Variable> variablesList = Arrays.asList(variablesArray);
ArrayList<Variable> variables = new ArrayList<>(variablesList);
return new Action(name, comment, enabled, variables);
}
}

View File

@@ -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<Action> {
@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;
}
}

View File

@@ -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<Config> {
@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<Group> groupsList = Arrays.asList(groupsArray);
ArrayList<Group> groups = new ArrayList<>(groupsList);
Presets presets = context.deserialize(data.get("presets"), Presets.class);
return new Config(configuration, groups, presets);
}
}

View File

@@ -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<Config> {
@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;
}
}

View File

@@ -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<Configuration> {
@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);
}
}

View File

@@ -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<Configuration> {
@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;
}
}

View File

@@ -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<Group> {
@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<Action> actionsList = Arrays.asList(actionsArray);
ArrayList<Action> actions = new ArrayList<>(actionsList);
return new Group(name, actions);
}
}

View File

@@ -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<Group> {
@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;
}
}

View File

@@ -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<Presets> {
@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<Group> groupsList = Arrays.asList(GroupsArray);
ArrayList<Group> groups = new ArrayList<>(groupsList);
List<Action> actionsList = Arrays.asList(actionsArray);
ArrayList<Action> actions = new ArrayList<>(actionsList);
return new Presets(groups, actions);
}
}

View File

@@ -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<Presets> {
@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;
}
}

View File

@@ -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<Settings> {
@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()
);
}
}

View File

@@ -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<Settings> {
@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;
}
}

View File

@@ -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<Variable> {
@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);
}
}

View File

@@ -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<Variable> {
@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;
}
}

View File

@@ -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();
}
}

View File

@@ -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();
}
}