Saturday, December 23, 2017

Laser cutting: Creating a phone stand

For the holiday season, I wanted to make my co-workers something useful.

Everyone has a cell-phone at work, and we are mobile developers.  To reduce desk clutter, I thought a phone stand might be perfect.  I had recently learned how to use a laser cutter, and looked around for a good project to grab from usual websites like thingiverse.  Despite looking online, I didn't find anything that suited my needs.

The goals:
  1. A simple material, hopefully sedate and elegant.
  2. Fewer pieces was better.  I wanted to make more than 30 holders, and simplicity would allow me to make many of them.
  3. Ability to fit a wide variety of phones.
  4. Tablets would be a plus, but not required.
I had cut some wood in an Epilog cutter before, so I decided to continue using 0.125 inch plywood.

Step 1: Prototype

This was my first experience designing a physical object.  Being a true engineer, I grabbed a pencil, a pen-knife and some cardboard from discarded packaging.  I started with a simple design with two pieces: one for the face plate, and one for the bottom.  The bottom would poke through the face plate and hopefully lock in.

I worked with various widths and lengths, to see what would hold a phone comfortably.  The first cut was made from a cardboard case, and was too short, and the base wouldn't lock tight because I made the base too curved and the cardboard was too weak.

Calculating the weight of a usual phone (adding 30% for future growth), the inclination, and the rough springiness from wood, my son and I worked out the lengths of the various segments.

With some iterations, I arrived at two perfect rectangles: the face plate was taller, the base had long prongs that would stick out.  On one cardboard prototype, we were able to hold a real phone without flopping.  This is the first prototype holding up a phone.


That night, I drew one face plate and one base on Inkscape, learning about Inkscape as I went. To personalize them, I added a co-worker's name to be etched (marked in red) while lines in black were to be cut.

Finally, 12 x 24 inches plywood at 1/8 inch thickness was bought.

Step 2: Test on Paperboard

To confirm the measurements, the first draft was loaded up on the laser cutter with some quarter inch paper board.  I added inch markings along the side to diagnose any deviations in the cut.  This is the desk area as I was waiting for the cut to finish.


To my utter shock, the first cut on cardboard was absolutely perfect. It measured exactly to spec, the two pieces locked together, and it held up my phone.

Step 3: Test on Wood

With the success of the paperboard run, I decided to calibrate the laser cutter for engraving and make a single test run on wood to see how well it held up.

I loaded up my wood, and sent the same .svg file, this time calculating laser intensity for wood instead.  The power on the first run was too weak, so I just repeated the run without moving the wood. It led to a working model, this time in wood!  The face plate is to the left and the bottom plate is to the right. Notice the inch markings along the left edge, still visible.


This is what it looks like with a phone on it.





Making a single phone holder is easy, and takes forty minutes to print the face plate, then readjust origin, print the base plate, and ensure the cut is good.  Making many copies without mistakes would be time consuming.  I did not want to manually adjust the origin, and find where the start from.  I would have to automate it to make all the holders in time for Christmas.

Step 4: Production!

Over the next few days, I used the wooden prototype, and learned:
  1. The prongs were too short, which led to the phone sliding off. This was bad.  The prongs needed to be longer.  To allow for a phone charger cable, gaps were cut at the bottom of the face plate and the bottom plate.
  2. To minimize wastage and manual effort, I had to fit 30 face plates and 30 bottom plates on as few sheets of plywood as possible. Eventually, I ended up fitting the face plates together on one run, while bottom plates would go in another svg file.
  3. After completing #2, I realized the laser cutter might not cut perfectly near the edges of the wood, and all wood warps over time. In addition, I might not set the origin perfectly, and the board might be smaller if it was at the edge. So all sizes were reduced by a small amount to leave a thin border of wood as a precaution for these issues.
  4. A friend came over and suggested that usernames worked better than first names for our office.  All first names were changed to usernames.  This was a great idea since there are people with the same name.
  5. Since I had increased the prong size in #1 and reduced the overall board in #3, we'd have to create another PVT to confirm before a final production run.
  6. The inch markers were left in the final run, adding a minimalist elegance to the design.
  7. I had just enough wood to make all holders, but no spare planks if I messed up.
  8. Kids fell ill, leading to production delays.

With all these lessons, three new Scalable Vector Graphics (.svg) files were created: two containing 15 faceplates each in a 3 x 5 grid. Since these had names for engraving, they had to be different files. The font sizes were reduced for longer usernames to make them fit on a line.   The inch markers and names are in red for engraving (less time at 100% intensity).  The black lines are for cutting through (more time at 100% intensity).



The bottom plates were not personalized so a single sheet with 21 bottom plates (3 x 7 grid) was made.  Ordering spare wood would have been wise, but I aimed for perfection instead.




Finally, I went over to the laser cutter with wood, .svg files, paperboard, pen-knife and tools.  After confirming that the new measurements with longer prongs and overall reduced size still held up a phone, I loaded up a full sheet of 12 x 24 inches plywood at 1/8 inch thickness.


Engraving all the names and inch markings took the most time: 40 minutes for each face plate sheet.  Here's the engraving, in progress.  The sound is the ventilator at full-throttle.  The laser cutter itself is relatively quiet.  You can see how painfully slow the progress is.  There are three other names that you don't see, which is why the arm is swinging far out of these two usernames




Cutting took 10 minutes, and just to ensure a precise cut, I ran the job again with just the cutting again.  Even so, some sheets were warped around the sides and some plates were loosely attached and had to be scored with a pen-knife to separate.

Here's a video of the cutting in progress.  You can see the inch markings along the left wall as the cut lines up perfectly against them.



The bottom plates were faster, having no engraving.  Here's the start of the bottom plate run.  You can see the narrow border left, which was to allow for some error while setting the origin of the run.



My co-workers got a personalized phone holder each, and I still had wood left over.  There was one hilarious mistake where a username were incorrectly spelled, and another where a natural knot in plywood marred the username.  These face plates were cut again, individually.

With that, production was complete.  The delight was not just in having a personalized gift for everyone, but the satisfaction of a job well done.

Images: courtesy me.

Saturday, February 18, 2017

Learning Blender


I have been learning Blender the last few weeks. Here is the video I made today.  There are four glass spheres moving with a wooden cube in the back.  It took about two hours to render on my Linux computer.  Each frame takes about two seconds to render, and there are 1800 frames.  The video has 24 frames a second.

Blender is a 3D modeling software that is freely available on Mac, Windows, and Linux.

Monday, December 12, 2016

KernelOops on Nvidia 367 with dual monitor setup

My home Linux display setup is complicated.  The Linux desktop has two monitors, and one of the monitors is connected to the desktop via a KVM switch.  The KVM is also connected to a non-Linux desktop.  I use a Nvidia GTX950 card on the Linux machine, which needs proprietary Nvidia drivers to run. In the last few weeks, I started getting Kerneloops when suspending the Linux machine.  The problem seemed to be the nvidia modules of the kernel.  I didn't have kernel oops earlier, so I started to track down the cause.

I finally found the issue was nvidia not being able to modeset correctly.  My previous Xorg config forced a specific screen layout.  The new Nvidia binary driver undid this, and that is roughly when the suspend problems started happening.

I have two screens: DFP-0, and DFP-4.  You can see your own list of screens by looking at the file /var/log/Xorg.0.log.  The relevant lines that show you which screens are connected is here:

[  6615.937] (--) NVIDIA(0): Ancor Communications Inc ASUS VS247 (DFP-0): connected
[  6615.937] (--) NVIDIA(0): Ancor Communications Inc ASUS VS247 (DFP-0): Internal TMDS
[  6615.937] (--) NVIDIA(0): Ancor Communications Inc ASUS VS247 (DFP-0): 330.0 MHz maximum pixel clock
[  6615.937] (--) NVIDIA(0): 
[  6615.937] (--) NVIDIA(0): DFP-1: disconnected
[  6615.937] (--) NVIDIA(0): DFP-1: Internal TMDS
[  6615.937] (--) NVIDIA(0): DFP-1: 330.0 MHz maximum pixel clock
[  6615.937] (--) NVIDIA(0): 
[  6615.937] (--) NVIDIA(0): DFP-2: disconnected
[  6615.937] (--) NVIDIA(0): DFP-2: Internal DisplayPort
[  6615.937] (--) NVIDIA(0): DFP-2: 960.0 MHz maximum pixel clock
[  6615.937] (--) NVIDIA(0): 
[  6615.937] (--) NVIDIA(0): DFP-3: disconnected
[  6615.937] (--) NVIDIA(0): DFP-3: Internal TMDS
[  6615.937] (--) NVIDIA(0): DFP-3: 330.0 MHz maximum pixel clock
[  6615.937] (--) NVIDIA(0): 
[  6615.937] (--) NVIDIA(0): Acer K272HUL (DFP-4): connected
[  6615.937] (--) NVIDIA(0): Acer K272HUL (DFP-4): Internal DisplayPort
[  6615.937] (--) NVIDIA(0): Acer K272HUL (DFP-4): 960.0 MHz maximum pixel clock
[  6615.937] (--) NVIDIA(0): 
[  6615.937] (--) NVIDIA(0): DFP-5: disconnected
[  6615.937] (--) NVIDIA(0): DFP-5: Internal TMDS
[  6615.937] (--) NVIDIA(0): DFP-5: 330.0 MHz maximum pixel clock
[  6615.937] (--) NVIDIA(0): 
[  6615.937] (--) NVIDIA(0): DFP-6: disconnected
[  6615.937] (--) NVIDIA(0): DFP-6: Internal DisplayPort
[  6615.937] (--) NVIDIA(0): DFP-6: 960.0 MHz maximum pixel clock
[  6615.937] (--) NVIDIA(0): 
[  6615.938] (--) NVIDIA(0): DFP-7: disconnected
[  6615.938] (--) NVIDIA(0): DFP-7: Internal TMDS
[  6615.938] (--) NVIDIA(0): DFP-7: 330.0 MHz maximum pixel clock
[  6615.938] (--) NVIDIA(0): 


In the above example, you can see that DFP-0 and DFP-4 are connected to the graphics device while the others are disconnected.

In my case, the graphics card was trying to determine which screens were still connected.  This is the incorrect behavior when I switch to the non-Linux computer through my KVM switch.  My Linux desktop resizes and all windows move to the screen that is still connected to the desktop.

To force X to force both screens to stay connected, you can use the magic 'ConnectedMonitor' option.  This goes in the Screen section.  It is confusing since the screens that you refer to in the Connected Monitor line are DFP entries that you get from Xorg, while the metamode lines refer to screens in the same terminology as the output from xrandr.  DVI-I-1 is the first DVI device, while DP-2 is the second DisplayPort device.   I suspect DFP means Digital Flat Panel, to distinguish it from Cathode Ray Tube (CRT) and Liquid Crystal Display (LCD).

Section "Screen"
    Identifier     "Screen0"
    Device         "Device0"
    Monitor        "Monitor0"
    DefaultDepth    24
    Option         "Stereo" "0"
    Option         "metamodes" "DVI-I-1: nvidia-auto-select +2560+0 {rotation=left}, DP-2: nvidia-auto-select +0+0"
    Option         "SLI" "Off"
    Option         "MultiGPU" "Off"
    Option         "BaseMosaic" "off"
    Option         "ConnectedMonitor"   "DFP-0,DFP-4"
    SubSection     "Display"
        Depth       24
    EndSubSection
EndSection

Using the example above you can force DFP-0 and DFP-4 as connected devices. Once you add the line in red, you need to restart X, and then the Nvidia driver will not reallocate the desktop when a screen is disconnected from the display (when the KVM switches).  As a happy side-effect, the Kerneloops while suspending are gone as well.


Monday, September 26, 2016

Space Chem: A brilliant game about Chemistry and Parallel computing

If you like programming, you might want to play SpaceChem, a delightful game.


The world doesn't need another platformer, another action RPG, or another First Person Shooter.  These genres have been explored beyond the point of creativity.  I played a game recently that blew my mind: it was a puzzle game that allowed you to write code to create molecules.  If you are left scratching your head, it is because the game truly is a mind-bender.


Spacechem bills itself as a puzzle game about chemical synthesis, but it is really about programming.  You build reactors using blocks which follow simple programming rules.  You can control two devices called Waldos (one Red, one Blue) that run the commands, and allow you to combine or uncombine molecules into elements.  Elements follow their true chemical properties: Oxygen can make two bonds, Hydrogen can only make a single bond.

The game board is limited, and the input and output areas are clearly delineated.  This constrains your programs, as objects cannot overlap on the game board.  And only one instruction can be placed for a single waldo: you cannot have two red instructions in a square.

Since there are two Waldos, sometimes you need to synchronize them to accomplish a task.  This forces you to reckon with parallel programming concepts.  Accomplishing the task is sufficient to advance to the next level, though you are given a graph of time and complexity (reactors used, and symbols used).  These correspond to time spent and space complexity of parallel programs.  To accomplish the goal quickly or with a small complexity, you quickly learn to keep your waldos busy as much as possible, and synchronize little.  This corresponds to high CPU utilization and reduced global barriers in parallel computing.

All put together, this is one of the most innovative games I have played.  There is a story-line which, while innovative,  pales compared to the beauty and elegance of the game.  The combination of Chemistry and Computing and puzzle solving is truly unmatched.

SpaceChem stands out as a wonderful example of gaming both as a creative art form, and a splendid way to motivate and teach programming.

Here is a program I made which creates Titanium Dioxide and Zinc Oxide from Oxygen, Zinc and Titanium.


Monday, September 19, 2016

Shutter Actuations for Nikon camera through exiv2

Digital SLR cameras have a physical shutter that wears out.  Shutter actuation count is a good measure of the age of the camera.  If you want to estimate how much life is still remaining in your shutter, or to estimate what to pay for a used camera, the actuation count can give you a good idea. Consumer cameras last about 100k actuations, and professional cameras often last much longer.

Exiv2 is a versatile tool for manipulating image metadata, and is often installed by default on modern Linux distributions.

To find shutter count from a photograph, a simple exiv2 command will do:

exiv2 -pa _DSC0901.NEF | grep Nikon3\.ShutterCount

The same command works for JPG files as well
exiv2 -pa _DSC0901.JPG | grep Nikon3\.ShutterCount


You can remove the grep filter to learn other parameters for the picture:
  • Exif.Nikon3.LensType: The lens used (D: DX, VR: Vibration Reduction)
  • Exif.Nikon3.Lens: Focal length and aperture specs of the lens used.
  • Exif.Nikon3.SerialNumber: Unique serial number of the camera body printed in the bottom of the camera.
  • Exif.Nikon3.ISOSpeed: ISO sensitivity of the sensor


Saturday, September 17, 2016

Kids jukebox using Python, Gst and Gtk


I had blogged earlier about an alphabet program for little children. That program allowed my children to recognize alphabets. Recently, a friend gifted us a children's CD that has an animal song for each letter of the alphabet. So I modified that program to play a song for each letter of the alphabet in addition to showing the alphabet.  Since it has been a while since I wrote the original program, I had to learn GObject, and gst-1.0.

The songs are hardcoded in the directory /home/dev/music/X.ogg.  You can change that location below.  The program is great for giving children control of a computer where they can play music.    You could either record your own voice for each alphabet, or a song that corresponds to the letter.  This program will also play songs for each number key 0.ogg, 1.ogg.  I have only tested this on X Windows on recent Ubuntu versions.  You can use this as a starting point for Mac or Windows assuming you can get gstreamer, GObject, Gtk and all dependencies installed.  It is easy to change this to handle MP3 files by changing the Vorbis decoder 'vorbisdec' to 'mpg123'.  To find out which decoders are installed, you can run the command 'gst-inspect-1.0 |grep dec |grep mp3'

This code is also up on github for collaboration.


#!/usr/bin/python

import datetime, string, os

# Audio handling in Gstreamer 1.0 and GObject
import gi
gi.require_version('Gst', '1.0')
from gi.repository import Gst, Gtk, Pango,Gdk, GObject

class AudioPlayer():
    """ A class that plays OGG Vorbis files. """
    def __init__(self):
        self.started = False
        # Change this location to indicate where the songs are stored.
        self.music_path = os.path.dirname(os.path.realpath(__file__)) + "/music/"
        print self.music_path

    def start(self, alphabet):
        """Starts playing music for the alphabet indicated."""
        if (self.started):
            self.pipeline.set_state(Gst.State.NULL)
        self.pipeline = Gst.Pipeline.new("pipe")

        source = Gst.ElementFactory.make('filesrc')
        demux = Gst.ElementFactory.make('oggdemux')
        # The demux does not expose any pads till it has a file. Attach
        # a callback when pads are added
        demux.connect("pad-added", self.demuxer_callback)
        self.decoder = Gst.ElementFactory.make('vorbisdec')
        converter = Gst.ElementFactory.make('audioconvert')
        sink = Gst.ElementFactory.make('autoaudiosink')

        # Attach all the elements to the pipeline
        self.pipeline.add(source)
        self.pipeline.add(demux)
        self.pipeline.add(self.decoder)
        self.pipeline.add(converter)
        self.pipeline.add(sink)

        # Attach source -> demux & decoder  -> converter -> sink
        # demux -> decoder is done in the demuxer_callback
        source.link(demux)
        self.decoder.link(converter)
        converter.link(sink)

        # Attach a signal handler for being turned on
        bus = self.pipeline.get_bus()
        bus.add_signal_watch()
        bus.connect("message", self.on_message)

        # Specify <current_dir>/music/S.ogg as the file to play when
        # the letter 's' or 'S' is pressed.
        filename = self.music_path + ("%s.ogg" % alphabet)

        # Set this as the source filename
        source.set_property("location", filename)

        # Start playing the pipeline
        self.pipeline.set_state(Gst.State.PLAYING)
        self.started = True

    def on_message(self, bus, message):
        """ Graciously handle End Of Stream and Error cases."""
        t = message.type
        if t == Gst.MessageType.EOS:
            self.pipeline.set_state(Gst.State.NULL)
            self.started = False
        elif t == Gst.MessageType.ERROR:
            err, debug = message.parse_error()
            print "Error: %s" % err, debug
            self.pipeline.set_state(Gst.State.NULL)
            self.started = False

    def demuxer_callback(self, demuxer, pad):
        "Connects the demux to the vorbis decoder"
        # Get the decoder pad that will accept the demultiplexed output
        decoder_pad = self.decoder.get_static_pad("sink")
        # And connect the newly formed pad to it, thereby joining the
        # demultiplexer to the decoder.
        pad.link(decoder_pad)

    def stop(self):
        if (self.started):
            self.pipeline.set_state(Gst.State.NULL)
        self.started = False



class BigChar():
    """ Create a Gtk window for a single giant textview that accepts
        all keyboard input. """
    def on_key_press(self, widget, data=None):
        """ Intercept all keypress events and show ascii
            characters. This requires the CAPS_LOCK to be off.  We
            don't intercept CAPS NUM or SCROLL lock, probably
            should."""
        # Set the time, even if the keypress is irrelevant
        self.set_time()

        ascii_value = data.keyval
        # Print the keycode received
        # print ascii_value

        self.audio_player.stop()

        # Uppercase and lowercase letters
        if (ascii_value >= 97 and ascii_value <= 122):
            self.display_alphabet(ascii_value - 97)
        if (ascii_value >= 65 and ascii_value <= 90):
            self.display_alphabet(ascii_value - 65)
        # Numbers
        if (ascii_value >= 48 and ascii_value <= 57):
            self.display_number(ascii_value - 48)
        # Number pad
        if (ascii_value >= 65456 and ascii_value <= 65466):
            self.display_number(ascii_value - 65456)
        # Special characters on the number pad.
        if (ascii_value == 65450):
            self.display("*")
        if (ascii_value == 65451):
            self.display("+")
        if (ascii_value == 65454):
            self.display(".")
        if (ascii_value == 65453):
            self.display("-")
        # Backspace should produce a left-pointing arrow.
        if (ascii_value == 65288):
            self.display(u"\u2190")
        if (ascii_value == 65515):
            self.display(u"\u25a1")

    def display(self, text):
        """ Show the text in the textbox."""
        self.textBuffer.set_text(text)
        start = self.textBuffer.get_start_iter()
        end = self.textBuffer.get_end_iter()
        self.textBuffer.apply_tag_by_name("real_big", start, end)

    def display_alphabet(self, index):
        """ Show the English alphabet (CAPS and lower) at 0 indexed
            position 'A a' = 0, 'B b' = 1, ...
            Also plays the song corresponding to the alphabet. """
        big = string.ascii_uppercase[index]
        small = string.ascii_lowercase[index]
        self.audio_player.start(big)
        self.display(big + " " + small)

    def display_number(self, number):
        """ Show the Number and play the song corresponding to it"""
        self.audio_player.start(number)
        self.display("%d" % number)

    def realize_handler(self, widget):
        pixmap = GdkPixbuf.Pixbuf(None, 1, 1, 1)
        color = Gdk.Color(0, 0, 0)
        cursor = Gdk.Cursor(pixmap, color, 0, 0)
        widget.window.set_cursor(cursor)

    def set_time(self):
        """Set the progress indicator to the current time.  Shows time
           in a horizontal access with the morning being near the left
           edge and night being near the right edge."""
        current_time = datetime.datetime.now()
        # Total hours past since (assume children wake up at 6am)
        minutes_past = ((current_time.hour - 6)
                        * 60.0 + current_time.minute)
        if (minutes_past < self.day_end):
            fraction = minutes_past / self.day_end
        else:
            fraction = 1.0
        self.progress.set_fraction(fraction)

    def __init__(self):
        """ Create a window with a single giant text view. Disables
            all chrome. """
        # Foreground and background color are read from here.
        #background_color = "black"
        #foreground_color = "#1111ff"
        background_color = "green"
        foreground_color = "black"

        self.audio_player = AudioPlayer()

        self.w = Gtk.Window(Gtk.WindowType.TOPLEVEL)
        # No border
        self.w.set_border_width(0)
        # Take over the entire screen
        self.w.fullscreen()

        # Connect the callback on_key_press to the signal key_press.
        self.w.connect("key_press_event", self.on_key_press)
        # self.w.connect("realize", self.realize_handler)
        # Make the widget aware of the signal to catch.
        self.w.set_events(Gdk.EventMask.KEY_PRESS_MASK)

        # Add a text view to show the key pressed
        textView = Gtk.TextView()
        # Disable a cursor in the text view.
        textView.set_editable(False)
        textView.set_can_focus(False)
        # Show the single character in the middle
        textView.set_justification(Gtk.Justification.CENTER)
        # This is the place we will write the character to
        self.textBuffer = textView.get_buffer()
        # Make the text view huge and bold
        fontdesc = Pango.FontDescription("monospace bold 400")
        textView.modify_font(fontdesc)

        # Creates a tag that is applied to the text every time
        tag = self.textBuffer.create_tag(
            "real_big"
            , background=background_color
            , foreground=foreground_color)
        # The progress bar shows the current proportion of awake-time
        # for a child.
        # Minutes are capped at 8am, which is when kids go to
        # bed. Expressed as minutes after 6am.
        self.day_end = ((20 - 6) * 60.0)
        self.progress = Gtk.ProgressBar()
        self.set_time()

        # Make the text view take the entire window
        vbox = Gtk.VBox(homogeneous=False, spacing=0)
        color = Gdk.Color.parse(background_color)[1]

        self.w.modify_bg(Gtk.StateType.NORMAL, color)
        self.progress.modify_bg(Gtk.StateType.NORMAL, color)
        textView.modify_bg(Gtk.StateType.NORMAL, color)

        vbox.pack_start(textView, fill=True, expand=False, padding=0)
        vbox.pack_start(self.progress, fill=True, expand=True, padding=0)

        self.w.add(vbox)

    def show(self):
        """ Show the window"""
        self.w.show_all()


if __name__ == '__main__':
    # Create a bigchar window, and show it.
    bigchar = BigChar()
    bigchar.show()
    GObject.threads_init()
    Gst.init(None)
    Gtk.main()