Monday, May 5, 2014

Android infrared (IR) transmitter code sample for Kitkat and Jelly Bean

There are many remote controller application in Google play store. But, I was not able to find an app that I can put my own IR pattern. For some reason, I need to transmit my own pattern.

So, I made a remote controller application. Now I can reach my purpose. It was easy to make for Kitkat OS. but, I wanted to make it run for Jelly Bean OS. Despite of Android API officially offer IR API from Kitkat(4.4). apparently, I see few Samsung Android device has IR transmitter which is running Jelly Bean OS(4.2).

When I run the same app which is using IR API. It didn't run on Jelly Bean as expected. I was wonder how I can control the IR from Jelly Bean OS. Luckily, I found a code sample from https://github.com/rngtng/IrDude thanks to him.

There are list of manufactur IR pattern. Please refer http://www.remotecentral.com/cgi-bin/codes/


\AndroidManifest.xml
Just make sure that minSdkVersion is 17 and target version is 19. as well it needs a permission and feature to handle IR transmitter.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.devtrigger.remotecontrol"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-permission android:name="android.permission.TRANSMIT_IR"
    android:required="false" />
    <uses-feature android:name="android.hardware.consumerir" />
    <uses-sdk
        android:minSdkVersion="17"
        android:targetSdkVersion="19" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="com.devtrigger.remotecontrol.MainActivity"
      android:screenOrientation="portrait"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>



\res\layout\activity_main.xml
There are just power , Channel up and down button.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <Button
        android:id="@+id/buttonPower"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="irSend"
        android:text="Power" />

    <Button
        android:id="@+id/buttonChUp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="irSend"
        android:text="CH +" />

    <Button
        android:id="@+id/buttonChDown"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="irSend"
        android:text="CH -" />

</LinearLayout>


\src\com\devtrigger\remotecontrol\MainActivity.java
I just removed my own pattern to generalize it and share it on my blog. I put Samsung TV IR pattern on below sample and this Class works find for my Samsung Android device. I have test Galaxy S4 mini Jelly Bean and Galaxy S4 Kitkat.
package com.devtrigger.remotecontrol;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Context;
import android.hardware.ConsumerIrManager;
import android.os.Build;
import android.os.Bundle;
import android.util.SparseArray;
import android.view.View;
import android.widget.TextView;

public class MainActivity extends Activity {

    Object irdaService;
    Method irWrite;    
    SparseArray<String> irData;
    TextView mFreqsText;
    ConsumerIrManager mCIR;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // Be sure to call the super class.
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        irData = new SparseArray<String>();
  irData.put(
    R.id.buttonPower,
    hex2dec("0000 006d 0022 0003 00a9 00a8 0015 003f 0015 003f 0015 003f 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 003f 0015 003f 0015 003f 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 003f 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 0040 0015 0015 0015 003f 0015 003f 0015 003f 0015 003f 0015 003f 0015 003f 0015 0702 00a9 00a8 0015 0015 0015 0e6e"));
  irData.put(
    R.id.buttonChUp,
    hex2dec("0000 006d 0022 0003 00a9 00a8 0015 003f 0015 003f 0015 003f 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 003f 0015 003f 0015 003f 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 003f 0015 0015 0015 0015 0015 003f 0015 0015 0015 0015 0015 0015 0015 003f 0015 0015 0015 003f 0015 003f 0015 0015 0015 0040 0015 003f 0015 003f 0015 0702 00a9 00a8 0015 0015 0015 0e6e"));
  irData.put(
    R.id.buttonChDown,
    hex2dec("0000 006d 0022 0003 00a9 00a8 0015 003f 0015 003f 0015 003f 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 003f 0015 003f 0015 003f 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 003f 0015 0015 0015 0015 0015 0015 0015 003f 0015 003f 0015 003f 0015 003f 0015 0015 0015 003f 0015 003f 0015 003f 0015 0702 00a9 00a8 0015 0015 0015 0e6e"));

  
   if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT){
    
    irInit4KitKat();
   }else{
    irInit4JellyBean();
   }
  
    }
    
    @TargetApi(Build.VERSION_CODES.KITKAT)
    public void irInit4KitKat() {
     
     // Get a reference to the ConsumerIrManager
        mCIR = (ConsumerIrManager)getSystemService(Context.CONSUMER_IR_SERVICE);
 
 }

 public void irInit4JellyBean() {
  irdaService = this.getSystemService("irda");
  Class c = irdaService.getClass();
  Class p[] = { String.class };
  try {
   irWrite = c.getMethod("write_irsend", p);
  } catch (NoSuchMethodException e) {
   e.printStackTrace();
  }
 }

 public void irSend(View view) {
  
  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT){
   
   irSend4Kitkat(view);
  }else{
   
   irSend4JellyBean(view);
  }
 }
 
 @TargetApi(Build.VERSION_CODES.KITKAT)
 private void irSend4Kitkat(View view) {

    
  String data = irData.get(view.getId());
  if (data != null) {
   String values[] = data.split(",");
   int[] pattern = new int[values.length-1];
   
   for (int i=0; i<pattern.length; i++){
    pattern[i] = Integer.parseInt(values[i+1]);
   }
   
   mCIR.transmit(Integer.parseInt(values[0]), pattern);
  }
 }
 
 private void irSend4JellyBean(View view) {
  String data = irData.get(view.getId());
  if (data != null) {
   try {
    irWrite.invoke(irdaService, data);
   } catch (IllegalArgumentException e) {
    e.printStackTrace();
   } catch (IllegalAccessException e) {
    e.printStackTrace();
   } catch (InvocationTargetException e) {
    e.printStackTrace();
   }
  }
 }

 protected String hex2dec(String irData) {
  List<String> list = new ArrayList<String>(Arrays.asList(irData
    .split(" ")));
  list.remove(0); // dummy
  int frequency = Integer.parseInt(list.remove(0), 16); // frequency
  list.remove(0); // seq1
  list.remove(0); // seq2

  for (int i = 0; i < list.size(); i++) {
   list.set(i, Integer.toString(Integer.parseInt(list.get(i), 16)));
  }

  frequency = (int) (1000000 / (frequency * 0.241246));
  list.add(0, Integer.toString(frequency));

  irData = "";
  for (String s : list) {
   irData += s + ",";
  }
  return irData;
 }
}

12 comments:

  1. I get "unable to start activity ComponentInfo" and I get null pointer at this line:

    irdaService = this.getSystemService("irda");

    ReplyDelete
    Replies
    1. It should be like this:

      @TargetApi(Build.VERSION_CODES.KITKAT)
      public void irInit4JellyBean() {
      irdaService = this.getSystemService(Context.CONSUMER_IR_SERVICE);
      Class c = irdaService.getClass();
      Class p[] = { String.class };
      try {
      irWrite = c.getMethod("write_irsend", p);
      } catch (NoSuchMethodException e) {
      e.printStackTrace();
      }
      }

      Delete
  2. You never actually call "irSend" so nothing is actually transmitted. I would assume it should be called in a onClick(View v) within the buttonPower.setOnClickListener. Even adding this code, it does not work for me on a Samsung Galaxy S4 with 4.4.4 on a Samsung TV

    ReplyDelete
    Replies
    1. It is working for me. Define the android:onClick="irSend" in layout xml file that replace setOnclickListener. I don't have to setOnclickListener for all each button. When a user clicks, the public void irSend(View view) method will be called. The method name "irSend" should be match with the defined name in the layout.xml file.

      Delete
  3. I get "unable to start activity ComponentInfo" and I get null pointer at this line:

    irdaService = this.getSystemService("irda");
    me too,my phone is mi4

    ReplyDelete
  4. Hi
    Good afternoon sir
    I am IT student , and I have programming project (smart home android ) , I really need help to start implementing the functions , like TV remote control if you can help me by sending me a source code or anything please help me :(

    ReplyDelete
  5. this is for samsung smart tv? if it is then it's not working when we press power button.

    ReplyDelete
  6. Hi everybody
    thanks for the very interesting code ! much apreciated.
    I try to create for my own purpose as well.
    I get some patterns as follow :
    { 0xdc, KEY_POWER },

    what shoudl be the pattern to be sent ? only 0xdc ?
    also, where did you get the frequency frequency * 0.241246 ?
    I do not understand why the freqyency is function of the code...
    thanks
    David
    david.marchioni@gmail.com

    ReplyDelete
  7. i want to use speaker out for ir blaster it may possibly ?

    ReplyDelete
  8. Replies
    1. https://github.com/ttomovcik/htx427-android-remote
      I´m working on an app that uses this snippet

      Delete