Guest User

HTML app for treadmill with BLE control

a guest
Dec 13th, 2025
199
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
HTML 26.50 KB | None | 0 0
  1. <!doctype html>
  2. <html>
  3.     <head>
  4.         <meta charset="UTF-8" />
  5.         <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  6.         <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://cdn.jsdelivr.net https://unpkg.com; style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://unpkg.com; connect-src 'self' https://cdn.jsdelivr.net https://unpkg.com" />
  7.         <title>FTMS Treadmill Dashboard</title>
  8.         <script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
  9.         <script src="https://unpkg.com/lucide@latest"></script>
  10.         <style type="text/tailwindcss">
  11.             @theme {
  12.                 --color-primary: #3b82f6;
  13.                 --color-success: #10b981;
  14.                 --color-warning: #f59e0b;
  15.                 --color-danger: #ef4444;
  16.             }
  17.  
  18.             body {
  19.                 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  20.                 min-height: 100vh;
  21.             }
  22.  
  23.             .metric-card {
  24.                 backdrop-filter: blur(10px);
  25.                 background: rgba(255, 255, 255, 0.95);
  26.                 transition: all 0.3s ease;
  27.             }
  28.  
  29.             .metric-card:hover {
  30.                 transform: translateY(-4px);
  31.                 box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1);
  32.             }
  33.  
  34.             .pulse-animation {
  35.                 animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
  36.             }
  37.  
  38.             @keyframes pulse {
  39.                 0%, 100% {
  40.                     opacity: 1;
  41.                 }
  42.                 50% {
  43.                     opacity: .7;
  44.                 }
  45.             }
  46.         </style>
  47.     </head>
  48.  
  49.     <body class="p-4 md:p-8">
  50.         <div class="max-w-7xl mx-auto">
  51.             <!-- Header -->
  52.             <div class="text-center mb-6 md:mb-8">
  53.                 <h1 class="text-2xl md:text-5xl font-bold text-white mb-4 drop-shadow-lg">
  54.                     Treadmill Dashboard
  55.                 </h1>
  56.             </div>
  57.  
  58.             <!-- Connection Card -->
  59.             <div class="bg-white rounded-2xl shadow-2xl p-6 mb-6 md:mb-8">
  60.                 <div class="flex items-center justify-between flex-wrap gap-4">
  61.                     <div class="flex items-center gap-3">
  62.                         <div id="connectionStatus" class="w-3 h-3 rounded-full bg-gray-400"></div>
  63.                         <span id="connectionText" class="text-gray-600 font-medium">Not connected</span>
  64.                     </div>
  65.                     <button id="bleConnectionButton" class="bg-gradient-to-r from-blue-500 to-purple-600 hover:from-blue-600 hover:to-purple-700 text-white font-bold py-3 px-8 rounded-xl shadow-lg transition-all duration-300 hover:scale-105 flex items-center gap-2">
  66.                         <i data-lucide="bluetooth" class="w-5 h-5"></i>
  67.                         Connect Treadmill
  68.                     </button>
  69.                 </div>
  70.             </div>
  71.  
  72.             <!-- Control Buttons -->
  73.             <div class="bg-white rounded-2xl shadow-2xl p-4 mb-6 md:mb-8">
  74.                 <div class="flex items-center justify-center flex-wrap gap-2 mb:gap-4">
  75.                     <button id="startStopButton" disabled class="bg-green-500 hover:bg-green-600 disabled:bg-gray-300 disabled:cursor-not-allowed text-white font-bold py-3 px-3 md:px-6 rounded-xl shadow-lg transition-all duration-300 hover:scale-105 flex items-center gap-2">
  76.                         <i data-lucide="play" class="w-5 h-5"></i>
  77.                         Start
  78.                     </button>
  79.                     <button id="pauseUnpauseButton" disabled class="bg-yellow-500 hover:bg-yellow-600 disabled:bg-gray-300 disabled:cursor-not-allowed text-white font-bold py-3 px-3 md:px-6 rounded-xl shadow-lg transition-all duration-300 hover:scale-105 flex items-center gap-2">
  80.                         <i data-lucide="pause" class="w-5 h-5"></i>
  81.                         Pause
  82.                     </button>
  83.                     <button id="decreaseSpeedButton" disabled class="bg-orange-500 hover:bg-orange-600 disabled:bg-gray-300 disabled:cursor-not-allowed text-white font-bold py-3 px-3 md:px-6 rounded-xl shadow-lg transition-all duration-300 hover:scale-105 flex items-center gap-2">
  84.                         <i data-lucide="minus" class="w-5 h-5"></i>
  85.                         0.5 <span class="hidden md:inline">km/h</span>
  86.                     </button>
  87.                     <button id="increaseSpeedButton" disabled class="bg-blue-500 hover:bg-blue-600 disabled:bg-gray-300 disabled:cursor-not-allowed text-white font-bold py-3 px-3 md:px-6 rounded-xl shadow-lg transition-all duration-300 hover:scale-105 flex items-center gap-2">
  88.                         <i data-lucide="plus" class="w-5 h-5"></i>
  89.                         0.5 <span class="hidden md:inline">km/h</span>
  90.                     </button>
  91.                 </div>
  92.             </div>
  93.  
  94.             <!-- Metrics Grid -->
  95.             <div class="grid grid-cols-2 md:grid-cols-4 gap-4 md:gap-6">
  96.                 <!-- Speed Card -->
  97.                 <div class="metric-card rounded-2xl shadow-xl p-6">
  98.                     <div class="flex items-center justify-between mb-4">
  99.                         <div class="bg-blue-100 p-3 rounded-xl">
  100.                             <i data-lucide="gauge" class="w-8 h-8 text-blue-600"></i>
  101.                         </div>
  102.                         <span class="text-sm font-medium text-gray-500">Speed</span>
  103.                     </div>
  104.                     <div class="text-4xl font-bold text-gray-800" id="speedValue">0.0</div>
  105.                     <div class="text-sm text-gray-500 mt-1">km/h</div>
  106.                 </div>
  107.  
  108.                 <!-- Distance Card -->
  109.                 <div class="metric-card rounded-2xl shadow-xl p-6">
  110.                     <div class="flex items-center justify-between mb-4">
  111.                         <div class="bg-green-100 p-3 rounded-xl">
  112.                             <i data-lucide="map-pin" class="w-8 h-8 text-green-600"></i>
  113.                         </div>
  114.                         <span class="text-sm font-medium text-gray-500">Distance</span>
  115.                     </div>
  116.                     <div class="text-4xl font-bold text-gray-800" id="distanceValue">0</div>
  117.                     <div class="text-sm text-gray-500 mt-1">meters</div>
  118.                 </div>
  119.  
  120.                 <!-- Calories Card -->
  121.                 <div class="metric-card rounded-2xl shadow-xl p-6">
  122.                     <div class="flex items-center justify-between mb-4">
  123.                         <div class="bg-orange-100 p-3 rounded-xl">
  124.                             <i data-lucide="flame" class="w-8 h-8 text-orange-600"></i>
  125.                         </div>
  126.                         <span class="text-sm font-medium text-gray-500">Calories</span>
  127.                     </div>
  128.                     <div class="text-4xl font-bold text-gray-800" id="kcalValue">0</div>
  129.                     <div class="text-sm text-gray-500 mt-1">kcal</div>
  130.                 </div>
  131.  
  132.                 <!-- Time Card -->
  133.                 <div class="metric-card rounded-2xl shadow-xl p-6">
  134.                     <div class="flex items-center justify-between mb-4">
  135.                         <div class="bg-indigo-100 p-3 rounded-xl">
  136.                             <i data-lucide="timer" class="w-8 h-8 text-indigo-600"></i>
  137.                         </div>
  138.                         <span class="text-sm font-medium text-gray-500">Time</span>
  139.                     </div>
  140.                     <div class="text-4xl font-bold text-gray-800" id="timeValue">00:00</div>
  141.                     <div class="text-sm text-gray-500 mt-1">minutes</div>
  142.                 </div>
  143.             </div>
  144.         </div>
  145.     </body>
  146.  
  147.     <script>
  148.         const serviceFTMS = 0x1826;
  149.         const treadmillData = 0x2acd;
  150.         const treadmillControl = 0x2ad9;
  151.         const fitnessMachineStatus = 0x2ada;
  152.  
  153.         let controlPointCharacteristic = null;
  154.         let isRunning = false;
  155.         let isPaused = false;
  156.         let currentSpeed = 2.0;
  157.  
  158.         // Client-side state management
  159.         // NOTE: We manage state client-side because the treadmill's pause/resume commands
  160.         // don't work as expected (pause seems to fully stop/reset instead of preserving session).
  161.         // So we track accumulated values ourselves and only reset on "stop".
  162.         let accumulatedState = {
  163.             distance: 0,    // meters
  164.             calories: 0,    // kcal
  165.             time: 0,        // seconds
  166.             speed: 2.0      // km/h - saved speed for unpause
  167.         };
  168.         let lastDeviceValues = {
  169.             distance: 0,
  170.             calories: 0,
  171.             time: 0
  172.         };
  173.  
  174.         // Initialize Lucide icons
  175.         document.addEventListener('DOMContentLoaded', () => {
  176.             lucide.createIcons();
  177.         });
  178.  
  179.         // Helper function to format time
  180.         function formatTime(seconds) {
  181.             const mins = Math.floor(seconds / 60);
  182.             const secs = seconds % 60;
  183.             return `${String(mins).padStart(2, '0')}:${String(secs).padStart(2, '0')}`;
  184.         }
  185.  
  186.         // Helper function to update UI
  187.         function updateMetric(elementId, value) {
  188.             const element = document.getElementById(elementId);
  189.             if (element) {
  190.                 element.textContent = value;
  191.             }
  192.         }
  193.  
  194.         // Helper function to enable/disable control buttons
  195.         function enableControlButtons(enabled) {
  196.             document.getElementById('startStopButton').disabled = !enabled;
  197.             document.getElementById('pauseUnpauseButton').disabled = !enabled;
  198.             document.getElementById('increaseSpeedButton').disabled = !enabled;
  199.             document.getElementById('decreaseSpeedButton').disabled = !enabled;
  200.  
  201.             if (enabled) {
  202.                 updatePauseUnpauseButton();
  203.             }
  204.         }
  205.  
  206.         // Update pause/unpause button appearance based on state
  207.         function updatePauseUnpauseButton() {
  208.             const pauseUnpauseButton = document.getElementById('pauseUnpauseButton');
  209.  
  210.             if (isPaused) {
  211.                 // Currently paused: show "Unpause" button
  212.                 pauseUnpauseButton.innerHTML = '<i data-lucide="play" class="w-5 h-5"></i> Unpause';
  213.                 pauseUnpauseButton.className = 'bg-green-500 hover:bg-green-600 text-white font-bold py-3 px-6 rounded-xl shadow-lg transition-all duration-300 hover:scale-105 flex items-center gap-2';
  214.             } else {
  215.                 // Not paused: show "Pause" button
  216.                 pauseUnpauseButton.innerHTML = '<i data-lucide="pause" class="w-5 h-5"></i> Pause';
  217.                 pauseUnpauseButton.className = 'bg-yellow-500 hover:bg-yellow-600 text-white font-bold py-3 px-6 rounded-xl shadow-lg transition-all duration-300 hover:scale-105 flex items-center gap-2';
  218.             }
  219.             lucide.createIcons();
  220.         }
  221.  
  222.         // Command functions
  223.         async function sendCommand(data) {
  224.             try {
  225.                 const result = await controlPointCharacteristic.writeValueWithResponse(data);
  226.                 console.log('Command sent successfully');
  227.                 return result;
  228.             } catch (error) {
  229.                 console.error('Error sending command:', error);
  230.                 throw error;
  231.             }
  232.         }
  233.  
  234.         async function requestControl() {
  235.             const data = new Uint8Array([0]);
  236.             await sendCommand(data);
  237.         }
  238.  
  239.         async function startTreadmill() {
  240.             const data = new Uint8Array([7]);
  241.             console.log('Sending START/RESUME command [7]');
  242.             await sendCommand(data);
  243.         }
  244.  
  245.         async function stopTreadmill() {
  246.             // NOTE: We use STOP for both stop and pause since PAUSE command doesn't work properly
  247.             const data = new Uint8Array([8, 1]);
  248.             console.log('Sending STOP command [8, 1]');
  249.             await sendCommand(data);
  250.         }
  251.  
  252.         async function setSpeed(value) {
  253.             const buffer = new ArrayBuffer(3);
  254.             const view = new DataView(buffer);
  255.             view.setUint8(0, 2);
  256.             view.setUint16(1, value * 100, true);
  257.             console.log('Setting speed to:', value, 'km/h');
  258.             await sendCommand(buffer);
  259.         }
  260.  
  261.         document.querySelector('#bleConnectionButton').addEventListener('click', function() {
  262.             if (isWebBluetoothEnabled()) {
  263.                 connect();
  264.             }
  265.         });
  266.  
  267.         // Start/Stop button - toggles based on current running state
  268.         document.getElementById('startStopButton').addEventListener('click', async function() {
  269.             if (!isRunning) {
  270.                 // Start the treadmill at 2km/h (keeps accumulated state)
  271.                 console.log('Starting treadmill...');
  272.                 await startTreadmill();
  273.                 await setSpeed(2.0);
  274.                 currentSpeed = 2.0;
  275.                 accumulatedState.speed = 2.0;
  276.                 isRunning = true;
  277.                 isPaused = false;
  278.                 // Update button appearance
  279.                 this.innerHTML = '<i data-lucide="square" class="w-5 h-5"></i> Stop';
  280.                 this.className = 'bg-red-500 hover:bg-red-600 text-white font-bold py-3 px-6 rounded-xl shadow-lg transition-all duration-300 hover:scale-105 flex items-center gap-2';
  281.                 lucide.createIcons();
  282.                 updatePauseUnpauseButton();
  283.             } else {
  284.                 // Stop the treadmill and RESET all accumulated state
  285.                 console.log('Stopping treadmill and resetting state...');
  286.                 await stopTreadmill();
  287.                 isRunning = false;
  288.                 isPaused = false;
  289.                 // Reset accumulated state
  290.                 accumulatedState = { distance: 0, calories: 0, time: 0, speed: 2.0 };
  291.                 lastDeviceValues = { distance: 0, calories: 0, time: 0 };
  292.                 // Update UI to show zeros
  293.                 updateMetric('distanceValue', 0);
  294.                 updateMetric('kcalValue', 0);
  295.                 updateMetric('timeValue', '00:00');
  296.                 currentSpeed = 2.0;
  297.                 // Update button appearance
  298.                 this.innerHTML = '<i data-lucide="play" class="w-5 h-5"></i> Start';
  299.                 this.className = 'bg-green-500 hover:bg-green-600 text-white font-bold py-3 px-6 rounded-xl shadow-lg transition-all duration-300 hover:scale-105 flex items-center gap-2';
  300.                 lucide.createIcons();
  301.                 updatePauseUnpauseButton();
  302.             }
  303.         });
  304.  
  305.         // Pause/Unpause button - toggles based on current paused state
  306.         // NOTE: Treadmill doesn't have proper pause, so we fake it client-side
  307.         document.getElementById('pauseUnpauseButton').addEventListener('click', async function() {
  308.             if (!isPaused) {
  309.                 // Currently running: pause it
  310.                 console.log('Pausing (saving state including speed, sending stop command)...');
  311.                 // Save current accumulated values before stopping
  312.                 accumulatedState.distance += lastDeviceValues.distance;
  313.                 accumulatedState.calories += lastDeviceValues.calories;
  314.                 accumulatedState.time += lastDeviceValues.time;
  315.                 accumulatedState.speed = currentSpeed;  // Save current speed
  316.                 lastDeviceValues = { distance: 0, calories: 0, time: 0 };
  317.  
  318.                 await stopTreadmill();
  319.                 isPaused = true;
  320.                 isRunning = false;
  321.                 updatePauseUnpauseButton();
  322.             } else {
  323.                 // Currently paused: unpause it
  324.                 console.log('Unpausing (starting treadmill at saved speed:', accumulatedState.speed, 'km/h)...');
  325.                 await startTreadmill();
  326.                 await setSpeed(accumulatedState.speed);  // Restore saved speed
  327.                 currentSpeed = accumulatedState.speed;
  328.                 isPaused = false;
  329.                 isRunning = true;
  330.                 updatePauseUnpauseButton();
  331.             }
  332.         });
  333.  
  334.         // Increase speed button
  335.         document.getElementById('increaseSpeedButton').addEventListener('click', async function() {
  336.             if (isRunning && !isPaused) {
  337.                currentSpeed += 0.5;
  338.                 accumulatedState.speed = currentSpeed;  // Update saved speed
  339.                 await setSpeed(currentSpeed);
  340.             }
  341.         });
  342.  
  343.         // Decrease speed button
  344.         document.getElementById('decreaseSpeedButton').addEventListener('click', async function() {
  345.             if (isRunning && !isPaused && currentSpeed > 0.5) {
  346.                currentSpeed -= 0.5;
  347.                 accumulatedState.speed = currentSpeed;  // Update saved speed
  348.                 await setSpeed(currentSpeed);
  349.             }
  350.         });
  351.  
  352.  
  353.  
  354.     function isWebBluetoothEnabled() {
  355.         if (navigator.bluetooth) {
  356.             return true;
  357.         } else {
  358.             ChromeSamples.setStatus('Web Bluetooth API is not available.\n' +
  359.                 'Please make sure the "Experimental Web Platform features" flag is enabled.');
  360.             return false;
  361.         }
  362.     }
  363.  
  364.  
  365.  
  366.     async function connect() {
  367.         try {
  368.             document.getElementById('connectionText').textContent = 'Connecting...';
  369.             document.getElementById('connectionStatus').className = 'w-3 h-3 rounded-full bg-yellow-400';
  370.  
  371.             const device = await navigator.bluetooth.requestDevice({
  372.                 filters:[{
  373.                     services: [serviceFTMS],
  374.                 }]
  375.             });
  376.  
  377.             const server = await device.gatt.connect();
  378.             const service = await server.getPrimaryService(serviceFTMS);
  379.  
  380.             const treadmillDataCharacteristic = await service.getCharacteristic(treadmillData);
  381.             await treadmillDataCharacteristic.startNotifications();
  382.             treadmillDataCharacteristic.addEventListener('characteristicvaluechanged', handleNotifications);
  383.  
  384.             controlPointCharacteristic = await service.getCharacteristic(treadmillControl);
  385.             await requestControl();
  386.  
  387.             // Subscribe to Fitness Machine Status for automatic state detection
  388.             try {
  389.                 const statusCharacteristic = await service.getCharacteristic(fitnessMachineStatus);
  390.                 await statusCharacteristic.startNotifications();
  391.                 statusCharacteristic.addEventListener('characteristicvaluechanged', handleStatusNotification);
  392.                 console.log('Subscribed to Fitness Machine Status');
  393.             } catch (error) {
  394.                 console.log('Fitness Machine Status not available:', error);
  395.             }
  396.  
  397.             document.getElementById('connectionText').textContent = 'Connected';
  398.             document.getElementById('connectionStatus').className = 'w-3 h-3 rounded-full bg-green-500 animate-pulse';
  399.             enableControlButtons(true);
  400.         } catch (error) {
  401.             console.error('Connection error:', error);
  402.             document.getElementById('connectionText').textContent = 'Connection failed';
  403.             document.getElementById('connectionStatus').className = 'w-3 h-3 rounded-full bg-red-500';
  404.         }
  405.     }
  406.  
  407.  
  408.  
  409.  
  410.     function handleNotifications(event) {
  411.         let value = event.target.value;
  412.  
  413.         var flags = value.getUint16(0, /*littleEndian=*/true);
  414.         // 2octets for flags, 2octets for instant speed, nextPosition is incremented following the number of octets for each value
  415.         var nextPosition = 4;
  416.  
  417.  
  418.         if ((flags & (1 << 1)) != 0) {posAvgSpeed=nextPosition; nextPosition+=2;}
  419.         if ((flags & (1 << 2)) != 0) {posTotDistance=nextPosition; nextPosition+=3;}//4
  420.         if ((flags & (1 << 3)) != 0) {posInclination=nextPosition; nextPosition+=4;}//8
  421.         if ((flags & (1 << 4)) != 0) {posElevGain=nextPosition; nextPosition+=4;}
  422.         if ((flags & (1 << 5)) != 0) {posInsPace=nextPosition; nextPosition+=1;}
  423.         if ((flags & (1 << 6)) != 0) {posAvgPace=nextPosition; nextPosition+=1;}
  424.         if ((flags & (1 << 7)) != 0) {posKcal=nextPosition; nextPosition+=5;}
  425.         if ((flags & (1 << 8)) != 0) {posHR=nextPosition; nextPosition+=1;}
  426.         if ((flags & (1 << 9)) != 0) {posMET=nextPosition; nextPosition+=1;}
  427.         if ((flags & (1 << 10)) != 0) {posElaspedTime=nextPosition; nextPosition+=2;}
  428.         if ((flags & (1 << 11)) != 0) {posRemainTime=nextPosition; nextPosition+=2;}
  429.         if ((flags & (1 << 12)) != 0) {posForceBelt=nextPosition; nextPosition+=4;}
  430.  
  431.         // instantaneous speed
  432.         speed = value.getUint16(2, /*littleEndian=*/true)/100;
  433.         updateMetric('speedValue', speed.toFixed(1));
  434.         currentSpeed = speed;
  435.  
  436.         // Auto-detect start/stop state based on speed
  437.         const startStopButton = document.getElementById('startStopButton');
  438.         if (speed > 0 && !isRunning && !isPaused) {
  439.            // Treadmill started externally, update button to "Stop"
  440.            isRunning = true;
  441.             startStopButton.innerHTML = '<i data-lucide="square" class="w-5 h-5"></i> Stop';
  442.             startStopButton.className = 'bg-red-500 hover:bg-red-600 text-white font-bold py-3 px-6 rounded-xl shadow-lg transition-all duration-300 hover:scale-105 flex items-center gap-2';
  443.             lucide.createIcons();
  444.         } else if (speed === 0 && isRunning && !isPaused) {
  445.            // Treadmill stopped externally, update button to "Start"
  446.            isRunning = false;
  447.             startStopButton.innerHTML = '<i data-lucide="play" class="w-5 h-5"></i> Start';
  448.             startStopButton.className = 'bg-green-500 hover:bg-green-600 text-white font-bold py-3 px-6 rounded-xl shadow-lg transition-all duration-300 hover:scale-105 flex items-center gap-2';
  449.             lucide.createIcons();
  450.         }
  451.  
  452.         // Read device values
  453.         //distance
  454.         let deviceDistance = value.getUint16(posTotDistance, /*littleEndian=*/true);
  455.         let distance_complement = value.getUint8(posTotDistance + 2, /*littleEndian=*/true);
  456.         distance_complement = distance_complement << 16;
  457.        deviceDistance = deviceDistance + distance_complement;
  458.        lastDeviceValues.distance = deviceDistance;
  459.  
  460.        let deviceCalories = 0;
  461.        if (typeof posKcal != "undefined")
  462.        {
  463.          deviceCalories = value.getUint16(posKcal, /*littleEndian=*/true);
  464.          lastDeviceValues.calories = deviceCalories;
  465.        }
  466.  
  467.        let deviceTime = 0;
  468.        if (typeof posElaspedTime != "undefined")
  469.        {
  470.          deviceTime = value.getUint16(posElaspedTime, /*littleEndian=*/true);
  471.          lastDeviceValues.time = deviceTime;
  472.        }
  473.  
  474.        // Display accumulated + current device values
  475.        // This way pause/unpause works correctly by keeping accumulated state
  476.        updateMetric('distanceValue', accumulatedState.distance + deviceDistance);
  477.        updateMetric('kcalValue', accumulatedState.calories + deviceCalories);
  478.        updateMetric('timeValue', formatTime(accumulatedState.time + deviceTime));
  479.    }
  480.  
  481.    // Handle Fitness Machine Status notifications (0x2ADA)
  482.    // This automatically detects treadmill state changes
  483.    function handleStatusNotification(event) {
  484.        let value = event.target.value;
  485.        const opCode = value.getUint8(0);
  486.  
  487.        console.log('Fitness Machine Status - Op Code:', '0x' + opCode.toString(16).padStart(2, '0'));
  488.  
  489.        const startStopButton = document.getElementById('startStopButton');
  490.  
  491.        switch(opCode) {
  492.            case 0x01: // Reset
  493.                console.log('Status: Reset');
  494.                isRunning = false;
  495.                isPaused = false;
  496.                startStopButton.innerHTML = '<i data-lucide="play" class="w-5 h-5"></i> Start';
  497.                 startStopButton.className = 'bg-green-500 hover:bg-green-600 text-white font-bold py-3 px-6 rounded-xl shadow-lg transition-all duration-300 hover:scale-105 flex items-center gap-2';
  498.                 lucide.createIcons();
  499.                 updatePauseUnpauseButton();
  500.                 break;
  501.  
  502.             case 0x02: // Stopped or Paused by User
  503.                 if (value.byteLength > 1) {
  504.                     const controlInfo = value.getUint8(1);
  505.                     if (controlInfo === 0x01) {
  506.                         console.log('Status: Stopped by User');
  507.                         isRunning = false;
  508.                         isPaused = false;
  509.                         startStopButton.innerHTML = '<i data-lucide="play" class="w-5 h-5"></i> Start';
  510.                         startStopButton.className = 'bg-green-500 hover:bg-green-600 text-white font-bold py-3 px-6 rounded-xl shadow-lg transition-all duration-300 hover:scale-105 flex items-center gap-2';
  511.                     } else if (controlInfo === 0x02) {
  512.                         console.log('Status: Paused by User');
  513.                         isRunning = false;
  514.                         isPaused = true;
  515.                     }
  516.                     lucide.createIcons();
  517.                     updatePauseUnpauseButton();
  518.                 }
  519.                 break;
  520.  
  521.             case 0x03: // Stopped by Safety Key
  522.                 console.log('Status: Stopped by Safety Key');
  523.                 isRunning = false;
  524.                 isPaused = false;
  525.                 startStopButton.innerHTML = '<i data-lucide="play" class="w-5 h-5"></i> Start';
  526.                 startStopButton.className = 'bg-green-500 hover:bg-green-600 text-white font-bold py-3 px-6 rounded-xl shadow-lg transition-all duration-300 hover:scale-105 flex items-center gap-2';
  527.                 lucide.createIcons();
  528.                 updatePauseUnpauseButton();
  529.                 break;
  530.  
  531.             case 0x04: // Started or Resumed by User
  532.                 console.log('Status: Started or Resumed by User');
  533.                 isRunning = true;
  534.                 isPaused = false;
  535.                 startStopButton.innerHTML = '<i data-lucide="square" class="w-5 h-5"></i> Stop';
  536.                 startStopButton.className = 'bg-red-500 hover:bg-red-600 text-white font-bold py-3 px-6 rounded-xl shadow-lg transition-all duration-300 hover:scale-105 flex items-center gap-2';
  537.                 lucide.createIcons();
  538.                 updatePauseUnpauseButton();
  539.                 break;
  540.  
  541.             case 0xFF: // Control Permission Lost
  542.                 console.log('Status: Control Permission Lost');
  543.                 alert('Control permission lost! Another device may have taken control.');
  544.                 break;
  545.  
  546.             default:
  547.                 console.log('Status: Unknown Op Code 0x' + opCode.toString(16));
  548.                 break;
  549.         }
  550.     }
  551.  
  552.  
  553.  
  554. </script>
  555. </html>
  556.  
Tags: Treadmill
Advertisement
Add Comment
Please, Sign In to add comment