Sunday, August 28, 2022

Dell Wyse 5060 Thin Client

OMG, it has been over a year since I posted last... I am so sorry! The COVID-19 situation gave me some sort of reluctance, in which I indulged, watching other folks' work. My Raspberry Pi projects were quiet too. I simply tried to focus on growing a business... Has that worked? Maybe!

Anyway, for my business, I figured that my (t)rusty old dual core Celeron NUC was not cutting it any longer. I like the NUC, but, it might do better service to me as an Owncloud server or something alike. So, in replacing my authoring workstation, I was looking for silent (!) alternatives. When I was employed, many years ago, I was given a "thin client" to connect with the company's servers. This thing was fully passively cooled and silent but butt ugly.  Not sure what brand it was, and this is not important either.

So, in recall of what the silence during my thin client usage was, I started researching on a suitable thin client for my own business. The distinction here is that there is no intention to use the thin client as intended by the manufacturer. The original plan was to replace my (t)rusty old NUC, with something more silent and more powerful.

The research of tech and availability got me to the Dell Wyse range, in particular the 5060 model. And here is why:

  • accessible BIOS (password "Fireport") with USB boot option
  • quad core AMD 64bit CPU up to 2.4 GHz
  • 2 SODIMM sockets (one populated with a 4 GB card)
  • 8 GB SATA SSD module (internal connector)
  • 4 USB2 ports
  • 2 USB3 ports
On top, there is an internal connector for a wlan card. 

Hence, this thing should be pretty decent as a Linux workplace PC. In fact, I am writing this article on this particular piece of kit.

However, there is of course no free lunch, so, there was some work to be done before arriving at this workstation.
First of all, 8 GB of storage night be enough to run Puppy Linux, or distributions alike, however, more serious work will ask for a heavier distro. I am a long time user of Linux Mint, as this is what I was intending to use on this machine. Linux Mint does not install on 8 GB storage however.
This might look like a big problem, but it isn't, promised!

First, I want to give you an overview what the thin client looks in it's original state...
DELL WYSE 5060

Now that we has established how the thin client looks with the covers removed, lets have a look at the guts.
SATA SSD module

installed SODIMM module

SODIMM module removed, showing 2nd slot below

I had a 2GB SODIMM module in my junk box, so, I dropped it in. The original 4 GB module is now in slot 0, while the spare 2 GB module is in slot 1. While this may not be a big deal, every little bit of (gratis) RAM helps.
2 SODIMM slots used

The more interesting mod is of course to purpose the the SATA slot. 8 GB might be something, but more might be something better.

It just so happened that I had a 240 GB Sandisk SATA SSD in one of my junk boxes. It previously served a Raspberry Pi 4 but was not presently in use. There are other SSDs in my junk box, however, it is important to point out that the PCB of the Sandisk SSDs are particularly small. Have a look:
SanDisk SSD plus 240 GB
Now, lets compare that to the SSD module of the Wyse 5060...
On the left: 8 GB SSD module, on the right: 240 GB SSD
It is obvious that the Sandisk PCB is a little bit larger than the one of the original 8 GB SSD, However, it is not large enough to pose a real problem.
First of all, there is a screw and a respective terminal right in the middle of the original SSD. For electrical reasons, this has to be covered (by some electrical tape).


When installing the Sandisk SSD, some additional measures are needed to prevent electrical problem. In particular the EM-shield of the of RAM section might interfere with the larger SSD pcb. In order to overcome this problem, pliers might be required to bend the EM-shield such to no longer interfere/touch the SSD pcb.
Sandisk 240 GB SSD pcb installed
I will spare you a picture how I secured the SSD with just a strip of electrical tape.

This was a weekend project, i.e. just a few hours old. Still, I think this is a very worthy project to do. Inexpensive (if you are patient enough) and pretty rewarding in the results in my view.

I am running this with Linux Mint 20.3 and I am very pleased. I wish to point out that tweaking of the CPU frequencies might improve your experience.

Sunday, April 4, 2021

Raspberry Pi 400 Document Scanning with Canon LiDE 30

My RPi400 evolves more and more into my daily driver for my office activities. Here and there I got paper documents to share, so I needed to scan those, for sending them as PDFs around the world.

Many year ago, I purchased a Canon LiDE 30 USB scanner. This scanner is powered by the USB connection only. Until today, I used this particular scanner on a powered USB hub, thinking the power draw would be to great for the RPi400.
Well, I was wrong. Today, I hooked the scanner up to one of the USB3 ports of the RPi400 directly and the scanner worked perfectly.

If you wonder, the Canon LiDE 30 is supported up to Windows Vista 32bit or Mac OS X 10.5 (Leopard) and won't work on anything more modern than that in Microsoft or Apple environments.
However, we are in good luck with linux and the "sane" environment. 
On Raspberry Pi linux systems, XSane is available. To my surprise, the RPi400 delivers sufficient current on the USB3 connectors to power my Canon LiDE 30 scanner. 


Monday, February 22, 2021

Raspberry Pi and Arduino revisted

In my earlier post, locating a valid Arduino connection, I used exception handling by catching the error message created by attempting to open a non-existing device.
Of course there is an easier way of doing this in a POSIX compliant system, by looking at the existence of the respective device files. If you are confused about the ttyUSB0 device, this is how an inexpensive Arduino clone appears in my system.

Here is my new approach, which is bit more elegant and less brutish. This approach uses the "glob" module and the "set" data-type.


import glob

def listFiles(prefix):
    _=set()
    for file in glob.glob(prefix):
        _.add(file)
    return _

def findArduino():
    ser_dev={'/dev/ttyACM0','/dev/ttyACM1','/dev/ttyUSB0'}
    dev_list=listFiles('/dev/tty*')
    for _ in ser_dev:
        if _ in dev_list:
            return _

if __name__=='__main__':
    arduino=findArduino()
    if arduino:
        print('Arduino found at',arduino)
    else:
        print('Arduino not found')


Python Script Re-Import

Here is another study, pretty useless what it does actually, however, this might come in handy for some self-modifying projects.

This script writes a script called test01.py. It then imports the test01.py module, overwrites it, including data to a list and finally re-imports the modified test01.py module. At the very end, a function of the re-imported module is called. As a bonus the test01.py module is a fully self sufficient python program itself.

The study demonstrates how a python program can not only modify data but also modify its own functionality during run-time. Having a dictionary with python instruction could be an interesting way to write self-learning scripts.


from importlib import reload
import sys

a=[]

def modify(a,i):
    a.append(i)
    return a


# create a module to import
filename='test01.py'
f=open(filename,'w')
f.write(f'b=[]\n')
f.write(f'def test01(b):\n')
f.write(f'    return b\n')
f.write(f'if __name__=="__main__":\n')
f.write(f'    test01(b)\n')
f.write(f'    print(b)\n')
f.close()

# import the module
from test01 import *

for i in range(3):

    # play with data
    a=modify(a,i)

    # write modified module
    filename='test01.py'
    f=open(filename,'w')
    f.write(f'b='+str(a)+'\n')
    f.write(f'def test01(b):\n')
    f.write(f'    b.pop(1)\n')
    f.write(f'    return b\n')
    f.write(f'if __name__=="__main__":\n')
    f.write(f'    test01(b)\n')
    f.write(f'    print(b)\n')
    f.close()

    # re-import module
    reload(sys.modules['test01'])
    from test01 import b, test01    

    # show data from module
    print(b)

print('===')
print(a)

# run function from module
print(test01(a))


Thursday, February 18, 2021

Python Moving Average

In data analysis, moving averages are often used to "smooth" noisy data. In terms of signal processing, applying a moving average is a very crude low-pass filter. In fact, a moving average filter is a special case of an FIR (finite impulse response) filter.

Here is a study I wrote in Python using numpy and matplotlib.

import numpy as np
from matplotlib import pyplot as plt

# calculate moving average and add zero-padding
# the function takes two parameter, the data array and the
# number of bins for averaging, default 5 can be overwritten
def moving_average(a, n=5):

    ret = np.cumsum(a, dtype=float)
    ret[n:] = ret[n:] - ret[:-n]
    return (np.insert((ret[n-1:]/n),0,[0]*(n-1)))

# create some sort of noisy data
data = np.arange(100)
data = np.sin(data/5)+np.cos(data/20)+np.random.randn(100)/5

data_avg = moving_average(data,7)

print(data)
print(data_avg)

plt.plot(data,'.')
plt.plot(data_avg,'.')
plt.show()


Tuesday, February 16, 2021

Python Circular Buffer

Just to continue the series about watching me learning Python, here is one that might be useful for any  application that samples data in real time into a circular buffer, aka ring buffer. This examples runs two threads, one thread is "reading" (creating) data and depositing this data into the ring buffer, while the other thread reads a certain amount of said data independently.
Obviously, the ring buffer is in shared memory (global) and accessible by both threads.
Of course, putting random numbers into a buffer in a particular frequency and reading said buffer out with a different frequency is not solving any real problems, however, this is supposed to demonstrate how two threads could be used for sampling and analyzing data.

from time import sleep
from threading import Thread
import random

def read_val(var):
    length=len(var)
    global pos
    while True:
        for i in range(length):
            var[i]=random.random()
            pos=i
            print("generated ",i, var[i])
            sleep(0.5)

def pick_val(var):
    length=len(var)
    while True:
        sleep(0.1)
        print("most recent ",pos,var[pos])
        for i in range(length):
            lpos=(pos+i)%length
            print("=> ",i,var[i],lpos,var[lpos])

# initialize memory
data = []
for i in range(5):
    data.append(0)

t1 = Thread(target=read_val, args=(data,))
t2 = Thread(target=pick_val, args=(data,))

t1.start()
t2.start()

Saturday, February 13, 2021

Raspberry Pi with Arduino

Over the last few months I was developing hard- and software for a project. While I won't disclose any details of the project here, I think it would be fun for the readers to see me learning Raspberry Pi, Arduino and Python.
And yes, the previous Raspberry Pi blog posts were all somewhat triggered by the project mentioned above. In fact, the main idea of the project was not only using Raspberry Pi hardware for the project itself, bu also as main hardware for the company running said project, including all staff. (I am diverting...)

On particular problem with Arduino boards hooked up to Raspberry Pi boards is that the device might change from /dev/ttyACM0 to /dev/ttyACM1 from one use to the other. When using cheap clones, the device might be /dev/ttyUSB0.
So, in a project using Arduino (clone) boards with a Raspberry Pi board, one needs to test which device is in use.

Here comes the learn Python with me. This might not be the most elegant way to do this, however, here is a solution that works for me:

def initialize():
    global SERIAL_PORT
    global ser
    # find a valid serial port
    try:
        TSER='/dev/ttyACM0'
        print('trying '+TSER)
        tser = serial.Serial(
            port=TSER,
            baudrate = 115200,
            parity=serial.PARITY_NONE,
            stopbits=serial.STOPBITS_ONE,
            bytesize=serial.EIGHTBITS,
            timeout=1
        )
        print(TSER + ' found')
        SERIAL_PORT=TSER
        tser.close()
    except serial.serialutil.SerialException:
        try:
            TSER='/dev/ttyACM1'
            print('trying '+TSER)
            tser = serial.Serial(
                port=TSER,
                baudrate = 115200,
                parity=serial.PARITY_NONE,
                stopbits=serial.STOPBITS_ONE,
                bytesize=serial.EIGHTBITS,
                timeout=1
            )
            print(TSER+' found')
            SERIAL_PORT=TSER
            tser.close()
        except:
            print('no Arduino found, using clone')
            SERIAL_PORT='/dev/ttyUSB0'
            
    finally:
        print('using '+SERIAL_PORT)

    ser = serial.Serial(
        port=SERIAL_PORT,
        baudrate = 115200,
        parity=serial.PARITY_NONE,
        stopbits=serial.STOPBITS_ONE,
        bytesize=serial.EIGHTBITS,
        timeout=1
    )