ESP32 Bluetooth TV remote

Управление телевизором по Bluetooth.

//#define UNIT_TEST false
#include <WiFi.h>
#include <WiFiUdp.h>
//#include <BleKeyboard.h>
#include <BleRcAtv.h>
#include <WiFiClient.h>
#include <WebServer.h>
#include <Esp.h>
//#include <assert.h>

//#include <IRremoteESP8266.h>
//#include "PinDefinitionsAndMore.h"
//#include <IRremote.hpp>
//#define SEND_PWM_BY_TIMER
//#define IR_SEND_PIN         2
//#include <IRSend.hpp>
#include <IRsend.h>
#include <IRrecv.h>
//
#include <IRtext.h>
#include <IRutils.h>
//
#include <ESPmDNS.h>
//#include <IRac.h>
//#include <IRtext.h>
//#include <IRutils.h>
#include <BT_VoiceRC_M9.h>
#include <Preferences.h>
//Preferences prefs;
//ESP sys;

const uint16_t kRecvPin = 14;
//Protocol  : NEC
//Code      : 0x5FA38C7 (32 Bits)
//uint16_t rawData[71] = {9012, 4558,  676, 480,  650, 508,  648, 508,  674, 480,  650, 506,  650, 1664,  650, 506,  676, 1638,  650, 1664,  650, 1664,  650, 1664,  676, 1638,  650, 1664,  650, 506,  652, 1664,  650, 506,  650, 506,  676, 480,  650, 1664,  650, 1664,  676, 1638,  676, 480,  652, 506,  652, 504,  626, 1690,  650, 1664,  674, 482,  654, 502,  678, 480,  650, 1664,  650, 1664,  650, 1664,  676, 40168,  9046, 2242,  648};  // NEC 5FA38C7
//uint32_t address = 0xA0;
//uint32_t command = 0x1C;
//uint64_t data = 0x5FA38C7;

// Kivi 4k
#define K_IR_POWER 0x5FA38C7
#define K_IR_VOLUP 0x5FA02FD
#define K_IR_VOLDOWN 0x5FABA45


BleRcAtv bleKeyboard("4DUK RCONTROL");
WiFiUDP Udp;
IRsend IrSender(4);
//IRsend IrSender(2);
//IRrecv irrecv(kRecvPin, kCaptureBufferSize, kTimeout, true);
const uint16_t kCaptureBufferSize = 1024;
IRrecv irrecv(kRecvPin, kCaptureBufferSize, 50, true);
decode_results results;
Preferences prefs;
#define STASSID "HBRIDGE"
//#define STASSID "HEP-BAM"
#define STAPSK "9096573963"
//#define STAPSK "P0lar1s2579"
#define UDP_TX_PACKET_MAX_SIZE ETH_MAX_PACKET_SIZE
//const MediaKeyReport KEY_TV_CHANNEL_UP = {1, 0};
const MediaKeyReport KEY_TV_CHANNEL_UP = KEY_MEDIA_CHANNELUP;
//const MediaKeyReport KEY_TV_CHANNEL_DOWN = {2, 0};
const MediaKeyReport KEY_TV_CHANNEL_DOWN = KEY_MEDIA_CHANNELDOWN;
//const MediaKeyReport KEY_TV_VOLUME_UP = {4, 0};
const MediaKeyReport KEY_TV_VOLUME_UP = KEY_MEDIA_VOLUMEUP;
//const MediaKeyReport KEY_TV_VOLUME_DOWN = {8, 0};
const MediaKeyReport KEY_TV_VOLUME_DOWN = KEY_MEDIA_VOLUMEDOWN;
//const MediaKeyReport KEY_TV_SEARCH = {16, 0};
const MediaKeyReport KEY_TV_SEARCH = KEY_MEDIA_SEARCH;
//const MediaKeyReport KEY_TV_BACK = {32, 0};
const MediaKeyReport KEY_TV_BACK = KEY_MEDIA_BACK;
//const MediaKeyReport KEY_TV_HOME = {64, 0};
const MediaKeyReport KEY_TV_HOME = KEY_MEDIA_HOMEPAGE;
//const MediaKeyReport KEY_TV_POWER = {128, 0};
const MediaKeyReport KEY_TV_POWER = {0x30, 0};
//const MediaKeyReport KEY_TV_PLAYPAUSE = {0, 1};
const MediaKeyReport KEY_TV_PLAYPAUSE = KEY_MEDIA_PLAYPAUSE;
//const MediaKeyReport KEY_TV_OK = {0, 2};
const MediaKeyReport KEY_TV_OK = KEY_MEDIA_SELECT;
//const MediaKeyReport KEY_TV_OK = KEY_MEDIA_SELECT;
//const MediaKeyReport KEY_TV_UP = {0, 4};
const MediaKeyReport KEY_TV_UP = KEY_MEDIA_UP;
//const MediaKeyReport KEY_TV_DOWN = {0, 8};
const MediaKeyReport KEY_TV_DOWN = KEY_MEDIA_DOWN;
//const MediaKeyReport KEY_TV_LEFT = {0, 16};
const MediaKeyReport KEY_TV_LEFT = KEY_MEDIA_LEFT;
//const MediaKeyReport KEY_TV_RIGHT = {0, 32};
const MediaKeyReport KEY_TV_RIGHT = KEY_MEDIA_RIGHT;
//const MediaKeyReport KEY_TV_MUTE = {0, 64};
const MediaKeyReport KEY_TV_MUTE = KEY_MEDIA_MUTE;
//const MediaKeyReport KEY_TV_INFO = {0, 128};
const MediaKeyReport KEY_TV_INFO = KEY_MEDIA_HELP;
const MediaKeyReport KEY_TV_MENU = KEY_MEDIA_MENU;
//const MediaKeyReport KEY_TV_INPUT = {0x59,0};
const MediaKeyReport KEY_TV_INPUT = {130,0};
// to home
const char * main_page="<html>\
  <head>\
      <title>Remote control</title>\
    <style>\
      body { background-color: #cccccc; font-family: Arial, Helvetica, Sans-Serif; Color: #000088; }\
      td { text-align: center; }\
    </style>\
  </head>\
  <body>\
    <h1>Remote control</h1>\
    <table>\
    <tr><td><button onclick='send_key(\"keypower\");'>POWER</button></td><td>&nbsp;</td><td><button onclick='send_key(\"keyinput\");'>INPUT</button></td></tr>\
    <tr><td><button onclick='send_key(\"keynetflix\");'>NETFLIX</button></td><td>&nbsp;</td><td><button onclick='send_key(\"keyyourtube\");'>YOUTUBE</button></td></tr>\
    <tr><td>&nbsp;</td><td><button onclick='send_key(\"keyup\");'>&uarr;</button></td><td>&nbsp;</td></tr>\
    <tr><td><button onclick='send_key(\"keyleft\");'>&larr;</button></td><td><button onclick='send_key(\"keyok\");'>OK</button></td><td><button onclick='send_key(\"keyright\");'>&rarr;</button></td></tr>\
    <tr><td>&nbsp;</td><td><button onclick='send_key(\"keydown\");'>&darr;</button></td><td>&nbsp;</td></tr>\
    <tr><td><button onclick='send_key(\"keyback\");'>&#11025;</button></td><td><button onclick='send_key(\"keyhome\");'>HOME</button></td><td><button onclick='send_key(\"keymenu\");'>MENU</button></td></tr>\
    <tr><td><button onclick='send_key(\"keyvolup\");'>+</button></td><td>&nbsp;</td><td><button onclick='send_key(\"keychannext\");'>+</button></td></tr>\
    <tr><td>VOL</td><td><button onclick='send_key(\"keysearch\");'>&#128269;</button></td><td>CHAN</td></tr>\
    <tr><td><button onclick='send_key(\"keyvoldown\");'>-</button></td><td>&nbsp;</td><td><button onclick='send_key(\"keychanprev\");'>-</button></td></tr>\
    <tr><td><button onclick='send_key(\"key1\");'>&#10102;</button></td><td><button onclick='send_key(\"key2\");'>&#10103;</button></td><td><button onclick='send_key(\"key3\");'>&#10104;</button></td></tr>\
    <tr><td><button onclick='send_key(\"key4\");'>&#10105;</button></td><td><button onclick='send_key(\"key5\");'>&#10106;</button></td><td><button onclick='send_key(\"key6\");'>&#10107;</button></td></tr>\
    <tr><td><button onclick='send_key(\"key7\");'>&#10108;</button></td><td><button onclick='send_key(\"key8\");'>&#10109;</button></td><td><button onclick='send_key(\"key3\");'>&#10110;</button></td></tr>\
    <tr><td><button onclick='send_key(\"keyinfo\");'>INFO</button></td><td><button onclick='send_key(\"key0\");'>&#9471;</button></td><td><button onclick='send_key(\"keychanback\");'>&#8634;</button></td></tr>\
    </table>\
    <script type='text/javascript'>\
    function send_key(key) {\
    const xhr=new XMLHttpRequest();\
    xhr.open('GET','/'+key);\
    xhr.send(null);}\
    </script>\
  </body>\
</html>";

const char* ntpServer1 = "pool.ntp.org";
const char* ntpServer2 = "time.nist.gov";
const unsigned int localPort = 9009;
int onoff_state=0;
unsigned long t_mil;
int packetSize=0;
WebServer server(80);
char packetBuffer[UDP_TX_PACKET_MAX_SIZE + 1];
char ser_buf[1024];

void handleRoot() {
  //digitalWrite(led, 1);
  //char temp[400];
  int sec = millis() / 1000;
  int min = sec / 60;
  int hr = min / 60;

// <meta http-equiv='refresh' content='5'/>
  
  server.send(200, "text/html", main_page);
  //digitalWrite(led, 0);
}

void handleNotFound() {
  //digitalWrite(led, 1);
  String message = "File Not Found\n\n";
  message += "URI: ";
  message += server.uri();
  message += "\nMethod: ";
  message += (server.method() == HTTP_GET) ? "GET" : "POST";
  message += "\nArguments: ";
  message += server.args();
  message += "\n";

  for (uint8_t i = 0; i < server.args(); i++) {
    message += " " + server.argName(i) + ": " + server.arg(i) + "\n";
  }

  server.send(404, "text/plain", message);
  //digitalWrite(led, 0);
}

void setup() {
  String mip;
  String mmc;
  onoff_state=0;
  Serial.begin(115200);
  if (prefs.begin("4duksets")) {
    Serial.println("NVS OK");
  } else {
    Serial.println("NVS ERROR");
  }
  WiFi.mode(WIFI_STA);
  if (prefs.isKey("SSID")) {
    Serial.println(prefs.getString("SSID","SSID"));
    if (prefs.isKey("SSPASS")) {
      Serial.println(prefs.getString("SSPASS","SSPASS"));
      WiFi.begin(prefs.getString("SSID","SSID"), prefs.getString("SSPASS","SSPASS"));
    } else {
      WiFi.begin(STASSID, STAPSK);
      Serial.println("SSID is set, but PASSWORD NOT FOUND.");
    }
  } else {
    WiFi.begin(STASSID, STAPSK);
    Serial.println("SSID NOT SET.");
  }
  prefs.end();
  //prefs.begin("4duksets");
  while (WiFi.status() != WL_CONNECTED) {
    Serial.print('.');
    delay(500);
    if (Serial.available() > 0) {
     int ser_len=Serial.available();
     int ser_cur=0;
     uint8_t s_kode;
      for (ser_cur=0;ser_cur<ser_len;ser_cur++) {
       char i_char=Serial.read();
        switch (i_char) {
         case 13:
          ser_buf[ser_cur]=0;
          break;
         case 10:
          ser_buf[ser_cur]=0;
          break;
         default:
          ser_buf[ser_cur]=i_char;
        }
      }
    String g_cmd=String(ser_buf);
    Serial.print("Get string:");
    Serial.println(g_cmd);
    Serial.flush();
    if (g_cmd.indexOf("ssid")>-1) {
      Serial.print("Get WIFI ssid ");
      int s_probel=g_cmd.indexOf(' ');
      String wifi_ssid=g_cmd.substring(s_probel+1);
      Serial.println(wifi_ssid);
      prefs.begin("4duksets");
      prefs.putString("SSID",wifi_ssid);
      prefs.end();
      Serial.println("Saved");
    } else if (g_cmd.indexOf("pass")>-1) {
      Serial.print("Get WIFI pass ");
      int s_probel=g_cmd.indexOf(' ');
      String wifi_pass=g_cmd.substring(s_probel+1);
      Serial.println(wifi_pass);
      prefs.begin("4duksets");
      prefs.putString("SSPASS",wifi_pass);
      prefs.end();
      Serial.println("Saved");
    } else if (g_cmd.indexOf("reboot")>-1) {
      ESP.restart();
    }
   }
  }
  int cl_buf=0;
  for (cl_buf=0;cl_buf<1024;cl_buf++) {
    ser_buf[cl_buf]=0;
  }
  configTime(3600*3, 0, ntpServer1, ntpServer2);
  Serial.print("Connected! IP address: ");
  Serial.println(WiFi.localIP());
  Serial.println(WiFi.broadcastIP());
  mip=WiFi.localIP().toString();
  Serial.println(WiFi.macAddress());
  mmc=WiFi.macAddress();
  mmc.replace(":","");
  Serial.printf("UDP server on port %d\n", localPort);
  Udp.begin(localPort);
  
  
  t_mil=millis();
  
  Serial.println("Starting BLE work!");
  bleKeyboard.setName(std::string(String("rc4duk"+mmc).c_str()));
  bleKeyboard.begin();
  //IrSender.begin(2);
  IrSender.begin();
  irrecv.enableIRIn();
  if (!MDNS.begin(String("rc4duk"+mmc))) {
        Serial.println("Error setting up MDNS responder!");
        while(1) {
            delay(1000);
        }
    }
    Serial.println("mDNS responder started");

    // Start TCP (HTTP) server
    server.on("/", handleRoot);
    server.on("/keyup", []() {
      bleKeyboard.write(KEY_TV_UP);
      server.send(200, "text/plain", "OK");
    });
    server.on("/keydown", []() {
      bleKeyboard.write(KEY_TV_DOWN);
      server.send(200, "text/plain", "OK");
    });
    server.on("/keyleft", []() {
      bleKeyboard.write(KEY_TV_LEFT);
      server.send(200, "text/plain", "OK");
    });
    server.on("/keyright", []() {
      bleKeyboard.write(KEY_TV_RIGHT);
      server.send(200, "text/plain", "OK");
    });
    server.on("/keyok", []() {
      bleKeyboard.write(KEY_TV_OK);
      //bleKeyboard.print("\n");
      server.send(200, "text/plain", "OK");
    });
    server.on("/key0", []() {
      bleKeyboard.print("0");
      server.send(200, "text/plain", "OK");
    });
    server.on("/key1", []() {
      bleKeyboard.print("1");
      server.send(200, "text/plain", "OK");
    });
    server.on("/key2", []() {
      bleKeyboard.print("2");
      server.send(200, "text/plain", "OK");
    });
    server.on("/key3", []() {
      bleKeyboard.print("3");
      server.send(200, "text/plain", "OK");
    });
    server.on("/key4", []() {
      bleKeyboard.print("4");
      server.send(200, "text/plain", "OK");
    });
    server.on("/key5", []() {
      bleKeyboard.print("5");
      server.send(200, "text/plain", "OK");
    });
    server.on("/key6", []() {
      bleKeyboard.print("6");
      server.send(200, "text/plain", "OK");
    });
    server.on("/key7", []() {
      bleKeyboard.print("7");
      server.send(200, "text/plain", "OK");
    });
    server.on("/key8", []() {
      bleKeyboard.print("8");
      server.send(200, "text/plain", "OK");
    });
    server.on("/key9", []() {
      bleKeyboard.print("9");
      server.send(200, "text/plain", "OK");
    });
    server.on("/keyhome", []() {
      bleKeyboard.write(KEY_TV_HOME);
      server.send(200, "text/plain", "OK");
    });
    server.on("/keyvolup", []() {
      bleKeyboard.write(KEY_TV_VOLUME_UP);
      server.send(200, "text/plain", "OK");
    });
    server.on("/keyvoldown", []() {
      bleKeyboard.write(KEY_TV_VOLUME_DOWN);
      server.send(200, "text/plain", "OK");
    });
    server.on("/keychannext", []() {
      bleKeyboard.write(KEY_TV_CHANNEL_UP);
      server.send(200, "text/plain", "OK");
    });
    server.on("/keychanprev", []() {
      bleKeyboard.write(KEY_TV_CHANNEL_DOWN);
      server.send(200, "text/plain", "OK");
    });
    server.on("/keymenu", []() {
      bleKeyboard.write(KEY_TV_MENU);
      server.send(200, "text/plain", "OK");
    });
    server.on("/keychanprev", []() {
      bleKeyboard.write(KEY_TV_CHANNEL_DOWN);
      server.send(200, "text/plain", "OK");
    });
    server.on("/keyback", []() {
      bleKeyboard.write(KEY_TV_BACK);
      //bleKeyboard.print((uint8_t) 0x08);
      server.send(200, "text/plain", "OK");
    });
    server.on("/keyhome", []() {
      bleKeyboard.write(KEY_TV_HOME);
      server.send(200, "text/plain", "OK");
    });
    server.on("/keyinput", []() {
      //bleKeyboard.print((uint8_t) 79);
      bleKeyboard.write(KEY_TV_INPUT);
      server.send(200, "text/plain", "OK");
    });
    server.on("/keypower", []() {
      //bleKeyboard.print((uint8_t) 79);
      //bleKeyboard.write(KEY_TV_INPUT);
      IrSender.sendNEC(0x20DF10EF,32);
      server.send(200, "text/plain", "OK");
    });
    //IrSender.sendNEC(0x20DF10EF,32);
    server.onNotFound(handleNotFound);
    //server.onNotFound(handleRoot);
    server.begin();
    Serial.println("TCP server started");

    // Add service to MDNS-SD
    MDNS.addService("http", "tcp", 80);
    MDNS.addService("4duk", "udp", 9009);
    //MDNS.addService("4duk", "udp", 9009);
    
}


void loop() {
  if (irrecv.decode(&results)) {
    //Serial.println("Click");
    if (results.decode_type == UNKNOWN) {
      //Serial.println("Click");
    } else {
      Serial.println("Click");
      if (!results.repeat) {
    Serial.print(resultToHumanReadableBasic(&results));
    Serial.print("Size resilts = ");
    Serial.println(sizeof(results));
    Serial.println(resultToSourceCode(&results));
      }
    }
  }

  //Serial input
  if (Serial.available() > 0) {
    int ser_len=Serial.available();
    int ser_cur=0;
    uint8_t s_kode;
    for (ser_cur=0;ser_cur<ser_len;ser_cur++) {
      char i_char=Serial.read();
      switch (i_char) {
        case 13:
        ser_buf[ser_cur]=0;
          break;
        case 10:
        ser_buf[ser_cur]=0;
          break;
        default:
        ser_buf[ser_cur]=i_char;
      }
    }
    String g_cmd=String(ser_buf);
    Serial.print("Get string:");
    Serial.println(g_cmd);
    Serial.flush();
    if (g_cmd.indexOf("ssid")>-1) {
      Serial.print("Get WIFI ssid ");
      int s_probel=g_cmd.indexOf(' ');
      String wifi_ssid=g_cmd.substring(s_probel+1);
      Serial.println(wifi_ssid);
      prefs.begin("4duksets");
      prefs.putString("SSID",wifi_ssid);
      prefs.end();
      Serial.println("Saved");
    } else if (g_cmd.indexOf("pass")>-1) {
      Serial.print("Get WIFI pass ");
      int s_probel=g_cmd.indexOf(' ');
      String wifi_pass=g_cmd.substring(s_probel+1);
      Serial.println(wifi_pass);
      prefs.begin("4duksets");
      prefs.putString("SSPASS",wifi_pass);
      prefs.end();
      Serial.println("Saved");
    } else if (g_cmd.indexOf("reboot")>-1) {
      ESP.restart();
    } else {
      s_kode=g_cmd.toInt();
      Serial.print("Send code:");
      Serial.println(s_kode,HEX);
      MediaKeyReport TESTKEY = {s_kode,0};
    }
    
    //uint8_t s_kode=bbo.toInt();
    //Serial.print("Parsing code:");
    //Serial.println(s_kode);
    
    //bleKeyboard.write(TESTKEY);
    //bleKeyboard.write((uint8_t) s_kode);
  }
//  if (bleKeyboard.isConnected()) {
//    int sss_c=0;
//    for (sss_c=0;sss_c<255;sss_c++) {
//      Serial.println(sss_c,HEX);
//      MediaKeyReport TESTKEY = {sss_c,2};
//      bleKeyboard.write(TESTKEY);
//      delay(1000);
//    }
//  }

  //WiFiClient client = server.available();
  //Serial.println("handle");
  server.handleClient();
  //Serial.println("handle");
  delay(5);//allow the cpu to switch to other tasks
  
  // put your main code here, to run repeatedly:
  if(bleKeyboard.isConnected()) {
    onoff_state=1;
  } else {
    onoff_state=0;
  }
  //IrSender.sendNEC(0x5FA38C7,32);
  packetSize = Udp.parsePacket();
  if (packetSize) {
//    Serial.printf("Received packet of size %d from %s:%d\n    (to %s:%d, free heap = %d B)\n", packetSize, Udp.remoteIP().toString().c_str(), Udp.remotePort(), "-", "-", ESP.getFreeHeap());
  

    // read the packet into packetBufffer
    int n = Udp.read(packetBuffer, UDP_TX_PACKET_MAX_SIZE);
    packetBuffer[n] = 0;
    Serial.println("Contents:");
    Serial.println(packetBuffer);
    if (strstr(packetBuffer,"device:rc4dukKiwi") != NULL) {
      if (strstr(packetBuffer,"action:on_off") != NULL) {
        if (strstr(packetBuffer,"value:on") != NULL) {
         if (onoff_state==0) {
            //bleKeyboard.print("Hello world");
            //IrSender_on();
            //IrSender.sendNEC(0x5FA38C7);
            bleKeyboard.write(KEY_TV_POWER);
            IrSender.sendNEC(0x20DF10EF,32);
         } else {
            bleKeyboard.write(KEY_TV_POWER);
            IrSender.sendNEC(0x20DF10EF,32);
         }
         //IrSender.sendNEC(0x5FA38C7,32);
         IrSender.sendNEC(0x20DF10EF,32);
        }
      } else if (strstr(packetBuffer,"action:channel") != NULL) {
        if (strstr(packetBuffer,"value:+") != NULL) {
         if (onoff_state==0) {
            //bleKeyboard.print("Hello world");
            //IrSender_on();
            //bleKeyboard.write(KEY_UP_ARROW);
            bleKeyboard.write(KEY_TV_CHANNEL_UP);
         } else {
            //bleKeyboard.write(KEY_UP_ARROW);
            bleKeyboard.write(KEY_TV_CHANNEL_UP);
         }
        } else if (strstr(packetBuffer,"value:-") != NULL) {
          //IrSender_on();
          //bleKeyboard.write(KEY_DOWN_ARROW);
          bleKeyboard.write(KEY_TV_CHANNEL_DOWN);
        } else {
          char * ch_num=strstr(packetBuffer,"value:")+6;
          bleKeyboard.print(ch_num);
          bleKeyboard.write(KEY_TV_OK);
          //bleKeyboard.write(KEY_RETURN);
          //bleKeyboard.write(KEY_NUM_ENTER);
        }
      }  else if (strstr(packetBuffer,"action:volume") != NULL) {
        if (strstr(packetBuffer,"value:+") != NULL) {
         if (onoff_state==0) {
            //bleKeyboard.print("Hello world");
            //IrSender_on();
            //bleKeyboard.write(KEY_TV_VOLUME_UP);
            //bleKeyboard.write(KEY_MEDIA_VOLUME_UP);
            IrSender.sendNEC(K_IR_VOLUP,32);
         } else {
            //bleKeyboard.write(KEY_MEDIA_VOLUME_UP);
            //bleKeyboard.write(KEY_TV_VOLUME_UP);
            IrSender.sendNEC(K_IR_VOLUP,32);
         }
        } else {
          //IrSender_on();
          //bleKeyboard.write(KEY_TV_VOLUME_DOWN);
          IrSender.sendNEC(K_IR_VOLDOWN,32);
        }
      }
    }
  }

}

//// BT_VoiceRC_M9.h

#define KEY_MEDIA_POWER {0x30,0}
#define KEY_MEDIA_MENU {0x40,0}
#define KEY_MEDIA_SELECT {0x41,0}
#define KEY_MEDIA_LEFT {0x44,0}
#define KEY_MEDIA_UP {0x42,0}
#define KEY_MEDIA_RIGHT {0x45,0}
#define KEY_MEDIA_DOWN {0x43,0}

#define KEY_MEDIA_INFO {0x60,0}
#define KEY_MEDIA_SUBTITLE {0x61,0}

#define KEY_MEDIA_RED {0x69,0}
#define KEY_MEDIA_GREEN {0x6a,0}
#define KEY_MEDIA_YELLOW {0x6c,0}
#define KEY_MEDIA_BLUE {0x6b,0}

#define KEY_MEDIA_HELP {0x95,0}

#define KEY_MEDIA_CHANNELUP {0x9c,0}
#define KEY_MEDIA_CHANNELDOWN {0x9d,0}

#define KEY_MEDIA_FASTFORWARD {0xb3,0}
#define KEY_MEDIA_REWIND {0xb4,0}

#define KEY_MEDIA_PLAYPAUSE {0xcd,0}

#define KEY_MEDIA_MUTE {0xe2,0}

#define KEY_MEDIA_VOLUMEUP {0xe9,0}
#define KEY_MEDIA_VOLUMEDOWN {0xea,0}

#define KEY_MEDIA_SEARCH {0x21,0x02}
#define KEY_MEDIA_HOMEPAGE {0x23,0x02}
#define KEY_MEDIA_BACK {0x24,0x02}
#define KEY_MEDIA_STOP {0x26,0x02}
////////////////   BleRcAtv.h

// uncomment the following line to use NimBLE library
//#define USE_NIMBLE

#ifndef ESP32_BLE_RCATV_H
#define ESP32_BLE_RCATV_H
//#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)

#if defined(USE_NIMBLE)

#include "NimBLECharacteristic.h"
#include "NimBLEHIDDevice.h"

#define BLEDevice                  NimBLEDevice
#define BLEServerCallbacks         NimBLEServerCallbacks
#define BLECharacteristicCallbacks NimBLECharacteristicCallbacks
#define BLEHIDDevice               NimBLEHIDDevice
#define BLECharacteristic          NimBLECharacteristic
#define BLEAdvertising             NimBLEAdvertising
#define BLEServer                  NimBLEServer

#else

#include "BLEHIDDevice.h"
#include "BLECharacteristic.h"

#endif // USE_NIMBLE

#include "Print.h"

#define BLE_RCATV_VERSION "0.0.4"
#define BLE_RCATV_VERSION_MAJOR 0
#define BLE_RCATV_VERSION_MINOR 0
#define BLE_RCATV_VERSION_REVISION 4

const uint8_t KEY_LEFT_CTRL = 0x80;
const uint8_t KEY_LEFT_SHIFT = 0x81;
const uint8_t KEY_LEFT_ALT = 0x82;
const uint8_t KEY_LEFT_GUI = 0x83;
const uint8_t KEY_RIGHT_CTRL = 0x84;
const uint8_t KEY_RIGHT_SHIFT = 0x85;
const uint8_t KEY_RIGHT_ALT = 0x86;
const uint8_t KEY_RIGHT_GUI = 0x87;

const uint8_t KEY_UP_ARROW = 0xDA;
const uint8_t KEY_DOWN_ARROW = 0xD9;
const uint8_t KEY_LEFT_ARROW = 0xD8;
const uint8_t KEY_RIGHT_ARROW = 0xD7;
const uint8_t KEY_BACKSPACE = 0xB2;
const uint8_t KEY_TAB = 0xB3;
const uint8_t KEY_RETURN = 0xB0;
const uint8_t KEY_ESC = 0xB1;
const uint8_t KEY_INSERT = 0xD1;
const uint8_t KEY_PRTSC = 0xCE;
const uint8_t KEY_DELETE = 0xD4;
const uint8_t KEY_PAGE_UP = 0xD3;
const uint8_t KEY_PAGE_DOWN = 0xD6;
const uint8_t KEY_HOME = 0xD2;
const uint8_t KEY_END = 0xD5;
const uint8_t KEY_CAPS_LOCK = 0xC1;
const uint8_t KEY_F1 = 0xC2;
const uint8_t KEY_F2 = 0xC3;
const uint8_t KEY_F3 = 0xC4;
const uint8_t KEY_F4 = 0xC5;
const uint8_t KEY_F5 = 0xC6;
const uint8_t KEY_F6 = 0xC7;
const uint8_t KEY_F7 = 0xC8;
const uint8_t KEY_F8 = 0xC9;
const uint8_t KEY_F9 = 0xCA;
const uint8_t KEY_F10 = 0xCB;
const uint8_t KEY_F11 = 0xCC;
const uint8_t KEY_F12 = 0xCD;
const uint8_t KEY_F13 = 0xF0;
const uint8_t KEY_F14 = 0xF1;
const uint8_t KEY_F15 = 0xF2;
const uint8_t KEY_F16 = 0xF3;
const uint8_t KEY_F17 = 0xF4;
const uint8_t KEY_F18 = 0xF5;
const uint8_t KEY_F19 = 0xF6;
const uint8_t KEY_F20 = 0xF7;
const uint8_t KEY_F21 = 0xF8;
const uint8_t KEY_F22 = 0xF9;
const uint8_t KEY_F23 = 0xFA;
const uint8_t KEY_F24 = 0xFB;

const uint8_t KEY_NUM_0 = 0xEA;
const uint8_t KEY_NUM_1 = 0xE1;
const uint8_t KEY_NUM_2 = 0xE2;
const uint8_t KEY_NUM_3 = 0xE3;
const uint8_t KEY_NUM_4 = 0xE4;
const uint8_t KEY_NUM_5 = 0xE5;
const uint8_t KEY_NUM_6 = 0xE6;
const uint8_t KEY_NUM_7 = 0xE7;
const uint8_t KEY_NUM_8 = 0xE8;
const uint8_t KEY_NUM_9 = 0xE9;
const uint8_t KEY_NUM_SLASH = 0xDC;
const uint8_t KEY_NUM_ASTERISK = 0xDD;
const uint8_t KEY_NUM_MINUS = 0xDE;
const uint8_t KEY_NUM_PLUS = 0xDF;
const uint8_t KEY_NUM_ENTER = 0xE0;
const uint8_t KEY_NUM_PERIOD = 0xEB;

typedef uint8_t MediaKeyReport[2];

const MediaKeyReport KEY_MEDIA_NEXT_TRACK = {1, 0};
const MediaKeyReport KEY_MEDIA_PREVIOUS_TRACK = {2, 0};
const MediaKeyReport KEY_MEDIA_STOP = {4, 0};
const MediaKeyReport KEY_MEDIA_PLAY_PAUSE = {8, 0};
const MediaKeyReport KEY_MEDIA_MUTE = {16, 0};
const MediaKeyReport KEY_MEDIA_VOLUME_UP = {32, 0};
const MediaKeyReport KEY_MEDIA_VOLUME_DOWN = {64, 0};
const MediaKeyReport KEY_MEDIA_WWW_HOME = {128, 0};
const MediaKeyReport KEY_MEDIA_LOCAL_MACHINE_BROWSER = {0, 1}; // Opens "My Computer" on Windows
const MediaKeyReport KEY_MEDIA_CALCULATOR = {0, 2};
const MediaKeyReport KEY_MEDIA_WWW_BOOKMARKS = {0, 4};
const MediaKeyReport KEY_MEDIA_WWW_SEARCH = {0, 8};
const MediaKeyReport KEY_MEDIA_WWW_STOP = {0, 16};
const MediaKeyReport KEY_MEDIA_WWW_BACK = {0, 32};
const MediaKeyReport KEY_MEDIA_CONSUMER_CONTROL_CONFIGURATION = {0, 64}; // Media Selection
const MediaKeyReport KEY_MEDIA_EMAIL_READER = {0, 128};


//  Low level key report: up to 6 keys and shift, ctrl etc at once
typedef struct
{
  uint8_t modifiers;
  uint8_t reserved;
  uint8_t keys[6];
} KeyReport;

class BleRcAtv : public Print, public BLEServerCallbacks, public BLECharacteristicCallbacks
{
private:
  BLEHIDDevice* hid;
  BLECharacteristic* inputKeyboard;
  BLECharacteristic* outputKeyboard;
  BLECharacteristic* inputMediaKeys;
  BLECharacteristic* inputMouse;
  BLECharacteristic* inputVendor;
  BLECharacteristic* outputVendor;
  BLEAdvertising*    advertising;
  KeyReport          _keyReport;
  MediaKeyReport     _mediaKeyReport;
  std::string        deviceName;
  std::string        deviceManufacturer;
  uint8_t            batteryLevel;
  bool               connected = false;
  uint32_t           _delay_ms = 7;
  void delay_ms(uint64_t ms);

  uint16_t vid       = 0x05ac;
  uint16_t pid       = 0x820a;
  uint16_t version   = 0x0210;

public:
  BleRcAtv(std::string deviceName = "ESP32 RcAtv", std::string deviceManufacturer = "Espressif", uint8_t batteryLevel = 100);
  void begin(void);
  void end(void);
  void sendReport(KeyReport* keys);
  void sendReport(MediaKeyReport* keys);
  size_t press(uint8_t k);
  size_t press(const MediaKeyReport k);
  size_t release(uint8_t k);
  size_t release(const MediaKeyReport k);
  size_t write(uint8_t c);
  size_t write(const MediaKeyReport c);
  size_t write(const uint8_t *buffer, size_t size);
  void releaseAll(void);
  bool isConnected(void);
  void setBatteryLevel(uint8_t level);
  void setName(std::string deviceName);  
  void setDelay(uint32_t ms);

  void set_vendor_id(uint16_t vid);
  void set_product_id(uint16_t pid);
  void set_version(uint16_t version);
protected:
  virtual void onStarted(BLEServer *pServer) { };
  virtual void onConnect(BLEServer* pServer) override;
  virtual void onDisconnect(BLEServer* pServer) override;
  virtual void onWrite(BLECharacteristic* me) override;

};

#endif // CONFIG_BT_ENABLED
#endif // ESP32_BLE_RCATV_H
////////////////     BleRcAtv.cpp
#include "BleRcAtv.h"

#if defined(USE_NIMBLE)
#include <NimBLEDevice.h>
#include <NimBLEServer.h>
#include <NimBLEUtils.h>
#include <NimBLEHIDDevice.h>
#else
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>
#include "BLE2902.h"
#include "BLEHIDDevice.h"
#endif // USE_NIMBLE
#include "HIDTypes.h"
#include <driver/adc.h>
#include "sdkconfig.h"


#if defined(CONFIG_ARDUHAL_ESP_LOG)
  #include "esp32-hal-log.h"
  #define LOG_TAG ""
#else
  #include "esp_log.h"
  static const char* LOG_TAG = "BLEDevice";
#endif


// Report IDs:
//#define KEYBOARD_ID 0x01
#define KEYBOARD_ID 0x07
#define KEYBOARD_LEDS_ID 0x08
//#define MEDIA_KEYS_ID 0x02
#define MEDIA_KEYS_ID 0x01
#define MOUSE_ID 0x03
#define VENDOR_04 0x04
#define VENDOR_02 0x02

static const uint8_t _hidReportDescriptor_old[] = {
  USAGE_PAGE(1),      0x01,          // USAGE_PAGE (Generic Desktop Ctrls)
  USAGE(1),           0x06,          // USAGE (Keyboard)
  COLLECTION(1),      0x01,          // COLLECTION (Application)
  // ------------------------------------------------- Keyboard
  REPORT_ID(1),       KEYBOARD_ID,   //   REPORT_ID (1)
  USAGE_PAGE(1),      0x07,          //   USAGE_PAGE (Kbrd/Keypad)
  USAGE_MINIMUM(1),   0xE0,          //   USAGE_MINIMUM (0xE0)
  USAGE_MAXIMUM(1),   0xE7,          //   USAGE_MAXIMUM (0xE7)
  LOGICAL_MINIMUM(1), 0x00,          //   LOGICAL_MINIMUM (0)
  LOGICAL_MAXIMUM(1), 0x01,          //   Logical Maximum (1)
  REPORT_SIZE(1),     0x01,          //   REPORT_SIZE (1)
  REPORT_COUNT(1),    0x08,          //   REPORT_COUNT (8)
  HIDINPUT(1),        0x02,          //   INPUT (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
  REPORT_COUNT(1),    0x01,          //   REPORT_COUNT (1) ; 1 byte (Reserved)
  REPORT_SIZE(1),     0x08,          //   REPORT_SIZE (8)
  HIDINPUT(1),        0x01,          //   INPUT (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
  REPORT_COUNT(1),    0x05,          //   REPORT_COUNT (5) ; 5 bits (Num lock, Caps lock, Scroll lock, Compose, Kana)
  REPORT_SIZE(1),     0x01,          //   REPORT_SIZE (1)
  USAGE_PAGE(1),      0x08,          //   USAGE_PAGE (LEDs)
  USAGE_MINIMUM(1),   0x01,          //   USAGE_MINIMUM (0x01) ; Num Lock
  USAGE_MAXIMUM(1),   0x05,          //   USAGE_MAXIMUM (0x05) ; Kana
  HIDOUTPUT(1),       0x02,          //   OUTPUT (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
  REPORT_COUNT(1),    0x01,          //   REPORT_COUNT (1) ; 3 bits (Padding)
  REPORT_SIZE(1),     0x03,          //   REPORT_SIZE (3)
  HIDOUTPUT(1),       0x01,          //   OUTPUT (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
  REPORT_COUNT(1),    0x06,          //   REPORT_COUNT (6) ; 6 bytes (Keys)
  REPORT_SIZE(1),     0x08,          //   REPORT_SIZE(8)
  LOGICAL_MINIMUM(1), 0x00,          //   LOGICAL_MINIMUM(0)
  LOGICAL_MAXIMUM(1), 0x65,          //   LOGICAL_MAXIMUM(0x65) ; 101 keys
  USAGE_PAGE(1),      0x07,          //   USAGE_PAGE (Kbrd/Keypad)
  USAGE_MINIMUM(1),   0x00,          //   USAGE_MINIMUM (0)
  USAGE_MAXIMUM(1),   0x65,          //   USAGE_MAXIMUM (0x65)
  HIDINPUT(1),        0x00,          //   INPUT (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
  END_COLLECTION(0),                 // END_COLLECTION
  // ------------------------------------------------- Media Keys
  USAGE_PAGE(1),      0x0C,          // USAGE_PAGE (Consumer)
  USAGE(1),           0x01,          // USAGE (Consumer Control)
  COLLECTION(1),      0x01,          // COLLECTION (Application)
  REPORT_ID(1),       MEDIA_KEYS_ID, //   REPORT_ID (3)
  USAGE_PAGE(1),      0x0C,          //   USAGE_PAGE (Consumer)
  LOGICAL_MINIMUM(1), 0x00,          //   LOGICAL_MINIMUM (0)
  LOGICAL_MAXIMUM(1), 0x01,          //   LOGICAL_MAXIMUM (1)
//  LOGICAL_MAXIMUM(1), 0x19,          //   LOGICAL_MAXIMUM (1)
//  REPORT_SIZE(1),     0x01,          //   REPORT_SIZE (1)
//  REPORT_COUNT(1),    0x10,          //   REPORT_COUNT (16)
//  USAGE(1),           0xB5,          //   USAGE (Scan Next Track)     ; bit 0: 1
//  USAGE(1),           0xB6,          //   USAGE (Scan Previous Track) ; bit 1: 2
//  USAGE(1),           0xB7,          //   USAGE (Stop)                ; bit 2: 4
//  USAGE(1),           0xCD,          //   USAGE (Play/Pause)          ; bit 3: 8
//  USAGE(1),           0xE2,          //   USAGE (Mute)                ; bit 4: 16
//  USAGE(1),           0xE9,          //   USAGE (Volume Increment)    ; bit 5: 32
//  USAGE(1),           0xEA,          //   USAGE (Volume Decrement)    ; bit 6: 64
//  USAGE(2),           0x23, 0x02,    //   Usage (WWW Home)            ; bit 7: 128
//  USAGE(2),           0x94, 0x01,    //   Usage (My Computer) ; bit 0: 1
//  USAGE(2),           0x92, 0x01,    //   Usage (Calculator)  ; bit 1: 2
//  USAGE(2),           0x2A, 0x02,    //   Usage (WWW fav)     ; bit 2: 4
//  USAGE(2),           0x21, 0x02,    //   Usage (WWW search)  ; bit 3: 8
//  USAGE(2),           0x26, 0x02,    //   Usage (WWW stop)    ; bit 4: 16
//  USAGE(2),           0x24, 0x02,    //   Usage (WWW back)    ; bit 5: 32
//  USAGE(2),           0x83, 0x01,    //   Usage (Media sel)   ; bit 6: 64
//  USAGE(2),           0x8A, 0x01,    //   Usage (Mail)        ; bit 7: 128 
// --------------------
//  USAGE_MINIMUM(1),   0xFE,          //   USAGE_MINIMUM (0)
//  USAGE_MAXIMUM(1),   0x65,          //   USAGE_MAXIMUM (0x65)
  REPORT_SIZE(1),	0x01,	// hjhjhg
  REPORT_COUNT(1),	0x10,	// MY KEYS
  USAGE(1),           0x9C,          // 00  USAGE (Next chan) 
  USAGE(1),           0x9D,          // 01  USAGE (Prev chan)
  USAGE(1),           0xE9,          // 02  USAGE (UP vol)
  USAGE(1),           0xEA,          // 03  USAGE (DOWN vol)
  USAGE(2),           0x21, 0x02,         // 04  USAGE (Search)
  USAGE(2),           0x24, 0x02,          // 05  USAGE (Back)
  USAGE(2),           0x23, 0x02,         // 06  USAGE (Home)
  USAGE(1),           0x40,          // 07  USAGE (Menu)
  USAGE(1),           0x30,          // 08  USAGE (Power)
  USAGE(1),           0x69,          // 09  USAGE (Red)
  USAGE(1),           0x6A,          // 0A  USAGE (Green)
  USAGE(1),           0x6B,          // 0B  USAGE (Blue)
  USAGE(1),           0x6C,          // 0C  USAGE (Yellow)
  USAGE(1),           0x95,          // 0D  USAGE (Guide)
  USAGE(1),           0xCD,          // 0E  USAGE (Media Play/Pause)
  USAGE(2),           0x26, 0x02,         // 0F  USAGE (Media Stop)
  USAGE(1),           0xB4,          // 10  USAGE (Media REW)
  USAGE(1),           0xB3,          // 11  USAGE (Media FFW)
  USAGE(1),           0x41,          // 12  USAGE (OK)
  USAGE(1),           0x42,          // 13  USAGE (UP)
  USAGE(1),           0x43,          // 14  USAGE (DOWN)
  USAGE(1),           0x44,          // 15  USAGE (LEFT)
  USAGE(1),           0x45,          // 16  USAGE (RIGHT)
  USAGE(1),           0x60,          // 17  USAGE (INFO)
  USAGE(1),           0xE2,          // 18  USAGE (MUTE)
  HIDINPUT(1),        0x02,          //   INPUT (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
  END_COLLECTION(0)                  // END_COLLECTION
};


static const uint8_t _hidReportDescriptor[] = {
//# 0x05, 0x0c,                    // Usage Page (Consumer Devices)       0
USAGE_PAGE(1),      0x0C,
//# 0x09, 0x01,                    // Usage (Consumer Control)            2
USAGE(1),           0x01,
//# 0xa1, 0x01,                    // Collection (Application)            4
COLLECTION(1),      0x01,
//# 0x85, 0x01,                    //  Report ID (1)                      6
REPORT_ID(1),       MEDIA_KEYS_ID,
//# 0x19, 0x00,                    //  Usage Minimum (0)                  8
USAGE_MINIMUM(1),   0x00,
//# 0x2a, 0x9c, 0x02,              //  Usage Maximum (668)                10
USAGE_MAXIMUM(2),   0x9c,0x02,
//# 0x15, 0x00,                    //  Logical Minimum (0)                13
LOGICAL_MINIMUM(1), 0x00,
//# 0x26, 0x9c, 0x02,              //  Logical Maximum (668)              15
LOGICAL_MAXIMUM(2), 0x9c,0x02,
//# 0x95, 0x02,                    //  Report Count (2)                   18
REPORT_COUNT(1),	0x02,
//# 0x75, 0x10,                    //  Report Size (16)                   20
REPORT_SIZE(1),	0x10,
//# 0x81, 0x00,                    //  Input (Data,Arr,Abs)               22
HIDINPUT(1),        0x00,
//# 0x09, 0x02,                    //  Usage (Numeric Key Pad)            24
USAGE(1),	0x02,
//# 0xa1, 0x02,                    //  Collection (Logical)               26
COLLECTION(1),	0x02,
//# 0x05, 0x09,                    //   Usage Page (Button)               28
USAGE_PAGE(1),	0x09,
//# 0x19, 0x01,                    //   Usage Minimum (1)                 30
USAGE_MINIMUM(1),	0x01,
//# 0x29, 0x0a,                    //   Usage Maximum (10)                32
USAGE_MAXIMUM(1),	0x0a,
//# 0x15, 0x01,                    //   Logical Minimum (1)               34
LOGICAL_MINIMUM(1),	0x01,
//# 0x25, 0x0a,                    //   Logical Maximum (10)              36
LOGICAL_MAXIMUM(1),	0x0a,
//# 0x95, 0x01,                    //   Report Count (1)                  38
REPORT_COUNT(1),	0x01,
//# 0x75, 0x08,                    //   Report Size (8)                   40
REPORT_SIZE(1),	0x08,
//0x75, 0x08,
//# 0x81, 0x40,                    //   Input (Data,Arr,Abs,Null)         42
HIDINPUT(1),	0x40,
//# 0xc0,                          //  End Collection                     44
END_COLLECTION(0),
//# 0xc0,                          // End Collection                      45
END_COLLECTION(0),
//# 0x05, 0x01,                    // Usage Page (Generic Desktop)        46
USAGE_PAGE(1),	0x01,
//# 0x09, 0x02,                    // Usage (Mouse)                       48
USAGE(1),	0x02,
//# 0xa1, 0x01,                    // Collection (Application)            50
COLLECTION(1),	0x01,
//# 0x09, 0x01,                    //  Usage (Pointer)                    52
USAGE(1),	0x01,
//# 0xa1, 0x00,                    //  Collection (Physical)              54
COLLECTION(1),	0x00,
//# 0x85, 0x03,                    //   Report ID (3)                     56
REPORT_ID(1),	MOUSE_ID,
//# 0x05, 0x01,                    //   Usage Page (Generic Desktop)      58
USAGE_PAGE(1),	0x01,
//# 0x09, 0x30,                    //   Usage (X)                         60
USAGE(1),	0x30,
//# 0x09, 0x31,                    //   Usage (Y)                         62
USAGE(1),	0x31,
//# 0x15, 0x80,                    //   Logical Minimum (-128)            64
LOGICAL_MINIMUM(1),	0x80,
//# 0x25, 0x7f,                    //   Logical Maximum (127)             66
LOGICAL_MAXIMUM(1),	0x7f,
//# 0x75, 0x08,                    //   Report Size (8)                   68
REPORT_SIZE(1),	0x08,
//# 0x95, 0x02,                    //   Report Count (2)                  70
REPORT_COUNT(1),	0x02,
//# 0x81, 0x06,                    //   Input (Data,Var,Rel)              72
HIDINPUT(1),	0x06,
//# 0x05, 0x09,                    //   Usage Page (Button)               74
USAGE_PAGE(1),	0x09,
//# 0x19, 0x01,                    //   Usage Minimum (1)                 76
USAGE_MINIMUM(1),	0x01,
//# 0x29, 0x05,                    //   Usage Maximum (5)                 78
USAGE_MAXIMUM(1),	0x05,
//# 0x15, 0x00,                    //   Logical Minimum (0)               80
LOGICAL_MINIMUM(1),	0x00,
//# 0x25, 0x01,                    //   Logical Maximum (1)               82
LOGICAL_MAXIMUM(1),	0x01,
//# 0x95, 0x05,                    //   Report Count (5)                  84
REPORT_COUNT(1),	0x05,
//# 0x75, 0x01,                    //   Report Size (1)                   86
REPORT_SIZE(1),	0x01,
//# 0x81, 0x02,                    //   Input (Data,Var,Abs)              88
HIDINPUT(1),	0x02,
//# 0x95, 0x01,                    //   Report Count (1)                  90
REPORT_COUNT(1),	0x01,
//# 0x75, 0x03,                    //   Report Size (3)                   92
REPORT_SIZE(1),	0x03,
//# 0x81, 0x03,                    //   Input (Cnst,Var,Abs)              94
HIDINPUT(1),	0x03,
//# 0xc0,                          //  End Collection                     96
END_COLLECTION(0),
//# 0xc0,                          // End Collection                      97
END_COLLECTION(0),
//# 0x06, 0x01, 0xff,              // Usage Page (Vendor Usage Page 0xff01) 98
USAGE_PAGE(2),	0x01,0xff,
//# 0x09, 0x02,                    // Usage (Vendor Usage 0x02)           101
USAGE(1),	0x02,
//# 0xa1, 0x02,                    // Collection (Logical)                103
COLLECTION(1),	0x02,
//# 0x85, 0x02,                    //  Report ID (2)                      105
REPORT_ID(1),	0x02,
//# 0x09, 0x14,                    //  Usage (Vendor Usage 0x14)          107
USAGE(1),	0x14,
//# 0x75, 0x08,                    //  Report Size (8)                    109
REPORT_SIZE(1),	0x08,
//# 0x95, 0x14,                    //  Report Count (20)                  111
REPORT_COUNT(1),	0x14,
//# 0x15, 0x80,                    //  Logical Minimum (-128)             113
LOGICAL_MINIMUM(1),	0x80,
//# 0x25, 0x7f,                    //  Logical Maximum (127)              115
LOGICAL_MAXIMUM(1),	0x7f,
//# 0x81, 0x22,                    //  Input (Data,Var,Abs,NoPref)        117
HIDINPUT(1),	0x22,
//# 0x85, 0x04,                    //  Report ID (4)                      119
REPORT_ID(1),	0x04,
//# 0x09, 0x04,                    //  Usage (Vendor Usage 0x04)          121
USAGE(1),	0x04,
//# 0x75, 0x08,                    //  Report Size (8)                    123
REPORT_SIZE(1),	0x08,
//0x75, 0x08,
//# 0x95, 0x01,                    //  Report Count (1)                   125
REPORT_COUNT(1),	0x01,
//# 0x91, 0x02,                    //  Output (Data,Var,Abs)              127
HIDOUTPUT(1),	0x02,
//# 0xc0,                          // End Collection                      129
END_COLLECTION(0),
//# 0x05, 0x01,                    // Usage Page (Generic Desktop)        130
USAGE_PAGE(1),	0x01,
//# 0x09, 0x06,                    // Usage (Keyboard)                    132
USAGE(1),	0x06,
//# 0xa1, 0x01,                    // Collection (Application)            134
COLLECTION(1),	0x01,
//# 0x85, 0x07,                    //  Report ID (7)                      136
REPORT_ID(1),	KEYBOARD_ID,
//# 0x05, 0x07,                    //  Usage Page (Keyboard)              138
USAGE_PAGE(1),	0x07,
//# 0x19, 0xe0,                    //  Usage Minimum (224)                140
USAGE_MINIMUM(1),	0xe0,
//# 0x29, 0xe7,                    //  Usage Maximum (231)                142
USAGE_MAXIMUM(1),	0xe7,
//# 0x15, 0x00,                    //  Logical Minimum (0)                144
LOGICAL_MINIMUM(1),	0x00,
//# 0x25, 0x01,                    //  Logical Maximum (1)                146
LOGICAL_MAXIMUM(1),	0x01,
//# 0x75, 0x01,                    //  Report Size (1)                    148
REPORT_SIZE(1),	0x01,
//# 0x95, 0x08,                    //  Report Count (8)                   150
REPORT_COUNT(1),	0x08,
//# 0x81, 0x02,                    //  Input (Data,Var,Abs)               152
HIDINPUT(1),	0x02,
//# 0x85, 0x08,                    //  Report ID (8)                      154
REPORT_ID(1),	0x08,
//# 0x95, 0x05,                    //  Report Count (5)                   156
REPORT_COUNT(1),	0x05,
//# 0x75, 0x01,                    //  Report Size (1)                    158
REPORT_SIZE(1),	0x01,
//# 0x05, 0x08,                    //  Usage Page (LEDs)                  160
USAGE_PAGE(1),	0x08,
//# 0x19, 0x01,                    //  Usage Minimum (1)                  162
USAGE_MINIMUM(1),	0x01,
//# 0x29, 0x05,                    //  Usage Maximum (5)                  164
USAGE_MAXIMUM(1),	0x05,
//# 0x91, 0x02,                    //  Output (Data,Var,Abs)              166
HIDOUTPUT(1),	0x02,
//# 0x95, 0x01,                    //  Report Count (1)                   168
REPORT_COUNT(1),	0x01,
//# 0x75, 0x03,                    //  Report Size (3)                    170
REPORT_SIZE(1),	0x03,
//# 0x91, 0x03,                    //  Output (Cnst,Var,Abs)              172
HIDOUTPUT(1),	0x03,
//# 0x85, 0x07,                    //  Report ID (7)                      174
REPORT_ID(1),	0x07,
//# 0x95, 0x06,                    //  Report Count (6)                   176
REPORT_COUNT(1),	0x06,
//# 0x75, 0x08,                    //  Report Size (8)                    178
REPORT_SIZE(1),	0x08,
//# 0x15, 0x00,                    //  Logical Minimum (0)                180
LOGICAL_MINIMUM(1),	0x00,
//# 0x26, 0xe7, 0x00,              //  Logical Maximum (231)              182
LOGICAL_MAXIMUM(2),	0xe7,0x00,
//# 0x05, 0x07,                    //  Usage Page (Keyboard)              185
USAGE_PAGE(1),	0x07,
//# 0x19, 0x00,                    //  Usage Minimum (0)                  187
USAGE_MINIMUM(1),	0x00,
//# 0x29, 0xe7,                    //  Usage Maximum (231)                189
USAGE_MAXIMUM(1),	0xe7,
//# 0x81, 0x00,                    //  Input (Data,Arr,Abs)               191
HIDINPUT(1),	0x00,
//# 0xc0,                          // End Collection                      193
END_COLLECTION(0)
};

BleRcAtv::BleRcAtv(std::string deviceName, std::string deviceManufacturer, uint8_t batteryLevel) 
    : hid(0)
    , deviceName(std::string(deviceName).substr(0, 15))
    , deviceManufacturer(std::string(deviceManufacturer).substr(0,15))
    , batteryLevel(batteryLevel) {}

void BleRcAtv::begin(void)
{
  BLEDevice::init(deviceName);
  BLEServer* pServer = BLEDevice::createServer();
  pServer->setCallbacks(this);

  hid = new BLEHIDDevice(pServer);
  inputKeyboard = hid->inputReport(KEYBOARD_ID);  // <-- input REPORTID from report map
  //outputKeyboard = hid->outputReport(KEYBOARD_ID);
  outputKeyboard = hid->outputReport(KEYBOARD_LEDS_ID);
  inputMediaKeys = hid->inputReport(MEDIA_KEYS_ID);
  inputMouse = hid->inputReport(MOUSE_ID);
  inputVendor = hid->inputReport(VENDOR_02);
  outputVendor = hid->outputReport(VENDOR_04);
  

  outputKeyboard->setCallbacks(this);

  hid->manufacturer()->setValue(deviceManufacturer);

  hid->pnp(0x02, vid, pid, version);
  hid->hidInfo(0x00, 0x01);


#if defined(USE_NIMBLE)

  BLEDevice::setSecurityAuth(true, true, true);

#else

  BLESecurity* pSecurity = new BLESecurity();
  pSecurity->setAuthenticationMode(ESP_LE_AUTH_REQ_SC_MITM_BOND);

#endif // USE_NIMBLE

  hid->reportMap((uint8_t*)_hidReportDescriptor, sizeof(_hidReportDescriptor));
  hid->startServices();

  onStarted(pServer);

  advertising = pServer->getAdvertising();
  advertising->setAppearance(HID_KEYBOARD);
  advertising->addServiceUUID(hid->hidService()->getUUID());
  advertising->setScanResponse(false);
  advertising->start();
  hid->setBatteryLevel(batteryLevel);

  ESP_LOGD(LOG_TAG, "Advertising started!");
}

void BleRcAtv::end(void)
{
}

bool BleRcAtv::isConnected(void) {
  return this->connected;
}

void BleRcAtv::setBatteryLevel(uint8_t level) {
  this->batteryLevel = level;
  if (hid != 0)
    this->hid->setBatteryLevel(this->batteryLevel);
}

//must be called before begin in order to set the name
void BleRcAtv::setName(std::string deviceName) {
  this->deviceName = deviceName;
}

/**
 * @brief Sets the waiting time (in milliseconds) between multiple keystrokes in NimBLE mode.
 * 
 * @param ms Time in milliseconds
 */
void BleRcAtv::setDelay(uint32_t ms) {
  this->_delay_ms = ms;
}

void BleRcAtv::set_vendor_id(uint16_t vid) { 
	this->vid = vid; 
}

void BleRcAtv::set_product_id(uint16_t pid) { 
	this->pid = pid; 
}

void BleRcAtv::set_version(uint16_t version) { 
	this->version = version; 
}

void BleRcAtv::sendReport(KeyReport* keys)
{
  if (this->isConnected())
  {
    this->inputKeyboard->setValue((uint8_t*)keys, sizeof(KeyReport));
    this->inputKeyboard->notify();
#if defined(USE_NIMBLE)        
    // vTaskDelay(delayTicks);
    this->delay_ms(_delay_ms);
#endif // USE_NIMBLE
  }	
}

void BleRcAtv::sendReport(MediaKeyReport* keys)
{
  if (this->isConnected())
  {
    this->inputMediaKeys->setValue((uint8_t*)keys, sizeof(MediaKeyReport));
    this->inputMediaKeys->notify();
#if defined(USE_NIMBLE)        
    //vTaskDelay(delayTicks);
    this->delay_ms(_delay_ms);
#endif // USE_NIMBLE
  }	
}

extern
const uint8_t _asciimap[128] PROGMEM;

#define SHIFT 0x80
const uint8_t _asciimap[128] =
{
	0x00,             // NUL
	0x00,             // SOH
	0x00,             // STX
	0x00,             // ETX
	0x00,             // EOT
	0x00,             // ENQ
	0x00,             // ACK
	0x00,             // BEL
	0x2a,			// BS	Backspace
	0x2b,			// TAB	Tab
	0x28,			// LF	Enter
	0x00,             // VT
	0x00,             // FF
	0x00,             // CR
	0x00,             // SO
	0x00,             // SI
	0x00,             // DEL
	0x00,             // DC1
	0x00,             // DC2
	0x00,             // DC3
	0x00,             // DC4
	0x00,             // NAK
	0x00,             // SYN
	0x00,             // ETB
	0x00,             // CAN
	0x00,             // EM
	0x00,             // SUB
	0x00,             // ESC
	0x00,             // FS
	0x00,             // GS
	0x00,             // RS
	0x00,             // US

	0x2c,		   //  ' '
	0x1e|SHIFT,	   // !
	0x34|SHIFT,	   // "
	0x20|SHIFT,    // #
	0x21|SHIFT,    // $
	0x22|SHIFT,    // %
	0x24|SHIFT,    // &
	0x34,          // '
	0x26|SHIFT,    // (
	0x27|SHIFT,    // )
	0x25|SHIFT,    // *
	0x2e|SHIFT,    // +
	0x36,          // ,
	0x2d,          // -
	0x37,          // .
	0x38,          // /
	0x27,          // 0
	0x1e,          // 1
	0x1f,          // 2
	0x20,          // 3
	0x21,          // 4
	0x22,          // 5
	0x23,          // 6
	0x24,          // 7
	0x25,          // 8
	0x26,          // 9
	0x33|SHIFT,      // :
	0x33,          // ;
	0x36|SHIFT,      // <
	0x2e,          // =
	0x37|SHIFT,      // >
	0x38|SHIFT,      // ?
	0x1f|SHIFT,      // @
	0x04|SHIFT,      // A
	0x05|SHIFT,      // B
	0x06|SHIFT,      // C
	0x07|SHIFT,      // D
	0x08|SHIFT,      // E
	0x09|SHIFT,      // F
	0x0a|SHIFT,      // G
	0x0b|SHIFT,      // H
	0x0c|SHIFT,      // I
	0x0d|SHIFT,      // J
	0x0e|SHIFT,      // K
	0x0f|SHIFT,      // L
	0x10|SHIFT,      // M
	0x11|SHIFT,      // N
	0x12|SHIFT,      // O
	0x13|SHIFT,      // P
	0x14|SHIFT,      // Q
	0x15|SHIFT,      // R
	0x16|SHIFT,      // S
	0x17|SHIFT,      // T
	0x18|SHIFT,      // U
	0x19|SHIFT,      // V
	0x1a|SHIFT,      // W
	0x1b|SHIFT,      // X
	0x1c|SHIFT,      // Y
	0x1d|SHIFT,      // Z
	0x2f,          // [
	0x31,          // bslash
	0x30,          // ]
	0x23|SHIFT,    // ^
	0x2d|SHIFT,    // _
	0x35,          // `
	0x04,          // a
	0x05,          // b
	0x06,          // c
	0x07,          // d
	0x08,          // e
	0x09,          // f
	0x0a,          // g
	0x0b,          // h
	0x0c,          // i
	0x0d,          // j
	0x0e,          // k
	0x0f,          // l
	0x10,          // m
	0x11,          // n
	0x12,          // o
	0x13,          // p
	0x14,          // q
	0x15,          // r
	0x16,          // s
	0x17,          // t
	0x18,          // u
	0x19,          // v
	0x1a,          // w
	0x1b,          // x
	0x1c,          // y
	0x1d,          // z
	0x2f|SHIFT,    // {
	0x31|SHIFT,    // |
	0x30|SHIFT,    // }
	0x35|SHIFT,    // ~
	0				// DEL
};


uint8_t USBPutChar(uint8_t c);

// press() adds the specified key (printing, non-printing, or modifier)
// to the persistent key report and sends the report.  Because of the way
// USB HID works, the host acts like the key remains pressed until we
// call release(), releaseAll(), or otherwise clear the report and resend.
size_t BleRcAtv::press(uint8_t k)
{
	uint8_t i;
	if (k >= 136) {			// it's a non-printing key (not a modifier)
		k = k - 136;
	} else if (k >= 128) {	// it's a modifier key
		_keyReport.modifiers |= (1<<(k-128));
		k = 0;
	} else {				// it's a printing key
		k = pgm_read_byte(_asciimap + k);
		if (!k) {
			setWriteError();
			return 0;
		}
		if (k & 0x80) {						// it's a capital letter or other character reached with shift
			_keyReport.modifiers |= 0x02;	// the left shift modifier
			k &= 0x7F;
		}
	}

	// Add k to the key report only if it's not already present
	// and if there is an empty slot.
	if (_keyReport.keys[0] != k && _keyReport.keys[1] != k &&
		_keyReport.keys[2] != k && _keyReport.keys[3] != k &&
		_keyReport.keys[4] != k && _keyReport.keys[5] != k) {

		for (i=0; i<6; i++) {
			if (_keyReport.keys[i] == 0x00) {
				_keyReport.keys[i] = k;
				break;
			}
		}
		if (i == 6) {
			setWriteError();
			return 0;
		}
	}
	sendReport(&_keyReport);
	return 1;
}

size_t BleRcAtv::press(const MediaKeyReport k)
{
    uint16_t k_16 = k[1] | (k[0] << 8);
    uint16_t mediaKeyReport_16 = _mediaKeyReport[1] | (_mediaKeyReport[0] << 8);

    mediaKeyReport_16 |= k_16;
    _mediaKeyReport[0] = (uint8_t)((mediaKeyReport_16 & 0xFF00) >> 8);
    _mediaKeyReport[1] = (uint8_t)(mediaKeyReport_16 & 0x00FF);

	sendReport(&_mediaKeyReport);
	return 1;
}

// release() takes the specified key out of the persistent key report and
// sends the report.  This tells the OS the key is no longer pressed and that
// it shouldn't be repeated any more.
size_t BleRcAtv::release(uint8_t k)
{
	uint8_t i;
	if (k >= 136) {			// it's a non-printing key (not a modifier)
		k = k - 136;
	} else if (k >= 128) {	// it's a modifier key
		_keyReport.modifiers &= ~(1<<(k-128));
		k = 0;
	} else {				// it's a printing key
		k = pgm_read_byte(_asciimap + k);
		if (!k) {
			return 0;
		}
		if (k & 0x80) {							// it's a capital letter or other character reached with shift
			_keyReport.modifiers &= ~(0x02);	// the left shift modifier
			k &= 0x7F;
		}
	}

	// Test the key report to see if k is present.  Clear it if it exists.
	// Check all positions in case the key is present more than once (which it shouldn't be)
	for (i=0; i<6; i++) {
		if (0 != k && _keyReport.keys[i] == k) {
			_keyReport.keys[i] = 0x00;
		}
	}

	sendReport(&_keyReport);
	return 1;
}

size_t BleRcAtv::release(const MediaKeyReport k)
{
    uint16_t k_16 = k[1] | (k[0] << 8);
    uint16_t mediaKeyReport_16 = _mediaKeyReport[1] | (_mediaKeyReport[0] << 8);
    mediaKeyReport_16 &= ~k_16;
    _mediaKeyReport[0] = (uint8_t)((mediaKeyReport_16 & 0xFF00) >> 8);
    _mediaKeyReport[1] = (uint8_t)(mediaKeyReport_16 & 0x00FF);

	sendReport(&_mediaKeyReport);
	return 1;
}

void BleRcAtv::releaseAll(void)
{
	_keyReport.keys[0] = 0;
	_keyReport.keys[1] = 0;
	_keyReport.keys[2] = 0;
	_keyReport.keys[3] = 0;
	_keyReport.keys[4] = 0;
	_keyReport.keys[5] = 0;
	_keyReport.modifiers = 0;
    _mediaKeyReport[0] = 0;
    _mediaKeyReport[1] = 0;
	sendReport(&_keyReport);
	sendReport(&_mediaKeyReport);
}

size_t BleRcAtv::write(uint8_t c)
{
	uint8_t p = press(c);  // Keydown
	release(c);            // Keyup
	return p;              // just return the result of press() since release() almost always returns 1
}

size_t BleRcAtv::write(const MediaKeyReport c)
{
	uint16_t p = press(c);  // Keydown
	release(c);            // Keyup
	return p;              // just return the result of press() since release() almost always returns 1
}

size_t BleRcAtv::write(const uint8_t *buffer, size_t size) {
	size_t n = 0;
	while (size--) {
		if (*buffer != '\r') {
			if (write(*buffer)) {
			  n++;
			} else {
			  break;
			}
		}
		buffer++;
	}
	return n;
}

void BleRcAtv::onConnect(BLEServer* pServer) {
  this->connected = true;

#if !defined(USE_NIMBLE)

  BLE2902* desc = (BLE2902*)this->inputKeyboard->getDescriptorByUUID(BLEUUID((uint16_t)0x2902));
  desc->setNotifications(true);
  desc = (BLE2902*)this->inputMediaKeys->getDescriptorByUUID(BLEUUID((uint16_t)0x2902));
  desc->setNotifications(true);

#endif // !USE_NIMBLE

}

void BleRcAtv::onDisconnect(BLEServer* pServer) {
  this->connected = false;

#if !defined(USE_NIMBLE)

  BLE2902* desc = (BLE2902*)this->inputKeyboard->getDescriptorByUUID(BLEUUID((uint16_t)0x2902));
  desc->setNotifications(false);
  desc = (BLE2902*)this->inputMediaKeys->getDescriptorByUUID(BLEUUID((uint16_t)0x2902));
  desc->setNotifications(false);

  advertising->start();

#endif // !USE_NIMBLE
}

void BleRcAtv::onWrite(BLECharacteristic* me) {
  uint8_t* value = (uint8_t*)(me->getValue().c_str());
  (void)value;
  ESP_LOGI(LOG_TAG, "special keys: %d", *value);
}

void BleRcAtv::delay_ms(uint64_t ms) {
  uint64_t m = esp_timer_get_time();
  if(ms){
    uint64_t e = (m + (ms * 1000));
    if(m > e){ //overflow
        while(esp_timer_get_time() > e) { }
    }
    while(esp_timer_get_time() < e) {}
  }
}

Начать надо со схемы девайса, а уж потом код. А то может у повторителя и деталей то не будет для сборки. Пульт управления телевизором предполагает, что питание его от батареек. Если так, то вы проверяли, сколько времени один комплект батареек будет работать на вашем пульте до замены?

Исходя из количества проектов, которые работают по ИК, то схема и питание ничем не отличается. И тут больше код интересен, а именно библиотека для полной имитации bluetooth пульта телевизора.

Тем не менее, схема и список частей обязательны для этого раздела

Сразу отвечу на возможный следующий вопрос - если вам не нравятся правила, вас никто не заставляет выкладывать проекты.
Боюсь, что в таком виде - несколько немых портянок кода без всяких пояснений - ваш проект вряд ли хоть кого-то заинтересует.

3 лайка

Т.е. пульт, который работает в УДЯ с Алисой вы назвали немытой портянкой?
Вы бы форум сделали удобным, а то кусок EGW дернули и рады.

Ну сотню раз наверно можно будет через ESP попереключать каналы на одном комплекте батареек. Отправляя её в глубокий сон после отпускания кнопки…

Пульт данный делал для замены Яндекс пульта, поскольку нужна была обратная связь в Алису. Если wroom поставить в такой пульт, то можно отслеживать состояние ТВ.

И еще один момент. Я перелопатил тонну документации по HID устройствам и сделал прототип библиотеки. Это полноценный пульт, который даже невозможно имитировать в Home Assistant.

А какова цель отслеживания?

Чтоб ТВ за тобой не следил

1 лайк

У ИК пульта команда включения и выключения одинаковые. Поэтому включенный телевизор можно выключить командой включи.

А у кого есть интерес, то может управлять несколькими телевизорами.

А вы точно правильно прочитали? :wink:

@Diskless, всё-таки сложно представить проект только из одного кода. Хоть какое-нибудь описание нужно. Какую схему, из каких железок я должен собрать, чтобы в них этот код залить и он работал. И главное - что оно делать-то будет? Может мне оно очень нужно, а может и совсем нет - сейчас я даже не могу понять.

  1. Код я посмотрел. К сожалению это не готовый проект, а скорее начало разработки.
  2. ТС! Ты бы написал сценарий:

есть ТВ, который управляется по БТ, на ЕСП32 я поднимаю Веб-сервер Ай-Пи сервер и БТ сервер, через них можно получать команды и транслировать их на ТВ. Я хотел приделать кусок для ТВ с управлением по ИР, но что-то мне помешало. Ай-Пи кусок для интеграции с Алисой я недоделал. MQTT даже не думал приделывать. БТ целиком стащил с кода, который предлагает ЧатЖПТ. В итоге я криво склепал Веб-сервер и примотал к нему синей изолентой БТ hid устройство для отправки команд на ТВ.

  1. Похвалите же ТС все! :wink:

Извини, я не критикую обычно код коллег. Но тебе стоит поработать над кодом реально. Важно нормально (я без сарказма) написать сценарий использования и прокомментировать код.

Если вы не задумывались над тем, что нет пультов управления телевизорами по bluetooth для умного дома, и не пытались реализовать исходя хотя бы из примеров кода для esp32 (клавиатура и мышка), то скорее всего Вам это будет не интересно.

Оказалось, что надо немного усилий, чтобы сделать полноценный пульт. Основная часть это профиль устройства, который должен понять телевизор. Я его достал через подключение пульта к компьютеру и “слизал” через /sys/ в linux. Декодировал и заменил имеющийся в примере esp32 (клавиатура). В коде это видно.
Потом через event получил коды кнопок пульта. Поскольку у меня был пульт от kivi телевизора, то часть кодов не заработало с LG WebOs телевизором, хотя многие сразу заработали. Попутно выяснил проблему с оригинальным пультом LG Magick. Оказалось, что телевизор в него “кидает” код, чтобы пульт с ИК переходил на bluetooth. Для дальнейшего исследования добавил WEB интерфейс в код, чтобы передавать любой двухбайтный код, который выдают пульты, чтобы поддержать больше кнопок.
Есть еще один тонкий момент - это каналы пульта. В моем коде это отражено.

Это был один из вариантов управления. Но он тупиковый, хотя вполне рабочий.

У меня есть mqtt, но он в другом устройстве и общение через udp broadcast.

Я не убирал исходные строки, чтобы оставить историю правок.

P.S. Сейчас у меня есть другой пульт на esp8266, который без bluetooth, но управляет практически так же.