W tym artykule opiszę sposób wykonania zdalnego sterowania jednym urządzeniem wpinanym do gniazdka 230V z wykorzystaniem Arduino. Wykorzystane będzie połączenie przez USB z komputerem, który jednocześnie będzie pełnił rolę serwera WWW. przez co możliwe będzie sterowanie zarówno z tej stacji jak i dzięki WiFi przez laptop, tablet, czy telefon komórkowy.
W sieci!
Ale mało tego, dzięki temu rozwiązaniu można będzie również sterować urządzeniem z dowolnego miejsca na świecie przez internet.
Efekt końcowy będzie wyglądał tak:
Artykuł można również przeczytać tu.
W takim razie zabieramy się do pracy :)
UWAGA! Niektóre z opisanych w tym rozdziale układów są zasilane napięciem sieciowym, które jest niebezpieczne dla życia. Jeśli nie masz doświadczenia w pracy z napięciem sieciowym, nie próbuj wykonywać samodzielnie tych układów – poproś kogoś oświadczonego lub kup zasilacz fabryczny. Nierozważne eksperymenty z napięciem sieciowym mogą zakończyć się śmiertelnym porażeniem prądem elektrycznym. |
Założenia
Przyznam, że zastanawiałem się trochę nad wybraniem technologii: java, czy node.js, ajax czy sockiet.io, itp. Zdecydowałem jednak że wykorzystam socket.io i jeden z kontenerów serwletów w javie. Dzięki socket.io komunikacja będzie szybsza i dodatkowo można będzie zaktualizować status oświetlenia na wszystkich urządzeniach podłączonych do panelu.
Elementy systemu:
- Sprzęt
- Arduino, (kupiłem zestaw od Elektroprzewodnika z Arduino Leonardo na Botlandzie)
- Moduł z przekaźnikiem do sterowania napięciem 230v, (kupiłem taki).
BARDZO WAŻNE!
Najlepszym rozwiązaniem będzie zakup gotowego modułu przekaźnika. Absolutnie nie wolno podłączyć bezpośrednio zwykłego przekaźnika do Arduino, ze względu na zakłócenia generowane przez cewkę co może się wiązać z następującymi problemami:- W momencie wyłączenia przekaźnika mogą pojawić się krótkotrwałe zakłócenia sięgające kilkuset wolt, które mogą nam bardzo szybko uszkodzić Arduino.
- Zakłócenia generowane przez styki:
- Łuk elektryczny.
- Drgania styków.
- Przewód 2 x 0,75mm²
- Gniazdo 230V
- Wtyczka 230V
- Przewody połączeniowe żeńsko-męskie do płytek stykowych
- Serwer WWW:
- java: apache tomcat (może też być jetty)
- java: netty-socketio
- java: rxtx
- html/js: bootstrap
- js: socket.io
No to zaczynamy!
Przewód zasilający i przekaźnik
Przeciągamy przewód 2 x 0,75mm² do zabezpieczającej puszki nadtynkowej, rozcinami osłonę i jeden z dwóch przewodów (ja akurat łączyłem dwa kable, stąd ta kostka w środku) i łączymy je pomiędzy wyjście COM i jedno z wyjść sterowanych przekaźnikiem. Z drugiej strony przekaźnika podłączamy przewody połączeniowe żeńsko-męskie (IN – wyjście sterujące, GND – masa, VCC – 5V).
Następnie do jednego z końców przewodu montujemy wtyczkę, a do drugiego gniazdo na 230V:
Zrobiliśmy w tym momencie przedłużacz, do którego w łatwy sposób wepniemy naszą lampkę biurkową.
Wychodzące trzy przewody połączeniowe z puszki wpinamy w Arduino: (IN – wyjście sterujące, GND – masa, VCC – 5V). Ja wykorzystałem pin 13 do sterowania przekaźnikiem.
Program Arduino
Nasz program będzie włączał przekaźnik, na podstawie otrzymanej liczby z portu szeregowego. Wartość 1 będzie załączała wyjście, a każda inna wartość będzie wyłączała. Aby można było zrealizować połączenie przez np. putty czy szeregowy monitor, w linii 22 pobierany jest kolejny znak komendy. Jeśli będzie to znak zakończenia linii, dopiero wtedy stan wyjścia zostanie zaktualizowany.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
//pin 13 jako wyjście const int LIGHT_PIN = 13; //zmienna przechowująca //żądany przez użytkownika stan lampki //true - załączona, false - wyłączona boolean lightState = false; void setup() { pinMode(LIGHT_PIN, OUTPUT); //otworzenie połączenia szeregowego //przez które wykonywana będzie komunikacja Serial.begin(9600); } void loop() { //jeżeli odebrano polecenie while (Serial.available() > 0) { //odczyt żądanego stanu lampki lightState = Serial.parseInt() == 1; //jeżeli następny znak jest znakiem końca linii int lastChar = Serial.read(); if (lastChar == 'n' || lastChar == 'r') { //ustawienie stanu na wyjściu digitalWrite(LIGHT_PIN, lightState); } } } |
Panel użytkownika serwera WWW
Ściągamy aktualną wersję bootstrapa i socket.io, i tworzymy stronę do sterowania żarówką (uwaga, poniżej przedstawiam tylko najważniejsze fragmenty, nie całość):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags --> <title>Arduino Remote Control</title> <!-- Bootstrap --> <link href="css/bootstrap.min.css" rel="stylesheet"> <!-- Custom styles for this template --> <link href="css/cover.css" rel="stylesheet"> <script src="js/socket.io/socket.io.js"></script> <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries --> <!-- WARNING: Respond.js doesn't work if you view the page via file:// --> <!--[if lt IE 9]> <script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script> <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script> <![endif]--> </head> <body> <div class="site-wrapper"> <div class="site-wrapper-inner"> <div class="cover-container"> <div class="masthead clearfix"> <div class="inner"> <h3 class="">Arduino Remote Control</h3> </div> </div> <div class="inner cover"> <div class="row"> <div class="col-sm-3 col-md-3"></div> <div class="col-sm-6 col-md-6"> <div class="thumbnail"> <img id="light_image" src="css/bulb_off.png" alt="..."> <div class="caption"> <p> <a href="#" id="button_on" class="btn btn-primary" role="button">ON</a> <a href="#" id="button_off" class="btn btn-default" role="button">OFF</a> </p> </div> </div> </div> <div class="col-sm-3 col-md-3"></div> </div> </div> <div class="mastfoot"> <div class="inner"> <p>Created by <a href="http://KrzysztofJelonek.net">KrzysztofJelonek.net</a>.</p> </div> </div> </div> </div> </div> <!-- jQuery (necessary for Bootstrap's JavaScript plugins) --> <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script> <!-- Include all compiled plugins (below), or include individual files as needed --> <script src="js/bootstrap.min.js"></script> <script src="js/arduino.js"></script> </body> </html> |
Tworzymy plik w katalogu js/arduino.js do obsługi interfejsu i wysyłaniu komend do serwera WWW.
Uwaga! Adres 192.168.1.12 należy zamienić na adres ip komputera na którym będzie uruchomiony serwer WWW, a docelowo go sparametryzować
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
function clearButtons() { $("#button_on").removeClass('btn-primary'); $("#button_on").removeClass('btn-default'); $("#button_off").removeClass('btn-primary'); $("#button_off").removeClass('btn-default'); } function lightOn() { $('#light_image').attr('src', 'css/bulb_on.png'); clearButtons(); $("#button_on").addClass('btn-default'); $("#button_off").addClass('btn-primary'); } function lightOff() { $('#light_image').attr('src', 'css/bulb_off.png'); clearButtons(); $("#button_off").addClass('btn-default') $("#button_on").addClass('btn-primary'); } $(document).ready(function () { var onButton = $("#button_on"); var offButton = $("#button_off"); var socket = io.connect('http://192.168.1.12:3700', { 'reconnection delay': 2000, 'force new connection': true }); socket.on('light', function (data) { console.log("Arduino response:", data); if (data.state) { lightOn(); } else { lightOff(); } }); onButton.click(function () { lightOn(); socket.emit('light', {state: true}); }); offButton.click(function () { lightOff(); socket.emit('light', {state: false}); }); }); |
Nasz panel sterowania źródłem światła wygląda następująco:
Kliknij tutaj aby zobaczy jak wygląda panel online!
Java – serwer WWW
Tworzymy nowy projekt Mavenowy (w Netbeans: File->New project->Maven->Web Application.
W dependencies dodajemy dwie zależności: netty-socketio 1.7.7 i org.rxtx
Ściągamy bibliotekę rxtx (w moim przypadku windows 64 bit) rozpakowujemy i plik rxtxSerial.dll kopiujemy do głównego katalogu Windowsa.
Klasa do przechowywania komend:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
package net.krzysztofjelonek.arduinoremotepanel; /** * * @author Krzysztof Jelonek */ public class Light { private boolean state; public Light() { } public boolean isState() { return state; } public void setState(boolean state) { this.state = state; } @Override public String toString() { return "Light{" + "state=" + state + '}'; } } |
Klasa do odbioru i wysyłki danych z portu szeregowego. Uwaga! Numer portu “COM5” należy zamienić na numer portu pod którym wpięty jest Arduino, a docelowo go sparametryzować
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 |
package net.krzysztofjelonek.arduinoremotepanel; import java.io.BufferedReader; import java.io.InputStreamReader; import java.io.OutputStream; import gnu.io.CommPortIdentifier; import gnu.io.SerialPort; import gnu.io.SerialPortEvent; import gnu.io.SerialPortEventListener; import java.io.IOException; import java.util.Enumeration; /** * * @author Krzysztof Jelonek */ public class SerialConnection implements SerialPortEventListener { SerialPort serialPort; private static final String PORT_NAMES[] = { "COM5" }; private BufferedReader input; private OutputStream output; private static final int TIME_OUT = 2000; private static final int DATA_RATE = 9600; public void initialize() { CommPortIdentifier portId = null; Enumeration portEnum = CommPortIdentifier.getPortIdentifiers(); //First, Find an instance of serial port as set in PORT_NAMES. while (portEnum.hasMoreElements()) { CommPortIdentifier currPortId = (CommPortIdentifier) portEnum.nextElement(); System.out.println(currPortId.getName()); for (String portName : PORT_NAMES) { if (currPortId.getName().equals(portName)) { portId = currPortId; break; } } } if (portId == null) { System.out.println("Could not find COM port."); return; } try { serialPort = (SerialPort) portId.open(this.getClass().getName(), TIME_OUT); serialPort.setSerialPortParams(DATA_RATE, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE); input = new BufferedReader(new InputStreamReader(serialPort.getInputStream())); output = serialPort.getOutputStream(); output.write("1n".getBytes()); serialPort.addEventListener(this); serialPort.notifyOnDataAvailable(true); } catch (Exception e) { System.err.println(e.toString()); } } public void write(String command) throws IOException { output.write((command + "n").getBytes()); } public synchronized void close() { if (serialPort != null) { serialPort.removeEventListener(); serialPort.close(); } } public synchronized void serialEvent(SerialPortEvent oEvent) { if (oEvent.getEventType() == SerialPortEvent.DATA_AVAILABLE) { try { String inputLine = input.readLine(); System.out.println(inputLine); } catch (Exception e) { System.err.println(e.toString()); } } } } |
Klasa do obsługi socket.io w celu komunikacji z javascript przeglądarek klientów. Uwaga! Adres 192.168.1.12 należy zamienić na adres ip serwera na którym będzie uruchomiony program, a docelowo go sparametryzować:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
package net.krzysztofjelonek.arduinoremotepanel; import com.corundumstudio.socketio.AckRequest; import com.corundumstudio.socketio.Configuration; import com.corundumstudio.socketio.SocketIOClient; import com.corundumstudio.socketio.SocketIOServer; import com.corundumstudio.socketio.listener.ConnectListener; import com.corundumstudio.socketio.listener.DataListener; import com.corundumstudio.socketio.listener.DisconnectListener; /** * * @author Krzysztof Jelonek */ public class PanelSocketSerwer { private SocketIOServer server; public static PanelSocketSerwer getInstance() { return PanelSocketSerwerHolder.INSTANCE; } private static class PanelSocketSerwerHolder { private static final PanelSocketSerwer INSTANCE = new PanelSocketSerwer(); } public void start() { final SerialConnection serialConnection = new SerialConnection(); serialConnection.initialize(); Configuration config = new Configuration(); config.setHostname("192.168.1.12"); config.setPort(3700); server = new SocketIOServer(config); server.addConnectListener(new ConnectListener() { @Override public void onConnect(SocketIOClient client) { System.out.println("onConnected"); } }); server.addDisconnectListener(new DisconnectListener() { @Override public void onDisconnect(SocketIOClient client) { System.out.println("onDisconnected"); } }); server.addEventListener("light", Light.class, new DataListener<Light>() { @Override public void onData(SocketIOClient client, Light data, AckRequest ackSender) throws Exception { serialConnection.write(data.isState() ? "1" : "0"); System.out.println("onLight: " + data.toString()); server.getBroadcastOperations().sendEvent("light", data); } }); System.out.println("Starting server..."); server.start(); System.out.println("Server started"); } public void stop() { if (server != null) { server.stop(); } } } |
Klasa inicjująca połączenie szeregowe w momencie startu serwera:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
package net.krzysztofjelonek.arduinoremotepanel; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; /** * * @author Krzysztof Jelonek */ public class ApplicationContextListener implements ServletContextListener { @Override public void contextInitialized(ServletContextEvent sce) { PanelSocketSerwer.getInstance().start(); } @Override public void contextDestroyed(ServletContextEvent sce) { PanelSocketSerwer.getInstance().stop(); } } |
Na sam koniec tworzymy plik web.xml w katalogu WEB-INF:
1 2 3 4 5 6 |
<?xml version="1.0" encoding="UTF-8"?> <web-app version="3.1" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"> <listener> <listener-class>net.krzysztofjelonek.arduinoremotepanel.ApplicationContextListener</listener-class> </listener> </web-app> |
To tyle. Wystarczy podłączyć nasz przygotowany przewód do zasilania i do niego lampkę.
Aplikacja jest gotowa do builda i uruchomienia np. przez Apache Tomcat czy np. Jetty.
Uwaga! Zarówno po stronie javascript i serwera java jest sporo parametrów do sparametryzowania której obsługi powyżej nie ma:
- adres ip serwera www
- numer portu serwera sockiet.io
- konfiguracja połączenia szeregowego
- itd.
Na bazie tego projektu powstanie w przyszłości m.in. wyświetlanie w czasie rzeczywistym wykresu zmiany temperatury w pomieszczeniach czy ciśnienia atmosferycznego.
Zachęcam Was wszystkich do własnych eksperymentów z wykorzystaniem ulubionych języków programowania i Arduino!
Nie mogłem umieścić bezpośrednio na stronie filmu z youtube i podglądu panelu do sterowania, ponieważ blokowane są chyba znaczniki iframe.
Administrator może zaktualizować treść, aby nie wykorzystywać linków do zewnętrznych źródeł tylko wyświetlać je bezpośrednio przez znaczniki iframe.
Bardzo sprytnie i proste, ale .. Dlaczego wszyscy używają tylko arduino ? To platforma do prototypowania, nie rozumiem dlaczego wszyscy są tutaj tak leniwi niczym Amerykanie i jak projekt już działa, to nie przenoszą go na osobny procek :D Przecież koszt takiej atmegi8 to 5 zł, nawet jeśli ktoś nie ma programatora ISP, można użyć arduino do wgrania programu :)
I ile ty kodu wgrasz na taka atmege8? Bardziej sie oplaca kupic np nano za 7 zl, jak kupowac atmege za 8 zl i mase dupereli za 3-4 zl, nie mowiac juz o tym ile jest z tym lutowania i oczywiscie trzeba wytrawic plytke.
No własnie dlatego Janku, że uczymy się i piszemy różne projekty, które następnego dnia są całkiem innymi projektami.
Dużo osób pisze w Arduino ponieważ (tak jak ja) nie ma wiedzy do zabawy bezpośrednio z mikrokontrolerami. A właściwie czasu. Arduino skraca czas wdrażania i uruchomienia pierwszego projektu. Myślę, że to jest główny powód Janku.
Oczywiście, racja. Chodziło mi o bardziej o rzeczy, które mają działać długi czas – dziś budujemy sobie inteligentny sterownik rolet na Arduino, a jutro chcemy coś innego. I co wtedy ? Nie będziemy przecież za każdym razem kupować kolejnej płytki, ani rozmontowywać innych konstrukcji :D
Masz rację, ale też chyba każdy ma tego świadomość, że po przygotowaniu prototypu w Arduino trzeba będzie przejść na wersję docelową. Ten wpis to oczywiście prototyp. Mało tego, który powstał w jeden wieczór, po tygodniu zabawy z Arduino. Pozdrawiam!
Jeśli jesteś zadowolony z projektu to super, ale… Skoro już sterujesz z wykorzystaniem komputera i RS232 to arduino jest zbędne. Wystarczyło by napisać aplikację która steruje sygnałami RTS i DTR ;-)
Druga opcja to port LPT (o ile posiadasz takowy). W prosty sposób można sterować 8-bitową magistralą tego portu, co daje 8 sygnałów we/wy.
Masz rację, robiłem nawet takie coś m.in. do sterowania silnikami krokowymi na pracę dyplomową w technikum :) Ten mini projekt posłuży w przyszłości również do odbierania sygnałów analogowych z czujników.
Nie uważasz, że Java jest tu trochę przesadą? Owszem, język elastyczny, ale JVM ma taką tendencję, że pożera trochę pamięci i jeśli ktoś chciałby wykorzystać platformę, która posiada niewiele RAM, to jest w czarnej d…
Socket.io, jeśli mnie pamięć nie myli, powstał przede wszystkim w ramach ekosystemu Node’owego i serwer w nim zaprototypowany byłby znacznie wydajniejszym rozwiązaniem.
Kluczowe pytanie ile to jest niewiele RAMu. Takie Raspberry za 180 zł. ma 1GB (choć nie orientowałem się na temat dedykowanych do niego jvm). J2ME też dawało radę w telefonach komórkowych. Ale rzeczywiście, w ogóle nie analizowałem kwestii wydajności, zapotrzebowania na pamięć, itp. bo akurat w ogóle nie wziąłem sobie tego za cel.
Na socket.io opartych jest kilka portali z wykorzystaniem netty, myślę więc, że tutaj też nie ma problemu.
Tak czy siak, na pewno napiszę coś w node.js komunikującego się z Arduino o czym chętnie się podzielę :)
Jedna uwaga – w projekcie nie ma podłączonego przekaźnika pod Arduino, tylko jest MODUŁ z przekaźnikiem. Różnica jest spora, bo ten kto ma przekaźnik solo i podłączy go pod wyprowadzenia Arduino upali port procka, a na module jest po drodze tranzystor i (zapewne) dioda zabezpieczająca.
Dzięki za zwrócenie uwagi – rzeczywiście dodałem tylko odnośnik do modułu przekaźnika który zastosowałem. Zaktualizuje wpis wieczorem.
Piszesz że nie można podłączać przekaźnika pod Arduino/ATmegę. Bezpośrednio nie -zresztą prądu nie starczy, ATmega ma rekomendowane obciążenie pinu 20 mA a cewka w przekaźnikach RM85 pobiera dwa razy tyle. Ale już załączanie cewki przez tranzystor i dioda Schotthyego wpięta jako flyback ładnie załatwiają sprawę. Wyjdzie pewnie taniej niż moduł do Arduino i mamy większy wybór przekaźników
Jak na szybko przejrzałem to nie ma żadnej autoryzacji i innych zabezpieczeń…
Tak samo jak nie ma generowania i obsługi sum kontrolnych wysyłanych komend i wielu innych zabezpieczeń na różnych poziomach komunikacji.
Witam, czy możesz polecić dokładny model Andruino?
Wykorzystywany przeze mnie model to Leonardo. Do projektu finalnego można wykorzystać np. Arduino Nano z ebaya za kilka złotych.
Jest jakaś różnica który przewód podłączymy do modułu przekaźnika- neutralny czy fazowy. Co mówi “dobra praktyka”?
Tak samo jak w przypadku montażu np. wyłączników światła – stosuje się przewód fazowy.
A-ok. To się zgadza.
Jejku, byłem pewny, że na focie podpięty był neutralny i stąd moje pytanie. Późna godzina i musiało mi się przestawić. Nie podmieniłeś przypadkiem foty? :P
Jak ma być prosty, to bierzemy rasberry
Świetny projekt, brakuje mi tu ogólnego schematu (może blokowego) jak to wszystko współpracuje; arduino steruje przekaźnikiem jak arduino odbiera “sygnały” od serwera?
@Anonim: Odgrzeję kotleta: z tego co widzę na filmie, serwer jest zainstalowany na lapku (lub kompie, nie wiem do czego podpięty jest kabel USB od Leonardo), i właśnie tak jest przekazywany sygnał z sieci do dalszych sterowników (a dokładniej same komendy do Leonardo).
Pozdro.
Pingback: Kupiłem Raspberry PI 2 - Krzysztof Jelonek
Fajne ale mega skomplikowane. Zrobiłem coś podobnego na wemos D1 mini. Wszystko działa bez włączonego kompa i jest na 4 przekaźnikach z pomiarem temperatutury. Serwer wab jest wbudowany i potrzeba mieć właczonego koma. Apkę zrobiłem w MIT appinventory. Mam podstawową znajomość programowania, a spięcie całości zajęło mi ze 3 godziny.