Thursday, December 13, 2012

ArduinoFX: A JavaFX GUI for Home Automation with Raspberry Pi and Arduino. Part I

Hi there!

I'll make a little break to the NXTLegoFX series posts, and blog about a very similar application I've made the last week: ArduinoFX. It's a JavaFX based simple GUI for remote monitoring and control of sensors, intended mainly for home automation. Though, to be fair, it's mainly a proof of concept.

More or less, both JavaFX applications share the same architecture, though the difference is now the back-end: instead of the Mindstorms Lego NXT now we've got an Arduino Uno with a low cost digital humidity and temperature sensor, RHT03.

Also, as a server, instead of a regular computer, I'll be using a Raspberry Pi, a single-board tiny computer with an ARM1176JZF-S CPU, powered with the new Java Embedded Suite 7.0, just released by Oracle in October 2012.

And for comunnications, instead of leJOS and Bluetooth, Arduino and Raspberry Pi will be connected wirelessly by two XBee antennas, using ZigBee protocol.

I'll try to describe briefly each and everyone of this components, focusing in how to install what's needed, and with each one ot them I'll make a simple test, so this could serve as a guide for thouse of you who want to give it a try.

This first part deals with all the hardware stuff, and the basic software required to work out, and I leave for Part II the JavaFX application and the embedded server development. 

Disclaimer: I'm not responsible in any way for what you do on your spare time with what I say here! The following procedures are provided without any warranty. Also, you should know by now that I'm not related to any of the companies mentioned in this blog.

Bill of Materials

Main components requiered to follow this guide:




 1. Arduino

I'm using an Arduino Uno, revision 3, a microcontroller board based on the ATmega328, with 14 digital input/output pins, 6 analog input pins, USB connection, power jack, operating at 5 V, with 32 kB of flash memory and a 16 MHz clock speed. 

It can be programmed easily with its own IDE. Its language is an implementation of Wiring. Programs are written in a simplified version of the C++ language, although only two main functions are needed to make a runnable program: setup(), a function run once at the start of a program, and loop(), a function called repeatedly until the board is powered off. 

For monitoring temperature and relative humidity values in my room, I have a DHT22 sensor, also known as RHT03. You can find here the schematics and here an open source library to read these values. You must install it in the libraries folder of Arduino IDE, before compiling your project.

Using a breadboard, connect pin 1 of the sensor to the 5 V pin of Arduino board. Pin 2 goes to Arduino pin 7, using a 1 kΩ pull-up resistor. Pin 4 goes to Arduino board ground. There're few blogs out there where you can find more detailed instructions, like this.

Let's add now a XBee antenna, so the data can be send remotely to a second antenna plugged in another Arduino or in a XBee Explorer plugged in the USB port of a computer. To connect the antenna with the Arduino I'll use an Arduino XBee Shield, from Libellium, though you can use other similar shields, or even plug the XBee on the breadboard through a breakout board.

Here it's the schematics done with the open-source tool Fritzing. The XBee is plugged on to the shield, and this is plugged on Arduino (according to the dotted purple line in the picture below). Note also the wire from pin 2 is reconnected through pin 7 on the shield.

Important tips (that work for me, at least): 
  • Never plug/unplug the shield with the Arduino powered on. 
  • Always power off, and then unplug the shield before loading the code to the Arduino. 
  • Then plug the board to the USB port, download the code, and unplug the USB. 
  • Finally, plug the shield and then power on. 
  • Remember that the jumpers on the shield must be on the XBee pins (on the two pins towards the interior of the board, see the picture below).

And this is how I did it.

Code

This is the code to make the Arduino microcontroller print the values of the measures taken by the sensor through the serial port:

#include <dht22.h>
#include <stdio.h>

// Data wire is plugged into port 7 on the Arduino
#define DHT22_PIN 7
// Setup a DHT22 instance
DHT22 myDHT22(DHT22_PIN);
char buf[128];

void setup(void)
{
  Serial.begin(9600);
  Serial.println("DHT22 Library Loaded");
}

void loop(void)
{ 
  DHT22_ERROR_t errorCode;
  // minimum 2s warm-up after power-on, then read every 2 seconds.
  delay(2000);  
  errorCode = myDHT22.readData();

  switch(errorCode){
      case DHT_ERROR_NONE:                
             sprintf(buf, "{%hi.%01hi,%i.%01i}",myDHT22.getTemperatureCInt()/10,
               abs(myDHT22.getTemperatureCInt()%10),
               myDHT22.getHumidityInt()/10, myDHT22.getHumidityInt()%10);
             Serial.println(buf);
             break;
      case DHT_ERROR_CHECKSUM:      Serial.print("check sum error "); break;
      case DHT_BUS_HUNG:            Serial.println("BUS Hung "); break;
      case DHT_ERROR_NOT_PRESENT:   Serial.println("Not Present "); break;
      case DHT_ERROR_ACK_TOO_LONG:  Serial.println("ACK time out "); break;
      case DHT_ERROR_SYNC_TIMEOUT:  Serial.println("Sync Timeout "); break;
      case DHT_ERROR_DATA_TIMEOUT:  Serial.println("Data Timeout "); break;
      case DHT_ERROR_TOOQUICK:      Serial.println("Polled to quick "); break;
    }
  }
}

To download the compiled code, just open the Arduino IDE, create a new sketch, paste the code and verify it. Then plug the Arduino without the shield, and without any wires connected, and select your serial port in Tools->Serial Port. Then download the compiled code, and unplug the USB cable.


Test #1

Let's make a simple test, before going any further, to check the Arduino works properly. Connect the sensor but not the shield nor the XBee, so sensor's pin 2 goes straight to pin 7 in the Arduino board. Plug again the USB cable, and in the Arduino IDE go to Tools->Serie Monitor, and you should see the measures appearing on the screen:


2. XBee

In order for the antennas to be connected, it is neccessary to establish a ZigBee network between them. For that, Digi International software X-CTU is required (only works in Windows, for other OS see xbeeconfigure sketch). 

Also, it's recommended to use a XBee Explorer, so just by attaching a mini USB cable you will have direct access to the serial and programming pins on the XBee unit. Otherwise, you'll have to use the access through the Arduino board, but changing the jumpers in the shield to the USB position and removing the microcontroller, which is always a little bit risky.


Anyway, for our project we'll need this Explorer, as we intend to connect it to the Raspberry Pi USB port.

There are quite good blogs out there showing how to configure both antennas (like this or this), so I'm going to give just a short description of how I do it.

Coordinator XBee #1

First, with the XBee #1 in the Explorer, plugged to the USB port, open the X-CTU application and select the correct USB serial port from the list displayed in the PC-Settings tab.

Then, go to Modem Configuration tab, uncheck Always update firmware and click on Read button and wait a few seconds. A valid name should appear under Modem label. Otherwise, click on Download new versions button, and try again.

Once you've got a right modem (in my case XB24-ZB), on the function set list, select Zigbee Coordinator AT. See left picture below. This one should be afterwards plugged to the Raspberry Pi.

A few parameters must be set:
  • Networking, ID: Pan ID. The network ID, 0 to 0xFFFF. For instance, 1111
  • Addressing. NI: Node Identifier, for instance COORDINATOR
  • Addressing. DL: Destination Address Low: 0 
  • Serial Interfacing. BD Baud Rate, 9600

then click Write button, and these parameters will be stored in your XBee #1. Close X-CTU and unplug the Explorer.


Router XBee #2

Plug on the Explorer the second XBee, and start again X-CTU. Choose the serial port and read the modem parameters. Now select Zigbee Router AT and set these parameters:
  • Networking, ID: Pan ID. The network ID, the same as #1: 1111
  • Addressing. NI: Node Identifier, for instance ARDUINO
  • Addressing. DL: Destination Address Low: 0 
  • Serial Interfacing. BD Baud Rate, 9600
Check that the Operating Channel is the same for both (CH=19). Then click Write button, and these parameters will be stored in your XBee #2. See right picture above. Close X-CTU and plug this anntena on the Arduino shield.

Note: You can use also AT commands in the Terminal Tab, just by entering +++ and waiting for an OK response. Then you can get or set the usual parameters, with AT and the parameter code: ATID, ATNI, ATDL, an so on.

Though this configuration is pretty simple, it gives you the idea of how you could set a mesh of nodes around the same coordinator, in case you have several arduinos around your house, with their own XBee antenna.


 Source: http://www.rfwireless-world.com/Tutorials/Zigbee_tutorial.html

Test #2

So let's now check if the ZigBee network is working properly. Let's plug the shield with the XBee #2 on the Arduino board, check the RHT03 connections (its pin 2 now goes through pin 7 in the shield) and power on the Arduino. Then plug the XBee #1 via Explorer to the PC and start X-CTU, select serial port, read the modem and go to Terminal tab to see the readings that should appear:

Otherwise, please check carefully the previous steps. Note that instead of X-CTU you can use any hyperterminal like software in your OS.

3. Raspberry Pi

First of all, we need to set the Raspberry Pi. For that you can follow this guide, or this. Briefly, you need:
  • A Raspberry Pi model B (with 256 or 512 MB RAM)
  • A USB keyboard and USB mouse (optional)
  • A display with HDMI, a HDMI cable, or a display with DVI and a HDMI to DVI conversor
  • A power supply of 5 V and minimum 0.7 A, with micro USB connector (some mobile chargers will do).
  • And a SD card, 4 or 8 GB, not-generic, class 6 to 10 recommended.

In order to install Java Embedded, for now we must use soft-float Debian wheezy image. Download it from here here, extract it and write it to the SD with Win32DiskImager for Windows. For other OS please check here.

Once the SD card is ready, insert it in the socket below the Raspberry Pi, connect the display and the keyboard and power on. Then it should boot up, and enter in the raspi-config menu, where we are going to config a few things (look here for a further explanation):
  • expand-rootfs: Press ok to use the whole SD card space.
  • change_locale: from en_GB.UTF8 to es_ES.UTF8 in my case, select yours.
  • change_timezone: Select continent (Europe) and major city (Madrid, in my case).
  • memory_split: you can give only 32MB to video, leaving the rest for the ARM, as in this case we're developing a server. You can change this later on.
  • ssh: Press enable to allow ssh connections.
Select Finish and the Pi will reboot.

Network

Now let's setup the network. My advise is that you set a static IP address. For that (look here for further instructions), edit with root privileges this file:

sudo nano /etc/network/interfaces

And change dhcp for static, adding your IP and LAN data configuration. This is mine:

auto lo
iface lo inet loopback
iface eth0 inet static
address 192.168.0.39
netmask 255.255.255.0
gateway 192.168.0.1

Save (Ctrl+O) and exit (Ctrl+X). Now edit this other file:

sudo nano /etc/resolv.conf

and add your DNS nameservers. This is my data:

nameserver 192.168.0.1
nameserver 62.42.230.24
nameserver 62.42.63.52

Save and exit, reboot and plug in the ethernet cable. Now we're able to install software from the repository, like a VNC server, so we can access remotely to the Pi headless, without display, keyboard nor mouse:

sudo apt-get install tightvncserver

After it has been installed, type:

sudo /usr/bin/tightvncserver

When prompted, select a password for remote users. Allow it to start at boot time, editing the file: 

sudo nano /etc/rc.local

At the end type:

su -c "/usr/bin/tightvncserver -geometry 1280x1024" pi

Save, exit and reboot. Now from your favourite VNC client try to connect to the Pi, using its IP address, the port 5901, and the specified password for remote users.

Test #3

In order to read serial port communications install minicom:

sudo apt-get install minicom

Then plug in the XBee #1 via Explorer in the USB port of the Pi. Power on the Arduino, and start minicom:

minicom -b 9600 -o -D /dev/ttyUSB0

You should see the readings appearing on the screen:


To close it, press Ctrl+A+Z+X.

4. Java Embedded Suite 7.0

It was launched by Oracle at the end of September 2012. As you can read here,
  • Oracle Java SE Embedded 7 provides runtime for Java embedded applications
  • Java DB provides a database to store local content securely
  • Glassfish for Embedded Suite provides an application server for Web pages
  • Jersey RESTful Web Services Framework for hosting and accessing Web services.
with the main goal of optimization for embedded devices and server systems.

 
Installation

In your PC, download the distribution for Raspberry Pi (ARM v6 soft-float) from here and the samples from here. Now connect by ssh and go to directory /home/pi and make a new directory there, install. Then copy both files to /home/pi/install.

Now on your Pi, go to /home/pi/install and unzip the code bundle, which creates /home/pi/install/jes7.0/Unzip also the samples bundle, which creates /home/pi/install/jes7.0/samples/. Then delete the bundles:


unzip jes-7.0-ga-bin-b11-linux-arm-runtime-15_nov_2012
rm jes-7.0-ga-bin-b11-linux-arm-runtime-15_nov_2012
unzip jes-7.0-ga-b11-linux-samples-15_nov_2012.zip
rm jes-7.0-ga-b11-linux-samples-15_nov_2012.zip

Now with root privileges create /usr/java directory and move /home/pi/install/jes7.0 to /usr/java:

sudo mv jes7.0 ../../../usr/java

Finally, we need to add to PATH the route for Java, and add a JAVA_HOME variable. For that edit the file:

sudo nano /etc/bash.bashrc

And nearly at the end, add:

export PATH=/usr/java/jes7.0/jre/bin:$PATH
JAVA_HOME="/usr/java/jes7.0/jre"
export JAVA_HOME

Save and exit, and reboot. Login, and check if everything is fine by typing java -version:


You should try now all the samples from the Suite that come within the bundle, following this guide, going to /usr/java/jes7.0/samples/dist/run directory.

Test #4
 

The next step in our tests is check if we can read the serial port with Java Embedded. For that we'll make a small project to read the serial port. First of all, we need to install RXTXcomm library and test java embedded serial reading with xbee.jar.

sudo apt-get install librxtx-java

It will install /usr/share/java/RXTXcomm-2.2pre2.jar. Back on your PC, create a new project in your Java IDE, with the class listed below, add this jar (copy it by ssh from your Pi), compile and build the project, and send it to the Pi by ssh (both jars), to /home/pi/java, for instance.

public class EmbSerial {

    private CommPort m_commPort=null;
    private SerialPort m_serialPort=null;
    
    public void connect( String portName ) throws Exception {
        
        CommPortIdentifier portIdentifier = 
                           CommPortIdentifier.getPortIdentifier(portName);
        if(portIdentifier.isCurrentlyOwned()){
            System.out.println( "Error: Port is currently in use" );
        } else {
            int timeout = 2000;
            m_commPort = portIdentifier.open(this.getClass().getName(), timeout);
            if( m_commPort instanceof SerialPort ) {
                m_serialPort = (SerialPort)m_commPort;
                m_serialPort.setSerialPortParams(9600, SerialPort.DATABITS_8,
                                SerialPort.STOPBITS_1, SerialPort.PARITY_NONE );
                final InputStream in = m_serialPort.getInputStream();
                new Thread(){                    
                    @Override public void run() {
                        byte[] buffer=new byte[1024];
                        int len = -1;
                        try {
                            while((len = in.read(buffer))>-1) {
                                System.out.print(new String(buffer,0,len));
                            }
                        } catch( IOException e ) {
                            System.out.println("Error reading: " + e.getMessage());
                        }
                    }  
                }.start();
            } else {
                System.out.println( "Error: Not a serial port" );
            }
        }
    }
        
    public void disconnect(){
        if(m_serialPort!=null){
            m_serialPort.removeEventListener();
            m_serialPort.close();
        }
        if(m_commPort!=null){
            System.out.println("Port closed");
            m_commPort.close();            
        }
    }
    
    public static void main(String[] args) {        
        final EmbSerial serial=new EmbSerial();        
        try {
            serial.connect("/dev/ttyUSB0");
        } catch(Exception e) {
            System.out.println("Error connecting: " + e.getMessage());
            return;
        }
        
        Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
            @Override public void run() {
                serial.disconnect();
            }
        }));
    }
}

Run it by:

java -Djava.library. path=/usr/lib/jni -jar EmbSerial.jar

You should get the readings on the terminal. Press Ctrl+C to finish.



Part I conclusion

Well, if you have followed closely the detailed procedures and performed successfully all the tests, congratulations!

Now you're ready for bigger adventures, so in the next part of this series we'll create the embedded server in the Raspberry Pi: 
  • Back-end task: it will repeatedly read the serial port to get the measures from the Arduino and store them in a database.
  • Web services: it will respond to web requests returning the current values measured.
  • RESTful web services: it will respond to REST requests returning lists in json format with the data measured between two dates.

And we will create the JavaFX based client application, that will mainly:
  • Connect to the server to get last measures and show them in a LED Matrix Panel control.
  • Connect to the server to get a list of measures between two dates, showing them in a chart. 
  • Show the status of the connection anytime.
This is a preview of this app:


Please, have a breath after this really long post and, if you're ready, continue reading Part II. In the meantime, I'd love to hear any comments from you.

19 comments:

  1. Excellent article! Looking forward to reading more!

    ReplyDelete
  2. This comment has been removed by a blog administrator.

    ReplyDelete
  3. There are lots of home services like home automation that will fit your needs. Now, there are lots of companies that offers this service. All we need to do is to find the right company for this matter.

    Smart Home Chicago

    ReplyDelete
  4. Wow very nice!

    Inspired by your work, I am trying some things. I have Raspberry Pi with a coordinator and an end device with sensors. I wrote Java code in Eclipse and everything works fine. However, when I write a JAR file of all classes & libraries, i get "Could not find port". To verify my /dev/ttyAMA0 port, I ran minicom as you suggest in your tutorial. I receive the packets from the end device (weird symbols, but data is coming in). Do you have any idea what might cause this "Could not find port"? Any help would be deeply appreciated.
    Best regards!

    ReplyDelete
  5. Hi Kasper. If you read weird symbols is highly probable you've set a different baud rate between both devices, so first thing you should try is check that.

    Regarding the port problem, it seems related to RXTX library. Please check that the name of the port where the coordinator is plugged is the same you try to open (ttyAMAo should be ttyAMA0, for instance).

    Hope you solve it! Otherwise, please let me know!
    Jose

    ReplyDelete
  6. Hello José,
    first of all, thank you for your time!
    The weird symbols, I attributed this to the fact that I am using analog sensors.

    Concerning the ports, I have tried some more...but cannot solve the problem. It seems that the problem indeed is caused by the RXTX library. Is it possible that I have to tell RXTX to look for this /dev/ttyAMA0 port?
    Do you have any suggestions on an elegant way to do this?
    Many thanks and best regards

    ReplyDelete
  7. Hi Kasper. I'm pretty sure you've adapted the code in XBee.java:

    serial=new Serial();
    serial.connect( "/dev/ttyUSB0" ); --> serial.connect( "/dev/ttyAMA0" );

    as this is the only call to the name of the port in all the server code.

    Have you tried also EmbSerial from Test #4? Without deploying the server, it's a simple test that would show you if port comms are working.

    Best

    ReplyDelete
    Replies
    1. Hi José,

      The problem was that rxtx does not recognise /dev/ttyAMA0, so I added one line of code: System.setproperty("gnu.io.rxtx.SerialPorts "," /dev/ttyAMA0") or you can do that directly on RPi via JAVA_OPTS

      Then everything works fine!

      Thanks for your help and I will try some more stuff from your blog in the future

      Greetings from Belgium!

      Delete
  8. I'm glad you made it work. Thanks for sharing the solution!

    Come back as often as you want, I'll try to post more in the near future.

    Jose

    ReplyDelete
  9. Hi Jose,

    Excellent work!! Thanks for that article!
    I got really lots of information from you blog. Thanks fot that!

    At the moment i have a little project and i want to use the MatrixPanel from jfxtras. I use the same Code for the
    MatrixPanel like you.

    But when i start my application i always get "java.lang.reflect.InvocationTargetException".
    Do you have any idea how to solve this problem?

    IDE.Netbeans 7.4
    JDK 8

    Many thanks and best regards

    ReplyDelete
    Replies
    1. Hi Andy, thanks for your nice words!

      Regarding the MatrixPanel, please check you've downloaded branch 8.0 from JFXtras.org.

      Then you'll notice the MatrixPanelBuilder class has all its code commented. I'll should commit this class at some point... But in the meantime: uncomment it, keeping in mind this is how the class should start:

      public class MatrixPanelBuilder <B extends MatrixPanelBuilder<B>> {
      }

      and you should also remove some @override anotation.

      Besides that, it should work. Make a short sample, and try to run it. Samples from my first posts should work without any changes.

      Please send me the code if it doesn't work, I can't help you otherwise.

      Jose

      Delete
  10. Hi Jose,

    Thanks for your quick answer and your help! This time i was using the correct branch 8.0 and succeeded!
    As you suggested I created a new JavaFX sample project and it worked immediately under JDK 1.7.0

    Then i took the working sample project and tryed to run it under jdk1.8.0(on other computer), and there i get the "java.lang.reflect.InvocationTargetException" again.

    But i noticed something is wrong with one of my imports:
    import javafx.scene.control.ControlBuilder; ( ControlBuilder is strikedout )

    Is there something else to do under JDK8 ?
    I want to run the code on my raspberry then, so far in know Jdk 8 is optimized for raspberry.

    your help is appreciated.
    Andy




    ReplyDelete
  11. Hi Andy, sorry for late replay, it's been a really busy week.

    Check MatrixPanelBuilder class, from branch 8.0. As I told you it's commented. If you uncomment all the code, and use this:

    public class MatrixPanelBuilder <B extends MatrixPanelBuilder<B>> {
    }


    instead of the old class declaration:

    public class MatrixPanelBuilder <B extends MatrixPanelBuilder<B>> extends ControlBuilder<B> implements Builder<MatrixPanel> {
    }

    then you don't need to import javafx.scene.control.ControlBuilder.

    Regarding the exception, I get that when I try to load something through the FXMLLoader and the resource is not found.

    Try it again and let me know.
    Jose



    ReplyDelete
  12. Hola Jose,

    I did how you said. but didn't got it working with JDK 8... I have used a new fresh sample, but end up with
    "Exception in Application start method java.lang.reflect.InvocationTargetException"

    In the sample i do not load any FXML which can cause the exeption.
    I dont know what i do wrong..
    thx,
    Andy


    ReplyDelete
  13. I am still testing the code, and found out something must be wrong with my settings in Netbeans.

    my application is working on PC with Netbeans settings Java Platform: JDK 1.7.0.(Default) but get following errors:

    WARNING: com.sun.javafx.css.StyleHelper calculateValue Could not resolve '-fx-text-base-color' while resolving lookups for '-fx-text-fill' from rule '*.button' in stylesheet jar:file:/C:/Program%20Files/Java/jdk1.7.0_45/jre/lib/jfxrt.jar!/com/sun/javafx/scene/control/skin/caspian/caspian.bss
    WARNING: com.sun.javafx.css.StyleHelper calculateValue Could not resolve '-fx-shadow-highlight-color' while resolving lookups for '-fx-background-color' from rule '*.button' in stylesheet jar:file:/C:/Program%20Files/Java/jdk1.7.0_45/jre/lib/jfxrt.jar!/com/sun/javafx/scene/control/skin/caspian/caspian.bss
    WARNING: com.sun.javafx.css.StyleHelper calculateValue Could not resolve '-fx-text-background-color' while resolving lookups for '-fx-text-fill' from rule '*.label' in stylesheet jar:file:/C:/Program%20Files/Java/jdk1.7.0_45/jre/lib/jfxrt.jar!/com/sun/javafx/scene/control/skin/caspian/caspian.bss
    WARNING: com.sun.javafx.css.StyleHelper calculateValue Could not resolve '-fx-text-background-color' while resolving lookups for '-fx-text-fill' from rule '*.label' in stylesheet jar:file:/C:/Program%20Files/Java/jdk1.7.0_45/jre/lib/jfxrt.jar!/com/sun/javafx/scene/control/skin/caspian/caspian.bss

    same code with Netbeans Java Platfrom: JDK 1.8
    Exception in Application start method
    java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)

    When i build the project with JDK7 or JDK 8 i get then the java.lang.reflect.InvocationTargetException
    at the raspberry and dont work.

    I dont know, maybe this information is helpfull for you.
    thanks a lot,
    Andy

    ReplyDelete
    Replies
    1. Hi Andy, the first warnings are just that, warnings about some css.

      The error with jdk8 is due to something missing, but without looking at the code I can't tell. I've sent you my email address via google+ so you can send me your code.

      Jose

      Delete
  14. Thanks for you email.
    It works perfect now:)
    THX, Andy

    ReplyDelete
  15. This specific HDMI leads can handle promises as much as 1080p. Most of these cables tend to be HDCP compliant. The particular spec handles high-bandwidth, uncompressed online video and also multi-channel electronic sound at the same time, all in a lead.
    http://www.dueltek.com.au/collections/hdmi-cables

    ReplyDelete