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. Dzięki temu rozwiązaniu możliwe będzie sterowanie zarówno z tej stacji jak i dzięki WiFi przez laptop, tablet, czy telefon komórkowy.
Efekt końcowy będzie wyglądał tak:
W takim razie zabieramy się do pracy 🙂
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 swoje materiały które opisywałem już wcześniej na blogu, a mianowicie: Czat z socket.io i Java. 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
- Przekaźnik do sterowania napięciem 230v, (kupiłem taki)
- 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 – wejś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}); }); }); |
Panel wyglada następująco:
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 65 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("1\n".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.
Artykuł na majsterkowo.pl
Ponieważ dodałem ten artykuł na majsterkowo.pl będzie mi niezmiernie miło jeśli zagłosujecie na niego 🙂
od kiedy-in to jest wyjscie ??? same błędy , jak to cłe arduino