Control TV with AppleScript & Arduino

I have an interesting conundrum.

I have the following script, it works perfectly, but only if I run it from the editor. If I run it as run-only, or as an app, or if I run it using BetterTouchTool (which is brilliant by the way) a small part of it doesn’t work. Specifically the part that deals with entering the digits on the screen.

This video shows script functioning properly when I run it from the editor:
https://www.youtube.com/watch?v=6ZN3farAoOs

This video shows the script being run as an app:
https://www.youtube.com/watch?v=Yr9qk5kpGJU

In the case of the second video, the Arduino is still functioning properly, as its status light blinks in time with each command, so I know it’s not a problem there.

The problem seems to be with these lines:


do shell script h1
do shell script h2
do shell script m1
do shell script m2

They work if I run them from the editor, but not if I run them as an app, or via terminal or as run-only.

Any ideas as to why this might happen?

The full script is below if you are interested. If you want the supporting scripts, or the Arduino sketch, I can post them as well.


set commander to load script "/Library/Scripts/ArduinoIRCommands.scpt"
set controlArduino to "/Library/Scripts/arduino-serial-master/arduino-serial -b 9600 -p /dev/tty.usbmodem621 -s "

on setup()
	set usertime to my GetWakeTime()
	set confirmation to my CheckUserTime(usertime)
	return confirmation
end setup

set waketime to my setup()
set a to first item of waketime as integer
if a is equal to 0 then
	set a to 10
end if
set b to item 2 of waketime as integer
if b is equal to 0 then
	set b to 10
end if
set c to item 3 of waketime as integer
if c is equal to 0 then
	set c to 10
end if
set d to item 4 of waketime as integer
if d is equal to 0 then
	set d to 10
end if

set h1 to commander's getNUMBERS(controlArduino, a)
set h2 to commander's getNUMBERS(controlArduino, b)
set m1 to commander's getNUMBERS(controlArduino, c)
set m2 to commander's getNUMBERS(controlArduino, d)

commander's sendGET2WAKE(controlArduino)
delay 10
do shell script h1
do shell script h2
do shell script m1
do shell script m2
commander's sendWAKE2SLEEP(controlArduino)

on CheckUserTime(usertime)
	if (count of usertime) is not 4 then
		
		my setup()
	else
		return usertime
	end if
end CheckUserTime

on GetWakeTime()
	try
		display dialog "Use 24hr time." & return & "eg. 0929 for 09:29am" with title "Set TV Wake Time" default answer "1029" buttons {"OK", "Cancel"} default button 1
		copy the result as list to {button_returned, text_returned}
		set usertime to items of text_returned
	on error
		display notification "error -128 occurred"
	end try
end GetWakeTime

I have found the problem now. It was embarrassingly simple.

It was simply a case of the script sending the commands too quickly. A simple delay 0.5 in-between each one fixed it nicely.

The scripts are below (with a few minor alterations).

Script for setting the LG’s wake timer:


set commander to load script "/Library/Scripts/ArduinoIRCommands.scpt"
set controlArduino to "/Library/Scripts/arduino-serial-master/arduino-serial -b 9600 -p /dev/tty.usbmodem621 -s "

on setup()
	set usertime to my GetWakeTime()
	if usertime is false then
	end if
	set confirmation to my CheckUserTime(usertime)
	return confirmation
end setup

set waketime to my setup()
set a to first item of waketime as integer
if a is equal to 0 then
	set a to 10
end if
set b to item 2 of waketime as integer
if b is equal to 0 then
	set b to 10
end if
set c to item 3 of waketime as integer
if c is equal to 0 then
	set c to 10
end if
set d to item 4 of waketime as integer
if d is equal to 0 then
	set d to 10
end if

set h1 to my getNUMBERS(controlArduino, a)
set h2 to my getNUMBERS(controlArduino, b)
set m1 to my getNUMBERS(controlArduino, c)
set m2 to my getNUMBERS(controlArduino, d)

commander's sendGET2WAKE(controlArduino)
delay 10
do shell script h1
delay 0.5
do shell script h2
delay 0.5
do shell script m1
delay 0.5
do shell script m2
delay 0.5
commander's sendWAKE2SLEEP(controlArduino)

on CheckUserTime(usertime)
	if (count of usertime) is not 4 then
		
		my setup()
	else
		return usertime
	end if
end CheckUserTime

on GetWakeTime()
	try
		display dialog "Use 24hr time." & return & "eg. 0929 for 09:29am" with title "Set TV Wake Time" default answer "1029" buttons {"OK", "Cancel"} default button 1
		copy the result as list to {button_returned, text_returned}
		set usertime to items of text_returned
	on error
		display notification "error -128 occurred"
		set usertime to false
	end try
end GetWakeTime

on getNUMBERS(sc, x)
	
	return item x of {sc & "m", sc & "n", sc & "o", sc & "p", sc & "q", sc & "r", sc & "s", sc & "t", sc & "u", sc & "v"}
	
end getNUMBERS

Script for sending commands to the Arduino:


(*
	Arduino IR Commands for 22LG3000
*)


--set sc to "/Library/Scripts/arduino-serial-master/arduino-serial -b 9600 -p /dev/tty.usbmodem621 -s "


on sendMUTE(sc)
	
	do shell script sc & "a"
	
end sendMUTE

on sendVOLUP(sc)
	
	do shell script sc & "b"
	
end sendVOLUP

on sendVOLDWN(sc)
	
	do shell script sc & "c"
	
end sendVOLDWN

on sendAUP(sc)
	
	do shell script sc & "d"
	
end sendAUP

on sendADWN(sc)
	
	do shell script sc & "e"
	
end sendADWN

on sendALFT(sc)
	
	do shell script sc & "f"
	
end sendALFT

on sendARGT(sc)
	
	do shell script sc & "g"
	
end sendARGT

on sendSELECT(sc)
	
	do shell script sc & "h"
	
end sendSELECT

on sendMENU(sc)
	
	do shell script sc & "i"
	
end sendMENU

on sendRETURN(sc)
	
	do shell script sc & "j"
	
end sendRETURN

on sendEXIT(sc)
	
	do shell script sc & "k"
	
end sendEXIT

on sendONOFF(sc)
	
	do shell script sc & "l"
	
end sendONOFF

on send1(sc)
	
	do shell script sc & "m"
	
end send1

on send2(sc)
	
	do shell script sc & "n"
	
end send2

on send3(sc)
	
	do shell script sc & "o"
	
end send3

on send4(sc)
	
	do shell script sc & "p"
	
end send4

on send5(sc)
	
	do shell script sc & "q"
	
end send5

on send6(sc)
	
	do shell script sc & "r"
	
end send6

on send7(sc)
	
	do shell script sc & "s"
	
end send7

on send8(sc)
	
	do shell script sc & "t"
	
end send8

on send9(sc)
	
	do shell script sc & "u"
	
end send9

on send0(sc)
	
	do shell script sc & "v"
	
end send0

on sendSLEEP(sc)
	
	do shell script sc & "w"
	
end sendSLEEP

on sendGET2WAKE(sc)
	
	do shell script sc & "x"
	
end sendGET2WAKE

on getNUMBERS(sc, x)
	
	return item x of {sc & "m", sc & "n", sc & "o", sc & "p", sc & "q", sc & "r", sc & "s", sc & "t", sc & "u", sc & "v"}
	
end getNUMBERS

on sendWAKE2SLEEP(sc)
	
	do shell script sc & "y"
	
end sendWAKE2SLEEP

on sendCANCELSLEEP(sc)
	
	do shell script sc & "z"
	
end sendCANCELSLEEP

And the Arduino sketch which actually sends the IR commands (using the IRremote library by Ken Shirriff https://github.com/shirriff/Arduino-IRremote)


// Control 22LG3000
// REV2

#include <IRremote.h>

IRsend irsend;
int incomingByte = 0;	// for incoming serial data

void setup()
{
  Serial.begin(9600);
}

void loop() {
  
  if (Serial.available() > 0) {
	// read the incoming byte:
	incomingByte = Serial.read();
        Serial.println(incomingByte);
        if(incomingByte == 97){
          sendMUTE();
          
        }else if(incomingByte == 48){
           
        }else if(incomingByte == 98){
          sendVOLUP();
        }else if(incomingByte == 99){
          sendVOLDWN();
        }else if(incomingByte == 100){
          sendAUP();
        }else if(incomingByte == 101){
          sendADWN();
        }else if(incomingByte == 102){
          sendALFT();
        }else if(incomingByte == 103){
          sendARGT();
        }else if(incomingByte == 104){
          sendSELECT();
        }else if(incomingByte == 105){
          sendMENU();
        }else if(incomingByte == 106){
          sendRETURN();
        }else if(incomingByte == 107){
          sendEXIT();
        }else if(incomingByte == 108){
          sendONOFF();
        }else if(incomingByte == 109){
          send1();
        }else if(incomingByte == 110){
          send2();
        }else if(incomingByte == 111){
          send3();
        }else if(incomingByte == 112){
          send4();
        }else if(incomingByte == 113){
          send5();
        }else if(incomingByte == 114){
          send6();
        }else if(incomingByte == 115){
          send7();
        }else if(incomingByte == 116){
          send8();
        }else if(incomingByte == 117){
          send9();
        }else if(incomingByte == 118){
          send0();
        }else if(incomingByte == 119){
          sendSLEEP();
        }else if(incomingByte == 120){
          sendGET2WAKE();
        }else if(incomingByte == 121){
          sendWAKE2SLEEP();
        }else if(incomingByte == 122){
          sendCANCELSLEEP();
        }        
        
   }
      
    }
    
void sendMUTE() {
  
  irsend.sendNEC(0x20df906f, 32);
  delay(4*100);
  
}

void sendVOLUP() {
 
  irsend.sendNEC(0x20df40bf, 32);
  delay(3*100);
  irsend.sendNEC(0x20df40bf, 32);
  
}

void sendVOLDWN() {
 
  irsend.sendNEC(0x20dfc03f, 32);
  delay(3*100);
  irsend.sendNEC(0x20dfc03f, 32);
}

void sendAUP() {
 
  irsend.sendNEC(0x20df01fd, 32);
  delay(5*100);
  
}

void sendADWN() {
 
  irsend.sendNEC(0x20df827d, 32);
  delay(5*100);
  
}

void sendALFT() {
 
  irsend.sendNEC(0x20dfe01f, 32);
  delay(5*100);
  
}

void sendARGT() {
 
  irsend.sendNEC(0x20df609f, 32);
  delay(5*100);
  
}

void sendSELECT() {
 
  irsend.sendNEC(0x20df22dd, 32);
  delay(5*100);
  
}

void sendMENU() {
 
  irsend.sendNEC(0x20dfc23d, 32);
  delay(5*100);
  
}

void sendRETURN() {
 
  irsend.sendNEC(0x20df14eb, 32);
  delay(5*100);
  
}

void sendEXIT() {
 
  irsend.sendNEC(0x20dfda25, 32);
  delay(5*100);
  
}

void sendONOFF() {
 
  irsend.sendNEC(0x20df10ef, 32);
  delay(10*100);
  
}

void send1() {
 
  irsend.sendNEC(0x20df8877, 32);
  delay(10*100);
  
}

void send2() {
 
  irsend.sendNEC(0x20df48b7, 32);
  delay(10*100);
  
}

void send3() {
 
  irsend.sendNEC(0x20dfc837, 32);
  delay(10*100);
  
}

void send4() {
 
  irsend.sendNEC(0x20df28d7, 32);
  delay(10*100);
  
}

void send5() {
 
  irsend.sendNEC(0x20dfa857, 32);
  delay(10*100);
  
}

void send6() {
 
  irsend.sendNEC(0x20df6897, 32);
  delay(10*100);
  
}

void send7() {
 
  irsend.sendNEC(0x20dfe817, 32);
  delay(10*100);
  
}

void send8() {
 
  irsend.sendNEC(0x20df18e7, 32);
  delay(10*100);
  
}

void send9() {
 
  irsend.sendNEC(0x20df9867, 32);
  delay(10*100);
  
}

void send0() {
 
  irsend.sendNEC(0x20df08f7, 32);
  delay(10*100);
  
}

void sendGET2WAKE() {
 
  sendMENU();
  sendADWN();
  sendADWN();
  sendSELECT();
  sendADWN();
  sendADWN();
  sendSELECT();
  sendADWN();
  sendSELECT();
  sendADWN();
  sendADWN();
  sendADWN();
  sendADWN();
  
}

//void getWAKEtime();
//
//  if 
//
//}




void sendSLEEP() {
 
  sendMENU();
  sendADWN();
  sendADWN();
  sendSELECT();
  sendADWN();
  sendADWN();
  sendSELECT();
  sendADWN();
  sendADWN();
  sendALFT();
  sendEXIT();
  delay (10*100);
  
}


void sendWAKE2SLEEP() {

  sendRETURN();
  sendADWN();
  sendALFT();
  sendEXIT();
  sendEXIT();

}

void sendCANCELSLEEP() {
  
  sendMENU();
  sendADWN();
  sendADWN();
  sendSELECT();
  sendADWN();
  sendADWN();
  sendSELECT();
  sendADWN();
  sendADWN();
  sendARGT();
  sendEXIT();
  sendEXIT();
  
}
  

As an example of using this set up, you can use Scenario (AppStore https://itunes.apple.com/gb/app/scenario/id423444193?mt=12) to run a script on sleep. This is very helpful because the TV (which I use as one of my monitors) doesn’t switch off when I put the machine to sleep. Often, pressing the on/off button on the front of the TV doesn’t actually do anything, and I often lose the remote.

So, on sleep, Scenario runs this script:


set commander to load script "/Library/Scripts/ArduinoIRCommands.scpt"

set controlArduino to "/Library/Scripts/arduino-serial-master/arduino-serial -b 9600 -p /dev/tty.usbmodem621 -s "

commander's sendONOFF(controlArduino)

error "MCScenarioReadyToGo"

. . . and again on wake, only without the error as it isn’t needed.

Using BetterTouchTool I can set the F18 and F19 keys to control the volume on the TV, and CMD+F15 to run the script at the top of the post.

It’s a very useful setup.

In case anybody is interested, I have rewritten one of the scripts to be far more efficient.

In case I forgot to mention it last time, you will need to use Tod Kurt’s arduino-serial package (linked below)

The Arduino sketch stays exactly the same, but the one that is referenced as “ArduinoIRCommands.scpt” has been completely re-written as follows:


(*
	By Dan Barker 05/2014
	
	Arduino IR Commands for 22LG3000
	
	You will need:
	
	My blog post (if you're new to this sort of thing):
	http://oneoclockjump.co.uk/2014/05/control-tv-via-ir-arduino-applescript-p1/
	
	Tod Kurt's Arduino-serial package:
	http://todbot.com/blog/2013/04/29/arduino-serial-updated/
	
	Ken Shirriff's IRremote library:
	http://www.righto.com/2009/08/multi-protocol-infrared-remote-library.html
*)

on signalTV(command)
	
	set sc to "/Library/Scripts/arduino-serial-master/arduino-serial -b 9600 -p /dev/tty.usbmodem621 -s "
	
	set commandlist to {"sendMUTE", "sendVOLUP", "sendVOLDWN", "sendAUP", "sendADWN", "sendALFT", "sendARGT", "sendSELECT", "sendMENU", "sendRETURN", "sendEXIT", "sendONOFF", "send1", "send2", "send3", "send4", "send5", "send6", "send7", "send8", "send9", "send0", "sendSLEEP", "sendGET2WAKE", "sendWAKE2SLEEP", "sendCANCELSLEEP"}
	
	set txlist to {"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"}
	
	set x to my getListPosition(command, commandlist)
	
	set command to item x of txlist
	
	do shell script sc & command
	
end signalTV


on getListPosition(this_item, this_list)
	repeat with i from 1 to the count of this_list
		if item i of this_list is this_item then return i
	end repeat
	return 0
end getListPosition

on getNUMBERS(x)
	
	return item x of {"send1", "send2", "send3", "send4", "send5", "send6", "send7", "send8", "send9", "send0"}
	
end getNUMBERS

This means that the script that did previously look like this:


set commander to load script "/Library/Scripts/ArduinoIRCommands.scpt"

set controlArduino to "/Library/Scripts/arduino-serial-master/arduino-serial -b 9600 -p /dev/tty.usbmodem621 -s "

commander's sendONOFF(controlArduino)

error "MCScenarioReadyToGo"

Now looks like this:


set commander to load script "/Library/Scripts/ArduinoIRCommands.scpt"

commander's signalTV("sendONOFF")

error "MCScenarioReadyToGo"