Pierwsze kroki z RPi i DS18B20

Nic, co opisane jest poniżej nie udałoby się bez pomocy Pawła, dzięki któremu podłączenie, instalacja i kodowanie zajęło 1 wieczór. Przy nim zrozumiałem, czemu 'Windows ogranicza'.  Gdy siada do konsoli linuxa, wyrasta mu dodatkowa para rąk i to z 8 palcami każda.  Paweł - wielkie dzięki za pomoc.

Pierwszym wrażeniem po wypakowaniu RPi z pudełka jest zaskoczenie niewielkim rozmiarem.  Komputer ten jest niewiele większy od karty kredytowej.  To naprawdę mało, jeśli przyłoży się do tego wielkość oczekiwań odnośnie funkcji :)

Aby zacząć pracę trzeba:

1. Włożyć kartę SD z obrazem systemu operacyjnego.  Na stronie http://www.raspberrypi.org/downloads znaleźć można najnowszą kompilację Raspbiana (system oparty o Debianie przygotowany do możliwości RPi).  Plik zip trzeba rozpakować i przy pomocy programu Win32DiskImager przenieść na kartę SD.  W moim wypadku była to karta o pojemności 8GB wciśnięta w odpowiednie czytnika w laptopie.  Całość trwała około 15 min.

Win32DiskImager

Źródło: https://wiki.ubuntu.com/Win32DiskImager

2. Wetknąć skrętkę w gniazdo Ethernet i podłączyć ją do switcha.

3. Podłączyć zasilanie - kabel z końcówką microUSB.

Wszystkie powyższe kroki dość szczegółowo i przejrzyście opisane są w instrukcji Getting Started dostępnej na stronach fundacji Raspberry.

Jako że nie posiadam telewizora/monitora z wtyczką hdmi, od samego początku wszelkie czynności na moim RPi wykonywane były przez terminal uruchomiony na laptopie.  Konieczne było znalezienie numeru IP RPi.  Po zalogowaniu do Routera, działającego pod Tomato 1.27, wybraniu Device List, pojawia się lista:

DeviceListTomato

Następnie w Putty trzeba wpisać nr IP: 192.168.1.164 i w Window, Translation, Remote character set wpisać UTF-8.  Sesję można zapisać na potem.  Przycisk OPEN rozpoczyna komunikację z Raspberry Pi.  Login to pi, hasło to raspberry.  Oto rozmawiamy z małym komputerem!

PuttyScreen PuttyScreen2

Aby zobaczyć menu konfiguracyjne, które pojawiłoby się na monitorze (gdyby takowy był podłączony), należy wpisać "sudo raspi-config".

raspi-config

Na ten moment wystarczy uruchomić 'expand_rootfs' aby rozszerzyć partycję na cały obszar karty SD oraz 'change_timezone' by dostosować czas.  Resztę można pozostawić nietknięte.

W międzyczasie jedna uwaga odnośnie komendy 'sudo', którą rozumiem jako 'wykonaj jako super user'.  Może to będą herezje, ale wg mnie nonsensem jest wpisywanie wszystkiego przez sudo.  Sudo i sudo, w każdym tutorialu sudo.  Aby móc wpisywać komendy bez tego sudo, trzeba przyjąć rolę super usera, czyli wykonać 2 kroki:

"sudo passwd" - czyli komenda do zmienienia hasła superusear (roota).  Podać na ten moment można banalne hasło jak pojedyncza spacja, czy litera 'x'.

"su" - czyli komenda, która pozwala przyjąć rolę superusera.  Po wprowadzeniu ustalonego chwilę wcześniej hasła pracować będziemy jako root, czyli darować można sobie wszystkie 'sudo' na początku komend.  Pamiętać trzeba jednak tylko, by po okresie grzebania, zmienić hasło roota na coś trudniejszego...

Teraz czas na podłączenie czujników.  Na adafruit zamieszczony jest bardzo szczegółowy opis co i jak.  Poniższe rysunki pozwolą zapewne rozwiać wszelkie wątpliwości:

sensor connection1 sensor connection2 sensor connection3

W moim wypadku podłączenie wyglądało następująco:

IMAG0424

IMAG0427

 

Czyli 4 czujniki DS18B20 i w związku z brakiem oczekiwanego opornika 4,7K są 2 oporniki 9K połączone równolegle.

Teraz czas na komendy.  W konsoli (Putty) wpisujemy:

modprobe w1-gpio

modprobe w1-therm

co uruchamia procesy odpowiedzialne za wykrywanie czujników.  Aby sprawdzić działanie można wpisać

cd /sys/bus/w1/devices
ls

czyli zmienić katalog na /sys/bus/w1/devices, gdzie 'zamontowane' są wszystkie wykryte czujniki i huby i wyświetlić zawartość tego katalogu.

W moim wypadku pojawia się:

root@raspberrypi:/sys/bus/w1/devices# ls
28-00000067a748 28-00000067ba5a 28-000002ff861f 28-000002fg961f w1_bus_master1

Ok, jeśli widzicie powyższe, znaczy to, że Raspberry Pi gotowe jest do odczytywania temperatur.  Aby jednak dane te dostępne były dla PLC przez zapytania POST/GET, konieczne jest uruchomienie na RPi serwera www z obsługą PHP.  Zainteresowanym polecam Googla (Raspberry Pi webserver PHP); szukający skrótów mogą wpisać komendę:

apt-get install php5

która powinna zainstalować i uruchomić wszystko.  Aby sprawdzić skuteczność powyższego, w przeglądarce internetowej wpisać należy adres Waszego RPi (w moim wypadku http://192.168.1.164/), by zobaczyć komunikat Apache:

apacheWroks 

Wszelki 'content', czyli pliki do wyświetlania, należy umieścić w /var/www.  Na potrzebę pierwszego prostego przykładu umieścić tam należy 2 pliki:

1. test.php

<?php
require_once('OWireTherm.class.php');

error_reporting(E_ALL);
ini_set('display_errors', '1');

$SlaveFile = file("/sys/bus/w1/devices/w1_bus_master1/w1_master_slaves");

if (!$SlaveFile) {
	echo "ERROR with slave-list file";
}
else {
	foreach ($SlaveFile as $line){
		$t1 = new OWireTherm(trim($line));
		if (($temp = $t1->readTemp()) !== false){
			echo $temp.";";
		} else {
			echo "ERROR";
		}
	}
}

2. OWireTherm.class.php (napisany przez Pawła w 3 minuty)

<?php
class OWireTherm {
	protected $id;

	public function __construct($id) {
		$this->id = $id;
	}

	protected function openFile() {
		return fopen("/sys/bus/w1/devices/$this->id/w1_slave", 'r');
	}

	public function readTemp() {
		$fp = $this->openFile();
		if (!$fp) {
			echo "Nie moge otworzyc pliku dla $this->id";
		return false;
	}

	$buff = fgets($fp);
	if (!preg_match('/YES$/', $buff)) {
		echo "Bledna suma CRC podczas odczytu";
		return false;
	}

	$buff = fgets($fp);
	if (!preg_match('/t=(-?[0-9]+)$/', $buff, $matches)) {
		echo "Niepoprawny odczyt temperatury ($buff)"; 
		return false;
	}

	return $matches[1]/1000;
	}
}

Jeśli oba pliki (test.php i OWireTherm.class.php) znajdą się w /var/www, po wpisaniu w przeglądarkę http://192.168.1.164/test.php, pojawią się odczyty temperatur ze wszystkich czujników oddzielone średnikami (np. 14.875;14.75;14.437;).... Dane te zaraz będziemy czytać przez PLC.

Po stronie PLC korzystam z biblioteki WagoLibHttp_02.lib, do działania której potrzebna jest też WagoLibBase64_01.lib. Obie biblioteki można pobrać ze strony WAGO wraz ze szczegółowymi przykładami zastosowania. Polecam w szczególności owe przykłady, gdyż jasno pokazują co (i jak wiele) jest możliwe.

W moim programie stworzyłem nowy proces (Resources->Task configuration, prawy klawisz w nowym oknie i Append Task), któremu nadałem niski priorytet i ustawiłem typ na „freewheeling”. Do nowego procesu dodałem wywołanie programu HttpComm(). Program ten oparty jest w 95% na Przykładzie nr 1 dostarczonym razem z biblioteką Http_02. Oto kod:

- w części definicji zmiennych

PROGRAM HttpComm
VAR
	xDoIt : BOOL; (*to initiate communication*)
	wState : WORD; (*storing the communication status*)
	
	(* HTTP_GET *)
	oHttpGet: HTTP_GET; (*the main function block*)
	sUrl : STRING(250):= '/test.php'; 
	xHttpSend : BOOL;
	diError : DINT;
	sStatus : STRING;
	abResponse : ARRAY [0..MAX_RECEIVE_TCP_CLIENT] OF BYTE;
	uiResponse : UINT;
	
	(* Parsing response data *)
	sTemperature : STRING;
	Temperatues : ARRAY [0..15] OF REAL; (*for storing received temperatures*)
	
	(* Helpers *)
	i : INT;
	TempCount : INT;
	iHelp : INT;
	abHelp : ARRAY [0..180] OF BYTE;
	psHelp : POINTER TO STRING(180);
END_VAR

W programie:

oHttpGet(sServerName:= '192.168.1.164',
	wServerPort:= 80,
	pabUrlData:= ADR(sUrl),
	uiUrlLength:= LEN(sUrl),
	tTimeOut:= t#3s,
	xSend:= xHttpSend,
	diError=> diError,
	sStatus=> sStatus,
	abContentData=> abResponse,
	uiContentLength=> uiResponse);

CASE wState OF
	0: (* IDLE - wait for something to do *)
		IF xDoIt THEN
			xHttpSend := TRUE; (* Send HTTP-GET request *)
			wState := 10;
		END_IF

	10: (* Wait for HTTP-GET response data *)
		IF NOT xHttpSend THEN
			IF diError = 0 THEN
			(* Success *)
				wState := 20; (* Parse response data *)
			ELSE
			(* Error *)
				wState := 999; (* Try again next time *)
			END_IF
		END_IF

	20: (* Parse response data for 'Temperature:'*)
		i:=0; (*Counter of fields in adResponse array*)
		TempCount:=0; (*Counter of found temperatures->fields in Temperatures array*)
		iHelp:=0; (*counter for supportive*)

		WHILE (abResponse[i]>0) DO (*meaning as long as there is data in the adResponse table*)
			IF (abResponse[i]<>59) THEN (*meaning if response char is not = „;”*)
				abHelp[iHelp]:=abResponse[i];
				iHelp:=iHelp+1;
			ELSE (*here is the conversion of data stored in 
				abHelp array to STRING and then to REAL*)
				psHelp := ADR(abHelp);
				sTemperature := psHelp^;
				Temperatues[TempCount]:=STRING_TO_REAL(sTemperature);
				TempCount:=TempCount+1;
				iHelp:=0;
			END_IF
			i:=i+1;
		END_WHILE

		wState := 999;

	999: (* *)
		xDoIt := FALSE;
		wState := 0;
END_CASE

Po stworzeniu prostej wizualizacji z 1 przyciskiem i 4 polami przetestować można działanie programu:

PLC VISU done

 

Komunikacja między PLC a Rpi i odczytywanie temperatur przy zaprezentowanej powyżej konfiguracji i w oparciu o ww. skrypty jest po stokroć ułomna. Serwer Apache w standardowych ustawieniach jest dość powolny i być może wymaga wymiany na inne, lżejsze rozwiązanie. Skrypty – zarówno po stronie serwera, jak i PLC – są zupełnie nieodporne na błędy. Ponadto, co być może najważniejsze, podłączenie czujników 1-wire w sposób tu zaprezentowany nie nadawać się będzie do rozległej struktury mojego domu tj. wielometrowych kabli w układzie gwiazdy prowadzonych blisko przewodów z napięciem 230VAC. Jest to więc jedynie próba uruchomienia pewnej koncepcji.

Jako że testy zakończyły się wstępnym sukcesem, kolejne etapy obejmować będą:

  • budowę rozszerzenia do RPi w oparciu o układy DS2482, który powinien pozwolić obsłużyć dużą sieć, znaczną liczbę czujników (plus dodatkowo dodanie zabezpieczeń przed przepięciami),
  • udoskonalenie konfiguracji samego Rpi i zainstalowanych serwisów, by usunąć to co zbędne i usprawnić to, co pozostanie,
  • poprawę skryptów, by były bezpieczne dla PLC, tj. działały prawidłowo przy wszelkich możliwych do wyobrażenia błędach otrzymanych na zapytanie GET.