Compare commits
No commits in common. "54122c0c3224fcc82556b871aafc52c9f0bcb54b" and "913b9ab6087138777fab17faaba148f2407a657e" have entirely different histories.
54122c0c32
...
913b9ab608
@ -1,104 +0,0 @@
|
|||||||
<script>
|
|
||||||
import mqtt from "mqtt";
|
|
||||||
|
|
||||||
// Consts
|
|
||||||
let topics = ['display/#', 'division/#'];
|
|
||||||
let states = ["init", "timer", "pre-game", "event-name", "alliance-selection", "break", "red-wins", "blue-wins", "prairies", "rockies", "ab-education", "ab-innovate", "tcenergy", "tourismcalgary", "westmech"];
|
|
||||||
let led_sizes = {
|
|
||||||
"center": { width: 1176, height: 168 },
|
|
||||||
"side": { width: 896, height: 128 }
|
|
||||||
}
|
|
||||||
|
|
||||||
const client = mqtt.connect("ws://10.42.0.36:8883");
|
|
||||||
let displays = {};
|
|
||||||
let division_names = {};
|
|
||||||
let divisions = {};
|
|
||||||
let active_division = "1";
|
|
||||||
|
|
||||||
client.on("connect", () => {
|
|
||||||
console.log("connected to mqtt");
|
|
||||||
|
|
||||||
topics.forEach((topic) => {
|
|
||||||
client.subscribe(topic, (err) => {
|
|
||||||
if (err) {
|
|
||||||
console.log(err);
|
|
||||||
} else {
|
|
||||||
console.log(`subscribed to topic '${topic}'`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
client.on("message", (topic, message) => {
|
|
||||||
let display_field_match = /^display\/(\d+)\/(\d+)\/(\w+)$/.exec(topic);
|
|
||||||
let division_metadata_match = /^division\/(\d+)\/(\w+)$/.exec(topic);
|
|
||||||
let division_stat_match = /^division\/(\d+)\/(\w+)\/(\d+)(?:\/(\w+))?$/.exec(topic);
|
|
||||||
|
|
||||||
if(division_metadata_match) {
|
|
||||||
let [_, division, type] = division_metadata_match;
|
|
||||||
|
|
||||||
switch(type) {
|
|
||||||
case "name":
|
|
||||||
division_names[division] = message.toString();
|
|
||||||
console.log(`Set division ${type} for ${division} to ${message.toString()}`)
|
|
||||||
console.log(divisions);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
console.warn(`Unknown division metadata type: ${type}`);
|
|
||||||
}
|
|
||||||
} else if(division_stat_match) {
|
|
||||||
let [_, division, matchType, matchNumber, type] = division_stat_match;
|
|
||||||
type ||= "info";
|
|
||||||
|
|
||||||
divisions[division] ||= {};
|
|
||||||
divisions[division][matchType] ||= {};
|
|
||||||
divisions[division][matchType][matchNumber] ||= {};
|
|
||||||
|
|
||||||
divisions[division][matchType][matchNumber][type] ||= {};
|
|
||||||
} else if(display_field_match) {
|
|
||||||
let [_, division, field, type] = display_field_match;
|
|
||||||
|
|
||||||
displays[division] ||= {};
|
|
||||||
displays[division][field] ||= {};
|
|
||||||
displays[division][field][type] = message.toString();
|
|
||||||
console.log(displays[active_division]);
|
|
||||||
} else {
|
|
||||||
console.warn(`no match for topic ${topic}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="displays">
|
|
||||||
<select bind:value={active_division} on:change={() => console.log(`Changed: ${active_division}`)}>
|
|
||||||
{#each Object.entries(division_names) as [division, name]}
|
|
||||||
<option value={division} selected={active_division === division}>{name}</option>
|
|
||||||
{/each}
|
|
||||||
</select>
|
|
||||||
|
|
||||||
{#if division_names[active_division] === undefined}
|
|
||||||
<p>Division {active_division} undefined</p>
|
|
||||||
{:else}
|
|
||||||
<div class="division">
|
|
||||||
<h2>{`${division_names[active_division]} Division`}</h2>
|
|
||||||
{#each Object.entries(displays[active_division]) as [field, field_data] }
|
|
||||||
<div class="field">
|
|
||||||
<h3>{field} - {field_data.name}</h3>
|
|
||||||
<iframe src="/?field={active_division}/{field}" width={led_sizes[field_data.class].width} height={led_sizes[field_data.class].height} title="Field {active_division}/{field}"></iframe>
|
|
||||||
<br />
|
|
||||||
<select bind:value={field_data.class} on:change={() => client.publish(`display/${active_division}/${field}/class`, field_data.class, { retain: true })}>
|
|
||||||
{#each Object.entries(led_sizes) as [size, _]}
|
|
||||||
<option value={size}>{size}</option>
|
|
||||||
{/each}
|
|
||||||
</select>
|
|
||||||
<select bind:value={field_data.state} on:change={() => client.publish(`display/${active_division}/${field}/state`, field_data.state, { retain: true })}>
|
|
||||||
{#each states as s}
|
|
||||||
<option value={s}>
|
|
||||||
{s}
|
|
||||||
</option>
|
|
||||||
{/each}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
@ -1,227 +0,0 @@
|
|||||||
<script>
|
|
||||||
import mqtt from "mqtt";
|
|
||||||
|
|
||||||
let div_id;
|
|
||||||
|
|
||||||
|
|
||||||
/** @type {string[]} */
|
|
||||||
|
|
||||||
let red_teams = [];
|
|
||||||
let blue_teams = [];
|
|
||||||
let field_matches = [];
|
|
||||||
|
|
||||||
|
|
||||||
let latest_start_time = 0;
|
|
||||||
let active_field = 0;
|
|
||||||
let offset = 0;
|
|
||||||
|
|
||||||
|
|
||||||
let event_name = "Mecha Mayhem 2024";
|
|
||||||
let match_name = "No Match";
|
|
||||||
let match_name_show = "No Match";
|
|
||||||
/** @type {string[]} */
|
|
||||||
let teams_red = [];
|
|
||||||
/** @type {string[]} */
|
|
||||||
let teams_blue = [];
|
|
||||||
|
|
||||||
/** @type {undefined | Date} */
|
|
||||||
let timer_end = undefined;
|
|
||||||
/** @type {undefined | ReturnType<typeof setInterval>} */
|
|
||||||
let timer_next_tick = undefined;
|
|
||||||
let timer = "Next Match";
|
|
||||||
|
|
||||||
/** @type {string} */
|
|
||||||
let display_state = "init";
|
|
||||||
|
|
||||||
/** @type {string} */
|
|
||||||
let display_class = "side-display";
|
|
||||||
|
|
||||||
const client = mqtt.connect("ws://metznet.ca:8883");
|
|
||||||
|
|
||||||
function stop_timer() {
|
|
||||||
if(timer_next_tick === undefined) {
|
|
||||||
} else {
|
|
||||||
clearInterval(timer_next_tick);
|
|
||||||
timer_next_tick = undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function tick_timer() {
|
|
||||||
if(timer_end === undefined) {
|
|
||||||
stop_timer();
|
|
||||||
return
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
const now = new Date()
|
|
||||||
const time_diff = timer_end.getTime() - now.getTime() + offset;
|
|
||||||
if (time_diff <= 0) {
|
|
||||||
timer = "0:00";
|
|
||||||
stop_timer();
|
|
||||||
} else {
|
|
||||||
const seconds_total = Math.ceil(time_diff/ 1000);
|
|
||||||
const minutes = Math.floor(seconds_total / 60);
|
|
||||||
const seconds = seconds_total - (minutes * 60);
|
|
||||||
timer = `${minutes}:${seconds.toString().padStart(2, '0')}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
client.on("message", (topic, message) => {
|
|
||||||
/** @type {RegExp} */
|
|
||||||
let field_metadata_topic = /^field\/\d+\/(\d+)$/;
|
|
||||||
/** @type {RegExp} */
|
|
||||||
let field_state_topic = /^field\/\d+\/(\d+)\/state$/;
|
|
||||||
|
|
||||||
let topic_str = topic.toString();
|
|
||||||
let message_str = message.toString();
|
|
||||||
let field_state_match = field_state_topic.exec(topic_str);
|
|
||||||
let field_metadata_match = field_metadata_topic.exec(topic_str);
|
|
||||||
|
|
||||||
console.log(`${topic_str} - ${message_str}`);
|
|
||||||
|
|
||||||
if (field_state_match) {
|
|
||||||
console.log("here")
|
|
||||||
let [_, field] = field_state_match;
|
|
||||||
const state_obj = JSON.parse(message_str);
|
|
||||||
if (state_obj.state != "Scheduled") { return; }
|
|
||||||
if (state_obj.start <= latest_start_time) { return; }
|
|
||||||
|
|
||||||
latest_start_time = parseFloat(state_obj.start);
|
|
||||||
active_field = parseInt(field);
|
|
||||||
console.log(`${latest_start_time}`);
|
|
||||||
console.log(`active field: ${active_field}`);
|
|
||||||
|
|
||||||
let field_obj = field_matches[active_field];
|
|
||||||
red_teams = [field_obj.red_teams[0], field_obj.red_teams[1]];
|
|
||||||
blue_teams = [field_obj.blue_teams[0], field_obj.blue_teams[1]];
|
|
||||||
}
|
|
||||||
else if (field_metadata_match) {
|
|
||||||
let [_, field] = field_metadata_match;
|
|
||||||
const field_obj = JSON.parse(message_str);
|
|
||||||
|
|
||||||
field_matches[parseInt(field)] = field_obj;
|
|
||||||
if(field == active_field) {
|
|
||||||
red_teams = [field_obj.red_teams[0], field_obj.red_teams[1]];
|
|
||||||
blue_teams = [field_obj.blue_teams[0], field_obj.blue_teams[1]];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
console.log(`${topic_str} not defined`);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// switch(topic_str){
|
|
||||||
// case "time":
|
|
||||||
// let local = Date.now();
|
|
||||||
// let server = JSON.parse(message_str) * 1000;
|
|
||||||
// offset = local - server;
|
|
||||||
// console.log(`NEW offset: ${local} - ${server} = ${offset}`);
|
|
||||||
// break;
|
|
||||||
// case field_metadata_topic:
|
|
||||||
// const field_obj = JSON.parse(message_str);
|
|
||||||
// teams_red = [field_obj.red_teams[0], field_obj.red_teams[1]];
|
|
||||||
// teams_blue = [field_obj.blue_teams[0], field_obj.blue_teams[1]];
|
|
||||||
// let num = field_obj.tuple.match_num;
|
|
||||||
// let round = field_obj.tuple.round;
|
|
||||||
// match_name = `${round} ${num}`;
|
|
||||||
// break;
|
|
||||||
// case field_state_topic:
|
|
||||||
// stop_timer();
|
|
||||||
|
|
||||||
// const state_obj = JSON.parse(message_str);
|
|
||||||
// const start_ms = state_obj.start * 1000;
|
|
||||||
// console.log(`Start_ms: ${start_ms}`);
|
|
||||||
// switch(state_obj.state) {
|
|
||||||
// case "Scheduled":
|
|
||||||
// timer = match_name;
|
|
||||||
// match_name_show = "Next Match";
|
|
||||||
// if (state_obj.start > latest_start_time) {
|
|
||||||
// latest_start_time = state_obj.start;
|
|
||||||
// active_field = parseFloat(topic_str.split("/")[2]);
|
|
||||||
// console.log(`${latest_start_time}`);
|
|
||||||
// console.log(`${active_field}`);
|
|
||||||
// }
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
// break;
|
|
||||||
// default:
|
|
||||||
// console.log(`Unhandled topic ${topic_str}`)
|
|
||||||
// }
|
|
||||||
});
|
|
||||||
|
|
||||||
import { onMount } from 'svelte';
|
|
||||||
onMount(() => {
|
|
||||||
const search_params = new URLSearchParams(window.location.search);
|
|
||||||
div_id = search_params.get("division");
|
|
||||||
if (div_id === null) {
|
|
||||||
div_id = "default";
|
|
||||||
}
|
|
||||||
// field_metadata_topic = `field/${div_id}/+`;
|
|
||||||
// field_state_topic = `field/${div_id}/+/state`;
|
|
||||||
|
|
||||||
client.on("connect", () => {
|
|
||||||
console.log("connected to mqtt");
|
|
||||||
|
|
||||||
client.subscribe(`field/${div_id}/#`, (err) => {
|
|
||||||
if (err) {
|
|
||||||
console.log("failed to subscribe to field metadata")
|
|
||||||
console.log(err);
|
|
||||||
} else {
|
|
||||||
console.log(`subscribed`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="img-grid">
|
|
||||||
{#each Object.entries(red_teams) as [index, team]}
|
|
||||||
<div class="item">
|
|
||||||
<video autoplay loop muted playsinline src="./{team}.mp4" alt="video {team}"></video>
|
|
||||||
<div class="text">{team}</div>
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
{#each Object.entries(blue_teams) as [index, team]}
|
|
||||||
<div class="item">
|
|
||||||
<video autoplay loop muted playsinline src="./{team}.mp4" alt="video {team}"></video>
|
|
||||||
<div class="text">{team}</div>
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
@import url('https://fonts.googleapis.com/css2?family=Bebas+Neue&display=swap');
|
|
||||||
|
|
||||||
.img-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(2, lfr);
|
|
||||||
grid-template-rows: repeat(2, lfr);
|
|
||||||
gap: 20px;
|
|
||||||
width: 1920px;
|
|
||||||
height: 1080px;
|
|
||||||
justify-content: center;
|
|
||||||
align-content: center;
|
|
||||||
margin: auto;
|
|
||||||
background-color: #2ae400;
|
|
||||||
font-family: 'Bebas Neue';
|
|
||||||
font-weight: 400;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
img {
|
|
||||||
width: 100%;
|
|
||||||
height: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text {
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
</style>
|
|
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 1.2 MiB |
After Width: | Height: | Size: 2.6 MiB |
After Width: | Height: | Size: 820 KiB |
After Width: | Height: | Size: 65 MiB |
After Width: | Height: | Size: 1.2 MiB |
After Width: | Height: | Size: 25 MiB |
After Width: | Height: | Size: 739 KiB |
After Width: | Height: | Size: 25 MiB |
After Width: | Height: | Size: 1.3 MiB |
After Width: | Height: | Size: 1.1 MiB |
After Width: | Height: | Size: 24 MiB |
After Width: | Height: | Size: 3.2 MiB |