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:
2020-09-08 19:25:04 -05:00
parent 08ada79e5b
commit 9dc3caca0f
17 changed files with 257 additions and 41 deletions

View File

@@ -14,6 +14,12 @@ module TAC
@config.configuration.updated_at = Time.now @config.configuration.updated_at = Time.now
@config.configuration.revision += 1 @config.configuration.revision += 1
@config_changed = true @config_changed = true
save_config
if @tacnet.connected?
upload_config(@config.name)
end
end end
def config_changed? def config_changed?
@@ -26,8 +32,9 @@ module TAC
end end
end end
def save_config(name) def save_config(name = nil, json = nil)
json = @config.to_json name = @config.name unless name
json = @config.to_json unless name && json
File.open("#{TAC::CONFIGS_PATH}/#{name}.json", "w") { |f| f.write json } File.open("#{TAC::CONFIGS_PATH}/#{name}.json", "w") { |f| f.write json }
@@ -35,14 +42,14 @@ module TAC
end end
def upload_config(config_name) def upload_config(config_name)
if @config && @tacnet.connected? if @tacnet.connected?
json = @config.to_json json = Config.new(config_name).to_json
@tacnet.puts( TAC::TACNET::PacketHandler.packet_upload_config(config_name, json) ) @tacnet.puts( TAC::TACNET::PacketHandler.packet_upload_config(config_name, json) )
end end
end end
def download_config(config_name) def download_config(config_name)
if @config && @tacnet.connected? if @tacnet.connected?
@tacnet.puts( TAC::TACNET::PacketHandler.packet_download_config(config_name) ) @tacnet.puts( TAC::TACNET::PacketHandler.packet_download_config(config_name) )
end end
end end

View File

@@ -1,7 +1,8 @@
module TAC module TAC
class Config class Config
attr_reader :configuration, :groups, :presets attr_reader :name, :configuration, :groups, :presets
def initialize(name) def initialize(name)
@name = name
@configuration = nil @configuration = nil
@groups = nil @groups = nil
@presets = nil @presets = nil
@@ -109,23 +110,24 @@ module TAC
end end
class Action class Action
attr_accessor :name, :enabled attr_accessor :name, :comment, :enabled
attr_reader :variables attr_reader :variables
def initialize(name:, enabled:, variables:) def initialize(name:, comment:, enabled:, variables:)
@name, @enabled = name, enabled @name, @comment, @enabled = name, comment, enabled
@variables = variables @variables = variables
end end
def to_json(*args) def to_json(*args)
{ {
name: @name, name: @name,
comment: @comment,
enabled: @enabled, enabled: @enabled,
variables: @variables variables: @variables
}.to_json(*args) }.to_json(*args)
end end
def self.from_json(hash) 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
end end

View File

@@ -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 @dialog_root = stack width: 250, height: 400, border_thickness: 2, border_color: [TAC::Palette::TIMECRAFTERS_PRIMARY, TAC::Palette::TIMECRAFTERS_SECONDARY] do
# Title bar # 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] background [TAC::Palette::TIMECRAFTERS_PRIMARY, TAC::Palette::TIMECRAFTERS_SECONDARY]
# title # title
flow width: 0.855 do 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 end
# Buttons # Buttons
@@ -27,11 +27,14 @@ module TAC
end end
# Dialog body # Dialog body
stack width: 1.0, height: 0.9 do @dialog_content = stack width: 1.0, height: 0.9 do
build
end end
end end
@dialog_content.clear do
build
end
center_dialog center_dialog
end end

View 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

View File

@@ -2,6 +2,9 @@ module TAC
class Dialog class Dialog
class ConfirmDialog < Dialog class ConfirmDialog < Dialog
def build 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 background Gosu::Color::GRAY
label @options[:message] label @options[:message]
@@ -9,7 +12,7 @@ module TAC
button "Cancel", width: 0.475 do button "Cancel", width: 0.475 do
close close
end end
button "Okay", width: 0.475 do button "Okay", width: 0.475, **TAC::THEME_DANGER_BUTTON do
close close
@options[:callback_method].call @options[:callback_method].call

View 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

View 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

View File

@@ -12,8 +12,8 @@ module TAC
BLUE_ALLIANCE = Gosu::Color.new(0xff_000080) BLUE_ALLIANCE = Gosu::Color.new(0xff_000080)
RED_ALLIANCE = Gosu::Color.new(0xff_800000) RED_ALLIANCE = Gosu::Color.new(0xff_800000)
TACNET_PRIMARY = Gosu::Color.new(0xff_003f7f) TACNET_PRIMARY = Gosu::Color.new(0xff000080)
TACNET_SECONDARY = Gosu::Color.new(0xff_007f7f) TACNET_SECONDARY = Gosu::Color.new(0xff000060)
GROUPS_PRIMARY = Gosu::Color.new(0xff_444444) GROUPS_PRIMARY = Gosu::Color.new(0xff_444444)
GROUPS_SECONDARY = 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_PRIMARY = Gosu::Color.new(0xff_446688)
EDITOR_SECONDARY = Gosu::Color.new(0xff_224466) EDITOR_SECONDARY = Gosu::Color.new(0xff_224466)
ALERT = TACNET_CONNECTING
end end
end end

View File

@@ -9,7 +9,7 @@ module TAC
@position = CyberarmEngine::Vector.new @position = CyberarmEngine::Vector.new
@scale = 1 @scale = 1
@size = 0 @size = 0
@field_size = 144 # inches [1 pxel = 1 inch] @field_size = 144 # inches [1 pixel = 1 inch]
@blue = Gosu::Color.new(0xff_004080) @blue = Gosu::Color.new(0xff_004080)
@red = Gosu::Color.new(0xff_800000) @red = Gosu::Color.new(0xff_800000)

View File

@@ -36,7 +36,7 @@ module TAC
push_state(ManageConfigurations) push_state(ManageConfigurations)
end 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 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 window.backend.save_settings
end 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 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
end end
button get_image("#{TAC::ROOT_PATH}/media/icons/information.png"), image_width: THEME_ICON_SIZE, width: 0.475 do 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 end
end end
@@ -193,6 +193,10 @@ module TAC
@tacnet_status.value = "Connection Error" @tacnet_status.value = "Connection Error"
@tacnet_status.background = TAC::Palette::TACNET_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" @tacnet_connection_button.value = "Connect"
when :not_connected when :not_connected
@tacnet_status.value = "Not Connected" @tacnet_status.value = "Not Connected"

View File

@@ -73,9 +73,14 @@ robot.forward 100"
def update def update
super super
@simulation.update if @simulation 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 @simulation_status.value = "Time: #{((Gosu.milliseconds - @simulation_start_time) / 1000.0).round(1)} seconds" if @simulation_start_time
end end
end end
end end
end end
end
end

View File

@@ -2,7 +2,7 @@ module TAC
class TACNET class TACNET
class Packet class Packet
PROTOCOL_VERSION = 1 PROTOCOL_VERSION = 1
PROTOCOL_HEADER_SEPERATOR = "|" PROTOCOL_SEPERATOR = "|"
PROTOCOL_HEARTBEAT = "heartbeat" PROTOCOL_HEARTBEAT = "heartbeat"
PACKET_TYPES = { PACKET_TYPES = {
@@ -12,6 +12,11 @@ module TAC
download_config: 10, download_config: 10,
upload_config: 11, upload_config: 11,
list_configs: 12,
select_config: 13,
add_config: 14,
update_config: 15,
delete_config: 16,
add_group: 20, add_group: 20,
update_group: 21, update_group: 21,
@@ -30,7 +35,7 @@ module TAC
slice = message.split("|", 4) slice = message.split("|", 4)
if slice.size < 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 return nil
end end
@@ -73,11 +78,11 @@ module TAC
def encode_header def encode_header
string = "" string = ""
string += protocol_version.to_s string += protocol_version.to_s
string += PROTOCOL_HEADER_SEPERATOR string += PROTOCOL_SEPERATOR
string += PACKET_TYPES[type].to_s string += PACKET_TYPES[type].to_s
string += PROTOCOL_HEADER_SEPERATOR string += PROTOCOL_SEPERATOR
string += content_length.to_s string += content_length.to_s
string += PROTOCOL_HEADER_SEPERATOR string += PROTOCOL_SEPERATOR
return string return string
end end

View File

@@ -10,6 +10,7 @@ module TAC
packet = Packet.from_stream(message) packet = Packet.from_stream(message)
if packet if packet
log.i(TAG, "Received packet of type: #{packet.type}")
hand_off(packet) hand_off(packet)
else else
log.d(TAG, "Rejected raw packet: #{message}") log.d(TAG, "Rejected raw packet: #{message}")
@@ -24,10 +25,21 @@ module TAC
handle_heartbeat(packet) handle_heartbeat(packet)
when :error when :error
handle_error(packet) handle_error(packet)
when :download_config when :download_config
handle_download_config(packet) handle_download_config(packet)
when :upload_config when :upload_config
handle_upload_config(packet) 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 else
log.d(TAG, "No hand off available for packet type: #{packet.type}") log.d(TAG, "No hand off available for packet type: #{packet.type}")
end end
@@ -45,11 +57,15 @@ module TAC
# TODO: Handle errors # TODO: Handle errors
def handle_error(packet) 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 end
def handle_upload_config(packet) def handle_upload_config(packet)
begin 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) data = JSON.parse(json, symbolize_names: true)
if @host_is_a_connection 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.") $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 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 } File.open("#{TAC::CONFIGS_PATH}/#{config_name}.json", "w") { |f| f.write json }
if $window.backend.config.name == config_name
$window.backend.load_config(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 elsif data.is_a?(Hash) && data.dig(:config, :spec_version) < TAC::CONFIG_SPEC_VERSION
# OLD CONFIG, Upgrade? # OLD CONFIG, Upgrade?
@@ -103,6 +123,56 @@ module TAC
end end
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) def self.packet_handshake(client_uuid)
Packet.create(Packet::PACKET_TYPES[:handshake], client_uuid) Packet.create(Packet::PACKET_TYPES[:handshake], client_uuid)
end end
@@ -120,10 +190,24 @@ module TAC
end end
def self.packet_upload_config(config_name, json) 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) Packet.create(Packet::PACKET_TYPES[:upload_config], string)
end 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
end end

View File

@@ -71,11 +71,9 @@ module TAC
@active_client = client @active_client = client
# TODO: Backup local config # TODO: Backup local config
# SEND 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_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!") log.i(TAG, "Client connected!")

View File

@@ -46,8 +46,8 @@ module TAC
padding_top: THEME_ITEM_PADDING, padding_top: THEME_ITEM_PADDING,
padding_bottom: THEME_ITEM_PADDING padding_bottom: THEME_ITEM_PADDING
} }
THEME_EVEN_COLOR = Gosu::Color.new(0xff_606060) THEME_EVEN_COLOR = Gosu::Color.new(0xff_202020)
THEME_ODD_COLOR = Gosu::Color.new(0xff_202020) THEME_ODD_COLOR = Gosu::Color.new(0xff_606060)
THEME_CONTENT_BACKGROUND = Gosu::Color.new(0x88_007f3f) THEME_CONTENT_BACKGROUND = Gosu::Color.new(0x88_007f3f)
THEME_HEADER_BACKGROUND = [ THEME_HEADER_BACKGROUND = [
TAC::Palette::TIMECRAFTERS_PRIMARY, TAC::Palette::TIMECRAFTERS_PRIMARY, TAC::Palette::TIMECRAFTERS_PRIMARY, TAC::Palette::TIMECRAFTERS_PRIMARY,

BIN
media/error_alarm.ogg Normal file

Binary file not shown.

View File

@@ -26,6 +26,8 @@ require_relative "lib/dialogs/alert_dialog"
require_relative "lib/dialogs/confirm_dialog" require_relative "lib/dialogs/confirm_dialog"
require_relative "lib/dialogs/name_prompt_dialog" require_relative "lib/dialogs/name_prompt_dialog"
require_relative "lib/dialogs/variable_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"
require_relative "lib/tacnet/packet" require_relative "lib/tacnet/packet"
require_relative "lib/tacnet/packet_handler" require_relative "lib/tacnet/packet_handler"