From 6ec6d38f61fe70a56cc455611f1847eee89ed72a Mon Sep 17 00:00:00 2001 From: Dragin Date: Thu, 22 Aug 2024 18:31:04 -0500 Subject: [PATCH 1/5] Add logic for encoder resets once limit switches are hit --- .../hardware/revrobotics/Spark.java | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/src/main/java/org/lasarobotics/hardware/revrobotics/Spark.java b/src/main/java/org/lasarobotics/hardware/revrobotics/Spark.java index 2432b67f..cda49d37 100644 --- a/src/main/java/org/lasarobotics/hardware/revrobotics/Spark.java +++ b/src/main/java/org/lasarobotics/hardware/revrobotics/Spark.java @@ -153,6 +153,12 @@ public static class SparkInputs { private FeedbackSensor m_feedbackSensor; private SparkLimitSwitch.Type m_limitSwitchType = SparkLimitSwitch.Type.kNormallyOpen; private RelativeEncoder m_encoder; + private boolean forwardLimitSwitchShouldReset = false; + private boolean reverseLimitSwitchShouldReset = false; + private double forwardLimitSwitchResetValue = 1; + private double reverseLimitSwitchResetValue = 0; + private boolean forwardLimitSwitchJustHit = false; + private boolean reverseLimitSwitchJustHit = false; private volatile SparkInputsAutoLogged m_inputs; @@ -492,6 +498,8 @@ private void updateInputs() { m_inputs.absoluteEncoderVelocity = getAbsoluteEncoderVelocity(); m_inputs.forwardLimitSwitch = getForwardLimitSwitch().isPressed(); m_inputs.reverseLimitSwitch = getReverseLimitSwitch().isPressed(); + if (m_inputs.forwardLimitSwitch) forwardLimitSwitchJustHit = true; + if (m_inputs.reverseLimitSwitch) reverseLimitSwitchJustHit = true; if (!getMotorType().equals(MotorType.kBrushed)) { m_inputs.encoderPosition = getEncoderPosition(); @@ -500,6 +508,21 @@ private void updateInputs() { } } + /** + * Handle limit switches + */ + private void handleLimitSwitches() { + if (forwardLimitSwitchShouldReset) { + if (forwardLimitSwitchJustHit) resetEncoder(forwardLimitSwitchResetValue); + } + if (reverseLimitSwitchShouldReset) { + if (reverseLimitSwitchJustHit) resetEncoder(reverseLimitSwitchResetValue); + } + + if (forwardLimitSwitchJustHit) forwardLimitSwitchJustHit = false; + if (reverseLimitSwitchJustHit) reverseLimitSwitchJustHit = false; + } + /** * Handle smooth motion */ @@ -524,6 +547,7 @@ private void handleSmoothMotion() { protected void periodic() { Logger.processInputs(m_id.name, m_inputs); + handleLimitSwitches(); handleSmoothMotion(); Logger.recordOutput(m_id.name + CURRENT_LOG_ENTRY, getOutputCurrent()); @@ -963,6 +987,57 @@ public REVLibError resetEncoder() { return resetEncoder(0.0); } + /** + * Sets whether the encoder should be reset once the forward limit switch is hit + * The value to set it to can be changed with setForwardLimitSwitchResetValue(double value) + * @param value Whether the encoder should be reset + */ + public void setForwardLimitSwitchShouldReset(boolean value) { + forwardLimitSwitchShouldReset = value; + } + + /** + * Change what value the encoder should be reset to once the forward limit switch is hit + * Only matters if forwardLimitSwitchShouldReset is true, which has its own get and set methods + * @param value Desired encoder value + */ + public void setForwardLimitSwitchResetValue(double value) { + forwardLimitSwitchResetValue = value; + } + /** + * @return What value the encoder will be reset to once the forward limit switch is hit + * Only matters if forwardLimitSwitchShouldReset is true, which has its own get and set methods + */ + public double getForwardLimitSwitchResetValue() { + return forwardLimitSwitchResetValue; + } + + + /** + * Sets whether the encoder should be reset once the reverse limit switch is hit + * The value to set it to can be changed with setReverseLimitSwitchResetValue(double value) + * @param value Whether the encoder should be reset + */ + public void setReverseLimitSwitchShouldReset(boolean value) { + reverseLimitSwitchShouldReset = value; + } + + /** + * Change what value the encoder should be reset to once the reverse limit switch is hit + * Only matters if reverseLimitSwitchShouldReset is true, which has its own get and set methods + * @param value Desired encoder value + */ + public void setReverseLimitSwitchResetValue(double value) { + reverseLimitSwitchResetValue = value; + } + /** + * @return What value the encoder will be reset to once the reverse limit switch is hit + * Only matters if reverseLimitSwitchShouldReset is true, which has its own get and set methods + */ + public double getReverseLimitSwitchResetValue() { + return reverseLimitSwitchResetValue; + } + /** * Disable forward limit switch * @return {@link REVLibError#kOk} if successful From 1818b7634b516d420060c92f511b2263e337d357 Mon Sep 17 00:00:00 2001 From: Dragin Date: Thu, 29 Aug 2024 17:11:54 -0500 Subject: [PATCH 2/5] Prepend m_ to member variables because conventions or something --- .../hardware/revrobotics/Spark.java | 52 +++++++++---------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/src/main/java/org/lasarobotics/hardware/revrobotics/Spark.java b/src/main/java/org/lasarobotics/hardware/revrobotics/Spark.java index cda49d37..f663fddb 100644 --- a/src/main/java/org/lasarobotics/hardware/revrobotics/Spark.java +++ b/src/main/java/org/lasarobotics/hardware/revrobotics/Spark.java @@ -153,12 +153,12 @@ public static class SparkInputs { private FeedbackSensor m_feedbackSensor; private SparkLimitSwitch.Type m_limitSwitchType = SparkLimitSwitch.Type.kNormallyOpen; private RelativeEncoder m_encoder; - private boolean forwardLimitSwitchShouldReset = false; - private boolean reverseLimitSwitchShouldReset = false; - private double forwardLimitSwitchResetValue = 1; - private double reverseLimitSwitchResetValue = 0; - private boolean forwardLimitSwitchJustHit = false; - private boolean reverseLimitSwitchJustHit = false; + private boolean m_forwardLimitSwitchShouldReset = false; + private boolean m_reverseLimitSwitchShouldReset = false; + private double m_forwardLimitSwitchResetValue = 1; + private double m_reverseLimitSwitchResetValue = 0; + private boolean m_forwardLimitSwitchJustHit = false; + private boolean m_reverseLimitSwitchJustHit = false; private volatile SparkInputsAutoLogged m_inputs; @@ -498,8 +498,8 @@ private void updateInputs() { m_inputs.absoluteEncoderVelocity = getAbsoluteEncoderVelocity(); m_inputs.forwardLimitSwitch = getForwardLimitSwitch().isPressed(); m_inputs.reverseLimitSwitch = getReverseLimitSwitch().isPressed(); - if (m_inputs.forwardLimitSwitch) forwardLimitSwitchJustHit = true; - if (m_inputs.reverseLimitSwitch) reverseLimitSwitchJustHit = true; + if (m_inputs.forwardLimitSwitch) m_forwardLimitSwitchJustHit = true; + if (m_inputs.reverseLimitSwitch) m_reverseLimitSwitchJustHit = true; if (!getMotorType().equals(MotorType.kBrushed)) { m_inputs.encoderPosition = getEncoderPosition(); @@ -512,15 +512,15 @@ private void updateInputs() { * Handle limit switches */ private void handleLimitSwitches() { - if (forwardLimitSwitchShouldReset) { - if (forwardLimitSwitchJustHit) resetEncoder(forwardLimitSwitchResetValue); + if (m_forwardLimitSwitchShouldReset) { + if (m_forwardLimitSwitchJustHit) resetEncoder(m_forwardLimitSwitchResetValue); } - if (reverseLimitSwitchShouldReset) { - if (reverseLimitSwitchJustHit) resetEncoder(reverseLimitSwitchResetValue); + if (m_reverseLimitSwitchShouldReset) { + if (m_reverseLimitSwitchJustHit) resetEncoder(m_reverseLimitSwitchResetValue); } - if (forwardLimitSwitchJustHit) forwardLimitSwitchJustHit = false; - if (reverseLimitSwitchJustHit) reverseLimitSwitchJustHit = false; + if (m_forwardLimitSwitchJustHit) m_forwardLimitSwitchJustHit = false; + if (m_reverseLimitSwitchJustHit) m_reverseLimitSwitchJustHit = false; } /** @@ -992,8 +992,8 @@ public REVLibError resetEncoder() { * The value to set it to can be changed with setForwardLimitSwitchResetValue(double value) * @param value Whether the encoder should be reset */ - public void setForwardLimitSwitchShouldReset(boolean value) { - forwardLimitSwitchShouldReset = value; + public void setM_forwardLimitSwitchShouldReset(boolean value) { + m_forwardLimitSwitchShouldReset = value; } /** @@ -1001,15 +1001,15 @@ public void setForwardLimitSwitchShouldReset(boolean value) { * Only matters if forwardLimitSwitchShouldReset is true, which has its own get and set methods * @param value Desired encoder value */ - public void setForwardLimitSwitchResetValue(double value) { - forwardLimitSwitchResetValue = value; + public void setM_forwardLimitSwitchResetValue(double value) { + m_forwardLimitSwitchResetValue = value; } /** * @return What value the encoder will be reset to once the forward limit switch is hit * Only matters if forwardLimitSwitchShouldReset is true, which has its own get and set methods */ - public double getForwardLimitSwitchResetValue() { - return forwardLimitSwitchResetValue; + public double getM_forwardLimitSwitchResetValue() { + return m_forwardLimitSwitchResetValue; } @@ -1018,8 +1018,8 @@ public double getForwardLimitSwitchResetValue() { * The value to set it to can be changed with setReverseLimitSwitchResetValue(double value) * @param value Whether the encoder should be reset */ - public void setReverseLimitSwitchShouldReset(boolean value) { - reverseLimitSwitchShouldReset = value; + public void setM_reverseLimitSwitchShouldReset(boolean value) { + m_reverseLimitSwitchShouldReset = value; } /** @@ -1027,15 +1027,15 @@ public void setReverseLimitSwitchShouldReset(boolean value) { * Only matters if reverseLimitSwitchShouldReset is true, which has its own get and set methods * @param value Desired encoder value */ - public void setReverseLimitSwitchResetValue(double value) { - reverseLimitSwitchResetValue = value; + public void setM_reverseLimitSwitchResetValue(double value) { + m_reverseLimitSwitchResetValue = value; } /** * @return What value the encoder will be reset to once the reverse limit switch is hit * Only matters if reverseLimitSwitchShouldReset is true, which has its own get and set methods */ - public double getReverseLimitSwitchResetValue() { - return reverseLimitSwitchResetValue; + public double getM_reverseLimitSwitchResetValue() { + return m_reverseLimitSwitchResetValue; } /** From b43a3c8dd3a5fab17179a7398f7338547992c704 Mon Sep 17 00:00:00 2001 From: Dragin Date: Thu, 29 Aug 2024 17:31:47 -0500 Subject: [PATCH 3/5] Update to use Trigger + fixed method names --- .../hardware/revrobotics/Spark.java | 39 +++++++++---------- 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/src/main/java/org/lasarobotics/hardware/revrobotics/Spark.java b/src/main/java/org/lasarobotics/hardware/revrobotics/Spark.java index f663fddb..f5870150 100644 --- a/src/main/java/org/lasarobotics/hardware/revrobotics/Spark.java +++ b/src/main/java/org/lasarobotics/hardware/revrobotics/Spark.java @@ -47,6 +47,8 @@ import edu.wpi.first.wpilibj.Notifier; import edu.wpi.first.wpilibj.RobotBase; import edu.wpi.first.wpilibj.Timer; +import edu.wpi.first.wpilibj2.command.Commands; +import edu.wpi.first.wpilibj2.command.button.Trigger; /** REV Spark */ public class Spark extends LoggableHardware { @@ -157,8 +159,6 @@ public static class SparkInputs { private boolean m_reverseLimitSwitchShouldReset = false; private double m_forwardLimitSwitchResetValue = 1; private double m_reverseLimitSwitchResetValue = 0; - private boolean m_forwardLimitSwitchJustHit = false; - private boolean m_reverseLimitSwitchJustHit = false; private volatile SparkInputsAutoLogged m_inputs; @@ -184,6 +184,8 @@ public Spark(ID id, MotorKind kind, SparkLimitSwitch.Type limitSwitchType, Measu this.m_isSmoothMotionEnabled = false; this.m_limitSwitchType = limitSwitchType; + initLimitSwitcheResets(); + // Set CAN timeout m_spark.setCANTimeout(CAN_TIMEOUT_MS); @@ -498,8 +500,6 @@ private void updateInputs() { m_inputs.absoluteEncoderVelocity = getAbsoluteEncoderVelocity(); m_inputs.forwardLimitSwitch = getForwardLimitSwitch().isPressed(); m_inputs.reverseLimitSwitch = getReverseLimitSwitch().isPressed(); - if (m_inputs.forwardLimitSwitch) m_forwardLimitSwitchJustHit = true; - if (m_inputs.reverseLimitSwitch) m_reverseLimitSwitchJustHit = true; if (!getMotorType().equals(MotorType.kBrushed)) { m_inputs.encoderPosition = getEncoderPosition(); @@ -509,18 +509,16 @@ private void updateInputs() { } /** - * Handle limit switches + * Declare triggers for limit switches so the encoders reset once the limit switches are hit */ - private void handleLimitSwitches() { - if (m_forwardLimitSwitchShouldReset) { - if (m_forwardLimitSwitchJustHit) resetEncoder(m_forwardLimitSwitchResetValue); - } - if (m_reverseLimitSwitchShouldReset) { - if (m_reverseLimitSwitchJustHit) resetEncoder(m_reverseLimitSwitchResetValue); - } + private void initLimitSwitcheResets() { + Trigger resetOnForwardLimit = new Trigger(() -> m_forwardLimitSwitchShouldReset && getInputs().forwardLimitSwitch); + resetOnForwardLimit.onTrue(Commands.runOnce(() -> resetEncoder(m_forwardLimitSwitchResetValue))); + resetOnForwardLimit.onTrue(Commands.none()); - if (m_forwardLimitSwitchJustHit) m_forwardLimitSwitchJustHit = false; - if (m_reverseLimitSwitchJustHit) m_reverseLimitSwitchJustHit = false; + Trigger resetOnReverseLimit = new Trigger(() -> m_reverseLimitSwitchShouldReset && getInputs().reverseLimitSwitch); + resetOnReverseLimit.onTrue(Commands.runOnce(() -> resetEncoder(m_reverseLimitSwitchResetValue))); + resetOnReverseLimit.onTrue(Commands.none()); } /** @@ -547,7 +545,6 @@ private void handleSmoothMotion() { protected void periodic() { Logger.processInputs(m_id.name, m_inputs); - handleLimitSwitches(); handleSmoothMotion(); Logger.recordOutput(m_id.name + CURRENT_LOG_ENTRY, getOutputCurrent()); @@ -992,7 +989,7 @@ public REVLibError resetEncoder() { * The value to set it to can be changed with setForwardLimitSwitchResetValue(double value) * @param value Whether the encoder should be reset */ - public void setM_forwardLimitSwitchShouldReset(boolean value) { + public void setForwardLimitSwitchShouldReset(boolean value) { m_forwardLimitSwitchShouldReset = value; } @@ -1001,14 +998,14 @@ public void setM_forwardLimitSwitchShouldReset(boolean value) { * Only matters if forwardLimitSwitchShouldReset is true, which has its own get and set methods * @param value Desired encoder value */ - public void setM_forwardLimitSwitchResetValue(double value) { + public void setForwardLimitSwitchResetValue(double value) { m_forwardLimitSwitchResetValue = value; } /** * @return What value the encoder will be reset to once the forward limit switch is hit * Only matters if forwardLimitSwitchShouldReset is true, which has its own get and set methods */ - public double getM_forwardLimitSwitchResetValue() { + public double getForwardLimitSwitchResetValue() { return m_forwardLimitSwitchResetValue; } @@ -1018,7 +1015,7 @@ public double getM_forwardLimitSwitchResetValue() { * The value to set it to can be changed with setReverseLimitSwitchResetValue(double value) * @param value Whether the encoder should be reset */ - public void setM_reverseLimitSwitchShouldReset(boolean value) { + public void setReverseLimitSwitchShouldReset(boolean value) { m_reverseLimitSwitchShouldReset = value; } @@ -1027,14 +1024,14 @@ public void setM_reverseLimitSwitchShouldReset(boolean value) { * Only matters if reverseLimitSwitchShouldReset is true, which has its own get and set methods * @param value Desired encoder value */ - public void setM_reverseLimitSwitchResetValue(double value) { + public void setReverseLimitSwitchResetValue(double value) { m_reverseLimitSwitchResetValue = value; } /** * @return What value the encoder will be reset to once the reverse limit switch is hit * Only matters if reverseLimitSwitchShouldReset is true, which has its own get and set methods */ - public double getM_reverseLimitSwitchResetValue() { + public double getReverseLimitSwitchResetValue() { return m_reverseLimitSwitchResetValue; } From ca65c1d9b6b79a8db5e93852371766f5e94b86bf Mon Sep 17 00:00:00 2001 From: Dragin Date: Thu, 5 Sep 2024 17:54:47 -0500 Subject: [PATCH 4/5] Simplify and make the triggers a member variable --- .../hardware/revrobotics/Spark.java | 31 +++++++++---------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/src/main/java/org/lasarobotics/hardware/revrobotics/Spark.java b/src/main/java/org/lasarobotics/hardware/revrobotics/Spark.java index f5870150..61e9500b 100644 --- a/src/main/java/org/lasarobotics/hardware/revrobotics/Spark.java +++ b/src/main/java/org/lasarobotics/hardware/revrobotics/Spark.java @@ -155,8 +155,8 @@ public static class SparkInputs { private FeedbackSensor m_feedbackSensor; private SparkLimitSwitch.Type m_limitSwitchType = SparkLimitSwitch.Type.kNormallyOpen; private RelativeEncoder m_encoder; - private boolean m_forwardLimitSwitchShouldReset = false; - private boolean m_reverseLimitSwitchShouldReset = false; + private Trigger m_forwardLimitSwitchTrigger; + private Trigger m_reverseLimitSwitchTrigger; private double m_forwardLimitSwitchResetValue = 1; private double m_reverseLimitSwitchResetValue = 0; @@ -183,8 +183,9 @@ public Spark(ID id, MotorKind kind, SparkLimitSwitch.Type limitSwitchType, Measu this.m_inputsThread = new Notifier(this::updateInputs); this.m_isSmoothMotionEnabled = false; this.m_limitSwitchType = limitSwitchType; + this.m_forwardLimitSwitchTrigger = new Trigger(() -> getInputs().forwardLimitSwitch); + this.m_reverseLimitSwitchTrigger = new Trigger(() -> getInputs().reverseLimitSwitch); - initLimitSwitcheResets(); // Set CAN timeout m_spark.setCANTimeout(CAN_TIMEOUT_MS); @@ -508,18 +509,6 @@ private void updateInputs() { } } - /** - * Declare triggers for limit switches so the encoders reset once the limit switches are hit - */ - private void initLimitSwitcheResets() { - Trigger resetOnForwardLimit = new Trigger(() -> m_forwardLimitSwitchShouldReset && getInputs().forwardLimitSwitch); - resetOnForwardLimit.onTrue(Commands.runOnce(() -> resetEncoder(m_forwardLimitSwitchResetValue))); - resetOnForwardLimit.onTrue(Commands.none()); - - Trigger resetOnReverseLimit = new Trigger(() -> m_reverseLimitSwitchShouldReset && getInputs().reverseLimitSwitch); - resetOnReverseLimit.onTrue(Commands.runOnce(() -> resetEncoder(m_reverseLimitSwitchResetValue))); - resetOnReverseLimit.onTrue(Commands.none()); - } /** * Handle smooth motion @@ -990,7 +979,11 @@ public REVLibError resetEncoder() { * @param value Whether the encoder should be reset */ public void setForwardLimitSwitchShouldReset(boolean value) { - m_forwardLimitSwitchShouldReset = value; + // This might look weird if you don't know a lot of Java, but it's literally just an if statement (is this true ? if it is return this : otherwise return this) + m_forwardLimitSwitchTrigger.onTrue(value ? + Commands.runOnce(() -> resetEncoder(m_forwardLimitSwitchResetValue)) : + Commands.none() + ); } /** @@ -1016,7 +1009,11 @@ public double getForwardLimitSwitchResetValue() { * @param value Whether the encoder should be reset */ public void setReverseLimitSwitchShouldReset(boolean value) { - m_reverseLimitSwitchShouldReset = value; + // This might look weird if you don't know a lot of Java, but it's literally just an if statement (is this true ? if it is return this : otherwise return this) + m_reverseLimitSwitchTrigger.onTrue(value ? + Commands.runOnce(() -> resetEncoder(m_reverseLimitSwitchResetValue)) : + Commands.none() + ); } /** From 5e126d0e0a6b33eec175ac24a5a0b8c31a3fb7f4 Mon Sep 17 00:00:00 2001 From: Vignesh Balasubramaniam Date: Fri, 6 Sep 2024 18:55:06 -0500 Subject: [PATCH 5/5] Code cleanup --- .../hardware/revrobotics/Spark.java | 27 +++---------------- 1 file changed, 4 insertions(+), 23 deletions(-) diff --git a/src/main/java/org/lasarobotics/hardware/revrobotics/Spark.java b/src/main/java/org/lasarobotics/hardware/revrobotics/Spark.java index 61e9500b..803359c5 100644 --- a/src/main/java/org/lasarobotics/hardware/revrobotics/Spark.java +++ b/src/main/java/org/lasarobotics/hardware/revrobotics/Spark.java @@ -974,12 +974,11 @@ public REVLibError resetEncoder() { } /** - * Sets whether the encoder should be reset once the forward limit switch is hit - * The value to set it to can be changed with setForwardLimitSwitchResetValue(double value) + * Sets whether the NEO encoder should be reset when the forward limit switch is hit + * The value to set it to can be configured using {@link Spark#setForwardLimitSwitchResetValue(double)} * @param value Whether the encoder should be reset */ public void setForwardLimitSwitchShouldReset(boolean value) { - // This might look weird if you don't know a lot of Java, but it's literally just an if statement (is this true ? if it is return this : otherwise return this) m_forwardLimitSwitchTrigger.onTrue(value ? Commands.runOnce(() -> resetEncoder(m_forwardLimitSwitchResetValue)) : Commands.none() @@ -988,28 +987,18 @@ public void setForwardLimitSwitchShouldReset(boolean value) { /** * Change what value the encoder should be reset to once the forward limit switch is hit - * Only matters if forwardLimitSwitchShouldReset is true, which has its own get and set methods * @param value Desired encoder value */ public void setForwardLimitSwitchResetValue(double value) { m_forwardLimitSwitchResetValue = value; } - /** - * @return What value the encoder will be reset to once the forward limit switch is hit - * Only matters if forwardLimitSwitchShouldReset is true, which has its own get and set methods - */ - public double getForwardLimitSwitchResetValue() { - return m_forwardLimitSwitchResetValue; - } - /** - * Sets whether the encoder should be reset once the reverse limit switch is hit - * The value to set it to can be changed with setReverseLimitSwitchResetValue(double value) + * Sets whether the NEO encoder should be reset when the reverse limit switch is hit + * The value to set it to can be configured using {@link Spark#setReverseLimitSwitchResetValue(double)} * @param value Whether the encoder should be reset */ public void setReverseLimitSwitchShouldReset(boolean value) { - // This might look weird if you don't know a lot of Java, but it's literally just an if statement (is this true ? if it is return this : otherwise return this) m_reverseLimitSwitchTrigger.onTrue(value ? Commands.runOnce(() -> resetEncoder(m_reverseLimitSwitchResetValue)) : Commands.none() @@ -1018,19 +1007,11 @@ public void setReverseLimitSwitchShouldReset(boolean value) { /** * Change what value the encoder should be reset to once the reverse limit switch is hit - * Only matters if reverseLimitSwitchShouldReset is true, which has its own get and set methods * @param value Desired encoder value */ public void setReverseLimitSwitchResetValue(double value) { m_reverseLimitSwitchResetValue = value; } - /** - * @return What value the encoder will be reset to once the reverse limit switch is hit - * Only matters if reverseLimitSwitchShouldReset is true, which has its own get and set methods - */ - public double getReverseLimitSwitchResetValue() { - return m_reverseLimitSwitchResetValue; - } /** * Disable forward limit switch