nativeEthernet - Client side processing of returned HTTP 1.1 responses

japreja

Active member
I have a project, in General Discussion https://forum.pjrc.com/threads/70429-Talk-to-your-ROKU-device-tv-using-Ethernet-) , that I have modified to get data from the TV as an HTTP/1.1 respons to a GET request. Here is the data I am trying to parse, on the teensy, that is from the serial terminal, I zero'd out some data for privacy :

Code:
HTTP/1.1 200 OK
Server: Roku/11.0.0 UPnP/1.0 Roku/11.0.0
Content-Length: 3340
Cache-Control: no-cache
Content-Type: text/xml; charset="utf-8"


<?xml version="1.0" encoding="UTF-8" ?>
<device-info>
    <udn>00000000-0000-0000-0000-000000000000</udn>
    <serial-number>000000000000</serial-number>
    <device-id>S035G06XCJJC</device-id>
    <advertising-id>00000000-0000-0000-0000-000000000000</advertising-id>
    <vendor-name>TCL</vendor-name>
    <model-name>55S425</model-name>
    <model-number>C104X</model-number>
    <model-region>US</model-region>
    <is-tv>true</is-tv>
    <is-stick>false</is-stick>
    <screen-size>55</screen-size>
    <panel-id>19</panel-id>
    <ui-resolution>1080p</ui-resolution>
    <tuner-type>ATSC</tuner-type>
    <supports-ethernet>true</supports-ethernet>
    <wifi-mac>FF:FF:FF:FF:FF:FF</wifi-mac>
    <wifi-driver>realtek</wifi-driver>
    <has-wifi-extender>false</has-wifi-extender>
    <has-wifi-5G-support>true</has-wifi-5G-support>
    <can-use-wifi-extender>true</can-use-wifi-extender>
    <ethernet-mac>FF:FF:FF:FF:FF:FF</ethernet-mac>
    <network-type>wifi</network-type>
    <network-name>MY_HOME_NET</network-name>
    <friendly-device-name>55" TCL Roku TV</friendly-device-name>
    <friendly-model-name>TCL•Roku TV</friendly-model-name>
    <default-device-name>TCL•Roku TV - X00000FXCJJC</default-device-name>
    <user-device-name>55" TCL Roku TV</user-device-name>
    <user-device-location>Living room</user-device-location>
    <build-number>93C.00E04193A</build-number>
    <software-version>11.0.0</software-version>
    <software-build>4193</software-build>
    <secure-device>true</secure-device>
    <language>en</language>
    <country>US</country>
    <locale>en_US</locale>
    <time-zone-auto>true</time-zone-auto>
    <time-zone>US/Mountain</time-zone>
    <time-zone-name>United States/Mountain</time-zone-name>
    <time-zone-tz>America/Denver</time-zone-tz>
    <time-zone-offset>-360</time-zone-offset>
    <clock-format>12-hour</clock-format>
    <uptime>755283</uptime>
    <power-mode>PowerOn</power-mode>
    <supports-suspend>true</supports-suspend>
    <supports-find-remote>true</supports-find-remote>
    <find-remote-is-possible>false</find-remote-is-possible>
    <supports-audio-guide>true</supports-audio-guide>
    <supports-rva>true</supports-rva>
    <developer-enabled>true</developer-enabled>
    <keyed-developer-id/>
    <search-enabled>true</search-enabled>
    <search-channels-enabled>true</search-channels-enabled>
    <voice-search-enabled>true</voice-search-enabled>
    <notifications-enabled>true</notifications-enabled>
    <notifications-first-use>false</notifications-first-use>
    <supports-private-listening>true</supports-private-listening>
    <supports-private-listening-dtv>true</supports-private-listening-dtv>
    <supports-warm-standby>true</supports-warm-standby>
    <headphones-connected>false</headphones-connected>
    <supports-audio-settings>false</supports-audio-settings>
    <expert-pq-enabled>1.0</expert-pq-enabled>
    <supports-ecs-textedit>true</supports-ecs-textedit>
    <supports-ecs-microphone>true</supports-ecs-microphone>
    <supports-wake-on-wlan>true</supports-wake-on-wlan>
    <supports-airplay>true</supports-airplay>
    <has-play-on-roku>true</has-play-on-roku>
    <has-mobile-screensaver>false</has-mobile-screensaver>
    <support-url>support.tcl.com/us</support-url>
    <grandcentral-version>7.2.53</grandcentral-version>
    <trc-version>3.0</trc-version>
    <trc-channel-version>6.0.15</trc-channel-version>
    <davinci-version>2.8.20</davinci-version>
    <av-sync-calibration-enabled>1.0</av-sync-calibration-enabled>
</device-info>

I am mainly interested in storing this data in variables as strings instead of chars but I cant seem to find sample code for client side, there is plenty of samples for server side but I am acting as a client. My code is slightly different than posted in the other thread since I have not finished editing it yet.

My current code is as follows, sorry if its sort of lengthy, the end of the code are the functions to POST/GET my data of interest, called from the end of Setup() and spit out to the serial terminal. I have also installed two extra memory chips for a total of 16MB that I have not utilized yet.

Code:
/* 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)


int screenIterations = 0; // just testing how many draws on an idle screen...


// 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;
//
// Joystick
//
const int joyPin_X    = A10;
const int joyPin_Y    = A11;
const int joyPin_Btn  = 26;
int joyVal_X    = 0;
int joyVal_Y    = 0;
int joyVal_Btn  = 0;


// 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";




////////////////////////////////////////////
// Ethernet
uint8_t mac[6]; // setup array for automaticaly getting the mac
unsigned int localPort = 8888;          // local port for Teensy to listen on


// Setup a client for Ethernet
EthernetClient ROKU;  // renamed from client so code is more self descriptive


String myMac;  // For storing the MAC Address as a printable String
String myIP;   // For storing the IP Addres issued to the Teensy 4.1 through DHCP


// Host name should be the IP address of your Roku TV you want to control
char   ROKU_TV_LivingRoom[] = "192.168.1.22";  // Changed HOST_NAME[] so code is more self descriptive


/*
 * **************************************************************
 * 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"; // OK button
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";   // enter as used in searches, not the same as OK button


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";


String query_DEVICE_INFO        = "/query/device-info";


void setup()
{
  Serial.begin(115200);
  while (!Serial){
    ; // wait for serial port to connect. Needed for native USB port only
  }


  delay(10);


  // Use this for a 1.44" TFT
  tft.initR(INITR_144GREENTAB);


  // setup easy access for text positions
  // Larger pixel displays will need to change byte to int for this calculation
  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); // clear screan to black with rotation 1 (One).


  //////////////////////////////////////////////////////////////////
  // Configure Buttons/Joystick
//  pinMode(bSelect, INPUT_PULLUP);
//  pinMode(bLeft,   INPUT_PULLUP);
//  pinMode(bRight,  INPUT_PULLUP);
  pinMode(joyPin_Btn, INPUT_PULLUP);


  //////////////////////////////////////////////////////////
  // Ethernet Setup
  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);
    }
  }


  // test volume down
  rokuGET(query_DEVICE_INFO);


  // populate predefined strings since this usualy doesn't change
  myMac = String(mac[0], HEX) + ":" + String(mac[1], HEX) + ":" + String(mac[2], HEX) + ":" + String(mac[3], HEX) + ":" + String(mac[4], HEX) + ":" + String(mac[5], HEX);
  myIP = String(Ethernet.localIP()[0]) + "." + String(Ethernet.localIP()[1]) + "." + String(Ethernet.localIP()[2]) + "." + String(Ethernet.localIP()[3]);
}


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);
  // Read Joystick
  joyVal_X = analogRead(joyPin_X);
  joyVal_Y = analogRead(joyPin_Y);
  joyVal_Btn = analogRead(joyPin_Btn);


  pl("X:               ",  8, 0);
  pl(String(joyVal_X),     8, 6);
  pl("Y:               ",  9, 0);
  pl(String(joyVal_Y),     9, 6);
  pl("BTN:             ", 10, 0);
  pl(String(joyVal_Btn),  10, 6);
  
}


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("Teensy 4.1 Template", 2,1);
  pl(sVersion, 3, 9);
  pl("MAC:",5,0);
  tft.setTextColor(c_red, c_black);
  pl(myMac,5,5);
  tft.setTextColor(c_white, c_black);
  pl("IP:", 6,0);
  tft.setTextColor(c_red, c_black);
  pl(myIP,6,4);
  tft.setTextColor(c_white, c_black);


  // test screen draws
  pl("Screen Draws:          ", 14, 0);
  pl(String(screenIterations), 14,14);
  screenIterations++;
}


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);
}


/////////////////////////////////////////////////////////
//
// Roku specific code follows
//
/////////////////////////////////////////////////////////
void rokuGET(String rokuString){
  // connect to Roku TV/Device on port 8060:
  if(ROKU.connect(ROKU_TV_LivingRoom, 8060))
  {
    Serial.println("Connected to Roku Device, posting command:");


    ROKU.println("GET " + rokuString + " HTTP/1.1"); // sends one press of the volume - (down) button
    ROKU.println("Host: " + String(ROKU_TV_LivingRoom));
    ROKU.println("Connection: close");
    ROKU.println(); // end HTTP header
    
    Serial.println();
    Serial.println("Recieved Response:");
    while(ROKU.connected()) {
      if(ROKU.available()){
        // read an incoming byte from the server and print it to serial monitor:
        char c = ROKU.read();
        Serial.print(c);
      }
    }


    // the server's disconnected, stop the client:
    ROKU.stop();
    Serial.println();
    Serial.println("disconnected");
  } else {// if not connected:
    Serial.println("connection failed");
  }
}


void rokuPOST(String rokuString){
  // connect to Roku TV/Device on port 8060:
  if(ROKU.connect(ROKU_TV_LivingRoom, 8060))
  {
    Serial.println("Connected to Roku Device, posting command:");


    ROKU.println("POST " + rokuString + " HTTP/1.1"); // sends one press of the volume - (down) button
    ROKU.println("Host: " + String(ROKU_TV_LivingRoom));
    ROKU.println("Connection: close");
    ROKU.println(); // end HTTP header
    
    Serial.println();
    Serial.println("Recieved Response:");
    while(ROKU.connected()) {
      if(ROKU.available()){
        // read an incoming byte from the server and print it to serial monitor:
        char c = ROKU.read();
        Serial.print(c);
      }
    }


    // the server's disconnected, stop the client:
    ROKU.stop();
    Serial.println();
    Serial.println("disconnected");
  } else {// if not connected:
    Serial.println("connection failed");
  }
}


void rokuVolumeUp(){
  rokuPOST(keypress_VOLUME_UP);
}


void rokuVolumeDown(){
  rokuPOST(keypress_VOLUME_DOWN);
}

Also, I would like to know about any XML parsing libraries and how I can remove the header response to get only the XML data, hopefully in string arrays instead of charactor arrays.

Thank you in advance for any help,
Jorge
 
Back
Top