<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[dans portfolio]]></title><description><![CDATA[Thoughts, projects and ideas.]]></description><link>https://dansportfol.io/</link><image><url>https://dansportfol.io/favicon.png</url><title>dans portfolio</title><link>https://dansportfol.io/</link></image><generator>Ghost 5.51</generator><lastBuildDate>Wed, 29 Apr 2026 05:11:01 GMT</lastBuildDate><atom:link href="https://dansportfol.io/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Color Correct]]></title><description><![CDATA[<p>A &quot;hello world&quot; to machine learning, and solving a real-world inefficiency? Sign me up! This article will share some of the highlights and &quot;Ah-ha!&quot; moments during the build of my latest React app - Color Correct.</p><p></p><!--kg-card-begin: markdown--><h1 id="some-context">Some Context...</h1>
<!--kg-card-end: markdown--><p>Print has always had fairly thin profit margins,</p>]]></description><link>https://dansportfol.io/color-correct/</link><guid isPermaLink="false">6602deb79d8ea0053639bb61</guid><dc:creator><![CDATA[Dan Halleck]]></dc:creator><pubDate>Thu, 02 May 2024 01:00:58 GMT</pubDate><media:content url="https://dansportfol.io/content/images/2024/05/Frontend-test-charts.png" medium="image"/><content:encoded><![CDATA[<img src="https://dansportfol.io/content/images/2024/05/Frontend-test-charts.png" alt="Color Correct"><p>A &quot;hello world&quot; to machine learning, and solving a real-world inefficiency? Sign me up! This article will share some of the highlights and &quot;Ah-ha!&quot; moments during the build of my latest React app - Color Correct.</p><p></p><!--kg-card-begin: markdown--><h1 id="some-context">Some Context...</h1>
<!--kg-card-end: markdown--><p>Print has always had fairly thin profit margins, and as a result, innovation and lean manufacturing is the name of the game. This can typically be observed at all levels of a print business. I have spent a fair amount of time on the front lines of production in a few print/packaging environments and have found my share of ways to improve efficiency.<br><br>The goal of this project is to reduce paper waste, ink waste, and labor hours on a modern printing press. A quick outline of some important print industry terms before moving forward:<br></p><!--kg-card-begin: markdown--><ul>
<li>Make-ready - This can refer to the process of setting a press up to run a job, or more specifically, in this case, the sacrificial sheets before &quot;good&quot; sheets start coming out of the machine.</li>
<li>Print Unit/Print Tower - One &quot;section&quot; of the press responsible for transferring a single color to the sheet.</li>
<li>Ink Fountain - Each Print Unit&apos;s ink resevoir, ink is transferred from here directly to the roller-train</li>
<li>Ink Key - There are a series of &quot;flaps&quot; that meter the amount of ink let through to the roller train. These allow the fine-tuning of ink density per region</li>
</ul>
<!--kg-card-end: markdown--><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://dansportfol.io/content/images/2024/05/Press-2.jpg" class="kg-image" alt="Color Correct" loading="lazy" width="2000" height="1500" srcset="https://dansportfol.io/content/images/size/w600/2024/05/Press-2.jpg 600w, https://dansportfol.io/content/images/size/w1000/2024/05/Press-2.jpg 1000w, https://dansportfol.io/content/images/size/w1600/2024/05/Press-2.jpg 1600w, https://dansportfol.io/content/images/size/w2400/2024/05/Press-2.jpg 2400w" sizes="(min-width: 720px) 720px"><figcaption>8 color press - K, C, M, Y (perfector flips the sheet), then K, C, M, Y again</figcaption></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://dansportfol.io/content/images/2024/05/print-unit-keys-1.png" class="kg-image" alt="Color Correct" loading="lazy" width="2000" height="1500" srcset="https://dansportfol.io/content/images/size/w600/2024/05/print-unit-keys-1.png 600w, https://dansportfol.io/content/images/size/w1000/2024/05/print-unit-keys-1.png 1000w, https://dansportfol.io/content/images/size/w1600/2024/05/print-unit-keys-1.png 1600w, https://dansportfol.io/content/images/size/w2400/2024/05/print-unit-keys-1.png 2400w" sizes="(min-width: 720px) 720px"><figcaption>Print unit 4 contains ink keys 102-136</figcaption></figure><!--kg-card-begin: markdown--><h1 id="the-goal">The goal</h1>
<!--kg-card-end: markdown--><p>In lithographic printing, there is a &quot;prepress&quot; department that is responsible for job layouts, creating print plates. Along with the print plates, prepress will generate a file to send to the press that tells the press how much ink to lay down in each area based on how heavy the coverage is in the image. This file is seen as a starting point for the press, and the pressman will adjust from here, and do fine-tuning on the fly. In a lot of cases, these files are not spot-on, due to mechanical changes on the press, ink/water balance, using different chemistry, impression, any one of a seemingly endless list of variables. My goal is to adjust the files from prepress to make for shorter make-readies and more accurate color densities early in each print run. This should reduce paper waste, ink used, and time spent on make-readies.<br></p><!--kg-card-begin: markdown--><h1 id="the-approach">The Approach</h1>
<!--kg-card-end: markdown--><p>The files prepress generates start as something with a proprietary encoding, however, the final file sent to the press is human-readable. And a nearly identical format is exported from the press when saving a color profile... We can use these exports to compare against the originals and treat this is a polynomial regression problem. I have not done a whole lot with neural nets or machine learning in general, and this seems like a great problem to solve with a neural net. I&apos;ll extract the ink key data, convert to tensors, and use TensorFlow to establish the relationship between the two - find the &quot;curve&quot; so to speak.</p><p>Each row of the prepress files corresponds to one print unit on the press. Each value/column in the row, corresponds to one of the 34 ink keys across each unit. This will make rank-2 tensors that are easy to work with. I know a few things going into this, that stand to create hurdles in the process:</p><!--kg-card-begin: markdown--><ol>
<li>I will have small datasets (ideally I can train this model on less than 10 samples)</li>
<li>I need to localize the influence the ink keys have on each other; I do not want an ink key from one side of a unit to influence the output of a key at the opposite end.</li>
</ol>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><h1 id="neural-net-architecture">Neural Net Architecture</h1>
<!--kg-card-end: markdown--><p>I want each ink key to take some amount of influence from it&apos;s immediate neighbors, but not further away than that. Given the small datasets and the goal of performing regression on smaller subsets of a larger data array, I will need to be creative here. There may be a few ways to separate each key into it&apos;s own zone, but the easiest solution seems to be by using a custom (non-trainable) weight matrix to define my zones, and perform the actual regression in the following layer. There are 8 print units, each with 34 keys across, this is 272 individual keys. As such, I will flatten my data and the input will be a vector of 272 ink key values. The architecture I&apos;ve settled into is illustrated below.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://dansportfol.io/content/images/2024/05/COL-file.png" class="kg-image" alt="Color Correct" loading="lazy" width="1242" height="513" srcset="https://dansportfol.io/content/images/size/w600/2024/05/COL-file.png 600w, https://dansportfol.io/content/images/size/w1000/2024/05/COL-file.png 1000w, https://dansportfol.io/content/images/2024/05/COL-file.png 1242w" sizes="(min-width: 720px) 720px"><figcaption>The human-readable color file. Highlighted is the ink key data.</figcaption></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://dansportfol.io/content/images/2024/05/model-architecture.png" class="kg-image" alt="Color Correct" loading="lazy" width="1531" height="1014" srcset="https://dansportfol.io/content/images/size/w600/2024/05/model-architecture.png 600w, https://dansportfol.io/content/images/size/w1000/2024/05/model-architecture.png 1000w, https://dansportfol.io/content/images/2024/05/model-architecture.png 1531w" sizes="(min-width: 720px) 720px"><figcaption>NN design</figcaption></figure><p>Following the input, the first layer is actually 272 parallel single-node layers, they have a fixed weight of 1 for the corresponding node in the input layer, and a fixed .35 for the immediate neighbors. Moving forward these continue as parallel layers, though are allowed to train and develop bias. Both of these layers use linear activation functions, as it is never a question of should a neuron fire or not - they all need to fire. The next layer concatenates these back to (1,272), and not imaged here - reshapes to (8,34), to resemble the original (print units) X (ink keys).</p><!--kg-card-begin: markdown--><h1 id="the-frontend-for-now">The frontend (for now)</h1>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><p>Embedded below is a <em>very</em> quick demo showing the current functionality. The frontend uses react, and react-redux for state management. (there is no audio, don&apos;t bother turning your sound up)</p>
<!--kg-card-end: markdown--><figure class="kg-card kg-embed-card"><iframe width="200" height="113" src="https://www.youtube.com/embed/Zp7TcpjlMdk?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen title="CC demo"></iframe></figure><!--kg-card-begin: markdown--><h1 id="optimizing-user-interaction">Optimizing user interaction</h1>
<!--kg-card-end: markdown--><p>This takes the form of a web app to avoid needing permissions to install or run on a work network. For now the workflow is a bit archaic, but functional.</p><p>In the meantime, we will continue sporting drag n&apos; drop elements, and a bare-bones database to save models. The TensorflowJS library generates a multipart/form-data MIME type request with the model architecture as JSON and weights as a binary. This should allow for importing model and weights to Tensorflow&apos;s other language libraries... I sense an API coming soon.<br></p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://dansportfol.io/content/images/2024/05/database.png" class="kg-image" alt="Color Correct" loading="lazy" width="930" height="185" srcset="https://dansportfol.io/content/images/size/w600/2024/05/database.png 600w, https://dansportfol.io/content/images/2024/05/database.png 930w" sizes="(min-width: 720px) 720px"><figcaption>Bare-bones DB</figcaption></figure><!--kg-card-begin: markdown--><h1 id="work-in-progress">Work in Progress</h1>
<!--kg-card-end: markdown--><p>Unlike my graveyard of half projects, this one is not currently on the back burner. Right now I am in somewhat of a testing phase, sorting out when during a print run is best to export ink settings to train with. Every printing press has its own &quot;personality&quot;, and while I am getting results that make a lot of sense from the model, honing in on the right training data will really make this shine.<br><br></p><!--kg-card-begin: markdown--><h1 id="update-12-14-24">Update 12-14-24</h1>
<!--kg-card-end: markdown--><p>This did, in fact, find it&apos;s way to the back burner, and finally the graveyard. <br>I have since moved away from the print industry and no longer had a need to improve this. However, should anyone come across this that can make use of this, please reach out (danieljhalleck@gmail.com) - I can host or would be happy to collaborate or help integrate this tech to see it used in the real world. </p>]]></content:encoded></item><item><title><![CDATA[100 Emoji Generator [.com?]]]></title><description><![CDATA[<p>The majority of this last year has been light on programming. That said, I found an immediate need for a quick text generator to recreate the &quot;100 points&quot; emoji with [mostly troll] text.</p><p>A handful of the technologies I will use listed below, spoiler alert (it&apos;s</p>]]></description><link>https://dansportfol.io/100emojigenerator-com/</link><guid isPermaLink="false">65a495f49d8ea0053639b96d</guid><dc:creator><![CDATA[Dan Halleck]]></dc:creator><pubDate>Wed, 17 Jan 2024 19:37:59 GMT</pubDate><media:content url="https://dansportfol.io/content/images/2024/03/brave_c7rryv9PzW.png" medium="image"/><content:encoded><![CDATA[<img src="https://dansportfol.io/content/images/2024/03/brave_c7rryv9PzW.png" alt="100 Emoji Generator [.com?]"><p>The majority of this last year has been light on programming. That said, I found an immediate need for a quick text generator to recreate the &quot;100 points&quot; emoji with [mostly troll] text.</p><p>A handful of the technologies I will use listed below, spoiler alert (it&apos;s just what I am most comfortable with):</p><!--kg-card-begin: markdown--><ul>
<li>React (Vite)</li>
<li>Bootstrap (for easy responsiveness)</li>
<li>GO (for any backend needed)</li>
<li>MySQL (found a excuse to implement a database)</li>
</ul>
<!--kg-card-end: markdown--><h2 id="the-foundation">The Foundation</h2><p>First thing&apos;s first, I need a font. After comparing a handful of these &quot;100&quot; emojis, I&apos;ve now seen many different fonts, and slightly different styles. Google fonts was a first and last stop to find a font with a touch of handwriting style, without being too <em>Comic Sans-y. </em><a href="https://fonts.google.com/specimen/Shantell+Sans?query=shantell&amp;ref=dansportfol.io">Shantell Sans SemiBold 600 Italic</a>? Sold!<br><br>Import a font, apply that to a canvas, write strings to canvas, draw some lines under that. The following is the logic portion of the React component that produces the emoji.<br></p><pre><code class="language-javascript">	//Excuse the formatting

	const [currentText, setCurrentText] = useState(&quot;&quot;);

	const canvas = document.getElementById(&quot;canvas&quot;);
        const ctx = canvas.getContext(&quot;2d&quot;);



            //background
            ctx.canvas.width = ctx.measureText(currentText).width + 20
            // ctx.fillStyle = &quot;black&quot;;
            ctx.globalAlpha = 0;
            ctx.fillRect(0, 0, canvas.width, canvas.height);


            //text
            ctx.globalAlpha = 1;
            ctx.font = &quot;50px myFont, myFontCSS&quot;;
            ctx.fillStyle = &quot;red&quot;;
            ctx.fillText(currentText, 10, 50);
            const firstArcStart = 1.4815;
            const secondArcStart = 1.4822;
            const radian = ctx.measureText(currentText).width / ((2 * Math.PI) * 3690) * (2 * Math.PI);


            //line1 
            ctx.beginPath();
            ctx.strokeStyle = &quot;red&quot;;
            ctx.lineWidth = 6;
            ctx.lineCap = &quot;round&quot;;
            if (currentText.length &gt; 0) {

                ctx.arc(220, 3606, 3550, firstArcStart * Math.PI, 		   (firstArcStart * Math.PI) + radian);
            }
            ctx.stroke();

            //line2
            ctx.beginPath();
            ctx.strokeStyle = &quot;red&quot;;
            ctx.lineWidth = 6;

            //Below some width, the bottom arc will &quot;wrap&quot;. This conditional is to not use an &quot;offset&quot; when text length is below 21
            if (ctx.measureText(currentText).width &gt; 21) {

                ctx.arc(220, 3618, 3550, secondArcStart * Math.PI, ((secondArcStart - .0014) * Math.PI) + radian);
            } else if ((ctx.measureText(currentText).width &gt; 0) &amp;&amp; (ctx.measureText(currentText).width &lt;= 21)) {
                ctx.arc(220, 3618, 3550, secondArcStart * Math.PI, ((secondArcStart) * Math.PI));
            }
            ctx.stroke();</code></pre><p>This logic is actually in a function of its own, but for the sake of context is shown here with state variable and selectors. Surely the circle-math needs some refactoring, but it works well, as is for now, so it stays.</p><p>From here, I did wrap the logic in a conditional to handle when the measure of the string in pixels exceeds a certain length. The has a large enough radius to cover a good span, but short enough to show the &quot;swoop&quot; on single character. A dynamically changing radius is outside the scope of this project, and static/straight lines are not desired - also not interested in creating a font.</p><figure class="kg-card kg-image-card"><img src="https://dansportfol.io/content/images/2024/01/longstring.gif" class="kg-image" alt="100 Emoji Generator [.com?]" loading="lazy" width="562" height="247"></figure><h2 id="copy-ability">Copy-ability</h2><p>It became apparent very quickly that, while using most browsers on PC, you can just right-click, copy the canvas, and the browser will handle turning this into a PNG. Safari on iPhone was the first indication that this was not going to work.</p><p>Easy fix - I use the canvas method <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toDataURL?ref=dansportfol.io">canvas.toDataURL()</a>, which returns the canvas as a base64 string and it&apos;s respective MIME type. I&apos;m now using this as the src for &lt;img&gt; elements and hiding the canvas that produced it.</p><h2 id="most-recent-emojis-rendered">Most Recent Emojis Rendered</h2><p>I needed some excuse to start up a database and backend. What better way than a feature that displays the last 5 strings entered. For this, I am using <a href="https://github.com/gin-gonic/gin?ref=dansportfol.io">Gin</a> in Go to serve up the world&apos;s smallest API. Of course this could have been done entirely within the standard library, but Gin just has so many GitHub stars and I like learning more about tools that are used at scale.</p><p>The strings are stored in a database on the same server. The backend is hosted on a port other than :80 or :443 and uses NGINX&apos;s reverse proxy feature to route requests accordingly.</p><pre><code class="language-json">/strings (submits current string)
    POST accepts: {string: &lt;string&gt;}
/lastfive (returns last five [unique] strings entered)
    GET returns: Array(5) {time: &lt;string&gt;, phrase: &lt;string&gt;}
    [
    {
        &quot;time&quot;: &quot;2024-01-17 15:19:02&quot;,
        &quot;phrase&quot;: &quot;Thisisalongstring&quot;
    },
    ....]</code></pre><p>//TODO create endpoint to submit a string and return the DataURL representation/png file by API call.<br><br>When first visiting the site, there is a useEffect hook with an empty dependency array to fetch from this endpoint. This will be iterated over using our function from earlier, and return a handful of &lt;img&gt; elements and a relative time description (currently using momentJS).</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://dansportfol.io/content/images/2024/01/last-five.png" class="kg-image" alt="100 Emoji Generator [.com?]" loading="lazy" width="585" height="755"><figcaption>Turned out alright.</figcaption></figure><h2 id="summary">Summary</h2><!--kg-card-begin: markdown--><p>This is my tentative summary.. It has been a good project so far to get back in the groove. It is still very basic - nearly no styling. Error handling needs some real attention. It has been fun so far.</p>
<!--kg-card-end: markdown--><p>Although it serves no real utility, I&apos;m happy to have introduced something to the internet that I have not seen yet. I plan to pick at this moving forward in my free time.<br><br>A very creative, SEO friendly domain: <a href="https://100emojigenerator.com/?ref=dansportfol.io">100emojigenerator.com</a></p><!--kg-card-begin: markdown--><h1 id="cache-me-outside-how-bout-dat">Cache me outside, how bout dat</h1>
<!--kg-card-end: markdown--><figure class="kg-card kg-image-card"><img src="https://dansportfol.io/content/images/2024/06/image.png" class="kg-image" alt="100 Emoji Generator [.com?]" loading="lazy" width="865" height="321" srcset="https://dansportfol.io/content/images/size/w600/2024/06/image.png 600w, https://dansportfol.io/content/images/2024/06/image.png 865w" sizes="(min-width: 720px) 720px"></figure><p>While this is a <em>very</em> specific search term and target, it is still neat to see one of my sites show up as a first result.</p>]]></content:encoded></item><item><title><![CDATA[Bike-Controller Part 2 - GUI]]></title><description><![CDATA[<p>I will need a GUI of sorts for the user to begin using bluetooth input for <em>something</em>. </p><p>Wails allows you to build a desktop app using modern frontend web technologies including, but not limited to React. Wails also uses Vite under the hood, which is exciting for someone who has</p>]]></description><link>https://dansportfol.io/bikenstein-part-2-wails/</link><guid isPermaLink="false">6490e577fc0eaf2f473297a8</guid><dc:creator><![CDATA[Dan Halleck]]></dc:creator><pubDate>Mon, 19 Jun 2023 23:40:47 GMT</pubDate><media:content url="https://dansportfol.io/content/images/2024/03/gopluswails.png" medium="image"/><content:encoded><![CDATA[<img src="https://dansportfol.io/content/images/2024/03/gopluswails.png" alt="Bike-Controller Part 2 - GUI"><p>I will need a GUI of sorts for the user to begin using bluetooth input for <em>something</em>. </p><p>Wails allows you to build a desktop app using modern frontend web technologies including, but not limited to React. Wails also uses Vite under the hood, which is exciting for someone who has historically used create-react-app. It hardly matters to me in this case if somewhere far abstracted from me is: webpack, rollup, or esbuild, but using a cutting edge tool like this is too exciting to pass up.</p><figure class="kg-card kg-image-card"><img src="https://dansportfol.io/content/images/2023/07/Bikenstein-GUI-1.png" class="kg-image" alt="Bike-Controller Part 2 - GUI" loading="lazy" width="1387" height="994" srcset="https://dansportfol.io/content/images/size/w600/2023/07/Bikenstein-GUI-1.png 600w, https://dansportfol.io/content/images/size/w1000/2023/07/Bikenstein-GUI-1.png 1000w, https://dansportfol.io/content/images/2023/07/Bikenstein-GUI-1.png 1387w" sizes="(min-width: 720px) 720px"></figure><p>Well, it&apos;s not pretty (yet), and it&apos;s not robust (yet), but we&apos;ve the beginnings of a GUI to work with. To this point, I&apos;ve used the Wails tool to spin up a Vite-React project, and for no particular reason I am styling using Tailwind. Ultimately, I&apos;ll just need a few neat tables, a diagram or two, and a menu of sorts. Using the Bluetooth Assigned Numbers documentation, I should be able to infer the names of the non-custom services and characteristics as well. </p><p> I&apos;ve opted for a Vite-React project, so the frontend updates are lightning fast. When updating Go files, it does have to perform a full rebuild as it creates JS bindings for your Go functions and builds a new binary. The creator of this tool seems reasonably dedicated to keeping the single file binary dream alive, and from what I&apos;ve seen, v3 will not disappoint.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://dansportfol.io/content/images/2023/07/Go-StopScan-1.png" class="kg-image" alt="Bike-Controller Part 2 - GUI" loading="lazy" width="407" height="138"><figcaption>Go function&#xA0;</figcaption></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://dansportfol.io/content/images/2023/07/JS-stopScan.png" class="kg-image" alt="Bike-Controller Part 2 - GUI" loading="lazy" width="318" height="72"><figcaption>JS function</figcaption></figure><p>The Go functions are called in JS exactly as they are &quot;exported&quot;. In JS, the Go function&apos;s return shows up as a promise - easy enough. I&apos;ve opted to wrap all of my Go functions on the JS side so I can add some JS logic and clean up the bigger files.</p><p></p><!--kg-card-begin: markdown--><h1 id="an-interesting-roadblock">An interesting roadblock..</h1>
<!--kg-card-end: markdown--><p>The official Bluetooth documentation is written well enough.. however, there are so many different types of data, it quickly becomes clear this would require an unreasonable amount of time and effort to support all devices. In the current build, I have borrowed the <a href="https://github.com/NordicSemiconductor/bluetooth-numbers-database?ref=dansportfol.io">bluetooth-numbers-database</a> data from Nordic Semiconductor&apos;s github. I have this list hard-coded in my project as follows, and it is great for displaying service/characteristic names of common devices. </p><figure class="kg-card kg-image-card"><img src="https://dansportfol.io/content/images/2023/07/UUID-hardcode.png" class="kg-image" alt="Bike-Controller Part 2 - GUI" loading="lazy" width="886" height="229" srcset="https://dansportfol.io/content/images/size/w600/2023/07/UUID-hardcode.png 600w, https://dansportfol.io/content/images/2023/07/UUID-hardcode.png 886w" sizes="(min-width: 720px) 720px"></figure><p>This proves only so useful, as each and every characteristic has a unique number of bits it sends, representing something specific, as defined in the bluetooth documents. While I plan to allow users to perceive the data in their own way and map it accordingly, there must still be a solid foundation within the software on which to provide the user some options.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://dansportfol.io/content/images/2023/07/badexamplebt.png" class="kg-image" alt="Bike-Controller Part 2 - GUI" loading="lazy" width="652" height="587" srcset="https://dansportfol.io/content/images/size/w600/2023/07/badexamplebt.png 600w, https://dansportfol.io/content/images/2023/07/badexamplebt.png 652w"><figcaption>Virtually unusable fields</figcaption></figure><p>As much as I would love to map the frequency at which you ignore Jeff Bezos&apos; phone calls to some mechanic in a game, it just is not going to happen, not dynamically anyway.</p><!--kg-card-begin: markdown--><h1 id="the-solution">The solution</h1>
<!--kg-card-end: markdown--><p>--This project currently on the back-burner. Left off in the early stages of writing the Windows driver--</p>]]></content:encoded></item><item><title><![CDATA[Bike Controller Part 1 - Bluetooth]]></title><description><![CDATA[Gamified cycling beyond Zwift and the like.]]></description><link>https://dansportfol.io/bikenstein-creating-a-universal-bike-controller/</link><guid isPermaLink="false">64886c19fc0eaf2f473296f2</guid><dc:creator><![CDATA[Dan Halleck]]></dc:creator><pubDate>Tue, 13 Jun 2023 19:22:12 GMT</pubDate><media:content url="https://dansportfol.io/content/images/2024/03/bettercapscanfocus.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://dansportfol.io/content/images/2024/03/bettercapscanfocus.jpg" alt="Bike Controller Part 1 - Bluetooth"><p>The goal here is to take input data from sensors attached to a bicycle, and build a no-frills GUI to map this data to a virtual controller. The user should ultimately be able to map this data to the functions of a common game controller with a level of precision that allows for a good gaming experience. <br><br>I&apos;d like to use as many sensors that already exist and are on the market, so to get started, I will focus on a Wahoo sensors for both wheel speed and pedal cadence. Steering, braking, and everything else will be a bit more custom. </p><!--kg-card-begin: markdown--><h1 id="how-does-bluetooth-work-anyway">How does bluetooth work anyway?</h1>
<!--kg-card-end: markdown--><p>I&apos;ve chosen to stick with Bluetooth Low Energy instead of the ANT+ protocol, for both quicker transfer speeds and a [slightly] lower barrier to entry. Let&apos;s start with a Wahoo wheel speed sensor.</p><figure class="kg-card kg-image-card"><img src="https://dansportfol.io/content/images/2023/06/wahoo-wheel-speed-sensor-1.jpg" class="kg-image" alt="Bike Controller Part 1 - Bluetooth" loading="lazy" width="2000" height="1467" srcset="https://dansportfol.io/content/images/size/w600/2023/06/wahoo-wheel-speed-sensor-1.jpg 600w, https://dansportfol.io/content/images/size/w1000/2023/06/wahoo-wheel-speed-sensor-1.jpg 1000w, https://dansportfol.io/content/images/size/w1600/2023/06/wahoo-wheel-speed-sensor-1.jpg 1600w, https://dansportfol.io/content/images/size/w2400/2023/06/wahoo-wheel-speed-sensor-1.jpg 2400w" sizes="(min-width: 720px) 720px"></figure><p>I will be using Bettercap to learn a little more about this sensor and it&apos;s BLE packet information.</p><figure class="kg-card kg-image-card"><img src="https://dansportfol.io/content/images/2023/06/bettercapcensor.gif" class="kg-image" alt="Bike Controller Part 1 - Bluetooth" loading="lazy" width="1318" height="904" srcset="https://dansportfol.io/content/images/size/w600/2023/06/bettercapcensor.gif 600w, https://dansportfol.io/content/images/size/w1000/2023/06/bettercapcensor.gif 1000w, https://dansportfol.io/content/images/2023/06/bettercapcensor.gif 1318w" sizes="(min-width: 720px) 720px"></figure><figure class="kg-card kg-image-card"><img src="https://dansportfol.io/content/images/2023/06/bettercapscanfocus.jpg" class="kg-image" alt="Bike Controller Part 1 - Bluetooth" loading="lazy" width="970" height="548" srcset="https://dansportfol.io/content/images/size/w600/2023/06/bettercapscanfocus.jpg 600w, https://dansportfol.io/content/images/2023/06/bettercapscanfocus.jpg 970w" sizes="(min-width: 720px) 720px"></figure><p>To make better sense of what is going on here, I will refer to the official <a href="https://www.bluetooth.com/specifications/assigned-numbers/?ref=dansportfol.io">BluetoothSIG Assigned Numbers</a> documentation. BLE uses 16-bit UUIDs to label <strong>services</strong> and <strong>characteristics</strong>, with their docs we can verify we have:</p><p>1816 - Cycling Speed and Cadence [service]</p><p>and within that service we have:</p><!--kg-card-begin: markdown--><p>2a5b - CSC Measurement [characteristic]<br>
<img src="https://dansportfol.io/content/images/2023/06/CSC-Measurement-Full.jpg" alt="Bike Controller Part 1 - Bluetooth" loading="lazy"><br>
So far I have only made a single connection to the device to obtain some base information, to expand on this I will need to &quot;subscribe&quot; to the characteristics I want to stream data from. This characteristic has a property called NOTIFY which means the peripheral device will continuously send updates of this characteristic to it&apos;s subscribers. The READ properties make characteristics useful for initial setup but have no logical reason to stream as they should not change.</p>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><p>2a5c - CSC Feature [characterstic]<br>
<img src="https://dansportfol.io/content/images/2023/06/CSC-Feature-Full.jpg" alt="Bike Controller Part 1 - Bluetooth" loading="lazy"><br>
Our feature field reads &apos;0100&apos;. Following the chart, we know there are going to be up to 16 boolean values to make up our 2 octets. At first glance this might suggest we have only Crank Revolution Data Supported, but this is a Wheel Speed Sensor. Using the <a href="https://github.com/NordicSemiconductor/Android-nRF-Connect?ref=dansportfol.io">Nordic Semiconductor app</a> for android, it is properly interpreted as wheel data, making our output from bettercap all the more confusing. We&apos;ll get back to this.</p>
<!--kg-card-end: markdown--><p></p><p>I will be using <a href="https://github.com/tinygo-org/bluetooth?ref=dansportfol.io">TinyGo&apos;s bluetooth</a> package, certainly there are other methods, but for this project I will be introducing myself to <a href="https://github.com/wailsapp/wails?ref=dansportfol.io">Wails</a> later and would prefer to keep the software itself Go-heavy. The code is omitted for now as we will be very much in-the-weeds on that later. </p><p>The introduction portion of the Bluetooth docs contain two very important lines that are essential to interpreting this data:</p><figure class="kg-card kg-image-card"><img src="https://dansportfol.io/content/images/2023/06/image.png" class="kg-image" alt="Bike Controller Part 1 - Bluetooth" loading="lazy" width="625" height="82" srcset="https://dansportfol.io/content/images/size/w600/2023/06/image.png 600w, https://dansportfol.io/content/images/2023/06/image.png 625w"></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://dansportfol.io/content/images/2023/06/raw-sensor-data.gif" class="kg-image" alt="Bike Controller Part 1 - Bluetooth" loading="lazy" width="408" height="91"><figcaption>&quot;Raw&quot; data from sensor</figcaption></figure><p>TinyGo delivers the data to us in uint8 values, the gif above shows us an array of said values representing the entire CSC measurement characteristic. Referring to the CSC Measurement figure above we can ascertain the following:</p><figure class="kg-card kg-image-card"><img src="https://dansportfol.io/content/images/2023/06/CSC-measurement-breakdown.png" class="kg-image" alt="Bike Controller Part 1 - Bluetooth" loading="lazy" width="878" height="391" srcset="https://dansportfol.io/content/images/size/w600/2023/06/CSC-measurement-breakdown.png 600w, https://dansportfol.io/content/images/2023/06/CSC-measurement-breakdown.png 878w" sizes="(min-width: 720px) 720px"></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://dansportfol.io/content/images/2023/06/Sensor-output.gif" class="kg-image" alt="Bike Controller Part 1 - Bluetooth" loading="lazy" width="442" height="230"><figcaption>The sensor output I actually need</figcaption></figure><p>The &quot;Last wheel event time&quot; unit of measurement is 1/1024th second as per the Bluetooth GATT docs. Notice the rollover on this metric, we also learned in the docs that this is represented by a uint16 number.</p><!--kg-card-begin: markdown--><h1 id="summary">Summary</h1>
<p>This has been a great intro to bluetooth data, surely building on this foundation will be imperative to moving forward on the larger project at hand. I now have some useful data here, I can actually calculate a speed with some basic math and bike measurements. In combination with a crank speed sensor I will be able to estimate power output from the rider and gearing. For my application that may be irrelevant, but I&apos;ll take all the data I can get! While the data appears to come in just over once per second, the &quot;last wheel event time&quot; field is going to help smooth the data in practice - a very interesting way of dealing with uncertain transmission.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item></channel></rss>