Vinod Kurup

Hospitalist/programmer in search of the meaning of life

Dec 26, 2021 - 2 minute read - Comments - tech support

USB-C lint

The problem

The USB-C cord kept falling out of my Android phone. It started happening in the car. A little bump while driving would make the cord fall out. Then it started happening even when I just had it plugged in on a table. I’d come down in the morning and the charge would be drained because it hadn’t been fully plugged in. Finally, I couldn’t even get it to charge unless I plugged it in forcefully, and put a book under the cord at a certain angle to keep it plugged in.

The googling

Googling for this problem didn’t find anything specific to my phone, but I did find people complaining of this issue and other people telling them that this was because of lint in the USB-C port. [1] [2] [3]

I just needed to get that lint out somehow.

What didn’t work

  1. Compressed air
  2. A toothpick (it was too thick)
  3. A plastic zip tie (It was too flimsy)

What did work

A SIM card removal tool. You know, one of those things that they send you with a new phone to poke into the SIM card housing to get it to eject? It was the perfect size and depth to be able to get all the way around the SIM card and get copious amounts of lint (ewww!) out of the port. Now my USB-C cord plugs in with a satisfying click and stays connected.

Philosophical musings

It’s weird that this three year old phone now feels new to me. For a while now, I would have a little dread every time I went to plug in this phone, knowing that it would be fiddly and that I might come down to a dead phone in the morning. Now, with this little improvement, I feel a weird warmth towards the phone again, similar to that feeling I had when it was a new phone. I cleaned out the lint a few weeks ago, and I still have this warm sensation towards it. Every time I plug it in, I get a little burst of happiness when it clicks in satisfyingly. It’s almost as if seeing a problem with the phone that I didn’t think would exist, and then seeing a solution to that problem, makes me appreciate the phone more than if it had never had that problem. That just feels weird and revelatory to me.

Nov 27, 2021 - 3 minute read - Comments - computers

Loving the frame.work laptop

I have a new favorite laptop. I needed a new non-work laptop and Linux is my OS of choice. It has been for at least a decade, but recently that favoritism has been cemented since I chose to use a Macbook Pro for work. After 6 months with the Mac, I’m finally starting to feel productive with it, but it has been a painful re-learning (and un-learning) process. I still fondly return to my Linux machines at the end of the work day. Mac OS X is just too “in-your-face” for me.

So I started planning to get another ThinkPad when I heard about frame.work on Hacker News. They are a startup focused on building functional, modular, repairable laptops which are also beautiful, light and fun to use. Check out their “About” page for more about their mission, but it resonated with me. I’ve always loved pokeing around my computers and it’s unfortunately gotten much harder to do that with all of my electronic devices. Frame.work is fighting against that trend.

Frame.work laptop contents Frame.work laptop contents

They have a DIY option where you install your own RAM, SSD and WiFi module. Even the non-DIY option comes in with external pluggable modules so you can configure your accessories. I got 2 USB-C ports, a USB-A port, an HDMI-port, and an SD-card reader. But it’s simple to pop out a module and pop in a new one. I don’t see myself doing that too much, but when I do need that SD card reader, I’ll be smiling. Setting up the DIY parts was fun and easy. Each part has a QR code on the part and on the location in the laptop where it gets installed, which points to detailed instructions.

Frame.work laptop insides Frame.work laptop insides

Ubuntu 20.04 doesn’t work out of the box (WiFi issues, I think?), so I installed 21.04, and everything works except for the fingerprint reader (which never worked with Ubuntu for me on my ThinkPad either). There are instructions to build the software needed to get it to work, but I’ll wait for it to get rolled into a future OS release (I’m old).

Overall, the computer itself is a dream. The screen is large and bright. The keyboard is dreamy, compared to my Thinkpad X1 Carbon. I think it’s better than my MacBook Pro too, but honestly that thing is too huge to use as a laptop so I always use it with an external keyboard. The computer is light and the battery life is more than adequate for my needs (though there are gripes and workarounds on the forums about this).

Frame.work laptop Frame.work laptop

I thought that there would be more rough edges to the whole process, but I’ve been pleasantly surprised. I hope others like it as much as I do so that the company sticks around!

May 9, 2021 - 2 minute read - Comments - life

First Post of 2021

Hi loyal reader(s)! It is nearly halfway through 2021 and this is my first post of the year. And writing on this here blog was one of my 2021 resolutions, so I guess that was a complete failure. Although, maybe I can make up for it by actually writing more regularly over the next few months. It has been a whirlwind of a year for just about everyone, right? When the pandemic started, I remember making a list of the things that I might want to blog about and the only thing I remember from that list was that I wanted to blog about “not wanting to blog about the pandemic”. It was just too exhausting to think about. And I think I still feel that way. I have written some private thoughts about things, but I’ll leave them under wraps.

The biggest change in my life is that I am starting a new job tomorrow. I’ll be a Software Engineer at Kevel, programming in Clojure (among other languages). I’ve been infatuated with Clojure (and functional programming in general) for a long time, so I am beyond excited to be able to use it professionally. This is also the first job that I’ll be starting as a “remote-only” worker. Normally, I’d walk into the office and introduce myself to someone and then start to get oriented. It will be weird to do that via an online chat of some sort. Am I showing my age?

What else is going on? Well I’m not going to talk about the pandemic, but I do encourage people to go help out people in India (or wherever else people need help.) We are luckily healthy and vaccinated. The kids have survived remote schooling and two of them have returned to school partly in-person. I’ve been running and meditating and gardening, all with minimal success.

So, what is up with you all? Hope to write again before the end of the year.

Dec 12, 2020 - 2 minute read - Comments - programming elixir

Advent of Code Elixir Day 1

There’s this cool thing called Advent of Code, described best here. I found out about it while reading my favorite weekly weblog and so I’ve decided to try my fledgling (maybe flailing is a better word?) Elixir skills on it. There are 2 puzzles each day (day_1_0 and day_1_1 are my solutions). Here’s my code for Day 1:

  @doc """
  Find 2 numbers in file that sum to 2020 and return their product.

      iex> Aoc.day_1_0()
      633216
  """
  def day_1_0 do
    goal = 2020
    list_of_nums = get_list_of_nums()

    first_num = find_first_partner(list_of_nums, goal)
    first_num * (goal - first_num)
  end

  @doc """
  Find 3 numbers in file that sum to 2020 and return their product.

      iex> Aoc.day_1_1()
      68348924
  """
  def day_1_1 do
    goal = 2020
    list_of_nums = get_list_of_nums()

    # find first number where there exists 2 other numbers that sum to `goal - x`
    first_num = Enum.find(list_of_nums, fn x -> find_first_partner(list_of_nums, goal - x) end)
    second_num = find_first_partner(list_of_nums, goal - first_num)

    first_num * second_num * (goal - first_num - second_num)
  end

  @doc """
  Given a `list` of integers, return the first number in that list which has a
  partner in the list that sums to `goal`.
  """
  def find_first_partner(list, goal) do
    Enum.find(list, fn x -> Enum.member?(list, goal - x) end)
  end

  defp get_file do
    Path.join("data", "day1.txt") |> File.read!()
  end

  defp get_list_of_nums do
    # Split into lines, filter out empty lines, convert each to an integer
    get_file()
    |> String.split("\n")
    |> Enum.filter(fn x -> String.trim(x) != "" end)
    |> Enum.map(&String.to_integer/1)
  end

The code is also available in Github, which I’ll update if I do find time to keep going: https://github.com/vkurup/aoc-elixir/ I feel like this could have been done more efficiently with recursion, but I am not comfortable enough with recursion to know how…

A sincere thanks to Eric Wastl for spending the enormous time and energy to create cool puzzles like this.

Dec 11, 2020 - 2 minute read - Comments - cheatsheet python linux bash

Install multiple python versions using pyenv

pyenv is awesome and has changed the way that I manage my Python environments. One tiny annoyance is that it takes a looong time to install each Python version, which of course makes sense, since it has to download and compile each version on your machine. But it’s not a big deal, since you generally just do this once and then not again until you really need to upgrade to a different Python version.

Recently, though, I was working on a project that uses tox to make sure that the project runs on multiple Python versions, in this case 3.6, 3.7, 3.8, 3.9. The specific versions were included in the .python-version file, which looked like this:

3.9.0
3.8.6
3.7.9
3.6.12

I didn’t have all of these installed, so rather than doing pyenv install 3.9.0 and then waiting a few minutes, and then repeating the process for each version, I wrote up this little script, which I named pyinstall.sh:

#!/bin/bash
# Install all versions specified in .python-version
set -ex

while read version; do
    pyenv install -s "$version"
done <.python-version

Since pyenv is nice enough to include the -s flag which means “skip if already installed”, this can be run as many times as you want and it will only install the version if it’s not already installed

Aug 21, 2020 - 2 minute read - Comments - homelab phone

Using an OBi110 for VOIP service

I set up an OBi110 device to provide a landline at home. OK, it’s not a land line, it’s a VOIP (Voice Over Internet Protocol) line, but it performs a similar function. We’ve always had a landline. Our cell phone coverage is spotty at times and ever since Mala started working from home, she needs to have a reliable phone line for her global conference calls.

The problem is that Spectrum was charging us $44.95 per month plus taxes, which took it near $50 every month. I considered a few options including:

  • MagicJack
  • Ooma
  • I looked at a few others… can’t remember them all

In the end, I settled on buying an ATA device and then connecting it to a cheaper VOIP service. I chose voip.ms which has been great so far.

One Time Costs:

  • OBi110 ATA device: $19.90
  • voip.ms setup fee: $0.40
  • voip.ms e911 setup fee: $1.50

Monthly Costs:

  • voip.ms fee for an incoming phone number: $0.85
  • voip.ms e911 monthly fee: $1.50

Per-Minute Costs:

  • voip.ms incoming: $0.009 per minute

I’ll have to keep an eye on my monthly per-minute costs because there is an unlimited plan that is $4.25 per month, which I’ll switch to if I use more than that per month. In any case, this is MUCH cheaper than Spectrum.

Now, if you do any research about the OBi110 device, you’ll see everyone telling you not to buy it because it is out of service and doesn’t support Google Voice. This is true, ObiTalk doesn’t support the device anymore. When it was supported by the company, you could just dial a special code on your phone and it would do all the set up for you. Since it’s not supported, that procedure doesn’t work anymore. But you can manually configure the device with the excellent docs from voip.ms, and for a geek like me that was not bad. And yes, you can’t link a Google Voice number to the device, but I didn’t want to do that anyway. I think if you wanted to keep using a google voice number, you could probably get pretty close by putting a different number on your device (through voip.ms) and then pointing Google Voice at that number. Incoming calls would work fine in that way, but I’m not sure about outgoing calls. If you want to seamlessly use Google Voice on an ATA device, then OBi110 isn’t for you. You’ll need the OBI202 (which was out of stock when I was looking).

Aug 17, 2020 - 2 minute read - Comments - django programming

Django forms have 2 modes

I worked on adding tests to some Django forms today and I was reminded that Django forms operate in two modes. I think of them as the GET mode and the POST mode. The GET mode displays the form and the POST mode processes the data that you supply to the form. I don’t know why, but I often forget that. Instead, I sometimes treat them as a normal Python class, especially when I’m creating automated tests for them. I instantiate the class (form = MyForm()) and then just call any of its methods and make sure they return the right values or achieve the correct side effects. The form that I was working with today had a couple features. The first was that it provided some helper functions to the template so that the template could display the form properly. Those are in the GET mode. So I did something like:

user = create_my_special_user()
form = MyForm(user=user)
self.assertTrue(form.is_user_special())

And that worked as I expected. The second feature of my custom form was that it cleaned the data in a particular way. So I did:

form = MyForm(user=user)
cleaned_data = form.clean()
self.assertEqual(cleaned_data, expected_data)

Instead of passing, the test blew up:

AttributeError: 'MyForm' object has no attribute 'cleaned_data'

Why? Because the clean() method is called when you provide it with POST data (normally… there are also other ways to populate forms). If you don’t supply it data, then cleaned_data doesn’t get populated. The answer is to provide data to the form:

form = MyForm(user=user, data={})
cleaned_data = form.clean()
self.assertEqual(cleaned_data, expected_data)

And that works as expected. The reminder to myself is to always identify which mode of the form you are testing, GET or POST.

Jan 27, 2020 - 1 minute read - Comments - cheatsheet aws

AWS Xray brain dump

These are very rough notes on how I got AWS Xray working in a kubernetes deployment. Posting this mainly for my auxiliary brain.

First, set up an IAM role that allows nodes to access xray.

Following this blog post: https://aws.amazon.com/blogs/compute/application-tracing-on-kubernetes-with-aws-x-ray/

I cloned this repo: https://github.com/aws-samples/aws-xray-kubernetes

Build the AWS Xray image:

cd xray-daemon/
docker build -t xray-daemon:latest .

THIS OUT OF DATE! try this: https://docs.aws.amazon.com/xray/latest/devguide/xray-daemon-ecs.html#xray-daemon-ecs-image

Create an AWS ECR repo:

aws ecr create-repository --repository-name xray-daemon

Login via docker:

aws ecr get-login --no-include-email

That command spits out a command which you can run that will log you into docker ^

Tag and push the image there:

docker tag xray-daemon:latest AWS-ACCOUNT-ID.dkr.ecr.us-east-1.amazonaws.com/xray-daemon:latest
docker push AWS-ACCOUNT-ID.dkr.ecr.us-east-1.amazonaws.com/xray-daemon:latest

You should now be able to see the image in the AWS ECR console

Update aws-xray-kubernetes/xray-daemon/xray-k8s-daemonset.yaml:

Change the image value to the image you just pushed

deploy it:

kubectl apply -f xray-k8s-daemonset.yaml