├── .gitignore ├── platformio.ini ├── LICENSE ├── README.md └── src └── esproj.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | .pioenvs 2 | .piolibdeps 3 | .pio 4 | *.sublime-project 5 | -------------------------------------------------------------------------------- /platformio.ini: -------------------------------------------------------------------------------- 1 | ; PlatformIO Project Configuration File 2 | ; 3 | ; Build options: build flags, source filter 4 | ; Upload options: custom upload port, speed and extra flags 5 | ; Library options: dependencies, extra library storages 6 | ; Advanced options: extra scripting 7 | ; 8 | ; Please visit documentation for the other options and examples 9 | ; http://docs.platformio.org/page/projectconf.html 10 | 11 | [platformio] 12 | ;src_dir = espbtc 13 | 14 | [env:d1_mini] 15 | platform = espressif8266 16 | board = d1_mini 17 | framework = arduino 18 | monitor_speed = 115200 19 | upload_speed = 460800 20 | lib_deps = Fauxmoesp, IRremoteESP8266, ESPAsyncTCP 21 | targets= upload, monitor 22 | build_flags = -g -DDEBUG_FAUXMO=Serial -DDEBUG_FAUXMO_VERBOSE_TCP=0 -DDEBUG_FAUXMO_VERBOSE_UDP=0 -DPIO_FRAMEWORK_ARDUINO_LWIP_HIGHER_BANDWIDTH 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 F 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ESProjector 2 | Voice control your Panasonic projector with an esp8266 (Alexa compatible) and control sources on an 4x1 HDMI switch 3 | 4 | ## The problem at hand 5 | My projector is on a high closet, which also houses the media stuff and HDMI cables. Since they're behind a door and I don't want to setup an expensive universal remote or leave the door open for IR to work, I wanted a reliable way of turning the projector on and off, and also switching video sources. Most crucially, searching for remotes is annoying in the dark. 6 | I decided I would find a way to voice control those functions for more convenience. 7 | 8 | Thankfully I found out about [fauxmoesp] which emulates a wemo device with on/off control, requiring little setup/piping and no extra servers/accounts to get it to work. (unlike other solutions I have seen through IFTTT and such) 9 | 10 | ## Requirements 11 | (Links are just examples for what I have ordered) 12 | * Panasonic PT-AE3000U projector (might work on other Panasonic models if they use the same protocol) [Projector manual][controlspec] 13 | * Monoprice [HDX-401TA][hdmiswitch] 4x1 HDMI switch 14 | * ESP8266: [D1 mini][d1mini] [wiki] 15 | * MAX232-based level shifter [board][rs232] 16 | * [IR led module][irmodule]: 5V powered, 3.3V data, built in transistor for high-power driving of the LED and a visible blue LED to see transmit activity. Handy. 17 | * [TSOP38238][tsop]: 38kHz IR receiver, for learning codes. Not used in final project. 18 | * wire, soldering iron, box, the usual 19 | 20 | ## Software 21 | * [platformIO core][piocore] 22 | * Arduino esp8266 framework (latest version) 23 | * [fauxmoesp] 24 | * [IRremoteESP8266][irremote] (I used `IRrecvDumpV2` to get the IR codes) 25 | 26 | Please ignore the pretty ugly `for` loops for writing the bytes, it worked. I may revisit the source to clean it up a bit sometime. 27 | You can probably use the Arduino IDE for building, assuming you git clone the libraries manually to your machine. platformio takes care of all of that for you. 28 | 29 | ### Projector commands 30 | `STX` and `ETX`, Ctrl+B and Ctrl+C respectively, are used to show message start and end, and the rest is ASCII uppercase chars. Pretty straightforward from the manual. I actually used "menu" keypresses to test stuff repeatedly without having to turn it on and off. 31 | * ON : `char pon[] = {0x2, 'P', 'O', 'N', 0x3};` 32 | * OFF: `char poff[] = {0x2, 'P', 'O', 'F', 0x3};` 33 | 34 | I don't really bother checking the return bytes from the projector since I can hear it powering up or down. 35 | 36 | ### Monoprice Switcher commands 37 | They are all NEC 32 bytes codes, easy once you know the actual value to use. All the values are in the code as `#define` if you need them. I originally tried to use my Intel NUC PC's IR receiver to get the raw codes but that didn't go anywhere so I ordered the TSOP chip. 38 | 39 | ## Hardware 40 | ### MAX232 41 | - MAX232 board pictured in a little project board, next to the upside-down esp8266. The annoying part is that it that the MAX232 breakout was wired opposite to what I thought it should be which caused a lot of head scratching (projector control was working sending bytes straight from a PC but not from the ESP board) Once I swapped RX/TX wires it all worked. Always double check mystery breakfout boards! 42 | 43 | ### IR 44 | - Setup I used to learn codes and make sure the LED module worked (the remote is the one I learned codes from) 45 | 46 | ![mvimg_20171222_112703](https://user-images.githubusercontent.com/11471500/34812212-94f7a148-f659-11e7-8f24-75ba53918ac6.jpg) 47 | 48 | - The IR led module is at the end of a ~60 cm long wire, to route it where it's needed. I was worried of signal integrity, but it's low-ish frequency (38kHz) and works very reliably inside the closed cabinet. 49 | 50 | ![mvimg_20180105_011141](https://user-images.githubusercontent.com/11471500/34757449-188e5d96-f586-11e7-9d6b-f21c1c85773b.jpg) 51 | 52 | - IR emitter aimed at the HDMI switch. Blue tape is to dim the brighter-than-a-thousand-suns blue leds, and makes for clearer labelling. 53 | 54 | ![mvimg_20180105_101719](https://user-images.githubusercontent.com/11471500/34757745-2ae277c8-f588-11e7-8af2-44cf7f94f5cf.jpg) 55 | 56 | ### Integration 57 | 58 | - Full setup. Hardware is next to the NUC (powered by it and serial can be monitored/firware reflashed), switch is above it. All wiring goes behind the shelf, and through a hole in the top to the projector. 59 | 60 | ![img_20180110_223709](https://user-images.githubusercontent.com/11471500/34811732-288416a6-f657-11e7-8e77-c1b21e1a309d.jpg) 61 | 62 | ### Extra ground pin 63 | I used two "gadget boards" so I was short one ground pin and didn't want to make a perf board just for that. I just added an extra ground pin to the D1 mini with a 90 degree bend on it. Works like a charm. 64 | 65 | ![img_20180105_010757](https://user-images.githubusercontent.com/11471500/34757563-ed5fc636-f586-11e7-976f-eda03e1e835f.jpg) 66 | 67 | [wiki]: https://wiki.wemos.cc/products:d1:d1_mini 68 | [piocore]: http://platformio.org/get-started/cli 69 | [controlspec]: http://pdfstream.manualsonline.com/9/9176fcb0-11f1-412d-8ffe-7b7810664a2b.pdf 70 | [irremote]: https://github.com/markszabo/IRremoteESP8266 71 | [fauxmoesp]: https://bitbucket.org/xoseperez/fauxmoesp 72 | [hdmiswitch]: https://www.monoprice.com/product?p_id=5557 73 | [rs232]: https://www.ebay.com/itm/253052504776 74 | [d1mini]: https://www.ebay.com/itm/172646774462 75 | [irmodule]: https://www.ebay.com/itm/132243573356 76 | [tsop]: https://www.ebay.com/itm/292101297804 77 | -------------------------------------------------------------------------------- /src/esproj.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | // Wifi parameters 9 | 10 | char passphrase[] = "***"; 11 | char ssid[] = "***"; 12 | 13 | typedef enum 14 | { 15 | DEV_PROJ = 0, 16 | DEV_CHROMECAST, 17 | DEV_FIRETV, 18 | DEV_CONSOLE, 19 | DEV_PC, 20 | DEV_MAX, 21 | } device_type_t; 22 | 23 | 24 | /* Monoprice HDX-401TA remote codes (NEC, 32 bits) */ 25 | #define SWITCH_IR_1 (0xFF10EF) //chromecast 26 | #define SWITCH_IR_2 (0xFF50AF) //firetv 27 | #define SWITCH_IR_3 (0xFF30CF) //console 28 | #define SWITCH_IR_4 (0xFF708F) //PC 29 | #define SWITCH_IR_MUTE (0xFF40BF) 30 | #define SWITCH_IR_LEFT (0xFF08F7) 31 | #define SWITCH_IR_RIGHT (0xFF48B7) 32 | #define SWITCH_IR_2CH (0xFF18E7) 33 | #define SWITCH_IR_51CH (0xFF58A7) 34 | #define SWITCH_IR_POWER (0xFF00FF) 35 | 36 | // Pin assignments 37 | IRsend irsend(D7); // IR LED control 38 | #define PROJ_TX D8 //To projector 39 | #define PROJ_RX D5 //From projector. Unused. 40 | 41 | #define SERIAL_BAUDRATE 115200 42 | #define PROJ_BAUDRATE 9600 43 | #define LED_PIN 2 44 | 45 | fauxmoESP fauxmo; 46 | 47 | SoftwareSerial SerialP(PROJ_RX, PROJ_TX); // RX, TX 48 | 49 | char pon[] = {0x2, 'P', 'O', 'N', 0x3}; 50 | char poff[] = {0x2, 'P', 'O', 'F', 0x3}; 51 | char menu[] = {0x2, 'O', 'M', 'N', 0x3}; 52 | char back[] = {0x2, 'O', 'B', 'K', 0x3}; 53 | char qpw[] = {0x2, 'Q', 'P', 'W', 0x3}; // query power state 54 | 55 | uint32_t codes[] = {SWITCH_IR_1, SWITCH_IR_2, SWITCH_IR_3, SWITCH_IR_4}; 56 | 57 | void switch_control(unsigned char device) { 58 | device--; 59 | Serial.printf("%s: Setting Input %d: IR[0x%x]\n", __func__, device, codes[device]); 60 | irsend.sendNEC(codes[device], 32); 61 | delay(500); 62 | Serial.print("Sending AUDIO 2CH keypress\n"); 63 | irsend.sendNEC(SWITCH_IR_2CH, 32); //Force stereo 64 | } 65 | 66 | 67 | void wifiSetup() { 68 | // Set WIFI module to STA mode 69 | WiFi.mode(WIFI_STA); 70 | 71 | // Connect 72 | Serial.printf("[WIFI] Connecting to %s ", ssid); 73 | WiFi.begin(ssid, passphrase); 74 | 75 | // Wait 76 | while (WiFi.status() != WL_CONNECTED) { 77 | Serial.print("."); 78 | delay(200); 79 | } 80 | Serial.println(); 81 | 82 | // Connected! 83 | Serial.printf("[WIFI] STATION Mode, SSID: %s, IP address: %s\n", WiFi.SSID().c_str(), WiFi.localIP().toString().c_str()); 84 | 85 | } 86 | 87 | void setup() { 88 | 89 | // Init serial port and clean garbage 90 | Serial.begin(SERIAL_BAUDRATE); 91 | Serial.println(); 92 | Serial.println(); 93 | 94 | SerialP.begin(PROJ_BAUDRATE); 95 | SerialP.println(); 96 | SerialP.println(); 97 | 98 | // Wifi 99 | wifiSetup(); 100 | 101 | // LED 102 | pinMode(LED_PIN, OUTPUT); 103 | digitalWrite(LED_PIN, HIGH); 104 | 105 | // IR 106 | irsend.begin(); 107 | 108 | // Add virtual devices (order matters) 109 | fauxmo.addDevice("projector"); //Panasonic PT-AE3000U 110 | fauxmo.addDevice("chromecast"); 111 | fauxmo.addDevice("firetv"); // TRIGGERS input 1 112 | fauxmo.addDevice("console"); 113 | fauxmo.addDevice("pc"); // TRIGGERS input 3 114 | 115 | fauxmo.setPort(80); //gen3 devices 116 | fauxmo.enable(true); 117 | 118 | fauxmo.onSetState([](unsigned char device_id, const char * device_name, bool state, unsigned char value) { 119 | 120 | Serial.printf("\n[MAIN] Device #%d (%s) state: %s\n", device_id, device_name, state ? "ON" : "OFF"); 121 | 122 | switch (device_id) { 123 | case DEV_PROJ: 124 | if (state) { 125 | for (int i = 0; i < 5; i++) { // clunky but works. 126 | SerialP.write(pon[i]); 127 | Serial.write(pon[i]); 128 | delay(300); 129 | } 130 | } else { 131 | for (int i = 0; i < 5; i++) { 132 | SerialP.write(poff[i]); 133 | Serial.write(poff[i]); 134 | delay(300); 135 | } 136 | } 137 | digitalWrite(LED_PIN, !state); 138 | break; 139 | case DEV_FIRETV: 140 | case DEV_CHROMECAST: 141 | case DEV_CONSOLE: 142 | case DEV_PC: 143 | if (state) 144 | switch_control(device_id); 145 | else 146 | irsend.sendNEC(SWITCH_IR_2, 32); /* Back to firetv whenever they're turned off*/ 147 | break; 148 | default: 149 | Serial.printf("Invalid/Unknown device."); 150 | } 151 | }); 152 | 153 | } 154 | 155 | void loop() { 156 | 157 | // fauxmoESP uses an async TCP server but a sync UDP server 158 | // Therefore, we have to manually poll for UDP packets 159 | fauxmo.handle(); 160 | 161 | if (SerialP.available()) { // If anything comes in SerialP (pins D1 & D2) 162 | Serial.write(SerialP.read()); // read it and send it out Serial (USB) 163 | } 164 | 165 | static unsigned long last = millis(); 166 | if (millis() - last > 5000) { 167 | last = millis(); 168 | //Serial.printf("[MAIN] Free heap: %d bytes\n", ESP.getFreeHeap()); 169 | Serial.printf("."); 170 | } 171 | 172 | } 173 | --------------------------------------------------------------------------------