Visualisierung von Messdaten mit ThingSpeak

Ein Beitrag von Johannes Kuder

ThingSpeak bietet eine sehr einfache Möglichkeit, Visualisierungen zu erstellen und diese sogar in deine eigene Homepage zu integrieren. Dies erfolgt mithilfe von sogenannten Widgets, die mit einer Codezeile aufgerufen werden.
In diesem Artikel beschreibe ich, welche Standard-Anzeigetypen in ThingSpeak verfügbar sind und wie einfach diese Widgets erstellt werden können.

In einem zweiten Artikel stelle ich einige Beispiele für individuelle Plots zur Verfügung.

Schließlich kannst du in einem dritten Artikel lesen wie einfach sich einzelne Anzeigeelemente in deine Jimdo Homepage einbinden lassen.

Auf meiner Honigseite findest du Beispiele wie das aussehen kann.

 

 

Einfache Visualisierungen in ThingSpeak erstellen

Bei ThingSpeak sind folgende Anzeigetypen defaultmäßig vorhanden und können über die entsprechenden Buttons hinzugefügt werden:

  • Das Zeigerinstrument („Gauge“) und die Numerische Anzeige („Numeric Display“) werden als „Widgets“ bezeichnet. Zum Hinzufügen den Button „Add Widgets“ betätigen.
  • Komplexere Darstellungen („Visualizations“) werden im einfachsten Fall zur Darstellung von Zeitverläufen in Diagrammen („Charts“) verwendet, bei denen eine Größe über der Zeit dargestellt werden kann. Zum Hinzufügen den Button „Add Visualization“ betätigen.
  • Wem diese Möglichkeiten nicht ausreichen, der kann auf den gesamten mächtigen Umfang von MATLAB zurückgreifen. Mithilfe weniger Code-Zeilen können damit ansprechende benutzerdefinierte Darstellungen erzeugt und sogar Werte aus mehreren Channels zusammengeführt werden. Zum Hinzufügen den Button „MATLAB Visualization“ betätigen.

Nachdem die Anzeige hinzugefügt wurde, lässt sie sich durch Klick auf den Stift rechts oben editieren und an die persönlichen Vorlieben anpassen.

 

Numerische Anzeige („Numeric Display“)

Die numerische Anzeige ist die einfachste und gleichzeitig exakteste Form der Visualisierung eines einzelnen Wertes.

Das Zahlenformat kann ausgewählt und die Anzahl der Nachkommastellen des Dezimalwertes kann angepasst werden.

 

Zeigerinstrument („Gauge“)

Mit dem Zeigerinstrument kann ein Wert anschaulicher dargestellt werden. Über farblich markierte Bereiche kann der Wert in Beziehung zu Sollwerten oder zu kritischen Werten gesetzt werden. So lässt sich z.B. auf einen Blick ablesen, ob der Wert „im grünen Bereich“ liegt.

Diese Anzeige enthält umfangreichere Möglichkeiten zur Konfiguration: Min-/Max-Wert, zusätzliche Anzeige des Dezimalwertes, Intervall der Achsenbeschriftung und die farblichen Markierungen auf der Skala.

 

 

Zeitverlauf („Chart“)

Dieser Diagrammtyp eignet sich zur Darstellung der Änderung eines Wertes über der Zeit.

Die Konfigurationsmöglichkeiten sind vielfältig und sollen an dieser Stelle nicht im Detail beschrieben werden. Hier gilt es auszuprobieren welche Darstellung das am besten veranschaulicht was man zeigen möchte.

 

 

 

Individuelle Diagramme mit MATLAB Visualization erstellen

Individuelle grafische Darstellungen können anhand vorgefertigter Beispiele oder generisch erzeugt werden, Grundkenntnisse in der Programmierung mit MATLAB vorausgesetzt.

Prinzipiell werden die Daten anhand von readAPIKey, ChannelID und FieldID eingelesen, danach ggf. bearbeitet und schließlich z.B. mit dem Plot Befehl visualisiert.

Mit Klick auf den Button „Save and Run“ wird der Code ausgeführt und die Ausgabe im Bereich „MATLAB Plot Output“ erzeugt. Eventuelle Fehlermeldungen werden im Bereich „Output from last evaluation“ angezeigt. Im Bereich „Display Settings“ kann man spezifizieren ob die Visualisierung im privaten oder öffentlichen Bereich, oder gar nicht angezeigt wird.

Unter Apps –> All Apps –> MATLAB Visualizations werden sämtliche Visualisierungen aufgeführt und können von hier aus verwaltet werden.

Im Folgenden stelle ich ein paar Beispiele vor, die ich recht informativ finde. Den MATLAB Quellcode stelle ich ebenfalls zur Verfügung, darin müssen lediglich die Variablen readAPIKey, ChannelID und FieldID angepasst werden.

Eine Schritt-für-Schritt Anleitung findet sich weiter unten.

 

 

Gewicht der letzten 3 Tage

In diesem Diagramm sind die übereinandergelegten Tagesverläufe des Gewichts der letzten 3 Tage gezeigt.

An Trachttagen ist hier besonders schön zu sehen, zu welcher Uhrzeit der Bienenflug beginnt und endet, wie das Gewicht vormittags ab- und im Tagesverlauf wieder zunimmt, wie das Gewicht abends höher ist als morgens, und wie das Gewicht nachts langsam wieder abnimmt. Die Intensität dieser Phasen ist natürlich stark vom Wetter und von den Trachtverhältnissen abhängig. An Tagen ohne Trachtflug lässt sich die Zehrung beobachten.

 

readChannelID = 123456;
WeightFieldID = 1; 
readAPIKey = 'ABCDE'; 

[Weight_Day1, timeStamps_Day1] = thingSpeakRead(readChannelID,'Fields',WeightFieldID,'dateRange',[datetime('today'),datetime('today')+days(1)],'ReadKey',readAPIKey);
[Weight_Day2, timeStamps_Day2] = thingSpeakRead(readChannelID,'Fields',WeightFieldID,'dateRange',[datetime('today')-days(1),datetime('today')],'ReadKey',readAPIKey);
[Weight_Day3, timeStamps_Day3] = thingSpeakRead(readChannelID,'Fields',WeightFieldID,'dateRange',[datetime('today')-days(2),datetime('today')-days(1)],'ReadKey',readAPIKey);

plot(timeStamps_Day1,Weight_Day1,timeStamps_Day2 + days(1),Weight_Day2,timeStamps_Day3 + days(2),Weight_Day3,'LineWidth', 2)

xlabel('Datum');
ylabel('Gewicht [kg]');
title('Gewicht der letzten 3 Tage');
legend({'heute','gestern','vorgestern'},'Location', 'best');
grid on

Tägliche Gewichtsveränderung

Die meisten Imker interessieren sich dafür wie viel Honig ihre fleißigen Bienen gesammelt haben. Eine einfache Balkengrafik gibt Aufschluss über die täglichen Gewichtszunahmen oder abnahmen.

Anmerkung zum Algorithmus:
Man kann darüber diskutieren, wie die dargestellen Werte am besten ermittelt werden. Im vorliegenden Matlab-Code wird für jeden Tag ein Gewicht berechnet und die Differenz zum Gewicht des Vortags (Ableitung) als Balken dargestellt.

Die spannende Frage ist welches die beste Methode zum Ermitteln des Gewichts eines Tages ist:

  • Die vielleicht einfachste Methode ist eine Maximalauswahl. Diese ist jedoch sehr anfällig auf Ausreisser – diese schlagen zu 100% durch.
  • Der Mittelwert oder auch der Median der Messwerte eines Tages erscheint auf den ersten Blick besser geeignet. Da jedoch das Gewicht der ausfliegenden Bienen durchaus 1 kg betragen kann, verfälscht dieser Effekt den Gewichtswert deutlich, vor allem dann wenn der Bienenflug von Tag zu Tag witterungsbedingt unterschiedlich in Intensität und Dauer ausfällt.
  • Die plausibelsten Ergebnisse liefert die Auswertung des Zeitraums abends nachdem der Bienenflug endet. Bei einem Messintervall von 15 Minuten können dafür die letzten 10 Messwerte herangezogen und darüber der Mittelwert gebildet werden.

Die gesamte Gewichtsveränderung wird über die Summe der Tageswerte ermittelt. Die Anzahl der Tage (Variable :AnzTage) muss zunächst angepasst werden, solange noch nicht genug Daten für 16 Tage zur Verügung stehen.

readChannelID = 123456; 
WeightFieldID = 1; 
readAPIKey = 'ABCDE'; 

AnzTage = 16;
StartDate = datetime('today') - days(AnzTage)

%Alternatively Start at a specific date
%StartDate = 'June 5, 2019';

%Read data from StartDate until yesterday ('today' liefert gestern(??))
[data,time] = thingSpeakRead(readChannelID,'DateRange',[StartDate,datetime('today')],'ReadKey',readAPIKey);

%Determine number of days in dataset
DaysInRange = ceil(days((max(time) - min(time))));

%Extract Weight
Weight = [data(:,WeightFieldID)];

i = 1;
while i <= AnzTage & i <= DaysInRange %Find max value of each day
    %Let's do some datetime magic ;-)
    BeginDay = day(min(time) + days(i-1)); 
    BeginMonth = month(min(time) + days(i-1)); 
    BeginYear = year(min(time) + days(i-1)); 
    EndDay = day(min(time) + days(i)); 
    EndMonth = month(min(time) + days(i)); 
    EndYear = year(min(time) + days(i)); 
    
    BeginDate = datetime(BeginYear,BeginMonth,BeginDay);
    EndDate = datetime(EndYear,EndMonth,EndDay);
  
    index = isbetween(time,BeginDate,EndDate); %find all measurements of this day
    Zeit(i) = min(time(index));
    
    %Now let's get the max weight value of each day (however this method is sensitive to outliers...)
    %Gewicht(i) = max(Weight(index));
    
    %It may be more reliable to get the mean value of each day
    %Gewicht(i) = mean(Weight(index));
    
    %Even more intelligent way to find the "real" weight:
    DayWeight = Weight(index);
    %Take the last 10 values of each days - according to my experience
    %it seems the most stable time for getting the weight values 
    %(this is later than 10 p.m. with a measurement interval of 15 minutes, may be adjusted):
    Gewicht(i) = mean(DayWeight(end-9:end));
    
    %Let's go to next day...
    i = i + 1;
end

%Differentiate the weight to get the day-to-day differences
barh(Zeit(2:AnzTage),diff(Gewicht),0.8) %Bar chart horizontal
%bar(Zeit(2:AnzTage),diff(Gewicht),0.8) %Bar chart

%Summarize the day-to-day differences to get the cumulated weight change
CumWeight = sum(diff(Gewicht));

%Now let's draw the plot
title('Tägliche Gewichtsveränderung');
xlabel(['Veränderung gesamt: ' num2str(CumWeight,2) ' kg'])
%ylabel('kg') %uncomment for Bar chart
grid on

 

Außentemperatur und Gewicht heute

Diese Darstellung eignet sich um gewünschte und auch unerwünschte Korrelationen zwischen Temperatur und Gewicht zu erkennen.

Der Zusammenhang zwischen Temperatur und Trachtflug ist naheliegend und interessant zu verfolgen.

Wenn jedoch an Tagen ohne Bienenflug eine gegenläufige Korrelation zwischen Temperatur und Gewicht beobachtet werden kann, so liegt wohl eine unerwünschte, ggf. nicht kompensierte Verfälschung des gemessenen Gewichts durch die Temperatur vor. Tritt das trotz aktivierter und kalibrierter Temperaturkompensation auf, so ist möglicherweise der Temperatursensor an einer für diesen Zweck ungünstigen Stelle angebracht.

readChannelID = 123456; 
WeightFieldID = 1; 
TempFieldID = 3; 
readAPIKey = 'ABCDEF'; 

[Weight_Day1, timeStamps_Day1] = thingSpeakRead(readChannelID,'Fields',WeightFieldID, ...
                                              'dateRange', [datetime('today')-days(0),datetime('today')-days(-1)],...
                                              'ReadKey',readAPIKey);

[TempDay1, timeStamps_Day1] = thingSpeakRead(readChannelID,'Fields',TempFieldID, ...
                                              'dateRange', [datetime('today')-days(0),datetime('today')-days(-1)],...
                                              'ReadKey',readAPIKey);

Weight_Day1 = Weight_Day1;

MyTime1 = timeStamps_Day1;

yyaxis left
plot(MyTime1(:),Weight_Day1(:), 'LineWidth', 2)
ylabel('Gewicht [kg]');

yyaxis right
plot(MyTime1(:),TempDay1(:), 'LineWidth', 2)

ylabel('Temperatur [°C]');

xlabel('Datum');
title('Außentemperatur und Gewicht heute');

legend({'Gewicht','Temperatur'},'Location', 'best');
grid on

 

Außentemperatur und Gewicht (Langzeit)

Die Darstellung von Außentemperatur und Gewicht über einen längeren Zeitraum, z.B. über zwei Monate, liefert ebenfalls interessante Einblicke.
Deutlich sind Trachtphasen und Trachtlücken bzw. Schlechtwetterperioden erkennbar.

 

 

readChannelID = 123456; 
readAPIKey = 'ABCDE'; 
WeightFieldID = 1; 
TemperatureFieldID = 3; 

AnzTage = 60;
Messintervall = 15; %in Minuten

AnzahlMesswerte = AnzTage * 24 * 60 / Messintervall;

[data, timeStamps] = thingSpeakRead(readChannelID, 'Fields',[WeightFieldID TemperatureFieldID], ...
                                                           'NumPoints', AnzahlMesswerte, ...
                                                           'ReadKey', readAPIKey);

% Extract the temperature data from the first column
WeightData = data(:, 1);%/1000;
% Extract the windspeed data from the second column
TemperatureData = data(:, 2);

% Visualize Data
yyaxis left
plot(timeStamps, WeightData,'LineWidth', 2);
ylabel('Gewicht [kg]');

yyaxis right
plot(timeStamps, TemperatureData,'LineWidth', 2);
ylabel('Außentemperatur [°C]');

title('Außentemperatur und Gewicht (Langzeit)');
grid on

 

Min-/Max-Temperatur und Max-Gewicht

In diesem Diagramm sind die Tages-Höchst- und Tiefstwerte der Temperatur und der Höchstwert des Gewichts über einen längeren Zeitraum aufgetragen. Dies ist gewissermaßen eine vereinfachte und übersichtlichere Darstellung der oben beschriebenen Langzeitdarstellung.

readChannelID = 123456; 
WeightFieldID = 1; 
TempFieldID = 3; 
readAPIKey = 'ABCDE'; 

AnzTage = 200;
StartDate = datetime('today') - days(AnzTage)

%Alternatively Start at a specific date
%StartDate = 'June 5, 2019';

%Read data from StartDate until yesterday ('today' liefert gestern(??))
[data,time] = thingSpeakRead(readChannelID,'DateRange',[StartDate,datetime('today')],'ReadKey',readAPIKey);

%Determine number of days in dataset
DaysInRange = ceil(days((max(time) - min(time))));

%Extract Weight and Temperature
Weight = [data(:,WeightFieldID)];
Temperature = [data(:,TempFieldID)];
i = 1;
while i <= AnzTage & i <= DaysInRange %Find max value of each day
    %Let's do some datetime magic ;-)
    BeginDay = day(min(time) + days(i-1)); 
    BeginMonth = month(min(time) + days(i-1)); 
    BeginYear = year(min(time) + days(i-1)); 
    EndDay = day(min(time) + days(i)); 
    EndMonth = month(min(time) + days(i)); 
    EndYear = year(min(time) + days(i)); 
    
    BeginDate = datetime(BeginYear,BeginMonth,BeginDay);
    EndDate = datetime(EndYear,EndMonth,EndDay);
  
    index = isbetween(time,BeginDate,EndDate); %find all measurements of this day
    Zeit(i) = min(time(index));
    
    %Now let's get the max weight value of each day (however this method is sensitive to outliers...)
    %Gewicht(i) = max(Weight(index));
    
    %It may be more reliable to get the mean value of each day
    %Gewicht(i) = mean(Weight(index));
    
    %Even more intelligent way to find the "real" weight:
    DayWeight = Weight(index);
    DayTemp = Temperature(index);
    %Take the last 10 values of each days - according to my experience
    %it seems the most stable time for getting the weight values 
    %(this is later than 10 p.m. with a measurement interval of 15 minutes, may be adjusted).
    %Take the very last value only:
    Gewicht(i) = mean(DayWeight(end:end));
    MaxTemp(i) = max(DayTemp(1:end));
    MinTemp(i) = min(DayTemp(1:end));
    
    %Let's go to next day...
    i = i + 1;
end

%Summarize the day-to-day differences to get the cumulated weight change
CumWeight = sum(diff(Gewicht));

%Now let's draw the plot
yyaxis right
plot(Zeit,MaxTemp,Zeit,MinTemp,'LineWidth', 2) %Line chart
ylabel('Außentemperatur [°C]');

yyaxis left
plot(Zeit,Gewicht,'LineWidth', 2.5) %Line chart
ylabel('Gewicht [kg]') %uncomment for Bar chart

title('Min-/Max-Temperatur und Gewicht');
xlabel(['Veränderung gesamt: ' num2str(CumWeight,2) ' kg'])


grid on

 

Temperaturprofil im Brutraum

Wenn man in jeder Wabengasse einen Temperatursensor platziert, dann kann man das Temperaturprofil anschaulich in einem Diagramm darstellen. Das Temperaturprofil gibt nicht nur Aufschluss darüber, ob noch gebrütet wird, sondern auch wo in der Kiste sich die Bienen aufhalten. In diesem Beispiel formiert sich die Wintertraube Ende September etwas links der Mitte.

% Versions:
% 15.04.2021
% Channel 1 enthält Temperaturen T1 bis T8
% Channel 2 enthält Temperaturen T9 bis T11

readChannelID1 = xxx
readAPIKey1 = 'xxx'; 

readChannelID2 = xxx; 
readAPIKey2 = 'xxx'; 

%Read data 
[data1] = thingSpeakRead(readChannelID1,'ReadKey',readAPIKey1);
[data2] = thingSpeakRead(readChannelID2,'ReadKey',readAPIKey2);
data = [data1,data2];

%Now let's draw the plot
xx = 1:0.2:11;
x = 1:1:11;
y = data;
yy = spline(x,y,xx);
%plot(x,y,'*',xx,yy,'LineWidth',3,'Color','r')  %Spline
plot(x,y,'LineWidth',3,'Color','r')
%title('Temperature Array');
%xlabel(['Querschnitt durch den Brutraum'])
set(gca,'xtick',[])
text(x,y,num2str(x'),'vert','bottom','horiz','center')
ylabel('Temperatur [°C]')
grid on
grid minor
box off

Schritt-für-Schritt Anleitung

So erstellst du eine individuelle Visualisierung in ThingSpeak auf Basis einer der oben aufgeführten Vorlagen:

  1. Button „MATLAB Visualization“ drücke
  2. Template „Custom (no starter code)“ auswählen
  3. Code aus den Anhängen hineinkopieren
  4. Deine individuellen ChannelID, readAPIKey und FieldIDs eintragen (siehe rechts unter Channel Info)
  5. Button „Save and Run“ drücken und beobachten ob sich die Grafik wie gewünscht aufbaut, ggf. Fehlermeldung beachten
  6. Nach Belieben modifizieren, um das darzustellen, was du sehen möchtest
  7. Plot veröffentlichen: „Create a public URL“ aktivieren, „ Add/Edit view on a channel“ auswählen und Halken bei “Public View” setzen

Jetzt sollte der Plot auf Deinem öffentlichen ThingSpeak Channel erscheinen.

ThingSpeak Widgets in Jimdo-Homepage einbetten

Es ist sehr einfach einzelne Anzeigeelemente in Jimdo einzubinden. Sobald ein Widget in ThingSpeak angelegt und für die Veröffentlichung “Public View” freigegeben ist, kann es aus einer Jimdo Seite heraus abgerufen werden.

  1. In ThingSpeak: auf die Sprechblase rechts oben über dem Plot klicken und den Code in die Zwischenablage kopieren
  2. In Jimdo: Inhalt hinzufügen  Weitere Inhalte und Add-ons  Widget / HTML
  3. HTML Code aus der Zwischenablage einfügen
  4. Wenn Du keinen Rahmen sehen möchtest: style=“border: 1px solid #cccccc;“ ändern in style=“border: 0px solid #cccccc;“

Jetzt sollte der Plot auf deiner Jimdo-Seite erscheinen. Auf meiner Honigseite findest du einige Beispiele.

Das funktioniert analog auch in anderen Systemen. In WordPress heißt die entsprechende Funktion z.B. [/] Shortcode.

<iframe style="border: 0px solid #cccccc;" src="https://thingspeak.com/apps/matlab_visualizations/286058" width="450" height="260"></iframe>

 

Nach oben scrollen