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;
}