Friday, December 20, 2019

Get MAC Address for a Pi Cluster

There are a lot of ways to get the MAC address for a Raspberry Pi. For the Super Pi there were a lot of Pi, 1050 to be specific (two of them are dead on arrival and we disabled 24 of them to get the exact number we wanted, 1024), we looked at the DHCP log files. Roy and Mike spent a few days doing this. Think about it, if you spend one minute times 1050, that's 17.5 hours! I'm surprised they didn't go crazy but they seemed to be having fun. For the Mini Super Pi, I need to know the order.

At this point I'm going to digress a little. When building a large Raspberry Pi cluster at some point it's worth network booting. I'd say after 8 the cost of network booting is worth it. Just the time and expense of buying and cloning eight network cards. Network booting a Pi isn't terribly difficult, it's doing it consistently with a lot of them that's the problem. The netboot on a Pi times out after a short period of time, it isn't implemented 100% to spec, I could go on but these are things you can find out with some research. Although it's buried so if people want me to document these issues let me know in the comments. If there are any problems netbooting the Pi can be dead in a big cluster with no way to reboot it except reboot the entire thing. It helps to have a static IP address. Best way of doing this is by using DHCP (yeah, that's confusing) but map the MAC address to an IP address. Oh, also if you have enough Raspberry Pi, the MAC address may not be unique. We have two Pi with the same MAC addresses. Most clusters won't run into this. Also, network booting while not difficult requires the use of overlay file system. The problem comes when combining the two which is rather difficult. I got overlay file system working. Roy merged netboot and overlay and Vijay did this with Oracle Linux. There are a few other options netboot and redirect all writes to separate files and hope you got all them, create an NFS mount for each Pi (yeah, no). The only downside to netboot + overlay file system is you can't run Docker, which means you can't run K8 because Docker uses overlay and you can't use overlay on top of overlay.


OK, back to the reason for this blog post. My new system for obtaining the MAC address is as follows:

1. Install Raspbian on an SD card. I used belenaEtcher on Mac just because it's easy.
2. Copy the following Python script to the Pi and call it pimac.py

import socket
import time
import threading
import datetime
import uuid
import random

# This is needed so we get a network interface
time.sleep(20)

# Global variables
MacAddress = hex(uuid.getnode())
print(MacAddress)
print(type(MacAddress))

if MacAddress.endswith('L'):
    MacAddress = MacAddress[:-1]
    print(MacAddress)

client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # UDP
client.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
client.bind((""2222))


server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
server.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
server.settimeout(0.2)
server.bind((""3333))
hostname = socket.gethostname()    
IPAddr = socket.gethostbyname(hostname)    
print("Your Computer Name is:" + hostname)    
print("Your Computer IP Address is:" + IPAddr)

def SendMessage(portmessage):
    data = message
    endodeddata = data.encode()
    server.sendto(endodeddata, ('<broadcast>', port))
    print("message sent!" + data)

while True:
    SendMessage(3333, MacAddress)
    time.sleep(2)

3. Create a cron job:

sudo crontab -e

4. Add this line to the end of the file:

@reboot python /home/pi/pimac.py &

5. Copy the following Python script to your desktop and run it. I'm running it in Visual Studio Code.

import datetime
import os
import signal
import socket
import threading
import time
import re
import uuid

if os.name == 'nt':
    signal.signal(signal.SIGINT, signal.SIG_DFL)


server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
server.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)

hostname = socket.gethostname()
IPAddr = socket.gethostbyname(hostname)
print("Your Computer Name is: {0}\n".format(hostname))
print("Your Computer IP Address is: {0}\n".format(IPAddr))


def waitForPosts():
  print('Ready')

addresses = {}

class Listen(threading.Thread):
    def __init__(selfport):
        threading.Thread.__init__(self)
        self.port = port
        self.UIDCount = 0

    def run(self):
        client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # UDP
        client.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
        client.bind((""self.port))

        while True:
            data, addr = client.recvfrom(1024)
            mac = data
            if mac not in addresses:
                addresses[mac] = mac
                val = mac.decode()
                print (':'.join(re.findall('..''%012x' % int(val, 16)))) 


publish_thread = Listen(3333)
publish_thread.start()

waitForPosts()

Note: If you decide you want to remove the dictionary in the code above to allow duplicate MAC addresses to print out, which his a great debugging tool and since UDP is not guaranteed is a perfectly fine way to go, then you might want to remove duplicates. To do this in Visual Studio Code this is one way I found to do it:

  1. Control+F
  2. Toggle "Replace mode"
  3. Toggle "Use Regular Expression" (the icon with the .* symbol)
  4. In the search field, type ((^[^\S$]*?(?=\S)(?:.*)+$)[\S\s]*?)^\2$(?:\n)?
  5. In the "replace with" field, type $1
  6. Click the Replace All button ("Replace All").
6. So now insert the SD card, plug the Pi into network, and power and wait. The output on your desktop will be a list of your MAC addresses in a known order and it is easy to know if one is dead and which one.

Tuesday, December 17, 2019

Shopify Payment System

It's a long story, but I have been dealing with an issue with Shopify for well over a year now. Many websites use them as their Payment system behind their merchant account. And they are very convenient, if one knew what they were getting into. Unfortunately there is absolutely zero disclosure on any of the websites that use it how this will work, how to edit your stored information or how to delete the data they stored. This is a direct breach of the GDPR, Informatique et Libertés as well as the new California data privacy law CCPA.

So with about a year of working with all of the merchants and Shopify support, I finally can report there is a way to opt out of Shopify:

https://pay.shopify.com/optout

Monday, December 9, 2019

Sparkfun MP3 Shield


I have used a lot of these MP3 player shields in projects. They are super awesome and super simple to use, but I figured a small writeup was worthwhile as there are some details that aren't in the official documentation as well as some tips I have when using the shield.

The MP3 shield uses just about every pin on the Arduino. The documentation states you can use D5, D10 and A0-A5. However, I have found that for some reason when using the shield I have found neither D5 nor D10 to be usable. So typically what I do is setup a software serial using two of the analog pins:

#include <SPI.h>
#include <SdFat.h>
#include <SFEMP3Shield.h>
#include <SoftwareSerial.h>


SdFat sd;
SFEMP3Shield MP3player;

SoftwareSerial input(A0, A1); // RX, TX


void setup() {
  Serial.begin(115200);
  
  input.begin(9600);
  input.flush();

  if(!sd.begin(9, SPI_HALF_SPEED)) {
    sd.initErrorHalt();
  }
   
  if (!sd.chdir("/")) {
    sd.errorHalt("sd.chdir");
  }

  uint8_t result = MP3player.begin();

  if(result != 0) // check result, see readme for error codes.
  {
    Serial.println("error");
    Serial.print(F("Error code: "));
    Serial.print(result);
  }

  // Loudest volume for left and right
  MP3player.setVolume(0, 0);
  
  Serial.println("Ready");
}

void loop() {
  if (input.available()) {
    Serial.println("reading");
    String action = readFromSerial();
    Serial.println(action);
    input.flush();
  
    if (action == "play") {
      Serial.println("Playing"); 
      uint8_t result = MP3player.playMP3("awesome.mp3");
      delay(3000);
      Serial.println("Ready again"); 
    }
  }
}

String readFromSerial() {
  static char buffer[80];
  if (readline(input.read(), buffer, 80) > 0) 
  {
    Serial.println(buffer);    
  }
  return buffer;
}

int readline(int readch, char *buffer, int len)
{
  static int pos = 0;
  int rpos;

  if (readch > 0) {
    switch (readch) {
      case '\n': // Ignore new-lines
        break;
      case '\r': // Return on CR
        rpos = pos;
        pos = 0;  // Reset position index ready for next time
        return rpos;
      default:
        if (pos < len-1) {
          buffer[pos++] = readch;
          buffer[pos] = 0;
        }
    }
  }
  // No end of line has been found, so return -1.
  return -1;
}

Monday, November 18, 2019

Using Max Build Volume of Ultimaker 3D Printer

Sometimes you have to print at the max build volume of your 3D printer. You'll get the error: "does not fit build volume". I have an Ultimaker and the easiest slicer to use is Cura. And sometimes I want the full build volume of 330 x 240 x 300 mm. If you set the "skirt link count" to zero, it won't print the line around the part so you can get the full x and/or y. As you may know the reason for the skirt is so that the printer gets a consistent extrusion of the filament, but I you need the full bed size, then you need the full bed size. Also turn off brim and set build plate adhesion to "none". Lastly set "travel avoid distance" to 0. Do it in that order for best results.



Tuesday, November 5, 2019

Raspberry Pi Super Computer STL Files Published on Thingiverse

Finally. I'm sorry it took so long. Here it is, the files to print your very own 2U rack mountable Raspberry Pi holder for 21 Raspberry Pi 3B+. Let me know if you have any questions or problems. Just because we made over 50 of these, doesn't mean it's easy.

https://www.thingiverse.com/thing:3958586

Monday, September 2, 2019

Bringing Pi Home

Tuesday, August 20, 2019

Arduino Eithernet Shield 2 NTP Time

I'm trying to get an accurate time on an Arduino. I have an Ethernet Shield 2 attached to an Arduino Mega. There's also a couple current sensors and a barcode scanner. The main reason for this post is for those people that happen to have the Ethernet Shield 2 on any Arduino, and want to get the time. Every single example on the internet or in the Arduino IDE examples doesn't work, except for this one:

https://www.arduino.cc/en/Tutorial/UdpNtpClient

So as of the time of reading this, that tutorial will most likely be out of data, but let's say for the next 6 months it'll be the place to go.

So, here's a little insight into what I'm doing and my decisions along with some code at the end:

1. I have an Arduino. That means no clock.
2. I have an Ethernet Shield 2. That means there are an abundance of clocks on the internet.
3. I am not hooking up a clock to the Arduino. That means I need to use the Arduino to keep time.
4. So I'm going to do this by polling a clock on the internet, keeping that time around and adding the time since boot to that time. It isn't a clock you'd want to use as your morning alarm clock, but it'll be accurate enough for what I need.

So I hacked together two functions from that tutorial:

unsigned long getCurrentTime() {
  unsigned long result = 0;
  sendNTPpacket(timeServer); // send an NTP packet to a time server

  // wait to see if a reply is available
  delay(1000);
  if (Udp.parsePacket()) {
    // We've received a packet, read the data from it
    Udp.read(packetBuffer, NTP_PACKET_SIZE); // read the packet into the buffer

    // the timestamp starts at byte 40 of the received packet and is four bytes,
    // or two words, long. First, extract the two words:

    unsigned long highWord = word(packetBuffer[40], packetBuffer[41]);
    unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]);
    // combine the four bytes (two words) into a long integer
    // this is NTP time (seconds since Jan 1 1900):
    unsigned long secsSince1900 = highWord << 16 | lowWord;
    Serial.print("Seconds since Jan 1 1900 = ");
    Serial.println(secsSince1900);

    // now convert NTP time into everyday time:
    Serial.print("Unix time = ");
    // Unix time starts on Jan 1 1970. In seconds, that's 2208988800:
    const unsigned long seventyYears = 2208988800UL;
    // subtract seventy years:
    unsigned long epoch = secsSince1900 - seventyYears;
    // print Unix time:
    Serial.println(epoch);
    result = epoch;
  }
  else {
    result = 2099414000; // Picked a date so we can test when not connected to the internet.
  }

  return result;
}

String timeToString(unsigned long value) {
    String result = "";
    unsigned long epoch = value;
    // print the hour, minute and second:
    result += String((epoch  % 86400L) / 3600); // print the hour (86400 equals secs per day)
    result += String('-');
    if (((epoch % 3600) / 60) < 10) {
      // In the first 10 minutes of each hour, we'll want a leading '0'
      result += String('0');
    }
    result += String((epoch  % 3600) / 60); // print the minute (3600 equals secs per minute)
    result += String('-');
    if ((epoch % 60) < 10) {
      // In the first 10 seconds of each minute, we'll want a leading '0'
      result += String('0');
    }
    result += String(epoch % 60); // print the second

    return result;
}

unsigned long startTime;

void setup() {
   // ...
   startTime = getCurrentTime();
}

And anytime you want the current time viola!

timeToString(startTime + millis() / 1000)

Wednesday, July 24, 2019

Fusion 360 - Threads Not Exported to STL

There is one thing that I always have problems with, so hopefully by writing this short blog post I'll remember more often. That problem is creating threads properly. Usually I noticed after I have already printed the part. Always remember to choose the "Modeled" check box. I wish I could make this the default.

Also, if you need to remove part of the hole after the threads in the timeline, you'll have thread remnants, so move the threads to be later in the timeline, then "Cut" the hole.




Thursday, July 18, 2019

Very Large Raspberry Pi Cluster - Part VI

At this point I've shown how the all the Pi will be held in place to make a 2U rack, and next is the overall structure but that will have to wait until next week. So for this post I wanted to talk a little about the software and what we're going to run on all these Pi. A lot of data will be processed and sent to the cluster. So here's the high level of the software stack:


  • Each Pi will network boot from a central server running Oracle Linux
  • The boot image will be running Oracle Linux
  • It will be running Kubernetes and Docker
  • There will be a very large video wall run by a Windows server
  • Everything will be written in Java
  • Gluon is helping
What exactly it will do will remain a mystery until you come see it in person. So sign up for Code One and come see it!

Thursday, July 11, 2019

Very Large Raspberry Pi Cluster - Part V

Last week I said I'd talk more about this part. So here is what the main bracket of the Pi holder looks like and the two parts individually.

The print time for the Pi holder is 98 minutes. It's a lot but it's a lot of plastic too. I print 12 of them at time, at 15% infill, with a .8mm print head, no rafts or supports. I can print one rack in 24 hours. You can do the math from there how many days the printers will be running. I spent a few days optimizing the amount of filament and the print speed with the strength needed. It is over engineers and will not break when being shipped. It is easy to assembly and requires very few parts. The reason for the curves is because it prints faster since the print head doesn't have to slow down when it approaches a corner. I was going to make the front two exes that are square round but it only saved 30 seconds print time and looks better being square when fully assembled.

Here are some views of the models from different angles since these are two separate pieces. Here are two views with the two pieces combined. You will notice a square rod coming out of that slot.


That square rod is a piece of aluminum extrusion. Specifically it is 80/20 series 10 that is .5in x 1in x 18in. I had 16.35mm milled off the end and the ends tapped for 10-32 by a local machine shop (GSFAB in Santa Cruz).

So I print 21 of the Pi Holders and two of them end caps (mirror of one another). Bolt an end cap to one end. Add one plastic threaded insert to the top hole, bolt a Pi on (the bottom holes just sort of clip on), slide it on the 80/20, repeat. After 21 Pis, bolt the last end cap and it looks like this:


So, there's a 2U containing 21 Raspberry Pi in a 19in server rack. I've only got 48 more of these to print and assemble.

Wednesday, July 3, 2019

Very Large Raspberry Pi Cluster - Part IV

So I've had a couple weeks here without much of a progress update. A lot of work has gone on. So here's a quick update before the holiday. First some photos. The first one is of the original prototype 3D printed out with some Pi in it.



This is a very nice design. The Pi just snap in place, there's room in the back to get to the micro USB power plug. But there are three problems:

1. I can only print half a 2U, so things had to be designed in such a way that once side is put into place, slid over, the other side goes in and is slid over and bolted, the first one slide over bolted, then the two remaining Pi on the ends are put in. And it requires two people.

2. It takes 50 hours to print one 2U holding 21 Pi.

3. It requires access to the rear of the rack. Without those rear bolts it wouldn't survive shipping.

So back to the drawing board. And here is the new design.



It's difficult to see what's going on here but there are 23 individually 3D printed pieces; one for each Pi and one for each end cap. They connect together by sliding on a piece of series 10 80/20 1"x.5"x10" long that has had about 3/4" milled off and the ends tapped. These are at the machine shop now so I will show some photos when I get them back.

One thing that's really nice about 3D printing is the RepRap; or the ability to rapidly prototype and revision. 3D printing is slow, but it's faster enough to do this. Here are some of my tests.





The one in the middle is close to the final design. You can see that two of the bolt hols of the Pi have a post and the last one has a threaded insert for a bolt.


Next post I will go into these two parts in detail. They have been designed for strength, speed of 3D printing and ease of assembly.

Thursday, June 20, 2019

Very Large Raspberry Pi Cluster - Part III

I spent most of today fighting with Fusion 360 so I don't have much progress to report besides a serious complaint about Fusion 360. A new updated was released. If I didn't upgrade I couldn't save. If I upgraded I lost my changes. I had no choice but to upgrade, loose my changes and redo them. Then when I loaded up my models there were errors with unresolved imported components. The error states that if I save my model in this state I will loose my model. Personally I find this unacceptable and while I like Fusion 360, I like the disruption to the CAD world, many of the features, the price is reasonable and the fact that it runs on Mac and Windows, I also find it extremely buggy and a toy. I may be switching back to SolidWorks. Hopefully I get all of this resolved in the next few days and can report on the progress.

Update: After restarting Fusion 360 five times, and reloading the model after closing it with the error saying it will be destroyed, I finally got all the linked models and everything to load. So it was just a lot of ignoring errors and closing and reopening things and much ado about nothing, but extremely worrisome that just an update being pushed could cause such concern.