new features: Mute, and different sounds
This commit is contained in:
1 parent
81ddf048ca
commit
cbd7d02ad8
6 files changed
+506
-48
No files matched your search
@@ -1,6 +1,7 @@
|
|||||||
import { createRouter, createWebHistory } from 'vue-router'
|
import { createRouter, createWebHistory } from 'vue-router'
|
||||||
import Home from '../views/Home.vue'
|
import Home from '../views/Home.vue'
|
||||||
import Maintenance from '../views/Maintenance.vue'
|
import Maintenance from '../views/Maintenance.vue'
|
||||||
|
import Setup from '../views/Setup.vue'
|
||||||
import Players from '../views/Players.vue'
|
import Players from '../views/Players.vue'
|
||||||
import Start from '../views/Start.vue'
|
import Start from '../views/Start.vue'
|
||||||
import Text from '../views/Text.vue'
|
import Text from '../views/Text.vue'
|
||||||
@@ -28,6 +29,11 @@ const routes: any[] = [
|
|||||||
name: 'TextDirect',
|
name: 'TextDirect',
|
||||||
component: Text
|
component: Text
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/setup',
|
||||||
|
name: 'Setup',
|
||||||
|
component: Setup
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/maintenance',
|
path: '/maintenance',
|
||||||
name: 'Maintenance',
|
name: 'Maintenance',
|
||||||
|
|||||||
+52
-20
@@ -30,11 +30,11 @@ export function getMqttClient(): any {
|
|||||||
export function publishStartHit(): void {
|
export function publishStartHit(): void {
|
||||||
const tempClient = getClient()
|
const tempClient = getClient()
|
||||||
|
|
||||||
const mqttTopic = mqttTopicRoot + 'run'
|
const mqttTopic = mqttTopicRoot + 'set'
|
||||||
|
|
||||||
tempClient.on('connect', () => {
|
tempClient.on('connect', () => {
|
||||||
tempClient.publish(mqttTopic, mqttMessage, { qos: 1 }, () => {
|
tempClient.publish(mqttTopic, mqttMessage, { qos: 1 }, () => {
|
||||||
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -71,28 +71,28 @@ export function subscribeToIntTopic(callback: (response: IntTopicResponse) => vo
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface IntTopicResponse {
|
interface IntTopicResponse {
|
||||||
version: string
|
|
||||||
mode: number
|
mode: number
|
||||||
sE: boolean
|
sE: boolean
|
||||||
sS: boolean
|
sS: boolean
|
||||||
sP: boolean
|
sP: boolean
|
||||||
lE: boolean
|
lE: boolean
|
||||||
|
snd: boolean
|
||||||
|
vol: number
|
||||||
|
delay: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parseIntMessage(message: string): IntTopicResponse {
|
export function parseIntMessage(message: string): IntTopicResponse {
|
||||||
const result: IntTopicResponse = {
|
const result: IntTopicResponse = {
|
||||||
version: '',
|
|
||||||
mode: 0,
|
mode: 0,
|
||||||
sE: false, // EndPoint
|
sE: false, // EndPoint
|
||||||
sS: false, // StartPoint
|
sS: false, // StartPoint
|
||||||
sP: false, // PullSensor
|
sP: false, // PullSensor
|
||||||
lE: false // LatchEnable
|
lE: false, // LatchEnable
|
||||||
|
snd: false, // LatchEnable
|
||||||
|
vol: 0, // Volumen
|
||||||
|
delay: 0, // Delay
|
||||||
}
|
}
|
||||||
|
|
||||||
const versionMatch = message.match(/T([\d.]+)/)
|
|
||||||
if (versionMatch) {
|
|
||||||
result.version = versionMatch[1]
|
|
||||||
}
|
|
||||||
const modeMatch = message.match(/Mode=(\d+)/)
|
const modeMatch = message.match(/Mode=(\d+)/)
|
||||||
if (modeMatch) {
|
if (modeMatch) {
|
||||||
result.mode = parseInt(modeMatch[1], 10)
|
result.mode = parseInt(modeMatch[1], 10)
|
||||||
@@ -113,19 +113,33 @@ export function parseIntMessage(message: string): IntTopicResponse {
|
|||||||
if (lEMatch) {
|
if (lEMatch) {
|
||||||
result.lE = lEMatch[1] === 'true'
|
result.lE = lEMatch[1] === 'true'
|
||||||
}
|
}
|
||||||
|
const soundMute = message.match(/snd:(true|false)/)
|
||||||
|
if (soundMute) {
|
||||||
|
result.snd = soundMute[1] === 'true'
|
||||||
|
}
|
||||||
|
const volMatch = message.match(/v:(\d+)/)
|
||||||
|
if (volMatch) {
|
||||||
|
result.vol = Number(volMatch[1])
|
||||||
|
}
|
||||||
|
const delayMatch = message.match(/pD:(\d+)/)
|
||||||
|
if (delayMatch) {
|
||||||
|
result.delay = Number(delayMatch[1])
|
||||||
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
export function enterMaintananceMode(
|
export function enterMaintananceMode(
|
||||||
callback: (sE: number, sS: number, sP: number, lE: number) => void)
|
statCallback: (snd:number, sE: number, sS: number, sP: number, lE: number, vol: number, delay: number) => void,
|
||||||
|
verCallback: (ver: string) => void)
|
||||||
: mqtt.MqttClient {
|
: mqtt.MqttClient {
|
||||||
const client = getClient()
|
const client = getClient()
|
||||||
|
|
||||||
const mqttTopic = mqttTopicRoot + 'stat'
|
const statMqttTopic = mqttTopicRoot + 'stat'
|
||||||
|
const verMqttTopic = mqttTopicRoot + 'ver'
|
||||||
|
|
||||||
client.on('connect', () => {
|
client.on('connect', () => {
|
||||||
publicMaintananceCmd(client, "SetMaintanance")
|
publishCmd(client, "SetMaintanance")
|
||||||
client.subscribe(mqttTopic, { qos: 1 }, (error) => {
|
client.subscribe(statMqttTopic, { qos: 1 }, (error) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error('MQTT subscribe failed:', error)
|
console.error('MQTT subscribe failed:', error)
|
||||||
}
|
}
|
||||||
@@ -137,14 +151,22 @@ export function enterMaintananceMode(
|
|||||||
})
|
})
|
||||||
|
|
||||||
client.on('message', (topic: string, message: Buffer) => {
|
client.on('message', (topic: string, message: Buffer) => {
|
||||||
if (topic === mqttTopic) {
|
console.log(`MQTT: Got ${topic}: ${message}`)
|
||||||
|
if (topic === statMqttTopic) {
|
||||||
const messageStr = message.toString()
|
const messageStr = message.toString()
|
||||||
const status = parseIntMessage(message.toString())
|
const status = parseIntMessage(messageStr)
|
||||||
|
const snd = status.snd ? 1 : 0
|
||||||
const sE = status.sE ? 1 : 0
|
const sE = status.sE ? 1 : 0
|
||||||
const sS = status.sS ? 1 : 0
|
const sS = status.sS ? 1 : 0
|
||||||
const sP = status.sP ? 1 : 0
|
const sP = status.sP ? 1 : 0
|
||||||
const lE = status.lE ? 1 : 0
|
const lE = status.lE ? 1 : 0
|
||||||
callback(sE, sS, sP, lE)
|
const vol = status.vol
|
||||||
|
const delay = status.delay
|
||||||
|
statCallback(snd, sE, sS, sP, lE, vol, delay)
|
||||||
|
}
|
||||||
|
if (topic === verMqttTopic) {
|
||||||
|
const messageStr = message.toString()
|
||||||
|
verCallback(messageStr)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -152,7 +174,7 @@ export function enterMaintananceMode(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function exitMaintananceMode(client: mqtt.MqttClient): void {
|
export function exitMaintananceMode(client: mqtt.MqttClient): void {
|
||||||
publicMaintananceCmd(client, "Exit")
|
publishCmd(client, "Exit")
|
||||||
const mqttTopic = mqttTopicRoot + 'stat'
|
const mqttTopic = mqttTopicRoot + 'stat'
|
||||||
client.unsubscribe(mqttTopic, (error) => {
|
client.unsubscribe(mqttTopic, (error) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
@@ -162,10 +184,20 @@ export function exitMaintananceMode(client: mqtt.MqttClient): void {
|
|||||||
client.end()
|
client.end()
|
||||||
}
|
}
|
||||||
|
|
||||||
export function publicMaintananceCmd(client: mqtt.MqttClient, message : string): void {
|
export function publishCmd(client: mqtt.MqttClient, message: string): void {
|
||||||
const mqttTopic = mqttTopicRoot + 'run'
|
const mqttTopic = mqttTopicRoot + 'cfg'
|
||||||
console.log(`MQTT: Publish ${mqttTopic}: ${message}`)
|
console.log(`MQTT: Publish ${mqttTopic}: ${message}`)
|
||||||
client.publish(mqttTopic, message, { qos: 1 }, (error) => {
|
client.publish(mqttTopic, message, { qos: 2 }, (error) => {
|
||||||
|
if (error) {
|
||||||
|
console.error('MQTT publish failed:', error)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function publishQueryVersionCmd(client: mqtt.MqttClient) {
|
||||||
|
const mqttTopic = mqttTopicRoot + 'ver'
|
||||||
|
console.log(`MQTT: Publish ${mqttTopic}`)
|
||||||
|
client.publish(mqttTopic, "", { qos: 1 }, (error) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error('MQTT publish failed:', error)
|
console.error('MQTT publish failed:', error)
|
||||||
}
|
}
|
||||||
|
|||||||
+45
-5
@@ -7,19 +7,34 @@
|
|||||||
<div class="info-section">
|
<div class="info-section">
|
||||||
<div class="info-box">
|
<div class="info-box">
|
||||||
<h3>Version</h3>
|
<h3>Version</h3>
|
||||||
<p id="version-number">1.0.0</p>
|
<p id="version-number">0.8.0</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="info-box">
|
<div class="info-box">
|
||||||
<h3>Service Hotline</h3>
|
<h3>Service Hotline</h3>
|
||||||
<p id="service-hotline">+1 800-123-4567</p>
|
<p id="service-hotline">+49 176 64036128</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<nav class="home-nav">
|
<div class="status-container">
|
||||||
|
<span class="status-label">Sword</span>
|
||||||
|
<div class="status-frame setup-frame">
|
||||||
|
<router-link to="/setup" class="nav-button">Setup</router-link>
|
||||||
|
<router-link to="/maintenance" class="nav-button">Maintenance</router-link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="status-container">
|
||||||
|
<span class="status-label">Throne</span>
|
||||||
|
<div class="status-frame setup-frame">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="status-container">
|
||||||
|
<span class="status-label">Game</span>
|
||||||
|
<div class="status-frame setup-frame">
|
||||||
<router-link to="/players" class="nav-button">Players</router-link>
|
<router-link to="/players" class="nav-button">Players</router-link>
|
||||||
<router-link to="/start" class="nav-button">Start</router-link>
|
<router-link to="/start" class="nav-button">Start</router-link>
|
||||||
<router-link to="/text" class="nav-button">Text</router-link>
|
<router-link to="/text" class="nav-button">Text</router-link>
|
||||||
<router-link to="/maintenance" class="nav-button">Maintenance</router-link>
|
</div>
|
||||||
</nav>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
@@ -27,3 +42,28 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
.status-container {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-frame {
|
||||||
|
border: 2px solid #d1d5db;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 16px;
|
||||||
|
background: #f9fafb;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-label {
|
||||||
|
font-size: 12px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
+142
-23
@@ -4,16 +4,17 @@
|
|||||||
|
|
||||||
<h2>Sword Status</h2>
|
<h2>Sword Status</h2>
|
||||||
<div class="status-container">
|
<div class="status-container">
|
||||||
|
|
||||||
<h3>Inputs</h3>
|
<h3>Inputs</h3>
|
||||||
<div class="status-frame">
|
<div class="status-frame">
|
||||||
<div class="status-item" :class="{ active: endSensStatus === 1 }">
|
|
||||||
<span class="status-label">EndSens:</span>
|
|
||||||
<span class="status-value">{{ endSensStatus }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="status-item" :class="{ active: startSensStatus === 1 }">
|
<div class="status-item" :class="{ active: startSensStatus === 1 }">
|
||||||
<span class="status-label">StartSens:</span>
|
<span class="status-label">StartSens:</span>
|
||||||
<span class="status-value">{{ startSensStatus }}</span>
|
<span class="status-value">{{ startSensStatus }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="status-item" :class="{ active: endSensStatus === 1 }">
|
||||||
|
<span class="status-label">EndSens:</span>
|
||||||
|
<span class="status-value">{{ endSensStatus }}</span>
|
||||||
|
</div>
|
||||||
<div class="status-item" :class="{ active: pullSensStatus === 1 }">
|
<div class="status-item" :class="{ active: pullSensStatus === 1 }">
|
||||||
<span class="status-label">PullSens:</span>
|
<span class="status-label">PullSens:</span>
|
||||||
<span class="status-value">{{ pullSensStatus }}</span>
|
<span class="status-value">{{ pullSensStatus }}</span>
|
||||||
@@ -29,14 +30,61 @@
|
|||||||
<div class="control-buttons">
|
<div class="control-buttons">
|
||||||
<button @click="handleLock" class="btn-lock">Lock</button>
|
<button @click="handleLock" class="btn-lock">Lock</button>
|
||||||
<button @click="handleUnlock" class="btn-unlock">Unlock</button>
|
<button @click="handleUnlock" class="btn-unlock">Unlock</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Sound</h2>
|
||||||
|
<div class="status-frame">
|
||||||
|
<div class="status-item" :class="{ active: soundStatus === 1 }">
|
||||||
|
<span class="status-label">Sound Active:</span>
|
||||||
|
<span class="status-value">{{ soundStatus }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="status-frame">
|
||||||
|
<div class="control-buttons">
|
||||||
|
<button @click="handleMute" class="btn-lock">Mute</button>
|
||||||
|
<button @click="handleUnMute" class="btn-unlock">Un-Mute</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>Sounds</h3>
|
||||||
|
<div class="status-frame">
|
||||||
|
<div class="control-buttons">
|
||||||
<button @click="handlePlayX(1)" class="btn-unlock">Play sound 1</button>
|
<button @click="handlePlayX(1)" class="btn-unlock">Play sound 1</button>
|
||||||
<button @click="handlePlayX(2)" class="btn-unlock">Play sound 2</button>
|
<button @click="handlePlayX(2)" class="btn-unlock">Play sound 2</button>
|
||||||
<button @click="handlePlayX(3)" class="btn-unlock">Play sound 3</button>
|
<button @click="handlePlayX(3)" class="btn-unlock">Play sound 3</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3>Status</h3>
|
<h2>Setup</h2>
|
||||||
|
<div class="status-container">
|
||||||
|
<div class="status-frame setup-frame">
|
||||||
|
<div class="slider-item">
|
||||||
|
<div class="slider-header">
|
||||||
|
<span class="slider-label">Volume</span>
|
||||||
|
<span class="slider-value">{{ volume }}</span>
|
||||||
|
</div>
|
||||||
|
<input class="slider-input" type="range" min="0" max="100" step="1" v-model.number="volume"
|
||||||
|
@input="handleVolume" />
|
||||||
|
</div>
|
||||||
|
<div class="slider-item">
|
||||||
|
<div class="slider-header">
|
||||||
|
<span class="slider-label">Promo Delay</span>
|
||||||
|
<span class="slider-value">{{ promoDelay }}</span>
|
||||||
|
</div>
|
||||||
|
<input class="slider-input" type="range" min="30" max="300" step="1" v-model.number="promoDelay"
|
||||||
|
@input="handlePromoDelay" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>Info</h3>
|
||||||
|
<div class="status-container">
|
||||||
|
<div class="status-frame">
|
||||||
|
<span class="status-label">Version:</span>
|
||||||
|
<span class="status-value">{{ version }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h2>Game Views</h2>
|
<h2>Game Views</h2>
|
||||||
@@ -55,61 +103,97 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { enterMaintananceMode, exitMaintananceMode, publicMaintananceCmd } from '../services/mqtt'
|
import { enterMaintananceMode, exitMaintananceMode, publishCmd, publishQueryVersionCmd } from '../services/mqtt'
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
import { ref, onMounted, onUnmounted } from 'vue'
|
import { ref, onMounted, onUnmounted } from 'vue'
|
||||||
import type mqtt from 'mqtt'
|
import type mqtt from 'mqtt'
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
|
||||||
|
const version = ref<string>()
|
||||||
const endSensStatus = ref<number>(0)
|
const endSensStatus = ref<number>(0)
|
||||||
const startSensStatus = ref<number>(0)
|
const startSensStatus = ref<number>(0)
|
||||||
const pullSensStatus = ref<number>(0)
|
const pullSensStatus = ref<number>(0)
|
||||||
const lockStatus = ref<number>(0)
|
const lockStatus = ref<number>(0)
|
||||||
|
const soundStatus = ref<number>(0)
|
||||||
|
const volume = ref<number>(50)
|
||||||
|
const promoDelay = ref<number>(180)
|
||||||
|
|
||||||
let statTopicClient: mqtt.MqttClient | null = null
|
let mqttClient: mqtt.MqttClient | null = null
|
||||||
|
|
||||||
function isActive(path: string): boolean {
|
function isActive(path: string): boolean {
|
||||||
return route.path === path
|
return route.path === path
|
||||||
}
|
}
|
||||||
|
|
||||||
function cleanup(): void {
|
function cleanup(): void {
|
||||||
if (statTopicClient) {
|
if (mqttClient) {
|
||||||
exitMaintananceMode(statTopicClient)
|
exitMaintananceMode(mqttClient)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleLock(): void {
|
function handleLock(): void {
|
||||||
if (statTopicClient) {
|
if (mqttClient) {
|
||||||
publicMaintananceCmd(statTopicClient, "ServiceClose")
|
publishCmd(mqttClient, "closeLock")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleUnlock(): void {
|
function handleUnlock(): void {
|
||||||
if (statTopicClient) {
|
if (mqttClient) {
|
||||||
publicMaintananceCmd(statTopicClient, "ServiceOpen")
|
publishCmd(mqttClient, "openLock")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleMute(): void {
|
||||||
|
if (mqttClient) {
|
||||||
|
publishCmd(mqttClient, "mute")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleUnMute(): void {
|
||||||
|
if (mqttClient) {
|
||||||
|
publishCmd(mqttClient, "unmute")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleVolume(): void {
|
||||||
|
if (mqttClient) {
|
||||||
|
publishCmd(mqttClient, `volume:${volume.value}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handlePromoDelay(): void {
|
||||||
|
if (mqttClient) {
|
||||||
|
publishCmd(mqttClient, `promoDelay:${promoDelay.value}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handlePlayX(sound: number): void {
|
function handlePlayX(sound: number): void {
|
||||||
if (statTopicClient) {
|
if (mqttClient) {
|
||||||
if (sound === 1) {
|
if (sound === 1) {
|
||||||
publicMaintananceCmd(statTopicClient, "ServicePlay1")
|
publishCmd(mqttClient, "playSound:Promo")
|
||||||
} else if (sound === 2) {
|
} else if (sound === 2) {
|
||||||
publicMaintananceCmd(statTopicClient, "ServicePlay2")
|
publishCmd(mqttClient, "playSound:Deny")
|
||||||
} else if (sound === 3) {
|
} else if (sound === 3) {
|
||||||
publicMaintananceCmd(statTopicClient, "ServicePlay3")
|
publishCmd(mqttClient, "playSound:Chosen")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
statTopicClient = enterMaintananceMode((endSens, startSens, pullSens, lock) => {
|
mqttClient = enterMaintananceMode(
|
||||||
endSensStatus.value = endSens
|
(sound, endSens, startSens, pullSens, lock, vol, delay) => {
|
||||||
startSensStatus.value = startSens
|
endSensStatus.value = endSens
|
||||||
pullSensStatus.value = pullSens
|
startSensStatus.value = startSens
|
||||||
lockStatus.value = lock
|
pullSensStatus.value = pullSens
|
||||||
})
|
lockStatus.value = lock
|
||||||
|
soundStatus.value = sound
|
||||||
|
volume.value = vol
|
||||||
|
promoDelay.value = delay
|
||||||
|
},
|
||||||
|
(ver) => {
|
||||||
|
version.value = ver
|
||||||
|
}
|
||||||
|
)
|
||||||
window.addEventListener('beforeunload', cleanup)
|
window.addEventListener('beforeunload', cleanup)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -135,6 +219,41 @@ onUnmounted(() => {
|
|||||||
gap: 12px;
|
gap: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.setup-frame {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider-label {
|
||||||
|
font-size: 12px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
color: #6b7280;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider-value {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #111827;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider-input {
|
||||||
|
width: 100%;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
.status-item {
|
.status-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|||||||
@@ -0,0 +1,259 @@
|
|||||||
|
<template>
|
||||||
|
<main class="app">
|
||||||
|
<h1>Setup View</h1>
|
||||||
|
|
||||||
|
<h2>Setup</h2>
|
||||||
|
<div class="status-container">
|
||||||
|
<div class="status-frame setup-frame">
|
||||||
|
<div class="slider-item">
|
||||||
|
<div class="slider-header">
|
||||||
|
<span class="slider-label">Volume</span>
|
||||||
|
<span class="slider-value">{{ volume }}</span>
|
||||||
|
</div>
|
||||||
|
<input class="slider-input" type="range" min="0" max="100" step="1" v-model.number="volume"
|
||||||
|
@input="handleVolume" />
|
||||||
|
</div>
|
||||||
|
<div class="slider-item">
|
||||||
|
<div class="slider-header">
|
||||||
|
<span class="slider-label">Promo Delay</span>
|
||||||
|
<span class="slider-value">{{ promoDelay }}</span>
|
||||||
|
</div>
|
||||||
|
<input class="slider-input" type="range" min="30" max="300" step="1" v-model.number="promoDelay"
|
||||||
|
@input="handlePromoDelay" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Sound</h2>
|
||||||
|
<div class="status-frame">
|
||||||
|
<div class="status-item" :class="{ active: soundStatus === 1 }">
|
||||||
|
<span class="status-label">Sound Active:</span>
|
||||||
|
<span class="status-value">{{ soundStatus }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="status-frame">
|
||||||
|
<div class="control-buttons">
|
||||||
|
<button @click="handleMute" class="btn-lock">Mute</button>
|
||||||
|
<button @click="handleUnMute" class="btn-unlock">Un-Mute</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Sword Status</h2>
|
||||||
|
<div class="status-container">
|
||||||
|
<div class="status-frame">
|
||||||
|
<div class="status-item" :class="{ active: endSensStatus === 1 }">
|
||||||
|
<span class="status-label">Sword Present:</span>
|
||||||
|
<span class="status-value">{{ endSensStatus }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="status-item" :class="{ active: lockStatus === 1 }">
|
||||||
|
<span class="status-label">Lock Active:</span>
|
||||||
|
<span class="status-value">{{ lockStatus }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { enterMaintananceMode, exitMaintananceMode, publishCmd } from '../services/mqtt'
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
|
import { ref, onMounted, onUnmounted } from 'vue'
|
||||||
|
import type mqtt from 'mqtt'
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
|
||||||
|
const version = ref<string>()
|
||||||
|
const endSensStatus = ref<number>(0)
|
||||||
|
const startSensStatus = ref<number>(0)
|
||||||
|
const pullSensStatus = ref<number>(0)
|
||||||
|
const lockStatus = ref<number>(0)
|
||||||
|
const soundStatus = ref<number>(0)
|
||||||
|
const volume = ref<number>(50)
|
||||||
|
const promoDelay = ref<number>(180)
|
||||||
|
|
||||||
|
let mqttClient: mqtt.MqttClient | null = null
|
||||||
|
|
||||||
|
function cleanup(): void {
|
||||||
|
if (mqttClient) {
|
||||||
|
exitMaintananceMode(mqttClient)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleMute(): void {
|
||||||
|
if (mqttClient) {
|
||||||
|
publishCmd(mqttClient, "mute")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleUnMute(): void {
|
||||||
|
if (mqttClient) {
|
||||||
|
publishCmd(mqttClient, "unmute")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleVolume(): void {
|
||||||
|
if (mqttClient) {
|
||||||
|
publishCmd(mqttClient, `volume:${volume.value}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handlePromoDelay(): void {
|
||||||
|
if (mqttClient) {
|
||||||
|
publishCmd(mqttClient, `promoDelay:${promoDelay.value}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handlePlayX(sound: number): void {
|
||||||
|
if (mqttClient) {
|
||||||
|
if (sound === 1) {
|
||||||
|
publishCmd(mqttClient, "playSound:Promo")
|
||||||
|
} else if (sound === 2) {
|
||||||
|
publishCmd(mqttClient, "playSound:Deny")
|
||||||
|
} else if (sound === 3) {
|
||||||
|
publishCmd(mqttClient, "playSound:Chosen")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
mqttClient = enterMaintananceMode(
|
||||||
|
(sound, endSens, startSens, pullSens, lock, vol, delay) => {
|
||||||
|
endSensStatus.value = endSens
|
||||||
|
startSensStatus.value = startSens
|
||||||
|
pullSensStatus.value = pullSens
|
||||||
|
lockStatus.value = lock
|
||||||
|
soundStatus.value = sound
|
||||||
|
volume.value = vol
|
||||||
|
promoDelay.value = delay
|
||||||
|
},
|
||||||
|
(ver) => {})
|
||||||
|
window.addEventListener('beforeunload', cleanup)
|
||||||
|
if (mqttClient) {
|
||||||
|
publishCmd(mqttClient, `promoDelay:${promoDelay.value}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
window.removeEventListener('beforeunload', cleanup)
|
||||||
|
cleanup()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.status-container {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-frame {
|
||||||
|
border: 2px solid #d1d5db;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 16px;
|
||||||
|
background: #f9fafb;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setup-frame {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider-label {
|
||||||
|
font-size: 12px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
color: #6b7280;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider-value {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #111827;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider-input {
|
||||||
|
width: 100%;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 12px;
|
||||||
|
border-radius: 6px;
|
||||||
|
background: #e5e7eb;
|
||||||
|
color: #6b7280;
|
||||||
|
transition: background 0.3s, color 0.3s;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-item.active {
|
||||||
|
background: #10b981;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-label {
|
||||||
|
font-size: 12px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-value {
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-buttons {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-lock,
|
||||||
|
.btn-unlock {
|
||||||
|
padding: 10px 20px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.2s, color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-lock {
|
||||||
|
background: #111827;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-lock:hover {
|
||||||
|
background: #1f2937;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-unlock {
|
||||||
|
background: #e5e7eb;
|
||||||
|
color: #111827;
|
||||||
|
border: 1px solid #d1d5db;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-unlock:hover {
|
||||||
|
background: #d1d5db;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Reference in new issue
Block a user