<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Aac on HandShakeTec</title><link>https://handshaketec.com/tags/aac/</link><description>Recent content in Aac on HandShakeTec</description><generator>Hugo</generator><language>en-GB</language><copyright>&amp;copy; 2026 Matt Oppenheim. All rights reserved.</copyright><lastBuildDate>Fri, 19 Jun 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://handshaketec.com/tags/aac/index.xml" rel="self" type="application/rss+xml"/><item><title>HandShake T-Watch S3 configuration</title><link>https://handshaketec.com/watch_configuration/</link><pubDate>Fri, 19 Jun 2026 00:00:00 +0000</pubDate><guid>https://handshaketec.com/watch_configuration/</guid><description>&lt;!-- enable hypermedia by importing from unpkg.com -->
&lt;head>
&lt;script src="https://unpkg.com/htmx.org@2.0.2" crossorigin="anonymous">&lt;/script>
&lt;/head>
&lt;h1 id="introduction">Introduction&lt;/h1>
&lt;p>HandShake configuration tool for the T-Watch S3.&lt;/p>
&lt;p>&lt;strong>Under test - something will need fixing!&lt;/strong>&lt;/p>

&lt;div class="session-box" id="session-box">
 &lt;h2>Your session name&lt;/h2>
 &lt;div class="session-name" id="session-name">…&lt;/div>
 &lt;div class="name-change">
 &lt;input type="text" id="new-god-name" placeholder="e.g. hermes" autocomplete="off">
 &lt;button type="button" onclick="changeSessionName()">Change&lt;/button>
 &lt;span id="name-status">&lt;/span>
 &lt;/div>
&lt;/div>

&lt;style>
 .session-box { background: #f0f4ff; border: 1px solid #b0c0e0; border-radius: 6px;
 padding: 1em 1.2em; margin-bottom: 1.5em; }
 .session-box h2 { margin: 0 0 0.4em; font-size: 1em; color: #444; }
 .session-name { font-size: 1.4em; font-weight: bold; color: #2244aa;
 text-transform: capitalize; }
 .name-change { display: flex; gap: 0.5em; margin-top: 0.7em; align-items: center; }
 .name-change input { padding: 0.3em 0.5em; border: 1px solid #aaa; border-radius: 4px;
 font-size: 0.95em; width: 160px; }
 .name-change button { padding: 0.3em 0.8em; border: none; border-radius: 4px;
 background: #3355cc; color: white; cursor: pointer; font-size: 0.95em; }
 .name-change button:hover { background: #2244aa; }
 #name-status { font-size: 0.9em; color: #555; }
 #name-status.error { color: #cc2222; }
&lt;/style>

&lt;script>
 
 fetch('/whoami')
 .then(r => r.json().then(data => ({ ok: r.ok, data })))
 .then(({ ok, data }) => {
 const el = document.getElementById('session-name');
 if (ok &amp;&amp; data.god_name) {
 el.textContent = data.god_name;
 } else if (data.error === 'server_full') {
 el.textContent = '(server full)';
 el.style.color = '#cc2222';
 document.getElementById('name-status').textContent =
 'All ' + data.max_users + ' sessions are in use — try again later.';
 } else {
 el.textContent = '(unavailable)';
 }
 })
 .catch(() => {
 document.getElementById('session-name').textContent = '(offline)';
 });

 function changeSessionName() {
 const input = document.getElementById('new-god-name');
 const status = document.getElementById('name-status');
 const name = input.value.trim().toLowerCase();

 if (!name) {
 status.textContent = 'Please enter a name.';
 status.className = 'error';
 return;
 }

 status.textContent = 'Changing...';
 status.className = '';

 const body = new URLSearchParams();
 body.append('god_name', name);

 fetch('/set_god_name', { method: 'POST', body: body })
 .then(r => r.text().then(text => ({ ok: r.ok, text })))
 .then(({ ok, text }) => {
 if (ok) {
 document.getElementById('session-name').textContent = text;
 status.textContent = 'Name changed.';
 status.className = '';
 input.value = '';
 } else {
 status.textContent = text;
 status.className = 'error';
 }
 })
 .catch(() => {
 status.textContent = 'Network error.';
 status.className = 'error';
 });
 }
&lt;/script>

&lt;h1 id="test-form-is-json-enabled">Gesture Selection&lt;/h1>
&lt;form hx-target="#gesture_response" hx-post="/submit_gesture" hx-swap="innerHTML" id="gesture_to_detect">
 &lt;label for="punch_gesture">punch gesture&lt;/label>
 &lt;input type="radio" id="option1" name="gesture_to_detect" value="punch_gesture" placeholder="punch" checked>&lt;br>
 &lt;label for="roll_gesture">roll gesture&lt;/label>
 &lt;input type="radio" id="option2" name="gesture_to_detect" value="roll_gesture" placeholder="roll">&lt;br>
 &lt;input type="submit" value="submit_gesture">
&lt;/form>
&lt;div id="gesture_response">&lt;/div>

&lt;h1 id="gesture-sensitivity-control">Gesture Sensitivity Control&lt;/h1>
&lt;form hx-target="#sensitivity_response" hx-post="/submit_sensitivity" hx-swap="innerHTML" id="sensitivity_form">
 &lt;label for="gesture_sensitivity">Gesture Sensitivity (580-900):&lt;/label>&lt;br>
 &lt;input type="range" id="gesture_sensitivity" name="gesture_sensitivity" min="580" max="900" value="740" step="10" oninput="this.nextElementSibling.value = this.value">
 &lt;output>740&lt;/output>&lt;br>&lt;br>
 &lt;input type="submit" value="submit_sensitivity">
&lt;/form>
&lt;div id="sensitivity_response">&lt;/div>

&lt;h1 id="esp-now-mac-address-configuration">ESP-NOW MAC Address Configuration&lt;/h1>
&lt;style>
.btn-active {
 background-color: #4CAF50 !important;
 color: white !important;
 border: 2px solid #45a049 !important;
}

.btn-flash {
 animation: flash-green 0.5s;
}

@keyframes flash-green {
 0%, 100% { background-color: initial; }
 50% { background-color: #4CAF50; color: white; }
}
&lt;/style>

&lt;button id="esp_now_button" style="padding: 8px 16px;">ESP-NOW target MAC&lt;/button>

&lt;div id="mac_address_section" style="display: none; margin-top: 20px;">
 &lt;form hx-target="#mac_response" hx-post="/submit_mac" hx-swap="innerHTML" hx-on::after-request="handleMacSubmit(event)" id="mac_form">
 &lt;label for="mac_address">MAC Address (6 pairs of hex values):&lt;/label>&lt;br>
 &lt;div style="display: flex; align-items: center; gap: 5px; margin-top: 10px;">
 &lt;input type="text" class="mac-input" id="mac1" name="mac1" maxlength="2" pattern="[0-9A-Fa-f]{2}" required style="width: 35px; text-align: center;">
 &lt;span>,&lt;/span>
 &lt;input type="text" class="mac-input" id="mac2" name="mac2" maxlength="2" pattern="[0-9A-Fa-f]{2}" required style="width: 35px; text-align: center;">
 &lt;span>,&lt;/span>
 &lt;input type="text" class="mac-input" id="mac3" name="mac3" maxlength="2" pattern="[0-9A-Fa-f]{2}" required style="width: 35px; text-align: center;">
 &lt;span>,&lt;/span>
 &lt;input type="text" class="mac-input" id="mac4" name="mac4" maxlength="2" pattern="[0-9A-Fa-f]{2}" required style="width: 35px; text-align: center;">
 &lt;span>,&lt;/span>
 &lt;input type="text" class="mac-input" id="mac5" name="mac5" maxlength="2" pattern="[0-9A-Fa-f]{2}" required style="width: 35px; text-align: center;">
 &lt;span>,&lt;/span>
 &lt;input type="text" class="mac-input" id="mac6" name="mac6" maxlength="2" pattern="[0-9A-Fa-f]{2}" required style="width: 35px; text-align: center;">
 &lt;/div>
 &lt;br>
 &lt;input type="submit" value="Submit MAC" id="submit_mac_button" style="padding: 8px 16px;">
 &lt;/form>
 &lt;div id="mac_response">&lt;/div>
&lt;/div>

&lt;script>
var espButton = document.getElementById('esp_now_button');
var macSection = document.getElementById('mac_address_section');

function toggleMacDisplay() {
 var section = document.getElementById('mac_address_section');
 var button = document.getElementById('esp_now_button');
 
 if (!section) {
 return;
 }
 
 if (section.style.display === 'none' || section.style.display === '') {
 section.style.display = 'block';
 button.classList.add('btn-active');
 
 
 fetch('/get_mac', {
 method: 'GET',
 headers: {
 'Content-Type': 'application/json',
 }
 })
 .then(function(response) { return response.json(); })
 .then(function(data) {
 if (data.mac_address) {
 var macParts = data.mac_address.split(',');
 if (macParts.length === 6) {
 macParts.forEach(function(part, index) {
 var inputField = document.getElementById('mac' + (index + 1));
 if (inputField) {
 inputField.value = part.toUpperCase();
 }
 });
 }
 }
 })
 .catch(function(error) {
 console.error('Error fetching MAC address:', error);
 });
 } else {
 section.style.display = 'none';
 button.classList.remove('btn-active');
 
 
 fetch('/delete_mac', {
 method: 'POST',
 headers: {
 'Content-Type': 'application/x-www-form-urlencoded',
 }
 });
 }
}

if (espButton) {
 espButton.addEventListener('click', toggleMacDisplay);
}

window.handleMacSubmit = function(event) {
 if (event.detail.successful) {
 var submitBtn = document.getElementById('submit_mac_button');
 if (submitBtn) {
 submitBtn.classList.add('btn-flash');
 setTimeout(function() {
 submitBtn.classList.remove('btn-flash');
 }, 500);
 }
 }
};
&lt;/script>

&lt;script>

document.addEventListener('DOMContentLoaded', function() {
 const macInputs = document.querySelectorAll('.mac-input');
 
 macInputs.forEach((input, index) => {
 input.addEventListener('input', function(e) {
 
 this.value = this.value.toUpperCase();
 
 
 if (this.value.length === 2 &amp;&amp; index &lt; macInputs.length - 1) {
 macInputs[index + 1].focus();
 }
 });
 
 
 input.addEventListener('keydown', function(e) {
 if (e.key === 'Backspace' &amp;&amp; this.value.length === 0 &amp;&amp; index > 0) {
 macInputs[index - 1].focus();
 }
 });
 });
});
&lt;/script>

&lt;h1>Generate Firmware&lt;/h1>

&lt;div id="generate-container">
 &lt;button 
 id="generate-btn"
 hx-post="/generate_firmware"
 hx-trigger="click"
 hx-swap="none"
 class="generate-button"
 onclick="this.classList.add('clicked'); document.getElementById('output-box').style.display='block'; document.getElementById('throbber').style.display='block'; document.getElementById('output-text').textContent='';"
 hx-on::after-request="if(event.detail.successful) { pollStatus(); }"
 >
 Generate Firmware
 &lt;/button>
 
 &lt;div id="throbber" style="display:none; margin-top:10px;">
 &lt;div class="spinner">&lt;/div>
 &lt;p>Building firmware, please wait...&lt;/p>
 &lt;/div>
 
 &lt;div id="output-box" style="display:none; margin-top:10px;">
 &lt;div class="output-container">
 &lt;h3>Build Output:&lt;/h3>
 &lt;pre id="output-text" class="output-text">&lt;/pre>
 &lt;button id="ok-button" class="ok-button" style="display:none;" onclick="document.getElementById('output-box').style.display='none'; document.getElementById('generate-btn').classList.remove('clicked');">OK&lt;/button>
 &lt;/div>
 &lt;/div>
&lt;/div>

&lt;style>
 .generate-button {
 padding: 10px 20px;
 font-size: 16px;
 background-color: #007bff;
 color: white;
 border: none;
 border-radius: 5px;
 cursor: pointer;
 transition: background-color 0.3s;
 }
 
 .generate-button:hover {
 background-color: #0056b3;
 }
 
 .generate-button.clicked {
 background-color: #28a745;
 }
 
 .spinner {
 border: 4px solid #f3f3f3;
 border-top: 4px solid #3498db;
 border-radius: 50%;
 width: 40px;
 height: 40px;
 animation: spin 1s linear infinite;
 }
 
 @keyframes spin {
 0% { transform: rotate(0deg); }
 100% { transform: rotate(360deg); }
 }
 
 .output-container {
 padding: 15px;
 background-color: #f8f9fa;
 border: 1px solid #dee2e6;
 border-radius: 5px;
 }
 
 .output-text {
 padding: 10px;
 background-color: #1e1e1e;
 color: #d4d4d4;
 border: 1px solid #333;
 border-radius: 3px;
 max-height: 400px;
 overflow-y: auto;
 font-family: 'Courier New', Courier, monospace;
 font-size: 12px;
 white-space: pre-wrap;
 word-wrap: break-word;
 }
 
 .ok-button {
 margin-top: 10px;
 padding: 8px 20px;
 font-size: 14px;
 background-color: #007bff;
 color: white;
 border: none;
 border-radius: 5px;
 cursor: pointer;
 }
 
 .ok-button:hover {
 background-color: #0056b3;
 }
 
 .success-indicator {
 color: #28a745;
 font-weight: bold;
 }
 
 .error-indicator {
 color: #dc3545;
 font-weight: bold;
 }
&lt;/style>

&lt;script>
 let pollInterval;
 
 function pollStatus() {
 pollInterval = setInterval(async () => {
 try {
 const response = await fetch('/generate_status');
 const data = await response.json();
 
 
 const outputText = document.getElementById('output-text');
 if (data.output) {
 outputText.textContent = data.output;
 
 outputText.scrollTop = outputText.scrollHeight;
 }
 
 if (!data.running) {
 clearInterval(pollInterval);
 document.getElementById('throbber').style.display = 'none';
 
 
 document.getElementById('ok-button').style.display = 'block';
 
 
 const statusIndicator = document.createElement('div');
 statusIndicator.style.marginTop = '10px';
 
 if (data.success) {
 statusIndicator.className = 'success-indicator';
 statusIndicator.textContent = '✓ Build completed successfully';
 
 
 const downloadContainer = document.getElementById('elf-check-container');
 if (downloadContainer) {
 htmx.trigger(downloadContainer, 'refreshDownload');
 }
 } else if (data.success === false) {
 statusIndicator.className = 'error-indicator';
 statusIndicator.textContent = '✗ Build failed';
 }
 
 
 const okButton = document.getElementById('ok-button');
 okButton.parentNode.insertBefore(statusIndicator, okButton);
 }
 } catch (error) {
 console.error('Error polling status:', error);
 clearInterval(pollInterval);
 document.getElementById('throbber').style.display = 'none';
 document.getElementById('output-text').textContent += '\n\nError: Failed to retrieve build status';
 document.getElementById('ok-button').style.display = 'block';
 }
 }, 1000); 
 }
&lt;/script>

&lt;h1>Download Firmware&lt;/h1>

&lt;div id="download-elf-container">
 
 &lt;div 
 id="elf-check-container"
 hx-get="/check_elf"
 hx-trigger="load, refreshDownload from:body"
 hx-swap="innerHTML"
 >
 &lt;p>Checking for firmware file...&lt;/p>
 &lt;/div>
&lt;/div>

&lt;style>
 .download-button {
 padding: 10px 20px;
 font-size: 16px;
 background-color: #28a745;
 color: white;
 border: none;
 border-radius: 5px;
 cursor: pointer;
 transition: background-color 0.3s;
 text-decoration: none;
 display: inline-block;
 }
 
 .download-button:hover {
 background-color: #218838;
 }
 
 .download-button:active {
 background-color: #1e7e34;
 }
 
 .file-info {
 margin-top: 10px;
 padding: 10px;
 background-color: #f8f9fa;
 border: 1px solid #dee2e6;
 border-radius: 5px;
 font-size: 14px;
 color: #495057;
 }
 
 .no-file-message {
 color: #6c757d;
 font-style: italic;
 }
&lt;/style>
</description></item></channel></rss>