{"version":"https://jsonfeed.org/version/1.1","title":"HandShakeTec","description":"Recent content on HandShakeTec","home_page_url":"https://handshaketec.com/","feed_url":"https://handshaketec.com/index.json","language":"en","icon":"https://handshaketec.com/logo.png","favicon":"https://handshaketec.com/logo.png","authors":[{"name":"Matt Oppenheim","url":"https://mattoppenheim.com"}],"items":[{"title":"Firmware changes","date_published":"2026-10-09T00:00:00Z","date_modified":"2026-10-09T00:00:00Z","id":"https://handshaketec.com/firmware_changes/","url":"https://handshaketec.com/firmware_changes/","content_html":"\u003c!--\n  One line per fix, newest first, labelled by device. Each line starts with the\n  FIRMWARE_VERSION date of the build that contains the fix (a letter suffix marks a\n  second fix on the same day).\n  scripts/remote_deployment/02_validate_and_prepare.sh fails if either firmware's\n  FIRMWARE_VERSION has no line here. Add the line in the same change that bumps\n  the version (see design.md, 2026-10-09).\n--\u003e\n\u003cp\u003eEach fix is listed with the version date that the device shows for about two seconds\nwhen it starts. Devices flashed before 2026-10-09 show no version. Reflash them to\nstart from the first versioned build.\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e2026-10-09b\u003c/strong\u003e — T-Watch S3: the green bar now follows the trigger level. When you change it from the T-Embed, a full bar means the movement would have triggered a shake.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e2026-10-09b\u003c/strong\u003e — T-Watch S3: the green bar keeps the highest level reached and falls back over about a second, so a short punch is visible. In roll mode it shows the roll instead of the punch strength.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e2026-10-09b\u003c/strong\u003e — T-Watch S3: \u0026ldquo;\u0026ndash; NO SAVE \u0026ndash;\u0026rdquo; stays on the screen after a shake instead of disappearing. \u0026ldquo;- TASK FAIL -\u0026rdquo; is shown if the watch could not start one of its tasks.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e2026-10-09\u003c/strong\u003e — T-Watch S3 and T-Embed: the firmware version is shown at start-up.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e2026-10-08\u003c/strong\u003e — T-Embed: flashes without holding the BOOT button again. The USB keyboard now starts on the first shake instead of at power-up.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e2026-10-06\u003c/strong\u003e — T-Watch S3: the watch confirms that each shake reached the T-Embed, and shows NO LINK when it did not.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e2026-10-06\u003c/strong\u003e — T-Watch S3: the radio settle delay is 10 ms instead of 100 ms, so a shake reaches the keyboard about 90 ms sooner.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e2026-09-08\u003c/strong\u003e — T-Watch S3: fixed a boot loop caused by the audio driver. The watch has no audio.\u003c/li\u003e\n\u003c/ul\u003e\n"},{"title":"Using HandShake","date_published":"2026-10-09T00:00:00Z","date_modified":"2026-10-09T00:00:00Z","id":"https://handshaketec.com/how_to_use_handshake/","url":"https://handshaketec.com/how_to_use_handshake/","content_html":"\u003cp\u003eThis page shows how to use the Lilygo T-Watch S3 and the Lilygo T-Embed to work as the \u003ca href=\"https://mattoppenheim.com/projects/handshake/\"\u003ehandshake project\u003c/a\u003e.\nThis page assumes that you have already programmed both devices with the HandShake firmware.\u003c/p\u003e\n\u003ch1 id=\"summary\"\u003eSummary\u003c/h1\u003e\n\u003cp\u003eTurn on the watch using a long press on the button.\u003c/p\u003e\n\u003cp\u003eShake the watch gently and check that this is detected as a switch by the receiver unit.\u003c/p\u003e\n\u003cp\u003eAdjust the level of shake that will trigger a switch action using the jog dial on the receiver, followed by a long press on the button in the middle of the jog dial. This sends the new shake detection threshold to the watch.\u003c/p\u003e\n\u003ch1 id=\"handshake-components\"\u003eHandShake components\u003c/h1\u003e\n\u003cp\u003eThe HandShake system comprises two parts, a smart watch (Lilygo T-Watch S3) and a receiver unit that sends a keystroke to a communication device (Lilygo T-Embed) through a USB cable. The two devices are shown in the photo below.\u003c/p\u003e\n\u003cfigure class=\"image center\"\u003e\n\u003cimg src=\"/images/t_watch_s3_off_t_embed_on.png\" loading=\"lazy\" width=\"400\" height=\"121\"\u003e\n\u003cfigcaption\u003e\n\u003cstrong\u003eLilygo T-Watch S3 and Lilygo T-Embed\u003c/strong\u003e\u003cbr\u003e\u003c/figcaption\u003e\n\u003c/figure\u003e\n\n\u003cp\u003eWhen the watch starts, it shows its firmware version, a date such as 2026-10-09, for about two seconds. The T-Embed does the same. The \u003ca href=\"/firmware_changes/\"\u003eFirmware changes\u003c/a\u003e page lists what changed in each version.\u003c/p\u003e\n\u003cp\u003eWhen the watch has booted up, which takes a few seconds, you will see the display below:\u003c/p\u003e\n\u003cfigure class=\"image center\"\u003e\n\u003cimg src=\"/images/t_watch_s3_standby.png\" loading=\"lazy\"\u003e\n\u003cfigcaption\u003e\n\u003cstrong\u003eWatch default display\u003c/strong\u003e\u003cbr\u003e\u003c/figcaption\u003e\n\u003c/figure\u003e\n\n\u003cp\u003eThe screen has two sliders:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eThe \u003cstrong\u003etop slider\u003c/strong\u003e (yellow) shows the trigger level, the amount of shake needed to operate your communication software. Its value is also shown on the watch screen as \u0026lsquo;shake trigger: xxx\u0026rsquo; where xxx is a number that varies from 525 to 1000 (these limits may change). The lower the number, the softer the motion required to trigger a shake detection. This slider only changes when you send a new trigger level from the receiver.\u003c/li\u003e\n\u003cli\u003eThe \u003cstrong\u003ebottom slider\u003c/strong\u003e (green) shows how close the latest movement came to triggering. It updates in real time.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eThe bottom slider runs from the watch at rest (empty) to the trigger level (full). Once it reaches the right-hand end, the shake detection is triggered. If you change the trigger level from the receiver, the green slider rescales to match, so a full bar always means a trigger.\u003c/p\u003e\n\u003cp\u003eThe green slider keeps the strongest movement of the last moment and then falls back over about a second, so even a short sharp punch is visible. If the watch is set to detect a roll rather than a punch, the green slider shows the strength of the roll.\u003c/p\u003e\n\u003cp\u003eWhen an intended shake is detected this display is shown on the watch:\u003c/p\u003e\n\u003cfigure class=\"image center\"\u003e\n\u003cimg src=\"/images/t_watch_s3_shake_detection.png\" loading=\"lazy\"\u003e\n\u003cfigcaption\u003e\n\u003cstrong\u003eShake detected display\u003c/strong\u003e\u003cbr\u003e\u003c/figcaption\u003e\n\u003c/figure\u003e\n\n\u003cp\u003eThe line below the battery reading is the watch\u0026rsquo;s status line. It shows \u003cstrong\u003e*** SHAKE ***\u003c/strong\u003e or \u003cstrong\u003e*** NO LINK ***\u003c/strong\u003e briefly after a gesture, and is otherwise empty. If the watch has a lasting problem, the message stays there and comes back after each gesture:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e\u0026ndash; NO SAVE \u0026ndash;\u003c/strong\u003e: the watch cannot store its settings. It runs with the built-in trigger level, and a new level sent from the receiver is lost when the watch restarts. Reflash the firmware.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e- TASK FAIL -\u003c/strong\u003e: the watch could not start one of its internal tasks. Restart the watch. If it comes back, reflash the firmware.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eWatches with firmware older than 2026-10-09b lose these messages after the first gesture.\u003c/p\u003e\n\u003cp\u003eTo charge the watch, attach a USB cable to the micro USB socket in the side of the watch and connect to a USB socket or charger. For whatever reason, once the watch battery is at 0% it stays at 0% for an age while charging then starts ramping up to 100% fairly quickly. I don\u0026rsquo;t know how to improve this yet.\u003c/p\u003e\n\u003cfigure class=\"image center\"\u003e\n\u003cimg src=\"/images/t_watch_s3_charging.png\" loading=\"lazy\"\u003e\n\u003cfigcaption\u003e\n\u003cstrong\u003eCharging the watch\u003c/strong\u003e\u003cbr\u003e\u003c/figcaption\u003e\n\u003c/figure\u003e\n\n\u003ch1 id=\"setting-up-the-receiver-unit\"\u003eSetting up the receiver unit\u003c/h1\u003e\n\u003cp\u003eThe T-Embed receiver unit is powered from the USB-C socket on its side.\u003c/p\u003e\n\u003cp\u003eThe receiver has a jog dial around the button.\nThis dial is used to adjust the trigger threshold.\nUse the dial to set a new trigger threshold then send this new value to the watch by giving a long press (about 2s) to the button in the middle of the dial.\nTurning the jog wheel clockwise will increase the amount of shake needed to send a trigger to your communication device, turning the jog wheel anti-clockwise decreases the amount of shake needed to send a trigger to your communication device.\nThe updated trigger will not take effect in the watch until you give a long press on the button though.\nCheck the watch to see that the new shake value is shown on the watch display.\u003c/p\u003e\n\u003cp\u003eThe button on the receiver can be used to adjust the sensitivity of the gesture detection algorithm running on the T-Watch:\u003c/p\u003e\n\u003cp\u003eA short press: The receiver asks the watch for the largest shake it measured over the last 5 seconds and shows that value on the receiver display.\nAsk the person wearing the watch to make the gesture you want to detect, then give a short press to see how strongly that gesture registered.\nSet the trigger threshold a little below the value you see, so the gesture is detected reliably without every small movement triggering it.\u003c/p\u003e\n\u003cp\u003eA long press: The receiver sends the trigger threshold shown on the receiver display to the watch.\u003c/p\u003e\n\u003cp\u003eThis allows the sensitivity of the gesture that is detected to be adjusted without having to touch the T-Watch.\u003c/p\u003e\n"},{"title":"Publications","date_published":"2026-10-05T00:00:00Z","date_modified":"2026-10-05T00:00:00Z","id":"https://handshaketec.com/publications/","url":"https://handshaketec.com/publications/","content_html":"\u003cp\u003eArticles about the HandShake project and the hardware behind it:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e\u003cstrong\u003eHandShake - Using Arm Movement as an AAC Switch\u003c/strong\u003e\u003cbr\u003e\nOppenheim, M. Frankowski, T.: Communication Matters. 40, 2, p. 16-19\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e\u003cstrong\u003eEmpowering Disabled Persons with Hand Motions for Environmental Controls: Real-Time Sensor Display Using Python and a Smart Watch\u003c/strong\u003e\u003cbr\u003e\nOppenheim, M., 1/11/2025, In: Circuit Cellar. 1, 424, p. 26-34\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e\u003cstrong\u003ePower Profiler for Accelerated Embedded Hardware Development: Streamlining the Development Process with Efficient Power Analysis\u003c/strong\u003e\u003cbr\u003e\nOppenheim, M., 24/06/2025, In: Circuit Cellar - The Magazine For Computer Applications. 1, 420, p. 26-33\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e\u003cstrong\u003eDevices Aid Speech for People with Disabilities\u003c/strong\u003e\u003cbr\u003e\nOppenheim, M., 21/06/2021, In: Circuit Cellar - The Magazine For Computer Applications. 1, 372, p. 30-37\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e\u003cstrong\u003eUsing the BBC micro:bit as AAC – Three Solutions\u003c/strong\u003e\u003cbr\u003e\nOppenheim, M., 30/11/2020, In: Communication Matters. 34, 3, p. 13-15\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e\u003cstrong\u003eHandShake – Using Hand Motion Recognition to Enable Communication\u003c/strong\u003e\u003cbr\u003e\nOppenheim, M. \u0026amp; McIntyre, F., 30/04/2020, In: Communication Matters. 34, 1, p. 19-21\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e\u003cstrong\u003eFull Stack Python: Micropython and Python for rapid embedded hardware and GUI development\u003c/strong\u003e\u003cbr\u003e\nOppenheim, M., 28/02/2017, In: Circuit Cellar - The Magazine For Computer Applications. 1, 319, p. 17-25\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n"},{"title":"Device Flasher help","date_published":"2026-09-14T00:00:00Z","date_modified":"2026-09-14T00:00:00Z","id":"https://handshaketec.com/flash_device_help/","url":"https://handshaketec.com/flash_device_help/","content_html":"\u003cp\u003eThe \u003ca href=\"https://handshaketec.com/flash_device/\"\u003e\u003cstrong\u003eDevice Flasher\u003c/strong\u003e page\u003c/a\u003e is where you load\nthe firmware file you built on the configuration page onto your device. This works for\nboth the T-Watch S3 and the T-Embed, using the same page.\u003c/p\u003e\n\u003cp\u003eThis is called \u0026lsquo;flashing\u0026rsquo; the device, as the memory that the firmware is written to is\ncalled flash memory.\u003c/p\u003e\n\u003cp\u003eIt all happens in your browser — there\u0026rsquo;s nothing to install. You\u0026rsquo;ll need \u003cstrong\u003eGoogle\nChrome or Microsoft Edge\u003c/strong\u003e; other browsers such as Firefox and Safari can\u0026rsquo;t talk to the\ndevice over USB and won\u0026rsquo;t work.\u003c/p\u003e\n\u003cp\u003eYou don\u0026rsquo;t need any programming knowledge — just work through the page from top to\nbottom.\u003c/p\u003e\n\u003ch1 id=\"before-you-start\"\u003eBefore you start\u003c/h1\u003e\n\u003cp\u003eYou\u0026rsquo;ll need:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eThe \u003ccode\u003e.bin\u003c/code\u003e firmware file you downloaded from the \u003ca href=\"https://handshaketec.com/watch_configuration/\"\u003eT-Watch-S3 configuration\npage\u003c/a\u003e or the \u003ca href=\"https://handshaketec.com/embed_configuration/\"\u003eT-Embed configuration\npage\u003c/a\u003e.\u003c/li\u003e\n\u003cli\u003eA USB cable that carries data, not just power. Some cables only charge — if your\ndevice isn\u0026rsquo;t found in Step 2 below, try a different cable.\n\u003cul\u003e\n\u003cli\u003eThe \u003cstrong\u003eT-Watch S3\u003c/strong\u003e uses a \u003cstrong\u003eUSB micro\u003c/strong\u003e cable.\u003c/li\u003e\n\u003cli\u003eThe \u003cstrong\u003eT-Embed\u003c/strong\u003e uses a \u003cstrong\u003eUSB-C\u003c/strong\u003e cable.\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch1 id=\"step-1--select-the-firmware-file\"\u003eStep 1 — Select the firmware file\u003c/h1\u003e\n\u003cp\u003eUnder \u003cstrong\u003e1. Select Firmware File\u003c/strong\u003e, click the file box and choose the \u003ccode\u003e.bin\u003c/code\u003e file you\ndownloaded from the configuration page.\u003c/p\u003e\n\u003cp\u003eThe page checks the file and uploads it. You don\u0026rsquo;t have to set a start address or any\nother technical option — the file you download already contains everything the device\nneeds, and the page works out where it goes.\u003c/p\u003e\n\u003cp\u003eIf you see an error message here:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cem\u003e\u0026ldquo;File must be .bin\u0026rdquo;\u003c/em\u003e — you\u0026rsquo;ve picked the wrong file, or renamed the firmware so it no\nlonger ends in \u003ccode\u003e.bin\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003e\u003cem\u003e\u0026ldquo;File too small\u0026rdquo;\u003c/em\u003e — the file isn\u0026rsquo;t a firmware image. A real one is at least 0.3 MB.\nThis usually means a download didn\u0026rsquo;t finish, so download it again from the\nconfiguration page.\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"about-the-audio-file-status-section\"\u003eAbout the Audio File Status section\u003c/h2\u003e\n\u003cp\u003eSection \u003cstrong\u003e2. Audio File Status\u003c/strong\u003e is there for information only. It lists what it found\nfor \u003cstrong\u003eboth\u003c/strong\u003e devices, whichever firmware you happen to have selected, so seeing a line\nabout the other device is normal and isn\u0026rsquo;t a mistake.\u003c/p\u003e\n\u003cp\u003eYou don\u0026rsquo;t need to do anything in this section. If you chose to have sound on the\nT-Embed, it is already built into the \u003ccode\u003e.bin\u003c/code\u003e file — there\u0026rsquo;s no separate audio file for\nyou to select or flash.\u003c/p\u003e\n\u003ch1 id=\"step-2--connect-your-device\"\u003eStep 2 — Connect your device\u003c/h1\u003e\n\u003cp\u003ePlug your device into your computer with a data-capable USB cable — micro-USB for the\nT-Watch S3, USB-C for the T-Embed.\u003c/p\u003e\n\u003cp\u003eOnce the firmware file has been checked, a \u003cstrong\u003eConnect\u003c/strong\u003e button will appear under \u003cstrong\u003e3.\nConnect \u0026amp; Flash\u003c/strong\u003e. Click it. Your browser will show a list of devices — choose the one\nfor your watch or T-Embed and click \u003cstrong\u003eConnect\u003c/strong\u003e in that list.\u003c/p\u003e\n\u003cp\u003eThe names in this list come from your computer, not from HandShake, so they\u0026rsquo;re often\nunhelpful — something like \u0026ldquo;USB Serial Device\u0026rdquo;, \u0026ldquo;USB JTAG/serial debug unit\u0026rdquo;, or a\nstring of letters and numbers rather than \u0026ldquo;T-Watch S3\u0026rdquo;. If it\u0026rsquo;s not obvious which entry\nis your device, close the list, unplug the device, click \u003cstrong\u003eConnect\u003c/strong\u003e again and note down\nwhat\u0026rsquo;s in the list without it, then plug the device back in and click \u003cstrong\u003eConnect\u003c/strong\u003e once\nmore. The extra entry that appears only when the device is plugged in is the one you\nwant.\u003c/p\u003e\n\u003cp\u003eIf your device doesn\u0026rsquo;t appear in the list, make sure it\u0026rsquo;s powered on and properly\nconnected, then try disconnecting and reconnecting the cable.\u003c/p\u003e\n\u003ch1 id=\"step-3--install-the-firmware\"\u003eStep 3 — Install the firmware\u003c/h1\u003e\n\u003cp\u003eOnce the device is connected, a small window opens with a few choices. Click\n\u003cstrong\u003eInstall HandShake code\u003c/strong\u003e.\u003c/p\u003e\n\u003cp\u003eYou\u0026rsquo;ll be asked to confirm. There\u0026rsquo;s also an \u003cstrong\u003eErase device\u003c/strong\u003e tick box — you can leave\nthis alone. The firmware you downloaded is a complete image, so erasing first isn\u0026rsquo;t\nneeded.\u003c/p\u003e\n\u003cp\u003eYou\u0026rsquo;ll then see it work through \u003cstrong\u003ePreparing installation\u003c/strong\u003e, \u003cstrong\u003eWriting\u003c/strong\u003e, and finally\n\u003cstrong\u003eInstallation complete\u003c/strong\u003e. Writing takes a short while — leave the cable connected and\ndon\u0026rsquo;t close the browser tab until it says it has finished.\u003c/p\u003e\n\u003cp\u003eThe device restarts on its own once installation is complete, so there\u0026rsquo;s nothing further\nfor you to do.\u003c/p\u003e\n\u003ch2 id=\"checking-which-firmware-version-is-on-the-device\"\u003eChecking which firmware version is on the device\u003c/h2\u003e\n\u003cp\u003eWhen the device restarts, its screen shows the word \u003cstrong\u003efirmware\u003c/strong\u003e and a date, such as\n\u003cstrong\u003e2026-10-09\u003c/strong\u003e, for about two seconds before the normal display appears. This date is the\nfirmware version. The \u003ca href=\"/firmware_changes/\"\u003eFirmware changes\u003c/a\u003e page lists what changed\nin each version, so you can see whether your device has a particular fix.\u003c/p\u003e\n\u003cp\u003eThe date is the version of the program, not the day you built or flashed it. A device\nflashed today can show an earlier date if nothing has changed since then.\u003c/p\u003e\n\u003cp\u003eFirmware flashed before 9 October 2026 doesn\u0026rsquo;t show a version. Build and flash new\nfirmware to get it.\u003c/p\u003e\n\u003ch1 id=\"if-something-goes-wrong\"\u003eIf something goes wrong\u003c/h1\u003e\n\u003cp\u003e\u003cstrong\u003eDon\u0026rsquo;t panic.\u003c/strong\u003e If flashing fails partway through, or you flash the wrong file by\nmistake, just reconnect the device and repeat the steps above with the correct file.\nThere\u0026rsquo;s no way to damage the device by flashing it again.\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003eDevice not detected?\u003c/strong\u003e Check it\u0026rsquo;s connected and powered on. Try a different USB\ncable or a different USB port on your computer.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eFlash failed?\u003c/strong\u003e Disconnect and reconnect the device, then try again.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eStill stuck?\u003c/strong\u003e Open the browser\u0026rsquo;s developer console (press F12) to see if there\u0026rsquo;s an\nerror message, and get in touch if you need help — it\u0026rsquo;s useful for me to know if\nyou\u0026rsquo;ve hit a problem.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e\u003cstrong\u003eT-Embed repeatedly not detected\u003c/strong\u003e\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eUnplug the T-Embed from the USB cable.\u003c/li\u003e\n\u003cli\u003ePush in the button in the middle of the jog dial.\u003c/li\u003e\n\u003cli\u003eKeep this button pressed while you plug the USB cable back in.\u003c/li\u003e\n\u003cli\u003eRelease the button. The display will probably stay black.\u003c/li\u003e\n\u003cli\u003eNow the T-Embed should be detected and flash.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e\u003cstrong\u003eT-Watch repeatedly not detected\u003c/strong\u003e\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eHave a look at the section \u0026lsquo;Resetting the watch if it becomes unresponsive\u0026rsquo; in this page: \u003ca href=\"https://handshaketec.com/watch_configuration_help/#resetting-the-watch-if-it-becomes-unresponsive\"\u003ewatch_configuration_help\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n"},{"title":"T-Embed configuration help","date_published":"2026-09-14T00:00:00Z","date_modified":"2026-09-14T00:00:00Z","id":"https://handshaketec.com/embed_configuration_help/","url":"https://handshaketec.com/embed_configuration_help/","content_html":"\u003cp\u003eThe \u003ca href=\"https://handshaketec.com/embed_configuration/\"\u003e\u003cstrong\u003eT-Embed Configuration\u003c/strong\u003e page\u003c/a\u003e lets you choose what your T-Embed receiver does, then builds a\ncustom firmware file (the program that runs on the device) for you to download and\nput onto the T-Embed. The T-Embed is the small receiver unit that sits plugged into\nyour computer or other device and passes on the signal sent by the watch — see the\n\u003ca href=\"https://handshaketec.com/getting_started/\"\u003eGetting Started\u003c/a\u003e and\n\u003ca href=\"https://handshaketec.com/how_to_use_handshake/\"\u003eUsing HandShake\u003c/a\u003e pages if you\u0026rsquo;re not\nsure which device is which.\u003c/p\u003e\n\u003cp\u003eThe options that you can set include:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eWhether the T-Embed plays a sound each time it gets a shake signal from the watch.\u003c/li\u003e\n\u003cli\u003eWhether the T-Embed sends a keystroke to whatever it\u0026rsquo;s plugged into each time it gets a shake signal from the watch.\u003c/li\u003e\n\u003cli\u003eWhich watch the T-Embed should listen to.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eYou don\u0026rsquo;t need any programming knowledge — just work through the page from top to\nbottom.\u003c/p\u003e\n\u003cp\u003eHow it works:\nEach time you change an option on this page, that choice is written to a configuration\nfile for your session. When you create the firmware at the bottom of the page, this\nconfiguration file is read and used to build the firmware. So, you can go back and\nchange your selections as many times as you want before you generate the firmware.\u003c/p\u003e\n\u003cp\u003eIf the option you\u0026rsquo;re setting has a \u0026lsquo;submit\u0026rsquo; button, the value you enter isn\u0026rsquo;t written to\nthe configuration file until you click that button. The two on/off switches (Audio and\nKeyboard) save themselves the moment you click them — there\u0026rsquo;s no separate submit button\nfor those.\u003c/p\u003e\n\u003ch1 id=\"using-the-t-embed-configuration-website\"\u003eUsing the T-Embed configuration website\u003c/h1\u003e\n\u003cp\u003eUsing this website you will configure and download a file for your T-Embed which will be\ncalled e.g.\u003c/p\u003e\n\u003cp\u003ezeuss-tembed-firmware.bin\u003c/p\u003e\n\u003cp\u003eYou are free to rename the file after you download it.\u003c/p\u003e\n\u003cp\u003eThe Greek god\u0026rsquo;s name at the start of the filename will probably be different from the\nexample and will change each time that you restart the configuration page. See below for\ndetails of why this is done.\u003c/p\u003e\n\u003ch2 id=\"step-1--check-your-session-name\"\u003eStep 1 — Check your session name\u003c/h2\u003e\n\u003cp\u003eAt the top of the page you\u0026rsquo;ll see your \u003cstrong\u003esession name\u003c/strong\u003e.\nThis is a name of a Greek god.\u003c/p\u003e\n\u003cp\u003eEach time you re-open the configuration page, you will be assigned a new session name.\nThis is done so that the firmware you build doesn\u0026rsquo;t get mixed up with anyone else\u0026rsquo;s.\nThe names are taken randomly from a pool of Greek god names.\nWhen you next use the site you will probably get a different session name.\u003c/p\u003e\n\u003cp\u003eThe firmware filename produced by the website contains the session name, e.g.\nzeuss-tembed-firmware.bin, so that you get the firmware for your device and not\nsomebody else\u0026rsquo;s, even if several people are using the site at the same time.\u003c/p\u003e\n\u003cp\u003eYou\u0026rsquo;re free to rename the firmware after you\u0026rsquo;ve downloaded it to your own device.\nRenaming the file won\u0026rsquo;t change how it works, so long as you keep \u0026lsquo;.bin\u0026rsquo; at the end of\nthe filename. The \u0026lsquo;.bin\u0026rsquo; ending is required so that the flashing tool recognises the\nfile as a firmware file.\u003c/p\u003e\n\u003cp\u003eIf the session name is \u0026lsquo;undefined\u0026rsquo; then there is a problem with the website! Try again\nin 10 minutes, or let me know and I\u0026rsquo;ll get it fixed.\u003c/p\u003e\n\u003ch2 id=\"step-2--turn-audio-on-shake-on-or-off\"\u003eStep 2 — Turn \u0026ldquo;Audio on shake\u0026rdquo; on or off\u003c/h2\u003e\n\u003cp\u003eUnder \u003cstrong\u003eAudio Control\u003c/strong\u003e, the toggle switch controls whether the T-Embed plays a short\nsound — a click, like a stick being tapped — whenever it receives a shake signal from\nthe paired watch. This gives you, or whoever is nearby, an audible confirmation that a\nshake has been picked up, separate from anything that happens on the connected device.\u003c/p\u003e\n\u003cp\u003eWith this switched on, you also get the same sound when you double-click the T-Embed\u0026rsquo;s\nbutton to test the switch, which is useful when you\u0026rsquo;re setting things up.\u003c/p\u003e\n\u003cp\u003eClick the switch to turn it on (it turns green) or off. There\u0026rsquo;s no submit button to\nclick — the setting is saved as soon as you click the switch.\u003c/p\u003e\n\u003ch2 id=\"step-3--turn-keyboard-on-shake-on-or-off\"\u003eStep 3 — Turn \u0026ldquo;Keyboard on shake\u0026rdquo; on or off\u003c/h2\u003e\n\u003cp\u003eUnder \u003cstrong\u003eKeyboard Control\u003c/strong\u003e, the toggle switch controls whether the T-Embed acts as a\nUSB keyboard plugged into whatever it\u0026rsquo;s connected to (a computer, tablet, or other\ndevice that accepts a USB keyboard). When this is switched on, each time the T-Embed\nreceives a shake signal from the paired watch it types a single letter — the letter\n\u003cstrong\u003ea\u003c/strong\u003e — into that device.\u003c/p\u003e\n\u003cp\u003eThis matters when you set up your communication software: whatever you want the shake to\ndo, set it to be triggered by the \u003cstrong\u003ea\u003c/strong\u003e key.\u003c/p\u003e\n\u003cp\u003eClick the switch to turn it on or off. As with Audio Control, this saves immediately —\nthere\u0026rsquo;s no separate submit button.\u003c/p\u003e\n\u003cp\u003eYou can turn both Audio Control and Keyboard Control on together, or use just one of\nthem, depending on what you need.\u003c/p\u003e\n\u003ch2 id=\"step-4--enter-the-t-watchs-mac-address\"\u003eStep 4 — Enter the T-Watch\u0026rsquo;s MAC address\u003c/h2\u003e\n\u003cp\u003eUnder \u003cstrong\u003eEnter MAC Address for the Paired T-Watch\u003c/strong\u003e, type in the MAC address of the\nT-Watch S3 that this T-Embed should listen to. This is what pairs the two devices\ntogether, so that your T-Embed only reacts to your own watch and ignores anybody\nelse\u0026rsquo;s.\u003c/p\u003e\n\u003cp\u003eYou can type the address either with colons (e.g. \u003ccode\u003eAA:BB:CC:DD:EE:FF\u003c/code\u003e) or without them\n(e.g. \u003ccode\u003eAABBCCDDEEFF\u003c/code\u003e) — either format is accepted.\u003c/p\u003e\n\u003cp\u003eIf you don\u0026rsquo;t already know your watch\u0026rsquo;s MAC address, see the\n\u003ca href=\"https://handshaketec.com/mac_identification/\"\u003eFinding the MAC address of your module\u003c/a\u003e\npage for step-by-step instructions on reading it off the watch using a free website\ncalled ESPConnect. Make sure you read the address \u003cstrong\u003efrom the watch\u003c/strong\u003e, not the T-Embed —\nit\u0026rsquo;s easy to mix the two devices up when you have both plugged in.\u003c/p\u003e\n\u003cp\u003eOnce you\u0026rsquo;ve typed in the address, click \u003cstrong\u003eSubmit MAC\u003c/strong\u003e. A message will appear\nconfirming it\u0026rsquo;s been saved. If you see a warning about an invalid address instead,\ndouble-check you\u0026rsquo;ve copied it correctly — the tips on the MAC identification page\nabout characters that look alike (0/O, 1/I/L, 8/B) are worth a look if you\u0026rsquo;re stuck.\u003c/p\u003e\n\u003ch2 id=\"step-5--generate-the-firmware\"\u003eStep 5 — Generate the firmware\u003c/h2\u003e\n\u003cp\u003eOnce you\u0026rsquo;ve set the options above the way you want them, click \u003cstrong\u003eGenerate Firmware\u003c/strong\u003e.\nThe site reads your choices and builds the firmware file. This usually takes a minute\nor two, and you\u0026rsquo;ll see progress messages while it works, so please be patient.\u003c/p\u003e\n\u003cp\u003eThe build carries on if you leave the page. Come back to this page and you\u0026rsquo;ll see its\nprogress, or its result if it has finished.\u003c/p\u003e\n\u003ch2 id=\"step-6--download-the-firmware\"\u003eStep 6 — Download the firmware\u003c/h2\u003e\n\u003cp\u003eWhile the build runs, the download button shows \u003cem\u003eBuilding\u0026hellip;\u003c/em\u003e. Once the build finishes,\na \u003cstrong\u003edownload\u003c/strong\u003e option appears. Click it to save the firmware file to your computer.\u003c/p\u003e\n\u003cp\u003eOnce it\u0026rsquo;s downloaded, the firmware is yours forever! You own it. No time limits, no\nsubscriptions. All yours.\u003c/p\u003e\n\u003cp\u003eTo put this firmware onto your T-Embed, see the\n\u003ca href=\"https://handshaketec.com/flash_device_help/\"\u003eDevice Flasher help page\u003c/a\u003e for step-by-step\ninstructions.\u003c/p\u003e\n"},{"title":"Finding the MAC address of your module","date_published":"2026-09-10T00:00:00Z","date_modified":"2026-09-10T00:00:00Z","id":"https://handshaketec.com/mac_identification/","url":"https://handshaketec.com/mac_identification/","content_html":"\u003ch1 id=\"what-is-a-mac\"\u003eWhat is a MAC?\u003c/h1\u003e\n\u003cp\u003eEvery wireless device has a MAC address. It is a unique code set at the factory. You can think of it as the device\u0026rsquo;s serial number for wireless communication.\u003c/p\u003e\n\u003cp\u003eA MAC address is 12 characters long. It is usually written in pairs separated by colons, for example:\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003e34:85:18:A1:2B:3C\u003c/code\u003e\u003c/p\u003e\n\u003cp\u003eIt only ever contains the numbers 0 to 9 and the letters A to F.\u003c/p\u003e\n\u003ch1 id=\"why-does-handshake-need-it\"\u003eWhy does HandShake need it?\u003c/h1\u003e\n\u003cp\u003eThe watch and the receiver each need to know the other\u0026rsquo;s MAC address:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eThe \u003cstrong\u003ewatch\u003c/strong\u003e needs the receiver\u0026rsquo;s MAC address so it knows where to send its signals.\u003c/li\u003e\n\u003cli\u003eThe \u003cstrong\u003ereceiver\u003c/strong\u003e needs the watch\u0026rsquo;s MAC address so it knows which watch to listen to. It will then ignore every other watch.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eThis matters when several people use HandShake in the same room. Each watch talks only to its own receiver and each receiver responds only to its own watch.\u003c/p\u003e\n\u003ch1 id=\"how-to-find-the-mac-addresses\"\u003eHow to find the MAC addresses\u003c/h1\u003e\n\u003cp\u003eYou need two MAC addresses: one for the watch and one for the receiver. You can read both using a free website called ESPConnect. Nothing needs to be installed.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eYou will need:\u003c/strong\u003e\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eA computer running Google Chrome or Microsoft Edge. Other browsers such as Firefox and Safari will not work.\u003c/li\u003e\n\u003cli\u003eA USB-C cable that carries data. Some cables only charge. If the device doesn\u0026rsquo;t appear in step 4, try a different cable.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e\u003cstrong\u003eSteps:\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003eFollow these steps for the watch, then repeat them for the receiver.\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003eConnect the device to the computer with the USB cable.\u003c/li\u003e\n\u003cli\u003eOpen Chrome or Edge and go to \u003ca href=\"https://thelastoutpostworkshop.github.io/ESPConnect/\"\u003ehttps://thelastoutpostworkshop.github.io/ESPConnect/\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003eClick \u003cstrong\u003eConnect\u003c/strong\u003e.\u003c/li\u003e\n\u003cli\u003eA box will pop up asking you to choose a device. Select the device. It is usually listed as \u003cem\u003eUSB JTAG/serial debug unit\u003c/em\u003e. Then click \u003cstrong\u003eConnect\u003c/strong\u003e in the box.\u003c/li\u003e\n\u003cli\u003eWait a few seconds for the page to finish connecting.\u003c/li\u003e\n\u003cli\u003eOpen the \u003cstrong\u003eDevice Info\u003c/strong\u003e tab. The MAC address is shown in the summary.\u003c/li\u003e\n\u003cli\u003eWrite the MAC address down and label it clearly as \u003cstrong\u003ewatch\u003c/strong\u003e or \u003cstrong\u003ereceiver\u003c/strong\u003e.\u003c/li\u003e\n\u003cli\u003eClick \u003cstrong\u003eDisconnect\u003c/strong\u003e and unplug the device.\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003eIf the device doesn\u0026rsquo;t start normally after unplugging, turn it off and on again.\u003c/p\u003e\n\u003ch1 id=\"tips-for-entering-the-mac-addresses\"\u003eTips for entering the MAC addresses\u003c/h1\u003e\n\u003cul\u003e\n\u003cli\u003eDon\u0026rsquo;t swap them. The watch is given the \u003cstrong\u003ereceiver\u0026rsquo;s\u003c/strong\u003e address. The receiver is given the \u003cstrong\u003ewatch\u0026rsquo;s\u003c/strong\u003e address.\u003c/li\u003e\n\u003cli\u003eCopy each address carefully, one pair at a time.\u003c/li\u003e\n\u003cli\u003eWatch out for characters that look alike. A MAC address never contains the letter O, so it is always the number zero. The same goes for I and L, which are always the number 1.\u003c/li\u003e\n\u003cli\u003eCheck that you haven\u0026rsquo;t mixed up 8 and B.\u003c/li\u003e\n\u003cli\u003eCapital or lower-case letters make no difference.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eIf the watch and receiver don\u0026rsquo;t connect, a mistyped or swapped MAC address is the most likely cause. Check both addresses character by character before trying anything else.\u003c/p\u003e\n"},{"title":"Getting Started","date_published":"2026-08-21T00:00:00Z","date_modified":"2026-08-21T00:00:00Z","id":"https://handshaketec.com/getting_started/","url":"https://handshaketec.com/getting_started/","content_html":"\u003cp\u003eThis page shows how to get started with setting up the HandShake project.\u003c/p\u003e\n\u003ch1 id=\"what-youll-need\"\u003eWhat you\u0026rsquo;ll need\u003c/h1\u003e\n\u003cp\u003e\u003ca href=\"https://lilygo.cc/products/t-watch-s3\"\u003eLilygo T-Watch S3\u003c/a\u003e, (~$45)\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://lilygo.cc/products/t-embed\"\u003eLilygo T-Embed\u003c/a\u003e (~$30)\u003c/p\u003e\n\u003cp\u003eOne USB micro cable to connect with the T-Watch for programming and charging.\u003c/p\u003e\n\u003cp\u003eOne USB C cable to connect with the T-Embed for programming, to connect to the device you want to send a keystroke to and for powering the T-Embed through.\u003c/p\u003e\n\u003ch1 id=\"getting-started-with-the-t-watch-s3-and-the-t-embed\"\u003eGetting started with the T-Watch S3 and the T-Embed\u003c/h1\u003e\n\u003cp\u003eThe first time that you use the T-Watch S3, you\u0026rsquo;ll need to turn on the battery. Instructions and photos showing how to do this are \u003ca href=\"https://handshaketec.com/t_watch_s3_first_time_use\"\u003eon this page\u003c/a\u003e.\nI got caught out by not switching on the battery!\u003c/p\u003e\n\u003cp\u003eThe T-Embed is powered through the USB cable so you don\u0026rsquo;t need to worry about turning on a battery for this.\u003c/p\u003e\n\u003cp\u003eOnce you\u0026rsquo;ve checked that the two devices are in good working order, you need to program each device with the HandShake code.\u003c/p\u003e\n\u003cp\u003eFor each device you work through the relevant configuration page, generate the code for the device, then download and save the code to your own laptop or PC. Now you own the code. It\u0026rsquo;s yours.\u003c/p\u003e\n\u003cp\u003eOnce you have the code saved, you can load it on to the watch or receiver. This is called \u0026lsquo;flashing\u0026rsquo; the device as the memory that the code is written to is called flash memory.\u003c/p\u003e\n\u003cp\u003eEach configuration page has a comprehensive help page.\u003c/p\u003e\n\u003cp\u003eThese are the pages that allow you to generate and download the code:\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://handshaketec.com/watch_configuration/\"\u003eT-Watch-S3 configuration page\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://handshaketec.com/embed_configuration/\"\u003eT-Embed configuration page\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003eThese are the help pages for the configuration pages:\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://handshaketec.com/watch_configuration_help/\"\u003eT-Watch-S3 configuration help page\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://handshaketec.com/embed_configuration_help/\"\u003eT-Embed configuration help page\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003eLoad the firmware onto the device using the \u003ca href=\"https://handshaketec.com/flash_device/\"\u003edevice flasher page\u003c/a\u003e\u003c/p\u003e\n"},{"title":"HandShake T-Embed configuration","date_published":"2026-08-21T00:00:00Z","date_modified":"2026-08-21T00:00:00Z","id":"https://handshaketec.com/embed_configuration/","url":"https://handshaketec.com/embed_configuration/","content_html":"\u003c!-- HTMX loaded locally to avoid CDN dependency and improve performance --\u003e\n\u003chead\u003e\n\u003cscript src=\"/static/js/htmx.min.js\"\u003e\u003c/script\u003e\n\n\u003cdiv id=\"backend-status-banner\" role=\"alert\" aria-live=\"assertive\" hidden\n     style=\"border:1px solid #b45309;border-left:4px solid #b45309;background:#fffbeb;\n            color:#7c2d12;padding:0.85rem 1rem;border-radius:4px;margin:1rem 0;\n            font-family:system-ui,-apple-system,'Segoe UI',sans-serif;line-height:1.5;\"\u003e\n  \u003cstrong style=\"display:block;margin-bottom:0.25rem;\"\u003eCannot reach the server\u003c/strong\u003e\n  \u003cspan\u003e\n    This page cannot contact the configuration service, so the controls below will\n    not work. Check your connection and reload the page.\n  \u003c/span\u003e\n\u003c/div\u003e\n\n\u003cscript\u003e\n(function () {\n  if (typeof htmx === 'undefined') {\n    console.error('backend_status: htmx is not loaded yet — include this shortcode after the htmx script tag.');\n    return;\n  }\n\n  \n  \n  \n  \n  \n  htmx.config.responseHandling = [\n    { code: '204', swap: false },\n    { code: '[23]..', swap: true },\n    { code: '5..', swap: true, error: true },\n    { code: '[45]..', swap: false, error: true }\n  ];\n\n  \n  document.body.addEventListener('htmx:sendError', function () {\n    var banner = document.getElementById('backend-status-banner');\n    if (banner) banner.hidden = false;\n  });\n})();\n\u003c/script\u003e\n\n\n\u003cscript\u003e\nwindow.csrfToken = null;\nwindow.csrfTokenError = null;\n\n\n\ndocument.addEventListener('htmx:configRequest', (event) =\u003e {\n  if (window.csrfToken) {\n    event.detail.headers['X-CSRFToken'] = window.csrfToken;\n  }\n});\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nwindow.sessionReady = fetch('/whoami', { signal: AbortSignal.timeout(10000) })\n  .then(r =\u003e r.json().then(data =\u003e {\n    if (!r.ok) throw Object.assign(new Error(data.error || `HTTP ${r.status}`), { data });\n    return data;\n  }));\n\nwindow.sessionReady\n  .then(() =\u003e fetch('/csrf_token', { signal: AbortSignal.timeout(10000) }))\n  .then(r =\u003e {\n    if (!r.ok) throw new Error(`HTTP ${r.status}`);\n    return r.json();\n  })\n  .then(data =\u003e {\n    window.csrfToken = data.csrf_token;\n    \n    document.body.dispatchEvent(new CustomEvent('session-ready'));\n  })\n  .catch(err =\u003e {\n    console.error('Failed to initialize page:', err);\n    window.csrfTokenError = err.message;\n    const statusEl = document.getElementById('csrf-error');\n    if (statusEl) {\n      statusEl.textContent = 'Security error: Could not initialize. Please refresh the page.';\n      statusEl.style.color = '#dc3545';\n      statusEl.style.padding = '10px';\n      statusEl.style.backgroundColor = '#ffe6e6';\n      statusEl.style.marginBottom = '15px';\n      statusEl.style.borderRadius = '4px';\n    }\n  });\n\u003c/script\u003e\n\n\u003c/head\u003e\n\u003ch1 id=\"introduction\"\u003eIntroduction\u003c/h1\u003e\n\u003cp\u003eHandShake configuration tool for the T-Embed device.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eUnder test - something will need fixing!\u003c/strong\u003e\u003c/p\u003e\n\u003cdiv style=\"background-color: #fff3cd; border: 1px solid #ffc107; border-radius: 4px; padding: 15px; margin-bottom: 20px; color: #856404; border-left: 4px solid #ff9800;\"\u003e\n  \u003cstrong\u003e⚠️ Disclaimer:\u003c/strong\u003e ** THIS SITE IS INCOMPLETE - FOR USE FOR TESTING BY THE DEVELOPERS ONLY **\nHandShake is free, open-source software provided as-is, with no warranty of any kind. It configures unmodified, manufacturer-certified hardware (Lilygo T-Watch S3 / T-Embed) that you purchase separately. Before relying on any device for communication access, test its switch response thoroughly with a professional (Occupational Therapist, Speech and Language Therapist, Technologist, experienced Carer). Report issues to \u003ca href=\"matt@mattoppenheim.com\"\u003ematt@mattoppenheim.com\u003c/a\u003e\n\u003c/div\u003e\n\n\u003cdiv id=\"csrf-error\"\u003e\u003c/div\u003e\n\u003c!-- shortcodes are in layouts/shortcodes --\u003e\n\n\u003clink rel=\"stylesheet\" href=\"/css/watch-configuration.css?v=3\"\u003e\n\n\u003cdiv class=\"session-box\" id=\"session-box\"\u003e\n  \u003ch2\u003eYour session name\u003c/h2\u003e\n  \u003cdiv class=\"session-name\" id=\"session-name\"\u003e…\u003c/div\u003e\n\u003c/div\u003e\n\n\u003cscript type=\"module\"\u003e\n  import { SessionNameComponent } from '/js/components/session-name.js';\n  new SessionNameComponent('session-box');\n\u003c/script\u003e\n\n\u003cscript\u003e\n(function () {\n  function init() {\n    const form = document.getElementById(\"mac_form_tembed\");\n    if (!form) return;\n\n    const errorDiv = document.getElementById(\"mac-twatch-submit-error\");\n    const responseDiv = document.getElementById(\"mac_response_tembed\");\n\n    form.addEventListener('htmx:responseError', function () {\n      if (errorDiv) errorDiv.style.display = 'block';\n    });\n\n    form.addEventListener('htmx:beforeRequest', function () {\n      if (errorDiv) errorDiv.style.display = 'none';\n      if (responseDiv) responseDiv.innerHTML = '';\n    });\n  }\n\n  \n  \n  if (document.readyState === 'loading') {\n    document.addEventListener('DOMContentLoaded', init);\n  } else {\n    init();\n  }\n})();\n\u003c/script\u003e\n\n\u003ch1 id=\"audio-control-tembed\"\u003eAudio Control\u003c/h1\u003e\n\n\u003cdiv class=\"audio-control-section\"\u003e\n  \u003clabel for=\"audio-toggle-tembed\" class=\"audio-label\"\u003eAudio on shake:\u003c/label\u003e\n  \u003cdiv class=\"toggle-wrapper\"\u003e\n    \u003cdiv hx-get=\"/get_audio_input_tembed\" hx-trigger=\"load, session-ready from:body\" hx-swap=\"innerHTML\" hx-on::afterSwap=\"htmx.process(this)\"\u003e\n      \u003cinput type=\"checkbox\" id=\"audio-toggle-tembed\" name=\"audio_enabled\" class=\"toggle-checkbox\" hx-post=\"/submit_audio_tembed\" hx-trigger=\"change\" hx-target=\"#audio-response-tembed\" hx-swap=\"innerHTML\"\u003e\n    \u003c/div\u003e\n    \u003clabel for=\"audio-toggle-tembed\" class=\"toggle-label\"\u003e\u003c/label\u003e\n  \u003c/div\u003e\n  \u003cdiv id=\"audio-load-error\" class=\"toggle-error\" style=\"display:none;\"\u003e\n    ⚠️ Failed to load audio setting. Please refresh the page.\n  \u003c/div\u003e\n  \n  \u003cdiv id=\"audio-response-tembed\" class=\"audio-response\"\n       hx-get=\"/get_audio_status_tembed\" hx-trigger=\"load, session-ready from:body\" hx-swap=\"innerHTML\"\u003e\u003c/div\u003e\n\u003c/div\u003e\n\n\u003cstyle\u003e\n  .audio-control-section {\n    padding: 1em;\n    border: 1px solid #ddd;\n    border-radius: 6px;\n    background: #fafafa;\n    margin-bottom: 1.5em;\n  }\n\n  .audio-label {\n    display: block;\n    margin-bottom: 0.5em;\n    font-weight: bold;\n    color: #333;\n  }\n\n  .toggle-wrapper {\n    display: flex;\n    align-items: center;\n    gap: 1em;\n  }\n\n  .toggle-checkbox {\n    appearance: none;\n    -webkit-appearance: none;\n    width: 50px;\n    height: 28px;\n    background: #ccc;\n    border-radius: 14px;\n    cursor: pointer;\n    position: relative;\n    outline: none;\n    transition: background 0.3s ease;\n  }\n\n  .toggle-checkbox:checked {\n    background: #4caf50;\n  }\n\n  .toggle-checkbox::before {\n    content: '';\n    position: absolute;\n    width: 24px;\n    height: 24px;\n    border-radius: 50%;\n    background: white;\n    top: 2px;\n    left: 2px;\n    transition: left 0.3s ease;\n    box-shadow: 0 2px 4px rgba(0,0,0,0.2);\n  }\n\n  .toggle-checkbox:checked::before {\n    left: 24px;\n  }\n\n  .toggle-label {\n    display: inline-block;\n    cursor: pointer;\n  }\n\n  .audio-response {\n    margin-top: 0.5em;\n    font-size: 0.9em;\n  }\n\n  .audio-response.success {\n    color: #4caf50;\n    padding: 0.5em;\n    background: #e8f5e9;\n    border-radius: 4px;\n  }\n\n  .audio-response.error {\n    color: #f44336;\n    padding: 0.5em;\n    background: #ffebee;\n    border-radius: 4px;\n  }\n\n  .toggle-error {\n    margin-top: 0.5em;\n    padding: 0.75em;\n    background-color: #fff3cd;\n    color: #856404;\n    border: 1px solid #ffc107;\n    border-radius: 4px;\n    font-size: 0.9em;\n  }\n\n  @media (prefers-color-scheme: dark) {\n    .toggle-error {\n      background-color: #664d03;\n      color: #fff;\n      border-color: #997404;\n    }\n  }\n\u003c/style\u003e\n\n\n\u003ch1 id=\"keyboard-control-tembed\"\u003eKeyboard Control\u003c/h1\u003e\n\n\u003cdiv class=\"keyboard-control-section\"\u003e\n  \u003clabel for=\"keyboard-toggle-tembed\" class=\"keyboard-label\"\u003eUSB keyboard on shake:\u003c/label\u003e\n  \u003cdiv class=\"toggle-wrapper\"\u003e\n    \u003cdiv hx-get=\"/get_keyboard_input_tembed\" hx-trigger=\"load, session-ready from:body\" hx-swap=\"innerHTML\" hx-on::afterSwap=\"htmx.process(this)\"\u003e\n      \u003cinput type=\"checkbox\" id=\"keyboard-toggle-tembed\" name=\"keyboard_enabled\" class=\"toggle-checkbox\" hx-post=\"/submit_keyboard_tembed\" hx-trigger=\"change\" hx-target=\"#keyboard-response-tembed\" hx-swap=\"innerHTML\"\u003e\n    \u003c/div\u003e\n    \u003clabel for=\"keyboard-toggle-tembed\" class=\"toggle-label\"\u003e\u003c/label\u003e\n  \u003c/div\u003e\n  \u003cdiv id=\"keyboard-load-error\" class=\"toggle-error\" style=\"display:none;\"\u003e\n    ⚠️ Failed to load keyboard setting. Please refresh the page.\n  \u003c/div\u003e\n  \n  \u003cdiv id=\"keyboard-response-tembed\" class=\"keyboard-response\"\n       hx-get=\"/get_keyboard_status_tembed\" hx-trigger=\"load, session-ready from:body\" hx-swap=\"innerHTML\"\u003e\u003c/div\u003e\n\u003c/div\u003e\n\n\u003cstyle\u003e\n  .keyboard-control-section {\n    padding: 1em;\n    border: 1px solid #ddd;\n    border-radius: 6px;\n    background: #fafafa;\n    margin-bottom: 1.5em;\n  }\n\n  .keyboard-label {\n    display: block;\n    margin-bottom: 0.5em;\n    font-weight: bold;\n    color: #333;\n  }\n\n  .toggle-wrapper {\n    display: flex;\n    align-items: center;\n    gap: 1em;\n  }\n\n  .toggle-checkbox {\n    appearance: none;\n    -webkit-appearance: none;\n    width: 50px;\n    height: 28px;\n    background: #ccc;\n    border-radius: 14px;\n    cursor: pointer;\n    position: relative;\n    outline: none;\n    transition: background 0.3s ease;\n  }\n\n  .toggle-checkbox:checked {\n    background: #2196f3;\n  }\n\n  .toggle-checkbox::before {\n    content: '';\n    position: absolute;\n    width: 24px;\n    height: 24px;\n    border-radius: 50%;\n    background: white;\n    top: 2px;\n    left: 2px;\n    transition: left 0.3s ease;\n    box-shadow: 0 2px 4px rgba(0,0,0,0.2);\n  }\n\n  .toggle-checkbox:checked::before {\n    left: 24px;\n  }\n\n  .toggle-label {\n    display: inline-block;\n    cursor: pointer;\n  }\n\n  .keyboard-response {\n    margin-top: 0.5em;\n    font-size: 0.9em;\n  }\n\n  .keyboard-response.success {\n    color: #2196f3;\n    padding: 0.5em;\n    background: #e3f2fd;\n    border-radius: 4px;\n  }\n\n  .keyboard-response.error {\n    color: #f44336;\n    padding: 0.5em;\n    background: #ffebee;\n    border-radius: 4px;\n  }\n\n  .toggle-error {\n    margin-top: 0.5em;\n    padding: 0.75em;\n    background-color: #fff3cd;\n    color: #856404;\n    border: 1px solid #ffc107;\n    border-radius: 4px;\n    font-size: 0.9em;\n  }\n\n  @media (prefers-color-scheme: dark) {\n    .toggle-error {\n      background-color: #664d03;\n      color: #fff;\n      border-color: #997404;\n    }\n  }\n\u003c/style\u003e\n\n\n\u003ch1 id=\"esp-now-mac-address-configuration-tembed\"\u003eEnter MAC Address for the Paired T-Watch\u003c/h1\u003e\n\n\u003cdiv id=\"esp-now-mac-tembed-component\"\u003e\n  \u003cform hx-target=\"#mac_submit_response_tembed\" hx-post=\"/submit_mac_tembed\" hx-swap=\"innerHTML\" hx-on::after-request=\"htmx.ajax('GET', '/get_mac_status_tembed', {target: '#mac_response_tembed', swap: 'innerHTML'})\" id=\"mac_form_tembed\"\u003e\n    \u003clabel for=\"mac_address_tembed\"\u003eT-Watch MAC Address (e.g. AA:BB:CC:DD:EE:FF or AABBCCDDEEFF):\u003c/label\u003e\u003cbr\u003e\n    \u003cdiv hx-get=\"/get_mac_input_tembed\" hx-trigger=\"load, session-ready from:body\" hx-swap=\"innerHTML\" hx-on::response-error=\"document.getElementById('mac-twatch-load-error').style.display='block'\"\u003e\n      \u003cinput type=\"text\" name=\"mac_address\" id=\"mac_address_tembed\" class=\"mac-address-input\" required\n             maxlength=\"23\" placeholder=\"AA:BB:CC:DD:EE:FF or AABBCCDDEEFF\" style=\"margin-top: 10px;\"\u003e\n    \u003c/div\u003e\n    \u003cdiv id=\"mac-twatch-load-error\" class=\"control-error\" style=\"display:none; margin-top: 10px;\"\u003e\n      ⚠️ Failed to load MAC address field. Please refresh the page.\n    \u003c/div\u003e\n    \u003cdiv id=\"mac-twatch-submit-error\" class=\"control-error\" style=\"display:none; margin-top: 10px;\"\u003e\n      ⚠️ Invalid MAC address. Please enter a valid address (e.g., AA:BB:CC:DD:EE:FF).\n    \u003c/div\u003e\n    \u003cbr\u003e\n    \u003cinput type=\"submit\" value=\"Submit MAC\" class=\"button-primary\"\u003e\n  \u003c/form\u003e\n  \u003cdiv id=\"mac_submit_response_tembed\" style=\"display: none;\"\u003e\u003c/div\u003e\n  \u003cdiv id=\"mac_response_tembed\" hx-get=\"/get_mac_status_tembed\" hx-trigger=\"load, session-ready from:body\" hx-swap=\"innerHTML\"\u003e\u003c/div\u003e\n\n\u003cstyle\u003e\n  .control-error {\n    padding: 0.75em;\n    background-color: #fff3cd;\n    color: #856404;\n    border: 1px solid #ffc107;\n    border-radius: 4px;\n    font-size: 0.9em;\n  }\n\n  #mac_response_tembed {\n    margin-top: 10px;\n    font-size: 0.9em;\n    color: #333;\n  }\n\n  @media (prefers-color-scheme: dark) {\n    .control-error {\n      background-color: #664d03;\n      color: #fff;\n      border-color: #997404;\n    }\n    #mac_response_tembed {\n      color: #e0e0e0;\n    }\n  }\n\u003c/style\u003e\n\u003c/div\u003e\n\n\n\n\u003ch1\u003eGenerate T-Embed Firmware\u003c/h1\u003e\n\n\u003cdiv id=\"generate-container-tembed\"\u003e\n  \u003cbutton\n    id=\"generate-btn-tembed\"\n    hx-post=\"/generate_firmware_tembed\"\n    hx-trigger=\"click\"\n    hx-swap=\"none\"\n    class=\"generate-button\"\n    hx-on::after-request=\"window.onGenerateRequestFinishedTembed?.(event)\"\n  \u003e\n    Generate Firmware\n  \u003c/button\u003e\n\n  \u003cp id=\"other-build-note-tembed\" class=\"build-note\" hidden\u003e\u003c/p\u003e\n\n  \u003cdiv id=\"throbber-tembed\" style=\"display:none; margin-top:10px;\"\u003e\n    \u003cdiv class=\"spinner\"\u003e\u003c/div\u003e\n    \u003cp\u003eBuilding T-Embed firmware, please wait...\u003c/p\u003e\n  \u003c/div\u003e\n\n  \u003cdiv id=\"output-box-tembed\" style=\"display:none; margin-top:10px;\"\u003e\n    \u003cdiv class=\"output-container\"\u003e\n      \u003ch3\u003eBuild Output:\u003c/h3\u003e\n      \u003cpre id=\"output-text-tembed\" class=\"output-text\"\u003e\u003c/pre\u003e\n      \u003cdiv id=\"build-result-tembed\"\u003e\u003c/div\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n\u003c/div\u003e\n\n\n\u003cscript type=\"module\"\u003e\n  import { GenerateFirmwareComponent } from '/js/components/generate-firmware.js?v=3';\n\n  \n  console.log('Checking elements:');\n  console.log('generate-container-tembed:', !!document.getElementById('generate-container-tembed'));\n  console.log('output-box-tembed:', !!document.getElementById('output-box-tembed'));\n  console.log('throbber-tembed:', !!document.getElementById('throbber-tembed'));\n  console.log('output-text-tembed:', !!document.getElementById('output-text-tembed'));\n\n  new GenerateFirmwareComponent('generate-container-tembed', 'tembed');\n\u003c/script\u003e\n\n\u003ch1\u003eDownload T-Embed Firmware\u003c/h1\u003e\n\n\u003cdiv id=\"download-elf-container-tembed\"\u003e\n  \n  \u003cdiv\n    id=\"elf-check-container-tembed\"\n    hx-get=\"/check_elf_tembed\"\n    hx-trigger=\"load, refreshDownload from:body\"\n    hx-swap=\"innerHTML\"\n    hx-on::response-error=\"document.getElementById('download-elf-tembed-load-error').style.display='block'\"\n  \u003e\n    \u003cp\u003eChecking for firmware file...\u003c/p\u003e\n  \u003c/div\u003e\n  \u003cdiv id=\"download-elf-tembed-load-error\" class=\"control-error\" style=\"display:none; margin-top: 10px;\"\u003e\n    ⚠️ Failed to check firmware file. Please refresh the page.\n  \u003c/div\u003e\n\u003c/div\u003e\n\n\u003cstyle\u003e\n  .download-button {\n    padding: 10px 20px;\n    font-size: 16px;\n    background-color: #28a745;\n    color: white;\n    border: none;\n    border-radius: 5px;\n    cursor: pointer;\n    transition: background-color 0.3s;\n    text-decoration: none;\n    display: inline-block;\n  }\n\n  .download-button:hover {\n    background-color: #218838;\n  }\n\n  .download-button:active {\n    background-color: #1e7e34;\n  }\n\n  .file-info {\n    margin-top: 10px;\n    padding: 10px;\n    background-color: #f8f9fa;\n    border: 1px solid #dee2e6;\n    border-radius: 5px;\n    font-size: 14px;\n    color: #495057;\n  }\n\n  .no-file-message {\n    color: #6c757d;\n    font-style: italic;\n  }\n\n  .control-error {\n    padding: 0.75em;\n    background-color: #fff3cd;\n    color: #856404;\n    border: 1px solid #ffc107;\n    border-radius: 4px;\n    font-size: 0.9em;\n  }\n\n  @media (prefers-color-scheme: dark) {\n    .control-error {\n      background-color: #664d03;\n      color: #fff;\n      border-color: #997404;\n    }\n  }\n\u003c/style\u003e\n\n"},{"title":"HandShake T-Watch S3 configuration","date_published":"2026-08-21T00:00:00Z","date_modified":"2026-08-21T00:00:00Z","id":"https://handshaketec.com/watch_configuration/","url":"https://handshaketec.com/watch_configuration/","content_html":"\u003c!-- HTMX loaded locally to avoid CDN dependency and improve performance --\u003e\n\u003chead\u003e\n\u003cscript src=\"/static/js/htmx.min.js\"\u003e\u003c/script\u003e\n\n\u003cdiv id=\"backend-status-banner\" role=\"alert\" aria-live=\"assertive\" hidden\n     style=\"border:1px solid #b45309;border-left:4px solid #b45309;background:#fffbeb;\n            color:#7c2d12;padding:0.85rem 1rem;border-radius:4px;margin:1rem 0;\n            font-family:system-ui,-apple-system,'Segoe UI',sans-serif;line-height:1.5;\"\u003e\n  \u003cstrong style=\"display:block;margin-bottom:0.25rem;\"\u003eCannot reach the server\u003c/strong\u003e\n  \u003cspan\u003e\n    This page cannot contact the configuration service, so the controls below will\n    not work. Check your connection and reload the page.\n  \u003c/span\u003e\n\u003c/div\u003e\n\n\u003cscript\u003e\n(function () {\n  if (typeof htmx === 'undefined') {\n    console.error('backend_status: htmx is not loaded yet — include this shortcode after the htmx script tag.');\n    return;\n  }\n\n  \n  \n  \n  \n  \n  htmx.config.responseHandling = [\n    { code: '204', swap: false },\n    { code: '[23]..', swap: true },\n    { code: '5..', swap: true, error: true },\n    { code: '[45]..', swap: false, error: true }\n  ];\n\n  \n  document.body.addEventListener('htmx:sendError', function () {\n    var banner = document.getElementById('backend-status-banner');\n    if (banner) banner.hidden = false;\n  });\n})();\n\u003c/script\u003e\n\n\n\u003cscript\u003e\nwindow.csrfToken = null;\nwindow.csrfTokenError = null;\n\n\n\ndocument.addEventListener('htmx:configRequest', (event) =\u003e {\n  if (window.csrfToken) {\n    event.detail.headers['X-CSRFToken'] = window.csrfToken;\n  }\n});\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nwindow.sessionReady = fetch('/whoami', { signal: AbortSignal.timeout(10000) })\n  .then(r =\u003e r.json().then(data =\u003e {\n    if (!r.ok) throw Object.assign(new Error(data.error || `HTTP ${r.status}`), { data });\n    return data;\n  }));\n\nwindow.sessionReady\n  .then(() =\u003e fetch('/csrf_token', { signal: AbortSignal.timeout(10000) }))\n  .then(r =\u003e {\n    if (!r.ok) throw new Error(`HTTP ${r.status}`);\n    return r.json();\n  })\n  .then(data =\u003e {\n    window.csrfToken = data.csrf_token;\n    \n    document.body.dispatchEvent(new CustomEvent('session-ready'));\n  })\n  .catch(err =\u003e {\n    console.error('Failed to initialize page:', err);\n    window.csrfTokenError = err.message;\n    const statusEl = document.getElementById('csrf-error');\n    if (statusEl) {\n      statusEl.textContent = 'Security error: Could not initialize. Please refresh the page.';\n      statusEl.style.color = '#dc3545';\n      statusEl.style.padding = '10px';\n      statusEl.style.backgroundColor = '#ffe6e6';\n      statusEl.style.marginBottom = '15px';\n      statusEl.style.borderRadius = '4px';\n    }\n  });\n\u003c/script\u003e\n\n\u003c/head\u003e\n\u003ch1 id=\"introduction\"\u003eIntroduction\u003c/h1\u003e\n\u003cp\u003eHandShake configuration tool for the T-Watch S3.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eUnder test - something will need fixing!\u003c/strong\u003e\u003c/p\u003e\n\u003cdiv style=\"background-color: #fff3cd; border: 1px solid #ffc107; border-radius: 4px; padding: 15px; margin-bottom: 20px; color: #856404; border-left: 4px solid #ff9800;\"\u003e\n  \u003cstrong\u003e⚠️ Disclaimer:\u003c/strong\u003e ** THIS SITE IS INCOMPLETE - FOR USE FOR TESTING BY THE DEVELOPERS ONLY **\nHandShake is free, open-source software provided as-is, with no warranty of any kind. It configures unmodified, manufacturer-certified hardware (Lilygo T-Watch S3 / T-Embed) that you purchase separately. Before relying on any device for communication access, test its switch response thoroughly with a professional (Occupational Therapist, Speech and Language Therapist, Technologist, experienced Carer). Report issues to \u003ca href=\"matt@mattoppenheim.com\"\u003ematt@mattoppenheim.com\u003c/a\u003e\n\u003c/div\u003e\n\n\u003cdiv id=\"csrf-error\"\u003e\u003c/div\u003e\n\u003c!-- shortcodes are in layouts/shortcodes --\u003e\n\n\u003clink rel=\"stylesheet\" href=\"/css/watch-configuration.css?v=3\"\u003e\n\n\u003cdiv class=\"session-box\" id=\"session-box\"\u003e\n  \u003ch2\u003eYour session name\u003c/h2\u003e\n  \u003cdiv class=\"session-name\" id=\"session-name\"\u003e…\u003c/div\u003e\n\u003c/div\u003e\n\n\u003cscript type=\"module\"\u003e\n  import { SessionNameComponent } from '/js/components/session-name.js';\n  new SessionNameComponent('session-box');\n\u003c/script\u003e\n\n\u003cscript\u003e\n(function () {\n  function init() {\n    const form = document.getElementById(\"mac_form\");\n    if (!form) return;\n\n    const errorDiv = document.getElementById(\"mac-embed-submit-error\");\n    const responseDiv = document.getElementById(\"mac_response\");\n\n    form.addEventListener('htmx:responseError', function () {\n      if (errorDiv) errorDiv.style.display = 'block';\n    });\n\n    form.addEventListener('htmx:beforeRequest', function () {\n      if (errorDiv) errorDiv.style.display = 'none';\n      if (responseDiv) responseDiv.innerHTML = '';\n    });\n  }\n\n  \n  \n  if (document.readyState === 'loading') {\n    document.addEventListener('DOMContentLoaded', init);\n  } else {\n    init();\n  }\n})();\n\u003c/script\u003e\n\n\u003ch1 id=\"gesture-selection\"\u003eGesture Selection\u003c/h1\u003e\n\n\u003cform id=\"gesture_to_detect\"\u003e\n  \n  \u003cdiv hx-get=\"/get_gesture_selection_input\"\n       hx-trigger=\"load, session-ready from:body\"\n       hx-swap=\"innerHTML\"\n       hx-on::afterSwap=\"htmx.process(this)\"\n       hx-on::response-error=\"document.getElementById('gesture-load-error').style.display='block'\"\u003e\n  \u003c/div\u003e\n  \u003cdiv id=\"gesture-load-error\" class=\"control-error\" style=\"display:none; margin-top: 10px;\"\u003e\n    ⚠️ Failed to load gesture control. Please refresh the page.\n  \u003c/div\u003e\n\u003c/form\u003e\n\n\n\u003cdiv id=\"gesture_response\"\n     hx-get=\"/get_gesture_status\" hx-trigger=\"load, session-ready from:body\" hx-swap=\"innerHTML\"\u003e\u003c/div\u003e\n\n\u003cstyle\u003e\n  .control-error {\n    padding: 0.75em;\n    background-color: #fff3cd;\n    color: #856404;\n    border: 1px solid #ffc107;\n    border-radius: 4px;\n    font-size: 0.9em;\n  }\n\n  @media (prefers-color-scheme: dark) {\n    .control-error {\n      background-color: #664d03;\n      color: #fff;\n      border-color: #997404;\n    }\n  }\n\u003c/style\u003e\n\n\n\n\u003ch1 id=\"gesture-sensitivity-control\"\u003eGesture Sensitivity Control\u003c/h1\u003e\n\n\u003cform id=\"sensitivity_form\"\u003e\n  \u003clabel for=\"gesture_sensitivity\"\u003eTrigger threshold (580-900):\u003c/label\u003e\u003cbr\u003e\n  \u003cdiv style=\"display: flex; align-items: center; gap: 1em; margin-top: 10px;\"\u003e\n    \u003cspan style=\"font-size: 0.9em; color: #666; min-width: 50px;\"\u003esofter shake\u003c/span\u003e\n    \u003cdiv style=\"min-width: 200px;\"\u003e\n      \u003cdiv hx-get=\"/get_gesture_sensitivity_input\" hx-trigger=\"load, session-ready from:body\" hx-swap=\"innerHTML\" hx-on::afterSwap=\"htmx.process(this)\" hx-on::response-error=\"document.getElementById('sensitivity-load-error').style.display='block'\" style=\"display: flex; flex: 1; min-width: 200px;\"\u003e\n        \u003cinput type=\"range\" id=\"gesture_sensitivity\" name=\"gesture_sensitivity\" style=\"flex: 1; min-width: 200px;\"\u003e\n      \u003c/div\u003e\n      \u003cdiv style=\"position: relative; height: 1.5em; margin-top: 0.25em; font-size: 0.75em; color: #999;\"\u003e\n        \u003cspan style=\"position: absolute; left: 0%; transform: translateX(-50%);\"\u003e580\u003c/span\u003e\n        \u003cspan style=\"position: absolute; left: 37.5%; transform: translateX(-50%);\"\u003e700\u003c/span\u003e\n        \u003cspan style=\"position: absolute; left: 68.75%; transform: translateX(-50%);\"\u003e800\u003c/span\u003e\n        \u003cspan style=\"position: absolute; left: 100%; transform: translateX(-50%);\"\u003e900\u003c/span\u003e\n      \u003c/div\u003e\n    \u003c/div\u003e\n    \u003cspan style=\"font-size: 0.9em; color: #666; min-width: 50px;\"\u003eharder shake\u003c/span\u003e\n  \u003c/div\u003e\n  \u003cdiv id=\"sensitivity-load-error\" class=\"control-error\" style=\"display:none; margin-top: 10px;\"\u003e\n    ⚠️ Failed to load sensitivity control. Please refresh the page.\n  \u003c/div\u003e\n  \u003cbr\u003e\u003cbr\u003e\n\u003c/form\u003e\n\n\u003cdiv id=\"sensitivity_response\"\n     hx-get=\"/get_gesture_sensitivity_status\" hx-trigger=\"load, session-ready from:body\" hx-swap=\"innerHTML\"\u003e\u003c/div\u003e\n\n\u003cstyle\u003e\n  .control-error {\n    padding: 0.75em;\n    background-color: #fff3cd;\n    color: #856404;\n    border: 1px solid #ffc107;\n    border-radius: 4px;\n    font-size: 0.9em;\n  }\n\n  @media (prefers-color-scheme: dark) {\n    .control-error {\n      background-color: #664d03;\n      color: #fff;\n      border-color: #997404;\n    }\n  }\n\u003c/style\u003e\n\n\n\n\u003ch1 id=\"min-shake-interval-control\"\u003eMinimum Shake Interval Control\u003c/h1\u003e\n\n\u003cform id=\"shake_interval_form\"\u003e\n  \u003clabel for=\"min_shake_interval\"\u003eMinimum Intershake Interval (ms) (0-1000):\u003c/label\u003e\u003cbr\u003e\n  \u003cdiv style=\"display: flex; align-items: center; gap: 1em; margin-top: 10px;\"\u003e\n    \u003cspan style=\"font-size: 0.9em; color: #666; min-width: 50px;\"\u003e\u003c/span\u003e\n    \u003cdiv style=\"min-width: 200px;\"\u003e\n      \u003cdiv hx-get=\"/get_twatch_config_input\" hx-trigger=\"load, session-ready from:body\" hx-swap=\"innerHTML\" hx-on::afterSwap=\"htmx.process(this)\" hx-on::response-error=\"document.getElementById('shake-load-error').style.display='block'\" style=\"display: flex; flex: 1; min-width: 200px;\"\u003e\n        \u003cinput type=\"range\" id=\"min_shake_interval\" name=\"min_shake_interval\" style=\"flex: 1; min-width: 200px;\"\u003e\n      \u003c/div\u003e\n      \u003cdiv style=\"position: relative; height: 1.5em; margin-top: 0.25em; font-size: 0.75em; color: #999;\"\u003e\n        \u003cspan style=\"position: absolute; left: 0%; transform: translateX(-50%);\"\u003e0\u003c/span\u003e\n        \u003cspan style=\"position: absolute; left: 25%; transform: translateX(-50%);\"\u003e250\u003c/span\u003e\n        \u003cspan style=\"position: absolute; left: 50%; transform: translateX(-50%);\"\u003e500\u003c/span\u003e\n        \u003cspan style=\"position: absolute; left: 75%; transform: translateX(-50%);\"\u003e750\u003c/span\u003e\n        \u003cspan style=\"position: absolute; left: 100%; transform: translateX(-50%);\"\u003e1000\u003c/span\u003e\n      \u003c/div\u003e\n    \u003c/div\u003e\n    \u003cspan style=\"font-size: 0.9em; color: #666; min-width: 50px;\"\u003e\u003c/span\u003e\n  \u003c/div\u003e\n  \u003cdiv id=\"shake-load-error\" class=\"control-error\" style=\"display:none; margin-top: 10px;\"\u003e\n    ⚠️ Failed to load shake interval control. Please refresh the page.\n  \u003c/div\u003e\n  \u003cbr\u003e\u003cbr\u003e\n\u003c/form\u003e\n\n\u003cdiv id=\"shake_interval_response\"\n     hx-get=\"/get_shake_interval_status\" hx-trigger=\"load, session-ready from:body\" hx-swap=\"innerHTML\"\u003e\u003c/div\u003e\n\n\u003cstyle\u003e\n  .control-error {\n    padding: 0.75em;\n    background-color: #fff3cd;\n    color: #856404;\n    border: 1px solid #ffc107;\n    border-radius: 4px;\n    font-size: 0.9em;\n  }\n\n  @media (prefers-color-scheme: dark) {\n    .control-error {\n      background-color: #664d03;\n      color: #fff;\n      border-color: #997404;\n    }\n  }\n\u003c/style\u003e\n\n\n\n\u003ch1 id=\"esp-now-mac-address-configuration\"\u003eEnter MAC Address for the T-Embed\u003c/h1\u003e\n\n\u003cdiv id=\"esp-now-mac-component\"\u003e\n  \u003cform hx-target=\"#mac_submit_response\" hx-post=\"/submit_mac\" hx-swap=\"innerHTML\" hx-on::after-request=\"htmx.ajax('GET', '/get_mac_status', {target: '#mac_response', swap: 'innerHTML'})\" id=\"mac_form\"\u003e\n    \u003clabel for=\"mac_address\"\u003eT-Embed MAC Address (e.g. AA:BB:CC:DD:EE:FF or AABBCCDDEEFF):\u003c/label\u003e\u003cbr\u003e\n    \u003cdiv hx-get=\"/get_mac_input\" hx-trigger=\"load, session-ready from:body\" hx-swap=\"innerHTML\"\u003e\n      \u003cinput type=\"text\" name=\"mac_address\" id=\"mac_address\" class=\"mac-address-input\" required\n             maxlength=\"23\" placeholder=\"AA:BB:CC:DD:EE:FF or AABBCCDDEEFF\" style=\"margin-top: 10px;\"\u003e\n    \u003c/div\u003e\n    \u003cdiv id=\"mac-embed-load-error\" class=\"control-error\" style=\"display:none; margin-top: 10px;\"\u003e\n      ⚠️ Failed to load MAC address field. Please refresh the page.\n    \u003c/div\u003e\n    \u003cdiv id=\"mac-embed-submit-error\" class=\"control-error\" style=\"display:none; margin-top: 10px;\"\u003e\n      ⚠️ Invalid MAC address. Please enter a valid address (e.g., AA:BB:CC:DD:EE:FF).\n    \u003c/div\u003e\n    \u003cbr\u003e\n    \u003cinput type=\"submit\" value=\"Submit MAC\" class=\"button-primary\"\u003e\n  \u003c/form\u003e\n  \u003cdiv id=\"mac_submit_response\" style=\"display: none;\"\u003e\u003c/div\u003e\n  \u003cdiv id=\"mac_response\" hx-get=\"/get_mac_status\" hx-trigger=\"load, session-ready from:body\" hx-swap=\"innerHTML\"\u003e\u003c/div\u003e\n\n\u003cstyle\u003e\n  .control-error {\n    padding: 0.75em;\n    background-color: #fff3cd;\n    color: #856404;\n    border: 1px solid #ffc107;\n    border-radius: 4px;\n    font-size: 0.9em;\n  }\n\n  #mac_response {\n    margin-top: 10px;\n    font-size: 0.9em;\n    color: #333;\n  }\n\n  @media (prefers-color-scheme: dark) {\n    .control-error {\n      background-color: #664d03;\n      color: #fff;\n      border-color: #997404;\n    }\n    #mac_response {\n      color: #e0e0e0;\n    }\n  }\n\u003c/style\u003e\n\u003c/div\u003e\n\n\n\n\u003ch1\u003eGenerate Firmware\u003c/h1\u003e\n\n\u003cdiv id=\"generate-container\"\u003e\n  \u003cbutton\n    id=\"generate-btn\"\n    hx-post=\"/generate_firmware\"\n    hx-trigger=\"click\"\n    hx-swap=\"none\"\n    class=\"generate-button\"\n    hx-on::after-request=\"window.onGenerateRequestFinished?.(event)\"\n  \u003e\n    Generate Firmware\n  \u003c/button\u003e\n\n  \u003cp id=\"other-build-note\" class=\"build-note\" hidden\u003e\u003c/p\u003e\n\n  \u003cdiv id=\"throbber\" style=\"display:none; margin-top:10px;\"\u003e\n    \u003cdiv class=\"spinner\"\u003e\u003c/div\u003e\n    \u003cp\u003eBuilding firmware, please wait...\u003c/p\u003e\n  \u003c/div\u003e\n\n  \u003cdiv id=\"output-box\" style=\"display:none; margin-top:10px;\"\u003e\n    \u003cdiv class=\"output-container\"\u003e\n      \u003ch3\u003eBuild Output:\u003c/h3\u003e\n      \u003cpre id=\"output-text\" class=\"output-text\"\u003e\u003c/pre\u003e\n      \u003cdiv id=\"build-result\"\u003e\u003c/div\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n\u003c/div\u003e\n\n\n\u003cscript type=\"module\"\u003e\n  import { GenerateFirmwareComponent } from '/js/components/generate-firmware.js?v=3';\n  new GenerateFirmwareComponent('generate-container');\n\u003c/script\u003e\n\n\u003ch1\u003eDownload Firmware\u003c/h1\u003e\n\n\u003cdiv id=\"download-elf-container\"\u003e\n  \n  \u003cdiv\n    id=\"elf-check-container\"\n    hx-get=\"/check_elf\"\n    hx-trigger=\"load, refreshDownload from:body\"\n    hx-swap=\"innerHTML\"\n    hx-on::response-error=\"document.getElementById('download-elf-load-error').style.display='block'\"\n  \u003e\n    \u003cp\u003eChecking for firmware file...\u003c/p\u003e\n  \u003c/div\u003e\n  \u003cdiv id=\"download-elf-load-error\" class=\"control-error\" style=\"display:none; margin-top: 10px;\"\u003e\n    ⚠️ Failed to check firmware file. Please refresh the page.\n  \u003c/div\u003e\n\u003c/div\u003e\n\n\u003cstyle\u003e\n  .download-button {\n    padding: 10px 20px;\n    font-size: 16px;\n    background-color: #28a745;\n    color: white;\n    border: none;\n    border-radius: 5px;\n    cursor: pointer;\n    transition: background-color 0.3s;\n    text-decoration: none;\n    display: inline-block;\n  }\n  \n  .download-button:hover {\n    background-color: #218838;\n  }\n  \n  .download-button:active {\n    background-color: #1e7e34;\n  }\n  \n  .file-info {\n    margin-top: 10px;\n    padding: 10px;\n    background-color: #f8f9fa;\n    border: 1px solid #dee2e6;\n    border-radius: 5px;\n    font-size: 14px;\n    color: #495057;\n  }\n  \n  .no-file-message {\n    color: #6c757d;\n    font-style: italic;\n  }\n\n  .control-error {\n    padding: 0.75em;\n    background-color: #fff3cd;\n    color: #856404;\n    border: 1px solid #ffc107;\n    border-radius: 4px;\n    font-size: 0.9em;\n  }\n\n  @media (prefers-color-scheme: dark) {\n    .control-error {\n      background-color: #664d03;\n      color: #fff;\n      border-color: #997404;\n    }\n  }\n\u003c/style\u003e\n\n"},{"title":"Conformance \u0026 Certifications","date_published":"2026-08-04T00:00:00Z","date_modified":"2026-08-04T00:00:00Z","id":"https://handshaketec.com/compliance/","url":"https://handshaketec.com/compliance/","content_html":"\u003ch1 id=\"lilygo-t-watch-s3\"\u003eLilygo T-Watch S3\u003c/h1\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"/documents/compliance/t_watch_s3_ce_emc_certificates/T-WATCH%20S3_2401U38108E%20ROHS%20Report.pdf\"\u003eRoHS Report\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"/documents/compliance/t_watch_s3_ce_emc_certificates/T-WATCH%20S3_C2401U21148E-EM%20EMC%20AOC.pdf\"\u003eEMC AOC CE)\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"/documents/compliance/t_watch_s3_ce_emc_certificates/T-WATCH%20S3_C2401U21148E-RF%2003-C2%20RED%20210115---CE.pdf\"\u003eRED Certificate (CE)\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"/documents/compliance/t_watch_s3_ce_emc_certificates/T-WATCH%20S3_Shenzhen%20Xin%20Yuan%202ASYE-T-WATCH-S3%20FCC%20Grant.pdf\"\u003eFCC Grant\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch1 id=\"lilygo-t-embed\"\u003eLilygo T-Embed\u003c/h1\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"/documents/compliance/t_embed_ce_emc_certificates/CE_33377632-f4f3-44b9-b0ec-a1ef26f14121.pdf\"\u003eCE Certification\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"/documents/compliance/t_embed_ce_emc_certificates/FCC_e2ebae2b-61f8-400b-96d6-56b4c81ee28f.pdf\"\u003eFCC Certification\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n"},{"title":"About the project","date_published":"2026-07-30T00:00:00Z","date_modified":"2026-07-30T00:00:00Z","id":"https://handshaketec.com/about/","url":"https://handshaketec.com/about/","content_html":"\u003ch1 id=\"the-handshake-project\"\u003eThe HandShake Project\u003c/h1\u003e\n\u003cp\u003eHandShake helps non-speaking people who cannot use standard button or joystick controls but can still make an intentional hand or arm movement.\nThe system also works when worn on the knee and leg.\nThe smart-watch (the T-Watch S3) detects an intentional shake using its accelerometer and sends a wireless trigger to a receiver (the T-Embed), which activates assistive technology switches - for example to drive communication software like Grid, or to operate environmental controls. HandShake is an AAC (assisted augmented communication) switch.\nThe sensitivity, haptic feedback and audio confirmation are all adjustable to suit each user.\u003c/p\u003e\n\u003cp\u003eThe project has gone through several hardware iterations, from home-made devices then micro:bits through to the current Lilygo-based T-Watch/T-Embed system.\nIt\u0026rsquo;s been under long-term testing and use at a specialist college under the supervision of a Technologist and a Speech Therapist.\u003c/p\u003e\n\u003cp\u003eRead the full project write-up \u003ca href=\"https://mattoppenheim.com/projects/2018/02/handshake/\"\u003ehere\u003c/a\u003e.\u003c/p\u003e\n\u003ch1 id=\"about-me\"\u003eAbout me\u003c/h1\u003e\n\u003cp\u003eI re-purpose off the shelf hardware to create enabling technology. All of the project ideas come from the staff who work at specialist schools with the target end-users.\u003c/p\u003e\n\u003cp\u003eI used to spend about half of the year working at sea in marine geophysical survey or offshore construction but now mostly loiter at InfoLab21, Lancaster University.\u003c/p\u003e\n\u003cp\u003eMy GitHub page is \u003ca href=\"https://github.com/mattoppenheim\"\u003ehere\u003c/a\u003e.\u003c/p\u003e\n\u003cp\u003eI\u0026rsquo;ll be releasing the code for this project as open-source under the AGPL licence on Gitlab once it\u0026rsquo;s tested.\u003c/p\u003e\n"},{"title":"Device Flasher","date_published":"2026-07-06T00:00:00Z","date_modified":"2026-07-06T00:00:00Z","id":"https://handshaketec.com/flash_device/","url":"https://handshaketec.com/flash_device/","content_html":"\u003ch1 id=\"flash-firmware-to-your-device\"\u003eFlash Firmware to Your Device\u003c/h1\u003e\n\u003cp\u003eThis page allows you to flash custom firmware directly to your device using the Web Serial API.\u003c/p\u003e\n\u003cdiv style=\"background-color: #fff3cd; border: 1px solid #ffc107; border-radius: 4px; padding: 15px; margin-bottom: 20px; color: #856404; border-left: 4px solid #ff9800;\"\u003e\n  \u003cstrong\u003e⚠️ Disclaimer:\u003c/strong\u003e ** THIS SITE IS INCOMPLETE - FOR USE FOR TESTING BY THE DEVELOPERS ONLY **\nHandShake is free, open-source software provided as-is, with no warranty of any kind. It configures unmodified, manufacturer-certified hardware (Lilygo T-Watch S3 / T-Embed) that you purchase separately. Before relying on any device for communication access, test its switch response thoroughly with a professional (Occupational Therapist, Speech and Language Therapist, Technologist, experienced Carer). Report issues to \u003ca href=\"matt@mattoppenheim.com\"\u003ematt@mattoppenheim.com\u003c/a\u003e\n\u003c/div\u003e\n\n\n\u003cstyle\u003e\n  body { font-family: system-ui, sans-serif; }\n  .flasher-container {\n    max-width: 600px;\n    margin: 2rem auto;\n    padding: 1rem;\n    border: 1px solid #ccc;\n    border-radius: 8px;\n    background-color: #f9f9f9;\n  }\n  @media (prefers-color-scheme: dark) {\n    .flasher-container { background-color: #222; border-color: #555; }\n  }\n  .flasher-section {\n    margin: 1.5rem 0;\n    padding: 1rem;\n    border-left: 4px solid #2196F3;\n  }\n  .flasher-section h3 { margin-top: 0; color: #2196F3; }\n  .file-input-wrapper { margin: 1rem 0; }\n  .file-input-wrapper input[type=\"file\"] { padding: 0.5rem; cursor: pointer; }\n  .file-input-wrapper input[type=\"file\"]:disabled { opacity: 0.5; cursor: not-allowed; }\n  .status-message {\n    padding: 0.75rem;\n    margin: 1rem 0;\n    border-radius: 4px;\n    display: none;\n  }\n  .status-message.show { display: block; }\n  .status-message.success {\n    background-color: #e8f5e9;\n    color: #1b5e20;\n    border-left: 4px solid #4caf50;\n  }\n  .status-message.error {\n    background-color: #ffebee;\n    color: #b71c1c;\n    border-left: 4px solid #f44336;\n  }\n  .status-message.info {\n    background-color: #e3f2fd;\n    color: #0d47a1;\n    border-left: 4px solid #2196F3;\n  }\n  @media (prefers-color-scheme: dark) {\n    .status-message.success { background-color: #1b5e20; color: #c8e6c9; }\n    .status-message.error { background-color: #b71c1c; color: #ffcdd2; }\n    .status-message.info { background-color: #0d47a1; color: #b3e5fc; }\n  }\n  esp-web-install-button { width: 100%; }\n  esp-web-install-button.hidden { visibility: hidden; }\n  esp-web-install-button[install-unsupported] { visibility: inherit; }\n\u003c/style\u003e\n\n\u003cdiv class=\"flasher-container\"\u003e\n  \u003cdiv class=\"flasher-section\"\u003e\n    \u003ch3\u003e1. Select Firmware File\u003c/h3\u003e\n    \u003cp\u003eChoose the .bin firmware file you downloaded from Configuration.\u003c/p\u003e\n    \u003cdiv class=\"file-input-wrapper\"\u003e\n      \u003cinput type=\"file\" id=\"firmware-file\" accept=\".bin\" /\u003e\n    \u003c/div\u003e\n    \u003cdiv id=\"file-status\" class=\"status-message\"\u003e\u003c/div\u003e\n  \u003c/div\u003e\n\n  \u003cdiv class=\"flasher-section\"\u003e\n    \u003ch3\u003e2. Audio File Status\u003c/h3\u003e\n    \u003cp\u003eAudio file detection and upload status:\u003c/p\u003e\n    \u003cdiv id=\"audio-status\" class=\"status-message\"\u003e\u003c/div\u003e\n  \u003c/div\u003e\n\n  \u003cdiv class=\"flasher-section\"\u003e\n    \u003ch3\u003e3. Connect \u0026 Flash\u003c/h3\u003e\n    \u003cp\u003eConnect your device via USB and click the button below to flash firmware and audio.\u003c/p\u003e\n    \u003cesp-web-install-button class=\"hidden\"\u003e\u003c/esp-web-install-button\u003e\n    \u003cdiv id=\"flash-status\" class=\"status-message\"\u003e\u003c/div\u003e\n  \u003c/div\u003e\n\n  \u003cdiv class=\"flasher-section\" style=\"border-left-color: #666;\"\u003e\n    \u003ch3\u003eTroubleshooting\u003c/h3\u003e\n    \u003cul\u003e\n      \u003cli\u003e\u003cstrong\u003eDevice not detected?\u003c/strong\u003e Make sure your device is connected via USB and powered on.\u003c/li\u003e\n      \u003cli\u003e\u003cstrong\u003eFlash failed?\u003c/strong\u003e Try disconnecting and reconnecting your device.\u003c/li\u003e\n      \u003cli\u003e\u003cstrong\u003eIssues?\u003c/strong\u003e Check the browser console (F12) for error messages.\u003c/li\u003e\n    \u003c/ul\u003e\n  \u003c/div\u003e\n\u003c/div\u003e\n\u003c!-- ESP Web Tools, vendored at /js/vendor/esp-web-tools-10.4.0-hs1/ (v10.4.0,\n     Apache-2.0). install-button.js is only a loader and imports ~24 sibling\n     chunks by relative path, so the whole directory must be served intact.\n     The -hs1 suffix is a cache-busting revision: Caddy serves *.js as\n     immutable for a year, and these filenames come from upstream's build, so\n     any local edit needs a NEW directory name to reach browsers. Bump it and\n     update this src. Read that directory's README before changing it. --\u003e\n\u003cscript type=\"module\" src=\"/js/vendor/esp-web-tools-10.4.0-hs1/install-button.js\"\u003e\u003c/script\u003e\n\u003cscript type=\"module\"\u003e\nimport { validateFirmware } from './modules/file-validator.js';\nimport { uploadFirmware } from './modules/firmware-uploader.js';\n\nlet isUploading = false;\nlet csrfToken = null;\n\nconst fileInput = document.getElementById('firmware-file');\nconst button = document.querySelector('esp-web-install-button');\n\nif (!fileInput) {\n  console.error('firmware-file input element not found');\n} else {\n  // Disable file input until CSRF token is loaded\n  fileInput.disabled = true;\n}\n\nif (!button) {\n  console.error('esp-web-install-button not found');\n}\n\n// Fetch CSRF token first, before enabling interactions\nfetch('/csrf_token', { signal: AbortSignal.timeout(10000) })\n  .then(r =\u003e {\n    if (!r.ok) throw new Error(`HTTP ${r.status}`);\n    return r.json();\n  })\n  .then(data =\u003e {\n    csrfToken = data.csrf_token;\n    console.log('CSRF token loaded');\n    // Enable file input and attach event listeners only after token is available\n    attachEventListeners();\n  })\n  .catch(err =\u003e {\n    console.error('Failed to load CSRF token:', err);\n    // This page uses fetch() rather than HTMX, so the backend_status shortcode\n    // does not cover it. Distinguish a backend outage from a genuine token\n    // problem: Caddy answers 502/503/504 when granian is unreachable, and a\n    // TypeError means the request never completed at all. Reporting either as a\n    // \"security error\" sends the user looking in entirely the wrong place.\n    const statusEl = document.getElementById('file-status');\n    const backendDown = /HTTP 50[234]/.test(err.message) || err.name === 'TypeError';\n    statusEl.textContent = backendDown\n      ? '⚠️ The firmware service is unavailable, so flashing is disabled. This usually clears within a minute — please reload the page.'\n      : '❌ Security error: Could not load CSRF token. Please refresh the page.';\n    statusEl.className = 'status-message show error';\n  });\n\n// Attach event listeners only after CSRF token is loaded\nfunction attachEventListeners() {\n  if (fileInput) {\n    fileInput.disabled = false;\n    fileInput.addEventListener('change', handleFileChange);\n  }\n\n  if (button) {\n    button.addEventListener('error', (e) =\u003e {\n      console.error('Flash error:', e.detail);\n      document.getElementById('flash-status').textContent = '❌ Flash failed: ' + e.detail;\n      document.getElementById('flash-status').className = 'status-message show error';\n    });\n\n    button.addEventListener('done', (e) =\u003e {\n      console.log('Flash complete');\n      document.getElementById('flash-status').textContent = '✅ Firmware flashed successfully! Your device is restarting.';\n      document.getElementById('flash-status').className = 'status-message show success';\n    });\n  }\n}\n\nasync function handleFileChange(e) {\n  const file = e.target.files[0];\n  if (!file) return;\n\n  if (isUploading) {\n    document.getElementById('file-status').textContent = '❌ Upload in progress, please wait';\n    document.getElementById('file-status').className = 'status-message show error';\n    return;\n  }\n\n  console.log('File selected:', file.name, file.size);\n\n  const validation = validateFirmware(file);\n  if (!validation.valid) {\n    document.getElementById('file-status').textContent = '❌ ' + validation.error;\n    document.getElementById('file-status').className = 'status-message show error';\n    return;\n  }\n\n  console.log('File validation passed');\n\n  document.getElementById('file-status').textContent = '✅ Valid firmware file - uploading...';\n  document.getElementById('file-status').className = 'status-message show success';\n\n  isUploading = true;\n  fileInput.disabled = true;\n\n  try {\n    const uploadData = await uploadFirmware(file, csrfToken);\n\n    console.log('Manifest URL:', uploadData.manifest_url);\n\n    // Display audio file status for both devices\n    const audioInfo = uploadData.audio_flash_info;\n    const audioStatusDiv = document.getElementById('audio-status');\n    if (audioInfo) {\n      let audioMsg = '';\n      let audioClass = 'status-message show';\n\n      // Show per-device status\n      if (audioInfo.device_status) {\n        audioMsg += '\u003cstrong\u003eAudio Files by Device:\u003c/strong\u003e\u003cbr\u003e';\n        for (const [device, status] of Object.entries(audioInfo.device_status)) {\n          audioMsg += `• \u003cstrong\u003e${device}:\u003c/strong\u003e ${status}\u003cbr\u003e`;\n        }\n      }\n\n      // Show overall status if audio file will be flashed\n      if (audioInfo.found \u0026\u0026 audioInfo.status === 'success') {\n        audioMsg += `\u003cbr\u003e✅ Audio file will be flashed: \u003cstrong\u003e${audioInfo.filename}\u003c/strong\u003e (${(audioInfo.size / 1024).toFixed(1)} KB)`;\n        audioClass += ' success';\n        if (audioInfo.note) {\n          audioMsg += `\u003cbr\u003eℹ️ ${audioInfo.note}`;\n        }\n      } else if (audioInfo.status === 'failed') {\n        audioMsg += `\u003cbr\u003e❌ Audio file found but upload failed: ${audioInfo.filename}`;\n        audioClass += ' error';\n      } else if (audioInfo.status \u0026\u0026 audioInfo.status.includes('error')) {\n        audioMsg += `\u003cbr\u003e⚠️ ${audioInfo.status}`;\n        audioClass += ' error';\n      } else if (audioInfo.status) {\n        audioMsg += `\u003cbr\u003eℹ️ ${audioInfo.status}`;\n        if (!audioInfo.device_status) {\n          audioClass += ' info';\n        }\n      }\n\n      if (audioMsg) {\n        audioStatusDiv.innerHTML = audioMsg;\n        if (!audioInfo.device_status) {\n          audioStatusDiv.className = audioClass;\n        } else {\n          audioStatusDiv.className = 'status-message show info';\n        }\n      }\n    }\n\n    if (button) {\n      button.manifest = uploadData.manifest_url;\n      button.classList.remove('hidden');\n      document.getElementById('file-status').textContent = '✅ Valid firmware file - ready to flash';\n      console.log('Button shown with manifest URL:', uploadData.manifest_url);\n    } else {\n      throw new Error('esp-web-install-button not found');\n    }\n  } catch (err) {\n    console.error('Error:', err);\n    document.getElementById('file-status').textContent = '❌ An error occurred during upload. Please try again.';\n    document.getElementById('file-status').className = 'status-message show error';\n  } finally {\n    isUploading = false;\n    fileInput.disabled = false;\n    fileInput.value = '';\n  }\n}\n\u003c/script\u003e\n\n\n"},{"title":"Contact","date_published":"0001-01-01T00:00:00Z","date_modified":"0001-01-01T00:00:00Z","id":"https://handshaketec.com/contact/","url":"https://handshaketec.com/contact/","content_html":"\u003cp\u003eEmail me at \u003ca href=\"mailto:matt@mattoppenheim.com\"\u003ematt@mattoppenheim.com\u003c/a\u003e\u003c/p\u003e\n"},{"title":"Getting started with the Lilygo T-Watch S3","date_published":"0001-01-01T00:00:00Z","date_modified":"0001-01-01T00:00:00Z","id":"https://handshaketec.com/t_watch_s3_first_time_use/","url":"https://handshaketec.com/t_watch_s3_first_time_use/","content_html":"\u003ch1 id=\"using-the-lilygo-t-watch-s3-for-the-first-time\"\u003eUsing the Lilygo T-Watch S3 for the first time\u003c/h1\u003e\n\u003ch2 id=\"turning-on-the-battery\"\u003eTurning on the battery\u003c/h2\u003e\n\u003cp\u003eBefore you can turn on a new T-Watch S3 for the first time, you need to take the back off the watch, pull out the battery and \u003cstrong\u003eturn on the power switch\u003c/strong\u003e to connect the battery to the watch.\nThis caught me out!\nI thought I had a bad watch and all I had to do was turn it on inside!\nWithout the battery turned on, the watch still works when connected to a USB port using the mini-USB socket (see below). This is as the watch will draw power through the USB connection even with the battery disconnected.\u003c/p\u003e\n\u003cp\u003eThe on/off slide switch is under the battery at the base of the circuit board.\nAs with everything, it is straight forwards when you know how, but the switch is small and hidden, so I\u0026rsquo;ve put a few photos together to help.\u003c/p\u003e\n\u003cp\u003eThe photo below shows the corner of the back plate that has a little recess allowing you to remove it easily.\u003c/p\u003e\n\u003cfigure class=\"image center\"\u003e\n\u003cimg src=\"/images/t_watch_s3_removing_back.png\" loading=\"lazy\"\u003e\n\u003cfigcaption\u003e\n\u003cstrong\u003eThe corner recess used to remove the back cover\u003c/strong\u003e\u003cbr\u003e\u003c/figcaption\u003e\n\u003c/figure\u003e\n\n\u003cp\u003eInside the T-Watch S3 you can see the battery:\u003c/p\u003e\n\u003cfigure class=\"image center\"\u003e\n\u003cimg src=\"/images/t_watch_s3_back_removed.png\" loading=\"lazy\"\u003e\n\u003cfigcaption\u003e\n\u003cstrong\u003eBack plate removed from the T-Watch S3, showing the battery\u003c/strong\u003e\u003cbr\u003e\u003c/figcaption\u003e\n\u003c/figure\u003e\n\n\u003cp\u003eThe battery needs to be pulled out.\nIt stays attached to the circuit board by two wires.\nThe battery is not removable, which is a bonus as it can\u0026rsquo;t be dropped then swallowed by your dog or a toddler or an unsupervised academic.\u003c/p\u003e\n\u003cp\u003eThe orange square in the photo below shows where the battery on/off switch is.\nThe watch ships with this in the off position.\nYou need to move the switch to the on position then replace the battery and the back plate.\u003c/p\u003e\n\u003cfigure class=\"image center\"\u003e\n\u003cimg src=\"/images/t_watch_s3_battery_switch_off.png\" loading=\"lazy\"\u003e\n\u003cfigcaption\u003e\n\u003cstrong\u003eBattery switch in the off position\u003c/strong\u003e\u003cbr\u003e\u003c/figcaption\u003e\n\u003c/figure\u003e\n\n\u003cfigure class=\"image center\"\u003e\n\u003cimg src=\"/images/t_watch_s3_battery_switch_on.png\" loading=\"lazy\"\u003e\n\u003cfigcaption\u003e\n\u003cstrong\u003eBattery switch in the on position\u003c/strong\u003e\u003cbr\u003e\u003c/figcaption\u003e\n\u003c/figure\u003e\n\n\u003ch2 id=\"usb-socket-and-power-switch-postion\"\u003eUSB Socket and power switch postion\u003c/h2\u003e\n\u003cp\u003eThe image below shows the side of the watch.\nThis photo shows the silver mini-USB socket cover and the on/off button.\u003c/p\u003e\n\u003cfigure class=\"image center\"\u003e\n\u003cimg src=\"/images/t_watch_usb_cover_on.png\" loading=\"lazy\"\u003e\n\u003cfigcaption\u003e\n\u003cstrong\u003eSide of T-Watch S3, showing the USB socket cover and on/off button\u003c/strong\u003e\u003cbr\u003e\u003c/figcaption\u003e\n\u003c/figure\u003e\n\n\u003cp\u003eThe silver cover on the side of the watch is on top of a mini-USB socket.\u003c/p\u003e\n\u003cp\u003eThe T-Watch S3 uses a mini-USB socket, not the latest USB-C sockets.\nThis socket has two uses:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eCharging using a powered USB socket or a 5V USB charger.\u003c/li\u003e\n\u003cli\u003eLoading new code onto the watch.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eThe cover over the mini USB socket is a little tricky to get off the first time. There is a little recess on the bottom of the cover.\nPull it open with a fingernail.\nThe cover will not drop off the watch when it is opened as it is attached to the watch by a hinge.\u003c/p\u003e\n\u003ch2 id=\"turning-on-the-watch\"\u003eTurning on the watch\u003c/h2\u003e\n\u003cp\u003eTo turn the watch on or off: press the button for about 3 seconds.\nIf you hold down the button too long, the watch turns off again as soon as it turns on.\nWhich is a little confusing!\u003c/p\u003e\n\u003cp\u003eThis button on the watch rotates but the rotation has no functionality.\u003c/p\u003e\n\u003ch2 id=\"connecting-the-usb-cable-to-charge-and-program-the-t-watch-s3\"\u003eConnecting the USB cable to charge and program the T-Watch S3\u003c/h2\u003e\n\u003cp\u003eThe watch ships with a short mini-USB cable which works for both charging and programming the watch.\nUse the cable to connect  your T-Watch S3 to your laptop or PC.\nI\u0026rsquo;ve not managed to program the watch from an Android phone, I\u0026rsquo;ve always had to use a laptop or PC.\nI\u0026rsquo;ve tested programming the T-Watch S3 with Windows and Linux and had reports that it works well with the Mac, but I don\u0026rsquo;t have a Mac to verify this.\u003c/p\u003e\n\u003cfigure class=\"image center\"\u003e\n\u003cimg src=\"/images/t_watch_s3_cable_connected.png\" loading=\"lazy\"\u003e\n\u003cfigcaption\u003e\n\u003cstrong\u003eUSB cable connected to the watch\u003c/strong\u003e\u003cbr\u003e\u003c/figcaption\u003e\n\u003c/figure\u003e\n\n"},{"title":"Privacy notice","date_published":"0001-01-01T00:00:00Z","date_modified":"0001-01-01T00:00:00Z","id":"https://handshaketec.com/privacy/","url":"https://handshaketec.com/privacy/","content_html":"\u003cp\u003eThis page explains what information handshaketec.com keeps about you, why it\nkeeps it and what you can do about it.\u003c/p\u003e\n\u003ch2 id=\"who-is-responsible\"\u003eWho is responsible\u003c/h2\u003e\n\u003cp\u003eThis site is run by Matt Oppenheim, who is solely responsible for it and for\nthe data described here — not Lancaster University. For anything about\nprivacy, contact \u003ca href=\"mailto:matt@mattoppenheim.com\"\u003ematt@mattoppenheim.com\u003c/a\u003e,\nthe same address given on the \u003ca href=\"/contact/\"\u003econtact page\u003c/a\u003e.\u003c/p\u003e\n\u003ch2 id=\"what-this-site-does-not-do\"\u003eWhat this site does not do\u003c/h2\u003e\n\u003cp\u003eThere is no advertising, no analytics and no tracking on this site. We do not\nsell or share your information, and we do not build profiles of visitors.\nApart from YouTube videos (see below), every page loads only from\nhandshaketec.com and comments.handshaketec.com.\u003c/p\u003e\n\u003ch2 id=\"comments\"\u003eComments\u003c/h2\u003e\n\u003cp\u003eThe comment box on every page is run by \u003ca href=\"https://remark42.com\"\u003eRemark42\u003c/a\u003e, an\nopen-source comment system that runs on our own server.\u003c/p\u003e\n\u003cp\u003eWhen you comment, we keep:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003eThe name you use.\u003c/strong\u003e If you comment anonymously, this is the name you type in. If you sign in with GitHub, it is your GitHub display name. We also keep a copy of your GitHub profile picture and an identifier for your GitHub account.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eYour comment and when you posted it.\u003c/strong\u003e\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eA scrambled form of your internet (IP) address.\u003c/strong\u003e We never store the address itself, only a one-way code made from it, which cannot be turned back into your address. It lets us block someone who is abusing the comments without keeping their address.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eWe do not ask for your email address.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eComments are public.\u003c/strong\u003e Anyone who visits the page can read them. Please don\u0026rsquo;t post anything that identifies a HandShake user or someone who took part in our research, unless it is you and you are happy for everyone to read it.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eSigning in with GitHub.\u003c/strong\u003e If you choose to sign in with GitHub, GitHub knows\nthat you signed in to this site. What GitHub does with that is covered by\n\u003ca href=\"https://docs.github.com/en/site-policy/privacy-policies/github-general-privacy-statement\"\u003eGitHub\u0026rsquo;s privacy statement\u003c/a\u003e.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eEmail notifications.\u003c/strong\u003e When someone posts a comment, the site emails a copy\nof it, with the commenter\u0026rsquo;s name, to the site owner so it can be read and\nmoderated. These emails are sent and stored through Google\u0026rsquo;s Gmail service.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eCookies.\u003c/strong\u003e Reading comments sets no cookies. When you sign in to comment, comments.handshaketec.com sets two: one keeps you signed in and the other protects your account from forged requests. They last about eight days, or until you sign out, which removes them straight away.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eHow long we keep it.\u003c/strong\u003e Comments stay until they are deleted. The comment system makes a backup copy once a day and keeps the last 10, so a deleted comment is gone from the backups after about ten days.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eDeleting your comments.\u003c/strong\u003e You can delete a comment for a few minutes after posting it. After that, \u003ca href=\"/contact/\"\u003econtact us\u003c/a\u003e and we will remove your comments and the information above.\u003c/p\u003e\n\u003ch2 id=\"the-configuration-and-flashing-pages\"\u003eThe configuration and flashing pages\u003c/h2\u003e\n\u003cp\u003eThe T-Watch S3 and T-Embed configuration pages build firmware to your\nsettings. To keep your settings separate from other visitors\u0026rsquo;, the site sets\none cookie, called \u003ccode\u003esession\u003c/code\u003e, when you open one of these pages. It holds a\nrandom name for your session and a security code that stops other sites\nchanging your settings, and nothing else. It is deleted when you close\nyour browser.\u003c/p\u003e\n\u003cp\u003eWhile your session is active, the server keeps:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003ethe settings you choose, such as gestures, sensitivity and keys\u003c/li\u003e\n\u003cli\u003ethe MAC addresses you enter, which identify your watch and receiver to each other\u003c/li\u003e\n\u003cli\u003ethe firmware built from those settings\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eA session ends after 2 hours without use. Its settings and firmware are then\ndeleted from the server within a few minutes.\u003c/p\u003e\n\u003cp\u003eIf you upload a firmware file on the \u003ca href=\"/flash_device/\"\u003eDevice Flasher\u003c/a\u003e page,\nit is deleted from the server after one hour. Flashing itself happens between\nyour browser and your device: nothing read from the device is sent to us.\u003c/p\u003e\n\u003ch2 id=\"server-logs\"\u003eServer logs\u003c/h2\u003e\n\u003cp\u003eLike most websites, the server records each request it receives: your IP\naddress, the date and time, the page or file asked for and the type of browser\nyou use. We use these records only to keep the site working and secure, for\nexample to find faults or block attacks. There is no fixed period for keeping\nthem: older records are overwritten as the logs fill up, and the logs are\ncleared whenever the site is updated. How long that takes depends on how busy\nthe site is and how often it changes, so we can only estimate it, at a few\nweeks at most.\u003c/p\u003e\n\u003ch2 id=\"videos\"\u003eVideos\u003c/h2\u003e\n\u003cp\u003ePages with a video use YouTube\u0026rsquo;s privacy-enhanced player, which does not set\ncookies until you press play. Playing a video is then subject to\n\u003ca href=\"https://policies.google.com/privacy\"\u003eGoogle\u0026rsquo;s privacy policy\u003c/a\u003e, not ours.\u003c/p\u003e\n\u003ch2 id=\"why-we-are-allowed-to-use-this-information\"\u003eWhy we are allowed to use this information\u003c/h2\u003e\n\u003cp\u003eUK data protection law requires a lawful basis for using personal data. Ours\nis \u003cstrong\u003elegitimate interests\u003c/strong\u003e: running a public discussion about HandShake,\nbuilding the firmware you ask for and keeping the site secure. We keep only\nwhat those need and for no longer than the times given above.\u003c/p\u003e\n\u003cp\u003eThe \u003ccode\u003esession\u003c/code\u003e cookie and the comment sign-in cookies are strictly necessary for\nthe features you choose to use, so the site does not ask for consent before\nsetting them.\u003c/p\u003e\n\u003ch2 id=\"where-the-data-is-kept\"\u003eWhere the data is kept\u003c/h2\u003e\n\u003cp\u003eThe server is hosted in the United Kingdom by IONOS SE. Comment notification\nemails are held by Google, which may store them outside the UK under its own\nsafeguards for international transfers.\u003c/p\u003e\n\u003ch2 id=\"your-rights\"\u003eYour rights\u003c/h2\u003e\n\u003cp\u003eUnder UK data protection law you have the right to:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eask what personal data is held about you and get a copy of it\u003c/li\u003e\n\u003cli\u003ehave inaccurate data corrected\u003c/li\u003e\n\u003cli\u003eask for your data to be deleted\u003c/li\u003e\n\u003cli\u003eobject to how your data is used, or ask for its use to be restricted\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eTo use any of these rights, \u003ca href=\"/contact/\"\u003econtact us\u003c/a\u003e. We will reply within one\nmonth. If you\u0026rsquo;re not satisfied with the response, you can complain to the UK\u0026rsquo;s\ndata protection regulator, the\n\u003ca href=\"https://ico.org.uk/make-a-complaint/\"\u003eInformation Commissioner\u0026rsquo;s Office\u003c/a\u003e.\u003c/p\u003e\n\u003ch2 id=\"changes-to-this-notice\"\u003eChanges to this notice\u003c/h2\u003e\n\u003cp\u003eIf what the site collects changes, this page will be updated first. The date\nunder the title shows when the page was last rebuilt.\u003c/p\u003e\n"},{"title":"Watch Configuration Help","date_published":"0001-01-01T00:00:00Z","date_modified":"0001-01-01T00:00:00Z","id":"https://handshaketec.com/watch_configuration_help/","url":"https://handshaketec.com/watch_configuration_help/","content_html":"\u003cp\u003eThe \u003ca href=\"https://handshaketec.com/watch_configuration/\"\u003e\u003cstrong\u003eWatch Configuration\u003c/strong\u003e page\u003c/a\u003e lets you choose what your T-Watch S3 does, then builds a\ncustom firmware file (the program that runs on the watch) for you to download and\nput onto the device.\nThe options that you can set include:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eWhat gesture is recognised by the watch.\u003c/li\u003e\n\u003cli\u003eHow sensitive the watch is to movement.\u003c/li\u003e\n\u003cli\u003eWhich receiver module the watch communicates with.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eYou don\u0026rsquo;t need any programming knowledge — just work through\nthe page from top to bottom.\u003c/p\u003e\n\u003cp\u003eHow it works:\nEach time you select an option or enter a value, this is written to a configuration file.\nWhen you create the firmware at the bottom of the page, this configuration file is read and used to create the firmware with.\nSo, you can go back and change your selections as many times as you want.\nThe configuration file is only read when you click on the \u0026lsquo;Generate firmware\u0026rsquo; button at the bottom of the page.\u003c/p\u003e\n\u003cp\u003eIf the option you\u0026rsquo;re setting has a \u0026lsquo;submit value\u0026rsquo; button, then the value you select is not written to the configuration file until you click the submit button.\u003c/p\u003e\n\u003ch1 id=\"using-the-t-watch-s3-configuration-website\"\u003eUsing the T-Watch-S3 configuration website\u003c/h1\u003e\n\u003cp\u003eUsing this website you will configure and download a file for your watch which will be called e.g.\u003c/p\u003e\n\u003cp\u003ezeuss-twatch-s3-firmware.bin\u003c/p\u003e\n\u003cp\u003eYou are free to rename the file after you download it.\u003c/p\u003e\n\u003cp\u003eThe Greek god\u0026rsquo;s name at the start of the filename will probably be different from the example and will change each time that you restart the configuration page. See below for details of why this is done.\u003c/p\u003e\n\u003ch2 id=\"step-1--check-your-session-name\"\u003eStep 1 — Check your session name\u003c/h2\u003e\n\u003cp\u003eAt the top of the page you\u0026rsquo;ll see your \u003cstrong\u003esession name\u003c/strong\u003e.\nThis is a name of a Greek god.\u003c/p\u003e\n\u003cp\u003eEach time you re-open the configuration page, you will be assigned a new session name.\nThis is done so that the firmware you build doesn\u0026rsquo;t get mixed up with anyone else\u0026rsquo;s.\nThe names are taken randomly from a pool of Greek god names.\nWhen you next use the site you will probably get a different session name.\nThis is done so that:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eIf several people are using the site at the same time, everybody gets a different session name.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eThe firmware filename produced by the website contains the session name.\u003c/p\u003e\n\u003cp\u003ee.g. zeuss-twatch-s3-firmware.bin\u003c/p\u003e\n\u003cp\u003eThis way, you get the software for your device and not for anybody elses device, even if several people are using the site at the same time.\nYou\u0026rsquo;ll get the same names popping up if you use the site enough as there\u0026rsquo;re only so many Greek gods!\nThe main reason for doing this is to enable several different users to access the site at the same time without interfering with each other.\nI could\u0026rsquo;ve used something other than Greek god names but it seemed as good a method as any.\u003c/p\u003e\n\u003cp\u003eI did add the functionality for you to assign your own session name but realised that this could be named with the name of whoever uses the system, which is a leak of personal information, so removed it.\u003c/p\u003e\n\u003cp\u003eYou\u0026rsquo;re free to rename the firmware that you create after you\u0026rsquo;ve downloaded it to your own device.\nRenaming the file won\u0026rsquo;t change how it works, so long as you keep \u0026lsquo;.bin\u0026rsquo; at the end of the filename.\nThe \u0026lsquo;.bin\u0026rsquo; ending is required so that the software that then takes your firmware and loads it onto the watch recognises the file as a firmware file.\u003c/p\u003e\n\u003cp\u003eIf the session name is \u0026lsquo;undefined\u0026rsquo; then there is a problem with the website!\nTry again in 10 minutes as this could mean that I\u0026rsquo;m uploading and testing a new version of the website and have cocked something up.\nI do test the website offline before loading up a new version but sometime things work offline but not on the Real World deployment site you\u0026rsquo;re using.\nOtherwise, let me know and I\u0026rsquo;ll get it fixed.\u003c/p\u003e\n\u003ch2 id=\"step-2--choose-a-gesture\"\u003eStep 2 — Choose a gesture\u003c/h2\u003e\n\u003cp\u003eUnder \u003cstrong\u003eGesture Selection\u003c/strong\u003e, pick the movement the watch should respond to — a \u003cstrong\u003epunch\u003c/strong\u003e\nor a \u003cstrong\u003eroll\u003c/strong\u003e of the wrist. If you\u0026rsquo;re just starting out trying HandShake, choose the punch gesture. This one is the most generic gesture detection algorithm.\nChoose one, then click \u003cstrong\u003esubmit_gesture\u003c/strong\u003e.\nThe two gesture algorithms are described below.\nI intend to make some videos showing the two gestures and to put some links here.\u003c/p\u003e\n\u003ch3 id=\"punch-gesture\"\u003ePunch gesture\u003c/h3\u003e\n\u003cp\u003eThis is a simple algorithm that looks for a shake in any direction that exceeds the sensitivity value.\nThe sensitivity for triggering the algorithm is set below.\u003c/p\u003e\n\u003cp\u003eA short video showing the type of motion that the punch gesture algorithm is designed to detect can be seen at:\u003c/p\u003e\n\u003cdiv style=\"position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;\"\u003e\n      \u003ciframe allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen\" loading=\"eager\" referrerpolicy=\"strict-origin-when-cross-origin\" src=\"https://www.youtube-nocookie.com/embed/6gIWDpBXn14?autoplay=0\u0026amp;controls=1\u0026amp;end=0\u0026amp;loop=0\u0026amp;mute=0\u0026amp;start=0\" style=\"position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;\" title=\"YouTube video\"\u003e\u003c/iframe\u003e\n    \u003c/div\u003e\n\n\u003ch3 id=\"roll-gesture\"\u003eRoll gesture\u003c/h3\u003e\n\u003cp\u003eThis gesture was written for a participant who had a slow wrist rolling gesture.\nThe roll algorithm differentiates the acceleration data from a single accelerometer axis, triggering gesture detection when the differentiated value exceeds an adjustable threshold.\u003c/p\u003e\n\u003cp\u003eA short video showing the type of motion that the roll gesture algorithm is designed to detect can be seen at:\u003c/p\u003e\n\u003cdiv style=\"position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;\"\u003e\n      \u003ciframe allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen\" loading=\"eager\" referrerpolicy=\"strict-origin-when-cross-origin\" src=\"https://www.youtube-nocookie.com/embed/OOAX4EiMQqg?autoplay=0\u0026amp;controls=1\u0026amp;end=0\u0026amp;loop=0\u0026amp;mute=0\u0026amp;start=0\" style=\"position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;\" title=\"YouTube video\"\u003e\u003c/iframe\u003e\n    \u003c/div\u003e\n\n\u003ch2 id=\"step-3--set-the-gesture-sensitivity\"\u003eStep 3 — Set the gesture sensitivity\u003c/h2\u003e\n\u003cp\u003eThe \u003cstrong\u003eGesture Sensitivity\u003c/strong\u003e slider controls how strong a movement has to be before the\nwatch registers it.\nThe value entered here is just a starting point as we can adjust the sensitivity later using the receiver:\u003c/p\u003e\n\u003cp\u003eThe sensitivity can be adjusted using the jog wheel on the receiver unit and then sent to the watch using a long press on the button in the middle of this wheel.\u003c/p\u003e\n\u003cp\u003eThe lower the value that is selected, the lower the amount of shake that is required to create a trigger. This means that the watch is more sensitive to faint shakes.\nThis is as the number represents the value of acceleration that has to be exceeded for a trigger to be sent.\nIf you don\u0026rsquo;t know what value to try, just start with the default and click \u003cstrong\u003esubmit_sensitivity\u003c/strong\u003e.\u003c/p\u003e\n\u003ch2 id=\"step-4--set-the-minimum-shake-interval-optional\"\u003eStep 4 — Set the minimum shake interval (optional)\u003c/h2\u003e\n\u003cp\u003eThe \u003cstrong\u003eMinimum Intershake Interval\u003c/strong\u003e slider controls the minimum time (in milliseconds)\nthat must pass between separate shake detections. This prevents the watch from registering\nthe same shake as multiple separate events. Start with the default of 300 ms and click\n\u003cstrong\u003esubmit_shake_interval\u003c/strong\u003e. If you find the watch is registering unintended multiple\ndetections, increase this value. If you find it\u0026rsquo;s too slow to respond to rapid successive\nmovements, decrease it.\u003c/p\u003e\n\u003ch2 id=\"step-5--enter-the-t-embeds-mac-address\"\u003eStep 5 — Enter the T-Embed\u0026rsquo;s MAC address\u003c/h2\u003e\n\u003cp\u003eUnder \u003cstrong\u003eEnter MAC Address for the T-Embed\u003c/strong\u003e, type in the MAC address of the T-Embed\nreceiver that this watch should send its signals to. This is what pairs the two devices\ntogether, so that your watch talks to your own receiver and not anybody else\u0026rsquo;s.\u003c/p\u003e\n\u003cp\u003eYou can type the address either with colons (e.g. \u003ccode\u003eAA:BB:CC:DD:EE:FF\u003c/code\u003e) or without them\n(e.g. \u003ccode\u003eAABBCCDDEEFF\u003c/code\u003e) — either format is accepted. Then click \u003cstrong\u003eSubmit MAC\u003c/strong\u003e.\u003c/p\u003e\n\u003cp\u003eIf you don\u0026rsquo;t already know your receiver\u0026rsquo;s MAC address, see the\n\u003ca href=\"https://handshaketec.com/mac_identification/\"\u003eFinding the MAC address of your module\u003c/a\u003e\npage for step-by-step instructions on reading it off the device. Make sure you read the\naddress \u003cstrong\u003efrom the T-Embed\u003c/strong\u003e, not the watch — the watch is given the receiver\u0026rsquo;s address,\nand the receiver is given the watch\u0026rsquo;s address, so it\u0026rsquo;s easy to get them the wrong way\nround.\u003c/p\u003e\n\u003cp\u003eIf the address is wrong, the watch tells you: it shows \u003cstrong\u003e*** NO LINK ***\u003c/strong\u003e after each\ngesture. See \u003ca href=\"/watch_configuration_help/#if-the-watch-shows-no-link\"\u003eIf the watch shows NO LINK\u003c/a\u003e below.\u003c/p\u003e\n\u003ch2 id=\"step-6--generate-the-firmware\"\u003eStep 6 — Generate the firmware\u003c/h2\u003e\n\u003cp\u003eEach time you change one of the controls above, make sure you\u0026rsquo;ve clicked its \u003cstrong\u003esubmit\u003c/strong\u003e\nbutton — this saves your choice in the background.\u003c/p\u003e\n\u003cp\u003eWhen you\u0026rsquo;re happy with all your selections, click \u003cstrong\u003eGenerate Firmware\u003c/strong\u003e. The site reads\nyour choices and builds the firmware file. This usually takes a minute or two, and you\u0026rsquo;ll\nsee progress messages while it works, so please be patient.\u003c/p\u003e\n\u003cp\u003eThe build carries on if you leave the page. Come back to this page and you\u0026rsquo;ll see its\nprogress, or its result if it has finished.\u003c/p\u003e\n\u003ch2 id=\"step-7--download-the-firmware\"\u003eStep 7 — Download the firmware\u003c/h2\u003e\n\u003cp\u003eWhile the build runs, the download button shows \u003cem\u003eBuilding\u0026hellip;\u003c/em\u003e. Once the build finishes, a\n\u003cstrong\u003edownload\u003c/strong\u003e option appears. Click it to save the firmware file to your computer.\u003c/p\u003e\n\u003cp\u003eOnce it\u0026rsquo;s downloaded, the firmware is yours forever! You own it. No time limits, no\nsubscriptions. All yours.\u003c/p\u003e\n\u003ch2 id=\"putting-the-firmware-onto-your-watch\"\u003ePutting the firmware onto your watch\u003c/h2\u003e\n\u003cp\u003eTo put this firmware onto your T-Watch S3, see the\n\u003ca href=\"https://handshaketec.com/flash_device_help/\"\u003eDevice Flasher help page\u003c/a\u003e for step-by-step\ninstructions.\u003c/p\u003e\n\u003cp\u003eI turned off the touch interface on the watch to prevent accidental touches changing things.\u003c/p\u003e\n\u003ch2 id=\"what-the-watch-does-when-it-detects-a-gesture\"\u003eWhat the watch does when it detects a gesture\u003c/h2\u003e\n\u003cp\u003eWhen the watch detects your gesture, it sends a signal to the T-Embed straight away,\nthen gives a short buzz and shows \u003cstrong\u003e*** SHAKE ***\u003c/strong\u003e on its screen. The T-Embed\nthen sends its keypress to your communication device. From the watch detecting the\ngesture to the keypress arriving takes about 30 milliseconds — too quick to notice.\u003c/p\u003e\n\u003cp\u003eThe watch also checks that the T-Embed received each signal. If it did, nothing more\nhappens. If it did not, the watch warns you, as described below.\u003c/p\u003e\n\u003ch2 id=\"if-the-watch-shows-no-link\"\u003eIf the watch shows NO LINK\u003c/h2\u003e\n\u003cp\u003eIf the T-Embed did not receive the signal, the buzz is cut short by \u003cstrong\u003ethree quick\nclicks\u003c/strong\u003e, and the watch shows \u003cstrong\u003e*** NO LINK ***\u003c/strong\u003e for two seconds. No keypress\nwas sent for that gesture.\u003c/p\u003e\n\u003cp\u003eCheck these, in this order:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003eIs the T-Embed switched on and plugged in?\u003c/strong\u003e The watch can only reach a T-Embed that\nis powered.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eIs the watch close enough?\u003c/strong\u003e The watch uses a low-power radio to save its battery.\nIn testing it has worked at over 10 metres. If you see NO LINK at a distance, try\nmoving the watch closer to the T-Embed.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eIs the T-Embed\u0026rsquo;s MAC address right?\u003c/strong\u003e If the watch shows NO LINK every time, even\nwith the T-Embed on and close by, the address entered in\n\u003ca href=\"/watch_configuration_help/#step-5--enter-the-t-embeds-mac-address\"\u003eStep 5\u003c/a\u003e is probably wrong. Check it against\nthe T-Embed, then generate and flash the firmware again.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eWatches with firmware built before 6 October 2026 do not have this check, so they give\nno warning. Generate and flash new firmware to get it.\u003c/p\u003e\n\u003ch2 id=\"watch-keeps-restarting-every-few-seconds\"\u003eWatch keeps restarting every few seconds\u003c/h2\u003e\n\u003cp\u003eIf the watch lights up, shows the display for a few seconds, restarts, and then keeps\nrepeating that same cycle, it is almost always the firmware crashing rather than a flat\nbattery. A battery that is genuinely too low tends to cut out at irregular moments, or\nnot turn the watch on at all, rather than restarting in a steady rhythm.\u003c/p\u003e\n\u003cp\u003eCharge the watch for 20 minutes or so to rule power out. If it still restarts on the\nsame few-second cycle once it has charge in it, reflash the firmware using the steps\nabove.\u003c/p\u003e\n\u003cp\u003eIf a fresh flash of the current firmware does not fix it, please get in touch and let me\nknow. A repeating cycle like that is a fault in the firmware rather than anything you\nhave done wrong, and it helps me to know you have hit it.\u003c/p\u003e\n\u003ch2 id=\"resetting-the-watch-if-it-becomes-unresponsive\"\u003eResetting the watch if it becomes unresponsive\u003c/h2\u003e\n\u003cp\u003eIt is possible to write code that stops the watch from programming!\nIn this case we need to turn the watch off, then turn it on while pressing a small reset button inside of the watch.\nI had to do this a few times when developing the firmware for the watch.\nPress the little reset button inside of the case, press the power button on the side of the watch for \u003cstrong\u003eone second only\u003c/strong\u003e release the power button, release the little button. You will feel the reset button \u0026lsquo;click\u0026rsquo; in when you press it.\nOnce the watch has restarted with the button held down, it will program again.\u003c/p\u003e\n\u003cp\u003eTo access this button, you need to take the back off the watch and remove the battery in the same way that you did when first using the watch to turn on the switch that connects the battery to the watch.\nNote the position of the reset button shown by the orange box. I use the end of a pencil to push this down. It\u0026rsquo;s small and fiddly!\u003c/p\u003e\n\u003cfigure class=\"image center\"\u003e\n\u003cimg src=\"/images/t_watch_s3_reset_button.png\" loading=\"lazy\"\u003e\n\u003cfigcaption\u003e\n\u003cstrong\u003eThe reset button is inside the orange box\u003c/strong\u003e\u003cbr\u003e\u003c/figcaption\u003e\n\u003c/figure\u003e\n\n"}]}