Skip to content

Instantly share code, notes, and snippets.

@partlyhuman
Last active February 18, 2022 21:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save partlyhuman/73faa59bb15e7126447684b5c88e4d7a to your computer and use it in GitHub Desktop.
Save partlyhuman/73faa59bb15e7126447684b5c88e4d7a to your computer and use it in GitHub Desktop.
Sony TV control via service

How to use a service to turn on and off your TV when the computer attached to it boots and shuts down.

Required:

Recording IR codes (Optional, if you want to use your own remote. My code works with Sony Trinitron TVs)

  • Connect the IR phototransistor:
    • GND leg to a ground pin
    • VCC leg to a +VCC/3.3V/5V pin or USB +VIN
    • OUT leg to digital pin 2
  • Load the Examples / IRemote / ReceiveDemo example in Arduino, connect your arduino, find the correct board and port, and upload the sketch.
  • View the serial monitor in Arduino as you blast the receiver with codes you want to remember. Copy and paste them for later use.

Setting up the Arduino to send IR:

  • Connect the IR LED to the Arduino
    • Positive leg to digital pin 3
    • Negative leg to an appropriate resistor and GND in serial
      • Given 20mA current, 1.2-1.5V forward voltage LED, and a 5V supply, a 220ohm resistor should work
  • Upload the ir_sony_tv.ino sketch. The included PinDefinitionsAndMore.h is untouched, from the IRemote example code.
  • You can test it out by pointing the LED at your TV and talking to the arduino in the serial monitor:
    • ! for power
    • m for menu
    • wasd for arrows
    • enter/return for enter
    • i for video input

Setting up the computer to talk to Arduino:

  • Ensure you have the prerequisites above installed (python3, pyserial)
  • Use the tv.py script to log out the available COM ports (python tv.py m will open the menu so you can see if it's working without toggling the power)
  • The COM port can change every time you plug in the Arduino, so this looks for a matching device
  • Change the serial_number in both python scripts to match the actual arduino's serial number
  • It appears that this number can change a little, on my computer it's a series of strings separated by & characters
  • I match on a prefix that excludes the last few short sections
  • Once you have a serial_number constant that works with tv.py and you can control the TV with tv.py you're ready to set up the service

Setting up the Windows service:

  • You'll be making a service that
    1. Starts up when the computer boots
    2. Turns on the TV power when the program starts
    3. Stays alive forever
    4. Turns off the TV power and exits the app when the program is killed via SIGINT
    5. Windows will shut down the service during normal shutdown
  • tv-service.py works this way. You may want to change the code to send m (menu) instead of ! (power) until you ensure it's working
  • Ensure you have nssm as in the prerequisites. It's a single executable, no installer.
  • Create a new service via nssm install tv-service
  • A GUI walks you through the options
    • Path = path to python.exe
    • Arguments = path to tv-service.py
    • Run as = your user is easiest
    • Details > Shutdown via "Generate Control+C" with a long timeout like 5000 ms. Other options unchecked except maybe the last one for last resort
    • I/O > create a path for output to log to. This is really useful for debugging.
    • Log cycling > also optional nice to have
  • Test your new service in Window's services interface or using nssm <start|stop|restart> tv-service
  • Make sure it can find the python site-packages. If you installed pyserial via pip, it's per-user so it's easiest to run the service as your user.
  • Alternately you can move pyserial into the system-wide site-packages dir
  • Test by doing a few boot cycles
  • Keep the arduino plugged in and aimed at the sensor
  • You may want to desolder the TV's original IR sensor from the mainboard, attach longer leads and place it in a more desirable location

Comments welcome!

#include <Arduino.h>
#include "PinDefinitionsAndMore.h"
#include <IRremote.hpp>
#define DELAY_AFTER_SEND 2000
#define DELAY_AFTER_LOOP 5000
void setup() {
Serial.begin(115200);
delay(4000); // To be able to connect Serial monitor after reset or power up and before first print out. Do not wait for an attached Serial Monitor!
Serial.println(F("START " __FILE__ " from " __DATE__ "\r\nUsing library version " VERSION_IRREMOTE));
#if defined(IR_SEND_PIN) || defined(NO_LED_FEEDBACK_CODE)
IrSender.begin(); // Start with IR_SEND_PIN as send pin and if NO_LED_FEEDBACK_CODE is NOT defined, enable feedback LED at default feedback LED pin
#else
IrSender.begin(IR_SEND_PIN, ENABLE_LED_FEEDBACK); // Specify send pin and enable feedback LED at default feedback LED pin
#endif
Serial.print(F("Ready to send IR signals at pin "));
#if defined(ARDUINO_ARCH_STM32) || defined(ESP8266)
Serial.println(IR_SEND_PIN_STRING);
#else
Serial.println(IR_SEND_PIN);
#endif
}
void send(uint8_t sCommand) {
// Serial.println(F("Send Sony/SIRCS with 7 command and 5 address bits"));
// Serial.flush();
IrSender.sendSony(0x1, sCommand, 0x3);
delay(DELAY_AFTER_SEND);
}
void loop() {
String line;
line = Serial.readString();
line.trim();
if (line.length() == 0) {
return;
}
switch (line[0]) {
case 'w':
send(0x74);
break;
case 'a':
send(0x34);
break;
case 's':
send(0x75);
break;
case 'd':
send(0x33);
break;
case '!':
send(0x15);
break;
case 'm':
send(0x60);
break;
case '\r':
case '\n':
send(0x65);
break;
case 'i':
send(0x25);
break;
}
}
/*
* PinDefinitionsAndMore.h
*
* Contains pin definitions for IRremote examples for various platforms
* as well as definitions for feedback LED and tone() and includes
*
* Copyright (C) 2021 Armin Joachimsmeyer
* armin.joachimsmeyer@gmail.com
*
* This file is part of IRremote https://github.com/Arduino-IRremote/Arduino-IRremote.
*
* Arduino-IRremote is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/gpl.html>.
*
*/
/*
* Pin mapping table for different platforms
*
* Platform IR input IR output Tone
* -----------------------------------------
* DEFAULT/AVR 2 3 4
* ATtinyX5 0 4 3
* ATtiny167 9 8 5 // Digispark pro number schema
* ATtiny167 3 2 7
* ATtiny3217 10 11 3 // TinyCore schema
* ATtiny1604 2 PA5/3 %
* SAMD21 3 4 5
* ESP8266 14 // D5 12 // D6 %
* ESP32 15 4 27
* BluePill PA6 PA7 PA3
* APOLLO3 11 12 5
*/
//#define _IR_MEASURE_TIMING // For debugging purposes.
//
#if defined(ESP8266)
#define FEEDBACK_LED_IS_ACTIVE_LOW // The LED on my board (D4) is active LOW
#define IR_RECEIVE_PIN 14 // D5
#define IR_RECEIVE_PIN_STRING "D5"
#define IR_SEND_PIN 12 // D6 - D4/pin 2 is internal LED
#define IR_SEND_PIN_STRING "D6"
#define _IR_TIMING_TEST_PIN 13 // D7
#define APPLICATION_PIN 0 // D3
#define tone(...) void() // tone() inhibits receive timer
#define noTone(a) void()
#define TONE_PIN 42 // Dummy for examples using it
#elif defined(ESP32)
#include <Arduino.h>
#define TONE_LEDC_CHANNEL 1 // Using channel 1 makes tone() independent of receiving timer -> No need to stop receiving timer.
void tone(uint8_t _pin, unsigned int frequency){
ledcAttachPin(_pin, TONE_LEDC_CHANNEL);
ledcWriteTone(TONE_LEDC_CHANNEL, frequency);
}
void tone(uint8_t _pin, unsigned int frequency, unsigned long duration){
ledcAttachPin(_pin, TONE_LEDC_CHANNEL);
ledcWriteTone(TONE_LEDC_CHANNEL, frequency);
delay(duration);
ledcWriteTone(TONE_LEDC_CHANNEL, 0);
}
void noTone(uint8_t _pin){
ledcWriteTone(TONE_LEDC_CHANNEL, 0);
}
#define IR_RECEIVE_PIN 15 // D15
#define IR_SEND_PIN 4 // D4
#define TONE_PIN 27 // D27 25 & 26 are DAC0 and 1
#define APPLICATION_PIN 16 // RX2 pin
#elif defined(ARDUINO_ARCH_STM32) || defined(ARDUINO_ARCH_STM32F1)
// BluePill in 2 flavors
// Timer 3 blocks PA6, PA7, PB0, PB1 for use by Servo or tone()
#define IR_RECEIVE_PIN PA6
#define IR_RECEIVE_PIN_STRING "PA6"
#define IR_SEND_PIN PA7
#define IR_SEND_PIN_STRING "PA7"
#define TONE_PIN PA3
#define _IR_TIMING_TEST_PIN PA5
#define APPLICATION_PIN PA2
#elif defined(__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__)
#include "ATtinySerialOut.hpp" // Available as Arduino library "ATtinySerialOut". saves 370 bytes program space and 38 bytes RAM for digistump core
#define IR_RECEIVE_PIN 0
#define IR_SEND_PIN 4 // Pin 2 is serial output with ATtinySerialOut. Pin 1 is internal LED and Pin3 is USB+ with pullup on Digispark board.
#define TONE_PIN 3
#define _IR_TIMING_TEST_PIN 3
#elif defined(__AVR_ATtiny87__) || defined(__AVR_ATtiny167__)
#include "ATtinySerialOut.hpp" // Available as Arduino library "ATtinySerialOut"
// For ATtiny167 Pins PB6 and PA3 are usable as interrupt source.
# if defined(ARDUINO_AVR_DIGISPARKPRO)
#define IR_RECEIVE_PIN 9 // PA3 - on Digispark board labeled as pin 9
//#define IR_RECEIVE_PIN 14 // PB6 / INT0 is connected to USB+ on DigisparkPro boards
#define IR_SEND_PIN 8 // PA2 - on Digispark board labeled as pin 8
#define TONE_PIN 5 // PA7
#define _IR_TIMING_TEST_PIN 10 // PA4
# else
#define IR_RECEIVE_PIN 3
#define IR_SEND_PIN 2
#define TONE_PIN 7
# endif
#elif defined(__AVR_ATtiny88__) // MH-ET Tiny88 board
#include "ATtinySerialOut.hpp" // Available as Arduino library "ATtinySerialOut". Saves 128 bytes program space
// Pin 6 is TX pin 7 is RX
#define IR_RECEIVE_PIN 3 // INT1
#define IR_SEND_PIN 4
#define TONE_PIN 9
#define _IR_TIMING_TEST_PIN 8
#elif defined(__AVR_ATtiny3217__)
#define IR_RECEIVE_PIN 10
#define IR_SEND_PIN 11
#define TONE_PIN 3
#define APPLICATION_PIN 5
#elif defined(__AVR_ATtiny1604__)
#define IR_RECEIVE_PIN 2 // To be compatible with interrupt example, pin 2 is chosen here.
#define IR_SEND_PIN 3
#define APPLICATION_PIN 5
#define tone(...) void() // Define as void, since TCB0_INT_vect is also used by tone()
#define noTone(a) void()
#define TONE_PIN 42 // Dummy for examples using it
# elif defined(__AVR_ATmega1284__) || defined(__AVR_ATmega1284P__) \
|| defined(__AVR_ATmega644__) || defined(__AVR_ATmega644P__) \
|| defined(__AVR_ATmega324P__) || defined(__AVR_ATmega324A__) \
|| defined(__AVR_ATmega324PA__) || defined(__AVR_ATmega164A__) \
|| defined(__AVR_ATmega164P__) || defined(__AVR_ATmega32__) \
|| defined(__AVR_ATmega16__) || defined(__AVR_ATmega8535__) \
|| defined(__AVR_ATmega64__) || defined(__AVR_ATmega128__) \
|| defined(__AVR_ATmega1281__) || defined(__AVR_ATmega2561__) \
|| defined(__AVR_ATmega8515__) || defined(__AVR_ATmega162__)
#define IR_RECEIVE_PIN 2
#define IR_SEND_PIN 13
#define TONE_PIN 4
#define APPLICATION_PIN 5
#define ALTERNATIVE_IR_FEEDBACK_LED_PIN 6 // E.g. used for examples which use LED_BUILDIN for example output.
#define _IR_TIMING_TEST_PIN 7
#elif defined(ARDUINO_ARCH_APOLLO3)
#define IR_RECEIVE_PIN 11
#define IR_SEND_PIN 12
#define TONE_PIN 5
#elif defined(ARDUINO_ARCH_MBED) // Arduino Nano 33 BLE
#define IR_RECEIVE_PIN 2
#define IR_SEND_PIN 3
#define TONE_PIN 4
#define APPLICATION_PIN 5
#define ALTERNATIVE_IR_FEEDBACK_LED_PIN 6 // E.g. used for examples which use LED_BUILDIN for example output.
#define _IR_TIMING_TEST_PIN 7
#elif defined(TEENSYDUINO)
#define IR_RECEIVE_PIN 2
#define IR_SEND_PIN 3
#define TONE_PIN 4
#define APPLICATION_PIN 5
#define ALTERNATIVE_IR_FEEDBACK_LED_PIN 6 // E.g. used for examples which use LED_BUILDIN for example output.
#define _IR_TIMING_TEST_PIN 7
#elif defined(__AVR__) // Default as for ATmega328 like on Uno, Nano etc.
#define IR_RECEIVE_PIN 2 // To be compatible with interrupt example, pin 2 is chosen here.
#define IR_SEND_PIN 3
#define TONE_PIN 4
#define APPLICATION_PIN 5
#define ALTERNATIVE_IR_FEEDBACK_LED_PIN 6 // E.g. used for examples which use LED_BUILDIN for example output.
#define _IR_TIMING_TEST_PIN 7
#elif defined(ARDUINO_ARCH_SAMD) || defined(ARDUINO_ARCH_SAM)
#define IR_RECEIVE_PIN 2
#define IR_SEND_PIN 3
#define TONE_PIN 4
#define APPLICATION_PIN 5
#define ALTERNATIVE_IR_FEEDBACK_LED_PIN 6 // E.g. used for examples which use LED_BUILDIN for example output.
#define _IR_TIMING_TEST_PIN 7
// On the Zero and others we switch explicitly to SerialUSB
#define Serial SerialUSB
// Definitions for the Chinese SAMD21 M0-Mini clone, which has no led connected to D13/PA17.
// Attention!!! D2 and D4 are switched on these boards!!!
// If you connect the LED, it is on pin 24/PB11. In this case activate the next two lines.
//#undef LED_BUILTIN
//#define LED_BUILTIN 24 // PB11
// As an alternative you can choose pin 25, it is the RX-LED pin (PB03), but active low.In this case activate the next 3 lines.
//#undef LED_BUILTIN
//#define LED_BUILTIN 25 // PB03
//#define FEEDBACK_LED_IS_ACTIVE_LOW // The RX LED on the M0-Mini is active LOW
#elif defined (NRF51) // BBC micro:bit
#define IR_RECEIVE_PIN 2
#define IR_SEND_PIN 3
#define APPLICATION_PIN 1
#define _IR_TIMING_TEST_PIN 4
#define tone(...) void() // no tone() available
#define noTone(a) void()
#define TONE_PIN 42 // Dummy for examples using it
#else
#warning Board / CPU is not detected using pre-processor symbols -> using default values, which may not fit. Please extend PinDefinitionsAndMore.h.
// Default valued for unidentified boards
#define IR_RECEIVE_PIN 2
#define IR_SEND_PIN 3
#define TONE_PIN 4
#define APPLICATION_PIN 5
#define ALTERNATIVE_IR_FEEDBACK_LED_PIN 6 // E.g. used for examples which use LED_BUILDIN for example output.
#define _IR_TIMING_TEST_PIN 7
#endif // defined(ESP8266)
#if !defined (FLASHEND)
#define FLASHEND 0xFFFF // Dummy value for platforms where FLASHEND is not defined
#endif
/*
* Helper macro for getting a macro definition as string
*/
#define STR_HELPER(x) #x
#define STR(x) STR_HELPER(x)
import sys
import time
import signal
import serial
from serial.tools import list_ports
from datetime import datetime
serial_number = '5&8222B2E'
ser = serial.Serial()
ser.baudrate = 115200
def ts():
return datetime.now().strftime('%c')
def find_arduino(serial_number):
print("Scanning COM ports:")
for pinfo in list_ports.comports():
print("%s '%s' serial='%s'" % (pinfo.name, pinfo.description, pinfo.serial_number))
if pinfo.serial_number != None and pinfo.serial_number.startswith(serial_number):
ser.port = pinfo.device
return
raise IOError("Could not find device matching serial %s. Aborting.", serial_number)
print("[%s] Starting TV service..." % ts())
find_arduino(serial_number)
print("[%s] Found arduino matching %s at %s" % (ts(), serial_number, ser.name))
def startup():
print("[%s] Starting up..." % ts())
ser.open()
ser.write(b'!')
ser.flush()
ser.close()
def shutdown(a,b):
print("[%s] Shutting down..." % ts())
ser.open()
ser.write(b'!')
ser.flush()
ser.close()
time.sleep(2)
exit()
startup()
signal.signal(signal.SIGINT, shutdown)
while True:
time.sleep(60)
# This is a commandline utility that sends IR commands to the TV as arguments to the command
import serial.tools.list_ports
import sys
import time
def find_arduino(serial_number):
print("Scanning COM ports:")
for pinfo in serial.tools.list_ports.comports():
print("%s '%s' serial='%s'" % (pinfo.name, pinfo.description, pinfo.serial_number))
if pinfo.serial_number != None and pinfo.serial_number.startswith(serial_number):
return serial.Serial(pinfo.device, 115200)
raise IOError("Could not find an arduino - is it plugged in?")
ser = find_arduino(serial_number='5&8222B2E')
print("Found arduino at %s" % ser.name)
cmd = "!"
if (len(sys.argv) >= 2):
cmd = sys.argv[1]
print("Sending command: %s" % cmd)
ser.write(cmd.encode())
ser.flush()
ser.close()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment