Analog-Digital-Analoges Thermometer

Ich habe mir ein analoges Voltmeter zugelegt und möchte es als Thermometer benutzen.

Da der Widerstand von Metallen mit der Temperatur steigt, kann man Temperatur relativ gut messen, indem man einen kalibrierten Widerstand misst. Daher kann man theoretisch mit einem Multimeter auch die Temperatur messen. (In der Praxis wird dies bei Multimetern allerdings in der Regel mithilfe eines anderen Effektes erledigt.)

Da ich mir aber keine Gedanken darüber machen möchte, wie ich eine Schaltung aussehen müsste, um \(15°\mathrm{C}\) in \(1.5 \mathrm{V}\) umzusetzen (vielleicht würde eine Brückenschaltung funktionieren?), wähle ich den einfachen Weg mit einer Reihe integrierter Schaltkreise und einem Microcontroller.

Schaltplan meines Analog-Digital-Analog-Thermometers

Hier ist ein günstiger DS18B20 Temperatursensor, der von einem ESP8266 ausgelesen wird. Dieser steuert dann einen MCP4725 Digital-Analog-Wandler so an, dass er eine Spannung ausgibt, deren Wert in Volt ein Zehntel der gemessenen Temperatur ist. Diese Spannung wird dann von meinem alten Voltmeter gemessen und angezeigt. Hier ist es also gerade \(24°\mathrm{C}\).

Foto meines Analog-Digital-Analog-Thermometers

Hier ist übrigens der simple Code, der beispielsweise mit der Arduino IDE auf einen ESP8266 geflasht werden kann:

#include <Wire.h>
#include <Adafruit_MCP4725.h>
#include <OneWire.h>
#include <DallasTemperature.h>

#define ONE_WIRE_BUS D4
#define MCP4725In A0

Adafruit_MCP4725 MCP4725;

OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature DS18B20(&oneWire);

void setup() {
    Serial.begin(9600);

    DS18B20.begin();
    // 0x60 is the I2C address of my MCP4725A0
    MCP4725.begin(0x60);
}

float getTemperature() {
    float temp;
    do {
      DS18B20.requestTemperatures();
      temp = DS18B20.getTempCByIndex(0);
      delay(100);
    } while (temp == 85.0 || temp == (-127.0));
    return temp;
}

void setVoltage(float value) {
    value /= 10;
    float voltageOut = value*4096/3.3;
    MCP4725.setVoltage(voltageOut, false);

    // read it for testing and maybe calibrating
    int adcInput = analogRead(MCP4725In);
    float voltageIn = (adcInput * 3.3 )/ 1024.0;
    Serial.print("Expected Voltage: ");
    Serial.println(value, 3);

    Serial.print("Measured Voltage: ");
    Serial.println(voltageIn, 3);
}

void loop() {

    float temperature = getTemperature();
    setVoltage(temperature);

    // send temperature to the serial console
    dtostrf(temperature, 2, 2, temperatureString);
    Serial.println(temperatureString);

    delay(1e3);
}

Fira Code

Fira ist eine humanist Sans-Serif Schriftart, die für FirefoxOS entwickelt wurde, und wird zur Zeit für die Sans-Serif Typen, wie die Überschriften, in diesem Blog genutzt. Aber eigentlich geht es mir hier um Fira Mono die dicktengleiche Variante, die später mit Ligaturen (und mehr) zu Fira Code erweitert wurde. Ich sehe wie in genau diesem Moment im Geist des Lesers die Frage „Ligaturen in einer dicktengleichen Schrift?!“ auftaucht. Beziehungsweise „Ist da ein Tippfehler in dickengleich?“ oder „Was sind Ligaturen?“ falls der Leser kein Hobby-Typographie-Nerd ist.

Für letztere klären wir erstmal kurz die beiden Fragen:

Die Dickte bezeichnet die Breite der Metall-Lettern im klassischen Buchdruck; wenn sie für alle Glyphen gleich ist, stehen die Buchstaben immer in perfekt ausgerichteten Spalten untereinander, was von vielen für das Schreiben von Code bevorzugt wird. Die meisten Schreibmaschinen haben ebenfalls solche Schrifttypen verwendet.

fi Ligaturen sind Kontraktionen von mehreren Glyphen in eine Glyphe. Die typischen Ligaturen sind fi oder fl (allerdings nicht in der Schriftart, in der diese Zeilen geschrieben sind, weshalb ich hier ein Bild der fi Ligatur in Computer Modern zeige). Ein paar Ligaturen haben sich mittlerweile zu eigenen Symbolen entwickelt, wie das Kaufmannsund &, das ursprünglich eine Ligatur von et war (Latein für und). Aber dieses Konzept beißt sich anscheinend mit einer dicktengleichen Schrift, in der jeder Buchstabe die gleiche Breite haben soll. Der Clou an der Sache ist, dass Fira Code Ligaturen für übliche Ausdrücke für mathematische Symbole in Programmiersprachen wie >=, != und -> hat, die wie folgt dargestellt werden: >=, !=, ->. Nur zu, kopiert diese Symbole in einen Editor eurer Wahl, um zu sehen, wie sie sich wieder in ihre Bestandteile zerlegen

Nur eine Spielerei? Möglicherweise. Aber ich bin begeistert davon, und verwende Fira Code in allen Editoren, die Ligaturen unterstützen. Der Fairness halber sollte gesagt werden, dass Fira Code nicht als erstes Projekt diese Idee hatte. Hasklig beispielsweise hatte ihr erstes Release 2 Jahre vor der Veröffentlichung von Fira Code im Jahr 2014. Und mittlerweile sind Code-Ligaturen so ziemlich im Mainstream angekommen, seitdem JetBrains Mono im letzten Jahr von dem gleichnamigen IDE-Entwickler veröffentlicht wurde.

Zum Schluss möchte ich noch auf eine Kleinigkeit aufmerksam machen, die wohl nur die wenigsten Nutzer von Fira Code bewusst bemerken würden, die aber zweifellos demonstriert wie durchdacht diese Schrift ist. Denn Fira Code passt die Position von arithmetischen Symbolen an die benachbarten Glyphen an: ein + zwischen zwei Großbuchstaben ist höher als eines zwischen zwei Kleinbuchstaben.

A+A a+a, die Plus-Zeichen haben unterschiedliche vertikale Positionen

Ich persönlich weiß solche Details sehr wertzuschätzen. Es ist ein Beispiel dafür, dass alle Aspekte unserer modernen Gesellschaft, so wenige Gedanken wir uns auch darum machen und für wie trivial wir sie halten, zahllose Stunden Design und Entwicklung gekostet haben und ständig verbessert werden. Typographie — und um das klarzustellen, ich bin beileibe kein Experte — fasziniert mich. Schriften sind exakt, mit klar definierter Funktion, aber obwohl wir sie seit Jahrtausenden benutzen, ist ihre Entwicklung noch lange nicht abgeschlossen. Mit jedem neuen Medium gibt es neue Anforderungen. Marken haben steten Bedarf an individuellen Schrifttypen als Teil ihres Brandings. Für jede neue Anwendung gibt es andere Optimierungskriterien.

Und jedes Mal wenn in meinem Code = und > wieder zu => verschmelzen, freue ich mich erneut über die Magie.

Twitter Profilhintergrundfarben

Für ein Projekt habe ich Tweets von >8‘000‘000 Twitter-Usern eingesammelt. Dabei fallen noch eine Reihe weiterer Daten an, wie die Profilhintergrundfarbe. Es wäre eine Schande diese Daten einfach verkommen zu lassen, also habe ich nach einer Möglichkeit gesucht diese Information ansprechend darzustellen, was sich als weniger trivial herausgestellt hat, als ich ursprünglich angenommen hatte: Im Idealfall sollten ähnliche Farben nahe beieinander liegen, allerdings ist der RGB Farbraum ein dreidimensionaler Kubus, ein Bild aber nur zweidimensional, sodass es keine „richtige“ Art und Weise gibt, ähnliche Farben nebeneinander anzuordnen.

Ich habe mich hier dafür entschieden eine 2D Hilbert-Kurve durch mein Bild zu legen und die Farben in der Reihenfolge zu zeichnen, in der eine 3D Hilbert-Kurve ihnen im RGB-Kubus begegnet. Wenn man dann noch die beiden Standardhintergrundfarben #F5F8FA und #C0DEED ignoriert, sieht das Ergebnis so aus.

Twitter-Profil-Hintergrundfarbe

Und dank der Python Pakete hilbertcurve und pypng ist der Code sogar ziemlich harmlos:

from math import ceil, sqrt, log2

from hilbertcurve.hilbertcurve import HilbertCurve
import png


"""
    turn an RGB string like `#C0DEED` into a tuple of integers,
    i.e., coordinates of the RGB cube
"""
def str2rgb(s):
    s = s.strip("#")
    return (int(s[0:2], 16), int(s[2:4], 16), int(s[4:6], 16))


"""
    `color_histogram` is a dict mapping an rgb string like `#F5F8FA`
    to the number of usages of this color
"""
def plot_background_colors(color_histogram, filename="colors.png"):
    defaults = {"F5F8FA", "C0DEED"}

    data = {str2rgb(rgb): d for rgb, d in color_histogram if rgb not in defaults}

    # calculate the size of the resulting image
    # for a 2D Hilbert curve, it mus be square with a width, which is a power of 2
    num_pixels = sum(data.values())
    min_width = ceil(sqrt(num_pixels))
    exponent = ceil(log2(min_width))
    width = 2**exponent

    # output buffer for a width x width png, with 4 color values per pixel
    buf = [[0 for _ in range(4 * width)] for _ in range(width)]

    hc2 = HilbertCurve(exponent, 2)
    # there are 256 = 2^8 values in each direction of the RGB cube
    hc3 = HilbertCurve(8, 3)

    sorted_rgbs = sorted(data.keys(), key=lambda x: hc3.distance_from_point(x))

    idx = 0
    for rgb in sorted_rgbs:
        for _ in range(data[rgb]):
            # get the coordinate of the next pixel
            x, y = hc2.point_from_distance(idx)
            # assign the RGBA values to the pixel
            buf[x][4 * y] = rgb[0]
            buf[x][4 * y + 1] = rgb[1]
            buf[x][4 * y + 2] = rgb[2]
            buf[x][4 * y + 3] = 255

            idx += 1

    png.from_array(buf, 'RGBA').save(filename)

Das Histogram, das als Input benötigt wird war in meinem Fall nur eine SQL Query entfernt:

SELECT profile_background_color, COUNT(profile_background_color) FROM users
    GROUP BY profile_background_color;