Turn your Raspberry Pi into a network multimeter

Turn your Raspberry Pi into a network multimeter

Measuring devices which you can directly connect to the network are usually very expensive, more than 1'000 Euros. However, numerous applications, such as monitoring an experiment started on a napkin, could benefit from them if they were more affordable. We are going to show you therefore how to very easily transform a Raspberry Pi into a very flexible Ethernet multimeter, with the help of a simple Python script. A simple solution based on standard USB modules, demonstrated in a video.


Update: the source code is now available on GitHub: https://github.com/yoctopuce-examples/multimeter

To make our Ethernet multimeter, we used:
- a Raspberry Pi, model B, for the Ethernet connection (28 EUR)
- a Yocto-Display OLED screen, for a very readable display (70 EUR)
- a RaspBox, to protect the Raspberry Pi (12 EUR)
- Yoctopuce sensors, e.g. a Yocto-Thermocouple (37 EUR) or a Yocto-Volt (48 EUR)
- a small Python script running on the Raspberry Pi to control the screen (free)
- the VirtualHub software (downloadable from our web site), for remote access to the measures (free)
The final price depends on the sensors you use, but it remains very attractive compared to a commercial solution (in the above example, you can have two thermocouple inputs and a tension measure with isolation for less than 200 EUR).

Assembling

Nothing complex, as we only use standard USB devices. We make 4 small holes on the top plate of the RaspBox so we can fix the Yocto-Display below it to protect it. The screen is connect with a USB cable through a 1.27mm connector, more compact than a micro-B plug. This connector adapts to all our modules and will be available in our shop in a few days.

Here is what we are going to build
Here is what we are going to build



Installing
Take a standard Linux image for Raspberry Pi, and check that your Raspberry Pi is up-to-date by running "sudo raspi-config" and by selecting "update". The USB bug on the Raspberry Pi is still not completely fixed, so make sure that your /boot/cmdline.txt file contains the dwc_otg.speed=1 option. Then restart. Edit: The "dwc_otg.speed" option is no more needed on Raspberry Pi B+ and later models.


Then, download and install the Yoctopuce VirtualHub, which automatically provides network access to your sensors so that you can query them remotely from your workstation, and install the Yoctopuce for Python using PyPi . That's it. We only need to write a few lines of Python to drive it all.

Coding
To be as easy to use as possible, the code repeatedly detects connected modules to automatically adapt to the connected probe. To do so, we start from the event programming example provided in the Yoctopuce library and generalize it somewhat to keep an up-to-date dictionary of available probes.

# List of sensors to display (discovered by Plug-and-play)
sensors = { }
currentSensor = ""

# add sensor to dictionary when plugged
def deviceArrival(m):
    global sensors, currentSensor
    for i in range(m.functionCount()):
        fctName = m.functionId(i)                     # eg. "voltage1"
        fctType = re.sub("\d+$", "", fctName)         # eg. "voltage"
        hwId = m.get_serialNumber() + "." + fctName
        yocto_mod = getattr(yoctopuce, "yocto_"+fctType.lower(), None)
        if(yocto_mod is not None):
            className = fctType[0].upper()+fctType[1:]# eg. "Voltage"
            YClass = getattr(yocto_mod, "Y"+className)# eg. YVoltage
            yFind = getattr(YClass, "Find"+className) # eg. YVoltage.FindVoltage
            fct = yFind(hwId)
            if getattr(fct, "get_unit", None) is not None:
                currentSensor = fct.get_hardwareId()
                sensors[currentSensor] = \
                    { "name" : fct.get_friendlyName(),
                      "val"  : fct.get_unit() }
                fct.registerValueCallback(sensorChanged)
    refreshDisplay()

# update display when sensor changes
def sensorChanged(fct,value):
    hwId = fct.get_hardwareId()
    if hwId in sensors: sensors[hwId]['val'] = value+" "+fct.get_unit()
    refreshDisplay()

# remove sensor from dictionary when unplugged
def deviceRemoval(m):
    deletePattern = m.get_serialNumber()+"\..*"
    deleteList = []
    for key in sensors:
        if re.match(deletePattern, key): deleteList.append(key)
    for key in deleteList:
        del sensors[key]
    refreshDisplay()



The display code is only a few lines long. As you can have several sensors, we keep the name of the function currently displayed in a variable.

# Get the display object
display = YDisplay.FirstDisplay()
if display is None:
    sys.exit("Display not connected")
display.resetAll()
dispLayer = display.get_displayLayer(1)
dispLayer.hide()

# Update the display value if needed (with double-buffering)
def refreshDisplay():
    global currentSensor
    if currentSensor not in sensors:
        currentSensor = sensors.keys()[-1]
    sensor = sensors[currentSensor]
    dispLayer.clear()
    dispLayer.selectFont("Small.yfm")
    dispLayer.drawText(0,0,YDisplayLayer.ALIGN.TOP_LEFT,sensor["name"])
    dispLayer.selectFont("Medium.yfm")
    dispLayer.drawText(127,28,YDisplayLayer.ALIGN.BOTTOM_RIGHT,sensor["val"])
    display.copyLayerContent(1,2)



To change the displayed function, we simply use the button inputs available on the screen. You don't even need to add a real push button: as they are analog inputs with calibration, you only need to lightly touch the contacts with your fingers to activate the inputs, following the principle of the Makey-Makey.

# Get the buttons objects, get an event when "pressed"
serial = display.get_module().get_serialNumber()
prevButton = YAnButton(serial+".anButton1")
nextButton = YAnButton(serial+".anButton6")
prevButton.set_userData(False)   # False = released
nextButton.set_userData(False)   # False = released
prevButton.registerValueCallback(buttonPressed);
nextButton.registerValueCallback(buttonPressed);

# Callback whenever a button value changes
def buttonPressed(fct,value):
    global currentSensor
    if(int(value) > 500):    # button is released
        fct.set_userData(False)
        return
    if(fct.get_userData()): # button was already pressed
        return
    # Button was just pressed, cycle through sensors values
    fct.set_userData(True)
    delta = (1 if fct.get_hardwareId()[-1] == '1' else -1)
    if(delta != 0):
        keys = sensors.keys()
        idx = len(keys)-1
        for i in range(len(keys)):
            if keys[i] == currentSensor:
                idx = (i+delta+len(keys)) % len(keys)
        currentSensor = keys[idx]
        refreshDisplay()


For ease of use, we add a displayable function: the IP address of the Raspberry Pi. This greatly simplifies life for these machines without screen ...

# Put the Raspberry Pi itself as default sensor, to show IP address
sensors[""] = { "name" : socket.gethostname(),
                "val"  : findMyIP() }
refreshDisplay()



We are done for direct display, you now have an Ethernet multimeter. As we installed the VirtualHub, you can query the sensors remotely through the same Yoctopuce API, from any programming language.

And, icing on the cake, you only need a few clicks to configure the VirtualHub to automatically post the measured values on Cosm. Thus, you can follow your experiments on your Smartphone without writing any additional line of code...

Data can be collected on Cosm automatically
Data can be collected on Cosm automatically



The result

  




1 - molkko Monday,april 29,2013 13H25

Hello, This article does not discuss raspi/yocto USB problem discussed in some earlier articles. Does this mean that USB problem has gone away? Or are some special tricks still needed in order to have fully functional USB for raspi-yocto combination?
tnx, Mikko

2 - mvuilleu (Yocto-Team)Monday,april 29,2013 13H46

@molkko: unfortunately the USB stack of the Pi is still not fixed, see the comments in the "Installing" section

3 - camillo777 Wednesday,june 26,2013 16H57

Hi all, just a note, I tried but in the latest Raspbian updates setting "dwc_otg.speed=1" in "cmdline.txt" seems to make raspberry pi boot forever...

4 - haba Tuesday,january 28,2014 18H51

Hi, I tried this example by having also following imports in beginning:
from yoctopuce.yocto_api import *
from yoctopuce.yocto_temperature import *
from yoctopuce.yocto_relay import *
from yoctopuce.yocto_display import *

But when I try to run python code, I got following error:
Traceback (most recent call last):
File "yocto_multimeter.py", line 50, in

5 - haba Tuesday,january 28,2014 18H53

File yocto_multimeter.py, line 50, in module
display = YDisplay.FirstDisplay()
File "/usr/local/lib/python2.7/dist-packages/yoctopuce/yocto_display.py", line 1212, in FirstDisplay
err = YAPI.apiGetFunctionsByClass("Display", 0, p, size, neededsizeRef, errmsgRef)
File "/usr/local/lib/python2.7/dist-packages/yoctopuce/yocto_api.py", line 1787, in apiGetFunctionsByClass
res = YAPI._yapiGetFunctionsByClass(ctypes.create_string_buffer(class_str.encode("ASCII")), precFuncDesc, dbuffer, maxsize, ctypes.byref(cneededsize), buffer)
AttributeError: class YAPI has no attribute '_yapiGetFunctionsByClass'

6 - haba Tuesday,january 28,2014 20H01

Ok, got it working, need to add
errmsg=YRefParam()
if YAPI.RegisterHub("usb", errmsg)!= YAPI.SUCCESS:
sys.exit("init error"+errmsg.value)


However full source code would be helpful for future users

Yoctopuce, get your stuff connected.