Skip to content

Commit

Permalink
Finish Command Scheduler and start trajectory rendering
Browse files Browse the repository at this point in the history
  • Loading branch information
nab138 committed Jun 21, 2024
1 parent abac0c8 commit d700982
Show file tree
Hide file tree
Showing 12 changed files with 306 additions and 163 deletions.
2 changes: 1 addition & 1 deletion .vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"version": "2.0.0",
"tasks": [
{
"label": "Deploy to iphone",
"label": "Deploy to iPhone",
"type": "shell",
"command": "./deploy.sh",
"problemMatcher": ["$swiftc"],
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,15 @@ If you add the [sideloader cli](https://github.com/Dadoum/Sideloader) to $THEOS/

## In progress features

Features that are being worked on, but are not included in a release yet.
Features that are being worked on. Features with a checkmark are in a working state, but aren't included in the latest release yet.

- [x] AprilTag Detector
- [ ] Command Scheduler Display
- [x] Command Scheduler Display
- [ ] Trajectory Rendering
- [ ] Alignment with real field via AprilTags

## Planned Features (in no particular order)

- [ ] Trajectory Rendering
- [ ] Field Switcher
- [ ] Field Element Placement
- [ ] Mechanism Rendering (maybe)
2 changes: 1 addition & 1 deletion compile_commands.json

Large diffs are not rendered by default.

59 changes: 45 additions & 14 deletions src/CommandScheduler/CommandScheduler.swift
Original file line number Diff line number Diff line change
@@ -1,47 +1,78 @@
import ARKit

class CommandScheduler {
var subscriptionID: Int?
var namesSubID: Int?

let view = CommandSchedulerView()
var node: SCNNode!
var plane: SCNPlane!
var arScene: ARSceneView!

var planeSize: Float = 0.25
var size: Float = 0.25
var height: Float = 3.0
var visible = true

// add constructor
var commands: [String] = []

var hasUpdatedLabel = false

init(scene: ARSceneView) {
NSLog("Initializing CommandScheduler")
arScene = scene

loadViewNode()
}

func subscribeToCommandScheduler(client: NT4Client, key: String) {
if subscriptionID != nil {
client.unsubscribe(subID: subscriptionID!)
if namesSubID != nil {
client.unsubscribe(subID: namesSubID!)
}
subscriptionID = client.subscribe(
key: key, callback: { topic, timestamp, data in }, periodic: 0.1)
namesSubID = client.subscribe(
key: key + "/Names",
callback: { topic, timestamp, data in
if let commands = data as? [String] {
NSLog("Recieved commands: \(commands.joined(separator: ", "))")
if commands != self.commands {
self.commands = commands
self.updateCommands()
}
}
}, periodic: 0.1)
}

func loadViewNode() {
let image = view.asImage()
let aspectRatio = image.size.width / image.size.height
plane = SCNPlane(width: CGFloat(size), height: CGFloat(size) / aspectRatio)
plane.firstMaterial?.diffuse.contents = image
private func loadViewNode() {
plane = SCNPlane(width: CGFloat(planeSize), height: CGFloat(planeSize))
plane.firstMaterial?.isDoubleSided = true

node = SCNNode(geometry: plane)
regenImage()

let billboardConstraint = SCNBillboardConstraint()
billboardConstraint.freeAxes = [.Y, .X]
node.constraints = [billboardConstraint]

node.isHidden = UserDefaults.standard.bool(forKey: "schedulerVisible")
node.isHidden = !UserDefaults.standard.bool(forKey: "schedulerVisible")
}

private func updateCommands() {
view.setCommands(commands)

if commands.count > 0 {
view.updateLabel(with: "\(commands.count) Commands")
} else {
view.updateLabel(with: "No Data")
}

regenImage()
}

func regenImage() {
let image = view.asImage()
let aspectRatio = image.size.width / image.size.height
plane.width = CGFloat(planeSize)
plane.height = CGFloat(planeSize) / aspectRatio
plane.firstMaterial?.diffuse.contents = image

arScene.scene.rootNode.addChildNode(node)
node.geometry = plane
}
}
57 changes: 51 additions & 6 deletions src/CommandScheduler/CommandSchedulerView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import UIKit

class CommandSchedulerView: UIView {
let mainLabel = UILabel()
let secondLabel = UILabel()
private let padding: CGFloat = 10

override init(frame: CGRect) {
super.init(frame: frame)
Expand All @@ -17,22 +19,36 @@ class CommandSchedulerView: UIView {
private func setupView() {
layer.cornerRadius = 10
clipsToBounds = true
frame = CGRect(x: 10, y: 10, width: 400, height: 100)
backgroundColor = UIColor.systemBackground.withAlphaComponent(0.8)
frame = CGRect(x: padding, y: padding, width: 325, height: 200)
backgroundColor = UIColor.systemBackground.withAlphaComponent(0.85)
let visualEffectView = UIVisualEffectView(effect: UIBlurEffect(style: .systemMaterial))
visualEffectView.frame = bounds
visualEffectView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
addSubview(visualEffectView)

mainLabel.text = "Command Scheduler ⋅ No Data"
mainLabel.text = "Command Scheduler"
mainLabel.font = UIFont.systemFont(ofSize: 20)
mainLabel.translatesAutoresizingMaskIntoConstraints = false
mainLabel.textAlignment = .left
mainLabel.textColor = UIColor.label
mainLabel.frame = CGRect(
x: 10, y: 10, width: frame.width - 20, height: frame.height - 20)
x: padding, y: padding, width: frame.width - (2 * padding),
height: frame.height - (2 * padding))
addSubview(mainLabel)
mainLabel.sizeToFit()

secondLabel.text = "No Data"
secondLabel.font = UIFont.systemFont(ofSize: 16)
secondLabel.translatesAutoresizingMaskIntoConstraints = false
secondLabel.textAlignment = .left
secondLabel.textColor = UIColor.label
secondLabel.frame = CGRect(
x: padding, y: padding + mainLabel.frame.height, width: frame.width - (2 * padding),
height: frame.height - (2 * padding))
addSubview(secondLabel)
secondLabel.sizeToFit()

frame.size.height = secondLabel.frame.maxY + (2 * padding)
}

func asImage() -> UIImage {
Expand All @@ -44,7 +60,36 @@ class CommandSchedulerView: UIView {
}

func updateLabel(with text: String) {
mainLabel.text = text
mainLabel.sizeToFit()
secondLabel.text = text
secondLabel.sizeToFit()
}

func setCommands(_ commands: [String]) {
// Remove existing command labels
subviews.forEach { view in
if view is UILabel && view != mainLabel && view != secondLabel {
view.removeFromSuperview()
}
}

var yOffset: CGFloat = secondLabel.frame.maxY + padding
for command in commands {
let commandLabel = PaddedLabel()
commandLabel.text = command
commandLabel.font = UIFont.systemFont(ofSize: 16)
commandLabel.translatesAutoresizingMaskIntoConstraints = true
commandLabel.textAlignment = .left
commandLabel.textColor = UIColor.label
commandLabel.layer.cornerRadius = 10
commandLabel.layer.masksToBounds = true
commandLabel.backgroundColor = UIColor.systemGray.withAlphaComponent(0.5)
commandLabel.leftInset = padding
commandLabel.frame = CGRect(
x: padding, y: yOffset, width: frame.width - (2 * padding), height: 20 + (2 * padding))
addSubview(commandLabel)
yOffset += commandLabel.frame.height + padding
}

frame.size.height = yOffset + padding
}
}
Empty file.
28 changes: 15 additions & 13 deletions src/ConfigViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,11 @@ class ConfigViewController: UITableViewController, UIDocumentPickerDelegate {
type: .toggleSwitch(
label: "Detect AprilTags",
defaultValue: UserDefaults.standard.bool(forKey: "detectAprilTags"))),
Row(
type: .textField(
placeholder: "Trajectory NT Key",
defaultValue: UserDefaults.standard.string(forKey: "trajectoryKey")
)),
],
[
Row(
Expand Down Expand Up @@ -126,11 +131,6 @@ class ConfigViewController: UITableViewController, UIDocumentPickerDelegate {
label: "Size",
defaultValue: UserDefaults.standard.float(forKey: "schedulerSize"), min: 0.05,
max: 1)),
Row(
type: .textField(
placeholder: "Scheduler NT Key",
defaultValue: UserDefaults.standard.string(forKey: "schedulerKey")
)),
],
[
Row(
Expand Down Expand Up @@ -456,6 +456,7 @@ class ConfigViewController: UITableViewController, UIDocumentPickerDelegate {
let portTextField = cellViews[IndexPath(row: 2, section: 0)] as? UITextField
let manualAddressSwitch = cellViews[IndexPath(row: 3, section: 0)] as? UISwitch
let robotKeyTextField = cellViews[IndexPath(row: 0, section: 2)] as? UITextField
let trajectoryKeyTextField = cellViews[IndexPath(row: 4, section: 1)] as? UITextField

if manualAddressSwitch?.isOn ?? false {
NTHandler.ip = ipTextField?.text
Expand All @@ -479,6 +480,7 @@ class ConfigViewController: UITableViewController, UIDocumentPickerDelegate {

NTHandler.port = portTextField?.text
NTHandler.robotKey = robotKeyTextField?.text
NTHandler.trajectoryKey = trajectoryKeyTextField?.text

let xOffsetTextField = cellViews[IndexPath(row: 1, section: 3)] as? UITextField
let yOffsetTextField = cellViews[IndexPath(row: 2, section: 3)] as? UITextField
Expand Down Expand Up @@ -519,7 +521,7 @@ class ConfigViewController: UITableViewController, UIDocumentPickerDelegate {
let schedulerHeightSlider = cellViews[IndexPath(row: 1, section: 4)] as? UISlider
let schedulerSizeSlider = cellViews[IndexPath(row: 2, section: 4)] as? UISlider

UserDefaults.standard.set(schedulerVisibleSwitch?.isOn, forKey: "schedulerVisible")
UserDefaults.standard.set(schedulerVisibleSwitch?.isOn ?? true, forKey: "schedulerVisible")
UserDefaults.standard.set(schedulerHeightSlider?.value ?? 3, forKey: "schedulerHeight")
UserDefaults.standard.set(schedulerSizeSlider?.value ?? 0.25, forKey: "schedulerSize")

Expand All @@ -529,19 +531,16 @@ class ConfigViewController: UITableViewController, UIDocumentPickerDelegate {
+ (controller.scheduler.height * controller.sceneView.fieldNode.scale.y)

let newSize = schedulerSizeSlider?.value ?? 0.25
let oldSize = controller.scheduler.size
controller.scheduler.node.scale = SCNVector3(
(controller.scheduler.node.scale.x / oldSize) * newSize,
(controller.scheduler.node.scale.y / oldSize) * newSize,
(controller.scheduler.node.scale.z / oldSize) * newSize)
controller.scheduler.size = newSize
controller.scheduler.planeSize = newSize
controller.scheduler.regenImage()

controller.scheduler.node.isHidden = !(schedulerVisibleSwitch?.isOn ?? false)
controller.scheduler.node.isHidden = !(schedulerVisibleSwitch?.isOn ?? true)

UserDefaults.standard.set(teamNumberTextField?.text, forKey: "teamNumber")
UserDefaults.standard.set(ipTextField?.text, forKey: "ip")
UserDefaults.standard.set(portTextField?.text, forKey: "port")
UserDefaults.standard.set(robotKeyTextField?.text, forKey: "robotKey")
UserDefaults.standard.set(trajectoryKeyTextField?.text, forKey: "trajectoryKey")
UserDefaults.standard.set(manualAddressSwitch?.isOn, forKey: "manualAddress")

UserDefaults.standard.set(customRobotSelected, forKey: "customRobotSelected")
Expand Down Expand Up @@ -602,6 +601,9 @@ class ConfigViewController: UITableViewController, UIDocumentPickerDelegate {
case 3:
return
"You can convert your model to .usdz online. Only one robot can be imported at a time; subsequent imports will overwrite. Offsets can be changed after import."
case 4:
return
"Uses /SmartDashboard/Scheduler to display scheduled commands. To publish this from your robot code, you can add SmartDashboard.putData(CommandScheduler.getInstance()); to robotPeriodic."
default:
return nil
}
Expand Down
1 change: 0 additions & 1 deletion src/NetworkTables/NT4Client.swift
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,6 @@ Unsubscribe from a NetworkTables topic
topic.latestTimestamp = timestamp
onNewTopicData?(topic, timestamp, data)
if let callback = subscriptionCallbacks[topic.name] {
NSLog("Found callback for topic \(topic.name)")
callback(topic, timestamp, data)
}
} else {
Expand Down
Loading

0 comments on commit d700982

Please sign in to comment.