Tyrel's Bloghttps://tyrel.dev/blog/2023-10-15T00:00:00-04:00Code, Flying, Tech, AutomationDjangocon 20232023-10-15T00:00:00-04:002023-10-15T00:00:00-04:00tyreltag:tyrel.dev,2023-10-15:/blog/2023/10/djangocon-2023.html<p>I am at DjangoCon 2023 this year!</p>
<p>I live in Durham, NC and have been avoiding conferences for the past few years because of Covid and not wanting to travel.
This year for, what ever reason, they picked Durham!</p>
<p>So I am breaking my "Don't go to conferences" for multiple …</p><p>I am at DjangoCon 2023 this year!</p>
<p>I live in Durham, NC and have been avoiding conferences for the past few years because of Covid and not wanting to travel.
This year for, what ever reason, they picked Durham!</p>
<p>So I am breaking my "Don't go to conferences" for multiple reasons.</p>
<ol class="arabic simple">
<li>The DSF has pretty great Covid rules, and mask requirements.</li>
<li>Testing requirements.</li>
<li>It's local, so I get to sleep in my own bed.</li>
</ol>
<p>I'm not guaranteed to get to see my daughter, but I hope she knows I still love her even if I'm not around.
I'm leaving at 7:30 tomorrow as I'm biking in, and she may be asleep.</p>
<p>Already I've gone to one of the local breweries I haven't gone to yet (See above: Covid), and met some great people, can't wait for the rest of the week.</p>
<p>While technically a "Tech Blog" I am by no means a great note taker, so don't expect any quality information from me in blog form, I'm mostly going for the HallwayTrack, and to watch talks.</p>
Rotate a Matrix in Python2023-10-03T00:00:00-04:002023-10-03T00:00:00-04:00tyreltag:tyrel.dev,2023-10-03:/blog/2023/10/rotate-a-matrix-in-python.html<p>I've been doing Advent of Code for a few years now, and every year I do it in my favorite language, Python.
One thing that comes up a lot, is rotating matrices.</p>
<p>One way to do this, is to use Numpy, using <tt class="docutils literal">np.rot90(mat)</tt>, but not everyone wants to …</p><p>I've been doing Advent of Code for a few years now, and every year I do it in my favorite language, Python.
One thing that comes up a lot, is rotating matrices.</p>
<p>One way to do this, is to use Numpy, using <tt class="docutils literal">np.rot90(mat)</tt>, but not everyone wants to install Numpy just to do one small task.
I know I don't always.</p>
<p>The way I always do it, that will support non-square matrixes, is to use zip.</p>
<div class="highlight"><pre><span></span><span class="o">>>></span> <span class="n">matrix</span> <span class="o">=</span> <span class="p">[</span>
<span class="p">[</span><span class="mi">1</span><span class="p">,</span><span class="mi">2</span><span class="p">,</span><span class="mi">3</span><span class="p">],</span>
<span class="p">[</span><span class="mi">4</span><span class="p">,</span><span class="mi">5</span><span class="p">,</span><span class="mi">6</span><span class="p">],</span>
<span class="p">[</span><span class="mi">7</span><span class="p">,</span><span class="mi">8</span><span class="p">,</span><span class="mi">9</span><span class="p">]</span>
<span class="p">]</span>
<span class="o">>>></span> <span class="n">rotated</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="nb">zip</span><span class="p">(</span><span class="o">*</span><span class="n">matrix</span><span class="p">[::</span><span class="o">-</span><span class="mi">1</span><span class="p">]))</span>
<span class="c1"># And result is</span>
<span class="p">[[</span><span class="mi">7</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">1</span><span class="p">],</span>
<span class="p">[</span><span class="mi">8</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">2</span><span class="p">],</span>
<span class="p">[</span><span class="mi">9</span><span class="p">,</span> <span class="mi">6</span><span class="p">,</span> <span class="mi">3</span><span class="p">]]</span>
</pre></div>
<p>We can break this down bit by bit.</p>
<p>This will copy the list, with a -1 step, resulting in a reverse order</p>
<div class="highlight"><pre><span></span><span class="o">>>></span> <span class="n">matrix</span><span class="p">[::</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span>
<span class="p">[[</span><span class="mi">7</span><span class="p">,</span><span class="mi">8</span><span class="p">,</span><span class="mi">9</span><span class="p">],</span>
<span class="p">[</span><span class="mi">4</span><span class="p">,</span><span class="mi">5</span><span class="p">,</span><span class="mi">6</span><span class="p">],</span>
<span class="p">[</span><span class="mi">1</span><span class="p">,</span><span class="mi">2</span><span class="p">,</span><span class="mi">3</span><span class="p">]]</span>
</pre></div>
<p>Next we need to call zip in order to get the x-th item from each inner list, but first, we need to unpack it. If you'll notice, the unpacked version isn't wrapped with another list, which is what zip needs from us.</p>
<div class="highlight"><pre><span></span><span class="c1"># Too many lists</span>
<span class="o">>>></span> <span class="nb">print</span><span class="p">(</span><span class="n">matrix</span><span class="p">[::</span><span class="o">-</span><span class="mi">1</span><span class="p">])</span>
<span class="p">[[</span><span class="mi">7</span><span class="p">,</span> <span class="mi">8</span><span class="p">,</span> <span class="mi">9</span><span class="p">],</span> <span class="p">[</span><span class="mi">4</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">6</span><span class="p">],</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">]]</span>
<span class="c1"># Just right</span>
<span class="o">>>></span> <span class="nb">print</span><span class="p">(</span><span class="o">*</span><span class="n">matrix</span><span class="p">[::</span><span class="o">-</span><span class="mi">1</span><span class="p">])</span>
<span class="p">[</span><span class="mi">7</span><span class="p">,</span> <span class="mi">8</span><span class="p">,</span> <span class="mi">9</span><span class="p">]</span> <span class="p">[</span><span class="mi">4</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">6</span><span class="p">]</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">]</span>
</pre></div>
<p>From there, we can pass this unpacked list of - in our case - three lists, to zip (and in Python 3 this returns a generator, so we need to call list again on it, or just use it)</p>
<div class="highlight"><pre><span></span><span class="o">>>></span> <span class="c1"># Again, we get the rotated matrix</span>
<span class="o">>>></span> <span class="nb">list</span><span class="p">(</span><span class="nb">zip</span><span class="p">(</span><span class="o">*</span><span class="n">matrix</span><span class="p">[::</span><span class="o">-</span><span class="mi">1</span><span class="p">]))</span>
<span class="p">[[</span><span class="mi">7</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">1</span><span class="p">],</span>
<span class="p">[</span><span class="mi">8</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">2</span><span class="p">],</span>
<span class="p">[</span><span class="mi">9</span><span class="p">,</span> <span class="mi">6</span><span class="p">,</span> <span class="mi">3</span><span class="p">]]</span>
</pre></div>
<div class="section" id="notes">
<h2>Notes</h2>
<p>Small note: If you run this, you will actually get a list of tuples, so you can map those back to a list, if you need to update them for any reason.
I just wanted square brackets in my examples.</p>
<div class="highlight"><pre><span></span><span class="c1"># This is just messy looking, so I didn't mention it until now</span>
<span class="o">>>></span> <span class="nb">list</span><span class="p">(</span><span class="nb">map</span><span class="p">(</span><span class="nb">list</span><span class="p">,</span> <span class="nb">zip</span><span class="p">(</span><span class="o">*</span><span class="n">matrix</span><span class="p">[::</span><span class="o">-</span><span class="mi">1</span><span class="p">])))</span>
</pre></div>
<p>As I mentioned, due to using <tt class="docutils literal">zip</tt> this will work with non-square examples as well.</p>
<div class="highlight"><pre><span></span><span class="o">>>></span> <span class="n">matrix</span> <span class="o">=</span> <span class="p">[</span>
<span class="o">...</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span><span class="mi">2</span><span class="p">,</span><span class="mi">3</span><span class="p">,</span><span class="mi">4</span><span class="p">,</span><span class="mi">5</span><span class="p">,</span><span class="mi">6</span><span class="p">,</span><span class="mi">7</span><span class="p">,</span><span class="mi">8</span><span class="p">,</span><span class="mi">9</span><span class="p">],</span>
<span class="o">...</span> <span class="p">[</span><span class="mi">9</span><span class="p">,</span><span class="mi">8</span><span class="p">,</span><span class="mi">7</span><span class="p">,</span><span class="mi">6</span><span class="p">,</span><span class="mi">5</span><span class="p">,</span><span class="mi">4</span><span class="p">,</span><span class="mi">3</span><span class="p">,</span><span class="mi">2</span><span class="p">,</span><span class="mi">1</span><span class="p">],</span>
<span class="o">...</span> <span class="p">]</span>
<span class="o">>>></span> <span class="nb">print</span><span class="p">(</span><span class="nb">list</span><span class="p">(</span><span class="nb">zip</span><span class="p">(</span><span class="o">*</span><span class="n">matrix</span><span class="p">[::</span><span class="o">-</span><span class="mi">1</span><span class="p">])))</span>
<span class="p">[(</span><span class="mi">9</span><span class="p">,</span> <span class="mi">1</span><span class="p">),</span>
<span class="p">(</span><span class="mi">8</span><span class="p">,</span> <span class="mi">2</span><span class="p">),</span>
<span class="p">(</span><span class="mi">7</span><span class="p">,</span> <span class="mi">3</span><span class="p">),</span>
<span class="p">(</span><span class="mi">6</span><span class="p">,</span> <span class="mi">4</span><span class="p">),</span>
<span class="p">(</span><span class="mi">5</span><span class="p">,</span> <span class="mi">5</span><span class="p">),</span>
<span class="p">(</span><span class="mi">4</span><span class="p">,</span> <span class="mi">6</span><span class="p">),</span>
<span class="p">(</span><span class="mi">3</span><span class="p">,</span> <span class="mi">7</span><span class="p">),</span>
<span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="mi">8</span><span class="p">),</span>
<span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">9</span><span class="p">)]</span>
</pre></div>
</div>
Which which is which?2023-09-26T00:00:00-04:002023-09-26T00:00:00-04:00tyreltag:tyrel.dev,2023-09-26:/blog/2023/09/which-which-is-which.html<p>I had a bit of a "Tyrel you know nothing" moment today with some commandline tooling.</p>
<p>I have been an avid user of ZSH for a decade now, but recently I tried to swap to fish shell.
Along the years, I've maintained a lot of different iterations of <a class="reference external" href="https://gitea.tyrel.dev/tyrel/dotfiles">dotfiles</a>, and …</p><p>I had a bit of a "Tyrel you know nothing" moment today with some commandline tooling.</p>
<p>I have been an avid user of ZSH for a decade now, but recently I tried to swap to fish shell.
Along the years, I've maintained a lot of different iterations of <a class="reference external" href="https://gitea.tyrel.dev/tyrel/dotfiles">dotfiles</a>, and shell aliases/functions.
I was talking to a <a class="reference external" href="https://fredeb.dev">friend</a> [citation needed] about updating from <tt class="docutils literal">exa</tt> to <tt class="docutils literal">eza</tt> and then noticed I didn't have my aliases loaded, so I was still using <tt class="docutils literal">ls</tt> directly, as I have <tt class="docutils literal">alias <span class="pre">ls="exa</span> <span class="pre">-lhFgxUm</span> <span class="pre">--git</span> <span class="pre">--time-style</span> <span class="pre">long-iso</span> <span class="pre">--group-directories-first"</span></tt> in my <tt class="docutils literal">.shell_aliases</tt> file.</p>
<p>I did this by showing the following output:</p>
<div class="highlight"><pre><span></span>$ which ls
/usr/bin/ls
</pre></div>
<p>Because I expected it to show me which alias was being pointed to by <tt class="docutils literal">ls</tt>.</p>
<p>My friend pointed out that "Which doesn't show aliases, it only points to files" to which I replied along the lines of "What? No way, I've used <tt class="docutils literal">which</tt> to show me aliases and functions loads of times."</p>
<p>And promptly sent a screenshot of my system NOT showing that for other aliases I have set up. Things then got conversational and me being confused, to the point of me questioning if "Had I ever successfully done that? Maybe my macbook is set up differrently" and went and grabbed that.</p>
<p>Friend then looked at the man page for which, and noticed that there's the <tt class="docutils literal"><span class="pre">--read-alias</span></tt> and <tt class="docutils literal"><span class="pre">--read-functions</span></tt> flags on <tt class="docutils literal">which</tt>, and I didn't have those set.
I then swapped over to bash "Maybe it's a bash thing only? I'm using Fish".</p>
<p>Nope, still nothing! Then went to google, and it turns out that ZSH is what has this setup by default.
Thank you <a class="reference external" href="https://stackoverflow.com/a/14196212">"Althorion"</a> from Stackoverflow for settling my "Yes you've done this before" confusion.</p>
<p>It turns out that ZSH's <tt class="docutils literal">which</tt> is equivalent to the ZSH shell built-in <tt class="docutils literal">whence <span class="pre">-c</span></tt> which shows aliases and functions.</p>
<p>After running <tt class="docutils literal">/usr/bin/zsh</tt> and sourcing my aliases (I don't have a zshrc file anymore, I need to set that back up), I was able to settle my fears and prove to myself that I wasn't making things up. There is a which which shows you which aliases you have set up, which is default for ZSH.</p>
<div class="highlight"><pre><span></span>$ which ls
ls: aliased to exa -lhFgxUm --git --time-style long-iso --group-directories-first
</pre></div>
My Life Story2023-08-24T00:00:00-04:002023-08-24T00:00:00-04:00tyreltag:tyrel.dev,2023-08-24:/blog/2023/08/my-life-story.html<p>Trying to write a more prose version of my Resume.
Kind of a living post about my life in the Software Engineering World.</p>
<div class="section" id="early-life">
<h2>Early Life</h2>
<p>I started programming with Visual Basic in the 90s on a laptop in my father's car.
Parents had just divorced and the drive to his …</p></div><p>Trying to write a more prose version of my Resume.
Kind of a living post about my life in the Software Engineering World.</p>
<div class="section" id="early-life">
<h2>Early Life</h2>
<p>I started programming with Visual Basic in the 90s on a laptop in my father's car.
Parents had just divorced and the drive to his new place every other weekend was two hours, so I had a lot of downtime going through the Visual Basic 5 and 6 books we had.
After that, I started trying to program chat bots for Starcraft Battle.net, and irc bots.
In highschool I started taking more Visual Basic courses, mostly because that's all that my teachers had for classes.
Senior year I took some Early Education courses at the local college, and then went to that college (Keene State) for my Computer Science Degree.
At Keene State, I took a lot of Java classes, that was the core curriculum.
I also took some more Visual Basic courses, some C++ and web design courses as well.</p>
<p>After college, I thought I'd be working Java again, but I got a referral from one of my favorite professors to this company called Appropriate Solutions Inc, and started working in Python/Django.
I had never touched Python outside of OpenRPG - and even then it was just installing the interpretor so I could run the game.</p>
</div>
<div class="section" id="appropriate-solutions-inc">
<h2>Appropriate Solutions Inc</h2>
<p>At Appropriate Solutions I worked on maybe ten different projects.
The first thing I worked on was an Hour Tracker to learn how Django works, it worked great, but definitley didn't look too flashy.
From there, I went on to work on a Real Estate Website, a Bus tracking/mapping system for a school district, a Pinterest Clone, and some more GIS mapping things.
One of our projects was with HubSpot, and I went to one of their hackathons, and a couple conferences in Boston.
I had a lot of friends in Boston, so I decided to move there mid 2012.</p>
</div>
<div class="section" id="propel-marketing">
<h2>Propel Marketing</h2>
<p>I got my first Boston job in Quincy, MA - working at a startup called Propel Marketing.
This was also a Python/Django role, but had more front end work.
While there I worked on their internal CMS tooling for selling white labeld websites to clients.
I worked on a lot of internal tooling, one that would pull leads from our system, and then upload to a lot of different vendor tooling.
A couple of Python PIL tools that would generate facebook and twitter banners, but a lot of the work there was learning and writing tests in Python.</p>
</div>
<div class="section" id="akamai">
<h2>Akamai</h2>
<p>From there, I started a six month contract at Akamai, working for their Tech Marketing team on a couple tools.
This later got extended another three months, until the team ran out of budget for contractors.
I worked on their <a class="reference external" href="https://gnet.akamai.com/">"Spinning Globe"</a> which was really fun.
Some internal dashboards, and a couple email tools that worked with SalesForce.</p>
</div>
<div class="section" id="addgene">
<h2>Addgene</h2>
<p>In 2015 I then landed a spot at Addgene - a nonprofit biotech!
This is where my career really started taking off.
I started to lead projects, do more valuable research and go to conferences.
The company itself was - for my tenure there - two Django Projects and some jQuery/Bootstrap.
The "core" site was the ecommerce and research site.
Buying, selling, research on Plasmids and Viruses.
The back end was the inventory management system.</p>
<p>While there, I also lead the charge on a couple projects.
We were migrating to AWS - from an in house rackmount server, so we needed to get a lot of data on S3.
Testing at Addgene was fickle, as everything was stored in tsv files and reloaded in memory.
I developed a python package that would save thousands of dollars of S3 costs, while still making the file upload process in testing seamless.
<a class="reference external" href="https://gitlab.com/Tyrel/django-dbfilestorage">Django DBFileStorage</a> was born.</p>
<p>Another charge I lead was helping Celigo alpha test Integrator.IO - working on building an integration of a lot of our sales data into netsuite/salesforce (I forget which one) by working with the Celigo API.
This was a fun project - as when I was done with this, we got to archive an old Java repo that was barely hanging on, and had no bugfixes in years.</p>
<p>While at Addgene, I also started the "Teaching Scientists How To Program" lunch and learn club.
We would have meetings where anyone from the Scientist team could come, ask questions about Python, and work through any of the problems they were having with our Jupyter Notebooks we set up that they could run.
This was great, I helped foster some friendships that I believe will last a lifetime, helped people transition into actual programmers, and helped the company save a lot of time by helping more people learn.</p>
</div>
<div class="section" id="tidelift">
<h2>Tidelift</h2>
<p>After Addgene, in 2018 I joined an early stage startup called Tidelift.
I was one of the first engineers there, so I got to help lead a lot of early shaping of the company.
I started with working on a lot of the <a class="reference external" href="https://tidelift.com/about/lifter">Lifter</a> focused side of the site.
Helping create tasks the lifters could complete so they could get paid.
From there working on the Subscriber side of things where the paying clients would get information about their dependencies.
I have an upcoming blog post about some work there, so I won't go into too much details.
I did help start the Tidelift CLI though.
A tool written in Go, it was a CI/CD tool to analyze software dependencies for security/licensing problems</p>
</div>
<div class="section" id="everquote">
<h2>EverQuote</h2>
<p>After Tidelift, I started at EverQuote in 2022.
My longtime friend Sam was a Director leading a team that was working on replatforming a monolith and needed to backfill a python role.
I had been asking him for years when he would work on Python stuff, as he had only been working at Ruby companies for the past few years, so this caught my ear and I started there.</p>
<p>The first project was replatforming a Python 2.7 monolith - with code from as far back as 2010 - to micro services.
These were mainly FastAPI services, that communicted amongst eachother with kafka.
Some of them would read from the database, and send events into the kafka stream.
Some would read from the stream, and write to the database.
Others would read from database or kafka, and then communicate with a third party system.</p>
<p>All of these had Grafana (and later NewRelic) metrics attached.
Every server had at least one dashboard, with many graphs, event logs and charts.
These were all deployed using kubernetes, terraform, AWS.
I can't speak to the specifics about past there - as there was another dedicated ops team.</p>
<p>Some other projects I worked on there were really fun.
One of the analysts used to maintain her daily workflow in a google doc, and I helped lead a project that took that apart and worked on it programatically.
This was then turned into a five part rebalancing, reweighting, and email route manipulating script - that ran daily using Cron, and saved that team over fourteen hours a week.</p>
<p>The Remarketing team came to an end, and there was a small re-org and we merged with another team and became the Consumer Engagement team.
This dealt with Calls, SMS, Email, and working with the Sales Reps.</p>
<p>We started a project using Go and React that would pull users from the database and show a script for the Sales rep to read to the client on the phone, with specific information about what plans were available.
Other projects on that team, which is what I spent most of my time on, was porting CI/CD processes from Atlassian Bamboo, to GitHub Actions.</p>
<p>During this time, I took a couple months of paternity leave, and a few weeks after I came back there was a major reduction in force and I was laid off.</p>
</div>
<div class="section" id="after-everquote">
<h2>After EverQuote</h2>
<p>Since leaving EQ, I have been on the job hunt.
If you know anything about tech in 2023, a LOT of people are job hunting right now.
I'm excited that this happened at a time in my daughter's life where I can spend SO MUCH more time with her than some fathers can.
That's the good side of things.
I hope I get a job soon though, as the sole earner in my family.</p>
<p>If you've made it this far, please check out my Resume in the sidebar, and contact me if you have anything you think would be a fit.
Who knows, I might delete this last section once I get a job!</p>
</div>
General Job Search Update2023-08-23T00:00:00-04:002023-08-23T00:00:00-04:00tyreltag:tyrel.dev,2023-08-23:/blog/2023/08/general-job-search-update.html<p>As mentioned in a previous post, I'm on the job hunt again.
I got laid off in June with a 30% Reduction In Force.</p>
<p>I've been searching primarily for Python and Go roles, but I'm not having a lot of luck right now - seems everyone else also got laid off …</p><p>As mentioned in a previous post, I'm on the job hunt again.
I got laid off in June with a 30% Reduction In Force.</p>
<p>I've been searching primarily for Python and Go roles, but I'm not having a lot of luck right now - seems everyone else also got laid off is who I am competing against.
<a class="reference external" href="https://nkantar.com/blog/2023/08/hire-me-v202308/">(That said, go hire my friend Nik! He's fantastic!)</a>
I've had a lot of job opportunity people ghost me.
Gotten through a few late rounds, only to never hear from the company again.
Even if I have emailed them a thank you letter for the interviews, to express my interest.</p>
<p>I've been around professionally for thirteen years.
Over those years, I have picked up mostly back end skills.
I have eight solid years of Django experience.
Four years of Ruby on Rails experience.
A couple years of Flask, FastAPI, and other smaller Python Frameworks mixed in.</p>
<p>I'm looking for an IC role, where I can move into a Tech Lead role. I want to eventually some day be a Staff/Principal role, but I don't have that on paper to show I can do it, so trying to get in somewhere new with an IC role.</p>
I am now Matrix Compatible2023-06-19T00:00:00-04:002023-06-19T00:00:00-04:00tyreltag:tyrel.dev,2023-06-19:/blog/2023/06/i-am-now-matrix-compatible.html<p>You can now message me on Matrix using <a class="reference external" href="https://matrix.to/#/@tyrel:tyrel.dev">@tyrel:tyrel.dev</a>.</p>
<p>I'm running Synapse.</p>
Laid Off - 2023 Edition!2023-06-16T00:00:00-04:002023-06-16T00:00:00-04:00tyreltag:tyrel.dev,2023-06-16:/blog/2023/06/laid-off-2023-edition.html<p>"Hey Tyrel, I put a meeting on your calendar, let me know if you can make it." The last words you want to hear from your manager.</p>
<p>Well it happened again, and I got caught in some layoffs from work and am on the job hunt again. I did want …</p><p>"Hey Tyrel, I put a meeting on your calendar, let me know if you can make it." The last words you want to hear from your manager.</p>
<p>Well it happened again, and I got caught in some layoffs from work and am on the job hunt again. I did want to be able to spend more time with Astrid, but not like this!</p>
<p>After the call with HR and them all explaining what was happening, and panic texting my wife and some friends. I emailed the recruiters I've been working with for a few weeks back, and all day today I've been appling to a lot of places.</p>
<p>There are a LOT of jobs out there I'm not interested in, a lot of Ruby on Rails jobs, crypto companies, etc. But I am finding a LOT of Python or Go jobs I'm applying to. I'd love to get a job doing rust and firmware work, but that's unlikely as I want to stay remote, and I have very minimal Rust experience.</p>
<p>What worries me is finding health insurance, because America ties it to work... my wife is unemployed and now we have to cancel a few appointments for us the next few weeks. Still going to keep Astrids 4mo vaccinations though. Those I'll be okay paying out of pocket for.</p>
<p>I just ended the day applying to twelve jobs, hopefully one pans out!</p>
<p>If anyone needs any Python consultation, let me know too! Thanks!</p>
Netgear WAC1042023-06-08T00:00:00-04:002023-06-08T00:00:00-04:00tyreltag:tyrel.dev,2023-06-08:/blog/2023/06/netgear-wac104.html<p>I recently bought four Netgear WAC104 devices, and am flashing OpenWRT onto them.
I have struggled a lot to get the firmware on, due to the not great interface they provide.</p>
<p>The issue is, that it prompts you to change the password, but then when you change it on the …</p><p>I recently bought four Netgear WAC104 devices, and am flashing OpenWRT onto them.
I have struggled a lot to get the firmware on, due to the not great interface they provide.</p>
<p>The issue is, that it prompts you to change the password, but then when you change it on the page you land on, nothing connects anymore and you can't access the router.</p>
<p>The solution is to click "Set Password" in the Administration menu on the left, and set it there.
Even though there is a prompt to set the password on every page, that will change other settings too and break things.</p>
<p>The router isn't great, and the software is awful so thats why I'm installing OpenWRT anyway.</p>
<p>Two down, two more to go!</p>
pfSense2023-06-07T00:00:00-04:002023-06-07T00:00:00-04:00tyreltag:tyrel.dev,2023-06-07:/blog/2023/06/pfsense.html<p>This week I finally got a machine that is solely to run pfSense.
I didn't want to spend _too_ much money so I bought a $200.00 Qotom Firewall Q330G4.
This was great and easy to set up.</p>
<p>First I bought a Netgear WAC104 and installed OpenWRT on it. Simple …</p><p>This week I finally got a machine that is solely to run pfSense.
I didn't want to spend _too_ much money so I bought a $200.00 Qotom Firewall Q330G4.
This was great and easy to set up.</p>
<p>First I bought a Netgear WAC104 and installed OpenWRT on it. Simple enough.
Then I put that into bridge mode, so it's just an Access Point and not a "smart" router too.</p>
<p>Then I put my Linksys EA9300 into bridge mode and behind the pfSense machine (into a switch) and couldn't access any of my server's sites.</p>
<p>After futzing with that for a couple days, I finally figured out the problem.
I thought I was behind a double NAT, but I wasn't. When I moved my EA9300 from my sole WiFi router, to behind the pfSense machine, I neglected to change some settings on my AT&T modem.</p>
<p>You see — dear reader— when I set up this network on my AT&T Modem, I had to enable Passthrough mode.
This, was set to a MAC Address, not an IP Address.
So when I was making sure to keep my IP network on the same 192.168.1.1/24, I thought that was all I needed.</p>
<p>Alas, there's a dropdown to pick the MAC address of the machine that everything passes through.
I can now access my bookmarks, notes, ebooks, and plex server!</p>
<p>Thanks to my friend Daniel (@sanitybit) - who was a great rubber duck and gave me some pointers when I was debugging, and also helped me find the hardware for the pfSense box!</p>
Emulation2023-05-26T00:00:00-04:002023-05-26T00:00:00-04:00tyreltag:tyrel.dev,2023-05-26:/blog/2023/05/emulation.html<p>I haven't had much time lately for blog posts, I've been dealing with bed time routines with my newborn, and once those are done, I get a few hours of alone time for computer things.</p>
<p>Lately I've been toying around with Amiga OS, FreeDOS, Windows 95, and Apple IIe things …</p><p>I haven't had much time lately for blog posts, I've been dealing with bed time routines with my newborn, and once those are done, I get a few hours of alone time for computer things.</p>
<p>Lately I've been toying around with Amiga OS, FreeDOS, Windows 95, and Apple IIe things.</p>
<p>I got a raspberry pi and installed Pimiga, got a fun set up and that was neat. I then installed Amiberry on my macbook, with some remote hard drive images on my samba share, and I have a consistent setup for Amiga on any machine in the house or on tailscale.</p>
<p>I then decided to install FreeDOS to a barely used Dell Vostro 1720 and install to that. It works great, I have WordStar, TurboC, and more installed and it's fun to get back to my roots in that way.</p>
<p>After that I decided to install Windows 95 with 86Box, did the same with remote hard disk images, and got that running. Been toying around in Visual Basic 6, Oh the memories!! I installed that so I could play Lego City, but having voodoo graphics errors I need to figure out before I can play.</p>
<p>I also ordered an Apple IIe emulator machine that runs on an Esp8266 from <a class="reference external" href="https://ct6502.org/product/mfa2-32emu/">CT6502</a> and it works great. So cool just tossing a disk image on the MicroSD card and loading it up. The downside to this is I can't figure out how to swap disks in realtime, so I can't play Ultima, or any multi disk games. I can however load .hdv files so if something comes with a hard disk image.</p>
<p>Not really much for a tech post, and nothing to share codewise, but thought I'd break some radio silence. I also imported my flying blog here, so I added the Flying category/tags.</p>
Neovim, Nix, Telescope, Tree-sitter, Mason2023-05-26T00:00:00-04:002023-05-26T00:00:00-04:00tyreltag:tyrel.dev,2023-05-26:/blog/2023/05/neovim-nix-telescope-tree-sitter-mason.html<p>I made a mistake with not reading CHANGELOGs for all my packages in Neovim this week. This sent me down a small rabbit hole trying to fix all the things.</p>
<p>What happened is I ran <tt class="docutils literal">:PackerUpdate</tt> which, pulls the latest version of Packer packages, good, updates! But... Telescope has a …</p><p>I made a mistake with not reading CHANGELOGs for all my packages in Neovim this week. This sent me down a small rabbit hole trying to fix all the things.</p>
<p>What happened is I ran <tt class="docutils literal">:PackerUpdate</tt> which, pulls the latest version of Packer packages, good, updates! But... Telescope has a new requirement on main branch that requires Neovim 0.9.0. The problem is that the latest NixPkgs for Neovim right now is 0.8.1. I ran to google, tried to set an overlay to use <tt class="docutils literal"><span class="pre">neovim-nightly</span></tt>, but that didn't work. If you recall in <a class="reference external" href="https://tyrel.dev/blog/2023/01/dotfiles-my-2022-way.html">Dotfiles - My 2022 Way</a> I'm not actually using NixOS so (please correct me if I'm wrong) overlays don't work. I tried specifing a version in my <tt class="docutils literal">programs.nix</tt>, I tried a bunch of other things at 1AM that I don't remember anymore.</p>
<p>Almost ripped it all out just to use Nvim 0.9.0 on this machine until NixPkgs has updated the repo. I decided that was the wrong idea, and went to sleep.</p>
<p>Tonight, I was able to figure out that in Packer, you can pin a commit!</p>
<p>It's clear in the docs, but I was trying to fix it at the Nix level, so I didn't immediately think of this, even though at my last job, Tidelift, I was doing package pinning analysis! Derp.</p>
<p>So, I added <tt class="docutils literal"><span class="pre">commit="c1a2af0"</span></tt> to my <tt class="docutils literal">use</tt> statment in <tt class="docutils literal">plugins.lua</tt> and Telescope started working again without a warning, or issue. <a class="reference external" href="https://gitea.tyrel.dev/tyrel/dotfiles/commit/eb32c2194aba355afec80e647bb4df31a3e40c73">Commit</a>.</p>
<p>That wasn't the only problem though. In my infinite wisdom, I followed some reddit posts that I won't link to, that suggested deleting <tt class="docutils literal"><span class="pre">~/.local/share/nvim</span></tt> and rerunning <tt class="docutils literal">PackerInstall</tt>, the problem there -- my tree-sitter configs are in my nix files.</p>
<p>This is an issue I need to look at later, but in my <a class="reference external" href="https://gitea.tyrel.dev/tyrel/dotfiles/src/commit/eb32c2194aba355afec80e647bb4df31a3e40c73/hosts/_common/programs.nix#L26-L32">programs.nix</a> file, I some reason have two entries of <tt class="docutils literal">plugins =</tt>. I had to uncomment the first one where I inject tree-sitter, and comment out the second setting. Then rebuild my nix flakes.</p>
<p>After that,I had to comment the first, uncomment the second, and rebuild with <tt class="docutils literal">withAllGrammars</tt> config.</p>
<p>This worked, I had my rust tree-sitter configs working, but was missing <tt class="docutils literal"><span class="pre">rust-analyzer</span></tt>.</p>
<p>That's in Mason! So I ran <tt class="docutils literal">:Mason</tt>, found <tt class="docutils literal"><span class="pre">rust-analyzer</span></tt> slapped that <tt class="docutils literal">i</tt> button, and I finally had my system back after 2 days of issues.</p>
<p>This was mostly a blogpost so I can reference back to it in the future, but hopefully at least _someone_ learns to pin your dang nvim Packages!</p>
Set Environment Variables with LastPass2023-05-26T00:00:00-04:002023-05-26T00:00:00-04:00tyreltag:tyrel.dev,2023-05-26:/blog/2023/05/set-environment-variables-with-lastpass.html<p>I have to use LastPass at work, and I store some API keys in there. Rather than copy/paste and have the actual api key on my terminal, I like to use <tt class="docutils literal">read <span class="pre">-rs</span> ENV_VAR_NAME</tt> to set environment variables, so they are hidden from scrollback.</p>
<p>Recently my coworker set something …</p><p>I have to use LastPass at work, and I store some API keys in there. Rather than copy/paste and have the actual api key on my terminal, I like to use <tt class="docutils literal">read <span class="pre">-rs</span> ENV_VAR_NAME</tt> to set environment variables, so they are hidden from scrollback.</p>
<p>Recently my coworker set something up that we need an environment variable set up for running some Terraform commands. I don't feel like pasting it in every time from LastPass, so I figured out how to set this up and automate it. I'm sure I've already talked a lot about how I love <tt class="docutils literal">direnv</tt> and I maintain a lot of different <tt class="docutils literal">.envrc</tt> files for work things. For my last team I had one per repo! Well <tt class="docutils literal">direnv</tt> comes to the rescue again.</p>
<ul class="simple">
<li>The first step is installing the <a class="reference external" href="https://github.com/lastpass/lastpass-cli">lastpass-cli</a>.</li>
<li>Then you need to set it up so you log in, how you do that is up to you. I have lpass checking status, and if it exits nonzero, then running lpass login again in my direnv.</li>
<li>After that you can use <tt class="docutils literal">lpass show</tt> and capture that in a variable to export your API key as an environment variable.</li>
</ul>
<div class="highlight"><pre><span></span>lpass status
<span class="k">if</span> <span class="o">[</span> <span class="nv">$?</span> -ne <span class="m">0</span> <span class="o">]</span><span class="p">;</span> <span class="k">then</span>
lpass login email@address.com
<span class="k">fi</span>
<span class="nb">export</span> <span class="nv">API_KEY</span><span class="o">=</span><span class="k">$(</span>lpass show <span class="s2">"Secret-Name-Here"</span> --password<span class="k">)</span>
</pre></div>
<p>Example <tt class="docutils literal">.envrc</tt> file.</p>
<p>I love automating things, and when a coworker says "oh no we have to do this"... I run to automate it!</p>
<div class="section" id="resources">
<h2>Resources</h2>
<ul class="simple">
<li>LastPass CLI <a class="reference external" href="https://github.com/lastpass/lastpass-cli">https://github.com/lastpass/lastpass-cli</a></li>
<li>Direnv <a class="reference external" href="https://github.com/direnv/direnv">https://github.com/direnv/direnv</a></li>
</ul>
</div>
Now Page2023-04-04T00:00:00-04:002023-04-04T00:00:00-04:00tyreltag:tyrel.dev,2023-04-04:/blog/2023/04/now-page.html<p>My friend Nik showed me his <tt class="docutils literal">/now/</tt> page, and I find that a cool idea.
This blog is more of a technical braindump than a log of my personal life, so I don't really talk about my life much.</p>
<p>I assume no one will visit <a class="reference external" href="https://tyrel.dev/now/">https://tyrel.dev/now</a> but …</p><p>My friend Nik showed me his <tt class="docutils literal">/now/</tt> page, and I find that a cool idea.
This blog is more of a technical braindump than a log of my personal life, so I don't really talk about my life much.</p>
<p>I assume no one will visit <a class="reference external" href="https://tyrel.dev/now/">https://tyrel.dev/now</a> but if you do, you will see a handcrafted update of my life and what has happend with me lately.</p>
<p>Thanks to Derek Sivers for setting up a network of <tt class="docutils literal">/now/</tt> pages! <a class="reference external" href="https://nownownow.com/about">https://nownownow.com/</a>.</p>
Brand New Server2023-03-28T00:00:00-04:002023-03-28T00:00:00-04:00tyreltag:tyrel.dev,2023-03-28:/blog/2023/03/brand-new-server.html<p>Per my last post, I did not succeed in cleaning off the malware.</p>
<p>That machine is dead and I am now running on a $4/mo Digital Ocean droplet - much less power than before, but I don't really need it anymore now that I have my own server at home …</p><p>Per my last post, I did not succeed in cleaning off the malware.</p>
<p>That machine is dead and I am now running on a $4/mo Digital Ocean droplet - much less power than before, but I don't really need it anymore now that I have my own server at home.</p>
<p>I am sad I don't have a Pixelfed anymore, maybe I'll relaunch it some day.</p>
I have been hit by Malware.2023-03-14T00:00:00-04:002023-03-14T00:00:00-04:00tyreltag:tyrel.dev,2023-03-14:/blog/2023/03/i-have-been-hit-by-malware.html<p>This morning I woke up to an email from DigitalOcean saying they have scanned my host and on port 8080 was botnet.</p>
<blockquote>
"We are writing to let you know that your Droplet tyrelsouza.com at 138.197.14.67 is a Command & Control server part of a botnet."</blockquote>
<p>UGH. This …</p><p>This morning I woke up to an email from DigitalOcean saying they have scanned my host and on port 8080 was botnet.</p>
<blockquote>
"We are writing to let you know that your Droplet tyrelsouza.com at 138.197.14.67 is a Command & Control server part of a botnet."</blockquote>
<p>UGH. This is not what I wanted to have to deal with today.</p>
<p>My first steps were to shut down all php things (the issue is with heysrv.php in EVERY directory). Then I ran <tt class="docutils literal">find / <span class="pre">-name</span> heysrv.php <span class="pre">-delete</span></tt> to delete all the files. After this, I decommissioned my pixelfed instance (rip pix.tyrel.dev) and disabled the startup scripts for that.</p>
<p>I then installed Simply Static on my one <a class="reference external" href="https://k3tas.radio/airband/">remaining wordpress</a> and turned that into a static collecton of html and related files. This elimiated two php instances. With one more remaining - my Mediawiki server.</p>
<p>I found an Export Pages link and now have an XML file of all my pages (only 78 or so) and can start working on putting this back to html notes on my joplin tool, instead of my wiki. Before I shut it down for good, I need to extract all the images, that's the only thing that's left to keep this knowledge secure.</p>
<p>Now the only thing left on this server is this static blog, pushed up from pelican. Everything else on this machine is just <tt class="docutils literal">index.php</tt> files that redirect around (example <a class="reference external" href="https://tyrel.bike">https://tyrel.bike/</a> to my Strava)</p>
<p>It's a bit sad I had to do this today, when I have other things I want to deal with - but DigitalOcean gave me a 24 hour ultimatum. I'll rebuild this server later, but for now, blog on!</p>
6502 NES Course by Pikuma2023-01-31T00:00:00-05:002023-01-31T00:00:00-05:00tyreltag:tyrel.dev,2023-01-31:/blog/2023/01/6502-nes-course-by-pikuma.html<p>As I mentioned in my <a class="reference external" href="https://tyrel.dev/blog/2022/12/advent-of-code-2022-end-of-year-updates.html">December</a> post I'm doing a 6502 course on <a class="reference external" href="https://pikuma.com/courses/nes-game-programming-tutorial">Pikuma.</a></p>
<p>I'm about 75% of the way done, and I think I need to circle back to some earlier stuff about how the PPU works, but it's super fun.</p>
<p>Over the holidays I was able to stop …</p><p>As I mentioned in my <a class="reference external" href="https://tyrel.dev/blog/2022/12/advent-of-code-2022-end-of-year-updates.html">December</a> post I'm doing a 6502 course on <a class="reference external" href="https://pikuma.com/courses/nes-game-programming-tutorial">Pikuma.</a></p>
<p>I'm about 75% of the way done, and I think I need to circle back to some earlier stuff about how the PPU works, but it's super fun.</p>
<p>Over the holidays I was able to stop at my father's and pick up my old NES.
I swapped out the ZIF connector for a new one, and cleaned up some contacts on the RCA ports, and it works great!
Once I found out that it was working - I played Sesame Street ABC 123, as that's the only one I had up in my office - I ordered an EverDrive N8.
That came last week.</p>
<p>The pictures are tall due to how I took them, so sorry I'll attach them at the end of the post.</p>
<p>Once I got the <a class="reference external" href="https://krikzz.com/our-products/legacy/edn8-72pin.html">EverDrive N8</a> I made sure it worked by playing a Battletoads ROM.
Battletoad tested - I then copied Atlantico.NES to my Everdrive.
Atlantico is the game that Gustavo is walking us through making in the current part of the course - not a real published game.
I loaded it up and HOLY COW - something I actually wrote in Assembly is running on real hardware.</p>
<p>If you want to watch the video, it's very simplistic at the 75% mark, this was before the Collisions chapter, and no sound yet.</p>
<p>The feeling of getting something running, locally, and seeing it working on screen, despite being a programmer for ~~20 years, is AMAZING.
Writing code that executes on the system you grew up playing the early 90's, wow.</p>
<p>I do wish the CRT TV my wife had was square, things get cut off on it.
I even got a remote, so I could try to fix that in the menu, alas, only picture option is brightness.
(Not that I realistically thought I could scale it, CRT Pixels are only Pixels.</p>
<hr class="docutils" />
<div class="section" id="picture-gallery">
<h2>Picture Gallery</h2>
<p>Trying out putting all the pictures at the end of my posts, if they are not directly related to paragraph content.</p>
<div class="figure">
<img alt="An NES Console with the flap opened up. On top is a Zapper gun with another controller, both with cables neatly wrapped up. On the floor, plugged in, in front of the NES, is another controller. The Power LED is on." src="https://tyrel.dev/blog/images/2023/01/NES_Console.png" />
<p class="caption">My NES plugged in and running.</p>
</div>
<div class="figure">
<img alt="A CRT TV screen with a game running on it. In 8bit graphics, the game is a ship sailing to the right, with planes flying to the left. Some missiles are shooting up." src="https://tyrel.dev/blog/images/2023/01/NES_Atlantico.png" />
<p class="caption">Atlantico game. There may be some interference with scanlines causing moire patterns, sorry.</p>
</div>
</div>
TurboC2 Setting Header and Include Locations2023-01-17T00:00:00-05:002023-01-17T00:00:00-05:00tyreltag:tyrel.dev,2023-01-17:/blog/2023/01/turboc2-setting-header-and-include-locations.html<p>This weekend I purchased a book from this seller on Craigslist - <a class="reference external" href="https://www.librarything.com/work/178384">"Advanced MS-DOS Programming: The Microsoft Guide for Assembly Language and C Programmers"</a> and before opening it, I wanted to get a C environment running.</p>
<p>I found a copy of <a class="reference external" href="https://archive.org/details/msdos_borland_turbo_c_2.01">TurboC2 on Archive.org</a> and tossed that into my DOS …</p><p>This weekend I purchased a book from this seller on Craigslist - <a class="reference external" href="https://www.librarything.com/work/178384">"Advanced MS-DOS Programming: The Microsoft Guide for Assembly Language and C Programmers"</a> and before opening it, I wanted to get a C environment running.</p>
<p>I found a copy of <a class="reference external" href="https://archive.org/details/msdos_borland_turbo_c_2.01">TurboC2 on Archive.org</a> and tossed that into my DOS Box install. I wrote a "Hello world" and pressed compile and it couldn't include "stdio.h", what the heck?</p>
<p>It seems that the Archive.org copy of Turbo C 2 ships with configuration that sets where the Includes and Lib directories to <tt class="docutils literal"><span class="pre">C:\TC</span></tt>. I keep all my programs in <tt class="docutils literal"><span class="pre">C:\PROGS</span></tt> so of course it can't find any header files for me!</p>
<p>To fix this you can either move your TurboC install to <tt class="docutils literal"><span class="pre">C:\TC</span></tt>, which feels wrong to me, or you could configure it in the options properly.</p>
<div class="section" id="steps">
<h2>Steps</h2>
<ul class="simple">
<li>Go to the Directories entry in the Options Menu.</li>
<li>You can see the default provided configuration directories</li>
<li>Fill out your appropriate directories for all three of the options.</li>
<li>Make sure all three are configured properly.</li>
<li>Then you can save your config, so you only have to do this once.</li>
</ul>
</div>
<div class="section" id="the-screenshot-way">
<h2>The Screenshot Way</h2>
<div class="figure">
<img alt="TurboC with the Options Menu selected and the Directories entry highlighted." src="https://tyrel.dev/blog/images/2023/01/dosbox_1_environment_menu.png" />
<p class="caption">Go to the Directories entry in the Options Menu.</p>
</div>
<div class="figure">
<img alt="TurboC with a pop up, showing entries of Include Directories, Library Directories, and Turbo C Directory configured to C:\TC\INCLUDE C:\TC\LIB and C:\TC" src="https://tyrel.dev/blog/images/2023/01/dosbox_2_directories.png" />
<p class="caption">You can see the default provided configuration directories</p>
</div>
<div class="figure">
<img alt="TurboC with a pop up, showing C:\PROGS\TC\INCLUDE" src="https://tyrel.dev/blog/images/2023/01/dosbox_3_directories_edit.png" />
<p class="caption">Fill out your appropriate directories for all three of the options.</p>
</div>
<div class="figure">
<img alt="TurboC with a pop up, showing entries of Include Directories, Library Directories, and Turbo C Directory configured to C:\PROGS\TC\INCLUDE C:\PROGS\TC\LIB and C:\PROGS\TC" src="https://tyrel.dev/blog/images/2023/01/dosbox_4_directories_filled.png" />
<p class="caption">Make sure all three are configured properly.</p>
</div>
<div class="figure">
<img alt="TurboC with a popup showing a Save Options location of C:\PROGS\TC\TCCONFIG.TC" src="https://tyrel.dev/blog/images/2023/01/dosbox_5_save_config.png" />
<p class="caption">Then you can save your config, so you only have to do this once.</p>
</div>
<p>Unfortunately - this file is a binary file. You can't just edit it in a text editor and carry on, so this is the only way I know how to change these locations.</p>
<p>Hopefully this helps anyone else who runs into any include errors with Borland Turbo C 2!</p>
</div>
Dotfiles - My 2022 Way2023-01-10T00:00:00-05:002023-01-10T00:00:00-05:00tyreltag:tyrel.dev,2023-01-10:/blog/2023/01/dotfiles-my-2022-way.html<p>New Year's eve eve, my main portable computer crashed. Rebooting to Safe mode, I could mount this MacBook's hard drive long enough to SCP the files over the network to my server, but I had to start that over twice because it fell asleep. I don't have access to rsync …</p><p>New Year's eve eve, my main portable computer crashed. Rebooting to Safe mode, I could mount this MacBook's hard drive long enough to SCP the files over the network to my server, but I had to start that over twice because it fell asleep. I don't have access to rsync in the "Network Recovery Mode" it seems - maybe I should look to see if next time I can install things, it's moot now.</p>
<p>I spent all January 1st evening working on learning how Nix works. Of course, I started with Nix on macOS (intel at least) so I had to also learn how nix-darwin works. I have my <a class="reference external" href="https://gitea.tyrel.dev/tyrel/dotfiles">dotfiles</a> set up to use Nix now, rather than an <cite>INSTALL.sh</cite> file that just sets a bunch of symlinks.</p>
<p>I played around for a litle bit with different structures, but what I ended up with by the end of the weekend was two bash scripts (still working on makefile, env vars are being funky) one for each operating system <cite>rebuild-macos.sh</cite> and <cite>rebuild-ubuntu.sh</cite>. For now I'm only Nixifying one macOS system and two Ubuntu boxes. Avoiding it on my work m1 Mac laptop, as I don't want to have to deal with managing <cite>synthetic.conf</cite> and mount points on a work managed computer. No idea how JAMF and Nix will fight.</p>
<p>My filetree currently looks like (trimmed out a host and a bunch of files in <cite>home/</cite>)</p>
<div class="highlight"><pre><span></span>.
├── home
│ ├── bin/
│ ├── config/
│ ├── gitconfig
│ ├── gitignore
│ ├── gpg/
│ ├── hushlogin
│ └── ssh/
├── hosts/
│ ├── _common/
│ │ ├── fonts.nix
│ │ ├── home.nix
│ │ ├── programs.nix
│ │ └── xdg.nix
│ ├── ts-tl-mbp/
│ │ ├── brew.nix
│ │ ├── default.nix
│ │ ├── flake.lock
│ │ ├── flake.nix
│ │ ├── home-manager.nix
│ │ └── home.nix
│ └── x1carbon-ubuntu/
│ ├── default.nix
│ ├── flake.lock
│ ├── flake.nix
│ ├── home-manager.nix
│ └── home.nix
├── rebuild-macos.sh
└── rebuild-ubuntu.sh
</pre></div>
<p>Under <cite>hosts/</cite> as you can see, I have a <a class="reference external" href="https://gitea.tyrel.dev/tyrel/dotfiles/src/branch/main/hosts/ts-tl-mbp/brew.nix">brew.nix</a> file in my macbook pro's folder. This is how I install anything in homebrew. In my <cite>flake.nix</cite> for my macos folder I am using <cite>home-manager</cite>, <cite>nix-darwin</cite>, and <cite>nixpkgs</cite>. I provide this <cite>brew.nix</cite> to my <cite>darwinConfigurations</cite> and it will install anything I put in my <cite>brew</cite> nixfile.</p>
<p>I also have a <cite>_common</cite> directory in my <cite>hosts</cite>, this is things that are to be installed on EVERY machine. Things such as <cite>bat</cite>, <cite>wget</cite>, <cite>fzf</cite>, <cite>fish</cite>, etc. along with common symlinks and xdg-config links. My nvim and fish configs are installed and managed this way. Rather than need to maintain a neovim config for every different system, in the nix way, I can just manage it all in <cite>_common/programs.nix</cite>.</p>
<p>This is not "The Standard Way" to organize things, if you want more inspiration, I took a lot from my friend <a class="reference external" href="https://github.com/shazow/nixfiles">Andrey's Nixfiles</a>. I was also chatting with him a bunch during this, so I was able to get three systems up and configured in a few days. After the first ubuntu box was configured, it was super easy to manage my others.</p>
<p>My <cite>home/</cite> directory is where I store my config files. My ssh public keys, my gpg public keys, my <cite>~/.<dotfiles></cite> and my <cite>~/.config/<files></cite>. This doesn't really need any explaination, but as an added benefit is I also decided to LUA-ify my nvim configs the same weekend. But that's a story for another time.</p>
<p>I am at this time choosing not to do NixOS - and relying on Ubuntu for managing my OS. I peeked into Andrey's files, and I really don't want to have to manage a full system configuration, drivers, etc. with Nix. Maybe for the future - when my Lenovo X1 Carbon dies and I need to reinstall that though.</p>
Advent of Code 2022 + End of Year Updates2022-12-16T00:00:00-05:002022-12-16T00:00:00-05:00tyreltag:tyrel.dev,2022-12-16:/blog/2022/12/advent-of-code-2022-end-of-year-updates.html<p>Advent of Code this year is kicking my butt so I haven't been doing any tech blogging really lately. If you want to follow my progress, I think I might be done as of day 15 - This one seems to be a traveling salesman/knapsack problem related. Here's my repo …</p><p>Advent of Code this year is kicking my butt so I haven't been doing any tech blogging really lately. If you want to follow my progress, I think I might be done as of day 15 - This one seems to be a traveling salesman/knapsack problem related. Here's my repo: <a class="reference external" href="https://gitea.tyrel.dev/tyrel/advent-of-code/src/branch/main/2022/python">https://gitea.tyrel.dev/tyrel/advent-of-code/src/branch/main/2022/python</a>.</p>
<p>I'm not on the computer that runs it, but I've been spending a lot of time playing with Apple's System7 in the BasiliskII emulator. Might have some fun projects with that coming up, but wanted to do some more learning before I start anything. So I have been going through a course on 6052 Assembly programming for the NES, and I'm about 73% done with that, it's really great!</p>
<p>It's By Gustavo Pezzi at <a class="reference external" href="https://pikuma.com/courses/nes-game-programming-tutorial">Pikuma</a>, if "oldschool" programming floats your boat then I definitely recommend it. It's all programming through making roms with CC65/CA65 assembler, and using FCEUX to see your results, super neat.</p>
<p>I've been picking up some more Go work at work. My current team is sort of disbanding so I'm going to be moving away from doing just Python. It's been a year since I've done Go stuff, since I left Tidelift, so I'm really rusty.</p>
<p>Speaking of Rust, I was trying to do Advent of code in Rust also, and made it TWO whole days in Rust. It's still on my bucket of stuff to learn, but my free time seems to be running out lately, and I have a lot of things on my plate to get done.</p>
Notary Public2022-12-06T00:00:00-05:002022-12-06T00:00:00-05:00tyreltag:tyrel.dev,2022-12-06:/blog/2022/12/notary-public.html<p>Short update today.</p>
<p>I kept meaning to put together a way for people to utilize my notary services, so I finally made <a class="reference external" href="https://tyrel.bike/notary">https://tyrel.bike/notary</a> to redirect here to my Notary Public page.</p>
<p>I am a North Carolina Notary Public, and love helping my neighbors use my services.</p>
<p>If …</p><p>Short update today.</p>
<p>I kept meaning to put together a way for people to utilize my notary services, so I finally made <a class="reference external" href="https://tyrel.bike/notary">https://tyrel.bike/notary</a> to redirect here to my Notary Public page.</p>
<p>I am a North Carolina Notary Public, and love helping my neighbors use my services.</p>
<p>If you're near me and need help - checkout <a class="reference external" href="/pages/notary-public.html">My Notary Page</a>.</p>
Coffee Gear2022-11-11T00:00:00-05:002022-11-11T00:00:00-05:00tyreltag:tyrel.dev,2022-11-11:/blog/2022/11/coffee-gear.html<p>I put this up on my wiki a bit ago when a friend asked for coffee recommendations. Hopefully you can enjoy it and learn about some coffee machines you don't know yet.</p>
<div class="section" id="what-i-drink">
<h2>What I drink</h2>
<div class="section" id="woke-living-coffee">
<h3>Woke Living Coffee</h3>
<p>My favorite coffee is from Pamela and Marcus at <a class="reference external" href="https://wokelivingcoffee.com/">https://wokelivingcoffee.com …</a></p></div></div><p>I put this up on my wiki a bit ago when a friend asked for coffee recommendations. Hopefully you can enjoy it and learn about some coffee machines you don't know yet.</p>
<div class="section" id="what-i-drink">
<h2>What I drink</h2>
<div class="section" id="woke-living-coffee">
<h3>Woke Living Coffee</h3>
<p>My favorite coffee is from Pamela and Marcus at <a class="reference external" href="https://wokelivingcoffee.com/">https://wokelivingcoffee.com/</a>. They are a couple of local to me roasters in Wake Forest, NC who have connections to a farm in La Dalia, Nicaragua. Not only do they sell great coffee, they are extremely nice and we visit them any chance we get at our local <a class="reference external" href="https://blackfarmersmkt.com/">Black Farmers Market</a>.</p>
</div>
</div>
<div class="section" id="gear">
<h2>Gear</h2>
<div class="section" id="grinders">
<h3>Grinders</h3>
<p>I prefer burr grinders, there's documented evidence that they are better, I won't get into that here.</p>
</div>
<div class="section" id="baratza">
<h3>Baratza</h3>
<p>I use a <a class="reference external" href="https://baratza.com/grinder/virtuoso/">Baratza Virtuoso</a> that I picked up refurbished. It works great! For drip coffee I will grind at step 28, for aeropress I will grind at 20, and for french press I will set to 30.</p>
</div>
<div class="section" id="hario">
<h3>Hario</h3>
<p>For travel I will bring my <a class="reference external" href="https://smile.amazon.com/gp/product/B001802PIQ/">Hario Skerton</a>, works great, super easy to clean. I usually don't change the grind setting while traveling so I don't complain about the annoying screw post to set it.</p>
</div>
</div>
<div class="section" id="brewing-machines">
<h2>Brewing Machines</h2>
<div class="section" id="technivorm">
<h3>Technivorm</h3>
<p>For my daily coffee, I have a <a class="reference external" href="https://us.moccamaster.com/collections/glass-carafe-brewers/products/kbgv-select">Moccamaster Technivorm</a>. My friend Andrey recommended it. It works extremely well and very consistent pours.</p>
</div>
<div class="section" id="chemex">
<h3>Chemex</h3>
<p>When I'm feeling fancy - or I'm trying a new coffee - I will break out my <a class="reference external" href="https://www.chemexcoffeemaker.com/eight-cup-classic-series-coffeemaker.html">Chemex</a>. I do a 1:16 ratio of beans to water. I use the <a class="reference external" href="https://www.chemexcoffeemaker.com/chemex-reg-bonded-filters-pre-folded-squares-natural.html">Brown Paper Chemex Filters</a>. I appreciate the bleached papers, but prefer unbleached.</p>
</div>
<div class="section" id="aeropress">
<h3>Aeropress</h3>
<p>For camping, I will bring my <a class="reference external" href="https://aeropress.com/">Aeropress</a>. It's plastic, lightweight, and to my experience it is indestructible for travel.</p>
</div>
<div class="section" id="french-press">
<h3>French Press</h3>
<p>When I roast my own coffee, I like to experience it in multiple brewing methods. I have a <a class="reference external" href="https://luvtocook.com/products/ak11683-xyb-y16">Bodum Bean French Press</a> I got over a decade ago as a gift that has worked great. This one has an o-ring to seal the pouring spout, so the temperature chamber inside doesn't leak - a feature I like.</p>
</div>
</div>
<div class="section" id="roasting-machines-software">
<h2>Roasting Machines & Software</h2>
<div class="section" id="freshroast-sr700">
<h3>FreshRoast SR700</h3>
<p>I have a <strong>glorified popcorn maker</strong> <a class="reference external" href="https://www.roastmasters.com/sr700.html">SR700</a> as a roaster. I'm not the biggest fan of it, the built in software is a mess, the manual buttons on it are a nightmare to use. It <strong>works</strong>, I can only get consistent coffee out of it if I use OpenRoast. It has a USB port so you can control it with software.</p>
</div>
<div class="section" id="openroast">
<h3>OpenRoast</h3>
<p>The <a class="reference external" href="https://github.com/Roastero/Openroast">OpenRoast</a> software is okay, but I don't have a temperature probe on my SR700, so I can only see "what is set" for temperature, and not get an accurate reading if I were using something like Artisan. I could set up a PID server on an arduino and plug into the usb port, but I feel at that rate I'd rather just buy a new roaster that works with better software. I do like OpenRoast is in Python so I can read and write the code.</p>
</div>
</div>
Neighbor's Water Heater Automation (part 1)2022-11-04T00:00:00-04:002022-11-04T00:00:00-04:00tyreltag:tyrel.dev,2022-11-04:/blog/2022/11/neighbors-water-heater-automation-part-1.html<div class="section" id="the-setting">
<h2>The Setting</h2>
<p>My neighbor has a Bosch tankless water heater he put in last year.
This water heater has one slight problem that when the power even blips a single second, it gets set back to its lowest temperature of 95°F.
My neighbor (we'll call him Frank for this …</p></div><div class="section" id="the-setting">
<h2>The Setting</h2>
<p>My neighbor has a Bosch tankless water heater he put in last year.
This water heater has one slight problem that when the power even blips a single second, it gets set back to its lowest temperature of 95°F.
My neighbor (we'll call him Frank for this post because Frank Tank is funny) Frank wants to set his heater to 120°F in his house.
The problem arises in that his water heater is under the house in his crawl space.</p>
<p>Without an easy way to set his temperature, he needs to crawl under his crawl space and turn a dial <em>EVERY. SINGLE. TIME.</em></p>
<p>He asked me if I knew of anything off the shelf that would help.
I did not.
So I said the only logical thing someone <a class="reference external" href="https://tyrel.website/wiki/HomeAssistant">like me</a> would have done.
"I can totally automate that!"</p>
</div>
<div class="section" id="the-lay-of-the-land">
<h2>The Lay Of The Land</h2>
<p>He has a <a class="reference external" href="https://www.prowaterheatersupply.com/PDFS/Bosch_Tronic_6000C_Pro_WH27_WH17_Installation_Manual.pdf">Bosch Tronic 6000C</a>, with what appears to be a rotary encoder knob to set the temperature.
I only spent a few minutes under his house while planning this and didn't think to any measuring of how many detents to rotate, or how long the dial took to rotate to 120°F, so my first pass of this project is done with estimations.</p>
<div class="figure">
<img alt="bosch heater with a temperature 7 segment LED set to 120F" src="https://tyrel.dev/blog/images/2022/11/04_heater.png" style="width: 920px;" />
</div>
</div>
<div class="section" id="project-time-round-1">
<h2>Project Time - Round 1!</h2>
<p>I have a few random servos laying around, and an NodeMCU ESP8266 module.
I figure these would be the perfect solution! ... note: was half right...</p>
<p>I found some code online by <a class="reference external" href="https://github.com/kumaraditya303">Kumar Aditya</a> that is for the <a class="reference external" href="https://github.com/kumaraditya303/ESP8266_SERVO_CONTROLLER">two items in my current parts list</a> (ESP8266 and SG90)</p>
<p>The Original code runs a web server on port 80, and runs a web page with some jQuery (wow it's been a while) to change the angle of the servo.
I realized this wasn't what I needed because my servos could only go 180° and I might need to go multiple rotations.
I found a youtube video on how to make a <a class="reference external" href="https://www.youtube.com/watch?v=zZGkkzMBL28">SG90 run infinite in either direction</a>, so I did those modifications.
I then modified the front end code a little bit.</p>
<p>The new code on the back end was actually exactly the same, even though the effect was slightly different.
It would run on port 80, listen at <tt class="docutils literal">/</tt> and <tt class="docutils literal">/angle</tt>, but the angle here was more of direction and speed (a vector?).
The way the servo was built, 160° was "Stop", higher than that was rotate clockwise, lower was rotate counter clockwise.</p>
<p>I put three buttons on my page that would be "Lower" (150), "STOP" (160), and "Higher" (170).
I then did some standard debouncing and disabling of buttons using setTimeout and such.</p>
<p>For a final touch I added in a range slider for "Time".
This held how many seconds after pressing Higher or Lower, that I would send the STOP command again.</p>
<p>This seemed to work relatively well, but I figure I should just use a stepper motor if I was attempting to emulate one this way.
I dug around in my closet and was able to find some parts.</p>
<div class="figure">
<img alt="blue case servo with a white arm, cables running off screen. sitting on a desk." src="https://tyrel.dev/blog/images/2022/11/04_servo.png" />
</div>
</div>
<div class="section" id="project-time-round-2">
<h2>Project Time - Round 2!</h2>
<p>I was able to rummage up a <a class="reference external" href="https://components101.com/motors/28byj-48-stepper-motor">28BYJ-48</a> stepper with control board, and a <a class="reference external" href="https://www.cafago.com/en/p-e8575.html">HW-131 power module</a>.</p>
<p>With these I needed a new library so I stripped the c++ code down to its basics, just getting me a server with the index page for the first pass.</p>
<p>On the Javascript side of things, I then decided I would add a temperature slider, from 90° to 120° (which writing this realize it should be from 95°... git commit...) with a confirmation button, and a small button to initialize down to 95°.</p>
<p>The initialize button would need to trigger an initialization where I rotate counter clockwise an appropriate amount of time (Length TBD) in order to force the rotary encoder dial to always start at a known state of 95.
The green submit button sends the new desired temperature as a post.</p>
<p>Server side, I was using a library called <a class="reference external" href="https://www.airspayce.com/mikem/arduino/AccelStepper/">AccelStepper</a>.
This I set some made up max speeds and steps per rotation, actual values TBD.</p>
<p>I added an endpoint called <tt class="docutils literal">/setTemperature</tt> that takes in a temperature and sets a local temperature variable.
From there, I calculate the temperature less 95, to find out how many degrees I need to increase by, for now I'm considering this rotations.</p>
<p>I then apply a multiplier (TBD also... there's a lot of these as you can see!) and call <tt class="docutils literal">stepper.moveTo()</tt> and it actually feels like it's pretty accurate.</p>
<p>The endpoint <tt class="docutils literal">/initialize</tt> runs <tt class="docutils literal">stepper.moveTo</tt> with ten rotations CCW, and then resets the "known location" back to zero (this also runs on power on for now).</p>
<div class="figure">
<img alt="webpage controls, title "Water Heater Control", a blue slider with a green button saying "Set Temperature: 90", and red "Initialize to 90" button" src="https://tyrel.dev/blog/images/2022/11/04_webpage.png" style="width: 920px;" />
</div>
<div class="figure">
<img alt="blue case servo with a white arm, cables running off screen. sitting on a desk." src="https://tyrel.dev/blog/images/2022/11/04_stepper.png" style="width: 920px;" />
</div>
</div>
<div class="section" id="in-action">
<h2>In Action</h2>
<p>The result of this second round of coding is a lot more that I expect to happen once I can finally get down beneath his house.
Frank will lose power, his water heater will reset to 95°F, the NodeMCU will reboot, and reinitialize itself.
Frank will then open his browser to the NodeMCU's server, set the desired temperature, and take warm showers.</p>
<p>Version 2 will come once I actually test EVERYTHING.
My first quesiton is if a rubber band on a lego tire with a servo wheel adaptor (<a class="reference external" href="https://www.thingiverse.com/thing:5594405">which I 3d modeled and printed...</a>) will work sufficiently.
Programming wise, I need to figure out how many steps is one degree. Is the rotary encoder one degree per detent? Is it a constant speed? Is it like an alarm clock where you can sometimes jump by 10?</p>
<p>Stay tuned to find out the exciting conclusion once I can go down below Frank's house.</p>
<div class="figure">
<img alt="blue case servo with a white arm, cables running off screen. sitting on a desk." src="https://tyrel.dev/blog/images/2022/11/04_stepper_wheel.png" style="width: 920px;" />
</div>
</div>
<div class="section" id="code">
<h2>Code</h2>
<p>The code is currently at <a class="reference external" href="https://gitea.tyrel.dev/tyrel/frank_tank.git">https://gitea.tyrel.dev/tyrel/frank_tank.git</a></p>
</div>
Office Meeting Sensor2022-11-04T00:00:00-04:002022-11-04T00:00:00-04:00tyreltag:tyrel.dev,2022-11-04:/blog/2022/11/office-meeting-sensor.html<div class="section" id="notes">
<h2>NOTES</h2>
<p>This post is ported over from my wiki, so the format isn't as storytelling as a blog post could be, but I wanted it here.</p>
</div>
<div class="section" id="bill-of-materials">
<h2>Bill of Materials</h2>
<ul class="simple">
<li><a class="reference external" href="https://www.adafruit.com/product/3708">Raspberry Pi Zero W H (WiFi + Headers)</a></li>
<li><a class="reference external" href="https://shop.pimoroni.com/products/blinkt">BlinkT LED Strip GPIO</a></li>
</ul>
</div>
<div class="section" id="home-assistant-parts">
<h2>Home Assistant Parts</h2>
<div class="section" id="third-party-plugin-requirements">
<h3>Third Party Plugin Requirements</h3>
<ul class="simple">
<li><a class="reference external" href="https://community.home-assistant.io/t/home-assistant-community-add-on-node-red/55023">Node-RED</a></li>
<li><a class="reference external" href="https://hacs.xyz/">HACS</a></li>
<li><a class="reference external" href="https://github.com/home-assistant/addons/blob/master/mosquitto/DOCS.md">Mosquitto …</a></li></ul></div></div><div class="section" id="notes">
<h2>NOTES</h2>
<p>This post is ported over from my wiki, so the format isn't as storytelling as a blog post could be, but I wanted it here.</p>
</div>
<div class="section" id="bill-of-materials">
<h2>Bill of Materials</h2>
<ul class="simple">
<li><a class="reference external" href="https://www.adafruit.com/product/3708">Raspberry Pi Zero W H (WiFi + Headers)</a></li>
<li><a class="reference external" href="https://shop.pimoroni.com/products/blinkt">BlinkT LED Strip GPIO</a></li>
</ul>
</div>
<div class="section" id="home-assistant-parts">
<h2>Home Assistant Parts</h2>
<div class="section" id="third-party-plugin-requirements">
<h3>Third Party Plugin Requirements</h3>
<ul class="simple">
<li><a class="reference external" href="https://community.home-assistant.io/t/home-assistant-community-add-on-node-red/55023">Node-RED</a></li>
<li><a class="reference external" href="https://hacs.xyz/">HACS</a></li>
<li><a class="reference external" href="https://github.com/home-assistant/addons/blob/master/mosquitto/DOCS.md">Mosquitto</a></li>
</ul>
</div>
<div class="section" id="zoom-plugin">
<h3>Zoom Plugin</h3>
<p>I followed the Read Me from <a class="reference external" href="https://github.com/raman325/ha-zoom-automation#installation-single-account-monitoring">https://github.com/raman325/ha-zoom-automation#installation-single-account-monitoring</a> and set up a Zoom Plugin for my account, that will detect if I am in a meeting or not.</p>
</div>
<div class="section" id="pi-zero">
<h3>Pi Zero</h3>
<p>I have a tiny project Enclosure box that I dremeled a hole for the GPIO pins in the cover and I then sandwich the Blinkt onto the Pi Zero with another dremeled hole running to the micro usb power, and that's it for hardware.</p>
<p>For software, I installed the python packages for Pimoroni and Blinkt, which came with a lovely set of sample projects. I deleted everything except the <a class="reference external" href="https://github.com/pimoroni/blinkt/blob/master/examples/mqtt.py">mqtt.py</a> file, which I then put my Mosquitto server settings.</p>
<p>I then added a new service in systemd to control the mqtt server</p>
<div class="highlight"><pre><span></span><span class="k">[Unit]</span><span class="w"></span>
<span class="na">Description</span><span class="o">=</span><span class="s">Meeting Indicator</span><span class="w"></span>
<span class="k">[Service]</span><span class="w"></span>
<span class="na">Type</span><span class="o">=</span><span class="s">simple</span><span class="w"></span>
<span class="na">ExecStart</span><span class="o">=</span><span class="s">/usr/bin/python2 /home/pi/mqtt.py</span><span class="w"></span>
<span class="na">WorkingDirectory</span><span class="o">=</span><span class="s">/home/pi/Pimoroni/blinkt/examples</span><span class="w"></span>
<span class="na">Restart</span><span class="o">=</span><span class="s">always</span><span class="w"></span>
<span class="na">RestartSec</span><span class="o">=</span><span class="s">2</span><span class="w"></span>
<span class="k">[Install]</span><span class="w"></span>
<span class="na">WantedBy</span><span class="o">=</span><span class="s">sysinit.target</span><span class="w"></span>
</pre></div>
<p>Pleased with the results, and testing by sending some messages over mqtt that changed the color, I then dove into Node-RED</p>
</div>
<div class="section" id="node-red-1">
<h3>Node-Red</h3>
<p>This is my first project using Node-RED, so I'm sure I could optimize better, but I have two entry points, one is from running HomeAssistant app on my mac, which gets me sensor data for my webcam, and the other is the aforementioned Zoom Presence plugin I created. These are <tt class="docutils literal">Events:State</tt> nodes.</p>
<p>When either of these are True, they call first my ceiling light to turn on, which next will then add a <tt class="docutils literal">msg.payload</tt> of</p>
<pre class="code literal-block">
rgb,0,255,0,0
rgb,1,255,0,0
rgb,2,255,0,0
rgb,3,255,0,0
rgb,4,255,0,0
rgb,5,255,0,0
rgb,6,255,0,0
rgb,7,255,0,0
</pre>
<p>as one string. This leads to a Split, which will in turn, emit a new MQTT message for each line (I split on <tt class="docutils literal">\n</tt>) and turn on all 8 LEDs as red. This is inefficient because I am still using the sample code for the blinkt which requires you to address each LED individually, my next phase I will remove the pin requirement and just have it send a color for all of them at once, one line.</p>
<p>When either of the sensors states are False, I then flow into a Time Range node, in which I check if it's between 9-5 or not. If it is, then I turn all the LEDs Green, and if it's outside 9-5 I just turn the LEDs off. I do not turn OFF the overhead light, in case it was already on. I don't care about the state enough.</p>
<p>I also intentionally trigger at the Office Hours node, which will inherently turn the Green on at 9:01am, and off at 5:01pm. As well as turn on Red for any long standing meeting times I have.</p>
</div>
<div class="section" id="images">
<h3>Images</h3>
<div class="figure">
<img alt="Screenshot of Nodered, with the flow of control for turning on the lights." src="https://tyrel.dev/blog/images/2022/11/04_nodered.png" />
</div>
<div class="figure">
<img alt="wall mounted enclosure with a strip of LED lights." src="https://tyrel.dev/blog/images/2022/11/04_lights.jpg" />
</div>
</div>
<div class="section" id="videos">
<h3>Videos</h3>
<ul class="simple">
<li><a class="reference external" href="https://i.imgur.com/kKIafiI.mp4">https://i.imgur.com/kKIafiI.mp4</a></li>
<li><a class="reference external" href="https://i.imgur.com/DLypDGD.mp4">https://i.imgur.com/DLypDGD.mp4</a></li>
</ul>
</div>
<div class="section" id="source">
<h3>Source</h3>
<p>Nodered configuration source json <a class="reference external" href="https://gist.github.com/tyrelsouza/c94329280848f0319d380cc750e995c2">https://gist.github.com/tyrelsouza/c94329280848f0319d380cc750e995c2</a></p>
</div>
</div>
Comparing Go GORM and SQLX2022-10-17T00:00:00-04:002022-10-17T00:00:00-04:00tyreltag:tyrel.dev,2022-10-17:/blog/2022/10/comparing-go-gorm-and-sqlx.html<div class="section" id="django-orm-my-history">
<h2>Django ORM - My History</h2>
<p>I'm not the best SQL developer, I know it's one of my weak points.
My history is I did php/mysql from the early 2000s until college.
In college I didn't really focus on the Database courses, the class selection didn't have many database course.
The …</p></div><div class="section" id="django-orm-my-history">
<h2>Django ORM - My History</h2>
<p>I'm not the best SQL developer, I know it's one of my weak points.
My history is I did php/mysql from the early 2000s until college.
In college I didn't really focus on the Database courses, the class selection didn't have many database course.
The one Data Warehousing course I had available, I missed out on because I was in England doing a study abroad program that semester.
My first job out of college was a Python/Django company - and that directed my next eight years of work.</p>
<p>Django, if you are unaware, is a MVC framework that ships with a really great ORM.
You can do about 95% of your database queries automatically by using the ORM.</p>
<div class="highlight"><pre><span></span><span class="n">entry</span><span class="p">,</span> <span class="n">created</span> <span class="o">=</span> <span class="n">Entry</span><span class="o">.</span><span class="n">objects</span><span class="o">.</span><span class="n">get_or_create</span><span class="p">(</span><span class="n">headline</span><span class="o">=</span><span class="s2">"blah blah blah"</span><span class="p">)</span>
</pre></div>
<div class="highlight"><pre><span></span><span class="n">q</span> <span class="o">=</span> <span class="n">Entry</span><span class="o">.</span><span class="n">objects</span><span class="o">.</span><span class="n">filter</span><span class="p">(</span><span class="n">headline__startswith</span><span class="o">=</span><span class="s2">"What"</span><span class="p">)</span>
<span class="n">q</span> <span class="o">=</span> <span class="n">q</span><span class="o">.</span><span class="n">filter</span><span class="p">(</span><span class="n">pub_date__lte</span><span class="o">=</span><span class="n">datetime</span><span class="o">.</span><span class="n">date</span><span class="o">.</span><span class="n">today</span><span class="p">())</span>
<span class="n">q</span> <span class="o">=</span> <span class="n">q</span><span class="o">.</span><span class="n">exclude</span><span class="p">(</span><span class="n">body_text__icontains</span><span class="o">=</span><span class="s2">"food"</span><span class="p">)</span>
</pre></div>
<p>Above are some samples from the DjangoDocs.
But enough about Django.</p>
</div>
<div class="section" id="my-requirements">
<h2>My Requirements</h2>
<p>Recently at my job I was given a little bit of leeway on a project.
My team is sort of dissolving and merging in with another team who already does Go.
My Go history is building a CLI tool for the two last years of my <a class="reference external" href="https://read.cv/tyrel/bl4Gp9PYIvGh54KSuhjr">previous job.</a>
I had never directly interacted with a database from Go yet.
I wanted to spin up a REST API (I chose Go+Gin for that based on forty five seconds of Googling) and talk to a database.</p>
</div>
<div class="section" id="gorm">
<h2>GORM</h2>
<p>Being that I come from the Django (and a few years of ActiveRecord) land, I reached immediately for an ORM, I chose GORM.
If you want to skip directly to the source, check out <a class="reference external" href="https://gitea.tyrel.dev/tyrel/go-webservice-gin">https://gitea.tyrel.dev/tyrel/go-webservice-gin</a>.
Full design disclosure: I followed a couple of blog posts in order to develop this, so it is in the form explictly decided upon by the <a class="reference external" href="https://blog.logrocket.com/how-to-build-a-rest-api-with-golang-using-gin-and-gorm/">logrocket blog post</a> and may not be the most efficient way to organize the module.</p>
<p>In order to instantiate a model definition, it's pretty easy.
What I did is make a new package called <tt class="docutils literal">models</tt> and inside made a file for my Album.</p>
<div class="highlight"><pre><span></span><span class="kd">type</span><span class="w"> </span><span class="nx">Album</span><span class="w"> </span><span class="kd">struct</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="nx">ID</span><span class="w"> </span><span class="kt">string</span><span class="w"> </span><span class="s">`json:"id" gorm:"primary_key"`</span><span class="w"></span>
<span class="w"> </span><span class="nx">Title</span><span class="w"> </span><span class="kt">string</span><span class="w"> </span><span class="s">`json:"title"`</span><span class="w"></span>
<span class="w"> </span><span class="nx">Artist</span><span class="w"> </span><span class="kt">string</span><span class="w"> </span><span class="s">`json:"artist"`</span><span class="w"></span>
<span class="w"> </span><span class="nx">Price</span><span class="w"> </span><span class="kt">float64</span><span class="w"> </span><span class="s">`json:"price"`</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</pre></div>
<p>This tracks with how I would do the same for any other kind of struct in Go, so this wasn't too difficult to do.
What was kind of annoying was that I had to also make some structs for Creating the album and Updating the Album, this felt like duplicated effort that might have been better served with some composition.</p>
<p>I would have structured the controllers differently, but that may be a Gin thing and how it takes points to functions, vs pointers to receivers on a struct.
Not specific to GORM.
Each of the controller functions were bound to a <tt class="docutils literal">gin.Context</tt> pointer, rather than receivers on an AlbumController struct.</p>
<p>The <tt class="docutils literal">FindAlbum</tt> controller was simple:</p>
<div class="highlight"><pre><span></span><span class="kd">func</span><span class="w"> </span><span class="nx">FindAlbum</span><span class="p">(</span><span class="nx">c</span><span class="w"> </span><span class="o">*</span><span class="nx">gin</span><span class="p">.</span><span class="nx">Context</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nx">album</span><span class="w"> </span><span class="nx">models</span><span class="p">.</span><span class="nx">Album</span><span class="w"></span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">models</span><span class="p">.</span><span class="nx">DB</span><span class="p">.</span><span class="nx">Where</span><span class="p">(</span><span class="s">"id = ?"</span><span class="p">,</span><span class="w"> </span><span class="nx">c</span><span class="p">.</span><span class="nx">Param</span><span class="p">(</span><span class="s">"id"</span><span class="p">)).</span><span class="nx">First</span><span class="p">(</span><span class="o">&</span><span class="nx">album</span><span class="p">).</span><span class="nx">Error</span><span class="p">;</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="nx">c</span><span class="p">.</span><span class="nx">JSON</span><span class="p">(</span><span class="nx">http</span><span class="p">.</span><span class="nx">StatusBadRequest</span><span class="p">,</span><span class="w"> </span><span class="nx">gin</span><span class="p">.</span><span class="nx">H</span><span class="p">{</span><span class="s">"error"</span><span class="p">:</span><span class="w"> </span><span class="s">"Record not found!"</span><span class="p">})</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="nx">c</span><span class="p">.</span><span class="nx">JSON</span><span class="p">(</span><span class="nx">http</span><span class="p">.</span><span class="nx">StatusOK</span><span class="p">,</span><span class="w"> </span><span class="nx">gin</span><span class="p">.</span><span class="nx">H</span><span class="p">{</span><span class="s">"data"</span><span class="p">:</span><span class="w"> </span><span class="nx">album</span><span class="p">})</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</pre></div>
<p>Which will take in a <tt class="docutils literal">/:id</tt> path parameter, and the GORM part of this is the third line there.</p>
<div class="highlight"><pre><span></span><span class="nx">models</span><span class="p">.</span><span class="nx">DB</span><span class="p">.</span><span class="nx">Where</span><span class="p">(</span><span class="s">"id = ?"</span><span class="p">,</span><span class="w"> </span><span class="nx">c</span><span class="p">.</span><span class="nx">Param</span><span class="p">(</span><span class="s">"id"</span><span class="p">)).</span><span class="nx">First</span><span class="p">(</span><span class="o">&</span><span class="nx">album</span><span class="p">).</span><span class="nx">Error</span><span class="w"></span>
</pre></div>
<p>To run a select, you chain a <tt class="docutils literal">Where</tt> on the DB (which is the connection here) and it will build up your query.
If you want to do joins, this is where you would chain <tt class="docutils literal">.Joins</tt> etc...
You then pass in your album variable to bind the result to the struct, and if there's no errors, you continue on with the bound variable.
Error handling is standard Go logic, <tt class="docutils literal">if err != nil</tt> etc and then pass that into your API of choice (Gin here) error handler.</p>
<p>This was really easy to set up, and if you want to get a slice back you just use <tt class="docutils literal">DB.Find</tt> instead, and bind to a slice of those structs.</p>
<div class="highlight"><pre><span></span><span class="kd">var</span><span class="w"> </span><span class="nx">albums</span><span class="w"> </span><span class="p">[]</span><span class="nx">models</span><span class="p">.</span><span class="nx">Album</span><span class="w"></span>
<span class="nx">models</span><span class="p">.</span><span class="nx">DB</span><span class="p">.</span><span class="nx">Find</span><span class="p">(</span><span class="o">&</span><span class="nx">albums</span><span class="p">)</span><span class="w"></span>
</pre></div>
</div>
<div class="section" id="sqlx">
<h2>SQLX</h2>
<p>SQLX is a bit different, as it's not an ORM, it's extensions in Go to query with SQL, but still a good pattern for abstracting away your SQL to some dark corner of the app and not inline everywhere.
For this I didn't follow someone's blog post — I had a grasp on how to use Gin pretty okay by now and essentially copied someone elses repo with my existing model.
<a class="reference external" href="https://github.com/wetterj/gin-sqlx-crud">gin-sqlx-crud</a>.</p>
<p>This one set up a bit wider of a structure, with deeper nested packages.
Inside my <tt class="docutils literal">internal</tt> folder there's <tt class="docutils literal">controllers</tt>, <tt class="docutils literal">forms</tt>, <tt class="docutils literal">models/sql</tt>, and <tt class="docutils literal">server</tt>.
I'll only bother describing the <tt class="docutils literal">models</tt> package here, as thats the SQLX part of it.</p>
<p>In the <tt class="docutils literal">models/album.go</tt> file, there's your standard struct here, but this time its bound to <tt class="docutils literal">db</tt> not <tt class="docutils literal">json</tt>, I didn't look too deep yet but I presume that also forces the columns to set the json name.</p>
<div class="highlight"><pre><span></span><span class="kd">type</span><span class="w"> </span><span class="nx">Album</span><span class="w"> </span><span class="kd">struct</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="nx">ID</span><span class="w"> </span><span class="kt">int64</span><span class="w"> </span><span class="s">`db:"id"`</span><span class="w"></span>
<span class="w"> </span><span class="nx">Title</span><span class="w"> </span><span class="kt">string</span><span class="w"> </span><span class="s">`db:"title"`</span><span class="w"></span>
<span class="w"> </span><span class="nx">Artist</span><span class="w"> </span><span class="kt">string</span><span class="w"> </span><span class="s">`db:"artist"`</span><span class="w"></span>
<span class="w"> </span><span class="nx">Price</span><span class="w"> </span><span class="kt">float64</span><span class="w"> </span><span class="s">`db:"price"`</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</pre></div>
<p>An interface to make a service, and a receiver are made for applying the <tt class="docutils literal">CreateAlbum</tt> form (in another package) which sets the form name and json name in it.</p>
<div class="highlight"><pre><span></span><span class="kd">func</span><span class="w"> </span><span class="p">(</span><span class="nx">a</span><span class="w"> </span><span class="o">*</span><span class="nx">Album</span><span class="p">)</span><span class="w"> </span><span class="nx">ApplyForm</span><span class="p">(</span><span class="nx">form</span><span class="w"> </span><span class="o">*</span><span class="nx">forms</span><span class="p">.</span><span class="nx">CreateAlbum</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="nx">a</span><span class="p">.</span><span class="nx">ID</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="o">*</span><span class="nx">form</span><span class="p">.</span><span class="nx">ID</span><span class="w"></span>
<span class="w"> </span><span class="nx">a</span><span class="p">.</span><span class="nx">Title</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="o">*</span><span class="nx">form</span><span class="p">.</span><span class="nx">Title</span><span class="w"></span>
<span class="w"> </span><span class="nx">a</span><span class="p">.</span><span class="nx">Artist</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="o">*</span><span class="nx">form</span><span class="p">.</span><span class="nx">Artist</span><span class="w"></span>
<span class="w"> </span><span class="nx">a</span><span class="p">.</span><span class="nx">Price</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="o">*</span><span class="nx">form</span><span class="p">.</span><span class="nx">Price</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</pre></div>
<p>So there's the receiver action I wanted at least!</p>
<p>Nested inside the <tt class="docutils literal">models/sql/album.go</tt> file and package, is all of the Receiver code for the service.
I'll just comment the smallest one, as that gets my point across.
Here is where the main part of GORM/SQLX differ - raw SQL shows up.</p>
<div class="highlight"><pre><span></span><span class="kd">func</span><span class="w"> </span><span class="p">(</span><span class="nx">s</span><span class="w"> </span><span class="o">*</span><span class="nx">AlbumService</span><span class="p">)</span><span class="w"> </span><span class="nx">GetAll</span><span class="p">()</span><span class="w"> </span><span class="p">(</span><span class="o">*</span><span class="p">[]</span><span class="nx">models2</span><span class="p">.</span><span class="nx">Album</span><span class="p">,</span><span class="w"> </span><span class="kt">error</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="nx">q</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="s">`SELECT * FROM albums;`</span><span class="w"></span>
<span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nx">output</span><span class="w"> </span><span class="p">[]</span><span class="nx">models2</span><span class="p">.</span><span class="nx">Album</span><span class="w"></span>
<span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">s</span><span class="p">.</span><span class="nx">conn</span><span class="p">.</span><span class="nx">Select</span><span class="p">(</span><span class="o">&</span><span class="nx">output</span><span class="p">,</span><span class="w"> </span><span class="nx">q</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="c1">// Replace the SQL error with our own error type.</span><span class="w"></span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="nx">sql</span><span class="p">.</span><span class="nx">ErrNoRows</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="kc">nil</span><span class="p">,</span><span class="w"> </span><span class="nx">models2</span><span class="p">.</span><span class="nx">ErrNotFound</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="kc">nil</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="o">&</span><span class="nx">output</span><span class="p">,</span><span class="w"> </span><span class="kc">nil</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</pre></div>
<p>This will return a slice of Albums - but if you notice on the second line, you have to write your own queries.
A little bit more in control of how things happen, with a <tt class="docutils literal">SELECT * ...</tt> vs the gorm <tt class="docutils literal">DB.Find</tt> style.</p>
<p>To me this feels more like using <tt class="docutils literal">pymysql</tt>, in fact its a very similar process.
(SEE NOTE BELOW)
You use the <tt class="docutils literal">service.connection.Get</tt> and pass in what you want the output bound to, the string query, and any parameters.
This feels kind of backwards to me - I'd much rather have the order be: query, bound, parameters, but thats what they decided for their order.</p>
</div>
<div class="section" id="conclusion">
<h2>Conclusion</h2>
<p>Overall, both were pretty easy to set up for one model.
Given the choice I would look at who the source code is written for.
If you're someone who knows a lot of SQL, then SQLX is fine.
If you like abstractions, and more of a "Code as Query" style, then GORM is probably the best of these two options.</p>
<p>I will point out that GORM does more than just "query and insert" there is migration, logging, locking, dry run mode, and more.
If you want to have a full fledged system, that might be a little heavy, then GORM is the right choice.</p>
<p>SQLX is great if what you care about is marshalling, and a very quick integration into any existing codebase.</p>
</div>
<div class="section" id="repositories">
<h2>Repositories</h2>
<ul class="simple">
<li><a class="reference external" href="https://gitea.tyrel.dev/tyrel/go-webservice-gin">Go, Gin, Gorm</a></li>
<li><a class="reference external" href="https://gitea.tyrel.dev/tyrel/go-webservice-gin-sqlx">Go, Gin, sqlx</a></li>
</ul>
</div>
<div class="section" id="notes">
<h2>Notes</h2>
<p>I sent this blog post to my friend <a class="reference external" href="https://shazow.net/">Andrey</a> and he mentioned that I was incorrect with my comparision of sqlx to pymysql.
To put it in a python metaphor, "sqlx is like using urllib3, gorm is like using something that generates a bunch of requests code for you. Using pymysql is like using tcp to do a REST request."
Sqlx is more akin to <a class="reference external" href="https://docs.sqlalchemy.org/en/14/core/">SqlAlchemy core</a> vs using <a class="reference external" href="https://docs.sqlalchemy.org/en/14/orm/">SqlAlchemy orm</a>.
Sqlx is just some slight extensions over <tt class="docutils literal">database/sql</tt>.
As the sort of equivalent to <tt class="docutils literal">pymysql</tt> in Go is <tt class="docutils literal">database/sql/driver</tt> from the stdlib.</p>
</div>
New Blog - Pelican!2022-10-16T23:30:00-04:002022-10-16T23:30:00-04:00tyreltag:tyrel.dev,2022-10-16:/blog/2022/10/pelican-new-blog.html<p>If you have read the previous post, and then looked at this one, there are a LOT of changes that happened.
I was recently exploited and had <tt class="docutils literal">heysrv.php</tt> files everywhere, so I have decided to forego wordpress for now.
I am now using <a class="reference external" href="https://getpelican.com">Pelican</a>!</p>
<p>It's very sleek, and only …</p><p>If you have read the previous post, and then looked at this one, there are a LOT of changes that happened.
I was recently exploited and had <tt class="docutils literal">heysrv.php</tt> files everywhere, so I have decided to forego wordpress for now.
I am now using <a class="reference external" href="https://getpelican.com">Pelican</a>!</p>
<p>It's very sleek, and only took me a few hours to port my Wordpress export to Pelican reStructuredText format.</p>
<p>All I have to do is run <tt class="docutils literal">invoke publish</tt> and it will be on the server.
No PHP, no database.
All files properly in their right places.</p>
<p>It comes with your standard blogging experience: Categories, Tags, RSS/Atom feeds, etc.
You need to set up Disqus — which I probably won't — in order to get comments though.</p>
<p>I'm pleased with it.
I have posts go under YYYY/MM/slug.html files, which I like for organization.
Posting images is easy, I just toss it under <tt class="docutils literal">content/images/YYYY/MM/</tt> with date for organization.</p>
Scrollbar Colors2022-10-13T12:07:00-04:002022-10-13T12:07:00-04:00tyreltag:tyrel.dev,2022-10-13:/blog/2022/10/scrollbar-colors.html<p>Was talking to someone about CSS Nostalgia and "back in my day" when scrollbar colors came up.</p>
<div class="highlight"><pre><span></span><span class="c">/* For Chromium based browsers */</span><span class="w"></span>
<span class="p">::</span><span class="nd">-webkit-scrollbar</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="k">background</span><span class="p">:</span><span class="w"> </span><span class="mh">#2A365F</span><span class="p">;</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
<span class="p">::</span><span class="nd">-webkit-scrollbar-thumb</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="k">background</span><span class="p">:</span><span class="w"> </span><span class="mh">#514763</span><span class="p">;</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
<span class="c">/* For Firefox */</span><span class="w"></span>
<span class="nt">html</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="k">scrollbar-color</span><span class="p">:</span><span class="w"> </span><span class="mh">#514763</span><span class="w"> </span><span class="mh">#2A365F</span><span class="p">;</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</pre></div>
<p>Firefox and Chrome have different selectors, so in order to support the majority of browsers, you need …</p><p>Was talking to someone about CSS Nostalgia and "back in my day" when scrollbar colors came up.</p>
<div class="highlight"><pre><span></span><span class="c">/* For Chromium based browsers */</span><span class="w"></span>
<span class="p">::</span><span class="nd">-webkit-scrollbar</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="k">background</span><span class="p">:</span><span class="w"> </span><span class="mh">#2A365F</span><span class="p">;</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
<span class="p">::</span><span class="nd">-webkit-scrollbar-thumb</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="k">background</span><span class="p">:</span><span class="w"> </span><span class="mh">#514763</span><span class="p">;</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
<span class="c">/* For Firefox */</span><span class="w"></span>
<span class="nt">html</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="k">scrollbar-color</span><span class="p">:</span><span class="w"> </span><span class="mh">#514763</span><span class="w"> </span><span class="mh">#2A365F</span><span class="p">;</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</pre></div>
<p>Firefox and Chrome have different selectors, so in order to support the majority of browsers, you need both.</p>
<div class="figure">
<img alt="Chrome with a blue/purple scrollbar" src="https://tyrel.dev/blog/images/2022/10/scrollbar-chrome.png" style="width: 940px; height: 688px;" />
<p class="caption">Chrome with a blue/purple scrollbar</p>
</div>
<div class="figure">
<img alt="Safari with a blue/purple scrollbar" src="https://tyrel.dev/blog/images/2022/10/scrollbar-safari.png" />
<p class="caption">Safari with a blue/purple scrollbar</p>
</div>
<div class="figure">
<img alt="Firefox with a blue/purple scrollbar" src="https://tyrel.dev/blog/images/2022/10/scrollbar-firefox.png" />
<p class="caption">Firefox with a blue/purple scrollbar</p>
</div>
2016 Monitoring a CO2 tank in a Lab with a raspberry pi2022-06-02T16:54:00-04:002022-06-02T16:54:00-04:00tyreltag:tyrel.dev,2022-06-02:/blog/2022/06/monitoring-a-co2-tank-in-a-lab-with-a-raspberry-pi.html<p>This was written in 2017, but I found a copy again, I wanted to post it again.</p>
<div class="section" id="the-story">
<h2>The Story</h2>
<p>For a few months last year, I lived around the block from work. I would sometimes stop in on the weekends and pick up stuff I forgot at my desk. One …</p></div><p>This was written in 2017, but I found a copy again, I wanted to post it again.</p>
<div class="section" id="the-story">
<h2>The Story</h2>
<p>For a few months last year, I lived around the block from work. I would sometimes stop in on the weekends and pick up stuff I forgot at my desk. One day the power went out at my apartment and I figure I would check in at work and see if there were any problems. I messaged our Lab Safety Manager on slack to say "hey the power went out, and I am at the office. Is there anything you'd like me to check?". He said he hadn't even gotten the alarm email/pages yet, so if I would check out in the lab and send him a picture of the CO2 tanks to make sure that nothing with the power outage compromised those. Once I had procured access to the BL2 lab on my building badge, I made my way out back and took a lovely picture of the tanks, everything was fine.</p>
<p>The following week, in my one on one meeting with my manager, I mentioned what happened and she and I discussed the event. It clearly isn't sustainable sending someone in any time there was a power outage if we didn't need to, but the lab equipment doesn't have any monitoring ports.</p>
<p>Operation Lab Cam was born. I decided to put together a prototype of a Raspberry Pi 3 with a camera module and play around with getting a way to monitor the display on the tanks. After a few months of not touching the project, I dug into it in a downtime day again. The result is now we have an automated camera box that will take a picture once a minute and display it on an auto refreshing web page. There are many professional products out there that do exactly this, but I wanted something that has the ability to be upgraded in the future.</p>
<div class="section" id="summary-of-the-technical-details">
<h3>Summary of the Technical Details</h3>
<p>Currently the entire process is managed by one bash script, which is a little clunky, but it's livable. The implementation of the script goes a little like:</p>
<ol class="arabic simple">
<li>Take a picture to a temporary location.</li>
<li>Add a graphical time stamp.</li>
<li>Copy that image to both the currently served image, and a timestamped filename backup.</li>
</ol>
<p>The web page that serves the image is just a simple web page that shows the image, and refreshes once every thirty seconds.</p>
</div>
<div class="section" id="the-gritty-technical-details">
<h3>The Gritty Technical Details</h3>
<p>The program I'm using to take pictures is the <strong>raspistill</strong> program. If I had written my script to just call <strong>raspistill</strong> every time I wanted a picture taken, it would have potentially taken a lot longer to save the images. This happens because it needs to meter the image every time, which adds up. The solution is Signal mode and turning raspistill into a daemon. If you enable signal mode, any time you send a SIGUSR1 to the process, the backgrounded process will then take the image.</p>
<p>Instead if setting up a service with <em>systemd</em>, I have a small bash script. At the beginning, I run a <strong>ps aux</strong> and check if raspistill is running, if it's not, I start up a new instance of raspistill with the appropriate options and background it. The next time this script runs, it will detect that raspistill is running and be almost a no-op.</p>
<p>After this, I send a SIGUSR1 (kill -10) to take the picture which is then saved, un-timestamped. Next up I call imagemagick's convert on this image, I crop out the center (so I couldn't use <strong>raspistill's</strong> "-a 12" option) because all I care about is a 500x700 pixel region.</p>
<p>This is then copied to the image that is served by the web page, and also backed up in a directory that nginx will listen to.</p>
<div class="figure">
<img alt="Leds on a CO2 tank" src="https://tyrel.dev/blog/images/2022/06/bl2cam-leds.jpg" />
</div>
</div>
</div>
Writing an EPUB parser. Part 12022-06-01T01:41:00-04:002022-06-01T01:41:00-04:00tyreltag:tyrel.dev,2022-06-01:/blog/2022/06/writing-an-epub-parser-part-1.html<div class="section" id="parsing-epubs">
<h2>Parsing Epubs</h2>
<p>Recently I've become frustrated with the experience of reading books on my Kindle Paperwhite. The swipe features, really bother me. I really like MoonReader on Android, but reading on my phone isn't always pleasing. This lead me to look into other hardware. I've been eyeing the BOOX company …</p></div><div class="section" id="parsing-epubs">
<h2>Parsing Epubs</h2>
<p>Recently I've become frustrated with the experience of reading books on my Kindle Paperwhite. The swipe features, really bother me. I really like MoonReader on Android, but reading on my phone isn't always pleasing. This lead me to look into other hardware. I've been eyeing the BOOX company a while ago, but definitely considering some of their new offerings some time. Until the time I can afford the money to splurge on a new ebook reader, I've decided to start a new project, making my own ebook reader tools!</p>
<p>I'm starting with EPUBs, as this is one of the easiest to work with. At its core, an EPUB is a zip file with the <tt class="docutils literal">.epub</tt> extension instead of <tt class="docutils literal">.epub</tt> with many individual XHTML file chapters inside it. You can read more of how they're structured yourself over at <a class="reference external" href="https://docs.fileformat.com/ebook/epub/">FILEFORMAT</a>.</p>
<p>The tool I've chosen for reading EPUBs is the Python library <a class="reference external" href="https://pypi.org/project/EbookLib/">ebooklib</a>. This seemed to be a nice lightweight library for reading EPUBs. I also used <a class="reference external" href="https://pypi.org/project/dearpygui/">DearPyGUI</a> for showing this to the screen, because I figured why not, I like GUI libraries.</p>
<p>My first task was to find an EPUB file, so I downloaded one from my calibre server. I convert all my ebook files to <tt class="docutils literal">.epub</tt> and <tt class="docutils literal">.mobi</tt> on my calibre server so I can access them anywhere I can read my OPDS feed. I chose Throne of Glass (abbreviating to <tt class="docutils literal">TOG.epub</tt> for rest of post). Loading I launched Python, and ran</p>
<div class="highlight"><pre><span></span><span class="go">>>> from ebooklib import epub</span>
<span class="go">>>> print(book := epub.read_epub("TOG.epub")</span>
</pre></div>
<p>This returned me a <tt class="docutils literal"><ebooklib.epub.EpubBook <span class="pre">object...></span></tt> , seeing I had an EpubBook I ran a <tt class="docutils literal">dir(book)</tt> and found the properties available to me</p>
<div class="highlight"><pre><span></span><span class="p">[</span><span class="s1">'add_author'</span><span class="p">,</span> <span class="s1">'add_item'</span><span class="p">,</span> <span class="s1">'add_metadata'</span><span class="p">,</span> <span class="s1">'add_prefix'</span><span class="p">,</span>
<span class="s1">'bindings'</span><span class="p">,</span> <span class="s1">'direction'</span><span class="p">,</span> <span class="s1">'get_item_with_href'</span><span class="p">,</span> <span class="s1">'get_item_with_id'</span><span class="p">,</span>
<span class="s1">'get_items'</span><span class="p">,</span> <span class="s1">'get_items_of_media_type'</span><span class="p">,</span> <span class="s1">'get_items_of_type'</span><span class="p">,</span>
<span class="s1">'get_metadata'</span><span class="p">,</span> <span class="s1">'get_template'</span><span class="p">,</span> <span class="s1">'guide'</span><span class="p">,</span>
<span class="s1">'items'</span><span class="p">,</span> <span class="s1">'language'</span><span class="p">,</span> <span class="s1">'metadata'</span><span class="p">,</span> <span class="s1">'namespaces'</span><span class="p">,</span> <span class="s1">'pages'</span><span class="p">,</span> <span class="s1">'prefixes'</span><span class="p">,</span>
<span class="s1">'reset'</span><span class="p">,</span> <span class="s1">'set_cover'</span><span class="p">,</span> <span class="s1">'set_direction'</span><span class="p">,</span> <span class="s1">'set_identifier'</span><span class="p">,</span> <span class="s1">'set_language'</span><span class="p">,</span>
<span class="s1">'set_template'</span><span class="p">,</span> <span class="s1">'set_title'</span><span class="p">,</span> <span class="s1">'set_unique_metadata'</span><span class="p">,</span> <span class="s1">'spine'</span><span class="p">,</span>
<span class="s1">'templates'</span><span class="p">,</span> <span class="s1">'title'</span><span class="p">,</span> <span class="s1">'toc'</span><span class="p">,</span> <span class="s1">'uid'</span><span class="p">,</span> <span class="s1">'version'</span><span class="p">]</span>
</pre></div>
<p>Of note, the <tt class="docutils literal">get_item_with_X</tt> entries caught my eye, as well as <tt class="docutils literal">spine</tt>. For my file, <tt class="docutils literal">book.spine</tt> looks like it gave me a bunch of tuples of ID and a <tt class="docutils literal">"yes"</tt> string of which I had no Idea what was. I then noticed I had a <tt class="docutils literal">toc</tt> property, assuming that was a Table of Contents, I printed that out and saw a bunch of <tt class="docutils literal">epub.Link</tt> objects. This looks like something I could use.</p>
<p>I will note, at this time I was thinking that this wasn't the direction I wanted to take this project. I really wanted to learn how to parse these things myself, unzip, parse XML, or HTML, etc., but I realized I needed to see someone else's work to even know what is going on. With this "defeat for the evening" admitted, I figured hey, why not at least make SOMETHING, right?" I decided to carry on.</p>
<p>Seeing I was on at least some track, I opened up PyCharm and made a new Project. First I setup a class called Epub, made a couple of functions for setting things up and ended up with</p>
<div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">Epub</span><span class="p">:</span>
<span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">book_path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-></span> <span class="kc">None</span><span class="p">:</span>
<span class="bp">self</span><span class="o">.</span><span class="n">contents</span><span class="p">:</span> <span class="n">ebooklib</span><span class="o">.</span><span class="n">epub</span><span class="o">.</span><span class="n">EpubBook</span> <span class="o">=</span> <span class="n">epub</span><span class="o">.</span><span class="n">read_epub</span><span class="p">(</span><span class="n">book_path</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">title</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">contents</span><span class="o">.</span><span class="n">title</span>
<span class="bp">self</span><span class="o">.</span><span class="n">toc</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">ebooklib</span><span class="o">.</span><span class="n">epub</span><span class="o">.</span><span class="n">Link</span><span class="p">]</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">contents</span><span class="o">.</span><span class="n">toc</span>
</pre></div>
<p>I then setup a <tt class="docutils literal">parse_chapters</tt> file, where I loop through the TOC. Here I went to the definition of <tt class="docutils literal">Link</tt> and saw I was able to get a <tt class="docutils literal">href</tt> and a <tt class="docutils literal">title</tt>, I decided my object for chapters would be a dictionary (I'll move to a DataClass later) with <tt class="docutils literal">title</tt> and <tt class="docutils literal">content</tt>. I remembered from earlier I had a <tt class="docutils literal">get_item_by_href</tt> so I stored the itext from the TOC's href: <tt class="docutils literal"><span class="pre">self.contents.get_item_with_href(link.href).get_content()</span></tt>. This would later prove to be a bad decision when I opened "The Fold.epub" and realized that a TOC could have a tuple of <tt class="docutils literal">Section</tt> and <tt class="docutils literal">Link</tt>, not just <tt class="docutils literal">Links</tt>. I ended up storing the item itself, and doing a double loop in the <tt class="docutils literal">parse_chapters</tt> function to loop if it's a tuple.</p>
<div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">parse_chapters</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-></span> <span class="kc">None</span><span class="p">:</span>
<span class="n">idx</span> <span class="o">=</span> <span class="mi">0</span>
<span class="k">for</span> <span class="n">_item</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">toc</span><span class="p">:</span>
<span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">_item</span><span class="p">,</span> <span class="nb">tuple</span><span class="p">):</span> <span class="c1"># In case is section tuple(section, [link, ...])</span>
<span class="k">for</span> <span class="n">link</span> <span class="ow">in</span> <span class="n">_item</span><span class="p">[</span><span class="mi">1</span><span class="p">]:</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_parse_link</span><span class="p">(</span><span class="n">idx</span><span class="p">,</span> <span class="n">link</span><span class="p">)</span>
<span class="n">idx</span> <span class="o">+=</span> <span class="mi">1</span>
<span class="k">else</span><span class="p">:</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_parse_link</span><span class="p">(</span><span class="n">idx</span><span class="p">,</span> <span class="n">_item</span><span class="p">)</span>
<span class="n">idx</span> <span class="o">+=</span> <span class="mi">1</span>
</pre></div>
<p><tt class="docutils literal">_parse_link</tt> simply makes that dictionary of <tt class="docutils literal">title</tt> and <tt class="docutils literal">item</tt> I mentioned earlier, with a new <tt class="docutils literal">index</tt> as I introduced buttons in the DearPyGUI at this time as well.</p>
<div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">_parse_link</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">idx</span><span class="p">,</span> <span class="n">link</span><span class="p">)</span> <span class="o">-></span> <span class="kc">None</span><span class="p">:</span>
<span class="n">title</span> <span class="o">=</span> <span class="n">link</span><span class="o">.</span><span class="n">title</span>
<span class="bp">self</span><span class="o">.</span><span class="n">chapters</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="nb">dict</span><span class="p">(</span>
<span class="n">index</span><span class="o">=</span><span class="n">idx</span><span class="p">,</span>
<span class="n">title</span><span class="o">=</span><span class="n">title</span><span class="p">,</span>
<span class="n">item</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">contents</span><span class="o">.</span><span class="n">get_item_with_href</span><span class="p">(</span><span class="n">link</span><span class="o">.</span><span class="n">href</span><span class="p">)</span>
<span class="p">))</span>
</pre></div>
<p>That's really all there is to make an MVP of an EPUB parser. You can use <tt class="docutils literal">BeautifulSoup</tt> to parse the HTML from the <tt class="docutils literal">get_body_contents()</tt> calls on items, to make more readable text if you want, but depending on your front end, the HTML may be what you want.</p>
<p>In my implementation my Epub class keeps track of the currently selected chapter, so this loads from all chapters and sets the <tt class="docutils literal">current_text</tt> variable.</p>
<div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">load_view</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-></span> <span class="kc">None</span><span class="p">:</span>
<span class="n">item</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">chapters</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">current_index</span><span class="p">][</span><span class="s1">'item'</span><span class="p">]</span>
<span class="n">soup</span> <span class="o">=</span> <span class="n">BeautifulSoup</span><span class="p">(</span><span class="n">item</span><span class="o">.</span><span class="n">get_body_content</span><span class="p">(),</span> <span class="s2">"html.parser"</span><span class="p">)</span>
<span class="n">text</span> <span class="o">=</span> <span class="p">[</span><span class="n">para</span><span class="o">.</span><span class="n">get_text</span><span class="p">()</span> <span class="k">for</span> <span class="n">para</span> <span class="ow">in</span> <span class="n">soup</span><span class="o">.</span><span class="n">find_all</span><span class="p">(</span><span class="s2">"p"</span><span class="p">)]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">current_text</span> <span class="o">=</span> <span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">text</span><span class="p">)</span>
</pre></div>
<p>I don't believe any of this code will be useful to anyone outside of my research for now, but it's my first step into writing an EPUB parser myself.</p>
<p>The DearPyGUI steps are out of scope of this blog post, but here is my <a class="reference external" href="https://gist.github.com/tyrelsouza/9c6681850fc00bf5d9f35568faf611d4">final ebook Reader</a> which is super inefficient!</p>
<div class="figure">
<img alt="final ebook reader, chapters on left, text on right" src="https://tyrel.dev/blog/images/2022/06/ebook-ebook_reader.png" />
</div>
<p>I figure the Dedication page is not <em>as</em> copywrited as the rest of the book, so it's fair play showing that much. Sarah J Maas, if you have any issues, I can find another book for my screenshots.</p>
</div>
An Update On Flying2022-05-07T01:15:00-04:002022-05-07T01:15:00-04:00tyreltag:tyrel.dev,2022-05-07:/blog/2022/05/an-update-on-flying.html<p>I took ten years to get my pilot's license. From March 17, 2010 to December 30, 2020. It was amazing. I now find myself a year and a half later from achieving my goal and I don't find myself interested enough right now to go flying. There's a gas crisis …</p><p>I took ten years to get my pilot's license. From March 17, 2010 to December 30, 2020. It was amazing. I now find myself a year and a half later from achieving my goal and I don't find myself interested enough right now to go flying. There's a gas crisis, there's a pandemic, there's a lot of political things going on, a war in Ukraine, that it kind of feels bad wasting hundreds of dollars just going sight seeing.</p>
<p>I just completed a ground school course for my Instrument Rating -- I still need to take the written test part. With that out of the way I can start actually flying with an instructor to get my instrument rating. One of the planes the club that I am a part of has is a <a class="reference external" href="http://wingsofcarolina.org/aircraft">Mooney M20J</a>. This requires 250 hours of flight time, or 100 hours and an instrument rating. I'm at that annoying 145 hour mark that dissuades me from wasting 100 hours just to fly that plane, and wanting to get my instrument rating.</p>
<p>I left my previous job last December so I didn't have the excess money to fly for a month while job hunting, and well, habit becomes habit... I haven't flown since <a class="reference external" href="https://k3tas.radio/airband/2021/10/18/october-17-2021-back-above-keene/">October</a>! I'm definitely in a better place now, with a much nicer job and salary though. I'm hoping to maybe pick it back up again this fall. I wasn't pleased AT ALL (those who follow me on twitter will probably know this, online class is not the environment for me) with the ground school over Zoom, so I want to redo this by watching the Sporty's ground school. I need to put aside some time over the next coming weeks to actually sit down and watch it. Hopefully I can start flying with an instructor soon. I'm not looking forward to taking the written test, as I have to go visit a testing center at RDU airport - so there is that kind of delaying me too.</p>
Garage Door Opener2022-01-09T22:46:00-05:002022-01-09T22:46:00-05:00tyreltag:tyrel.dev,2022-01-09:/blog/2022/01/garage-door-opener.html<p>I bought a house on October 9, 2020. This house has a garage door, and like any <em>normal person</em> of course I had to automate it.</p>
<p>One of the first things I did when I moved into my house was research some automation. I initially bought a half dozen <a class="reference external" href="https://www.amazon.com/gp/product/B07HF44GBT/ref=ppx_yo_dt_b_search_asin_title?ie=UTF8&psc=1">ESP8266 …</a></p><p>I bought a house on October 9, 2020. This house has a garage door, and like any <em>normal person</em> of course I had to automate it.</p>
<p>One of the first things I did when I moved into my house was research some automation. I initially bought a half dozen <a class="reference external" href="https://www.amazon.com/gp/product/B07HF44GBT/ref=ppx_yo_dt_b_search_asin_title?ie=UTF8&psc=1">ESP8266 devices</a> and tried to figure out what I could do with them. I found Home Assistant and set that up on my server computer, along with ZoneMinder for security cameras.</p>
<div class="figure">
<img alt="NodeMCU ESP8266 module" src="https://tyrel.dev/blog/images/2022/01/garage-nodemcu_esp8266_module.jpg" />
<p class="caption">NodeMCU ESP8266 module</p>
</div>
<p>I knew I would need some sort of relay (domain purchased from is gone) and <a class="reference external" href="https://www.amazon.com/gp/product/B00LYCUSBY/">reed switches</a> to trigger the door and sense its position, so I purchased some from the internet. But my friend Paul said all I needed was a <a class="reference external" href="https://www.amazon.com/gp/product/B071J39678/">MOSFET</a> so I bought one of those too. I tried to figure out how to trigger the door with a mosfet, but I was never able to. I won't document those failures.</p>
<div class="wp-image-183 figure">
<img alt="Magnetic Reed Switch" src="https://tyrel.dev/blog/images/2022/01/garage-magnetic_reed_switch.png" />
<p class="caption">Magnetic Reed Switch</p>
</div>
<p>Home Assistant has a plugin called ESPHome where you can write yaml files to configure an esp8266 module. This then builds a binary which you need to flash onto the esp8266 at least once via usb. From then on you can then on you can upload from a web form and drop the bin file in manually, or just press the UPLOAD button from ESPHome. I set my relay up on pin 19/D1 for the digital pin, and 16/GND,10/3v3 for the power. The Reed switch I tossed on 15/D7 and 11/GND but that could have been anywhere. See Schematic below. It still doesn't have an enclosure.</p>
<div class="figure">
<img alt="Relay in blue, and wires going to the NodeMCU" src="https://tyrel.dev/blog/images/2022/01/09_relay.jpg" />
<p class="caption">Relay in blue, and wires going to the NodeMCU</p>
</div>
<div class="figure">
<img alt="Schematic" src="https://tyrel.dev/blog/images/2022/01/garage-Garage_door_schematic.png" />
<p class="caption">Schematic</p>
</div>
<p>With the relay triggering, I still had one problem - I'd trigger the door and it wouldn't do anything! Something else was a problem. The wiring for the garage door terminates in four screws, two of which are the door trigger. I tried poking it with a multimeter and having someone push the door button on the wall, but I was never successful that way, as any contact between the two poles would just open the door anyway.</p>
<p>After some unsuccessful thinking, I figured it was time to purchase an oscilloscope. I've always wanted one, in fact I bought an old <a class="reference external" href="https://i.redd.it/kcovfng8w4u71.jpg">Heathkit</a> one once, but never got it working as I would have had to replace all the capacitors, and the insides is all perfboard - A NIGHTMARE.</p>
<p>I found this <a class="reference external" href="https://www.amazon.com/gp/product/B07PHS4C9B/">USB Logic Analyzer and Oscilloscope</a> on amazon and figure I'd try it out. It came while my father was in town, so he was pretty interested in seeing it work as well. Of course I'm very fond of open source software so I downloaded <a class="reference external" href="https://sigrok.org/wiki/PulseView">PulseView</a> and found out that the LHT00SU1 is a <tt class="docutils literal">fx2lafw</tt> driver device. The interface to connect is really simple, you just look for your driver, and Scan for any usb devices that are using that driver and they show up to choose from.</p>
<p>I plugged the 1ACH and GND cables in and hooked them on the +/- wires where they attach to the door motor. Once you have a device connected, you then click Run on the top left, and trigger what ever mechanism (my garage door button) and see what happens.</p>
<p>I was very pleasantly surprised when I saw some movement on the A0 line!</p>
<div class="wp-image-184 figure">
<img alt="Pulses of about 180ms and 140ms" src="https://tyrel.dev/blog/images/2022/01/garage-pulses_180ms_140ms.png" />
<p class="caption">Pulses of about 180ms and 140ms</p>
</div>
<p>I added some markers to figure out what I needed to imitate in ESPHome, and saw that it's about 150ms high with a 225ms low, then another 150ms high and then low again.</p>
<p>This looks like this in yaml:</p>
<div class="highlight"><pre><span></span><span class="nt">switch</span><span class="p">:</span><span class="w"></span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="nt">platform</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">gpio</span><span class="w"></span>
<span class="w"> </span><span class="nt">pin</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">D1</span><span class="w"></span>
<span class="w"> </span><span class="nt">id</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">relay</span><span class="w"></span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="nt">platform</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">template</span><span class="w"></span>
<span class="w"> </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="s">"Garage</span><span class="nv"> </span><span class="s">Remote"</span><span class="w"></span>
<span class="w"> </span><span class="nt">icon</span><span class="p">:</span><span class="w"> </span><span class="s">"mdi:gate"</span><span class="w"></span>
<span class="w"> </span><span class="nt">turn_on_action</span><span class="p">:</span><span class="w"></span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="nt">switch.turn_on</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">relay</span><span class="w"></span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="nt">delay</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">150ms</span><span class="w"></span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="nt">switch.turn_off</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">relay</span><span class="w"></span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="nt">delay</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">225ms</span><span class="w"></span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="nt">switch.turn_on</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">relay</span><span class="w"></span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="nt">delay</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">150ms</span><span class="w"></span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="nt">switch.turn_off</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">relay</span><span class="w"></span>
</pre></div>
<p>I'm pretty sure I jumped and screamed with excitement when it opened!</p>
<p>Once the door was opening and closing, I was able to add more yaml to set another binary sensor to show whether it was open or closed (from the reed sensor):</p>
<div class="highlight"><pre><span></span><span class="nt">binary_sensor</span><span class="p">:</span><span class="w"></span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="nt">platform</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">gpio</span><span class="w"></span>
<span class="w"> </span><span class="nt">pin</span><span class="p">:</span><span class="w"></span>
<span class="w"> </span><span class="nt">number</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">D7</span><span class="w"></span>
<span class="w"> </span><span class="nt">inverted</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">true</span><span class="w"></span>
<span class="w"> </span><span class="nt">mode</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">INPUT_PULLUP</span><span class="w"></span>
<span class="w"> </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="s">"Garage</span><span class="nv"> </span><span class="s">Door</span><span class="nv"> </span><span class="s">Closed"</span><span class="w"></span>
</pre></div>
<p>All together this is shown on my Home Assistant Lovelace dashboard using two cards, one that shows a closed door, and one with an open door (both actual pictures of the door!) with a button to open it. Once it opens or closes the other card switches into place, Home Assistant at least at the time didn't have good conditional cards like I wanted.</p>
<div class="highlight"><pre><span></span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">conditional</span><span class="w"></span>
<span class="nt">conditions</span><span class="p">:</span><span class="w"></span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="nt">entity</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">binary_sensor.garage_door_closed</span><span class="w"></span>
<span class="w"> </span><span class="nt">state</span><span class="p">:</span><span class="w"> </span><span class="s">'on'</span><span class="w"></span>
<span class="nt">card</span><span class="p">:</span><span class="w"></span>
<span class="w"> </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">picture-glance</span><span class="w"></span>
<span class="w"> </span><span class="nt">title</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">Garage (Closed)</span><span class="w"></span>
<span class="w"> </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="s">'https://tyrel.dev/house/garage_door.jpeg'</span><span class="w"></span>
<span class="w"> </span><span class="nt">entities</span><span class="p">:</span><span class="w"></span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="nt">entity</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">switch.garage_remote</span><span class="w"></span>
<span class="w"> </span><span class="nt">hold_action</span><span class="p">:</span><span class="w"></span>
<span class="w"> </span><span class="nt">action</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">none</span><span class="w"></span>
</pre></div>
<div class="figure">
<img alt="Closed door state and button" src="https://tyrel.dev/blog/images/2022/01/garage-Lovelace_garage_door_closed.png" />
<p class="caption">Closed door state and button</p>
</div>
<p>Happy with the state of my Garage Door opening button, I can now yell at my phone to open the garage door (it's a "secure" switch so it requires the phone to to be open before OK Google will trigger the door).</p>
<p>There's a couple more pictures in my <a class="reference external" href="https://www.instagram.com/p/CIrYO3SlxON/">Instagram post</a> about it.</p>
<p>I know I could have bought a device to do this myself, but this is fully mine, my code, and my experiment with learning how to automate things at home, I gained way more out of this project than I did if I just bought a MyQ or what ever is popular these days.</p>
<div class="section" id="bill-of-materials">
<h2>Bill of Materials</h2>
<ul class="simple">
<li><a class="reference external" href="https://www.amazon.com/gp/product/B00LYCUSBY/">Magnetic Switch</a></li>
<li><a class="reference external" href="https://www.amazon.com/gp/product/B07HF44GBT/">NodeMCU</a></li>
<li><a class="reference external" href="https://acrobotic.com/products/acr-00016">Relay Shield</a></li>
</ul>
</div>
<div class="section" id="notes">
<h2>Notes</h2>
<p>This is no longer in service, as I replaced the door and have a Chamberlain MyQ system now. Less fun, but at least it's serviceable.</p>
</div>
Postmortem of a fun couple bugs2021-11-11T14:55:00-05:002021-11-11T14:55:00-05:00tyreltag:tyrel.dev,2021-11-11:/blog/2021/11/postmortem-of-a-fun-couple-bugs.html<p>Story at my previous job:</p>
<blockquote>
Tieg: Hey Tyrel, I can't run <tt class="docutils literal">invoke sign 5555</tt>, can you help with this?</blockquote>
<p>This is How my night started last night at 10pm. My coworker Tieg did some work on our <a class="reference external" href="https://tidelift.com/cli">CLI</a> project and was trying to release the latest version. We use <a class="reference external" href="https://www.pyinvoke.org/">invoke …</a></p><p>Story at my previous job:</p>
<blockquote>
Tieg: Hey Tyrel, I can't run <tt class="docutils literal">invoke sign 5555</tt>, can you help with this?</blockquote>
<p>This is How my night started last night at 10pm. My coworker Tieg did some work on our <a class="reference external" href="https://tidelift.com/cli">CLI</a> project and was trying to release the latest version. We use <a class="reference external" href="https://www.pyinvoke.org/">invoke</a> to run our code signing and deployment scripts, so I thought it was just a quick "oh maybe I screwed up some python!" fix. It wasn't.</p>
<p>I spent from 10:30 until 1:30am this morning going through and looking into why Tieg wasn't able to sign the code. The first thing I did was re-run the build on CircleCI, which had the same error, so hey! at least it was reproducible. The problem was that in our Makefile scripts we run <tt class="docutils literal">tidelift version > <span class="pre">tidelift-cli.version</span></tt> and then upload that to our deployment directories, but this was failing for some reason. We let clients download this file to see what the latest version is and then our CLI tool has the ability to selfupdate (except on homebrew) to pull this latest version if you're outdated.</p>
<p>Once I knew what was failing, I was able to use CircleCI's ssh commands and log in, and see what happened, but I was getting some other errors. I was seeing some problems with <tt class="docutils literal"><span class="pre">dbus-launch</span></tt> so I promptly (mistakenly) yelled to the void on twitter about <tt class="docutils literal"><span class="pre">dubs-launch</span></tt>. Well would you know it, I may have mentioned before, but I work with Havoc Pennington.</p>
<blockquote>
Havoc Pennington: fortunately I wrote dbus-launch so may be able to tell you something, unfortunately it was like 15 years ago</blockquote>
<p>Pumped about this new revelation, I started looking at our <tt class="docutils literal">keychain</tt> dependency, because I thought the issue was there as that's the only thing that uses <tt class="docutils literal">dbus</tt> on Linux. Then we decided (Havoc Pointed it out) that it was a red herring, and maybe the problem was elsewhere. I at least learned a bit about dbus and what it does, but not enough to really talk about it to any detail.</p>
<p>Would you know it, the problem was elsewhere. Tieg was running <tt class="docutils literal">dtruss</tt> and saw that one time it was checking his <tt class="docutils literal">/etc/hosts</tt> file when it was failing, and another time it was NOT, which was passing. Then pointed out a 50ms lookup to our <tt class="docutils literal">download.tidelift.com</tt> host.</p>
<p>Tieg then found <a class="reference external" href="https://github.com/golang/go/issues/49517">Issue 49517</a> this issue where someone mentions that Go 1.17.3 was failing them for net/http calls, but not the right way.</p>
<p>It turns out, that it wasn't the keyring stuff, it wasn't the <em>technically</em> the version calls that failed. What was happening is every command starts with a check to <a class="reference external" href="https://download.tidelift.com/cli/tidelift-cli.version">https://download.tidelift.com/cli/tidelift-cli.version</a> which we then compare to the current running version, if it's different and outdated, we then say "you can run selfupdate!". What fails is that call to download.tidelift.com, because of compiling with go1.17.3 and a <tt class="docutils literal">context canceled</tt> due to stream cleanup I guess?</p>
<p>Okay so we need to downgrade to Go 1.17.2 to fix this. Last night in my trying, I noticed that our CircleCI config was using <tt class="docutils literal">circle/golang:1.16</tt> as its docker image, which has been superseded by <tt class="docutils literal">cimg/go:1.16.x</tt> style of images. But I ran into some problems with that while upgrading to <tt class="docutils literal">cimg/go:1.17.x</tt>. The problem was due to the image having different permissions, so I couldn't write to the same directories that when Mike wrote our <tt class="docutils literal">config.yml</tt> file, worked properly.</p>
<p>Tieg and I did a paired zoom chat and finished this up by cutting out all the testing/scanning stuff in our config files, and just getting down to the Build and Deploy steps. Found ANOTHER bug that Build seems to run as the <tt class="docutils literal">circleci</tt> user, but Deploy was running as <tt class="docutils literal">root</tt>. So in the build <tt class="docutils literal">working_directory</tt> setting, using a <tt class="docutils literal">~/go/tidelift/cli</tt> path, worked. But when we restored the saved cache to Deploy, it still put it in <tt class="docutils literal">/home/circle/go/tidelift/cli</tt>, but then the <tt class="docutils literal">working_directory</tt> of <tt class="docutils literal">~/go/tidelift/cli</tt> was relative to <tt class="docutils literal">/root/</tt>. What a nightmare!</p>
<p>All tildes expanded to <tt class="docutils literal">/home/circleci/go/tidelift/cli</tt> set, Makefile hacks undone, (removing windows+darwin+arm64 builds from your scripts during testing makes things A LOT faster!) and PR Merged, we were ready to roll.</p>
<p>I merged the PR, we cut a new version of TideliftCLI 1.2.5, updated the changelog and signed sealed delivered a new version which uses Go 1.17.2, writes the proper <tt class="docutils literal"><span class="pre">tidelift-cli.version</span></tt> file in deployment steps, and we were ready to ROCK!</p>
<p>That was fun day. Now it's time to write some rspec tests.</p>
Finished my GitHub CLI tool2021-11-05T00:08:00-04:002021-11-05T00:08:00-04:00tyreltag:tyrel.dev,2021-11-05:/blog/2021/11/finished-my-github-cli-tool.html<p>I never intended this to be a full fleshed CLI tool comparable to the likes of the real GitHub CLI. This was simply a way to refresh myself and have fun. I have accomplished this, and am now calling this <em>"Feature Complete"</em>. You can play around with it yourself from …</p><p>I never intended this to be a full fleshed CLI tool comparable to the likes of the real GitHub CLI. This was simply a way to refresh myself and have fun. I have accomplished this, and am now calling this <em>"Feature Complete"</em>. You can play around with it yourself from the <a class="reference external" href="https://gitlab.com/Tyrel/ghub">repository on gitlab</a>.</p>
<div class="section" id="httpx-links">
<h2>HTTPX.LINKS</h2>
<p>Today I learned some fun things with <tt class="docutils literal">httpx</tt> mainly. The main thing I focused on today was figuring out pagination. The GitHub API uses the <tt class="docutils literal">link</tt> header which I had never seen before.</p>
<p>The format of the header is a url, and then a relationship of that url. Lets take my friend Andrey's repos for example:</p>
<figure class="wp-block-pullquote"><!-- -->
<blockquote>
<p><tt class="docutils literal">{'link': <span class="pre">'<https://api.github.com/user/6292/repos?page=2>;</span> <span class="pre">rel="next",</span> <span class="pre"><https://api.github.com/user/6292/repos?page=7>;</span> <span class="pre">rel="last"',</span> <span class="pre">...}</span></tt></p>
<p>sample header value from Getting Andrey's repositories list and checking pagination</p>
</blockquote>
</figure><p>The link header there has two items split by a comma, each with two fields split by a semicolon, the first of which is a URL inside angle brackets... and AGHH this is going to be annoying to parse! Luckily <tt class="docutils literal">httpx</tt> responses have this handled and <tt class="docutils literal"><span class="pre">client.get(...).links</span></tt> returns a lovely dictionary of the proper data.</p>
<p>With a <tt class="docutils literal"><span class="pre">response.links.get('next')</span></tt> check, you can get the url from <tt class="docutils literal"><span class="pre">response.links['next']['url']</span></tt>. So much nicer than writing some regular expressions.</p>
</div>
<div class="section" id="testing">
<h2>TESTING</h2>
<p>With that accomplished, I then added <tt class="docutils literal"><span class="pre">pytest-cov</span></tt> to my <tt class="docutils literal">requirements.in</tt> and was able to leverage some coverage checks. I was about 30% with the latest changes (much higher than anticipated!) so I knew what I wanted to focus on next. The API seemed the easiest to test first again, so I changed around how I loaded my fixtures and made it pass in a name and open that file instead. In real code I would not have the function in both my test files, I would refactor it, but again, this is just a refresher, I'm lazy.</p>
<p>I decided earlier that I also wanted to catch HTTP 403 errors as I ran into a rate limit issue. Which, <em>I assure you dear reader</em>, was a thousand percent intentional so I would know what happens. Yeah, we'll go with that.</p>
<p>Py.Test has a context manager called <tt class="docutils literal">pytest.raises</tt> and I was able to just <tt class="docutils literal">with pytest.raises(httpx.HttpStatusError)</tt> and check that raise really easily.</p>
<p>The next bits of testing for the API were around the pagination, I faked two responses and needed to update my <tt class="docutils literal">link</tt> header, checking the cases where there was NO <tt class="docutils literal">link</tt>, was multiple pages, and with my shortcut return - in case the response was an object not a list. Pretty straight forward.</p>
<p>The GHub file tests were kind of annoying, I'm leveraging <tt class="docutils literal">rich.table.Table</tt> so I haven't been able to find a nice "this will make a string for you" without just using <tt class="docutils literal">rich</tt>'s <tt class="docutils literal">print</tt> function. I decided the easiest check was to see if the <tt class="docutils literal">Table.Columns.Cells</tt> matched what I wanted, which felt a little off but it's fine.</p>
<p>The way I generated the table is by making a generator in a pretty ugly way and having a bunch of <tt class="docutils literal"><span class="pre">repo['column'],</span> <span class="pre">repo['column']</span></tt> responses, rather than doing a dict comprehension and narrowing the keys down. If I ever come back to this, I MIGHT reassess that with a <tt class="docutils literal">{k:v for k,v in repos if k in SELECTED_KEYS}</tt> and then yield a dictionary, but it's not worth the effort.</p>
<p>Overall I'd say this project was fun. It gave me a glimpse back into the Python world, and an excuse to write a couple blog posts. My next project is to get a Django site up and running again, so I can figure out how to debug my <tt class="docutils literal"><span class="pre">django-dbfilestorage</span></tt>.</p>
</div>
<div class="section" id="closing-thoughts">
<h2>Closing Thoughts</h2>
<p>If I had to do this again, I would probably have tried some test driven development. I've tried in the past, but I don't work on a lot of greenfield projects. I tend to be the kind of engineer who jumps HEAD FIRST into code and then tests are an after thought.</p>
<p>I also kind of want to rewrite this in Go and Rust, two other languages I've been fond of lately, just to see how they'd compare in fun. I haven't done any API calls with Rust yet, only made a little Roguelike by following <a class="reference external" href="https://pragprog.com/titles/hwrust/hands-on-rust/">Herbert Wolverson's Hands-On-Rust book</a>. The <a class="reference external" href="https://tidelift.com/cli">Tidelift CLI</a> is all Go and a bazillion API calls (okay like ten) so that wouldn't be too hard to use like SPF13's Cobra CLI library and make a quick tool that way.</p>
<p>One fun thing I learned while moving things over to GitLab is that my user Tyrel is a super early adopter. I was in the first 36,000 people! I showed a screenshot of my user ID to my friend Sunanda at GitLab and we had fun finding that out.</p>
</div>
Python3 GitHub CLI tool as a refresher2021-11-04T01:29:00-04:002021-11-04T01:29:00-04:00tyreltag:tyrel.dev,2021-11-04:/blog/2021/11/python3-github-cli-tool-as-a-refresher.html<p>It's no lie that I love terminals. I wish I could live on a terminal and never really need to see a GUI application again.</p>
<p>Last night I migrated a lot of my old code from one GitLab account to another (<a class="reference external" href="https://gitlab.com/tyrelsouza">tyrelsouza</a> to <a class="reference external" href="https://gitlab.com/tyrel">tyrel</a>) in an effort to clean up …</p><p>It's no lie that I love terminals. I wish I could live on a terminal and never really need to see a GUI application again.</p>
<p>Last night I migrated a lot of my old code from one GitLab account to another (<a class="reference external" href="https://gitlab.com/tyrelsouza">tyrelsouza</a> to <a class="reference external" href="https://gitlab.com/tyrel">tyrel</a>) in an effort to clean up some of my usernames spread across the world. While doing that I noticed my <a class="reference external" href="https://gitlab.com/tyrel/django-dbfilestorage">django-dbfilestorage</a> Python module that has been sitting and rotting for three years. I played around a little bit in order to port it to Python 3.9, but I ran into some base64 errors. I tried a little bit but it was late and I couldn't figure it out. My resolve is that I have been away from Python for too long so the little things - that I knew and love - had fallen away. I mentioned this to my friend Alex and he said <em>"make a barebones github cli (readonly?) with issue viewer, and stats display"</em>. I've embarked on a journey to refresh my Python well enough to repair DBFS.</p>
<div class="figure">
<img alt="Me: "okay python frioends, what should I make as a quick refresher into the Python world?" alex: "maybe: barebonx github cli (reasdonly?) with issue viewer and stats display"" src="https://tyrel.dev/blog/images/2021/11/github_cli-alex_prompt.png" />
</div>
<p>I knew I wanted to use <tt class="docutils literal">httpx</tt> as my network client library, it's new, fast, and I have a couple friends who work on it. I started with a barebones <tt class="docutils literal">requirements.in</tt> file, tossed in <tt class="docutils literal">invoke</tt>, <tt class="docutils literal">pytes</tt>t, and <tt class="docutils literal">black</tt>. From there I used <tt class="docutils literal"><span class="pre">pip-compile</span></tt> to generate my <tt class="docutils literal">requirements.txt</tt> - (a tip I picked up recently while adding Pip-Compile support to the <a class="reference external" href="https://tidelift.com/cli">Tidelift CLI</a>) and I was good to go.</p>
<p>The <a class="reference external" href="https://docs.github.com/en/rest/overview/resources-in-the-rest-api#schema">docs for the GitHub API</a> are pretty easy to read, so I knew all I really needed to do was set my <tt class="docutils literal">Accept</tt> header to be Version3 and I could view the schema. With the schema saved to a <tt class="docutils literal">.json</tt> file I then wrote a <tt class="docutils literal">GHub</tt> class to pull this data down using <tt class="docutils literal">httpx.client.Client.get</tt>, super simple! The only two endpoints I care about right now are the user and repos endpoints, so I made two <tt class="docutils literal">get_</tt> functions for each. After a little bit of work - which I won't bore you with the super intricate details - I have a functional cli.py file. For now, the only interaction is a propmt from <tt class="docutils literal">rich</tt> for a username, and then you get a fancy table (also from <tt class="docutils literal">rich</tt>) of the first page of results of repos, stargazer/watchers/forks counts, and a description.</p>
<div class="figure">
<img alt="Prompting for the username and showing my table of repositories." src="https://tyrel.dev/blog/images/2021/11/github_cli-prompting_and_table.png" />
<p class="caption">Prompting for the username and showing my table of repositories.</p>
</div>
<p>It was a fun evening of learning what's changed in Python3 since I last touched it, especially as I've spent the majority of my career in Python2.7. Type annotations are super awesome. I'll probably pick it up again once I get some more free time later in the week. It's also nice blog fodder! I already have a million things I want to do next - pagination, caching, some more interaction.</p>
<div class="figure">
<img alt="Showing py.test running" src="https://tyrel.dev/blog/images/2021/11/github_cli-pytest_running.png" />
<p class="caption">Showing py.test running</p>
</div>
<p>I know the tool I'm writing is nothing special, especially with their own <a class="reference external" href="https://github.com/cli/cli">cli</a> now, but I'm not looking at reinventing the wheel!</p>
<p>Check out the code so far on my <a class="reference external" href="https://gitlab.com/tyrel/ghub">GitLab</a> (<em>heh</em>, ironic it's there).</p>
<p>Dependencies: <a class="reference external" href="https://www.python-httpx.org/">httpx</a>, <a class="reference external" href="https://github.com/jazzband/pip-tools">pip-tools</a>, <a class="reference external" href="https://github.com/psf/black">black</a>, <a class="reference external" href="https://www.pyinvoke.org/">invoke</a>, <a class="reference external" href="https://docs.pytest.org/en/6.2.x/">pytest</a>, <a class="reference external" href="https://pypi.org/project/pytest-httpx/">pytest-httpx</a>, <a class="reference external" href="https://github.com/willmcgugan/rich">rich</a>.</p>
Hello, world!2021-10-31T19:10:00-04:002021-10-31T19:10:00-04:00tyreltag:tyrel.dev,2021-10-31:/blog/2021/10/hello-world.html<p>This blog here I want to keep some permanent tech thoughts in more written form than my <a class="reference external" href="https://tyrel.website/wiki/">Wiki</a> where I keep the majority of my written work. I do have a <a class="reference external" href="https://k3tas.radio/airband">flight blog</a>, which has a lot of customization for maps and such that I don't want on here.</p>
<p>I …</p><p>This blog here I want to keep some permanent tech thoughts in more written form than my <a class="reference external" href="https://tyrel.website/wiki/">Wiki</a> where I keep the majority of my written work. I do have a <a class="reference external" href="https://k3tas.radio/airband">flight blog</a>, which has a lot of customization for maps and such that I don't want on here.</p>
<p>I have revived some old blog posts from previous blogs, to give some life to this blog while I'm writing new articles.</p>
Back Above Keene2021-10-17T00:00:00-04:002021-10-17T00:00:00-04:00tyreltag:tyrel.dev,2021-10-17:/blog/2021/10/back-above-keene.html<div class="figure">
<img alt="Mom and I hugging in front of the right wing of N43337" src="https://tyrel.dev/blog/images/2021/10/17_me_mom_n43337.jpg" />
<p class="caption">Mom and I hugging in front of the right wing of N43337</p>
</div>
<p>It's my brother's wedding weekend and he asked me to take him and his new father-in-law flying. On October 16th I get to the airport, very well knowing that it's still too foggy for my 8am-10am flight that …</p><div class="figure">
<img alt="Mom and I hugging in front of the right wing of N43337" src="https://tyrel.dev/blog/images/2021/10/17_me_mom_n43337.jpg" />
<p class="caption">Mom and I hugging in front of the right wing of N43337</p>
</div>
<p>It's my brother's wedding weekend and he asked me to take him and his new father-in-law flying. On October 16th I get to the airport, very well knowing that it's still too foggy for my 8am-10am flight that I was about to cancel in person. I still went in, because I haven't seen Beth in almost a year and wanted to say hi. We caught up, talked about how I've been going to Monadnock Aviation since I was in college - after my first flight on March 17, 2010! I called Levi and told him that we weren't flying, so he should just go finish getting ready for his wedding at 11:30.</p>
<p>The next day I had booked for my mother to go up the first time with me. We get to the airport at 9:55 and the FBO is locked up, Uhhhh. I email/text some people and no one knows how to get a hold of the desk attendant. We had apparently JUST missed David, one of the CFIs, who was getting into N44836 with a student I presume. So we waited a bit and it seems he was called away to do some fueling with the fuel truck. No fault of his at all, Sundays can get a little rough with only one person being at the FBOs desk.</p>
<p>We finally grab the book and keys and I go out and preflight. I'm in N43337 today, my favorite plane. I'm even wearing my "WARRIOR 337" shirt I got for soloing, for good luck! Preflight was easy as usual, once you've done it a hundred times in the same plane, you know what you're looking for from the checklists.</p>
<p>I waved to mom and Lauren to come over and we got in, I showed mom and Lauren how to set up the headphones and get into the back seat. Following a safety briefing we were off!</p>
<p>We taxied over from the Northwest ramp (The FBO area) to the East ramp and I did my run up there, the winds were 340° at 7kts gusting to 17, so the other planes were using runway 32. Waited around a bit for a radio check, the PTT button was sticky and no one was replying to my ask for radio checks, even though there were a few planes around, oh well.</p>
<p>We took off on runway 32 after waiting in line (it was busy today, wow!) and I did a lap around the pattern. It was not clean, I haven't flown in just over two months, and we had the added weight of a back passenger, so I was a little nervous the first pattern loop. But I will always do one pattern loop with a new passenger - just to give them a chance to bail out!</p>
<p>I'm not fond of runway 32, it has a high hill over Marcy Hill in Swanzey, NH and it always throws me off (probably because I have like 300 landings on runway 02 and maybe 15 on runway 32). The landing was fine, Lauren was taking pictures from the back and my mom said it was "wicked smooth, and you barely even felt the tires hit, I thought it was great, especially with the wind and everything" which as a first time passenger in a "oh my god I didn't know it was quite this small" airplane, I feel good about!</p>
<p>We then taxied back to 32, and took off again, this time it was a west departure out to Spofford Lake and over my mom's house in Chesterfield. I probably should have headed to Brattleboro first after Spofford. Had I done this, we would have been in position to fly Brattleboro, VT north to Putney, VT west of the Connecticut River and my mom would have gotten a MUCH nicer view of her house. Instead we flew over the lake and through a little valley over Westmoreland and went south along the river.</p>
<p>At one point I saw a dark cloud above and said "Okay we're about to go under a dark cloud, I'll try to avoid it but it might get turbulent", so mom and Lauren would know to hold on and probably 10 seconds later the plane went "woomp" down a bit because of turbulence - so they were prepared. After that small cloud we found a tiny patch of clean air and I turned to the right a little bit above Exit 3 and headed north again towards moms house, staying a little bit west of the river. I paralleled I91 again for a minute or two and then we got in line that I could fly close enough to mom's house that she could see it.</p>
<p>After that fun bit we headed back, directly over Spofford Lake for some more sights, and onward to KEEN.</p>
<p>We flew over Yale Forest, and I saw a cool cliff face I had never seen before - as we were entering RWY32 on a 45° entry. Entered the pattern and found myself VERY high (900msl at at 488msl airport) on final, on a 4000' runway with a displaced threshold so I executed a go around there. I probably could have made it, but with the wind and avoiding Marcy Hill, I figure it's always safe to Go Around.</p>
<p>The next loop I had my sight pictures again at runway 32 and we landed, rolled out to Taxiway Sierra and parked the plane!</p>
<p>Mom said it was fun!</p>
<div class="figure">
<img alt="Back of my head while turning a bit" src="https://tyrel.dev/blog/images/2021/10/17_back-of-my-head.jpg" />
<p class="caption">Back of my head while turning a bit</p>
</div>
<div class="figure">
<img alt="Cloudy and Sunny View off the right wing. Monadnock in the distance." src="https://tyrel.dev/blog/images/2021/10/17_cloudy-sun-view.jpg" />
<p class="caption">Cloudy and Sunny View off the right wing. Monadnock in the distance.</p>
</div>
<div class="figure">
<img alt="Turning over route 9 near Home Depot and Hannaford" src="https://tyrel.dev/blog/images/2021/10/17_hannaford-kmart.jpg" />
<p class="caption">Turning over route 9 near Home Depot and Hannaford</p>
</div>
<div class="figure">
<img alt="Cockpit view, a mile out from runway 32/14 with the runway in sight." src="https://tyrel.dev/blog/images/2021/10/17_landing-32-14-on-final.jpg" />
<p class="caption">Cockpit view, a mile out from runway 32/14 with the runway in sight.</p>
</div>
<div class="figure">
<img alt="Cockpit view, a 1/4 mile from runway 32/14 with the runway in sight." src="https://tyrel.dev/blog/images/2021/10/17_landing-32-14-short-final.jpg" />
<p class="caption">Cockpit view, a 1/4 mile from runway 32/14 with the runway in sight.</p>
</div>
<div class="figure">
<img alt="Looking off the left wing at mount Monadnock." src="https://tyrel.dev/blog/images/2021/10/17_left-wing-looking-at-airport-and-monadnock.jpg" />
<p class="caption">Looking off the left wing at mount Monadnock.</p>
</div>
<div class="figure">
<img alt="Just pretty sky views, out the right window above the right wing at the sun and clouds." src="https://tyrel.dev/blog/images/2021/10/17_sun-above-right-wing.jpg" />
<p class="caption">Just pretty sky views, out the right window above the right wing at the sun and clouds.</p>
</div>
<div class="figure">
<img alt="Three degree turn above moms house." src="https://tyrel.dev/blog/images/2021/10/17_turning-from-backseat.jpg" />
<p class="caption">Three degree turn above moms house.</p>
</div>
Two flights and some nice weather2021-08-08T00:00:00-04:002021-08-08T00:00:00-04:00tyreltag:tyrel.dev,2021-08-08:/blog/2021/08/two-flights-and-some-nice-weather.html<p>The other day there was a call to action on the Wings of Carolina slack. "Is anyone able to help this pilot get one of our planes back?" Seems this newly licensed pilot, (Congrats!) passed his check ride on Friday and the weather was not great so had to leave …</p><p>The other day there was a call to action on the Wings of Carolina slack. "Is anyone able to help this pilot get one of our planes back?" Seems this newly licensed pilot, (Congrats!) passed his check ride on Friday and the weather was not great so had to leave N69012 at Asheboro. I said I would be glad to help out if the IFR weather cleared by 9am, I could give him a ride over in N8080A.</p>
<p>I booked a 9am-12pm block, and got to the airport at 8:55am. Grabbed the 80A book book and went to the lounge to check weather. Luckily the fog had burned off and the skies were clear! The pilot met me in the lounge, I wrapped up the weather briefing (nothing of note) and we went out to preflight. The left red nav light was out, but that's only required at night, confirmed by asking the other pilot. The fuel truck came and filled us up to the tabs (34 gallons) and we were off.</p>
<p>My flight plans were from KTTA -> KHBI (drop off pilot) -> KEXX then back to KTTA. Just under a 2 hour round trip, which took a bit longer because when we got to the hold short line for RWY03, I realized my iPad wasn't connected to the GPS, so we fiddled with it for a few (no one was behind us, but oof Hobbs running) and couldn't get it working. That's fine, we had our GPS and the pilot knew what KHBI looked like form the air. We took off, departed the pattern to the north, got to a little bit higher then turned west.</p>
<p>The pilot was super helpful, having a copilot be able to put in the radios for Siler City as we passed, and the AWOS/CTAFs in for KHBI was actually a really big load off my plate. It's the little things! Flew at about 3000ft over to KHBI because It was only a 20 minute flight so I stayed low. We get to the airport and he lets me know that RWY24 has a papi, so we chose that runway to land on. Land pretty okay, didn't grease it, but it is what it is. Taxi over and get him to his plane. I shut down so he can get out safely, then realize after he got out, I CAN'T OPEN THE DOOR. I had to yell him over to make sure I could open the door again, the bottom latch was stuck! I recorded a video, then instagrammed a <a class="reference external" href="https://www.instagram.com/p/CLnMONphlsu/">fellow pilot who is famous in his circles for getting stuck in his plane and having to call someone down in the area who landed and helped him out</a>. Told him he's not alone, hah.</p>
<p>Anyway, I was able to open the door so I felt like it was okay if I crashed and had to open the door myself. I turn the plane back on, plug in my Stratux for ADSB on iPad, and get ready to take off. I taxi over to the runway (took 24 again out) and hold short for a taildragger and another plane to land. I then take off and woahh there were like 80 birds at the end of the runway at like 300ft up. Luckily I was able to scare them and flew above them, then departed to the right (west) and headed to KEXX. Luckily thr CTAF is the same for both (122.8) so I was able to hear the traffic without fiddling with the radio. I set the AWOS beforehand, so I checked that when I was close, and still winds calm, not bad.</p>
<p>Got 10 miles, out, announced position, 5 miles out, announced I was entering the downwind at a 45. I was on the down when a pilot to my right asked if I was on base, kind of weirded out because I wasn't so I was vigilant and said I was on downwind (again…). Then called base, and final and landed. Final was neat at Davidson county, had to fly over a factory, which was kind of cool.</p>
<p>Landed, taxied back to the runway and lined up and waited again. The taildragger had followed me! Saw him land as I was at the hold short line. Waited for a jet to land and then off I went! I climbed out, departed to the east, and climbed up to 5500msl. Held 5500 very well this trip, the air was super calm and I'm getting better at electric trim, much easier than just wheel trim.</p>
<p>At one point I turn the auto pilot on, with it set to 109° and 5500msl, but for some reason it made me descend and banked me left 45° and I wasn't liking that so I disengaged it. I climbed up back to 5500' for the rest of my trip. By the time I was back to KTTA, the winds still favored RWY03, so I joined the pattern on a 45° entry, and landed smoothly. At around 3000msl descending near Ashbury it was a bit bumpy, but that's the only turbulence I experienced. Such a nice morning.</p>
<p>Well I get back to the airport and land and shut down… I'm stuck again! Door won't open. There was no one around I could yell to, so I called the front desk, no answer. I tried again, using different pressures and trying to see if there was like a little latch that didn't hook. I fiddled for a few minutes and was able to get out, I guess I got it just right.</p>
<p>I then cover the plane, and squawk the door not opening, and posted the video on slack. The next pilot replied to my slack post later that evening that he had no problems. Maybe I'm just door cursed.</p>
<p>Lauren and I then went to lunch before my next flight where I was taking her up in a Cessna 152 (N89333). Well we do the preflight dance, go taxi and take off. At like 300' above the runway, the damn pilot side door opens up! It's fine but I don't want to worry my wife, on her second flight with me ever, so I have her hold the door closed while I go finish the pattern loop (I was doing that anyway). Well MORE fun happened the pilots PTT(push to talk) button got stuck off, so I had to use her microphone to talk, (I could have just switched the plugs, but we were in the base turn). I kind of overshot base so had to over correct a little bit, but we landed okay, taxied back and agreed we were discontinuing our flight. We could have gone back up and carried on - we had the allotted time - but you know what? The rest of the flight wasn't in the cards.</p>
<p>Video of me trapped in The Warrior - <a class="reference external" href="https://youtu.be/0uAL30nCuYE">https://youtu.be/0uAL30nCuYE</a>.</p>
<div class="figure">
<img alt="Selfie in the cockpit, me wearing a blue shirt." src="https://tyrel.dev/blog/images/2021/08/08_cockpit_selfie.jpg" />
<p class="caption">Cockpit Selfie</p>
</div>
<div class="figure">
<img alt="A look at the ground behind the left wing" src="https://tyrel.dev/blog/images/2021/08/08_behind_left_wing.jpg" />
<p class="caption">A look at the ground behind the left wing</p>
</div>
<div class="figure">
<img alt="Looking at a runway in the distance, not sure which one now." src="https://tyrel.dev/blog/images/2021/08/08_behind_right_wing.jpg" />
<p class="caption">Looking at a runway in the distance, not sure which one now.</p>
</div>
<div class="figure">
<img alt="Looking in front of the left wing, at a highway, the sky is hazy" src="https://tyrel.dev/blog/images/2021/08/08_hazy-highway.jpg" />
<p class="caption">Looking in front of the left wing, at a highway, the sky is hazy</p>
</div>
<div class="figure">
<img alt="Hazy sky with a runway a couple miles away." src="https://tyrel.dev/blog/images/2021/08/08_hazy_runway.jpg" />
<p class="caption">Hazy sky with a runway a couple miles away.</p>
</div>
Cessna 152 Checkout2021-08-04T00:00:00-04:002021-08-04T00:00:00-04:00tyreltag:tyrel.dev,2021-08-04:/blog/2021/08/cessna-152-checkout.html<p>Today I got checked out in a Cessna 152. It was really my first time (besides spin training) flying high wing planes and I was a little nervous. We had a pretty standard pre-flight check, took a lot longer than just a "I'm out here to go on a flight …</p><p>Today I got checked out in a Cessna 152. It was really my first time (besides spin training) flying high wing planes and I was a little nervous. We had a pretty standard pre-flight check, took a lot longer than just a "I'm out here to go on a flight", again because Luke was explaining things to me. There's a lot of differences like with the warrior, all the flaps are on hinges, but in the Cessna, there's one pulley hinge and some rollers you need to check, not just actuation hinges. Pointed out how the landing gear is different, there's plastic fairings that we need to repair quite frequently, and there are no dampeners.</p>
<p>After the pre-flight we took off, Vr is pretty low, at 50 so the plane just wanted to float almost immediately in my opinion. It was also a cooler day outside than my most recent few flights, so that helped too. We took off, headed north to the practice area. I get up to 3000' msl and we level off, trim for cruise flight and lean the engine. This plane also has a vernier mixture control, so it's nice to be able to dial in the mixture, even if there's no on screen display of the gallons per hour like the M20J. There were some clouds at 4000' I think, but they were tiny, and we figured we didn't need to go above them, so we stuck at the 3000' level.</p>
<p>This is where Luke says to just take the plane and do tiny things with it, like turn it uncoordinated, see how much rudder I'd need to do a turn. So I did that for a bit, did a standard rate turn to the left, and we switched to some 30° turns, leveled off then did two 45° turns, back to back. I hit my prop wash at the end of the second one, that always feels great. The first few turns were gross, this plane has old cables for the ailerons, so there's a lot of play when turning, so it took a few tries to be able to maintain altitude and find that comfortable dead spot with cable tension. The last plane (M20J) I flew, everything was much stiffer for turning.</p>
<p>After the few turns, he said "oh no, there goes your engine" and throttled to idle. I looked around for a place to land, and didn't see one immediately. One thing I could have done better is look out the back window, because the 152 actually has one! Well I found a spot to "crash land" so I brought the plane to Vglide (60) and slowly descended towards the place to land. I guess in my "everything is okay" did the A B part of the emergency checklist, but I didn't do ABCDE, so we did it again. This time I:</p>
<ul class="simple">
<li>A- pitched for best airspeed (60kts)</li>
<li>B- found the best place to land</li>
<li>C - Checklist, pretended to check fuel, master, key, primer, etc.</li>
<li>D - pretended to switch to 121.5mHz and declare an emergency</li>
<li>E- executed the landing, and prepared to exit (opened the door, turn off mixture, etc so the plane doesn't blow up and I can get out)</li>
</ul>
<p>and found a cute little field to land in. That was successful so we headed back towards the airport.</p>
<p>At the airport everything was standard traffic pattern. There were three of us in the pattern, one tail dragger that was always just behind us, really friendly guy who liked chatting on frequency, and one low wing - maybe a warrior? - I can't remember. Well I did six landings. One normal landing, three short field (the last one I did great, landed on numbers and was done before A2 taxiway entrance) and then a few more normal.
Luke said that I was death gripping the yoke, so he did one lap around the airport - where he trimmed and only used his two fingers lightly to move the yoke. Watching him do that, I copied and did a lap around the pattern the same, much easier this time.</p>
<p>We then landed, taxied back and went to park. Parking is WILD, you sit on the tail and then back then walk backwards to get the plane in place. I guess when your plane's max ramp weight is 1675lbs, that's easy to do! Three times as much as my motorcycle… After we chatted, he said he feels safe with me flying it, and I agreed. "I feel safe, but not super comfortable, but that only comes with time so I feel safe to take it up and get more comfortable".</p>
<p>I then had to fill out the quiz, and scanned it, then emailed it to him.</p>
<div class="figure">
<img alt="Picture of me in the cockpit being a weirdo with a big smirky mouth" src="https://tyrel.dev/blog/images/2021/08/04_me-being-weird.jpg" />
<p class="caption">Picture of me in the cockpit being a weirdo with a big smirky mouth</p>
</div>
<div class="figure">
<img alt="The cockpit of a Cessna 152, yokes, gauges, etc. Outside you can see other planes on the ramp." src="https://tyrel.dev/blog/images/2021/08/04_cessna-152-cockpit.jpg" />
<p class="caption">The cockpit of a Cessna 152, yokes, gauges, etc. Outside you can see other planes on the ramp.</p>
</div>
<div class="figure">
<img alt="Just a 3d diagram of the flight, not sure what I was focusing on here." src="https://tyrel.dev/blog/images/2021/08/04_3d-track-1.jpg" />
<p class="caption">Just a 3d diagram of the flight, not sure what I was focusing on here.</p>
</div>
<div class="figure">
<img alt="Just a 3d diagram of the flight, not sure what I was focusing on here." src="https://tyrel.dev/blog/images/2021/08/04_3d-track-2.jpg" />
<p class="caption">Just a 3d diagram of the flight, not sure what I was focusing on here.</p>
</div>
Mooney M20J checkout... Sort of2021-07-25T00:00:00-04:002021-07-25T00:00:00-04:00tyreltag:tyrel.dev,2021-07-25:/blog/2021/07/mooney-m20j-checkout-sort-of.html<p>Tuesday night I emailed my instructor saying "Hey the Mooney is available Sunday afternoon, could we start getting me ready for a checkout", and he said "sure, book it", so I did. Then last night I started reading through the POH. There's a lot of neat differences. Cowl flaps, retractable …</p><p>Tuesday night I emailed my instructor saying "Hey the Mooney is available Sunday afternoon, could we start getting me ready for a checkout", and he said "sure, book it", so I did. Then last night I started reading through the POH. There's a lot of neat differences. Cowl flaps, retractable landing gear, constant speed prop. I definitely wanted to start getting my complex rating. I read through that POH, and look up a lot of videos on how to work the prop. I'm used to just throttle and mixture in the Warriors, but this has throttle, manifold pressure, and mixture levers. I learned the cool parts about how the oil pressure, and springs set the propeller angle.</p>
<p>So I get to the airport at about 1pm, and we start talking about what I know. I explained how the undercarriage stuff works, under 132kias for lowering, and under 107kias for raising, and learned from Luke that there's two different speeds because of gravity and fighting gravity. We spend the first hour talking about why you need different manifold pressures. Then we head out to the plane and do a very comprehensive pre-check. I learned that the M20J doesn't have a trim tab, the whole tail raises and lowers on a pivot, NEAT! The flaps are suuuper wide and the ailerons are kind of thin. The landing gear was an interesting inspection because of how the poles and hinges open/close the gear.</p>
<p>After the pre-flight we get flying. Things are pretty similar on the ground, as the propeller lever is pushed all the way forward, and the throttle is the main control. Luke points out that the propeller is rounded, so there's an RPM range that you shouldn't idle in, (1550-1950ish), vs the squarer prop that the other Mooney had.</p>
<p>We take off, and one thing I check different is if I have available runway left, and when I don't, I can raise the landing gear at that point. It was pretty cloudy, so we had to climb up to 5500′, and eventually we hit 7000′ to jump over some clouds. First time really being above clouds this much, for the first hour we were over them. For the climb out he told me to set the plane to "twenty-five squared" - 2500rpm and 25in/hg. And every so often I would need to add more manifold pressure because of the atmosphere lapse rate while climbing.</p>
<p>We got up to 6000′ ish, initially and then did some straight and level flying, super smooth above the clouds today. Then we did some 30 degree turns, did a few of those okay. When we did 45 degree turns, my first left one was kind of bad - lost a lot of altitude. One to the right was okay, and then the third one to the left I kept my altitude right.</p>
<p>After turns we did slow flight, and it's pretty much the same controls. The power off stalls I was I guess putting in unconscious left aileron, because we kept starting to spin a little to the left, next time I fly I definitely need to practice that more.</p>
<p>Well we ended up going pretty far east, at one point we were like 15 miles southeast of RDU, felt uncomfortable not talking to anyone there, but I guess it was out of the normal flight path for the jets so we were fine.We ended up needing to head back to the airport for 4pm, so I put the plane in a 750fpm descent while heading back, at one point I was able to punch through the clouds at around 2500′, and we joined the traffic pattern on the 45 for downwind on 21. I landed VERY well he said. He said that people transitioning from Warriors to Mooneys land hard because the M20J is a lot lower of a plane than the PA28, so they flare early and land hard. I didn't flare early both times we landed so he said we could just end for the day. I always love when instructors say I do well with landings!</p>
<p>We then get back to the airport to do some payments and find out "Oh, Tyrel needs 250 hours without an instrument rating, to fly the M20J, or 150 with an instrument rating." So Oops, I guess it'll be a while before I can rent this, and get an actual check out.</p>
First Flight With My Dad2021-07-10T00:00:00-04:002021-07-10T00:00:00-04:00tyreltag:tyrel.dev,2021-07-10:/blog/2021/07/first-flight-with-my-dad.html<p>I actually did a flight on July 7th, but It was only one lap around the pattern. It was 95°F here, and I knew I wouldn't be in the right mind to continue my flight as I got to the traffic pattern after take off, so I just called …</p><p>I actually did a flight on July 7th, but It was only one lap around the pattern. It was 95°F here, and I knew I wouldn't be in the right mind to continue my flight as I got to the traffic pattern after take off, so I just called crosswind, and headed back to land, a whopping 0.3 hour flight time! So that's what just one melting traffic pattern loop looks like!</p>
<div class="figure">
<img alt="GPS track around the TTA Airport, just one pattern loop" src="https://tyrel.dev/blog/images/2021/07/10_loop.png" />
<p class="caption">GPS track around the TTA Airport, just one pattern loop</p>
</div>
<p>Anyway! My father was excited to fly with me, we took N2114F up. We intended to go from KTTA-SDZ-KRCZ, but it was still hot, so we decided to just do SDZ and back.</p>
<p>We take off, and do one lap around the pattern - I want this to be my standard when I bring new people up, it gives them a way out to say "GET ME OFF PLEASE". After that one pattern lap we headed towards SDZ. It was a really smooth flight, we flew at 4500 feet to the VOR, then tried to get to 7500 feet on the way back (We had time, I wanted to climb) but it was just SO HOT that we ended up just staying at 5500feet, the climb was taking forever!</p>
<p>When we were 10mi from the airport, it started getting super busy (BBQ day at the airport!), so we decided to waste some time in the practice area and I did a steep turn for him to waste more time. After that the traffic died down so we headed back to KTTA. I did a touch and go, a regular landing, and then being that there was no other traffic, practiced an emergency engine out procedure (what I failed on my checkride) and that went smoothly.</p>
<p>So nothing super special about this trip, besides it was my first time flying my father.</p>
<div class="figure">
<img alt="Me in the passenger seat climbing in and getting ready to go." src="https://tyrel.dev/blog/images/2021/07/10_tyrel-in-passenger-seat.jpg" />
<p class="caption">Me in the passenger seat climbing in and getting ready to go.</p>
</div>
<div class="figure">
<img alt="Me and Dad sitting in airplane, somewhere above North Carolina" src="https://tyrel.dev/blog/images/2021/07/10_tyrel-and-tony.jpg" />
<p class="caption">Me and Dad sitting in airplane, somewhere above North Carolina</p>
</div>
<div class="figure">
<img alt="Me pointing out the tiny window on the airplane." src="https://tyrel.dev/blog/images/2021/07/10_tyrel-pointing-out-window.jpg" />
<p class="caption">Me pointing out the tiny window on the airplane.</p>
</div>
<div class="figure">
<img alt="Part of the dashboard, and CO detector, plus a blurry propeller in the background" src="https://tyrel.dev/blog/images/2021/07/10_propeller-and-dashboard.jpg" />
<p class="caption">Part of the dashboard, and CO detector, plus a blurry propeller in the background</p>
</div>
<div class="figure">
<img alt="Looking out the right window, the sky is hazy, part of a wing can be seen" src="https://tyrel.dev/blog/images/2021/07/10_right-side-haze.jpg" />
<p class="caption">Looking out the right window, the sky is hazy, part of a wing can be seen</p>
</div>
<div class="figure">
<img alt="Inside the cockpit, profile view of me, and part of my father's head." src="https://tyrel.dev/blog/images/2021/07/10_tony-and-tyrel.jpg" />
<p class="caption">Inside the cockpit, profile view of me, and part of my father's head.</p>
</div>
First Flight With Lauren2021-06-14T00:00:00-04:002021-06-14T00:00:00-04:00tyreltag:tyrel.dev,2021-06-14:/blog/2021/06/first-flight-with-lauren.html<p>Today was my first flight with my wife, Lauren. She is my first real passenger ("technically your DPE is, blah blah"). I didn't know what to expect, I knew she had been up in planes before - she's gone skydiving, something I will probably never do. I'd rather be the person …</p><p>Today was my first flight with my wife, Lauren. She is my first real passenger ("technically your DPE is, blah blah"). I didn't know what to expect, I knew she had been up in planes before - she's gone skydiving, something I will probably never do. I'd rather be the person to fly the "JUMPERS AWAY OVER X" than be a jumper. I don't want to make this entire blog about Lauren, although we all know I could. So I will try to keep it 50% airplane talk!</p>
<p>We scheduled a 12pm flight, with an estimate of me probably taking off at 12:30 (took off at 12:40 for pattern and 12:49 to KDAN), Cross country from KTTA to KDAN and back to KTTA. I wanted to fly at 4500'msl there, and 5500'msl back, following the East is Odd +500 and West is Even +500, because it was a 358°ish flight there and a 178°ish back (that's from airport center to airport center, but I did some cloud avoidance, a few times (like, the whole time) so I was pretty off course, but ForeFlight will get me there. I brought my Stratux because I remembered that N8116J didn't like bluetooth and ADSB so my iPad wouldn't connect to the plane. I also bought a new GPYes unit that I wanted to try out, and it worked great, no more magnetic GPS sticking to random magnetic things, everything is self contained in my Stratux!</p>
<p>Wind was good for RWY21, so I had to taxi via Taxiway Alfa to get there, still not a fan of how Wings of Carolina has us do a runup off the taxiway, something I need to get used to at a busier airport than KEEN. Well, we took off and I did a loop of the pattern - I wanted to gauge Lauren's stomach, and my flying abilities to make sure she was okay to fly. The Pressure Density was 2,800'-2,900, which for a 255'msl runway, oooof. After the second takeoff we flew out to the traffic pattern then departed to the north! We climbed up to 3000, the cloud layer was about 3800 so I chose to stay under it, vs fly over the top and chance not having an opening. It was pretty bumpy - there's a convective SIGMET over like the whole east coast - so I knew it was going to be a little bumpy. Lauren was taking pictures the whole time (she took 47! I'll share some) and got some fun ones of me. In my pictures I look like I'm super concentration face, but she got a couple of me smiling.</p>
<p>Nearing KDAN I realized that the CTAF was Actually a CTAF (Common Traffic Advisory Frequency) and that KDAN had a tiny little tower. I called 15 to the south (because I heard a lot of traffic) and the nice person on the frequency came in and said something like 16J there's two planes on the taxiway, one in the pattern and one just departing, which now I understand why people come on our Unicom back at KEEN and keep saying "KEEN Unicom please advise". Well we got closer I said I was flying over midfield to make a tear drop into the downwind. I did as such, flew over at 2500', did the tear drop, down to 1500' and landed. We then taxied to the FBO and shut down [my outside camera picked THREE seconds after I shut down to die, perfect timing]. The nice ramp assistant gave us a ride to the FBO in the golf cart so we could pee.</p>
<p>Gatorade passed through our system completely we headed out to the plane. We got some weird looks from the people chilling in the FBO (Enterprise people, and like a grandfather? idk) for wearing masks. Yes we're both vaccinated, but I sure as hell don't know if the random people I run into are, gotta keep you safe!</p>
<p>We took a few pictures, and then went and started up the plane. When I was doing my run up, I didn't have the mixture full rich, was still leaned for ground ops, so when I did the mag drop I heard a backfire, was like OH SHIT and realized I was leaned. Put in full mixture, and did the mag drops again, ~150rpm loss and we were good to go. Lesson learned here is even if there are shortcuts for when the engine is hot, and you just land for 10 minutes, probably good to go through the full checklist even just skimming it.</p>
<p>We took down taxiway Alfa again and got to RWY02. Side note, KEEN has 02-20 and 14-32, this airport KDAN has 02-20 and 13-31 so when I came upon it, I was like "holycrap I feel like I'm home". Anyway - when we got to the end of the taxiway there were two entrances only like 100ft apart to the runway, it was weird. We had to wait like 10 minutes to take off, because there were 4 planes doing touch and goes and they were perfectly spaced to give me like NO time to take off. I know how long of a runway I need to be safe, but with a 2900' Density Altitude, I know I needed a longer ground roll, so finding time to slip in to where I knew I could take off was annoying. Finally one of the guys on downwind said "Plane waiting at 02, I'm extending my downwind a little bit to give you some time to take off, so I rolled out, thanked him and took off to the north a bit. I extended my upwind and gave the pattern a large buffer, then took off to the south.</p>
<p>Lauren seemed to be getting tired by like 75% of the trip back so maybe next time I'll take the plane for four hours, and then we can take a full hour between legs, instead of 15 minutes. I did three hours, because I planned for a longer break between - we even bought zucchini bread I made last night! - but I threw in that extra pattern lap before we left the airport, so that added like 15 minutes, and I couldn't find the flight book so that took some extra time.</p>
<p>When we were on the way back to Raleigh Exec, we saw another plane like 500' below us so I flew above them and circled past them then tried to come in behind them but I have no idea where they went when I flew over them, my ADSB decided to not pick them up, so I did a larger right turn so I knew I'd avoid them. It was weird and I'll have to check FlightRadar24 playback to see - but it's not up now for some reason. After that steep turn I entered the downwind and landed. We landed with 2 minutes to spare, but it took a few minutes to shut down, and tie down. Glad someone wasn't right after us (if someone was I would not have gone as far of course). Covered the plane, paid the $260 for 2.3 hours of flying, put the book away and headed home.</p>
<p>The return trip took about exactly the same length. We left KTTA at 12:49, landed at KDAN at 1:36 (49min). Then with waiting for takeoff, we left KDAN at 2:07 and landed at 2:57(50min). On the flight back, it was a bit bumpier, the clouds were still scattered at about 3800 but some were darker than others so I flew around them. I wonder if when I finally fly IFR if I'll fly a lot straighter, instead of just "oops avoid that cloud!" that remains to be seen.</p>
<div class="figure">
<img alt="Tyrel sitting in passenger seat, waiting for Lauren to get into plane." src="https://tyrel.dev/blog/images/2021/06/14_tyrel-passenger-seat.jpg" />
<p class="caption">Tyrel sitting in passenger seat, waiting for Lauren to get into plane.</p>
</div>
<div class="figure">
<img alt="Power line swath through the trees, with a nuclear power plant in the disance." src="https://tyrel.dev/blog/images/2021/06/14_powerlines.jpg" />
<p class="caption">Power line swath through the trees, with a nuclear power plant in the disance.</p>
</div>
<div class="figure">
<img alt="Tyrel looking to the left, watching for traffic" src="https://tyrel.dev/blog/images/2021/06/14_tyrel-looking-left.jpg" />
<p class="caption">Tyrel looking to the left, watching for traffic</p>
</div>
<div class="figure">
<img alt="Danville, Virginia airport, from above." src="https://tyrel.dev/blog/images/2021/06/14_danville-VA.jpg" />
<p class="caption">Danville, Virginia airport, from above.</p>
</div>
<div class="figure">
<img alt="Clouds and the right wing of a Piper Warrior airplane" src="https://tyrel.dev/blog/images/2021/06/14-clouds.jpg" />
<p class="caption">Clouds and the right wing of a Piper Warrior airplane</p>
</div>
<div class="figure">
<img alt="Tyrel pulling the plane forward, to get it lined up to push back for parking." src="https://tyrel.dev/blog/images/2021/06/14_tyrel-pulling.jpg" />
<p class="caption">Tyrel pulling the plane forward, to get it lined up to push back for parking.</p>
</div>
<div class="figure">
<img alt="TTA Airpot Diagram" src="https://tyrel.dev/blog/images/2021/06/14_TTA.gif" />
<p class="caption">Airport Diagram of TTA</p>
</div>
<div class="figure">
<img alt="DAN Airport Diagram" src="https://tyrel.dev/blog/images/2021/06/14_DAN.gif" />
<p class="caption">Airport Diagram of DAN</p>
</div>
WOC Final Checkout Ride2021-06-07T00:00:00-04:002021-06-07T00:00:00-04:00tyreltag:tyrel.dev,2021-06-07:/blog/2021/06/woc-final-checkout-ride.html<p>I can finally rent planes through Wings of Carolina! Flew today and the CFI passed me.</p>
<p>I was a bit nervous about the North Carolina weather so I text the instructor to see if we could leave an hour early for the controlled airspace portion of the checkout. He said …</p><p>I can finally rent planes through Wings of Carolina! Flew today and the CFI passed me.</p>
<p>I was a bit nervous about the North Carolina weather so I text the instructor to see if we could leave an hour early for the controlled airspace portion of the checkout. He said let's shoot for 3:30, we took off at 4 because he gave me a bit of ground instruction on the systems in N8116J. (Also we couldn't figure out the radio, so had to push a few buttons to get comms working. It seems when 16J was in the shop, they had undone all the COM1 COM2 selection buttons, which caught us off guard.)</p>
<p>We took off, then flew south to Fayetteville (KFAY), Luke showed me the auto pilot, how to climb, descend, turn to a heading, so we enabled that on the flight to KFAY. 20 miles north, called approach, I fumbled my radio A LOT. I definitely need more controlled airspace practice. ATC Talks FAST. It's hard to fly the plane and copy things down, I think I need to start using a pen and paper, not Foreflight for ATC remarks, it'll be much easier. I put a FieldNotes book in my flight bag the other day, time to use it! After a few more fumbles (I copied back the altimeter, not the altitude, talk about stress!) I was cleared to land. Squawked 0210 and landed on runway 22 almost straight in, I took a right handed base, it was weird! There were no commercial flights so they were at a lull of traffic which is why I just got the RWY22 CLEAR TO LAND. Unfortunately (or Fortunately..) LIVE ATC and KFAY approach are down so I have no recording of my fumbles. Would have been nice to hear them again so I could learn.</p>
<p>We landed, taxied back to runway 22, and then said we were taking off VFR. Squawked 0212 now and departed, stayed runway heading until they said turn right heading 320 staying at or below 2500. Then a bit later they said fly flight plan heading I turned 355 and then we finally got out of their airspace. There's two airports there, KFAY and Simmons Army Airfield (KFBG), so their airspace looks like a cell dividing, two round circles and of course we bisected it the long way.</p>
<p>Out of their airspace Luke said "I have the controls", then banked hard 60 degrees right, and pointed down. He said "If you're ever VFR and inadvertently hit IMC weather, and get into an unusual attitude, hit this button [LVL] and presto, the wings will level." which of course they did. Auto pilot is still wild to me, I never flew with it in N43337, so it's going to take a lot to get used to, but I'm sure I'll start loving it.</p>
<p>We then called 10mi on the 45 for RWY22, did a touch and go, (I came in a little higher than I wanted I still need to learn the sight pictures for this airport, but oof it's FLAT still.) took off did the pattern once more and did a full stop.</p>
<p>On this flight, I learned the GPS a little bit more, the touch screen menu will make selecting comms so much easier, glad they have these consistently in all their planes. I also learned AutoPilot, I need to find a flight sim model with this auto pilot (I did get a Logitech/Saitek Multi Panel with AutoPilot on it this weekend, so good timing!) that I can learn how the IAS, ALT, VS, HDG buttons work better. HDG and ALT I get, those just hold heading and alt, but airspeed and vertical speed climbs are still magical!</p>
<p>Luke then signed me off, so I'm clear to rent any of the PA-28-161 that they have at Wings of Carolina. I told him I want to learn how to fly the Mooney M20J soon, I'm excited to get my complex endorsement.</p>
May 27, 2021 - Second flight as a PPL2021-05-27T00:00:00-04:002021-05-27T00:00:00-04:00tyreltag:tyrel.dev,2021-05-27:/blog/2021/05/may-27-2021-second-flight-as-a-ppl.html<p>I was able to sneak in a cooler morning flight in N8080A. I took my motorcycle to the airport, but forgot my yoke mount so I was iPad-less. We had to wait for a long while to get fuel because the services truck was filling the HU-16 that had arrived …</p><p>I was able to sneak in a cooler morning flight in N8080A. I took my motorcycle to the airport, but forgot my yoke mount so I was iPad-less. We had to wait for a long while to get fuel because the services truck was filling the HU-16 that had arrived a few days beforehand. There was also a C-27 taking off, super cute, Feels like a baby C-130!</p>
<div class="figure">
<img alt="Grumman HU-16 Albatross" src="https://tyrel.dev/blog/images/2021/05/20210527_hu16.jpg" />
<p class="caption">Grumman HU-16 Albatross</p>
</div>
<div class="figure">
<img alt="Alenia C-27J Spartan" src="https://tyrel.dev/blog/images/2021/05/20210527_c27.jpg" />
<p class="caption">Alenia C-27J Spartan</p>
</div>
<p>We went up, took a few minutes to do another 45° bank turn. I did that a little better, less shaky. Then he had me practice some emergency descents. I didn't do as well at those as I should. I need faster ADM skills. That will come with time, one of my big weaknesses is emergency things, so it's one thing I'm excited to start practicing.</p>
<p>After the emergency procedures, he had me practice rudder control. Putting in enough rudder while turning to hold on target before the turn and rolling out while pointing at a target - but rolling out. I'm much better at rolling out than rolling in, so I'm excited to have a new procedure to practice. One of the things I like a lot about this CFI giving me the checkout to borrow planes, is that he's young. He doesn't have these really old ways of thinking about things and I appreciate that.</p>
<p>After the higher altitude stuff, he wanted to see some short-field landings. The first time in the pattern we had to extend our downwind A LOT, there were three people on long finals, so our downwind was funky and long. I'm still getting used to this plane and the runway so by the time we were about to touch down, the other plane in front of us was still on the runway (albeit turning onto taxiway A3 a mile down the runway) so he said go around, as technically the other plane was still on the runway and things could go wrong even with that far separation.</p>
<p>Did a go around and the next two short fields were okay. We got off at Taxiway A2 I think, and then took taxiway A back to the ramp. When we got to the intersection with A and A1 there was another plane getting ready to cross the threshold, but it was a Cessna 172… high wing! They didn't notice that there was a taildragger apparently norad coming on a 45° final, not straight in and almost rolled into the runway. Luckily the CFI I was with shouted STOP on the CTAF and they stopped, seconds before an incursion! EEEEK!</p>
<p>Wrapped up, and planned our third checkout ride, it'll be down to KFAY, a controlled field. Should be on June 2nd and then I'll be cleared to rent the planes and feel like a full member of the Wings of Carolina!</p>
<p>Unfortunately no GPX file, but I have a screenshot.</p>
<div class="figure">
<img alt="GPX Track across the Raleigh Durham area west of Jordan Lake" src="https://tyrel.dev/blog/images/2021/05/20210527_track.png" />
<p class="caption">GPX Track across the Raleigh Durham area west of Jordan Lake</p>
</div>
May 26, 2021 - First flight as a PPL2021-05-26T00:00:00-04:002021-05-26T00:00:00-04:00tyreltag:tyrel.dev,2021-05-26:/blog/2021/05/may-26-2021-first-flight-as-a-ppl.html<p>On April 26th, I took my first flight as a Private Pilot. I was flying with a CFI for a checkout to rent planes from the Wings of Carolina club, so the flight was nothing really to talk much about. It was really hot, and my first flight in almost …</p><p>On April 26th, I took my first flight as a Private Pilot. I was flying with a CFI for a checkout to rent planes from the Wings of Carolina club, so the flight was nothing really to talk much about. It was really hot, and my first flight in almost 5 months, so I was definitely rusty.</p>
<p>Mostly using this flight as a stepping stone to try out the GPX viewer§. I flew a PA28-161 - Warrior II - N8080A for One hour. Flew from KTTA, up north west a bit, did a few steep turns, then back to KTTA.</p>
<p>§ - Not ported over, no JS on this blog.</p>
Too many open files2015-01-28T04:02:00-05:002015-01-28T04:02:00-05:00tyreltag:tyrel.dev,2015-01-28:/blog/2015/01/too-many-open-files.html<p>When I worked at Propel Marketing, we used to outsource static websites to a third party vendor, and then host them on our server. It was our job as developers to pull down the finished website zip file from the vendor, check it to make sure they used the proper …</p><p>When I worked at Propel Marketing, we used to outsource static websites to a third party vendor, and then host them on our server. It was our job as developers to pull down the finished website zip file from the vendor, check it to make sure they used the proper domain name, (they didn't a lot of the time,) and make sure it actually looks nice. If these few criteria were met, we could launch the site.</p>
<p>Part of this process was SCPing the directory to our sites server. The sites server was where we had Apache running with every custom static site as a vhost. We would put the website in <tt class="docutils literal">/var/www/vhosts/domain.name.here/</tt> and then create the proper files in sites-available and sites-enabled (more on this in another entry). After that the next step was to run a checkconfig and restart Apache.</p>
<p>Here's where it all went wrong one day. If I can recall correctly, my boss was on vacation so he had me doing a bit of extra work and launching a few more sites than I usually do. Not only that, but we also had a deadline of the end of the month which was either the next day, or the day after. I figure I'll just setup all mine for two days, and then have some extra time the next day for other things to work on. So I started launching my sites. After each one, I would add the domain it was supposed to be at into my <tt class="docutils literal">/etc/hosts</tt> file and make sure it worked.</p>
<p>I was probably half way done with my sites, and suddenly I ran into one that didn't work. I checked another one to see if maybe it was just my network being stupid and not liking my hosts file, but no, that wasn't the problem. Suddenly, EVERY SITE stopped working on this server. Panicking, I delete the symlink in sites-enabled and restart Apache. Everything works again. I then proceed to put that site aside, maybe something in the php files breaks the server, who knows, but I have other sites I can launch.</p>
<p>I setup the next site and the same thing happens again, no sites work. Okay, now it's time to freak out and call our sysadmin. He didn't answer his phone, so I call my boss JB. I tell him the problem and he says he will reach out to the sysadmin and see what the problem is, all the while I'm telling JB "It's not broken broken, it just doesn't work, it's not my fault" etc etc. A couple hours later, our sysadmin emails us back and says he was able to fix the problem.</p>
<p>It turns out, there's a hard limit to the number of files your system can have open per user, and this was set to 1000 for the www-data user. The site I launched was coincidentally the 500th site on that server, each of them having an access.log and an error.log. These two files apparently constantly open on each site for apache to log to. He was able to change www-data's ulimit to a lot higher, (I don't recall now what it was) and that gave a lot more leeway in how many sites the sites server could host.</p>
Python Debugger2015-01-13T04:02:00-05:002015-01-13T04:02:00-05:00tyreltag:tyrel.dev,2015-01-13:/blog/2015/01/python-debugger.html<p>When I worked at Propel Marketing, my dev team used to have presentations on things they loved. I love the Python debugger. It's very useful and I believe a proper understanding of how to use a debugger, will make you a better programmer. Here is a presentation on the debugger …</p><p>When I worked at Propel Marketing, my dev team used to have presentations on things they loved. I love the Python debugger. It's very useful and I believe a proper understanding of how to use a debugger, will make you a better programmer. Here is a presentation on the debugger I made for my team. <a class="reference external" href="https://prezi.com/cdc4uyn4ghih/python-debugger/">https://prezi.com/cdc4uyn4ghih/python-debugger/</a></p>
SSH Agent on "boot"2015-01-09T04:03:00-05:002015-01-09T04:03:00-05:00tyreltag:tyrel.dev,2015-01-09:/blog/2015/01/ssh-agent-on-boot.html<p>I had a friend complain that he had to keep adding his ssh key to his ssh-agent every time he rebooted. I have a really easy bit of shell code you can put into your .bashrc or your .zshrc file:</p>
<div class="highlight"><pre><span></span><span class="nv">SSHKEYFILE</span><span class="o">=</span>/path/to/your/ssh/key/file
ssh-add -l <span class="p">|</span> grep …</pre></div><p>I had a friend complain that he had to keep adding his ssh key to his ssh-agent every time he rebooted. I have a really easy bit of shell code you can put into your .bashrc or your .zshrc file:</p>
<div class="highlight"><pre><span></span><span class="nv">SSHKEYFILE</span><span class="o">=</span>/path/to/your/ssh/key/file
ssh-add -l <span class="p">|</span> grep -q <span class="nv">$SSHKEYFILE</span>
<span class="nv">RC</span><span class="o">=</span><span class="nv">$?</span>
<span class="k">if</span> <span class="o">[</span> <span class="nv">$RC</span> -eq <span class="m">1</span> <span class="o">]</span>
<span class="k">then</span>
ssh-add -t <span class="m">86400</span> <span class="nv">$SSHKEYFILE</span>
<span class="nb">echo</span> <span class="s2">"Added key internal to keychain."</span>
<span class="k">fi</span>
</pre></div>
<p>This will check every time your bash or zsh rc file is sourced for your key in the currently added keys, and if it's not, it will add it.</p>
<p>This has the benefit of not requiring you to put in your password every time you connect to a remote machine if you password your ssh keys (which you should).</p>
First day back in Java since college2014-10-01T04:03:00-04:002014-10-01T04:03:00-04:00tyreltag:tyrel.dev,2014-10-01:/blog/2014/10/java.html<p>Recently I decided I wanted to learn Java again. I last programmed in Java when I was in College and that was the main language they taught in. I wouldn't say I was a great Java developer, although I completed every Java course well enough to get an A or …</p><p>Recently I decided I wanted to learn Java again. I last programmed in Java when I was in College and that was the main language they taught in. I wouldn't say I was a great Java developer, although I completed every Java course well enough to get an A or better.</p>
<p>I want to relearn Java because for the past four years I have primarily focused on Python. While it is a great language, I feel I need a change from what I'm focusing on now with primarily web based programming.</p>
<p>I decided to refresh myself with Java and read a "Java for Python developers" guide, which was a great refresher. After that I sat around wondering what to program, inspiration wasn't coming quickly. I settled on a SSH Configuration Manager, which is something I've wanted for a while now.</p>
<p>This Configuration Manager will read in your ~/.ssh/config files, and show you what hosts you have in a GUI interface. The great part of it will be that you can also create new ssh configurations, without having to remember every little detail. There will be a lot of help tooltips, and pre-fills as well. I have a pretty basic idea of what I want it to look like. Ideally a list on the far left with +/- buttons to add a new Host, and to the right of that will be another hierarchy list of all the key groups you can change, with the most common (that I or people I talk to) being in a "General" or "Common" list. To the right of that will be the actual keys and values you change. I think I would like to be able to "favorite" keys that you use frequently. This way when you create a new host entry, you can quickly fill out your usual configurations be it only adding an IdentityFile and User. Another feature I thought of would be copying/templating, for example being able to create a new "work based server" configuration by just copying one you already have.</p>
<p>Some of the options will be a bit tricky, a couple of them are along the lines of allowing "yes", "no", "ask", or an integer, and I haven't figured out exactly how I want to manage that yet.</p>
<p>Currently I have a model that only has getters/setters and toString support, there's a lot of them so it's already a 1050 line file last I checked. Next time I work on this project I want to start with data validation and learning how to write tests in Java. I think learning good BDD or TDD habits while learning a "new" language would definitely benefit me.</p>
Readline2014-06-21T04:01:00-04:002014-06-21T04:01:00-04:00tyreltag:tyrel.dev,2014-06-21:/blog/2014/06/readline.html<p>A lot of times when I stop at someone's computer and help them in the terminal, I use a Readline command and people say "How the heck did you do that?"</p>
<p>Let me first backup and explain what Readline is. From the GNU Readline Documentation - "The GNU Readline library provides …</p><p>A lot of times when I stop at someone's computer and help them in the terminal, I use a Readline command and people say "How the heck did you do that?"</p>
<p>Let me first backup and explain what Readline is. From the GNU Readline Documentation - "The GNU Readline library provides a set of functions for use by applications that allow users to edit command lines as they are typed in." By default, Readline is set up in Emacs mode, no you don't have to have an extra four fingers to use Readline, most of the commands are simple.</p>
<p>Here are a couple of the commands I use daily:</p>
<div class="section" id="movement">
<h2>Movement</h2>
<ul class="simple">
<li>To move to the beginning of a line, you press <tt class="docutils literal"><span class="pre">C-a</span></tt></li>
<li>To move to the end of a line you press <tt class="docutils literal"><span class="pre">C-e</span></tt></li>
</ul>
</div>
<div class="section" id="killing-and-yanking">
<h2>Killing and Yanking</h2>
<ul class="simple">
<li>To cut the rest of the line from where your cursor is, to the end, you press <tt class="docutils literal"><span class="pre">C-k</span></tt></li>
<li>To delete one word you press <tt class="docutils literal"><span class="pre">C-w</span></tt></li>
<li>To paste either of the two previous back you can press <tt class="docutils literal"><span class="pre">C-y</span></tt></li>
</ul>
</div>
<div class="section" id="miscellaneous">
<h2>Miscellaneous</h2>
<ul class="simple">
<li>To clear the screen and get to a fresh start, you can press <tt class="docutils literal"><span class="pre">C-l</span></tt></li>
<li>To end your session you can send a <tt class="docutils literal"><span class="pre">C-d</span></tt> (This will send an end of file character)</li>
<li>To search for a command you typed recently, press <tt class="docutils literal"><span class="pre">C-r</span></tt> and start typing, it will search backwards. <tt class="docutils literal"><span class="pre">C-r</span></tt> again will search for an earlier match.</li>
<li>The inverse of <tt class="docutils literal"><span class="pre">C-r</span></tt> is <tt class="docutils literal"><span class="pre">C-s</span></tt>, they function the same.</li>
<li>To open your <tt class="docutils literal">$EDITOR</tt> to edit the current shell command you wish to write, press <tt class="docutils literal"><span class="pre">C-x</span> <span class="pre">C-e</span></tt></li>
</ul>
<p>Finally, don't forget about <tt class="docutils literal"><span class="pre">C-c</span></tt>. While not specifically Readline, it's very useful because it sends the SIGINT signal to the program, which if just on the command line, will not execute the line you have type, and give you a new line with nothing on it. A nice clean start.</p>
<p>To find out a lot more, read the documentation at <a class="reference external" href="http://www.gnu.org/software/bash/manual/html_node/Bindable-Readline-Commands.html">the Readline Commands Docs</a> I even learned some things while writing this up, apparently pressing <tt class="docutils literal"><span class="pre">C-x</span> $</tt> will list off all the possible usernames. Good to know, and good to always keep learning.</p>
</div>
How to not trigger a post_save in Django, but still modify data.2013-11-13T03:58:00-05:002013-11-13T03:58:00-05:00tyreltag:tyrel.dev,2013-11-13:/blog/2013/11/how-to-not-trigger-a-post_save-in-django-but-still-modify-data.html<p>Recently I have been diving into using signals with Django, which of course are pretty neat.</p>
<p>I am working on a website for work which in the most basicexplanation, is a task management site. Recently I have added in the ability to subscribe to tasks and get emails, I did …</p><p>Recently I have been diving into using signals with Django, which of course are pretty neat.</p>
<p>I am working on a website for work which in the most basicexplanation, is a task management site. Recently I have added in the ability to subscribe to tasks and get emails, I did this by connecting to the post_save signal. I only email out when a task is changed, not created (of course, no one would be subscribed to it). This worked flawlessly and "emails" out to anyone who is subscribed. I say that in quotes, because I haven't actually hooked it up to a real SMTP server, and only use</p>
<div class="highlight"><pre><span></span>python -m smtpd -n -c DebuggingServer localhost:1025
</pre></div>
<p>which will output any emails to stdout. But I digress… A problem arose when I was working on ordering tasks.</p>
<p>I store an integer in the "ordering" column, which any authenticated user can drag the row to a new location and that will reorder the task. I did this after I setup the emailing signal, so I didn't think about an email being sent out for EVERY task being changed.</p>
<p>I tried a lot of different things, and was debating some that would be a bit messy. Among those ideas were trying to store the past values in another table, but that would get expensive fast. The reason I tried this was because I wanted to see if the ordering was the only thing that changed, and if that was the case, not send an email. I eventually found a thread on StackOverflow that says to use update on the queryset to not trigger the signal.</p>
<p>You can do this by doing something like this:</p>
<div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">app.models</span> <span class="kn">import</span> <span class="n">ModelName</span>
<span class="k">def</span> <span class="nf">reorder</span><span class="p">(</span><span class="n">request</span><span class="p">):</span>
<span class="n">new_order</span> <span class="o">=</span> <span class="n">request</span><span class="o">.</span><span class="n">POST</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">'new_order'</span><span class="p">,</span> <span class="kc">None</span><span class="p">)</span>
<span class="n">pk</span> <span class="o">=</span> <span class="n">request</span><span class="o">.</span><span class="n">POST</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">'modelname_pk'</span><span class="p">,</span> <span class="kc">None</span><span class="p">)</span>
<span class="k">if</span> <span class="n">new_order</span><span class="p">:</span>
<span class="n">ModelName</span><span class="o">.</span><span class="n">objects</span><span class="o">.</span><span class="n">filter</span><span class="p">(</span><span class="n">pk</span><span class="o">=</span><span class="n">pk</span><span class="p">)</span><span class="o">.</span><span class="n">update</span><span class="p">(</span><span class="n">ordering</span><span class="o">=</span><span class="n">new_order</span><span class="p">)</span>
</pre></div>
<p>I am not sure if this is the proper way save changes and not trigger a post_save signal, but this is the way that worked for me so I figured I would document this.</p>
Help, I have too many Django ManyToMany Queries [FIXED]2013-08-06T04:00:00-04:002013-08-06T04:00:00-04:00tyreltag:tyrel.dev,2013-08-06:/blog/2013/08/help-i-have-too-many-django-manytomany-queries-fixed.html<p>My boss tasked me with getting the load time of 90 seconds(HOLY CARP!) on one page down. First thing I did was install the Django Debug Toolbar to see what was really happening.</p>
<p>There are currently 2,000 users in the database, the way our model is setup is …</p><p>My boss tasked me with getting the load time of 90 seconds(HOLY CARP!) on one page down. First thing I did was install the Django Debug Toolbar to see what was really happening.</p>
<p>There are currently 2,000 users in the database, the way our model is setup is that a UserProfile can have other UserProfiles attached to it in one of three M2M relations, which in the Django Admin would cause 2,000 queries PER M2M field. This is very expensive as obviously you don't want 10,000 queries that each take 0.3ms to take place.</p>
<p>The solution, after a day and a half of research is to override the <strong>formfield_for_manytomany</strong> method in the Admin class for our UserProfile object.</p>
<p>Our solution is to prefetch for any M2M that are related to the current Model.</p>
<div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">formfield_for_manytomany</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">db_field</span><span class="p">,</span> <span class="n">request</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
<span class="k">if</span> <span class="n">db_field</span><span class="o">.</span><span class="vm">__class__</span><span class="o">.</span><span class="vm">__name__</span> <span class="o">==</span> <span class="s2">"ManyToManyField"</span> <span class="ow">and</span> \
<span class="n">db_field</span><span class="o">.</span><span class="n">rel</span><span class="o">.</span><span class="n">to</span><span class="o">.</span><span class="vm">__name__</span> <span class="o">==</span> <span class="bp">self</span><span class="o">.</span><span class="n">model</span><span class="o">.</span><span class="vm">__name__</span><span class="p">:</span>
<span class="n">kwargs</span><span class="p">[</span><span class="s1">'queryset'</span><span class="p">]</span> <span class="o">=</span> <span class="n">db_field</span><span class="o">.</span><span class="n">rel</span><span class="o">.</span><span class="n">to</span><span class="o">.</span><span class="n">objects</span><span class="o">.</span><span class="n">prefetch_related</span><span class="p">(</span><span class="s2">"user"</span><span class="p">)</span>
<span class="k">return</span> <span class="nb">super</span><span class="p">(</span><span class="n">UserProfileInline</span><span class="p">,</span> <span class="bp">self</span><span class="p">)</span><span class="o">.</span><span class="n">formfield_for_manytomany</span><span class="p">(</span>
<span class="n">db_field</span><span class="p">,</span> <span class="n">request</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
</pre></div>
<p>This goes inside our admin class <strong>UserProfileInline(admin.StackedInline)</strong>. Simple clean and easy to drop into another ModelAdmin with minimal changes.</p>
<p>Other things I pondered was to set all our M2M's as raw_id_fields, then using Select2 or Chosen, query our UserProfiles when the related users were being selected. This would take a lot of load off the initial page load, but is more of a bandaid rather than a real fix.</p>
<p>I tried to override the Admin class's <strong>def queryset(self, request):</strong> but this was not affecting anything.</p>
Getting started in Python Part 12013-07-02T03:59:00-04:002013-07-02T03:59:00-04:00tyreltag:tyrel.dev,2013-07-02:/blog/2013/07/getting-started-in-python-part-1.html<p>I have a friend who is interested in becoming a Python developer. He has some Python experience with CodeAcademy, but he of course wants to take this a step further and develop on his own computer. I figure I'd give him a few pointers, and I know this has been …</p><p>I have a friend who is interested in becoming a Python developer. He has some Python experience with CodeAcademy, but he of course wants to take this a step further and develop on his own computer. I figure I'd give him a few pointers, and I know this has been rehashed a million times, but what the hell, why not blog on it again.
There are a few important things to learn besides the actual language itself. The first I am going to discuss is has to deal with installing packages, then followed up closely with Python's path trickery. Finally I'm going to wrap up by discussing some software related to development, that could be used for any language, but I use daily in my work as a Python Software Engineer. Let's get started.</p>
<div class="section" id="pip">
<h2>PIP</h2>
<p>Python is a wonderful language, but how useful would it be if you had to rewrite everything by hand? Not useful at all. That's why the lovely pip developers were born. <a class="reference external" href="https://pypi.python.org/pypi/pip">PIP</a> (executable pip) is a package manager written for Python. It's very simple to use, and in my opinion is way better than easy_install. To use pip you need to know at a minimum three commands.</p>
<p><tt class="docutils literal">pip install</tt></p>
<p>This command does exactly what it says on the box. It queries PyPI (Python Package Index) and downloads the latest version of the package on the server. It then installs it to your site-packages.</p>
<p><tt class="docutils literal">pip uninstall</tt></p>
<p>This deletes all files associated with the package supplied. 100% simple.</p>
<p><tt class="docutils literal">pip freeze</tt>
This shows what packages are installed on your system and what versions. If you supply ‐‐local it will show what packages are installed in your current environment.
These three commands will get you started with package management, there are more commands you can find by looking through the help documents.</p>
</div>
<div class="section" id="virtualenv">
<h2>Virtualenv</h2>
<p>If you notice I mentioned a current environment in my previous <tt class="docutils literal">pip freeze</tt> explanation, here is why. Python has a default place that it looks when you reference a package. This is generally in something like <tt class="docutils literal"><span class="pre">/usr/lib/python2.7/site-packages/</span></tt> or <tt class="docutils literal"><span class="pre">C:\Python27\lib</span></tt>. There is a set of scripts called <tt class="docutils literal">virtualenv</tt> that creates an environment where you run it with a complete copy of your Python executable, and a blank (unless you copy them over) site-packages directory. You can then install any packages there activate the virtual environment. When activated you use those specific versions, no matter the version of what is installed on your system.</p>
<p>Let's show an example of the first time use of <tt class="docutils literal">virtualenv</tt>:</p>
<div class="highlight"><pre><span></span><span class="gp">$ </span>sudo pip install virtualenv <span class="c1"># Only time you might need sudo, try without first.</span>
<span class="gp">$ </span>virtualenv myenv <span class="c1"># Create the virtual environment</span>
<span class="gp">$ </span><span class="nb">source</span> myenv/bin/activate <span class="c1"># Activate the virtual environment</span>
<span class="gp gp-VirtualEnv">(myenv)</span><span class="gp">$ </span>python -c <span class="s2">"import MYPACKAGE; print MYPACKAGE"</span>
</pre></div>
<p>Notice how it says your package is not in <tt class="docutils literal"><span class="pre">/usr/lib/python2.7/site-packages/</span></tt> ? That's because you're using <tt class="docutils literal">virtualenv</tt> to tell your copied python to use that library instead. There are many reasons you would want to use a virtual environment. The most frequent reason is to preserve version numbers of installed packages between a production and a development environment. Another reason virtualenv is useful if you do not have the power to install packages on your system, you can create a virtual environment and install them there.</p>
</div>
<div class="section" id="virtualenvwrapper">
<h2>Virtualenvwrapper</h2>
<p>After you create a virtual environment, you just run``source bin/activate`` and it will activate the virtual environment. This can get tedious knowing exactly where your virtual environments are all the time, so some developers wrote some awesome scripts to fix that problem. This is called``virtualenvwrapper`` and once you use it once, you will always want to use it more. What it does is that it has you create a hidden directory in your home directory, set that to an environment variable and references that directory as the basis for your virtual environments. The installation of this is pretty easy, you can``pip install virtualenvwrapper`` if you want, or download the package and compile by hand.</p>
<p>Once installed correctly, you can run the command <tt class="docutils literal">mkvirtualenv envname</tt> to create a virtual environment. You can then run``workon envname`` from anywhere, and it will activate that environment. For example, you could be at``/var/www/vhosts/www.mysite.com/django/`` and run``workon envname`` and it would activate the environment from there. This isn't a required package (none of them are really…) as I went a couple years without using``virtualenvwrapper``, but it is very useful and now I use it every day. Some tips I use with my setup of``virtualenvwrapper`` is that I use the postactivate scripts to automatically try to change into the proper project directory of my environment. This also means I usually name my``virtualenv`` after my project name for easy memory. It makes no sense to have a project called "cash_register" but the``virtualenv`` be called "fez". This is how I change to the right project after activating my <tt class="docutils literal">virtualenv</tt>. This goes in <tt class="docutils literal">$WORKON_HOME/postactivate</tt></p>
<div class="highlight"><pre><span></span><span class="ch">#!/bin/bash</span>
<span class="c1"># This hook is run after every virtualenv is activated.</span>
<span class="c1"># if its a work or a personal project (Example)</span>
<span class="nv">proj_name</span><span class="o">=</span><span class="k">$(</span><span class="nb">echo</span> <span class="nv">$VIRTUAL_ENV</span><span class="p">|</span>awk -F<span class="s1">'/'</span> <span class="s1">'{print $NF}'</span><span class="k">)</span>
<span class="k">if</span> <span class="o">[[</span> -e <span class="s2">"/Users/tsouza/PersonalProjects/</span><span class="nv">$proj_name</span><span class="s2">"</span> <span class="o">]]</span>
<span class="k">then</span>
<span class="nb">cd</span> ~/PersonalProjects/<span class="nv">$proj_name</span>
<span class="k">else</span>
<span class="nb">cd</span> ~/WorkProjects/<span class="nv">$proj_name</span>
<span class="k">fi</span>
</pre></div>
<p>This about wraps up part one of this two part blog series. Next time I will discuss how to use Git and how to configure SublimeText2 and Aptana Studio for use with Python. Stay tuned!</p>
</div>
CFEngine3 Install on CentOS 5.72012-05-25T03:57:00-04:002012-05-25T03:57:00-04:00tyreltag:tyrel.dev,2012-05-25:/blog/2012/05/cfengine3-install-on-centos-5-7.html<div class="line-block">
<div class="line">Today I was tasked with installing CFEngine3 on CentOS-5.7 (A little outdated). When installing CFEngine-3.3.1 I kept getting an error that I couldn't find libtokyocabinet.so.9. I had to set my prefix to /usr/ because the location that tokyocabinet was installing my libraries to was not …</div></div><div class="line-block">
<div class="line">Today I was tasked with installing CFEngine3 on CentOS-5.7 (A little outdated). When installing CFEngine-3.3.1 I kept getting an error that I couldn't find libtokyocabinet.so.9. I had to set my prefix to /usr/ because the location that tokyocabinet was installing my libraries to was not being read by CFEngine's make script.</div>
<div class="line">To do this (I am using tokyocabinet 1.4.47)</div>
</div>
<div class="highlight"><pre><span></span><span class="go">wget http://fallabs.com/tokyocabinet/tokyocabinet-1.4.47.tar.gz</span>
<span class="go">tar -xzvf tokyocabinet-1.4.47.tar.gz</span>
<span class="go">cd tokyocabinet-1.4.47/</span>
<span class="go">./configure --prefix=/usr/</span>
<span class="go">make</span>
<span class="go">sudo make install</span>
</pre></div>
<p>Then I was able to ./configure && make && make install cfengine3 without any problems.</p>
<p>So my overall script looked something like this:</p>
<div class="highlight"><pre><span></span><span class="go">wget http://fallabs.com/tokyocabinet/tokyocabinet-1.4.47.tar.gz</span>
<span class="go">tar -xzvf tokyocabinet-1.4.47.tar.gz</span>
<span class="go">cd tokyocabinet-1.4.47/</span>
<span class="go">./configure --prefix=/usr/</span>
<span class="go">make</span>
<span class="go">sudo make install</span>
</pre></div>
<div class="highlight"><pre><span></span><span class="go">wget http://cfengine.com/source-code/download?file=cfengine-3.3.1.tar.gz</span>
<span class="go">tar -xzvf cfengine-3.3.1.tar.gz</span>
<span class="go">cd cfengine-3.3.1</span>
<span class="go">./configure</span>
<span class="go">make</span>
<span class="go">sudo make install</span>
<span class="go">sudo cp /var/cfengine/bin/cf-* /usr/bin/</span>
</pre></div>
Harry Delmolino2012-05-25T03:56:00-04:002012-05-25T03:56:00-04:00tyreltag:tyrel.dev,2012-05-25:/blog/2012/05/harry-delmolino.html<p>I met this random kid online on IRC a year and a half ago (I believe it was December 19th, 2010). His name was HarryD. We got talking and one time he mentioned that he was going to hike Mount Monadnock. That is near me so we got talking and …</p><p>I met this random kid online on IRC a year and a half ago (I believe it was December 19th, 2010). His name was HarryD. We got talking and one time he mentioned that he was going to hike Mount Monadnock. That is near me so we got talking and he said he lived near North Hampton, MA. That was cool that I met some random kid who lived near me. He was only 17 at the time.</p>
<p>Eventually we met up because my new girlfriend lived near NoHo in Holyoke. One day I went down to see her and met up with him. He was wearing all brown, so I joked that he was a UPS delivery guy. We hung out for a bit, I think I met his friend Sam that day. Then I left and went home. For the next year we would hang occasionally, usually I would go down and visit NoHo because it was easy for him to Bike to and he worked there. We went bowling, once or twice, and he came up for this super bowl to watch Star Wars instead because screw sports!</p>
<p>I think that was the last time I saw him.</p>
<p>On this Saturday, May 19th, 2012, he was riding his bicycle in North Hampton and collided with a car, sending him flying off and smacking his head on the edge of the curb. There are some other details which I am not 100% sure about. He then was in the hospital until Tuesday May 22, 2012 at which time he passed away.</p>
<p>We used to talk every day, either on IRC, AIM or gTalk. His account on IRC (no_numbers_here) is still active because his VPS for <a class="reference external" href="http://harry.is">http://harry.is</a> is still paid for and online until the next billing period. Its so sad seeing his name online and not being able to send a "hey dood" or some other random greeting, then start talking about computers, python, bikes, etc.</p>
<p>Harry was an avid cyclist. I am reading stories that even if he had to go thirty feet, he would hop on his bike to get there. He got me interested in cycling as well. He was going to sell me a bike, but the I was talking to a friend and he gave me on, so I never bought it. Which as it turns out was good as he wanted to give that to his new girlfriend.</p>
<p>I was planning on hanging out with him next weekend, as he was busy with something this weekend that I can't remember. I wanted to take him target shooting, and wanted to eventually be in enough shape to go hiking with him. None of this ever came to fruition.</p>
<p>Harry Delmolino, we may not have been as close as we could have been, but you definitely made a difference in my life for the better and I never got to thank you for this.</p>
<div class="section" id="edit">
<h2>Edit:</h2>
<p>I went to the Calling Hours today and I was only there maybe a half hour, but there were so many people there. It's amazing that a man so young has touched so many lives of so many people, and had accomplished so much. People might say "Oh we was just a kid, he was only 18″ but if they looked at the accomplishments of this young man, they would realize how grown up he was.</p>
<p>I think his mother and father might eventually delete his Facebook, so I went back and took a screenshot of one of our last conversations so I can remember. Everything he said usually warranted a laugh.</p>
</div>
Hypertherm2012-05-07T23:30:00-04:002012-05-07T23:30:00-04:00tyreltag:tyrel.dev,2012-05-07:/blog/2012/05/hypertherm.html<p>For the past three months I have been upgrading and rewriting version 2 of my software for Hypertherm. I am under a contract for my father's company. His company is developing a machine to test how well air flows (laminar flow) through a plasma cutting torch head, and how much …</p><p>For the past three months I have been upgrading and rewriting version 2 of my software for Hypertherm. I am under a contract for my father's company. His company is developing a machine to test how well air flows (laminar flow) through a plasma cutting torch head, and how much air leaks out over a certain time (delta pressure loss).</p>
<p>This has been a nice adventure. I am talking to the tester over serial, reading in a hand scanner (barcodes and acts as a keyboard easy), talking to a DYMO printer and using a database.</p>
<p>The serial communication was pretty straightforward. I started a new thread and listen for serial all the time. The tricky part with that was that because it was on another thread, I needed a delegate to talk to my UI when I did things like change the picture from blank to a big red X, or update a label.</p>
<p>The hand scanner wasn't even a factor that took longer than 10 minutes, I just pop up a dialog box asking for input.</p>
<p>The DYMO printer was the hardest part. This took me a month to figure out, I kept fighting with the printer. I could figure out how to print to the left roll, the ones we setup as as the passing labels, but I couldn't for the life of me figure out how to get it to print to the right label, using a custom label. I tried to load the labels into data and use a StreamWriter/StreamReader object to treat that as the label, but it kept printing one that had, for reasons unknown to me, been locked into the printer. I finally gave up on using the interface they provided and am writing the label to a temporary file. The file is in the user's %appdata% directory in a sub directory that it will not be mistakenly written to, so I feel safe doing it this way. Granted, the machine is a single purpose machine, once this program is installed it will only run this program day and night.</p>
<p>Once I got the printer working, I checked it in to github and realized it took me way longer than anticipated. I learned a lot about .NET development (by no means everything, or even most things, just a lot compared to what I did know before [nothing].)</p>
<p>Tonight while developing I decided to video some aspects of the Program.</p>
<p>The following four links are videos, showing parts of the program and machine in action.</p>
<ul class="simple">
<li>[Hosted on Qik - no longer available]</li>
<li>[Hosted on Qik - no longer available]</li>
<li>[Hosted on Qik - no longer available]</li>
</ul>
Ganymede, Twilio2012-05-04T23:30:00-04:002012-05-04T23:30:00-04:00tyreltag:tyrel.dev,2012-05-04:/blog/2012/05/ganymede-twilio.html<p>Last night I wrote the beginnings of my first NodeJS application. Is application even the correct word?</p>
<p>I've been meaning to try out the cool API by Twilio, which is used for SMS and VoiceCalling. I decided to design a system that will be two+ endpoints. One is the main …</p><p>Last night I wrote the beginnings of my first NodeJS application. Is application even the correct word?</p>
<p>I've been meaning to try out the cool API by Twilio, which is used for SMS and VoiceCalling. I decided to design a system that will be two+ endpoints. One is the main server which will listen for UDP messages. When it receives the correct UDP message, configured in the config(<a class="reference external" href="https://github.com/pgte/konphyg">konphyg</a>) files, it will fire off a message to Twilio and send me a text message.</p>
<p>The next steps, which I should be getting to tonight, are to create the Arduino portion and the serial listener. The Arduino will have a button that will send a message over serial to another NodeJS listener. This will decide if the press was good enough, if it passes the debouncing filter, and then fire a message to the main Ganymede server.</p>
<p>This could be used as a little text message doorbell for when you have your music on too loud. I don't believe I will ever sell this, as it's just for me to get into NodeJS, but It would be fun to share with friends.</p>
<p>The source so far is located on my github at [DEADLINK].</p>
<p>I will write more as the project continues about the different technologies and comment on my choices in the source a little bit.</p>
Some BASH tips2012-03-08T03:56:00-05:002012-03-08T03:56:00-05:00tyreltag:tyrel.dev,2012-03-08:/blog/2012/03/some-bash-tips.html<p>I realize I haven't updated in a while. I haven't had much free time recently as I've been working on a project for my father in C# after work hours. This is a great change from only working in Python and JavaScript recently. I'm making a program that will analyze …</p><p>I realize I haven't updated in a while. I haven't had much free time recently as I've been working on a project for my father in C# after work hours. This is a great change from only working in Python and JavaScript recently. I'm making a program that will analyze test results from a plasma torch for a company called HyperTherm. My father built the physical machine, but the employees want something that they can better see the results of a passed torch unit, or a failed torch unit. This program has a bar code scanner that scans the tool used in the test and matches it up to the lot of torch parts. Another added feature is the ability to print a white label that says "UNIT PASSED" or a giant red label that says the unit failed and which of the 8 tests failed were.I had to learn how to use delegates, as my serial event listener is on a separate thread and I can't update labels, or parts of the User Interface without them. Still working on it, hopefully will wrap it up by Saint Patrick's day.</p>
<p>I recently found a cool command in BASH that I hadn't previously known. <tt class="docutils literal"><span class="pre">C-o</span></tt> will execute the current line, and then bring the following line up from BASH history. If you have a set of commands you want to execute again, rather than having to press up 20 times, hit enter, press up 19 times, hit enter, and so on… You can just hit up 20 times. Press C-o as many times as you need to.</p>
<p>For example:</p>
<div class="highlight"><pre><span></span><span class="gp">$ </span>touch a
<span class="gp">$ </span>touch b
<span class="gp">$ </span>touch c
<span class="gp"># </span><span class="o">[</span>up<span class="o">]</span> <span class="o">[</span>up<span class="o">]</span> <span class="o">[</span>up<span class="o">]</span>
<span class="gp">$ </span>touch a <span class="o">[</span>C-o<span class="o">]</span>
<span class="gp">$ </span>touch b <span class="o">[</span>C-o<span class="o">]</span>
<span class="gp">$ </span>touch c <span class="o">[</span>C-o<span class="o">]</span>
</pre></div>
<p>As you can see there, all I had to do was go back to the <tt class="docutils literal">$ touch a</tt> line, and hit control-o three times and it touched the files again!</p>
Hubspot2012-02-17T15:10:00-05:002012-02-17T15:10:00-05:00tyreltag:tyrel.dev,2012-02-17:/blog/2012/02/hubspot.html<p>I was invited to a Hackathon that one of our client's client was throwing. Being that I love programming and learning, I decided I would go.</p>
<p>The event was in Cambridge, MA. I arrive early, (my friend said there would be a lot more traffic than there was at that …</p><p>I was invited to a Hackathon that one of our client's client was throwing. Being that I love programming and learning, I decided I would go.</p>
<p>The event was in Cambridge, MA. I arrive early, (my friend said there would be a lot more traffic than there was at that time of day) so I got a tour of office. It's situated in an old, what I believe to be, factory building. The coolest part of the office was that they had whiteboard paint on every wall surface, complete with markers of course.</p>
<p>The event started and people who were attending had tossed up ideas on the white board. A couple people wanted to integrate LinkedIN with HubSpot. Another person wanted to integrate Eventbrite with HubSpot, to get information to/from event goers after the event ends. I didn't like any of those ideas and my only experience with HubSpot is their Leads API, so I stuck to what I know.</p>
<p>I had an idea for an app the second I walked in the door, it was like magic. My main hassle was that HubSpot's Canvas integration REQUIRES HTTPS. Now, my web host is DreamHost and I am kind of cheap, so of course I don't have any way to host a HTTPS site immediately. A big part of me wanted to bite the bullet and order a secure server from DreamHost, or setup another linode, but I felt that I've been spending a lot of money lately and that I would figure out a way. Adrian, my contact at HubSpot, of who I am working with on the PPC project(more on that later), walked by and saved me.</p>
<p>He asked if I had ever used GoogleAppEngine. Of course I hadn't because I was under the belief that it cost money to use, but then I realized I was thinking of Amazon's EC2. I sign up for GAE and within an hour I have a HelloWorld site setup. The slow part was installing Python2.5 so I could use the same version that GAE used and not have to fix a lot of backwards compatibility errors between 2.5->2.7.</p>
<p>After I had a site up that could do HTTPS I dove into programming for my HubSpot app. The app I am doing for work graphs leads per day combined with Google AdWords data per day. I decided to do something different. My app is still a graph, as graphs are fun and easy to understand by everyone.</p>
<p>This app graphs a set of leads and shows how many leads happened in a given hour for the previous day. Given extra time I would have added an interface to specify the day to graph leads, but last night my time was severely limited by the fact that I had to setup my environment for GoogleAppsEngine.</p>
<p>Improvements I can and want to do to this app are database, faster processing, and being able to select a date. I almost wanted to break down and learn NodeJS for this, because from my understanding of the event driven nature of NodeJS would be a lot easier to load data over a longer period of time, than to just load it all at once and timeout with HubSpot's Jakarta Commons-HttpClient.</p>
Vertical Bars In Graphite2012-02-08T15:10:00-05:002012-02-08T15:10:00-05:00tyreltag:tyrel.dev,2012-02-08:/blog/2012/02/vertical-bars-in-graphite.html<p>I am working with txStatsD and Graphite. I was having the hardest problem looking through the txStatsD code today finding how to graph something as an event, not a data point. I eventually went into every option on the graphite dashboard and found an option to make a bar.</p>
<div class="figure">
<img alt="menu in graphite showing draw nonzero as infinite" src="https://tyrel.dev/blog/images/2012/02/graphite-menu.png" />
</div>
<p>This …</p><p>I am working with txStatsD and Graphite. I was having the hardest problem looking through the txStatsD code today finding how to graph something as an event, not a data point. I eventually went into every option on the graphite dashboard and found an option to make a bar.</p>
<div class="figure">
<img alt="menu in graphite showing draw nonzero as infinite" src="https://tyrel.dev/blog/images/2012/02/graphite-menu.png" />
</div>
<p>This is the option that you must use when you want to mark events. For example we want to know "Server restarted", we would use this technique, as it doesn't make sense to aggregate "server restarted". Using nonzero as infinite is a good way to show an event took place.</p>
You can un-expire a GPG key.2012-01-13T03:54:00-05:002012-01-13T03:54:00-05:00tyreltag:tyrel.dev,2012-01-13:/blog/2012/01/you-can-un-expire-a-gpg-key.html<p>Today we had a problem at work on a system.
Without getting into too much detail as to give away secrets behind the verbal NDA I am behind, I will just say that it had to do with a GPG public key of mine that was expired on a dev …</p><p>Today we had a problem at work on a system.
Without getting into too much detail as to give away secrets behind the verbal NDA I am behind, I will just say that it had to do with a GPG public key of mine that was expired on a dev machine, accidentally propagating during install to a production machine.
This key had a sub key as well, so figuring out this was tricky.</p>
<p>To start, you can list your gpg keys like so:</p>
<div class="highlight"><pre><span></span><span class="gp">$ </span>gpg --list-keys
</pre></div>
<p>This will list keys such as</p>
<div class="highlight"><pre><span></span><span class="go">pub 4096R/01A53981 2011-11-09 [expires: 2016-11-07]</span>
<span class="go">uid Tyrel Anthony Souza (Five year key for email.)</span>
<span class="go">sub 4096R/C482F56D 2011-11-09 [expires: 2016-11-07]</span>
</pre></div>
<p>To make this not expire, (same steps to change expiration date to another time), you must first edit the key</p>
<div class="highlight"><pre><span></span><span class="gp">$ </span>gpg --edit-key 01A53981
</pre></div>
<p>You will then see a gpg prompt <tt class="docutils literal">gpg></tt></p>
<p>Type "expire" in and you will be prompted for how long to change it to</p>
<div class="highlight"><pre><span></span><span class="go">Changing expiration time for the primary key.</span>
<span class="go">Please specify how long the key should be valid.</span>
<span class="go">0 = key does not expire</span>
<span class="go"><n> = key expires in n days</span>
<span class="go"><n>w = key expires in n weeks</span>
<span class="go"><n>m = key expires in n months</span>
<span class="go"><n>y = key expires in n years</span>
</pre></div>
<p>You are then done setting the expiration on the primary key, if you have sub key, doing this is as easy as typing <tt class="docutils literal">key 1</tt> and repeating the expiration step.</p>
<p>To finish and wrap things up, type <tt class="docutils literal">save</tt> and you are done.</p>
Custom Django URLField2012-01-05T03:55:00-05:002012-01-05T03:55:00-05:00tyreltag:tyrel.dev,2012-01-05:/blog/2012/01/custom-django-urlfield.html<p>For work I had to write a custom url model field. This model field when setting up accepts a default protocol, and a list of other protocols.</p>
<p>When checking the protocol, the url is split by "://". If the split has one or two parts, then the url is validly formed …</p><p>For work I had to write a custom url model field. This model field when setting up accepts a default protocol, and a list of other protocols.</p>
<p>When checking the protocol, the url is split by "://". If the split has one or two parts, then the url is validly formed.</p>
<p>In the event of a single element split, there is no protocol specified. When there is no protocol, the url is prepended with the default protocol specified. If there is a protocol, it is checked to make sure it exists in a union of the default protocol and other protocols. If it is not, a ValidationError is raised letting the user know that the protocol is not accepted.</p>
<p>This can all be found at On my github [deadlink].</p>
<p>I have a couple ways I could have done this better and probably will. Improvements would be just one parameter called parameters in which it is checked if there is at least one element. Passing this, when there is no protocol specified, the first element is the default one.</p>
<p>This would be a little cleaner.</p>
<p>this example would allow for http, https, ssh, spdy and mailto, anything else would error out.</p>
<div class="highlight"><pre><span></span><span class="n">facebook_page</span> <span class="o">=</span> <span class="n">URLField</span><span class="p">(</span><span class="n">default_protocol</span><span class="o">=</span><span class="s2">"http"</span><span class="p">,</span> <span class="n">protocols</span><span class="o">=</span><span class="p">[</span><span class="s2">"https"</span><span class="p">,</span><span class="s2">"ssh"</span><span class="p">,</span><span class="s2">"spdy"</span><span class="p">,</span><span class="s2">"mailto"</span><span class="p">])</span>
</pre></div>
<p>The way I could improve this would be</p>
<div class="highlight"><pre><span></span><span class="n">facebook_page</span> <span class="o">=</span> <span class="n">URLField</span><span class="p">(</span><span class="n">protocols</span><span class="o">=</span><span class="p">[</span><span class="s2">"https"</span><span class="p">,</span><span class="s2">"https"</span><span class="p">,</span><span class="s2">"ssh"</span><span class="p">,</span><span class="s2">"spdy"</span><span class="p">,</span><span class="s2">"mailto"</span><span class="p">])</span>
</pre></div>
Python Progress Bar2011-12-21T03:52:00-05:002011-12-21T03:52:00-05:00tyreltag:tyrel.dev,2011-12-21:/blog/2011/12/python-progress-bar.html<p>I was looking for a nice progress bar today at work to show progress rather than just printing "<strong>Waiting 30 seconds…</strong>" and having the script do nothing, I wanted to have a progress bar show.</p>
<p>I found a progress bar from <a class="reference external" href="http://code.google.com/p/corey-projects/source/browse/trunk/python2/progress_bar.py">Corey Goldberg</a></p>
<p>I did make a couple changes, and …</p><p>I was looking for a nice progress bar today at work to show progress rather than just printing "<strong>Waiting 30 seconds…</strong>" and having the script do nothing, I wanted to have a progress bar show.</p>
<p>I found a progress bar from <a class="reference external" href="http://code.google.com/p/corey-projects/source/browse/trunk/python2/progress_bar.py">Corey Goldberg</a></p>
<p>I did make a couple changes, and have uploaded my changes to my GitHub account.</p>
<p>newPythonProgressBar [deadlink]</p>
<p>To use this progressbar, it is very easy.</p>
<div class="highlight"><pre><span></span><span class="c1"># To Setup</span>
<span class="kn">from</span> <span class="nn">progress_bar</span> <span class="kn">import</span> <span class="n">ProgressBar</span>
<span class="kn">import</span> <span class="nn">sys</span>
<span class="kn">import</span> <span class="nn">time</span>
<span class="k">def</span> <span class="nf">updateBar</span><span class="p">(</span><span class="n">step</span><span class="p">):</span>
<span class="n">p</span><span class="o">.</span><span class="n">update_time</span><span class="p">(</span><span class="n">step</span><span class="p">)</span>
<span class="n">sys</span><span class="o">.</span><span class="n">stdout</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="s2">"</span><span class="si">%s</span><span class="se">\r</span><span class="s2">"</span> <span class="o">%</span> <span class="n">p</span><span class="p">)</span>
<span class="n">sys</span><span class="o">.</span><span class="n">stdout</span><span class="o">.</span><span class="n">flush</span><span class="p">()</span>
<span class="c1">#</span>
<span class="c1"># to call</span>
<span class="c1">#</span>
<span class="n">wait_time</span> <span class="o">=</span> <span class="mi">100</span> <span class="c1"># seconds</span>
<span class="n">p</span> <span class="o">=</span> <span class="n">ProgressBar</span><span class="p">(</span><span class="n">wait_time</span><span class="p">)</span>
<span class="n">p</span><span class="o">.</span><span class="n">empty_char</span> <span class="o">=</span> <span class="s2">"."</span>
<span class="n">p</span><span class="o">.</span><span class="n">unit</span> <span class="o">=</span> <span class="s2">"^"</span>
<span class="k">for</span> <span class="n">step</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">wait_time</span><span class="o">+</span><span class="mi">1</span><span class="p">):</span>
<span class="n">updateBar</span><span class="p">(</span><span class="n">step</span><span class="p">)</span>
<span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
</pre></div>
<p>It will look like this when you use it</p>
<p><tt class="docutils literal"><span class="pre">[###...............7%..................]</span> <span class="pre">7^/100^</span></tt></p>