Fotometer Marke Eigenbau

Tiegars

Member
Guten Abend liebe Bastler :smile:

Ich bin kein Freund des Messens. Alter Spruch eines Schlossermeisters: "Wer misst misst Mist". Aber nachdem ich von einigen Hersteller sehr enttäuscht wurde was die Qualität der Testreagenzen betrifft, habe ich mich entschieden ein Fotometer zu bauen der folgende Anforderungen erfüllt:

  • einfach Handhabung
    flexibel
    einfache Einstellung
    konstant
    kompakt
    mit jedem kommerziellen Test verwendbar


Die Idee dahinter ist nicht von mir die gibt es eigentlich schon lange. Ich habe einfach die Ideen die es im Internet gibt an meine Bedürfnisse angepasst. Dazu braucht man folgende Bauteile die zum Teil im Handel erhältlich sind.

Bauteile

Fertiges Produkt

So soll es am Schluss Aussehen:


Gehäuse

Das Aussenteile wurden auf einem 3D Kunststoffprinter hergestellt. Man braucht dazu ein Gehäuse,Küvettenhalter und Deckel. Man kann natürlich jedes kommerzielle Gehäuse verwenden das geht auch.




Die Funktionsweise des Geräts beruht darin das eine LED an ein Lichtsensor leuchtet. Das Licht wird in Lux gemessen. Der Name leitet sich von der lateinischen Bezeichnung lux für Licht ab.

1 lx = 1 lm / m²

Durch die Verfärbung des Wassers wird das Licht verringert. Das wird gemessen und mit dem Ausgangswert verglichen. Das Delta verändert sich um so dunkler die Verfärbung des Wassers zunimmt.



Elektronik

Das Herzstück des Ganzen ist ein Brick der Firma <a href="http://www.tinkerforge.com/" target="_blank">Tinkerforge</a>. Man braucht dazu noch ein Lichtsensor sowie ein Powermodul für die LED. Und schon kann man loslegen. Für die Steuerung verwendet man am besten ein kleiner Netbook. Man instaliert den Treiber und die Software und somit kann man die Lux messen. Ich habe mich für ein Ausgangswert von 800 Lux entschieden und habe darum die LED mit einem Widerstand von 2k Ohm vorgeschaltet. Damit ich eine Werkseinstellung durchführen kann habe ich noch ein Potentiometer von 1k Ohm eingebaut.




Kalibrierung und Betrieb
Um eine bequeme Steuerung zu gewährleisten wurde eine Software entwickelt die aufgrund von Werten den Gehalt des Messwassers anzeigt. Aber bevor man es verwenden kann muss zuerst eine Kalibrierung durchgeführt werden. Dies erfolgt indem man eine Kalibrierlösung verwendet und den Test den man verwendet will in den verschiedenen Werten durchmisst. Aber zuerst nimmt man klares Wasser und hält es in das Fotometer und drückt "Reset bei er Software. Somit wird auf Null gestellt. Danach nimmt man z.b. den Eisentest von JBL und man testet die Werte 2,1,0.5,0.25,0.125 usw. durch. Die hält man in das Fotometer und schreibt die Luxwerte auf. Diese werden dann in der Software in einer Tabelle erfasst. Diese Kalibrierung wird nur einmal durchgeführt pro Test.


Die Software wurde in Python geschrieben. Sie ist noch in der Betaphase und es fehlt noch einiges.


Tabellen und Tests
Die Werte werden in einem Excelsheet erfasst zwecks Archivierung.



Ich habe diverse Wassertests getestet und die geeignetsten sind für mich im Moment Tetra und JBL. Sera sowie Tropica sind sehr ungenau und die Verfärbung ist nicht linear. Im Grunde kann man jeden Wassertest so verwenden. Man ist so nicht herstellergebunden.

Auf meinem Blog kann man den Artikel ebenfalls nachlesen.

Viel Spass beim nachbauen :D
 

Anhänge

  • Fotometer_07k.jpg
    Fotometer_07k.jpg
    139,1 KB · Aufrufe: 5.587
  • Fotometer_00k.jpg
    Fotometer_00k.jpg
    94,4 KB · Aufrufe: 5.580
  • Fotometer_01k.jpg
    Fotometer_01k.jpg
    103 KB · Aufrufe: 5.581
  • Fotometer_02k.jpg
    Fotometer_02k.jpg
    112,3 KB · Aufrufe: 5.593
  • Prinzip.png
    Prinzip.png
    8 KB · Aufrufe: 5.580
  • Fotometer_05k.jpg
    Fotometer_05k.jpg
    121,8 KB · Aufrufe: 5.583
  • Fotometer_03k.jpg
    Fotometer_03k.jpg
    91,9 KB · Aufrufe: 5.579
  • Fotometer_04k.jpg
    Fotometer_04k.jpg
    100,5 KB · Aufrufe: 5.582
  • Fotometer_08k.jpg
    Fotometer_08k.jpg
    116,7 KB · Aufrufe: 5.583
  • FE-JBL.jpg
    FE-JBL.jpg
    94,3 KB · Aufrufe: 5.581
Hallo Javi,

beeindruckend, wenn man die notwendigen Kenntnisse hat sowas selber zu bauen... :thumbs:
wie hoch in etwa waren die reinen Materialkosten...?
 

Tiegars

Member
Hallo Stephan,

die Elektronik war so ca. 70 Euro. Alles zusammen ca. 100 Euro.

Grüsse

Javi
 

Ma00aM

Member
Hi

ich hab da mal ne (evtl blöde) Frage, woher bekommst du die zu testenden Werte 2,1,0.5,0.25,0.125 ?
 

Tiegars

Member
Hallo Markus,

mit einer Kalibirierlösung. Als Beispiel Nitrat.
1,635 Gramm Kaliumnitrat werden mit destiliertem Wasser zu einem Liter gelöst.

Somit erhält man 1000mg/Liter. Nun wird verdünnt. Man nimmt z.b. 20ml der Kalibirerlüsung und verdünnt es mit einem 1 Liter destiliertem Wasser somit erhält man 20mg / Liter Lösung und die kann man nun als Testlösung verwenden. Man kann dann die 20mg/ Liter Lösung nehmen und 50% zu 50% verdünnen und somit erhält man eine 10mg / Liter Lösung usw...

Für jeden Test kann man eben eine Kalibrierlösung herstellen die man zum Eichen braucht. Hoffe habe es einiegrmassen verständlich erklärt :)
 

Tiegars

Member
Hallo Dirk,

da kannst bei vielen Elektroanbietern Gehäuse kaufen bei Conrad oder ELV oder sonst wo. Einfach in Kunstoffgehäuse nehmen das einigermassen passt.
 

doerk

Member
Hallo,

leider Fehlt mir das nötige Programmierwissen, also wird das Projekt abgebrochen bevor es richtig begonnen hat.


Gruß
Dirk
 

ToBe

Member
Hi Javi,

da ich das Fotometer nachbauen will, habe ich noch mal ein paar Fragen.

Hast du den Widerstand vorgeschaltet da die LED mehr also 900 Lux liefert? Habe mir die Spezifikationen der LED nicht ansehen können. Das Ambient Light Bricklet kann ja "nur" bis 900 Lux messen.

Wo und wieso hast du den Poti eingebaut?

Auf dem Foto sehe ich keine Stromverbindung, die das Power Supply versorgt. Auf dem Bild nicht angeschlossen oder wie machst du das?

Danke & Gruss,
Tobias
 

turbokoeni

New Member
Hallo,

ich habe mich in den letzten Tagen auch mit diesem Fotometer auseinander gesetzt.
Ich denke das Poti dient dem Kalibrieren der LED, vermutlich auf die 900Lux, wie Tobi ja auch schon geschrieben hat.
Über das Power Modul habe ich spekuliert.
Meiner Meinung nach, dient es alleine zum Abgreifen der 5V USB Spannung dient.
Die könnte man theoretisch auch über einen Bricklet Anschluss am Master Brick realisieren.
Werde das die Tage mal testen ;-)

Da ich mich mit Python nicht auskenne und Messreihen auch sehr Aufwendig sind, könnte man ja Wissenstransfer hier im Thread schaffen.

Mich würde die Programmierung und Messtabellen, für z.B. die JBL Tests interessieren.

Auf jeden Fall werde ich diese Projekt auch bald angehen.
Eine inspirierende Anregung von Javi - Danke dafür! :)

Gruß
Thorsten
 

ToBe

Member
Hi Thorsten,

wenn ich das richtige versteht, dann müsste Javi doch den 2k Widerstand und das Poti fest eingebaut/eingelötet haben, um dann per Poti die LED auf 800 Lux zu stellen. Allerdings kann ich das Poti auf dem Foto nicht sehen. Lediglich den 2k Widerstand würd ich direkt an der Anschlussklemme vermuten.

Auch die Spannungsversorgung kann ich nicht erkennen... würde es wahrscheinlich auch am Brick abgreifen falls dies möglich ist.

Wahrscheinlich sind die Bilder nicht 100% passend zur Beschreibung!? Wird uns sicherlich nur Javi beantworten können! :wink:

Einfacher wäre es natürlich wenn die Tabellen und das Tool zur Verfügung gestellt werden... mein Plan wäre jedenfalls, ein passendes Tool in Java zu entwickeln und dann mit den JBL-Tests zu arbeiten.

Gruß, Tobias
 

Tiegars

Member
Hallo Zusammen,

das Step-Down Power Supply habe ich nicht verwendet. Das stimmt. Das habe ich später nicht mehr verwendet. Ich kann euch die Messwerte der Kurven liefern wen ihr wollt. Ebenfalls auch den Sourcecode habe kein Problem damit.

Hier einmal die Messkurven:
Code:
# Configuration file for wxLightSensor application
#
# Lines starting with a # and empty lines are ignored
#
# Each line starts with the name of the preset, followed by as many
# key value pairs as you need to provide.  Every key is in Lux
# and the value is whatever you are measuring in the preset.

Tetra NO3;-323 0;-418 6;-448 13;-493 18;-517 25;-568 50;-597 100
JBL Phosphat/100;0 0;-12 3;-20 5;-28 10;-44 21;-80 42;-145 83;-250 167;-400 334;-530 669;-575 1338
JBL Fe/1000;0 0;-17 19;-22 38;-34 63;-61 125;-100 250;-185 500;-325 1000;-490 2000
Kalium K;-29 0;-130 1;-388 3;-508 9;-589 12;-606 13;-618 14;-652 16;-675 17
Magnesium 150;-270 0;-285 6;-305 12;-355 22;-405 38;-477 75;-542 150
Magnesium 20;-263 0;-433 6;-490 9;-510 11;-542 18
JBL NO3;-140 0;-280 6;-340 3;-370 18;-408 25;-429 50;-463 10

Und hier den Python Source Code:
Code:
#!/usr/bin/env python
#
#   wxLightSensor is a simple wxPython based light sensor display.
#   Copyright (C) 2012 
#
#   This program is free software: you can redistribute it and/or modify
#   it under the terms of the GNU General Public License as published by
#   the Free Software Foundation, either version 3 of the License, or
#   (at your option) any later version.
#
#   This program is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU General Public License for more details.
#
#   You should have received a copy of the GNU General Public License
#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
#

import os
import sys
import time
import wx


# TODO: Put these in the config file
HOST = "localhost"
PORT = 4223
UID = "7es" # Change to your UID

from tinkerforge.ip_connection import IPConnection
from tinkerforge.bricklet_ambient_light import AmbientLight


def interpolate_middle(k, values):
    keys = sorted(values.keys())
    k1 = [x for x in keys if x < k][-1]
    k2 = [x for x in keys if x > k][0]
    v = (values[k1] + values[k2]) / 2.0
    return v


def interpolate_linear(k, values):
    keys = sorted(values.keys())
    k1 = [x for x in keys if x < k][-1]
    k2 = [x for x in keys if x > k][0]
    v1 = values[k1]
    v2 = values[k2]
    v = v1 + ((v2 - v1) / (k2 - k1)) * (k - k1)
    return v


# TODO: Create sensible preset class for different measurements
class wxLightSensorPreset:
    """This class represents presets the user can choose."""

    def __init__(self, name='', values=None):
        self.name = name
	if values is None:
	    self.values = {}
	else:
	    self.values = values

    def getDisplayName(self):
        return "%s" % self.name

    def __str__(self):
        return self.getDisplayName()


class wxLightSensorFrame(wx.Frame):

    def __init__(self):
        wx.Frame.__init__(self, None, wx.ID_ANY, "wxLightSensor")
        self.__initVariables()
        self.__createGUI()
        self.__loadPresets()

    def __initVariables(self):
        self.__absoluteValue = 0
        self.__taraValue = 0
        self.__measurementValue = 0
        self.__isMeasurementRunning = False
        self.__timeStep = 1000 # milliseconds
        progDir = os.path.abspath(os.path.dirname(sys.argv[0]))
        homeDir = os.path.expanduser('~')
        self.__globalPresetsFile = os.path.join(progDir, '.wxlightsensorrc')
        self.__selectedPreset = None

    def __createGUI(self):
        self.__createControls()
        self.__layoutControls()
        self.__updateGUI()

    def __createControls(self):
        self.__mainPanel = wx.Panel(self, wx.ID_ANY)

        self.__txtValue = wx.StaticText(self.__mainPanel, wx.ID_ANY, "00:00:00")
        self.__txtValue.SetFont(wx.Font(36, wx.DEFAULT, wx.NORMAL, wx.BOLD,
            0, ""))

        self.__btnStartStop = wx.Button(self.__mainPanel, wx.NewId(),
            "START/STOP")
        self.Bind(wx.EVT_BUTTON, self.__OnBtnStartStop, self.__btnStartStop)
        self.__btnReset = wx.Button(self.__mainPanel, wx.NewId(), "Reset")
        self.Bind(wx.EVT_BUTTON, self.__OnBtnReset, self.__btnReset)

        self.__txtPresets = wx.StaticText(self.__mainPanel, wx.ID_ANY,
            "Presets:")
        self.__choicePresets = wx.Choice(self.__mainPanel, wx.NewId())
        self.Bind(wx.EVT_CHOICE, self.__OnPresetSelected, self.__choicePresets)

    def __layoutControls(self):
        topSizer = wx.BoxSizer(wx.VERTICAL)
        vBox = wx.BoxSizer(wx.VERTICAL)
        hBox = wx.BoxSizer(wx.HORIZONTAL)
        hBoxPresets = wx.BoxSizer(wx.HORIZONTAL)


        self.SetSizer(topSizer)
        self.__mainPanel.SetSizer(vBox)

        hBox.Add(self.__btnStartStop, True,  wx.CENTER)
        hBox.Add(self.__btnReset, True,  wx.CENTER)

        hBoxPresets.Add(self.__txtPresets, False, wx.CENTER)
        hBoxPresets.Add(self.__choicePresets, True, wx.CENTER)

        vBox.Add(self.__txtValue, 0,  wx.CENTER)
        vBox.Add(hBox, True,  wx.EXPAND | wx.CENTER)
        vBox.Add(hBoxPresets, True, wx.EXPAND | wx.CENTER)
        topSizer.Add(self.__mainPanel, 1, wx.EXPAND)

        topSizer.Fit(self)

    def __loadPresets(self):
        self.__presets = []
        self.__loadPresetsFromFile(self.__globalPresetsFile)
        for p in self.__presets:
            self.__choicePresets.Append(p.getDisplayName())

    def __loadPresetsFromFile(self, fname):
        if fname is None: return
        if not os.path.exists(fname): return
        if not os.path.isfile(fname):
            sys.stderr.write('Not a file: fname\n')
            return
        try:
            f = open(fname, 'r')
            contents = f.read()
            f.close()
        except IOError, e:
            sys.stderr.write(e)
            return
        self.__parsePresetsFile(contents)

    def __parsePresetsFile(self, contents):
        for i, line in enumerate([x.strip() for x in contents.splitlines()]):
            # skip empty lines and comments
            if line == '': continue
            if line.startswith('#'): continue
            fields = line.split(';')
            if len(fields) < 2:
                sys.stderr.write('Line %d: Wrong number of fields\n' % (i + 1))
                continue
            preset = wxLightSensorPreset()
            preset.name = fields[0]
	    for f in fields[1:]:
            	try:
                    k, v = f.split(' ')
                    preset.values[int(k)] = float(v)
                except Exception, e:
                    sys.stderr.write('Invalid data field: %s\n' % f)
                    continue
            self.__presets.append(preset)

    def __OnBtnReset(self, event):
	self.__taraValue = self.__absoluteValue
	self.__updateGUI()

    def __OnBtnStartStop(self, event):
        if self.__isMeasurementRunning:
            self.__cancelMeasurement()
        else:
            self.__startMeasurement()

    def __OnPresetSelected(self, event):
        sel = self.__choicePresets.GetSelection()
        self.__selectedPreset = self.__presets[sel]
        self.__measurementValue = 0
        self.__updateGUI()

    def __updateGUI(self):
        self.__updateValue()
        self.__updateButtons()

    def __updateValue(self):
	txt = self.__getValueForDisplay()
        self.__txtValue.SetLabel(txt)

    def __getValueForDisplay(self):
	if not self.__isMeasurementRunning:
	    return 'off'
	p = self.__selectedPreset
	if p is None: return 'No preset selected'
	print "measured (abs/rel):", self.__absoluteValue, "/", self.__measurementValue
        k = int(self.__measurementValue)
	if p.values.has_key(k):
	    v = p.values[k]
	else:
	    keys = p.values.keys()
	    keys.sort()
	    if k > keys[-1]:
		return 'too high'
            elif k < keys[0]:
		return 'too low'
	    else:
		#v = interpolate_middle(k, p.values)
		v = interpolate_linear(k, p.values)
        txt = '%.2f Lux / %.2f' % (self.__measurementValue, v)
        return txt

    def __updateButtons(self):
        #self.__btnReset.Enable(not self.__isMeasurementRunning)
        self.__btnReset.Enable(True)
        self.__btnStartStop.Enable(True)

    def __cancelMeasurement(self):
        self.__isMeasurementRunning = False
	self.__ipcon.destroy()
        self.__absoluteValue = 0
        self.__taraValue = 0
        self.__measurementValue = 0
        self.__updateGUI()

    def __startMeasurement(self):
        self.__isMeasurementRunning = True
        self.__ipcon = IPConnection(HOST, PORT) # Create ip connection to brickd

        al = AmbientLight(UID) # Create device object
        self.__ipcon.add_device(al) # Add device to ip connection
        # Don't use device before it is added to a connection

        # Set Period for illuminance callback to 1s (1000ms)
        # Note: The illuminance callback is only called every second if the 
        #       illuminance has changed since the last call!
        al.set_illuminance_callback_period(self.__timeStep)

        # closure
        # Callback function for illuminance callback (parameter has unit Lux/10)
        def cb_illuminance(illuminance):
	    self.__absoluteValue = illuminance/10.0
	    self.__measurementValue = self.__absoluteValue - self.__taraValue
            self.__updateGUI()

        # Register illuminance callback to function cb_illuminance
        al.register_callback(al.CALLBACK_ILLUMINANCE, cb_illuminance)

        self.__updateGUI()


if __name__ == '__main__':
    app = wx.App(False)
    frame = wxLightSensorFrame()
    frame.Show(True)
    app.MainLoop()

Wen ihr noch Fragen habt nur zu.
 

ToBe

Member
Hi Javi,

erstmal besten Dank für die Daten!

Ich habe mir zunächst die Messkurven angesehen. Leider kann ich die Daten nicht sinnvoll interpretieren.

JBL NO3;-140 0;-280 6;-340 3;-370 18;-408 25;-429 50;-463 10

-140 0;
-280 6;
-340 3;
-370 18;
-408 25;
-429 50;
-463 10

Ist der erste Wert die gemessene Lichthelligkeit in Lux und der zweite Wert der entsprechende NO3 Wert? Das kann doch nicht ganz korrekt sein wenn ich mir die ersten 3 Werte ansehe. Oder wie sind die Daten zu verstehen?

Danke & Gruß,
Tobias
 

Ähnliche Themen

Oben