mirror of
https://github.com/TimeCrafters/timecrafters_configuration_tool_desktop.git
synced 2025-12-15 05:22:34 +00:00
TACNET is now able to dynamically sync configs on initial connection, added error sound, made tacnet status dialog update stats, made simulator clock stop after all robots have run out of 'states' to run, changed some dialogs titlebar and borders to be different colors, misc. other changes.
This commit is contained in:
@@ -14,6 +14,12 @@ module TAC
|
||||
@config.configuration.updated_at = Time.now
|
||||
@config.configuration.revision += 1
|
||||
@config_changed = true
|
||||
|
||||
save_config
|
||||
|
||||
if @tacnet.connected?
|
||||
upload_config(@config.name)
|
||||
end
|
||||
end
|
||||
|
||||
def config_changed?
|
||||
@@ -26,8 +32,9 @@ module TAC
|
||||
end
|
||||
end
|
||||
|
||||
def save_config(name)
|
||||
json = @config.to_json
|
||||
def save_config(name = nil, json = nil)
|
||||
name = @config.name unless name
|
||||
json = @config.to_json unless name && json
|
||||
|
||||
File.open("#{TAC::CONFIGS_PATH}/#{name}.json", "w") { |f| f.write json }
|
||||
|
||||
@@ -35,15 +42,15 @@ module TAC
|
||||
end
|
||||
|
||||
def upload_config(config_name)
|
||||
if @config && @tacnet.connected?
|
||||
json = @config.to_json
|
||||
@tacnet.puts(TAC::TACNET::PacketHandler.packet_upload_config(config_name, json))
|
||||
if @tacnet.connected?
|
||||
json = Config.new(config_name).to_json
|
||||
@tacnet.puts( TAC::TACNET::PacketHandler.packet_upload_config(config_name, json) )
|
||||
end
|
||||
end
|
||||
|
||||
def download_config(config_name)
|
||||
if @config && @tacnet.connected?
|
||||
@tacnet.puts(TAC::TACNET::PacketHandler.packet_download_config(config_name))
|
||||
if @tacnet.connected?
|
||||
@tacnet.puts( TAC::TACNET::PacketHandler.packet_download_config(config_name) )
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
module TAC
|
||||
class Config
|
||||
attr_reader :configuration, :groups, :presets
|
||||
attr_reader :name, :configuration, :groups, :presets
|
||||
def initialize(name)
|
||||
@name = name
|
||||
@configuration = nil
|
||||
@groups = nil
|
||||
@presets = nil
|
||||
@@ -109,23 +110,24 @@ module TAC
|
||||
end
|
||||
|
||||
class Action
|
||||
attr_accessor :name, :enabled
|
||||
attr_accessor :name, :comment, :enabled
|
||||
attr_reader :variables
|
||||
def initialize(name:, enabled:, variables:)
|
||||
@name, @enabled = name, enabled
|
||||
def initialize(name:, comment:, enabled:, variables:)
|
||||
@name, @comment, @enabled = name, comment, enabled
|
||||
@variables = variables
|
||||
end
|
||||
|
||||
def to_json(*args)
|
||||
{
|
||||
name: @name,
|
||||
comment: @comment,
|
||||
enabled: @enabled,
|
||||
variables: @variables
|
||||
}.to_json(*args)
|
||||
end
|
||||
|
||||
def self.from_json(hash)
|
||||
Action.new(name: hash[:name], enabled: hash[:enabled], variables: hash[:variables].map { |h| Variable.from_json(h) })
|
||||
Action.new(name: hash[:name], comment: hash[:comment], enabled: hash[:enabled], variables: hash[:variables].map { |h| Variable.from_json(h) })
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -10,12 +10,12 @@ module TAC
|
||||
|
||||
@dialog_root = stack width: 250, height: 400, border_thickness: 2, border_color: [TAC::Palette::TIMECRAFTERS_PRIMARY, TAC::Palette::TIMECRAFTERS_SECONDARY] do
|
||||
# Title bar
|
||||
flow width: 1.0, height: 0.1 do
|
||||
@titlebar = flow width: 1.0, height: 0.1 do
|
||||
background [TAC::Palette::TIMECRAFTERS_PRIMARY, TAC::Palette::TIMECRAFTERS_SECONDARY]
|
||||
|
||||
# title
|
||||
flow width: 0.855 do
|
||||
label @title, text_size: THEME_SUBHEADING_TEXT_SIZE
|
||||
label @title, text_size: THEME_SUBHEADING_TEXT_SIZE, text_shadow_color: Gosu::Color::BLACK
|
||||
end
|
||||
|
||||
# Buttons
|
||||
@@ -27,11 +27,14 @@ module TAC
|
||||
end
|
||||
|
||||
# Dialog body
|
||||
stack width: 1.0, height: 0.9 do
|
||||
build
|
||||
@dialog_content = stack width: 1.0, height: 0.9 do
|
||||
end
|
||||
end
|
||||
|
||||
@dialog_content.clear do
|
||||
build
|
||||
end
|
||||
|
||||
center_dialog
|
||||
end
|
||||
|
||||
|
||||
52
lib/dialogs/action_dialog.rb
Normal file
52
lib/dialogs/action_dialog.rb
Normal file
@@ -0,0 +1,52 @@
|
||||
module TAC
|
||||
class Dialog
|
||||
class ActionDialog < Dialog
|
||||
def build
|
||||
background Gosu::Color::GRAY
|
||||
|
||||
@type = @options[:action].type if @options[:action]
|
||||
|
||||
label "Name"
|
||||
@name_error = label "Error", color: TAC::Palette::TACNET_CONNECTION_ERROR
|
||||
@name_error.hide
|
||||
@name = edit_line @options[:action] ? @options[:action].name : "", width: 1.0
|
||||
|
||||
label "Comment"
|
||||
@comment = edit_line @options[:action] ? @options[:action].comment : "", width: 1.0
|
||||
|
||||
flow width: 1.0 do
|
||||
button "Cancel", width: 0.475 do
|
||||
close
|
||||
end
|
||||
|
||||
button @options[:action] ? "Update" : "Add", width: 0.475 do |b|
|
||||
if valid?
|
||||
if @options[:action]
|
||||
@options[:callback_method].call(@options[:action], @name.value.strip, @comment.value.strip)
|
||||
else
|
||||
@options[:callback_method].call(@name.value.strip, @comment.value.strip)
|
||||
end
|
||||
|
||||
close
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def valid?
|
||||
valid = true
|
||||
|
||||
if @name.value.strip.empty?
|
||||
@name_error.value = "Error: Name cannot be blank\n or only whitespace."
|
||||
@name_error.show
|
||||
valid = false
|
||||
else
|
||||
@name_error.value = ""
|
||||
@name_error.hide
|
||||
end
|
||||
|
||||
return valid
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -2,6 +2,9 @@ module TAC
|
||||
class Dialog
|
||||
class ConfirmDialog < Dialog
|
||||
def build
|
||||
@dialog_root.style.border_color = [ Palette::ALERT, darken(Palette::ALERT, 50) ]
|
||||
@titlebar.style.background = [ Palette::ALERT, darken(Palette::ALERT, 50) ]
|
||||
|
||||
background Gosu::Color::GRAY
|
||||
label @options[:message]
|
||||
|
||||
@@ -9,7 +12,7 @@ module TAC
|
||||
button "Cancel", width: 0.475 do
|
||||
close
|
||||
end
|
||||
button "Okay", width: 0.475 do
|
||||
button "Okay", width: 0.475, **TAC::THEME_DANGER_BUTTON do
|
||||
close
|
||||
|
||||
@options[:callback_method].call
|
||||
|
||||
25
lib/dialogs/tacnet_dialog.rb
Normal file
25
lib/dialogs/tacnet_dialog.rb
Normal file
@@ -0,0 +1,25 @@
|
||||
module TAC
|
||||
class Dialog
|
||||
class TACNETDialog < Dialog
|
||||
def build
|
||||
@dialog_root.style.border_color = [ Palette::TACNET_PRIMARY, Palette::TACNET_SECONDARY ]
|
||||
@titlebar.style.background = [ Palette::TACNET_PRIMARY, Palette::TACNET_SECONDARY ]
|
||||
|
||||
background Gosu::Color::GRAY
|
||||
label @options[:message]
|
||||
|
||||
@sound = Gosu::Sample.new(TAC::ROOT_PATH + "/media/error_alarm.ogg").play(1, 1, true)
|
||||
|
||||
button "Close", width: 1.0 do
|
||||
close
|
||||
end
|
||||
end
|
||||
|
||||
def close
|
||||
super
|
||||
|
||||
@sound.stop
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
24
lib/dialogs/tacnet_status_dialog.rb
Normal file
24
lib/dialogs/tacnet_status_dialog.rb
Normal file
@@ -0,0 +1,24 @@
|
||||
module TAC
|
||||
class Dialog
|
||||
class TACNETStatusDialog < Dialog
|
||||
def build
|
||||
background Gosu::Color::GRAY
|
||||
@message_label = label $window.backend.tacnet.full_status
|
||||
|
||||
button "Close", width: 1.0 do
|
||||
close
|
||||
end
|
||||
|
||||
@timer = CyberarmEngine::Timer.new(1000.0) do
|
||||
@message_label.value = $window.backend.tacnet.full_status
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
super
|
||||
|
||||
@timer.update
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -12,8 +12,8 @@ module TAC
|
||||
BLUE_ALLIANCE = Gosu::Color.new(0xff_000080)
|
||||
RED_ALLIANCE = Gosu::Color.new(0xff_800000)
|
||||
|
||||
TACNET_PRIMARY = Gosu::Color.new(0xff_003f7f)
|
||||
TACNET_SECONDARY = Gosu::Color.new(0xff_007f7f)
|
||||
TACNET_PRIMARY = Gosu::Color.new(0xff000080)
|
||||
TACNET_SECONDARY = Gosu::Color.new(0xff000060)
|
||||
|
||||
GROUPS_PRIMARY = Gosu::Color.new(0xff_444444)
|
||||
GROUPS_SECONDARY = Gosu::Color.new(0xff_444444)
|
||||
@@ -26,5 +26,7 @@ module TAC
|
||||
|
||||
EDITOR_PRIMARY = Gosu::Color.new(0xff_446688)
|
||||
EDITOR_SECONDARY = Gosu::Color.new(0xff_224466)
|
||||
|
||||
ALERT = TACNET_CONNECTING
|
||||
end
|
||||
end
|
||||
@@ -9,7 +9,7 @@ module TAC
|
||||
@position = CyberarmEngine::Vector.new
|
||||
@scale = 1
|
||||
@size = 0
|
||||
@field_size = 144 # inches [1 pxel = 1 inch]
|
||||
@field_size = 144 # inches [1 pixel = 1 inch]
|
||||
|
||||
@blue = Gosu::Color.new(0xff_004080)
|
||||
@red = Gosu::Color.new(0xff_800000)
|
||||
|
||||
@@ -36,7 +36,7 @@ module TAC
|
||||
push_state(ManageConfigurations)
|
||||
end
|
||||
button get_image("#{TAC::ROOT_PATH}/media/icons/save.png"), image_width: THEME_ICON_SIZE, margin_left: 10, tip: "Save config and settings to disk" do
|
||||
window.backend.save_config(window.backend.settings.config)
|
||||
window.backend.save_config
|
||||
window.backend.save_settings
|
||||
end
|
||||
button get_image("#{TAC::ROOT_PATH}/media/icons/export.png"), image_width: THEME_ICON_SIZE, margin_left: 10, tip: "Upload local config to remote, if connected." do
|
||||
@@ -81,7 +81,7 @@ module TAC
|
||||
end
|
||||
end
|
||||
button get_image("#{TAC::ROOT_PATH}/media/icons/information.png"), image_width: THEME_ICON_SIZE, width: 0.475 do
|
||||
push_state(Dialog::AlertDialog, title: "TACNET Status", message: window.backend.tacnet.full_status)
|
||||
push_state(Dialog::TACNETStatusDialog, title: "TACNET Status", message: window.backend.tacnet.full_status)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -193,6 +193,10 @@ module TAC
|
||||
@tacnet_status.value = "Connection Error"
|
||||
@tacnet_status.background = TAC::Palette::TACNET_CONNECTION_ERROR
|
||||
|
||||
if @tacnet_connection_button.value != "Connect"
|
||||
push_state(Dialog::TACNETDialog, title: "TACNET Error", message: window.backend.tacnet.full_status)
|
||||
end
|
||||
|
||||
@tacnet_connection_button.value = "Connect"
|
||||
when :not_connected
|
||||
@tacnet_status.value = "Not Connected"
|
||||
|
||||
@@ -73,8 +73,13 @@ robot.forward 100"
|
||||
def update
|
||||
super
|
||||
|
||||
@simulation.update if @simulation
|
||||
@simulation_status.value = "Time: #{((Gosu.milliseconds - @simulation_start_time) / 1000.0).round(1)} seconds" if @simulation_start_time
|
||||
if @simulation
|
||||
@simulation.update
|
||||
|
||||
unless @simulation.robots.all? { |robot| robot.queue.empty? } # Only update clock if simulation is running
|
||||
@simulation_status.value = "Time: #{((Gosu.milliseconds - @simulation_start_time) / 1000.0).round(1)} seconds" if @simulation_start_time
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2,7 +2,7 @@ module TAC
|
||||
class TACNET
|
||||
class Packet
|
||||
PROTOCOL_VERSION = 1
|
||||
PROTOCOL_HEADER_SEPERATOR = "|"
|
||||
PROTOCOL_SEPERATOR = "|"
|
||||
PROTOCOL_HEARTBEAT = "heartbeat"
|
||||
|
||||
PACKET_TYPES = {
|
||||
@@ -12,6 +12,11 @@ module TAC
|
||||
|
||||
download_config: 10,
|
||||
upload_config: 11,
|
||||
list_configs: 12,
|
||||
select_config: 13,
|
||||
add_config: 14,
|
||||
update_config: 15,
|
||||
delete_config: 16,
|
||||
|
||||
add_group: 20,
|
||||
update_group: 21,
|
||||
@@ -30,7 +35,7 @@ module TAC
|
||||
slice = message.split("|", 4)
|
||||
|
||||
if slice.size < 4
|
||||
warn "Failed to split packet along first 4 " + PROTOCOL_HEADER_SEPERATOR + ". Raw return: " + slice.to_s
|
||||
warn "Failed to split packet along first 4 " + PROTOCOL_SEPERATOR + ". Raw return: " + slice.to_s
|
||||
return nil
|
||||
end
|
||||
|
||||
@@ -73,11 +78,11 @@ module TAC
|
||||
def encode_header
|
||||
string = ""
|
||||
string += protocol_version.to_s
|
||||
string += PROTOCOL_HEADER_SEPERATOR
|
||||
string += PROTOCOL_SEPERATOR
|
||||
string += PACKET_TYPES[type].to_s
|
||||
string += PROTOCOL_HEADER_SEPERATOR
|
||||
string += PROTOCOL_SEPERATOR
|
||||
string += content_length.to_s
|
||||
string += PROTOCOL_HEADER_SEPERATOR
|
||||
string += PROTOCOL_SEPERATOR
|
||||
|
||||
return string
|
||||
end
|
||||
|
||||
@@ -10,6 +10,7 @@ module TAC
|
||||
packet = Packet.from_stream(message)
|
||||
|
||||
if packet
|
||||
log.i(TAG, "Received packet of type: #{packet.type}")
|
||||
hand_off(packet)
|
||||
else
|
||||
log.d(TAG, "Rejected raw packet: #{message}")
|
||||
@@ -24,10 +25,21 @@ module TAC
|
||||
handle_heartbeat(packet)
|
||||
when :error
|
||||
handle_error(packet)
|
||||
|
||||
when :download_config
|
||||
handle_download_config(packet)
|
||||
when :upload_config
|
||||
handle_upload_config(packet)
|
||||
when :list_configs
|
||||
handle_list_configs(packet)
|
||||
when :select_config
|
||||
handle_select_config(packet)
|
||||
when :add_config
|
||||
handle_add_config(packet)
|
||||
when :update_config # rename config/file
|
||||
handle_update_config(packet)
|
||||
when :delete_config
|
||||
handle_delete_config(packet)
|
||||
else
|
||||
log.d(TAG, "No hand off available for packet type: #{packet.type}")
|
||||
end
|
||||
@@ -45,11 +57,15 @@ module TAC
|
||||
|
||||
# TODO: Handle errors
|
||||
def handle_error(packet)
|
||||
if @host_is_a_connection
|
||||
title, message = packet.body.split(Packet::PROTOCOL_SEPERATOR, 2)
|
||||
$window.push_state(TAC::Dialog::TACNETDialog, title: title, message: message)
|
||||
end
|
||||
end
|
||||
|
||||
def handle_upload_config(packet)
|
||||
begin
|
||||
config_name, json = packet.body.split(Packet::PROTOCOL_HEADER_SEPERATOR)
|
||||
config_name, json = packet.body.split(Packet::PROTOCOL_SEPERATOR, 2)
|
||||
data = JSON.parse(json, symbolize_names: true)
|
||||
|
||||
if @host_is_a_connection
|
||||
@@ -58,11 +74,15 @@ module TAC
|
||||
$window.push_state(TAC::Dialog::AlertDialog, title: "Invalid Config", message: "Remote config to old.")
|
||||
|
||||
elsif data.is_a?(Hash) && data.dig(:config, :spec_version) == TAC::CONFIG_SPEC_VERSION
|
||||
$window.push_state(TAC::Dialog::ConfirmDialog, title: "Replace Config", message: "Replace local config\nwith remote config?", callback_method: proc {
|
||||
File.open("#{TAC::CONFIGS_PATH}/#{config_name}.json", "w") { |f| f.write json }
|
||||
|
||||
$window.backend.load_config(config_name)
|
||||
})
|
||||
if $window.backend.config.name == config_name
|
||||
$window.backend.load_config(config_name)
|
||||
|
||||
$window.instance_variable_get(:"@states").each do |state|
|
||||
state.populate_groups_list if state.is_a?(TAC::States::Editor)
|
||||
end
|
||||
end
|
||||
|
||||
elsif data.is_a?(Hash) && data.dig(:config, :spec_version) < TAC::CONFIG_SPEC_VERSION
|
||||
# OLD CONFIG, Upgrade?
|
||||
@@ -103,6 +123,56 @@ module TAC
|
||||
end
|
||||
end
|
||||
|
||||
def handle_list_configs(packet)
|
||||
if @host_is_a_connection # Download new or updated configs
|
||||
list = packet.body.split(Packet::PROTOCOL_SEPERATOR).map { |part| part.split(",") }
|
||||
|
||||
remote_configs = list.map { |l| l.first }
|
||||
local_configs = Dir.glob("#{TAC::CONFIGS_PATH}/*.json").map { |f| File.basename(f, ".json") }
|
||||
_diff = local_configs - remote_configs
|
||||
|
||||
list.each do |name, revision|
|
||||
revision = Integer(revision)
|
||||
path = "#{TAC::CONFIGS_PATH}/#{name}.json"
|
||||
|
||||
if File.exist?(path)
|
||||
config = Config.new(name)
|
||||
|
||||
if config.configuration.revision < revision
|
||||
$window.backend.tacnet.puts( PacketHandler.packet_download_config(name) )
|
||||
elsif config.configuration.revision > revision
|
||||
$window.backend.tacnet.puts( PacketHandler.packet_upload_config(name, JSON.dump( config )) )
|
||||
end
|
||||
|
||||
else
|
||||
$window.backend.tacnet.puts( PacketHandler.packet_download_config(name) )
|
||||
end
|
||||
end
|
||||
|
||||
_diff.each do |name|
|
||||
config = Config.new(name)
|
||||
|
||||
$window.backend.tacnet.puts( PacketHandler.packet_upload_config(name, JSON.dump( config )) )
|
||||
end
|
||||
else
|
||||
if $server.active_client && $server.active_client.connected?
|
||||
$server.active_client.puts(PacketHandler.packet_list_configs)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def handle_select_config(packet)
|
||||
end
|
||||
|
||||
def handle_add_config(packet)
|
||||
end
|
||||
|
||||
def handle_update_config(packet)
|
||||
end
|
||||
|
||||
def handle_delete_config(packet)
|
||||
end
|
||||
|
||||
def self.packet_handshake(client_uuid)
|
||||
Packet.create(Packet::PACKET_TYPES[:handshake], client_uuid)
|
||||
end
|
||||
@@ -120,10 +190,24 @@ module TAC
|
||||
end
|
||||
|
||||
def self.packet_upload_config(config_name, json)
|
||||
string = "#{config_name}#{Packet::PROTOCOL_HEADER_SEPERATOR}#{json.gsub("\n", " ")}"
|
||||
string = "#{config_name}#{Packet::PROTOCOL_SEPERATOR}#{json.gsub("\n", " ")}"
|
||||
|
||||
Packet.create(Packet::PACKET_TYPES[:upload_config], string)
|
||||
end
|
||||
|
||||
def self.packet_list_configs
|
||||
files = Dir.glob("#{TAC::CONFIGS_PATH}/*.json")
|
||||
list = files.map do |file|
|
||||
name = File.basename(file, ".json")
|
||||
config = Config.new(name)
|
||||
"#{name},#{config.configuration.revision}"
|
||||
end.join(Packet::PROTOCOL_SEPERATOR)
|
||||
|
||||
Packet.create(
|
||||
Packet::PACKET_TYPES[:list_configs],
|
||||
list
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -71,11 +71,9 @@ module TAC
|
||||
@active_client = client
|
||||
# TODO: Backup local config
|
||||
# SEND CONFIG
|
||||
settings = TAC::Settings.new
|
||||
config = File.read("#{TAC::CONFIGS_PATH}/#{settings.config}.json")
|
||||
|
||||
@active_client.puts(PacketHandler.packet_handshake(@active_client.uuid))
|
||||
@active_client.puts(PacketHandler.packet_upload_config(settings.config, config))
|
||||
@active_client.puts(PacketHandler.packet_list_configs)
|
||||
|
||||
log.i(TAG, "Client connected!")
|
||||
|
||||
@@ -137,4 +135,4 @@ module TAC
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -46,8 +46,8 @@ module TAC
|
||||
padding_top: THEME_ITEM_PADDING,
|
||||
padding_bottom: THEME_ITEM_PADDING
|
||||
}
|
||||
THEME_EVEN_COLOR = Gosu::Color.new(0xff_606060)
|
||||
THEME_ODD_COLOR = Gosu::Color.new(0xff_202020)
|
||||
THEME_EVEN_COLOR = Gosu::Color.new(0xff_202020)
|
||||
THEME_ODD_COLOR = Gosu::Color.new(0xff_606060)
|
||||
THEME_CONTENT_BACKGROUND = Gosu::Color.new(0x88_007f3f)
|
||||
THEME_HEADER_BACKGROUND = [
|
||||
TAC::Palette::TIMECRAFTERS_PRIMARY, TAC::Palette::TIMECRAFTERS_PRIMARY,
|
||||
|
||||
BIN
media/error_alarm.ogg
Normal file
BIN
media/error_alarm.ogg
Normal file
Binary file not shown.
@@ -26,6 +26,8 @@ require_relative "lib/dialogs/alert_dialog"
|
||||
require_relative "lib/dialogs/confirm_dialog"
|
||||
require_relative "lib/dialogs/name_prompt_dialog"
|
||||
require_relative "lib/dialogs/variable_dialog"
|
||||
require_relative "lib/dialogs/tacnet_dialog"
|
||||
require_relative "lib/dialogs/tacnet_status_dialog"
|
||||
require_relative "lib/tacnet"
|
||||
require_relative "lib/tacnet/packet"
|
||||
require_relative "lib/tacnet/packet_handler"
|
||||
|
||||
Reference in New Issue
Block a user