/* Copyright 2018-2022 Jorge Joaquin Pareja
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the Software without restriction, including without
limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
Software, and to permit persons to whom the Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
*/
/*
* Roku Network Remote using Teensy 4.1 with Ethernet
*
* Requirements:
* A modern ROKU TV or Device in "Developer Mode" requires a developer account at https://developer.roku.com/docs/developer-program/getting-started/developer-setup.md
* A local Ethernet Hub with a free ethernet port
* A Teensy 4.1 with Ethernet Module Installed and working properly
*
* Read the NativEthernet thread for more info on Ethernet, at https://forum.pjrc.com/threads/60857-T4-1-Ethernet-Library
*
* This sample sends one volume down keypress to the Roku device at the given IP Address in HOST_NAME[] on your
* local network.
*
* Jorge Joaquin Pareja
*
**/
/* Impliment a UTFT 128x128 SPI display menu template
This code is for the 1.44" TFT LCD Module using Adafruit's current libraries pulled fresh from github,
ignore the products linked video since the links included in the video, from the amazon seller, have
some older code that causes errors on this screen.
* 1.44" TFT LCD https://www.amazon.com/gp/product/B07B4BDWCL
* Adafruit GFX library https://github.com/adafruit/Adafruit-GFX-Library
* Adafruit HW Specific Library https://github.com/adafruit/Adafruit-ST7735-Library
Pinout Display:
These budget displays have what seem to be a misprinted silkscreen text that, at first sight, one
will think these are I2C. They are not! They are in fact SPI. Wire them as follows:
Display | Arduino
* 5V to +5V
* GND to GND
* GND to GND
* NC
* NC
* LED to 5V
* SCL to Digital 13
* SDA to Digital 11
* RS to Digital 7
* RST to Digital 6
* CS to Digital 5
Pinout Buttons (Active Low With Internal PU Resistors enabled, see setup().)
* Left to pin 4
* Right to pin 3
* Select to pin 2 ( not yet implimented )
*/
#include <Adafruit_GFX.h> // Core graphics library ( https://github.com/adafruit/Adafruit-GFX-Library )
#include <Adafruit_ST7735.h> // Hardware-specific library for ST7735 ( https://github.com/adafruit/Adafruit-ST7735-Library )
#include <SPI.h> // is this needed on Teensy 4.1 ?
#include "namedColors.h"
#include <NativeEthernet.h>
const byte TFT_CS = 7; // Silkscreen label CS
const byte TFT_RST = 6; // Silkscreen label RST
const byte TFT_DC = 5; // Silkscreen label RS
//const byte TFT_SCLK = 13; // Silkscreen label SCL (Needs to be Connected even if this line is commented)
//const byte TFT_MOSI = 11; // Silkscreen label SDA (Needs to be Connected even if this line is commented)
// For 1.44" TFT with ST7735 use
Adafruit_ST7735 tft = Adafruit_ST7735(TFT_CS, TFT_DC, TFT_RST);
///// Display, Font, and Padding constants, may need to be changed to int instead of byte values for larger
// screens when the values will excede 255
// Display Properties
const byte screenWidth = 128; // in pixels
const byte screenHeight = 128; // in pixels
// font properties
const byte fntWidth = 6; // in pixels
const byte fntHeight = 8; // in pixels
const byte screenMaxCharsPerLine = screenWidth/fntWidth;
const byte screenMaxLines = screenHeight/fntHeight;
// Padding in pixels, incase font is slightly off screen
const byte cpadding = 0; // column padding
const byte rpadding = 0; // row/line padding
// 21 Columns on a 1.44 (128x128) display. I use this for calculating the
// columns for text placement using setCursor, default font 6x8!
// DONE!! This can be populated in a for loop and account for alternate screen
// and font sizes, and also to possibly save memory, see setup().
int tft144Col[screenMaxCharsPerLine];
int tft144Row[screenMaxLines];
void cls(int color = c_black, byte rotation = 1);
// Buttons
// TODO Add Up and down buttons also
const byte bSelect = 2; // not yet implimented
const byte bLeft = 3;
const byte bRight = 4;
// Menu Settings and variables
byte mIndex = 5; // menu index 5= default title screen
byte pIndex = 5; // previous page
byte cIndex = 5; // current page
const String sVersion = "V1.0";
uint8_t mac[6]; // setup array for automaticaly getting the mac
unsigned int localPort = 8888; // local port for Teensy to listen on
EthernetClient client;
int HTTP_PORT = 8060; // ROKU Device PORT as required by the Roku Documentation
String HTTP_METHOD = "POST"; // GET or POST - Roku Devices will send XML data in return to some commands
// Host name should be the IP address of your Roku TV you want to control
char HOST_NAME[] = "192.168.1.22"; // ROKU Device IP Address, Change this to your Roku Devices IP Address
/*
* **************************************************************
* Roku TV/Device keypress commands from https://developer.roku.com/docs/developer-program/debugging/external-control-api.md#keypress-key-values
*/
String keypress_HOME = "/keypress/Home";
String keypress_REVERSE = "/keypress/Rev";
String keypress_FORWARD = "/keypress/Fwd";
String keypress_PLAY = "/keypress/Play";
String keypress_SELECT = "/keypress/Select";
String keypress_LEFT = "/keypress/Left";
String keypress_RIGHT = "/keypress/Right";
String keypress_DOWN = "/keypress/Down";
String keypress_UP = "/keypress/Up";
String keypress_BACK = "/keypress/Back";
String keypress_INSTANTREPLAY = "/keypress/InstantReplay";
String keypress_INFO = "/keypress/Info";
String keypress_BACKSPACE = "/keypress/Backspace";
String keypress_SEARCH = "/keypress/Search"; // activates voice commands? not sure how to send this with audio
String keypress_ENTER = "/keypress/Enter";
String keypress_VOLUME_UP = "/keypress/VolumeUp";
String keypress_VOLUME_DOWN = "/keypress/VolumeDown";
String keypress_VOLUME_MUTE = "/keypress/VolumeMute";
String keypress_CHANNEL_UP = "/keypress/ChannelUp";
String keypress_CHANNEL_DOWN = "/keypress/ChannelDown";
String keypress_POWER_OFF = "/keypress/PowerOff";
String keypress_INPUT_TUNER = "/keypress/InputTuner";
String keypress_INPUT_HDMI1 = "/keypress/InputHDMI1";
String keypress_INPUT_HDMI2 = "/keypress/InputHDMI2";
String keypress_INPUT_HDMI3 = "/keypress/InputHDMI3";
String keypress_INPUT_HDMI4 = "/keypress/InputHDMI4";
String keypress_INPUT_AV1 = "/keypress/InputAV1";
String keypress_NUMPAD_0 = "/keypress/Lit_0";
String keypress_NUMPAD_1 = "/keypress/Lit_1";
String keypress_NUMPAD_2 = "/keypress/Lit_2";
String keypress_NUMPAD_3 = "/keypress/Lit_3";
String keypress_NUMPAD_4 = "/keypress/Lit_4";
String keypress_NUMPAD_5 = "/keypress/Lit_5";
String keypress_NUMPAD_6 = "/keypress/Lit_6";
String keypress_NUMPAD_7 = "/keypress/Lit_7";
String keypress_NUMPAD_8 = "/keypress/Lit_8";
String keypress_NUMPAD_9 = "/keypress/Lit_9";
void setup()
{
Serial.begin(115200);
while (!Serial){
; // wait for serial port to connect. Needed for native USB port only
}
delay(10);
teensyMAC(mac); // Get Teensy built-in mac
// start the Ethernet connection:
Serial.println("Initialize Ethernet with DHCP:");
if (Ethernet.begin(mac) == 0){
Serial.println("Failed to configure Ethernet using DHCP");
if (Ethernet.hardwareStatus() == EthernetNoHardware)
{
//
// Do something when no hardware
//
Serial.println("Ethernet shield was not found. Sorry, can't run without hardware. :(");
}
else if (Ethernet.linkStatus() == LinkOFF)
{
//
// do something when cable is not connected
//
Serial.println("Ethernet cable is not connected.");
}
while (true)
{
delay(1);
}
}
// print your local IP address:
Serial.print("Teensy IP address is: ");
Serial.println(Ethernet.localIP());
// connect to Roku TV/Device on port 8060:
if(client.connect(HOST_NAME, HTTP_PORT)) {
Serial.println("Connected to Roku Device");
client.println(HTTP_METHOD + " " + keypress_VOLUME_DOWN + " HTTP/1.1"); // sends one press of the volume - (down) button
client.println("Host: " + String(HOST_NAME));
client.println("Connection: close");
client.println(); // end HTTP header
while(client.connected()) {
if(client.available()){
// read an incoming byte from the server and print it to serial monitor:
char c = client.read();
Serial.print(c);
}
}
// the server's disconnected, stop the client:
client.stop();
Serial.println();
Serial.println("disconnected");
} else {// if not connected:
Serial.println("connection failed");
}
// Use this for a 1.44" TFT
tft.initR(INITR_144GREENTAB);
// setup easy access for text positions
for (byte i = 0; i < screenMaxCharsPerLine; i++){
tft144Col[i] = (cpadding+(i*fntWidth));
}
for (byte i = 0; i < screenMaxLines; i++){
tft144Row[i] = (rpadding+(i*fntHeight));
}
// set the text color, and the background color so that new text will cover over any old text
// without having to blank the entire screen
setTextWhiteBlack(); // white text on black background, prevents having to redraw the entire screen
tft.setTextWrap(false); // disable wrapping of text
cls(c_black, 1);
// configure buttons
pinMode(bSelect, INPUT_PULLUP);
pinMode(bLeft, INPUT_PULLUP);
pinMode(bRight, INPUT_PULLUP);
}
void loop()
{
switch (Ethernet.maintain())
{
case 1:
//renewed fail
Serial.println("Error: renewed fail");
break;
case 2:
//renewed success
Serial.println("Renewed success");
//print your local IP address:
Serial.print("My IP address: ");
Serial.println(Ethernet.localIP());
break;
case 3:
//rebind fail
Serial.println("Error: rebind fail");
break;
case 4:
//rebind success
Serial.println("Rebind success");
//print your local IP address:
Serial.print("My IP address: ");
Serial.println(Ethernet.localIP());
break;
default:
//nothing happened
break;
} // END switch(Ethernet.maintain())
layoutScreen();
delay(100);
}
// From the forums, https://forum.pjrc.com/threads/62932-Teensy-4-1-MAC-Address
// Detects the pre-programed unique mac address on all Teensy 4.1's, I don't think
// any libs are required for this to work...
void teensyMAC(uint8_t *mac) {
for(uint8_t by=0; by<2; by++) mac[by]=(HW_OCOTP_MAC1 >> ((1-by)*8)) & 0xFF;
for(uint8_t by=0; by<4; by++) mac[by+2]=(HW_OCOTP_MAC0 >> ((3-by)*8)) & 0xFF;
Serial.printf("MAC: %02x:%02x:%02x:%02x:%02x:%02x\n", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
}
////////////////////////////
// TFT Code
////////////////////////////
void layoutScreen(){
processButtons();
drawMenu(mIndex);
switch(mIndex){
case 1:
Page1();
break;
case 2:
Page2();
break;
case 3:
Page3();
break;
case 4:
Page4();
break;
case 5:
Page5();
break;
default:
Page5();
break;
}
}
void processButtons(){
// read the state of the pushbutton value:
int bSelectState = digitalRead(bSelect);
int bLeftState = digitalRead(bLeft);
int bRightState = digitalRead(bRight);
// check if the pushbutton is pressed. If it is, the buttonState is HIGH:
if ((bLeftState == HIGH) && (bRightState == LOW)){
pIndex = mIndex;
mIndex -= 1;
}
if ((bRightState == HIGH) && (bLeftState == LOW)){
pIndex = mIndex;
mIndex += 1;
}
if( mIndex <= 0){
mIndex = 5;
}
else if (mIndex > 5){
mIndex = 1;
}
}
void Page1(){ // display Page 1
setTextWhiteBlack();
}
void Page2(){ // display Page 2
}
void Page3(){ // display Page 3
}
void Page4(){ // display Page 4
}
void Page5(){// Main Screen Page - Title Screen
// "0123456789012345678901" // max 22 chars per line referance on 128x128px display (1.44 inch
pl("Menu Template", 2,4);
pl(sVersion, 3, 8);
pl("using a",6,7);
pl("1.44\" TFT LCD", 7,4);
pl("By", 8,10);
pl("Jorge Pareja", 9, 5);
tft.setTextColor(c_red, c_black);
pl("Press Left Or Right", 13, 1);
pl("buttons To Begin", 14, 3);
}
void drawMenu(byte index) {// calling this will clear the screen and draw the menu on line 0, similar to tabbed pages. No need to modify, add code to Page#() functions instead
String page0 = " P1 ";
String page1 = " P2 ";
String page2 = " P3 ";
String page3 = " P4 ";
cIndex = index;
setTextWhiteBlue();
if(pIndex != cIndex){
cls();
pIndex = cIndex;
}
if(index == 1){
setTextBlackRed();
pl(page0, 0, 1);
setTextWhiteBlue();
pl(page1, 0, 6);
pl(page2, 0, 11);
pl(page3, 0, 16);
}
else if(index == 2){
pl(page0, 0, 1);
setTextBlackRed();
pl(page1, 0, 6);
setTextWhiteBlue();
pl(page2, 0, 11);
pl(page3, 0, 16);
}
else if(index == 3){
pl(page0, 0, 1);
pl(page1, 0, 6);
setTextBlackRed();
pl(page2, 0, 11);
setTextWhiteBlue();
pl(page3, 0, 16);
}
else if(index == 4){
// Page 4
pl(page0, 0, 1);
pl(page1, 0, 6);
pl(page2, 0, 11);
setTextBlackRed();
pl(page3, 0, 16);
}
else if(index == 5){
// menu item not selected
pl(page0, 0, 1);
pl(page1, 0, 6);
pl(page2, 0, 11);
pl(page3, 0, 16);
}
setTextWhiteBlack();
}
void setTextWhiteBlue() {tft.setTextColor(c_white, c_blue);}
void setTextWhiteBlack(){tft.setTextColor(c_white, c_black);}
void setTextBlackRed() {tft.setTextColor(c_black, c_red);}
void uprint(String txt, int fg_color, int bg_color, byte lineNumber, byte columnNumber){
tft.setTextColor(fg_color, bg_color);
tft.setCursor(tft144Col[columnNumber], tft144Row[lineNumber]);
tft.print(txt);
}
void pl(String txt, byte lineNumber, byte columnNumber) { // print string based on the line and column numbers, calculated in the arrays
tft.setCursor(tft144Col[columnNumber], tft144Row[lineNumber]);
tft.print(txt);
}
void cls(int color = c_black, byte rotation = 1){ // Clear the Screen w/ screen rotation
tft.setRotation(rotation);
tft.fillScreen(color);
}