├── sirius-tft-touch-add-on ├── materialdesignicons-webfont.ttf ├── sirius_docker_compose.yml ├── README.md ├── sirius-tft_esphome.yaml ├── sirius-tft-monitor.h ├── ha_dashboard_sirius_subview.yaml └── deskamp_package_add_ons.yaml ├── ir_codes ├── ir_remote_codes.ino └── README.md ├── src ├── Credentials.h ├── README.md ├── Settings.h └── arylic_amp.ino ├── homeassistant ├── README.md ├── dashboard.yaml └── deskamp_package.yaml ├── README.md └── LICENSE /sirius-tft-touch-add-on/materialdesignicons-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Resinchem/Arylic-Amp-MQTT/HEAD/sirius-tft-touch-add-on/materialdesignicons-webfont.ttf -------------------------------------------------------------------------------- /sirius-tft-touch-add-on/sirius_docker_compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.7' 2 | 3 | services: 4 | sxm: 5 | image: angellusmortis/sxm-player:latest 6 | restart: unless-stopped 7 | container_name: sxm 8 | environment: 9 | - SXM_USERNAME=your_sirius_user_name 10 | - SXM_PASSWORD=your_sirius_password 11 | ports: 12 | - 9000:9999 -------------------------------------------------------------------------------- /ir_codes/ir_remote_codes.ino: -------------------------------------------------------------------------------- 1 | // Use this sketch to capture IR code from a remote 2 | #include 3 | 4 | const int RECV_PIN = 14; 5 | IRrecv irrecv(RECV_PIN); 6 | decode_results results; 7 | 8 | void setup(){ 9 | Serial.begin(115200); 10 | irrecv.enableIRIn(); 11 | //irrecv.blink13(true); 12 | } 13 | 14 | void loop(){ 15 | if (irrecv.decode()){ 16 | Serial.print("IR Code Received: "); 17 | Serial.println(irrecv.decodedIRData.command); 18 | delay(250); 19 | irrecv.resume(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Credentials.h: -------------------------------------------------------------------------------- 1 | #define SID "your_ssid" // Your wifi SSID 2 | #define PW "your_password" // Your wifi password 3 | #define MQTTUSERNAME "mqtt_user" // MQTT user name 4 | #define MQTTPWD "mqtt_pw" // MQTT password 5 | #define MQTTSERVER "192.168.1.202" // IP Address (or url) of MQTT Broker. Use '0.0.0.0' if not enabling MQTT 6 | #define MQTTPORT 1883 // Port of MQTT Broker. This is usually 1883 7 | #define AP_SSID "myamp_ap" // Local AP to broadcast for WIFIMODE 0 or 2 (see Settings.h) (normally broadcasts on 192.168.4.1) 8 | #define AP_PWD "ap_password" // Password for local AP mode 9 | -------------------------------------------------------------------------------- /src/README.md: -------------------------------------------------------------------------------- 1 | ### Arduino Source Code 2 | 3 | These are the Arduino files needed for the project. Note that you need the desk_amp.ino file, plus the Credentials.h and Settings.h files. The Credentials.h file **must be edited** with your wifi and MQTT information. There are other requirements for the Arduino environment, including the ESP32 board add-on and other libraries. 4 | 5 | It is **HIGHLY RECOMMENDED** that you review the information in the [wiki pages](https:/Resinchem/Arylic-Amp-MQTT/wiki) carefully. Different firmware revisions of the amp, or different build options may require other modifications to the code. 6 | 7 | ### This code is being provided "as-is" and is meant to serve as a framework for your own project and not as a plug-and-play solution! 8 | 9 | I've provided as much documentation in the wiki and via comments in the code, but you should have some experience working with the Arduino code and should expect to make some modifications for your particular project. I will be happy to answer questions posted in the discussion area, but will not be addressing or fixing any issues reported for other amp versions or firmware... or for builds that differ from mine. 10 | -------------------------------------------------------------------------------- /ir_codes/README.md: -------------------------------------------------------------------------------- 1 | ### Determing a Remote's IR Codes 2 | 3 | If you want to use your own remote with the custom IR receiver in this project, you will need to know the IR codes sent by the remote. If you do not know these and don't have another method if determining these, you can easily create a simply IR Decoder to get the code associated with each button press. This should work with most IR remotes. 4 | 5 | #### Build the decoder 6 | ![IR Get Code Wiring](https://user-images.githubusercontent.com/55962781/217076400-a7302264-0603-4c20-894e-c88e9a5cfcf2.jpg) 7 | 8 | Using any ESP8266 or ESP32 dev board (I'm showing a Wemos D1 mini in the above), connect the data pin of an IR receiver to GPIO14 and the +V and ground to the 5V and GND pins on the dev board. Connect the dev board to your computer via USB cable. 9 | 10 | #### Flash the code and read the values 11 | In the Arduino IDE, select the board you are using and the COM port the board is connected to. Then load the above ir_remote_codes.ino file and flash it to your board. After the sketch loads and the board reboots, open up the serial monitor window. Press a button on the remote and read the corresponding code in the serial monitor. 12 | 13 | ![Sample_IR_Output](https://user-images.githubusercontent.com/55962781/217077562-75a6d4ad-c865-452a-bede-da2fccb515b6.jpg) 14 | 15 | Once you've determined and recorded the codes for the buttons you want to use with the amp, see the wiki page [Using a Custom IR Receiver and Remote](https://github.com/Resinchem/Arylic-Amp-MQTT/wiki/06-Using-a-Custom-IR-Receiver-and-Remote) for information on how to map these codes to the UART commands for the amp. 16 | -------------------------------------------------------------------------------- /homeassistant/README.md: -------------------------------------------------------------------------------- 1 | ### Home Assistant Sample Configurations 2 | 3 | The files provided here are my YAML for the deskamp, as I have it built and configured. It is highly likely that this YAML will need to be edited and cannot simply be copied into your own Home Assistant instance. _These are provided only as examples for creating your own!_ 4 | 5 | **Please review the MQTT and UART commands and the Home Assistant Integration sections of the wiki before attempting to use any of this code!** 6 | 7 | #### deskamp_package.yaml 8 | This is my package file for all the amp-related entities, automations and scripts. To use as a package, you must have packages enabled within your Home Assistant instance (see the Home Assistant documentation for more informaition on using packages). If you are using a split configuration, or have all your configuration in one big configuration.yaml file, you cannot simply copy and paste this code. It will not work! Instead, you'll need to split out the various sections (e.g. sensors, automations, etc.) and will likely need to adjust indentations, etc. My code is primarily meant to serve as an example for creating your own. 9 | 10 | Even if using packages, it is highly likely that you will still need to edit the YAML. If you modified the MQTT topics in the ESP32 code or want your entities to have different names (mine are all 'desk amp'), then you will need to update those in your version of the YAML. 11 | 12 | #### dashboard.yaml 13 | The file contains the YAML I used to create this dashboard: 14 | 15 | ![HA_Dashboard_02](https://user-images.githubusercontent.com/55962781/217309510-49e82392-10da-49a4-83f4-4ee910d7d225.jpg) 16 | 17 | Again, it will likely need to be edited to match the entity names you used or created as part of the amp integration. It is meant only as a guide for creating your own and not a copy/paste resource. In this particular dashboard, a few custom add-ons are also used (these can be found in the Home Assistant Community Store (HACS): 18 | 19 | - ios-dark-mode theme 20 | - custom text-divider row 21 | - custom button card 22 | -------------------------------------------------------------------------------- /src/Settings.h: -------------------------------------------------------------------------------- 1 | // =============================================================================== 2 | // Update/Add any #define values to match your build and board type if not using D1 Mini 3 | // ================================================================================ 4 | // Change these if you use different pins. 5 | #define RX_PIN 16 // Serial2 RX to Amp TX 6 | #define TX_PIN 17 // Serial2 TX to Amp RX 7 | #define SDA_PIN 21 // Data pin for SSD1306 display 8 | #define SCL_PIN 22 // Clock pin for SSD1306 display 9 | #define DISP_PIN 19 // Rotary knob click for controlling display 10 | #define IR_RECV_PIN 18 // IR Receiver Pin 11 | 12 | #define WIFIMODE 2 // 0 = Only Soft Access Point, 1 = Only connect to local WiFi network with UN/PW, 2 = Both 13 | #define MQTTMODE 1 // 0 = Disable MQTT (not recommended), 1 = Enable (will only be enabled if WiFi mode = 1 or 2 - broker must be on same network) 14 | 15 | // Change to desired MQTT topics 16 | #define MQTTCLIENT "DeskAmp" // MQTT Client Name 17 | #define WIFIHOSTNAME "DeskAmp" // Wifi Host Name 18 | #define MQTT_TOPIC_SUB "cmnd/deskamp" // MQTT subscribe topic (should be separate from pub - these values are NOT retained) 19 | #define MQTT_TOPIC_PUB "stat/deskamp" // MQTT publish topic (should be separate from sub - these values ARE retained) 20 | #define OTA_HOSTNAME "DeskAmpOTA" // Hostname to broadcast as port in the IDE of OTA Updates 21 | 22 | //Display Settings 23 | #define SCREEN_WIDTH 128 // OLED display width, in pixels 24 | #define SCREEN_HEIGHT 64 // OLED display height, in pixels 25 | #define SCREEN_ADDR 0x3C //I2C 7-bit Address 26 | #define OLED_RESET -1 // Reset pin # (or -1 if display does not have reset pin) 27 | 28 | // OTA Updates - do not change these unless needed and you are sure you know what you are doing 29 | bool ota_flag = true; // Must leave this as true for board to broadcast port to IDE upon boot 30 | uint16_t ota_time_elapsed = 0; // Counter when OTA active 31 | uint16_t ota_boot_time_window = 2500; // minimum time on boot for IP address to show in IDE ports, in millisecs 32 | uint16_t ota_time_window = 20000; // time to start file upload when ota_flag set to true (after initial boot), in millsecs 33 | 34 | // --------------------------------------------------------------------------------------------------------------- 35 | // Options - Defaults upon boot-up or any other custom ssttings 36 | // --------------------------------------------------------------------------------------------------------------- 37 | String mqttTopicPub = "stat/deskamp"; 38 | 39 | // Custom settings applied/reapplied at boot 40 | unsigned long dispTempDisplay_duration = 4000; // How long to display changed value on display before returning to selected mode (in milliseconds) 41 | byte bootDispMode = 1; // Default display mode at boot (1=Source, 2=Vol, 3=Title, 4=Track, 5=Mute, 6=blank) 42 | bool enableIR = 1; // 0 = disabled. Can toggled off/on via MQTT after boot. This is just default boot setting 43 | -------------------------------------------------------------------------------- /sirius-tft-touch-add-on/README.md: -------------------------------------------------------------------------------- 1 | ## Sirius XM Stream and TFT-Touch Add-ons 2 | 3 | The files included here are for optional add-on components and are not required for the original amp build. While related, each add-on could be implmented individually: 4 | 5 | - Creation of local Sirius XM streaming server 6 | - Addition of a TFT-touch panel for controlling the amp 7 | 8 | To utilize the Sirius XM streaming server, you must have a valid Sirius XM subscription that includes streaming. It will not give you access beyond your current subscription and is meant for private, individual non-commercial use. 9 | 10 | SiriusXM and all related materials to this service and channels referenced are © 2023 Sirius XM Radio Inc. 11 | 12 | The TFT touch panel does require Home Assistant, but the Sirius XM stream server can be implemented with or without Home Assistant. For more details on the build and implementation, please see the following two sources: 13 | 14 | - YouTube Video: [Local SiriusXM Server and Touch Controls for the DIY Amp](https://youtu.be/VQ3LSnCgpeE) (highlights) 15 | - Blog Article: [Adding Local Sirius XM and Touch to the DIY Amp](https://resinchemtech.blogspot.com/2023/04/amp-siriusxm.html) (full details) 16 | 17 | The files included here are my versions of how I implemented these add-ons and serve as examples or templates for implementing your own solution. This means they will likely need to be modified to match your own entity names, reflect your own Sirius XM favorite stations, etc. The files included are: 18 | 19 | ### deskamp_package_add_ons.yaml 20 | This are the additional Home Assistant entities, automations and scripts related to use of the Sirius XM and the touch panel. They are meant to be added to the main amp package, deskamp_package.yaml, found in the /homeassistant folder of this repo and likely will not work standalone. But they can also server as examples for creating your own Home Assistant automations and scripts. 21 | 22 | ### ha_dashboard_sirius_subview.yaml 23 | This is the YAML used to create my Sirius XM dashboard as shown in the video and blog article. Mine is actually defined as a subview, called from a button press on the main amp Home Assistant dashboard. The YAML for the main amp Home Assistant dashboard can be found in the /homeassistant folder. 24 | 25 | ### materialdesignicons-webfont.ttf 26 | This is the font file for using material design icons on the touch panel. It must be installed in your Home Assistant /esphome/fonts folder. See the blog article linked above for more details on using icons on the touch display. 27 | 28 | ### sirius_docker_compose.yml 29 | This is the docker-compose file used to create the local Sirius XM streaming server. Note that the actual app itself is a Python application, so it can be run natively on any system that runs Python. Docker is not a requirement. See the [sxm-player repo](https://github.com/AngellusMortis/sxm-player) for more details. Please not I am NOT the author of this repo, so if you have issues or questions regarding the Sirius XM Player, please contact the author of that repo. 30 | 31 | ### sirius-tft_esphome.yaml 32 | This is my main ESPHome file that gets uploaded to the ESP32 connected to the touch display. It **_will_** require your own modifications, so this file is included as a starting point for your own implementation. Note that it requires the header (.h) file and an external library (see below). 33 | 34 | ### sirius-tft-monitor.h 35 | This is the header file that gets included as part of the main ESPHome YAML file above. It, among other things, defines the pages and panel layout, panel text, color and fonts, etc. Again, you will need to modify this for your particular project. 36 | 37 | ### Additional Files Required 38 | The ESPHome and touch panel also requires the inclusion of an external library developed by Kevin Dorff (Dorffmeister). It generally does not need to be modified, but simply included as part of the ESPHome.yaml file. For more information, or to contact the author, please visit his [Github Repository](https://github.com/kdorff/esphome-display-panel). 39 | 40 | ### All files provided here are for reference only and will likely require modification for use in your own projects. 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Adding MQTT (and more) to the Arylic Up2Stream DIY Amplifier 2 | An ESP32-based project that adds MQTT control and more to the Arylic DIY Amp without the need to install **any** mobile applications! 3 | 4 | ![Finished_Build_Small](https://user-images.githubusercontent.com/55962781/216686591-e848d1ba-e7c6-480c-b4ff-9059607b4078.jpg) 5 | 6 | For build instructions, wiring diagrams, parts lists and more, please see the following: 7 | - YouTube Video: [Adding an ESP32, MQTT and more to the Arylic Amp](https://youtu.be/bE3YqMwv0SI) 8 | - Blog article: [Adding MQTT to the Arylic DIY Amp with an ESP32](https://resinchemtech.blogspot.com/2022/12/arylic-amp-mqtt.html) 9 | 10 | The Arylic DIY Up2Stream Amp has convenient interfaces that allow you to add controls such as a volume knob and push buttons for some features. But many of its features and settings are normally only available via a mobile app (and resultant cloud account). However, for those local-only minded folks, the amp also offers an HTTP API and UART intefaces. By adding an ESP32, this UART can be used to communicate MQTT topics to and from the amp to control nearly all the amp's features. In addition, the ESP32 adds some addtional features that are either separate purchases or are simply not available otherwise. 11 | 12 | ![Amp_and_ESP_02](https://user-images.githubusercontent.com/55962781/216692174-f3b8c337-defc-4340-985e-c0c6e9065719.png) 13 | 14 | The ESP32 and this project's code adds: 15 | 16 | - An MQTT-to-UART translation. This allows integration into platforms like Home Assistant with many more features and options other than the standard media player integration 17 | - An optional OLED display that shows most state changes to the amp (e.g. source, volume, balance and more) 18 | - Click option to the default rotary encoder that is provided by the amp's encoder interface 19 | - Optional IR receiver that replaces the onboard receiver and allows the use of any IR remote to control the amp. 20 | 21 | ![HA_Dashboard_01](https://user-images.githubusercontent.com/55962781/216692697-9169711e-2550-4d4c-950d-c2796c9e8901.jpg) 22 | 23 | ### Please see the [wiki](https://github.com/Resinchem/Arylic-Amp-MQTT/wiki) for important information on adapting and using this firmware for your own projects 24 | 25 | Because the board revisions and firmware versions for the Arylic amps can vary widely, and due to the lack of current 'official' documentation from Arylic on the UART command set, it is highly likely that you will need to adapt the code for your particular install, firmware version, etc. During development, I found UART commands that were not documented at all... and others that appeared not to work any longer (or had been revised with no documentation). For these reasons, you should be comfortable with modifying Arduino/C++ code and compiling and uploading that code to your own ESP32 to use this project. Again, please refer to the wiki for more details. 26 | 27 | For the above reasons (and other project prioirities), this code is being provided **as-is** to serve as a framework for your own project and build. I will happily answer questions, but likely will not make any 'fixes', 'upgrades' or 'feature additions' as the current firmware works ideally for my particular amp build and firmware version. 28 | 29 | ## Additional Optional Updates 30 | 31 | ![ReadMe_Sirius_Thumb_Small](https://user-images.githubusercontent.com/55962781/232343345-83bbd0ef-2f0c-4257-8b67-6a444a9ae559.jpg) 32 | 33 | A later video and blog article adds a **local** SiriusXM streaming server and an external touch panel for selecting favorite channels (plus a few additional amp controls). 34 | 35 | Please the wiki and the following sources for more information on these add-ons: 36 | 37 | - YouTube Overview: [Local SiriusXM Server and Touch Controls for the DIY Amp](https://youtu.be/VQ3LSnCgpeE) 38 | - Step-by-step Blog Article: [Adding Local SiriuxSM and Touch to the DIY Amp](https://resinchemtech.blogspot.com/2023/04/amp-siriusxm.html) 39 | 40 | It takes substantial time, effort and cost to develop and maintain this repository. If you find it helpful and would like to say 'thanks', please consider supporting this project and future development: 41 | 42 | Buy Me A Coffee 43 | -------------------------------------------------------------------------------- /sirius-tft-touch-add-on/sirius-tft_esphome.yaml: -------------------------------------------------------------------------------- 1 | esphome: 2 | name: sirius-tft 3 | includes: 4 | - sirius-tft-monitor.h 5 | libraries: 6 | - esphome-display-panel=https://github.com/kdorff/esphome-display-panel.git#v0.0.13 7 | 8 | esp32: 9 | board: esp32dev 10 | framework: 11 | type: arduino 12 | 13 | # Enable logging 14 | logger: 15 | 16 | # Enable Home Assistant API 17 | api: 18 | encryption: 19 | key: !secret api_key 20 | id: api_server 21 | 22 | ota: 23 | password: !secret ota_password 24 | 25 | wifi: 26 | ssid: !secret wifi_ssid 27 | password: !secret wifi_password 28 | fast_connect: true 29 | 30 | 31 | # Enable fallback hotspot (captive portal) in case wifi connection fails 32 | ap: 33 | ssid: "Siriux tft Fallback Hotspot" 34 | password: !secret ap_password 35 | 36 | # captive_portal: 37 | 38 | color: 39 | - id: color_red 40 | red: 100% 41 | green: 0% 42 | blue: 0% 43 | - id: color_green 44 | red: 0% 45 | green: 100% 46 | blue: 0% 47 | - id: color_blue 48 | red: 0% 49 | green: 0% 50 | blue: 100% 51 | - id: color_yellow 52 | red: 100% 53 | green: 100% 54 | blue: 0% 55 | - id: color_orange 56 | red: 100% 57 | green: 65% 58 | blue: 0% 59 | - id: color_white 60 | red: 100% 61 | green: 100% 62 | blue: 100% 63 | - id: color_magenta 64 | red: 100% 65 | green: 0% 66 | blue: 100% 67 | - id: color_cyan 68 | red: 0% 69 | green: 100% 70 | blue: 100% 71 | 72 | font: 73 | - file: 'fonts/materialdesignicons-webfont.ttf' 74 | id: icon_font_45 75 | size: 45 76 | ## Method for using glyphs in YAML and C++ based on neil123's comments in 77 | ## https://tinyurl.com/esphome-font-glyphs 78 | ## 79 | ## Process: 80 | ## * Install the font materialdesignicons-webfont.ttf both locally 81 | ## and in the the esphome/fonts folder. 82 | ## * Using Character Map UWP (available in the Microsoft Store), 83 | ## * Select the font materialdesignicons-webfont.ttf 84 | ## * Find the symbol you want to use (you can search) 85 | ## * Note it's name and code code (such as 'U+F0020' for 'alarm'). 86 | glyphs: 87 | - "\U000F02DC" # 'home' 88 | - "\U000F0062" # 'up arrow drop circle' 89 | - "\U000F004A" # 'down arrow drop circle' 90 | 91 | - file: "gfonts://Rubik@600" 92 | id: font_flash 93 | size: 30 94 | - file: "gfonts://Rubik@500" 95 | id: font_button_label 96 | size: 22 97 | 98 | spi: 99 | clk_pin: 18 100 | mosi_pin: 23 101 | miso_pin: 19 102 | 103 | 104 | globals: 105 | - id: brightness 106 | type: float 107 | restore_value: yes 108 | initial_value: "1.0" 109 | 110 | display: 111 | - platform: ili9xxx 112 | model: TFT 2.4 113 | cs_pin: 33 114 | dc_pin: 5 115 | reset_pin: 16 116 | rotation: 270 117 | ## The panels will redraw themselves completely. 118 | auto_clear_enabled: True 119 | update_interval: 0.1s 120 | lambda: |- 121 | static bool panelsInitialized = 0; 122 | if (!panelsInitialized) { 123 | initializePanels(it); 124 | panelsInitialized = true; 125 | } 126 | 127 | updatePanelStates(); 128 | drawPanels(); 129 | 130 | # Define a PWM output on the ESP32 131 | output: 132 | - platform: ledc 133 | pin: 17 134 | id: backlight 135 | 136 | light: 137 | - platform: monochromatic 138 | output: backlight 139 | name: "Sirius TFT Backlight" 140 | id: tft_backlight 141 | restore_mode: ALWAYS_ON 142 | 143 | touchscreen: 144 | platform: xpt2046 145 | id: touch 146 | cs_pin: 32 147 | interrupt_pin: 21 148 | swap_x_y: false 149 | ## 150 | ## From running calibration (code is commented out, below) 151 | ## for rotation: 0 these were good 152 | ## minx=426, maxx=3771, miny=471, maxy=3845 153 | calibration_x_min: 426 154 | calibration_x_max: 3771 155 | calibration_y_min: 3845 156 | calibration_y_max: 471 157 | ## Help eliminate phantom presses 158 | threshold: 400 159 | update_interval: 50ms 160 | report_interval: 200ms 161 | on_touch: 162 | then: 163 | - if: 164 | condition: 165 | lambda: |- 166 | return isPanelTouched((id(touch)).x, (id(touch)).y); 167 | then: 168 | - lambda: |- 169 | ESP_LOGD("yaml", "touched name=%s", (lastTouchedPanel->name.c_str())); 170 | // If backlight is ON (amp is powered up) 171 | if (id(tft_backlight).current_values.is_on()) { 172 | if ((lastTouchedPanel != &panelUp) && (lastTouchedPanel != &panelDown) && (lastTouchedPanel != &panelHome)) { 173 | enableFlash({"Launching:", (lastTouchedPanel->name.c_str()) }); 174 | HomeassistantServiceResponse resp; 175 | resp.service = "script.turn_on"; 176 | HomeassistantServiceMap entity_id_kv; 177 | entity_id_kv.key = "entity_id"; 178 | entity_id_kv.value = lastTouchedPanel->tag.c_str(); //"script.sirius_classicrewind"; 179 | resp.data.push_back(entity_id_kv); 180 | id(api_server).send_homeassistant_service_call(resp); 181 | 182 | } else if (lastTouchedPanel == &panelDown) { 183 | if (pageNumber < maxPageNumber) pageNumber++; 184 | } else if (lastTouchedPanel == &panelUp) { 185 | if (pageNumber > 0) pageNumber--; 186 | } else if (lastTouchedPanel == &panelHome) { 187 | pageNumber = 0; 188 | } 189 | // Backlight is off (amp is in standby - wake amp - which will turn on backlight) 190 | } else { 191 | HomeassistantServiceResponse resp; 192 | resp.service = "script.turn_on"; 193 | HomeassistantServiceMap entity_id_kv; 194 | entity_id_kv.key = "entity_id"; 195 | entity_id_kv.value = "script.sirius_power_up_amp"; 196 | resp.data.push_back(entity_id_kv); 197 | id(api_server).send_homeassistant_service_call(resp); 198 | } 199 | 200 | time: 201 | - platform: homeassistant 202 | id: esptime 203 | on_time_sync: 204 | then: 205 | - lambda: !lambda |- 206 | static boolean bootTimeSet = false; 207 | if (!bootTimeSet) { 208 | auto now = id(esptime).now(); 209 | char buffer[20]; 210 | now.strftime(buffer, sizeof(buffer), "%Y-%m-%dT%H:%M:%S"); 211 | id(sirius_tft_boot_time).publish_state(buffer); 212 | bootTimeSet = true; 213 | } 214 | 215 | text_sensor: 216 | - platform: template 217 | name: "Sirius tft Last Boot" 218 | id: sirius_tft_boot_time 219 | icon: mdi:clock-start 220 | 221 | -------------------------------------------------------------------------------- /homeassistant/dashboard.yaml: -------------------------------------------------------------------------------- 1 | - theme: ios-dark-mode 2 | title: Amp 3 | path: amp 4 | badges: [] 5 | cards: 6 | - type: vertical-stack 7 | cards: 8 | - text: Audio Sources 9 | type: custom:text-divider-row 10 | - square: false 11 | columns: 4 12 | type: grid 13 | cards: 14 | - aspect_ratio: 1.2/1 15 | color: rgb(252,255,158) 16 | color_type: card 17 | entity: switch.desk_amp_source_standby 18 | icon: mdi:power-cycle 19 | name: Standby 20 | tap_action: 21 | action: toggle 22 | type: custom:button-card 23 | - aspect_ratio: 1.2/1 24 | color: rgb(0,25,158) 25 | color_type: card 26 | entity: switch.desk_amp_source_bluetooth 27 | icon: mdi:bluetooth-audio 28 | name: Bluetooth 29 | tap_action: 30 | action: toggle 31 | type: custom:button-card 32 | - aspect_ratio: 1.2/1 33 | color: rgb(200,25,0) 34 | color_type: card 35 | entity: switch.desk_amp_source_usb 36 | icon: mdi:usb-flash-drive 37 | name: USB 38 | tap_action: 39 | action: toggle 40 | type: custom:button-card 41 | - aspect_ratio: 1.2/1 42 | color: rgb(0,200,100) 43 | color_type: card 44 | entity: switch.desk_amp_source_usbdac 45 | icon: mdi:usb 46 | name: USBDAC 47 | tap_action: 48 | action: toggle 49 | type: custom:button-card 50 | - aspect_ratio: 1.2/1 51 | color: rgb(190,250,255) 52 | color_type: card 53 | entity: switch.desk_amp_source_network 54 | icon: mdi:wifi-star 55 | name: Net/DLNA 56 | tap_action: 57 | action: toggle 58 | type: custom:button-card 59 | - aspect_ratio: 1.2/1 60 | color: rgb(0,255,0) 61 | color_type: card 62 | entity: switch.desk_amp_source_line_in 63 | icon: mdi:audio-input-stereo-minijack 64 | name: Line-In 65 | tap_action: 66 | action: toggle 67 | type: custom:button-card 68 | - aspect_ratio: 1.2/1 69 | color: rgb(158,0,158) 70 | color_type: card 71 | entity: switch.desk_amp_source_optical 72 | icon: mdi:transit-connection-variant 73 | name: Optical 74 | tap_action: 75 | action: toggle 76 | type: custom:button-card 77 | - aspect_ratio: 1.2/1 78 | color: rgb(150,25,25) 79 | color_type: card 80 | entity: switch.desk_amp_mute 81 | icon: mdi:volume-high 82 | name: Mute 83 | tap_action: 84 | action: toggle 85 | state: 86 | - color: rgb(255,0,0) 87 | icon: mdi:volume-off 88 | name: 'Mute: ON' 89 | styles: 90 | icon: 91 | - animation: blink 2s ease infinite 92 | value: 'on' 93 | - color: rgb(35,35,35) 94 | icon: mdi:volume-high 95 | name: 'Mute: Off' 96 | value: 'off' 97 | type: custom:button-card 98 | - text: Volume, Balance and Tone 99 | type: custom:text-divider-row 100 | - type: entities 101 | entities: 102 | - entity: input_number.desk_amp_volume 103 | icon: mdi:knob 104 | - type: entities 105 | entities: 106 | - entity: input_number.desk_amp_balance 107 | icon: mdi:scale-balance 108 | - type: entities 109 | entities: 110 | - entity: input_number.desk_amp_treble 111 | icon: mdi:chart-bell-curve 112 | - type: entities 113 | entities: 114 | - entity: input_number.desk_amp_midrange 115 | icon: mdi:chart-bell-curve 116 | - type: entities 117 | entities: 118 | - entity: input_number.desk_amp_bass 119 | icon: mdi:chart-bell-curve 120 | - square: false 121 | columns: 4 122 | type: grid 123 | cards: 124 | - aspect_ratio: 2/1 125 | color: rgb(252,255,158) 126 | color_type: card 127 | entity: switch.desk_amp_pregain 128 | icon: mdi:power-cycle 129 | name: Pregain 130 | tap_action: 131 | action: toggle 132 | type: custom:button-card 133 | - aspect_ratio: 2/1 134 | color: rgb(252,255,158) 135 | color_type: card 136 | entity: switch.desk_amp_virtual_bass 137 | icon: mdi:power-cycle 138 | name: Virt. Bass 139 | tap_action: 140 | action: toggle 141 | type: custom:button-card 142 | - type: vertical-stack 143 | cards: 144 | - text: Player Controls 145 | type: custom:text-divider-row 146 | - square: false 147 | columns: 4 148 | type: grid 149 | cards: 150 | - aspect_ratio: 1.2/1 151 | icon: mdi:stop 152 | name: Stop 153 | show_entity_picture: true 154 | show_name: true 155 | tap_action: 156 | action: call-service 157 | service: script.desk_amp_stop_button 158 | type: custom:button-card 159 | - aspect_ratio: 1.2/1 160 | icon: mdi:skip-previous 161 | name: Previous 162 | show_entity_picture: true 163 | show_name: true 164 | tap_action: 165 | action: call-service 166 | service: script.desk_amp_previous_button 167 | type: custom:button-card 168 | - aspect_ratio: 1.2/1 169 | icon: mdi:pause 170 | name: Play/Pause 171 | show_entity_picture: true 172 | show_name: true 173 | tap_action: 174 | action: call-service 175 | service: script.desk_amp_pause_button 176 | type: custom:button-card 177 | - aspect_ratio: 1.2/1 178 | icon: mdi:skip-next 179 | name: Next 180 | show_entity_picture: true 181 | show_name: true 182 | tap_action: 183 | action: call-service 184 | service: script.desk_amp_next_button 185 | type: custom:button-card 186 | - type: entities 187 | entities: 188 | - entity: sensor.desk_amp_title 189 | icon: mdi:music 190 | - entity: sensor.desk_amp_artist 191 | icon: mdi:account 192 | - entity: sensor.desk_amp_album 193 | icon: mdi:record-circle 194 | - text: Additional Controls 195 | type: custom:text-divider-row 196 | - square: false 197 | columns: 5 198 | type: grid 199 | cards: 200 | - aspect_ratio: 2/1.3 201 | color: rgb(252,255,158) 202 | color_type: card 203 | entity: switch.desk_amp_led 204 | icon: mdi:led-on 205 | name: LED 206 | tap_action: 207 | action: toggle 208 | state: 209 | - icon: mdi:led-on 210 | value: 'on' 211 | - color: rgb(35,35,35) 212 | icon: mdi:led-off 213 | value: 'off' 214 | type: custom:button-card 215 | - aspect_ratio: 2/1.3 216 | color: rgb(252,255,158) 217 | color_type: card 218 | entity: switch.desk_amp_key_beep 219 | icon: mdi:volume-high 220 | name: Key Beep 221 | tap_action: 222 | action: toggle 223 | state: 224 | - icon: mdi:volume-high 225 | value: 'on' 226 | - color: rgb(35,35,35) 227 | icon: mdi:volume-off 228 | value: 'off' 229 | type: custom:button-card 230 | - aspect_ratio: 2/1.3 231 | color: rgb(252,255,158) 232 | color_type: card 233 | entity: switch.desk_amp_voice_prompts 234 | icon: mdi:account-voice 235 | name: Voice 236 | tap_action: 237 | action: toggle 238 | state: 239 | - icon: mdi:account-voice 240 | value: 'on' 241 | - color: rgb(35,35,35) 242 | icon: mdi:account-voice-off 243 | value: 'off' 244 | type: custom:button-card 245 | - aspect_ratio: 2/1.3 246 | color: rgb(252,255,158) 247 | color_type: card 248 | entity: switch.desk_amp_ir_receiver 249 | icon: mdi:remote 250 | name: IR 251 | tap_action: 252 | action: toggle 253 | state: 254 | - icon: mdi:remote 255 | value: 'on' 256 | - color: rgb(35,35,35) 257 | icon: mdi:remote-off 258 | value: 'off' 259 | type: custom:button-card 260 | - aspect_ratio: 2/1.3 261 | color: rgb(35,35,35) 262 | color_type: card 263 | icon: mdi:web 264 | name: Web app 265 | tap_action: 266 | action: navigate 267 | navigation_path: /lovelace-tablet/amp-np 268 | type: custom:button-card 269 | - type: entities 270 | entities: 271 | - entity: input_number.desk_amp_max_volume 272 | name: Max Volume Limit 273 | icon: mdi:volume-high 274 | - entity: input_select.desk_amp_loopmode 275 | name: Playback Loop Mode 276 | icon: mdi:repeat 277 | - entity: input_text.desk_amp_uart_command 278 | name: Send Raw UART Command 279 | icon: mdi:code-braces 280 | - type: entities 281 | entities: 282 | - entity: sensor.desk_amp_last_uart 283 | icon: mdi:code-braces 284 | name: Last UART Command 285 | - type: vertical-stack 286 | cards: 287 | - text: Additional Information 288 | type: custom:text-divider-row 289 | - type: entities 290 | entities: 291 | - entity: sensor.desk_amp_device_name 292 | name: Device Name 293 | icon: mdi:identifier 294 | - entity: sensor.desk_amp_firmware 295 | name: Firmware Version 296 | icon: mdi:numeric-9-box-multiple 297 | - entity: binary_sensor.desk_amp_internet 298 | name: Internet 299 | icon: mdi:web 300 | - entity: binary_sensor.desk_amp_ethernet 301 | name: Ethernet 302 | icon: mdi:ethernet 303 | - entity: binary_sensor.desk_amp_wifi 304 | name: WiFi 305 | icon: mdi:wifi 306 | - entity: binary_sensor.desk_amp_bluetooth 307 | name: Bluetooth 308 | icon: mdi:bluetooth 309 | - entity: binary_sensor.desk_amp_audio 310 | name: Audio 311 | icon: mdi:speaker 312 | - entity: sensor.desk_amp_channel 313 | name: Channel(s) 314 | icon: mdi:speaker-multiple 315 | - entity: sensor.desk_amp_multiroom_mode 316 | name: Multi-room Mode 317 | icon: mdi:floor-plan 318 | - entity: binary_sensor.desk_amp_fixed_volume 319 | name: Fixed Volume 320 | - entity: sensor.desk_amp_last_ir_code 321 | name: Last IR Code 322 | icon: mdi:remote 323 | -------------------------------------------------------------------------------- /sirius-tft-touch-add-on/sirius-tft-monitor.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // The current page number. 4 | int pageNumber = 0; 5 | // Max page number (zero based) 6 | int maxPageNumber = 3; 7 | 8 | // Last touched page 9 | DisplayPanel* lastTouchedPanel = NULL; 10 | 11 | // The display/lcd we are working with. Defined in initializePanels(). 12 | esphome::display::DisplayBuffer* lcd = NULL; 13 | 14 | // For sprintf calls. 15 | char buffer[25]; 16 | 17 | #define WIDTH 320 18 | #define HEIGHT 240 19 | // Convert percentage width or height (0-100) to pixels 20 | // for easier panel layout. 21 | #define PW(PCT_WIDTH) (PCT_WIDTH * 0.01 * WIDTH) 22 | #define PH(PCT_HEIGHT) (PCT_HEIGHT * 0.01 * HEIGHT) 23 | 24 | // The Panels used by this app. Params are (X, Y, W, H) 25 | //Page 0 - system settings 26 | DisplayPanel panelStandby(PW(5), PH(4), PW(65), PH(28)); 27 | DisplayPanel panelBootTFT(PW(5), PH(36), PW(65), PH(28)); 28 | DisplayPanel panelBootAmp(PW(5), PH(68), PW(65), PH(28)); 29 | 30 | // Create 3 x 3 grid of equal panels for each favorites page 31 | //Page 1 32 | DisplayPanel page1_01(0 * PW(25), 0 * PH(33), PW(25), PH(33)); 33 | DisplayPanel page1_02(1 * PW(25), 0 * PH(33), PW(25), PH(33)); 34 | DisplayPanel page1_03(2 * PW(25), 0 * PH(33), PW(25), PH(33)); 35 | 36 | DisplayPanel page1_04(0 * PW(25), 1 * PH(33), PW(25), PH(33)); 37 | DisplayPanel page1_05(1 * PW(25), 1 * PH(33), PW(25), PH(33)); 38 | DisplayPanel page1_06(2 * PW(25), 1 * PH(33), PW(25), PH(33)); 39 | 40 | DisplayPanel page1_07(0 * PW(25), 2 * PH(33), PW(25), PH(33)); 41 | DisplayPanel page1_08(1 * PW(25), 2 * PH(33), PW(25), PH(33)); 42 | DisplayPanel page1_09(2 * PW(25), 2 * PH(33), PW(25), PH(33)); 43 | 44 | //Page 2 45 | DisplayPanel page2_01(0 * PW(25), 0 * PH(33), PW(25), PH(33)); 46 | DisplayPanel page2_02(1 * PW(25), 0 * PH(33), PW(25), PH(33)); 47 | DisplayPanel page2_03(2 * PW(25), 0 * PH(33), PW(25), PH(33)); 48 | 49 | DisplayPanel page2_04(0 * PW(25), 1 * PH(33), PW(25), PH(33)); 50 | DisplayPanel page2_05(1 * PW(25), 1 * PH(33), PW(25), PH(33)); 51 | DisplayPanel page2_06(2 * PW(25), 1 * PH(33), PW(25), PH(33)); 52 | 53 | DisplayPanel page2_07(0 * PW(25), 2 * PH(33), PW(25), PH(33)); 54 | DisplayPanel page2_08(1 * PW(25), 2 * PH(33), PW(25), PH(33)); 55 | DisplayPanel page2_09(2 * PW(25), 2 * PH(33), PW(25), PH(33)); 56 | 57 | //Page 3 58 | DisplayPanel page3_01(0 * PW(25), 0 * PH(33), PW(25), PH(33)); 59 | DisplayPanel page3_02(1 * PW(25), 0 * PH(33), PW(25), PH(33)); 60 | DisplayPanel page3_03(2 * PW(25), 0 * PH(33), PW(25), PH(33)); 61 | 62 | DisplayPanel page3_04(0 * PW(25), 1 * PH(33), PW(25), PH(33)); 63 | DisplayPanel page3_05(1 * PW(25), 1 * PH(33), PW(25), PH(33)); 64 | DisplayPanel page3_06(2 * PW(25), 1 * PH(33), PW(25), PH(33)); 65 | 66 | DisplayPanel page3_07(0 * PW(25), 2 * PH(33), PW(25), PH(33)); 67 | DisplayPanel page3_08(1 * PW(25), 2 * PH(33), PW(25), PH(33)); 68 | DisplayPanel page3_09(2 * PW(25), 2 * PH(33), PW(25), PH(33)); 69 | 70 | 71 | // Menu buttons (right most column) 72 | DisplayPanel panelHome(3 * PW(25), 0 * PH(33), PW(25), PH(33)); 73 | DisplayPanel panelUp(3 * PW(25), 1 * PH(33), PW(25), PH(33)); 74 | DisplayPanel panelDown(3 * PW(25), 2 * PH(33), PW(25), PH(33)); 75 | 76 | DisplayPanel flashPanel(PW(5), PH(20), PW(90), PH(50)); 77 | 78 | //Setup pages with panels 79 | 80 | std::vector> pages = { 81 | { //Page 0 - System 82 | &panelStandby, 83 | &panelBootTFT, 84 | &panelBootAmp, 85 | &panelHome, 86 | &panelUp, 87 | &panelDown 88 | }, 89 | { 90 | // Page 1. 91 | &page1_01, 92 | &page1_02, 93 | &page1_03, 94 | &page1_04, 95 | &page1_05, 96 | &page1_06, 97 | &page1_07, 98 | &page1_08, 99 | &page1_09, 100 | &panelHome, 101 | &panelUp, 102 | &panelDown 103 | }, 104 | { 105 | // Page 2. 106 | &page2_01, 107 | &page2_02, 108 | &page2_03, 109 | &page2_04, 110 | &page2_05, 111 | &page2_06, 112 | &page2_07, 113 | &page2_08, 114 | &page2_09, 115 | &panelHome, 116 | &panelUp, 117 | &panelDown 118 | }, 119 | { 120 | // Page 3. 121 | &page3_01, 122 | &page3_02, 123 | &page3_03, 124 | &page3_04, 125 | &page3_05, 126 | &page3_06, 127 | &page3_07, 128 | &page3_08, 129 | &page3_09, 130 | &panelHome, 131 | &panelUp, 132 | &panelDown 133 | } 134 | }; 135 | // icons for menu buttons 136 | std::vector panelHomeText = { "\U000F02DC" }; 137 | std::vector panelUpText = { "\U000F0062" }; 138 | std::vector panelDownText = { "\U000F004A" }; 139 | std::vector blankText = {}; 140 | 141 | // One time, initialize the Panels 142 | void initializePanels(esphome::display::DisplayBuffer &display) { 143 | lcd = &display; 144 | //Page 0 - System 145 | panelStandby.font = font_button_label; 146 | panelStandby.color = color_green; 147 | panelStandby.textColor = Color::BLACK; 148 | panelStandby.touchable = true; 149 | panelStandby.text = {"STANDBY"}; 150 | panelStandby.name = "Standby"; 151 | panelStandby.tag = "script.sirius_power_down_amp"; 152 | 153 | panelBootTFT.font = font_button_label; 154 | panelBootTFT.color = color_blue; 155 | panelBootTFT.textColor = Color::WHITE; 156 | panelBootTFT.touchable = true; 157 | panelBootTFT.text = {"Reboot TFT"}; 158 | panelBootTFT.name = "BootTFT"; 159 | panelBootTFT.tag = "script.sirius_reboot_tft"; 160 | 161 | panelBootAmp.font = font_button_label; 162 | panelBootAmp.color = color_red; 163 | panelBootAmp.textColor = Color::WHITE; 164 | panelBootAmp.touchable = true; 165 | panelBootAmp.text = {"Reboot Amp"}; 166 | panelBootAmp.name = "BootAmp"; 167 | panelBootAmp.tag = "script.sirius_reboot_amp"; 168 | 169 | //Page 1 170 | page1_01.font = font_button_label; 171 | page1_01.color = color_red; 172 | page1_01.textColor = Color::WHITE; 173 | page1_01.touchable = true; 174 | page1_01.text = {"Clssc", "Rwnd"}; 175 | page1_01.name = "Classic Rewind"; 176 | page1_01.tag = "script.sirius_classicrewind"; 177 | 178 | page1_02.font = font_button_label; 179 | page1_02.color = color_green; 180 | page1_02.textColor = Color::BLACK; 181 | page1_02.touchable = true; 182 | page1_02.text = {"Clssc", "Rock"}; 183 | page1_02.name = "Classic Rock"; 184 | page1_02.tag = "script.sirius_classicrock"; 185 | 186 | page1_03.font = font_button_label; 187 | page1_03.color = color_blue; 188 | page1_03.textColor = Color::WHITE; 189 | page1_03.touchable = true; 190 | page1_03.text = {"Clssc", "Vinyl"}; 191 | page1_03.name = "Classic Vinyl"; 192 | page1_03.tag = "script.sirius_classicvinyl"; 193 | 194 | page1_04.font = font_button_label; 195 | page1_04.color = color_cyan; 196 | page1_04.textColor = Color::BLACK; 197 | page1_04.touchable = true; 198 | page1_04.text = {"70s", "on", "7"}; 199 | page1_04.name = "70s on 7"; 200 | page1_04.tag = "script.sirius_70son7"; 201 | 202 | page1_05.font = font_button_label; 203 | page1_05.color = color_magenta; 204 | page1_05.textColor = Color::WHITE; 205 | page1_05.touchable = true; 206 | page1_05.text = {"80s", "on", "8"}; 207 | page1_05.name = "80s on 8"; 208 | page1_05.tag = "script.sirius_80son8"; 209 | 210 | page1_06.font = font_button_label; 211 | page1_06.color = color_red; 212 | page1_06.textColor = Color::WHITE; 213 | page1_06.touchable = true; 214 | page1_06.text = {"70/80s", "Pop"}; 215 | page1_06.name = "70s/80s Pop"; 216 | page1_06.tag = "script.sirius_7080pop"; 217 | 218 | page1_07.font = font_button_label; 219 | page1_07.color = color_red; 220 | page1_07.textColor = Color::WHITE; 221 | page1_07.touchable = true; 222 | page1_07.text = {"Red", "White", "Booze"}; 223 | page1_07.name = "Red White Booze"; 224 | page1_07.tag = "script.sirius_redwhitebooze"; 225 | 226 | page1_08.font = font_button_label; 227 | page1_08.color = color_green; 228 | page1_08.textColor = Color::BLACK; 229 | page1_08.touchable = true; 230 | page1_08.text = {"Road", "House"}; 231 | page1_08.name = "Willies Roadhouse"; 232 | page1_08.tag = "script.sirius_roadhouse"; 233 | 234 | page1_09.font = font_button_label; 235 | page1_09.color = color_blue; 236 | page1_09.textColor = Color::WHITE; 237 | page1_09.touchable = true; 238 | page1_09.text = {"Road", "Trip"}; 239 | page1_09.name = "Rdtrip Radio"; 240 | page1_09.tag = "script.sirius_roadtrip"; 241 | 242 | //Page 2 243 | page2_01.font = font_button_label; 244 | page2_01.color = color_green; 245 | page2_01.textColor = Color::BLACK; 246 | page2_01.touchable = true; 247 | page2_01.text = {"Hair", "Nation"}; 248 | page2_01.name = "Hair Nation"; 249 | page2_01.tag = "script.sirius_hairnation"; 250 | 251 | page2_02.font = font_button_label; 252 | page2_02.color = color_blue; 253 | page2_02.textColor = Color::WHITE; 254 | page2_02.touchable = true; 255 | page2_02.text = {"Bone", "Yard"}; 256 | page2_02.name = "Ozzys Boneyard"; 257 | page2_02.tag = "script.sirius_boneyard"; 258 | 259 | page2_03.font = font_button_label; 260 | page2_03.color = color_red; 261 | page2_03.textColor = Color::WHITE; 262 | page2_03.touchable = true; 263 | page2_03.text = {"Oldies", "Party"}; 264 | page2_03.name = "Oldies Party"; 265 | page2_03.tag = "script.sirius_oldiesparty"; 266 | 267 | page2_04.font = font_button_label; 268 | page2_04.color = color_magenta; 269 | page2_04.textColor = Color::WHITE; 270 | page2_04.touchable = true; 271 | page2_04.text = {"Outlaw"}; 272 | page2_04.name = "Outlaw Cntry"; 273 | page2_04.tag = "script.sirius_outlaw"; 274 | 275 | page2_05.font = font_button_label; 276 | page2_05.color = color_red; 277 | page2_05.textColor = Color::WHITE; 278 | page2_05.touchable = true; 279 | page2_05.text = {"Prime", "Cntry"}; 280 | page2_05.name = "Prime Country"; 281 | page2_05.tag = "script.sirius_primecountry"; 282 | 283 | page2_06.font = font_button_label; 284 | page2_06.color = color_cyan; 285 | page2_06.textColor = Color::BLACK; 286 | page2_06.touchable = true; 287 | page2_06.text = {"The", "Highwy"}; 288 | page2_06.name = "The Highway"; 289 | page2_06.tag = "script.sirius_highway"; 290 | 291 | page2_07.font = font_button_label; 292 | page2_07.color = color_green; 293 | page2_07.textColor = Color::BLACK; 294 | page2_07.touchable = true; 295 | page2_07.text = {"Girl", "Cntry"}; 296 | page2_07.name = "Girl Country"; 297 | page2_07.tag = "script.sirius_womencountry"; 298 | 299 | page2_08.font = font_button_label; 300 | page2_08.color = color_blue; 301 | page2_08.textColor = Color::WHITE; 302 | page2_08.touchable = true; 303 | page2_08.text = {"Elvis", "Radio"}; 304 | page2_08.name = "Elvis Radio"; 305 | page2_08.tag = "script.sirius_elvis"; 306 | 307 | page2_09.font = font_button_label; 308 | page2_09.color = color_red; 309 | page2_09.textColor = Color::WHITE; 310 | page2_09.touchable = true; 311 | page2_09.text = {"Blue", "Grass"}; 312 | page2_09.name = "Bluegrass Jnct"; 313 | page2_09.tag = "script.sirius_bluegrass"; 314 | 315 | //Page 3 316 | page3_01.font = font_button_label; 317 | page3_01.color = color_red; 318 | page3_01.textColor = Color::WHITE; 319 | page3_01.touchable = true; 320 | page3_01.text = {"Mrgta", "ville"}; 321 | page3_01.name = "Margarita"; 322 | page3_01.tag = "script.sirius_margaritaville"; 323 | 324 | page3_02.font = font_button_label; 325 | page3_02.color = color_green; 326 | page3_02.textColor = Color::BLACK; 327 | page3_02.touchable = true; 328 | page3_02.text = {"Sintra"}; 329 | page3_02.name = "Sinatra"; 330 | page3_02.tag = "script.sirius_sinatra"; 331 | 332 | page3_03.font = font_button_label; 333 | page3_03.color = color_blue; 334 | page3_03.textColor = Color::WHITE; 335 | page3_03.touchable = true; 336 | page3_03.text = {"Studio", "54"}; 337 | page3_03.name = "Studio 54"; 338 | page3_03.tag = "script.sirius_studio54"; 339 | 340 | page3_04.font = font_button_label; 341 | page3_04.color = color_cyan; 342 | page3_04.textColor = Color::BLACK; 343 | page3_04.touchable = true; 344 | page3_04.text = {"50s", "on", "5"}; 345 | page3_04.name = "50s on 5"; 346 | page3_04.tag = "script.sirius_50son5"; 347 | 348 | page3_05.font = font_button_label; 349 | page3_05.color = color_magenta; 350 | page3_05.textColor = Color::WHITE; 351 | page3_05.touchable = true; 352 | page3_05.text = {"60s", "on", "6"}; 353 | page3_05.name = "60s on 6"; 354 | page3_05.tag = "script.sirius_60son6"; 355 | 356 | page3_06.font = font_button_label; 357 | page3_06.color = color_red; 358 | page3_06.textColor = Color::WHITE; 359 | page3_06.touchable = true; 360 | page3_06.text = {"Road", "Dog"}; 361 | page3_06.name = "Road Dog Trkn"; 362 | page3_06.tag = "script.sirius_roaddog"; 363 | 364 | page3_07.font = font_button_label; 365 | page3_07.color = color_red; 366 | page3_07.textColor = Color::WHITE; 367 | page3_07.touchable = true; 368 | page3_07.text = {"Fox", "Sports"}; 369 | page3_07.name = "Fox Sports"; 370 | page3_07.tag = "script.sirius_foxsports"; 371 | 372 | page3_08.font = font_button_label; 373 | page3_08.color = color_green; 374 | page3_08.textColor = Color::BLACK; 375 | page3_08.touchable = true; 376 | page3_08.text = {"CBS", "Sports"}; 377 | page3_08.name = "CBS Sports"; 378 | page3_08.tag = "script.sirius_cbssports"; 379 | 380 | page3_09.font = font_button_label; 381 | page3_09.color = color_blue; 382 | page3_09.textColor = Color::WHITE; 383 | page3_09.touchable = true; 384 | page3_09.text = {"NBC", "Sports"}; 385 | page3_09.name = "NBC Sports"; 386 | page3_09.tag = "script.sirius_nbcsports"; 387 | 388 | // Menu 389 | panelHome.font = icon_font_45; 390 | panelHome.color = color_yellow; 391 | panelHome.textColor = Color::BLACK; 392 | panelHome.touchable = true; 393 | panelHome.text = panelHomeText; 394 | panelHome.name = "Home"; 395 | 396 | panelUp.font = icon_font_45; 397 | panelUp.color = color_yellow; 398 | panelUp.textColor = Color::BLACK; 399 | panelUp.touchable = true; 400 | panelUp.text = panelUpText; 401 | panelUp.name = "Prev Page"; 402 | 403 | panelDown.font = icon_font_45; 404 | panelDown.color = color_yellow; 405 | panelDown.textColor = Color::BLACK; 406 | panelDown.touchable = true; 407 | panelDown.text = panelDownText; 408 | panelDown.name = "Next Page"; 409 | 410 | flashPanel.font = font_flash; 411 | flashPanel.color = Color::BLACK; 412 | flashPanel.textColor = Color::WHITE; 413 | flashPanel.touchable = false; 414 | flashPanel.drawPanelOutline = true; 415 | flashPanel.enabled = false; 416 | 417 | 418 | // Backlight initial level 419 | backlight->set_level(brightness->value()); 420 | 421 | // Fill the screen the first time to have BLACK in any gaps in Panels. 422 | display.fill(Color::BLACK); 423 | } 424 | 425 | // The time until which to display flash 426 | esphome::time::ESPTime flashUntil; 427 | 428 | // Enable the Flash message with some specific text 429 | void enableFlash(std::vector flashText) { 430 | flashUntil = esptime->now(); 431 | flashUntil.increment_second(); 432 | flashPanel.enabled = true; 433 | flashPanel.text = flashText; 434 | } 435 | 436 | void updatePanelStates() { 437 | // 438 | // Update configuration for Panels that might have changed. 439 | // 440 | auto now = esptime->now(); 441 | 442 | if (flashPanel.enabled && now > flashUntil) { 443 | flashPanel.enabled = false; 444 | } 445 | 446 | } 447 | 448 | // Draw all of the panels 449 | void drawPanels() { 450 | // drawAllPanels is generally preferred 451 | DisplayPanel::drawAllPanels(*lcd, pages[pageNumber]); 452 | 453 | 454 | // But draw flashPanel separately so it over-draws 455 | // what is below it. 456 | flashPanel.draw(*lcd); 457 | } 458 | 459 | // See if one of the enabled, touchable panels on the 460 | // current page has been touched. 461 | // lastTouchedPanel will be set to a pointer to the 462 | // touched panel (or NULL of no panel was found for the coordinates). 463 | boolean isPanelTouched(int tpX, int tpY) { 464 | lastTouchedPanel = DisplayPanel::touchedPanel(pages[pageNumber], tpX, tpY); 465 | return lastTouchedPanel != NULL; 466 | } -------------------------------------------------------------------------------- /sirius-tft-touch-add-on/ha_dashboard_sirius_subview.yaml: -------------------------------------------------------------------------------- 1 | - theme: Backend-selected 2 | title: Sirius XM Subview 3 | path: sirius-xm-sub 4 | subview: true 5 | badges: [] 6 | cards: 7 | - type: vertical-stack 8 | cards: 9 | - type: entity 10 | entity: input_text.sirius_now_playing 11 | icon: mdi:radio 12 | name: 'Now Playing:' 13 | - text: Music / Favorites 14 | text-divider-font-size: 18px 15 | type: custom:text-divider-row 16 | - square: false 17 | columns: 4 18 | type: grid 19 | cards: 20 | - type: custom:button-card 21 | aspect_ratio: 2/1.5 22 | name: Cls Rewind 23 | entity_picture: /local/sirius/classicrewind.jpg 24 | show_entity_picture: true 25 | tap_action: 26 | action: call-service 27 | service: script.sirius_classicrewind 28 | - type: custom:button-card 29 | aspect_ratio: 2/1.5 30 | name: Red Wht Booze 31 | entity_picture: /local/sirius/redwhitebooze.jpg 32 | show_entity_picture: true 33 | styles: 34 | name: 35 | - font-size: 14px 36 | - color: white 37 | tap_action: 38 | action: call-service 39 | service: script.sirius_redwhitebooze 40 | - type: custom:button-card 41 | aspect_ratio: 2/1.5 42 | name: Classic Rock 43 | entity_picture: /local/sirius/classicrock_2.jpg 44 | show_entity_picture: true 45 | tap_action: 46 | action: call-service 47 | service: script.sirius_classicrock 48 | - type: custom:button-card 49 | aspect_ratio: 2/1.5 50 | name: Road Trip 51 | entity_picture: /local/sirius/roadtripradio.jpg 52 | show_entity_picture: true 53 | tap_action: 54 | action: call-service 55 | service: script.sirius_roadtrip 56 | - type: custom:button-card 57 | aspect_ratio: 2/1.5 58 | name: 50's on 5 59 | entity_picture: /local/sirius/50son5_2.jpg 60 | show_entity_picture: true 61 | tap_action: 62 | action: call-service 63 | service: script.sirius_50son5 64 | - type: custom:button-card 65 | aspect_ratio: 2/1.5 66 | name: 60's on 6 67 | entity_picture: /local/sirius/60son6_2.jpg 68 | show_entity_picture: true 69 | tap_action: 70 | action: call-service 71 | service: script.sirius_60son6 72 | - type: custom:button-card 73 | aspect_ratio: 2/1.5 74 | name: 70's on 7 75 | entity_picture: /local/sirius/70son7_2.jpg 76 | show_entity_picture: true 77 | tap_action: 78 | action: call-service 79 | service: script.sirius_70son7 80 | - type: custom:button-card 81 | aspect_ratio: 2/1.5 82 | name: 80's on 8 83 | entity_picture: /local/sirius/80son8_2.jpg 84 | show_entity_picture: true 85 | tap_action: 86 | action: call-service 87 | service: script.sirius_80son8 88 | - type: custom:button-card 89 | aspect_ratio: 2/1.5 90 | name: 70s/80s Pop 91 | entity_picture: /local/sirius/70s80spop.jpg 92 | show_entity_picture: true 93 | tap_action: 94 | action: call-service 95 | service: script.sirius_7080pop 96 | - type: custom:button-card 97 | aspect_ratio: 2/1.5 98 | name: Clsc Vinyl 99 | entity_picture: /local/sirius/classicvinyl.jpg 100 | show_entity_picture: true 101 | tap_action: 102 | action: call-service 103 | service: script.sirius_classicvinyl 104 | - type: custom:button-card 105 | aspect_ratio: 2/1.5 106 | name: Road Dog 107 | entity_picture: /local/sirius/roaddog.jpg 108 | show_entity_picture: true 109 | tap_action: 110 | action: call-service 111 | service: script.sirius_roaddog 112 | - type: custom:button-card 113 | aspect_ratio: 2/1.5 114 | name: Roadhouse 115 | entity_picture: /local/sirius/roadhouse.jpg 116 | show_entity_picture: true 117 | tap_action: 118 | action: call-service 119 | service: script.sirius_roadhouse 120 | - text: Music / Other 121 | text-divider-font-size: 18px 122 | type: custom:text-divider-row 123 | - square: false 124 | columns: 4 125 | type: grid 126 | cards: 127 | - type: custom:button-card 128 | aspect_ratio: 2/1.5 129 | name: Boneyard 130 | entity_picture: /local/sirius/boneyard.jpg 131 | show_entity_picture: true 132 | tap_action: 133 | action: call-service 134 | service: script.sirius_boneyard 135 | - type: custom:button-card 136 | aspect_ratio: 2/1.5 137 | name: Hair Nation 138 | entity_picture: /local/sirius/hairnation.jpg 139 | show_entity_picture: true 140 | tap_action: 141 | action: call-service 142 | service: script.sirius_hairnation 143 | - type: custom:button-card 144 | aspect_ratio: 2/1.5 145 | name: Oldies Party 146 | entity_picture: /local/sirius/oldiesparty.jpg 147 | show_entity_picture: true 148 | tap_action: 149 | action: call-service 150 | service: script.sirius_oldiesparty 151 | - type: custom:button-card 152 | aspect_ratio: 2/1.5 153 | name: Margaritaville 154 | entity_picture: /local/sirius/magaritaville.jpg 155 | show_entity_picture: true 156 | tap_action: 157 | action: call-service 158 | service: script.sirius_margaritaville 159 | - type: custom:button-card 160 | aspect_ratio: 2/1.5 161 | name: Girl Country 162 | entity_picture: /local/sirius/womencountry.jpg 163 | show_entity_picture: true 164 | tap_action: 165 | action: call-service 166 | service: script.sirius_womencountry 167 | - type: custom:button-card 168 | aspect_ratio: 2/1.5 169 | name: Outlaw 170 | entity_picture: /local/sirius/outlaw.jpg 171 | show_entity_picture: true 172 | tap_action: 173 | action: call-service 174 | service: script.sirius_outlaw 175 | - type: custom:button-card 176 | aspect_ratio: 2/1.5 177 | name: Prime 178 | entity_picture: /local/sirius/primecountry.jpg 179 | show_entity_picture: true 180 | tap_action: 181 | action: call-service 182 | service: script.sirius_primecountry 183 | - type: custom:button-card 184 | aspect_ratio: 2/1.5 185 | name: The Highway 186 | entity_picture: /local/sirius/highway.jpg 187 | show_entity_picture: true 188 | tap_action: 189 | action: call-service 190 | service: script.sirius_highway 191 | - type: custom:button-card 192 | aspect_ratio: 2/1.5 193 | name: Elvis Radio 194 | entity_picture: /local/sirius/elvis.jpg 195 | show_entity_picture: true 196 | tap_action: 197 | action: call-service 198 | service: script.sirius_elvis 199 | - type: custom:button-card 200 | aspect_ratio: 2/1.5 201 | name: Bluegrass 202 | entity_picture: /local/sirius/bluegrass.jpg 203 | show_entity_picture: true 204 | tap_action: 205 | action: call-service 206 | service: script.sirius_bluegrass 207 | - type: custom:button-card 208 | aspect_ratio: 2/1.5 209 | name: Sinatra 210 | entity_picture: /local/sirius/sinatra.jpg 211 | show_entity_picture: true 212 | tap_action: 213 | action: call-service 214 | service: script.sirius_sinatra 215 | - type: custom:button-card 216 | aspect_ratio: 2/1.5 217 | name: Studio 54 218 | entity_picture: /local/sirius/studio54.jpg 219 | show_entity_picture: true 220 | tap_action: 221 | action: call-service 222 | service: script.sirius_studio54 223 | - type: vertical-stack 224 | cards: 225 | - type: horizontal-stack 226 | cards: 227 | - type: entities 228 | entities: 229 | - entity: input_text.sirius_manual_channel 230 | name: Enter channel ID 231 | icon: mdi:pen 232 | - type: entities 233 | entities: 234 | - type: weblink 235 | name: Station List 236 | url: >- 237 | https://docs.google.com/spreadsheets/d/1ot4KJ2VTnL3_b886BGdVMJdIeldWMbFb7GKMO8rrOhs/edit?usp=sharing 238 | icon: mdi:list-box 239 | - text: Sports / Talk 240 | text-divider-font-size: 18px 241 | type: custom:text-divider-row 242 | - square: false 243 | columns: 4 244 | type: grid 245 | cards: 246 | - type: custom:button-card 247 | aspect_ratio: 2/1.5 248 | name: Fox Sports 249 | entity_picture: /local/sirius/foxsports.jpg 250 | show_entity_picture: true 251 | tap_action: 252 | action: call-service 253 | service: script.sirius_foxsports 254 | - type: custom:button-card 255 | aspect_ratio: 2/1.5 256 | name: CBS Sports 257 | entity_picture: /local/sirius/cbssports.jpg 258 | show_entity_picture: true 259 | tap_action: 260 | action: call-service 261 | service: script.sirius_cbssports 262 | - type: custom:button-card 263 | aspect_ratio: 2/1.5 264 | name: NBC Sports 265 | entity_picture: /local/sirius/nbcsports.jpg 266 | show_entity_picture: true 267 | tap_action: 268 | action: call-service 269 | service: script.sirius_nbcsports 270 | - type: custom:button-card 271 | aspect_ratio: 2/1.5 272 | name: ESPN Radio 273 | entity_picture: /local/sirius/espn.jpg 274 | show_entity_picture: true 275 | tap_action: 276 | action: call-service 277 | service: script.sirius_espn 278 | - type: custom:button-card 279 | aspect_ratio: 2/1.5 280 | name: Colts Radio 281 | entity_picture: /local/sirius/colts.jpg 282 | show_entity_picture: true 283 | tap_action: 284 | action: call-service 285 | service: script.sirius_colts 286 | - type: custom:button-card 287 | aspect_ratio: 2/1.5 288 | name: Pacers Radio 289 | entity_picture: /local/sirius/pacers.jpg 290 | show_entity_picture: true 291 | tap_action: 292 | action: call-service 293 | service: script.sirius_pacers 294 | - type: custom:button-card 295 | aspect_ratio: 2/1.5 296 | name: NFL Radio 297 | entity_picture: /local/sirius/nflradio.jpg 298 | show_entity_picture: true 299 | tap_action: 300 | action: call-service 301 | service: script.sirius_nflradio 302 | - type: custom:button-card 303 | aspect_ratio: 2/1.5 304 | name: Mad Dog 305 | entity_picture: /local/sirius/maddog.jpg 306 | show_entity_picture: true 307 | tap_action: 308 | action: call-service 309 | service: script.sirius_maddog 310 | - type: custom:button-card 311 | aspect_ratio: 2/1.5 312 | name: Fox News 313 | entity_picture: /local/sirius/foxnews.jpg 314 | show_entity_picture: true 315 | tap_action: 316 | action: call-service 317 | service: script.sirius_foxnews 318 | - type: custom:button-card 319 | aspect_ratio: 2/1.5 320 | name: NPR Now 321 | entity_picture: /local/sirius/npr.jpg 322 | show_entity_picture: true 323 | tap_action: 324 | action: call-service 325 | service: script.sirius_npr 326 | - type: custom:button-card 327 | aspect_ratio: 2/1.5 328 | name: Howard 100 329 | entity_picture: /local/sirius/howard100.jpg 330 | show_entity_picture: true 331 | tap_action: 332 | action: call-service 333 | service: script.sirius_howard100 334 | - type: custom:button-card 335 | aspect_ratio: 2/1.5 336 | name: Howard 101 337 | entity_picture: /local/sirius/howard101.jpg 338 | show_entity_picture: true 339 | tap_action: 340 | action: call-service 341 | service: script.sirius_howard101 342 | - text: Comedy / Misc 343 | text-divider-font-size: 18px 344 | type: custom:text-divider-row 345 | - square: false 346 | columns: 4 347 | type: grid 348 | cards: 349 | - type: custom:button-card 350 | aspect_ratio: 2/1.5 351 | name: Laugh USA 352 | entity_picture: /local/sirius/laughusa.jpg 353 | show_entity_picture: true 354 | tap_action: 355 | action: call-service 356 | service: script.sirius_laughusa 357 | - type: custom:button-card 358 | aspect_ratio: 2/1.5 359 | name: Comedy Cntl 360 | entity_picture: /local/sirius/comedycentral.jpg 361 | show_entity_picture: true 362 | tap_action: 363 | action: call-service 364 | service: script.sirius_comedycentral 365 | - type: custom:button-card 366 | aspect_ratio: 2/1.5 367 | name: Raw Dog 368 | entity_picture: /local/sirius/rawdog.jpg 369 | show_entity_picture: true 370 | tap_action: 371 | action: call-service 372 | service: script.sirius_rawdog 373 | - type: custom:button-card 374 | aspect_ratio: 2/1.5 375 | name: Roundup 376 | entity_picture: /local/sirius/comedyroundup.jpg 377 | show_entity_picture: true 378 | tap_action: 379 | action: call-service 380 | service: script.sirius_espn 381 | - type: custom:button-card 382 | aspect_ratio: 2/1.5 383 | name: Com. Greats 384 | entity_picture: /local/sirius/comedygreats.jpg 385 | show_entity_picture: true 386 | tap_action: 387 | action: call-service 388 | service: script.sirius_comedygreats 389 | - type: custom:button-card 390 | aspect_ratio: 2/1.5 391 | name: LOL Radio 392 | entity_picture: /local/sirius/lolradio.jpg 393 | show_entity_picture: true 394 | tap_action: 395 | action: call-service 396 | service: script.sirius_lolradio 397 | -------------------------------------------------------------------------------- /homeassistant/deskamp_package.yaml: -------------------------------------------------------------------------------- 1 | # ============================================= 2 | # This package contains all desk amp 3 | # related entities, scrips and automations 4 | # except for these natively integrated entities: 5 | # media_player.basement_desk (attributes create as sensors here) 6 | # ============================================= 7 | 8 | 9 | # ************************************ 10 | # SENSORS (non-MQTT) 11 | # ************************************ 12 | sensor: 13 | - platform: template 14 | sensors: 15 | desk_amp_title: 16 | friendly_name: Title 17 | value_template: "{{ (state_attr('media_player.basement_desk', 'media_title')) }}" 18 | 19 | desk_amp_artist: 20 | friendly_name: Artist 21 | value_template: "{{ (state_attr('media_player.basement_desk', 'media_artist')) }}" 22 | 23 | desk_amp_album: 24 | friendly_name: Album 25 | value_template: "{{ (state_attr('media_player.basement_desk', 'media_album_name')) }}" 26 | 27 | # ************************************ 28 | # SWITCHES (non-MQTT) 29 | # ************************************ 30 | 31 | 32 | # ************************************ 33 | # MQTT ENTITIES 34 | # ************************************ 35 | mqtt: 36 | # ================== 37 | # BINARY SENSORS 38 | # ================== 39 | binary_sensor: 40 | # Internet 41 | - name: "Desk Amp Internet" 42 | state_topic: "stat/deskamp/internet" 43 | payload_on: "1" 44 | payload_off: "0" 45 | 46 | # Ethernet 47 | - name: "Desk Amp Ethernet" 48 | state_topic: "stat/deskamp/ethernet" 49 | payload_on: "1" 50 | payload_off: "0" 51 | 52 | # Wifi 53 | - name: "Desk Amp WiFi" 54 | state_topic: "stat/deskamp/wifi" 55 | payload_on: "1" 56 | payload_off: "0" 57 | 58 | # Bluetooth 59 | - name: "Desk Amp Bluetooth" 60 | state_topic: "stat/deskamp/btconnect" 61 | payload_on: "1" 62 | payload_off: "0" 63 | 64 | # Audio Output 65 | - name: "Desk Amp Audio" 66 | state_topic: "stat/deskamp/audio" 67 | payload_on: "1" 68 | payload_off: "0" 69 | 70 | # Playing 71 | - name: "Desk Amp Playing" 72 | state_topic: "stat/deskamp/playing" 73 | payload_on: "1" 74 | payload_off: "0" 75 | 76 | # Fixed Volume 77 | - name: "Desk Amp Fixed Volume" 78 | state_topic: "stat/deskamp/fixedvol" 79 | payload_on: "1" 80 | payload_off: "0" 81 | 82 | # ================== 83 | # SENSORS 84 | # ================== 85 | sensor: 86 | # Active Source 87 | - name: "Desk Amp Source" 88 | state_topic: "stat/deskamp/source" 89 | 90 | # Vol 91 | - name: "Desk Amp Volume" 92 | state_topic: "stat/deskamp/volume" 93 | 94 | # Device Name 95 | - name: "Desk Amp Device Name" 96 | state_topic: "stat/deskamp/devicename" 97 | 98 | # Firmware Version 99 | - name: "Desk Amp Firmware" 100 | state_topic: "stat/deskamp/version" 101 | 102 | # Power On Source 103 | - name: "Desk Amp Power On Source" 104 | state_topic: "stat/deskamp/poweronsource" 105 | 106 | # Channel 107 | - name: "Desk Amp Channel" 108 | state_topic: "stat/deskamp/channel" 109 | 110 | # Multiroom Mode 111 | - name: "Desk Amp Multiroom Mode" 112 | state_topic: "stat/deskamp/multiroom" 113 | 114 | # Last UART Command 115 | - name: "Desk Amp Last UART" 116 | state_topic: "stat/deskamp/uart" 117 | 118 | # Last IR Code 119 | - name: "Desk Amp Last IR Code" 120 | state_topic: "stat/deskamp/ircode" 121 | 122 | # ================== 123 | # SWITCHES 124 | # ================== 125 | # Source switches for BT, NET, USBDAC, USB, LINE-IN, OPT, STANDBY* (standby handled with SYS) 126 | switch: 127 | # Bluetooth 128 | - name: "Desk Amp Source Bluetooth" 129 | state_topic: "stat/deskamp/source" 130 | value_template: "{{ value == 'BT' }}" 131 | command_topic: "cmnd/deskamp/source" 132 | payload_on: "BT" 133 | payload_off: "" 134 | state_on: true 135 | state_off: false 136 | retain: false 137 | # Network 138 | - name: "Desk Amp Source Network" 139 | state_topic: "stat/deskamp/source" 140 | value_template: "{{ value == 'NET' }}" 141 | command_topic: "cmnd/deskamp/source" 142 | payload_on: "NET" 143 | payload_off: "" 144 | state_on: true 145 | state_off: false 146 | retain: false 147 | # USBDAC 148 | - name: "Desk Amp Source USBDAC" 149 | state_topic: "stat/deskamp/source" 150 | value_template: "{{ value == 'USBDAC' }}" 151 | command_topic: "cmnd/deskamp/source" 152 | payload_on: "USBDAC" 153 | payload_off: "" 154 | state_on: true 155 | state_off: false 156 | retain: false 157 | # USB 158 | - name: "Desk Amp Source USB" 159 | state_topic: "stat/deskamp/source" 160 | value_template: "{{ value == 'USB' }}" 161 | command_topic: "cmnd/deskamp/source" 162 | payload_on: "USB" 163 | payload_off: "" 164 | state_on: true 165 | state_off: false 166 | retain: false 167 | # Line In 168 | - name: "Desk Amp Source Line In" 169 | state_topic: "stat/deskamp/source" 170 | value_template: "{{ value == 'LINE-IN' }}" 171 | command_topic: "cmnd/deskamp/source" 172 | payload_on: "LINE-IN" 173 | payload_off: "" 174 | state_on: true 175 | state_off: false 176 | retain: false 177 | # Optical 178 | - name: "Desk Amp Source Optical" 179 | state_topic: "stat/deskamp/source" 180 | value_template: "{{ value == 'OPT' }}" 181 | command_topic: "cmnd/deskamp/source" 182 | payload_on: "OPT" 183 | payload_off: "" 184 | state_on: true 185 | state_off: false 186 | retain: false 187 | # Standby 188 | - name: "Desk Amp Source Standby" 189 | state_topic: "stat/deskamp/source" 190 | value_template: "{{ value == 'STANDBY' }}" 191 | command_topic: "cmnd/deskamp/system" 192 | payload_on: "STANDBY" 193 | payload_off: "REBOOT" 194 | state_on: true 195 | state_off: false 196 | retain: false 197 | 198 | # Pregain 199 | - name: "Desk Amp Pregain" 200 | state_topic: "stat/deskamp/pregain" 201 | value_template: "{{value == '1' }}" 202 | command_topic: "cmnd/deskamp/pregain" 203 | payload_on: "1" 204 | payload_off: "0" 205 | state_on: true 206 | state_off: false 207 | retain: false 208 | 209 | # Virtual Bass 210 | - name: "Desk Amp Virtual Bass" 211 | state_topic: "stat/deskamp/virtbass" 212 | value_template: "{{ value == '1' }}" 213 | command_topic: "cmnd/deskamp/virtbass" 214 | payload_on: "1" 215 | payload_off: "0" 216 | state_on: true 217 | state_off: false 218 | retain: false 219 | 220 | # Mute 221 | - name: "Desk Amp Mute" 222 | state_topic: "stat/deskamp/mute" 223 | value_template: "{{ value == '1' }}" 224 | command_topic: "cmnd/deskamp/mute" 225 | payload_on: "1" 226 | payload_off: "0" 227 | state_on: true 228 | state_off: false 229 | retain: false 230 | 231 | # LED 232 | - name: "Desk Amp LED" 233 | state_topic: "stat/deskamp/led" 234 | value_template: "{{ value == '1' }}" 235 | command_topic: "cmnd/deskamp/led" 236 | payload_on: "1" 237 | payload_off: "0" 238 | state_on: true 239 | state_off: false 240 | retain: false 241 | 242 | # IR Receiver 243 | - name: "Desk Amp IR Receiver" 244 | state_topic: "stat/deskamp/irmode" 245 | value_template: "{{ value == '1' }}" 246 | command_topic: "cmnd/deskamp/irmode" 247 | payload_on: "1" 248 | payload_off: "0" 249 | state_on: true 250 | state_off: false 251 | retain: false 252 | 253 | # Key Beep 254 | - name: "Desk Amp Key Beep" 255 | state_topic: "stat/deskamp/beep" 256 | value_template: "{{ value == '1' }}" 257 | command_topic: "cmnd/deskamp/beep" 258 | payload_on: "1" 259 | payload_off: "0" 260 | state_on: true 261 | state_off: false 262 | retain: false 263 | 264 | # Voice Prompts 265 | - name: "Desk Amp Voice Prompts" 266 | state_topic: "stat/deskamp/voice" 267 | value_template: "{{ value == '1' }}" 268 | command_topic: "cmnd/deskamp/voice" 269 | payload_on: "1" 270 | payload_off: "0" 271 | state_on: true 272 | state_off: false 273 | retain: false 274 | 275 | # ************************************ 276 | # HELPER ENTITIES 277 | # ************************************ 278 | 279 | # ====================== 280 | # INPUT BOOLEANS 281 | # ====================== 282 | 283 | 284 | # ====================== 285 | # INPUT NUMBERS 286 | # ====================== 287 | input_number: 288 | # -------------------------- 289 | # Master Volume: 290 | # -------------------------- 291 | desk_amp_volume: 292 | name: Volume 293 | #initial: 30 294 | min: 0 295 | max: 100 296 | step: 1 297 | mode: slider 298 | # -------------------------- 299 | # Balance: 300 | # -------------------------- 301 | desk_amp_balance: 302 | name: Balance 303 | #initial: 0 304 | min: -100 305 | max: 100 306 | step: 1 307 | mode: slider 308 | # -------------------------- 309 | # Treble: 310 | # -------------------------- 311 | desk_amp_treble: 312 | name: Treble 313 | #initial: 0 314 | min: -10 315 | max: 10 316 | step: 1 317 | mode: slider 318 | # -------------------------- 319 | # Midrange: 320 | # -------------------------- 321 | desk_amp_midrange: 322 | name: Midrange 323 | #initial: 0 324 | min: -10 325 | max: 10 326 | step: 1 327 | mode: slider 328 | # -------------------------- 329 | # Bass: 330 | # -------------------------- 331 | desk_amp_bass: 332 | name: Bass 333 | #initial: 0 334 | min: -10 335 | max: 10 336 | step: 1 337 | mode: slider 338 | 339 | # ----------------------- 340 | # Max Volume 341 | # ------------------------ 342 | desk_amp_max_volume: 343 | name: Max Volume 344 | #initial: 95 345 | min: 30 346 | max: 100 347 | mode: slider 348 | 349 | # ====================== 350 | # INPUT SELECTS 351 | # ====================== 352 | input_select: 353 | desk_amp_loopmode: 354 | name: "Loop Mode" 355 | options: 356 | - REPEATALL 357 | - REPEATONE 358 | - REPEATSHUFFLE 359 | - SHUFFLE 360 | - SEQUENCE 361 | #initial: SEQUENCE 362 | 363 | # ===================== 364 | # INPUT TEXT 365 | # ===================== 366 | input_text: 367 | desk_amp_uart_command: 368 | name: UART Command 369 | 370 | 371 | # //////////////////////////////////////////////////////////////////////// 372 | # AUTOMATIONS 373 | # //////////////////////////////////////////////////////////////////////// 374 | automation: 375 | # ================================== 376 | # MQTT Set/Gets 377 | # Set: Publishes value to amp when local control changes 378 | # Get: Updates local control when MQTT changes 379 | # ================================== 380 | # Volume 381 | - alias: Desk Amp Volume Set 382 | id: fe580fa1-915b-4bd9-845e-dcb337e8151a 383 | trigger: 384 | platform: state 385 | entity_id: input_number.desk_amp_volume 386 | action: 387 | service: mqtt.publish 388 | data: 389 | topic: "cmnd/deskamp/volume" 390 | payload: "{{ states('input_number.desk_amp_volume') | int }}" 391 | - alias: Desk Amp Volume Get 392 | id: c0363a4e-0636-4289-b8d5-3116d9204d5c 393 | trigger: 394 | platform: mqtt 395 | topic: "stat/deskamp/volume" 396 | action: 397 | service: input_number.set_value 398 | data: 399 | entity_id: input_number.desk_amp_volume 400 | value: "{{ trigger.payload }}" 401 | 402 | # Balance 403 | - alias: Desk Amp Balance Set 404 | id: bc45575c-6b39-4775-8c72-b7aca577f430 405 | trigger: 406 | platform: state 407 | entity_id: input_number.desk_amp_balance 408 | action: 409 | service: mqtt.publish 410 | data: 411 | topic: "cmnd/deskamp/balance" 412 | payload: "{{ states('input_number.desk_amp_balance') | int }}" 413 | - alias: Desk Amp Balance Get 414 | id: 63382d2f-2eee-420a-a17e-ac35f0498a1c 415 | trigger: 416 | platform: mqtt 417 | topic: "stat/deskamp/balance" 418 | action: 419 | service: input_number.set_value 420 | data: 421 | entity_id: input_number.desk_amp_balance 422 | value: "{{ trigger.payload }}" 423 | 424 | # Treble 425 | - alias: Desk Amp Treble Set 426 | id: 8d59d377-923d-4098-b49e-f3a1fa0faca5 427 | trigger: 428 | platform: state 429 | entity_id: input_number.desk_amp_treble 430 | action: 431 | service: mqtt.publish 432 | data: 433 | topic: "cmnd/deskamp/treble" 434 | payload: "{{ states('input_number.desk_amp_treble') | int }}" 435 | - alias: Desk Amp Treble Get 436 | id: d8c8fa7b-7b8e-4317-9503-216df1ec8513 437 | trigger: 438 | platform: mqtt 439 | topic: "stat/deskamp/treble" 440 | action: 441 | service: input_number.set_value 442 | data: 443 | entity_id: input_number.desk_amp_treble 444 | value: "{{ trigger.payload }}" 445 | 446 | # Midrange 447 | - alias: Desk Amp Midrange Set 448 | id: 340c932d-5aea-4acc-bf1e-5b8ba899b2c4 449 | trigger: 450 | platform: state 451 | entity_id: input_number.desk_amp_midrange 452 | action: 453 | service: mqtt.publish 454 | data: 455 | topic: "cmnd/deskamp/midrange" 456 | payload: "{{ states('input_number.desk_amp_midrange') | int }}" 457 | - alias: Desk Amb Midrange Get 458 | id: 60388e3e-f7c0-4ce6-8d59-1102d6dbda22 459 | trigger: 460 | platform: mqtt 461 | topic: "stat/deskamp/midrange" 462 | action: 463 | service: input_number.set_value 464 | data: 465 | entity_id: input_number.desk_amp_midrange 466 | value: "{{ trigger.payload }}" 467 | 468 | # Bass 469 | - alias: Desk Amp Bass Set 470 | id: 80d30263-bdac-4a91-8c3b-7bdd74590ce5 471 | trigger: 472 | platform: state 473 | entity_id: input_number.desk_amp_bass 474 | action: 475 | service: mqtt.publish 476 | data: 477 | topic: "cmnd/deskamp/bass" 478 | payload: "{{ states('input_number.desk_amp_bass') }}" 479 | - alias: Desk Amp Bass Get 480 | id: ba6ae75c-3407-4055-be36-4aabb67b96a6 481 | trigger: 482 | platform: mqtt 483 | topic: "stat/deskamp/bass" 484 | action: 485 | service: input_number.set_value 486 | data: 487 | entity_id: input_number.desk_amp_bass 488 | value: "{{ trigger.payload }}" 489 | 490 | # Max Volume 491 | - alias: Desk Amp Max Volume Set 492 | id: 2c7c4cd2-de0c-4c87-b3a6-71575fdaa6ff 493 | trigger: 494 | platform: state 495 | entity_id: input_number.desk_amp_max_volume 496 | action: 497 | service: mqtt.publish 498 | data: 499 | topic: "cmnd/deskamp/maxvol" 500 | payload: "{{ states('input_number.desk_amp_max_volume') | int }}" 501 | - alias: Desk Amp Max Volume Get 502 | id: f82b70ef-79d5-47d2-98d8-1718473a0786 503 | trigger: 504 | platform: mqtt 505 | topic: "stat/deskamp/maxvol" 506 | action: 507 | service: input_number.set_value 508 | data: 509 | entity_id: input_number.desk_amp_max_volume 510 | value: "{{ trigger.payload }}" 511 | 512 | # Loop Mode 513 | - alias: Desk Amp Loop Mode Set 514 | id: f0b9ec2e-1b2f-4e2c-a3ed-41f87655163f 515 | trigger: 516 | platform: state 517 | entity_id: input_select.desk_amp_loopmode 518 | action: 519 | service: mqtt.publish 520 | data: 521 | topic: "cmnd/deskamp/loopmode" 522 | payload: "{{ states('input_select.desk_amp_loopmode') }}" 523 | - alias: Desk Amp Loop Mode Get 524 | id: 6f17f307-1414-4ce5-a7a8-8fc163e02b09 525 | trigger: 526 | platform: mqtt 527 | topic: "stat/deskamp/loopmode" 528 | action: 529 | service: input_select.select_option 530 | data: 531 | entity_id: input_select.desk_amp_loopmode 532 | option: "{{ trigger.payload }}" 533 | 534 | # UART Command (set only) 535 | - alias: Desk Amp UART Command 536 | id: e9d38eb6-a64b-444d-bd8b-7e276927aefd 537 | trigger: 538 | platform: state 539 | entity_id: input_text.desk_amp_uart_command 540 | action: 541 | - service: mqtt.publish 542 | data: 543 | topic: "cmnd/deskamp/uart" 544 | payload: "{{ states('input_text.desk_amp_uart_command') }}" 545 | - service: input_text.set_value 546 | data: 547 | entity_id: input_text.desk_amp_uart_command 548 | value: "" 549 | 550 | # //////////////////////////////////////////////////////////////////////// 551 | # SCRIPTS 552 | # //////////////////////////////////////////////////////////////////////// 553 | # These are needed for stop/pause/prev/next buttons since UART does not return anything 554 | script: 555 | desk_amp_stop_button: 556 | alias: Stop Button 557 | sequence: 558 | - service: mqtt.publish 559 | data: 560 | topic: "cmnd/deskamp/stop" 561 | retain: false 562 | 563 | desk_amp_pause_button: 564 | alias: PlayPause Button 565 | sequence: 566 | - service: mqtt.publish 567 | data: 568 | topic: "cmnd/deskamp/pause" 569 | retain: false 570 | 571 | desk_amp_next_button: 572 | alias: Next Button 573 | sequence: 574 | - service: mqtt.publish 575 | data: 576 | topic: "cmnd/deskamp/next" 577 | retain: false 578 | 579 | desk_amp_previous_button: 580 | alias: Previous Button 581 | sequence: 582 | - service: mqtt.publish 583 | data: 584 | topic: "cmnd/deskamp/previous" 585 | retain: false -------------------------------------------------------------------------------- /sirius-tft-touch-add-on/deskamp_package_add_ons.yaml: -------------------------------------------------------------------------------- 1 | # ======================================= 2 | # These are the ADD-ONs to the existing 3 | # Home Assistant Desktop-Amp package 4 | # See the Github /homeassistant folder for 5 | # the rest of the package 6 | # (these add-ons will not work alone) 7 | # ======================================= 8 | 9 | # ===================== 10 | # INPUT TEXT 11 | # ===================== 12 | input_text: 13 | sirius_now_playing: 14 | name: Sirius Now Playing 15 | sirius_manual_channel: 16 | name: Sirius Manual Channel 17 | initial: ' ' 18 | 19 | # ==================== 20 | # RESTFUL Commands 21 | # =================== 22 | rest_command: 23 | play_sirius_station: 24 | url: 'http://192.168.1.103/httpapi.asp?command=setPlayerCmd:play:http://192.168.1.102:9000/{{ station_id }}.m3u8' 25 | 26 | # ==================== 27 | # Automations 28 | # ==================== 29 | automation: 30 | # Standby (toggles TFT Display off/on) 31 | - alias: Desk Amp Standy 32 | trigger: 33 | platform: state 34 | entity_id: switch.desk_amp_source_standby 35 | from: 'off' 36 | to: 'on' 37 | action: 38 | service: light.turn_off 39 | target: 40 | entity_id: light.sirius_tft_backlight 41 | 42 | - alias: Desk Amp Reboot 43 | trigger: 44 | platform: state 45 | entity_id: switch.desk_amp_source_standby 46 | from: 'on' 47 | to: 'off' 48 | action: 49 | service: light.turn_on 50 | target: 51 | entity_id: light.sirius_tft_backlight 52 | 53 | - alias: Sirius Manual Channel 54 | mode: single 55 | max_exceeded: silent 56 | trigger: 57 | platform: state 58 | entity_id: input_text.sirius_manual_channel 59 | action: 60 | - service: rest_command.play_sirius_station 61 | data: 62 | station_id: "{{ states('input_text.sirius_manual_channel') }}" 63 | - service: input_text.set_value 64 | target: 65 | entity_id: input_text.sirius_now_playing 66 | data: 67 | value: "{{ states('input_text.sirius_manual_channel') }}" 68 | - service: input_text.set_value 69 | target: 70 | entity_id: input_text.sirius_manual_channel 71 | data: 72 | value: "" 73 | 74 | - alias: Sirius Clear Now Playing 75 | trigger: 76 | platform: mqtt 77 | topic: "stat/deskamp/source" 78 | condition: 79 | - condition: template 80 | value_template: "{{ not (is_state('sensor.desk_amp_source', 'NET')) }}" 81 | action: 82 | - service: input_text.set_value 83 | target: 84 | entity_id: input_text.sirius_now_playing 85 | data: 86 | value: '' 87 | 88 | # ===================== 89 | # Scripts 90 | # ===================== 91 | # ----------------------------------- 92 | # ILI9341 TFT Touch Scripts 93 | # ------------------------------------ 94 | sirius_power_up_amp: 95 | alias: Sirius Power Up Amp 96 | sequence: 97 | - service: switch.turn_off # Turn off the standby switch 98 | target: 99 | entity_id: switch.desk_amp_source_standby 100 | 101 | sirius_power_down_amp: 102 | alias: Sirius Power Down Amp 103 | sequence: 104 | - service: switch.turn_on # Turn on the standby switch 105 | target: 106 | entity_id: switch.desk_amp_source_standby 107 | 108 | sirius_reboot_amp: 109 | alias: Sirius Reboot Amp 110 | sequence: 111 | - service: mqtt.publish 112 | data: 113 | topic: "cmnd/deskamp/system" 114 | payload: "REBOOT" 115 | 116 | sirius_reboot_tft: 117 | alias: Sirius Reboot TFT 118 | sequence: 119 | - service: button.press 120 | target: 121 | entity_id: button.sirius_tft_restart 122 | 123 | # --------------------------------------------- 124 | # Sirius XM Button Scripts - substitute your own favorite channels 125 | # --------------------------------------------- 126 | sirius_classicrewind: 127 | alias: Sirius Classic Rewind 128 | sequence: 129 | - service: rest_command.play_sirius_station 130 | data: 131 | station_id: "classicrewind" 132 | - service: input_text.set_value 133 | target: 134 | entity_id: input_text.sirius_now_playing 135 | data: 136 | value: "Classic Rewind" 137 | sirius_redwhitebooze: 138 | alias: Sirius Red White Booze 139 | sequence: 140 | - service: rest_command.play_sirius_station 141 | data: 142 | station_id: "9178" 143 | - service: input_text.set_value 144 | target: 145 | entity_id: input_text.sirius_now_playing 146 | data: 147 | value: "Red White and Booze" 148 | sirius_classicrock: 149 | alias: Sirius Classic Rock 150 | sequence: 151 | - service: rest_command.play_sirius_station 152 | data: 153 | station_id: "9375" 154 | - service: input_text.set_value 155 | target: 156 | entity_id: input_text.sirius_now_playing 157 | data: 158 | value: "Classic Rock Party" 159 | sirius_roadtrip: 160 | alias: Sirius Road Trip 161 | sequence: 162 | - service: rest_command.play_sirius_station 163 | data: 164 | station_id: "9415" 165 | - service: input_text.set_value 166 | target: 167 | entity_id: input_text.sirius_now_playing 168 | data: 169 | value: "Road Trip Radio" 170 | sirius_50son5: 171 | alias: Sirius 50s on 5 172 | sequence: 173 | - service: rest_command.play_sirius_station 174 | data: 175 | station_id: "siriusgold" 176 | - service: input_text.set_value 177 | target: 178 | entity_id: input_text.sirius_now_playing 179 | data: 180 | value: "50's on 5" 181 | sirius_60son6: 182 | alias: Sirius 60s on 6 183 | sequence: 184 | - service: rest_command.play_sirius_station 185 | data: 186 | station_id: "60svibrations" 187 | - service: input_text.set_value 188 | target: 189 | entity_id: input_text.sirius_now_playing 190 | data: 191 | value: "60's on 6" 192 | sirius_70son7: 193 | alias: Sirius 70s on 7 194 | sequence: 195 | - service: rest_command.play_sirius_station 196 | data: 197 | station_id: "totally70s" 198 | - service: input_text.set_value 199 | target: 200 | entity_id: input_text.sirius_now_playing 201 | data: 202 | value: "70's on 7" 203 | sirius_80son8: 204 | alias: Sirius 80s on 8 205 | sequence: 206 | - service: rest_command.play_sirius_station 207 | data: 208 | station_id: "big80s" 209 | - service: input_text.set_value 210 | target: 211 | entity_id: input_text.sirius_now_playing 212 | data: 213 | value: "80's on 8" 214 | sirius_7080pop: 215 | alias: Sirius 7080 Pop 216 | sequence: 217 | - service: rest_command.play_sirius_station 218 | data: 219 | station_id: "9372" 220 | - service: input_text.set_value 221 | target: 222 | entity_id: input_text.sirius_now_playing 223 | data: 224 | value: "70's/80's Pop" 225 | sirius_classicvinyl: 226 | alias: Sirius Classic Vinyl 227 | sequence: 228 | - service: rest_command.play_sirius_station 229 | data: 230 | station_id: "classicvinyl" 231 | - service: input_text.set_value 232 | target: 233 | entity_id: input_text.sirius_now_playing 234 | data: 235 | value: "Classic Vinyl" 236 | sirius_roaddog: 237 | alias: Sirius Roaddog 238 | sequence: 239 | - service: rest_command.play_sirius_station 240 | data: 241 | station_id: "roaddogtrucking" 242 | - service: input_text.set_value 243 | target: 244 | entity_id: input_text.sirius_now_playing 245 | data: 246 | value: "Road Dog Trucking" 247 | sirius_roadhouse: 248 | alias: Sirius Roadhouse 249 | sequence: 250 | - service: rest_command.play_sirius_station 251 | data: 252 | station_id: "theroadhouse" 253 | - service: input_text.set_value 254 | target: 255 | entity_id: input_text.sirius_now_playing 256 | data: 257 | value: "Willie's Roadhouse" 258 | sirius_boneyard: 259 | alias: Sirius Boneyard 260 | sequence: 261 | - service: rest_command.play_sirius_station 262 | data: 263 | station_id: "buzzsaw" 264 | - service: input_text.set_value 265 | target: 266 | entity_id: input_text.sirius_now_playing 267 | data: 268 | value: "Ozzy's Boneyard" 269 | sirius_hairnation: 270 | alias: Sirius Hair Nation 271 | sequence: 272 | - service: rest_command.play_sirius_station 273 | data: 274 | station_id: "hairnation" 275 | - service: input_text.set_value 276 | target: 277 | entity_id: input_text.sirius_now_playing 278 | data: 279 | value: "Hair Nation" 280 | sirius_oldiesparty: 281 | alias: Sirius Oldies Party 282 | sequence: 283 | - service: rest_command.play_sirius_station 284 | data: 285 | station_id: "9378" 286 | - service: input_text.set_value 287 | target: 288 | entity_id: input_text.sirius_now_playing 289 | data: 290 | value: "Oldies Party" 291 | sirius_margaritaville: 292 | alias: Sirius Margaritaville 293 | sequence: 294 | - service: rest_command.play_sirius_station 295 | data: 296 | station_id: "radiomargaritaville" 297 | - service: input_text.set_value 298 | target: 299 | entity_id: input_text.sirius_now_playing 300 | data: 301 | value: "Radio Margaritaville" 302 | sirius_womencountry: 303 | alias: Sirius Women of Country 304 | sequence: 305 | - service: rest_command.play_sirius_station 306 | data: 307 | station_id: "9421" 308 | - service: input_text.set_value 309 | target: 310 | entity_id: input_text.sirius_now_playing 311 | data: 312 | value: "Women of Country" 313 | sirius_outlaw: 314 | alias: Sirius Outlaw Country 315 | sequence: 316 | - service: rest_command.play_sirius_station 317 | data: 318 | station_id: "outlawcountry" 319 | - service: input_text.set_value 320 | target: 321 | entity_id: input_text.sirius_now_playing 322 | data: 323 | value: "Outlaw Country" 324 | sirius_primecountry: 325 | alias: Sirius Prime Country 326 | sequence: 327 | - service: rest_command.play_sirius_station 328 | data: 329 | station_id: "primecountry" 330 | - service: input_text.set_value 331 | target: 332 | entity_id: input_text.sirius_now_playing 333 | data: 334 | value: "Prime Country" 335 | sirius_highway: 336 | alias: Sirius Highway 337 | sequence: 338 | - service: rest_command.play_sirius_station 339 | data: 340 | station_id: "newcountry" 341 | - service: input_text.set_value 342 | target: 343 | entity_id: input_text.sirius_now_playing 344 | data: 345 | value: "The Highway" 346 | sirius_elvis: 347 | alias: Sirius Elvis 348 | sequence: 349 | - service: rest_command.play_sirius_station 350 | data: 351 | station_id: "elvisradio" 352 | - service: input_text.set_value 353 | target: 354 | entity_id: input_text.sirius_now_playing 355 | data: 356 | value: "Elvis Radio" 357 | sirius_bluegrass: 358 | alias: Sirius Bluegrass Junction 359 | sequence: 360 | - service: rest_command.play_sirius_station 361 | data: 362 | station_id: "bluegrass" 363 | - service: input_text.set_value 364 | target: 365 | entity_id: input_text.sirius_now_playing 366 | data: 367 | value: "Bluegrass Junction" 368 | sirius_sinatra: 369 | alias: Sirius Sinatra 370 | sequence: 371 | - service: rest_command.play_sirius_station 372 | data: 373 | station_id: "siriuslysinatra" 374 | - service: input_text.set_value 375 | target: 376 | entity_id: input_text.sirius_now_playing 377 | data: 378 | value: "Siriusly Sinatra" 379 | sirius_studio54: 380 | alias: Sirius Studio54 381 | sequence: 382 | - service: rest_command.play_sirius_station 383 | data: 384 | station_id: "9145" 385 | - service: input_text.set_value 386 | target: 387 | entity_id: input_text.sirius_now_playing 388 | data: 389 | value: "Studio 54" 390 | sirius_foxsports: 391 | alias: Sirius Fox Sports 392 | sequence: 393 | - service: rest_command.play_sirius_station 394 | data: 395 | station_id: "9445" 396 | - service: input_text.set_value 397 | target: 398 | entity_id: input_text.sirius_now_playing 399 | data: 400 | value: "FOX Sports" 401 | sirius_cbssports: 402 | alias: Sirius CBS Sports 403 | sequence: 404 | - service: rest_command.play_sirius_station 405 | data: 406 | station_id: "9473" 407 | - service: input_text.set_value 408 | target: 409 | entity_id: input_text.sirius_now_playing 410 | data: 411 | value: "CBS Sports Radio" 412 | sirius_nbcsports: 413 | alias: Sirius NBC Sports 414 | sequence: 415 | - service: rest_command.play_sirius_station 416 | data: 417 | station_id: "9452" 418 | - service: input_text.set_value 419 | target: 420 | entity_id: input_text.sirius_now_playing 421 | data: 422 | value: "NBC Sports Radio" 423 | sirius_espn: 424 | alias: Sirius ESPN 425 | sequence: 426 | - service: rest_command.play_sirius_station 427 | data: 428 | station_id: "espnradio" 429 | - service: input_text.set_value 430 | target: 431 | entity_id: input_text.sirius_now_playing 432 | data: 433 | value: "ESPN Radio" 434 | sirius_colts: 435 | alias: Sirius Colts 436 | sequence: 437 | - service: rest_command.play_sirius_station 438 | data: 439 | station_id: "9159" 440 | - service: input_text.set_value 441 | target: 442 | entity_id: input_text.sirius_now_playing 443 | data: 444 | value: "Indianapolis Colts Radio" 445 | sirius_pacers: 446 | alias: Sirius Pacers 447 | sequence: 448 | - service: rest_command.play_sirius_station 449 | data: 450 | station_id: "9277" 451 | - service: input_text.set_value 452 | target: 453 | entity_id: input_text.sirius_now_playing 454 | data: 455 | value: "Indiana Pacers Radio" 456 | sirius_nflradio: 457 | alias: Sirius NFL Radio 458 | sequence: 459 | - service: rest_command.play_sirius_station 460 | data: 461 | station_id: "siriusnflradio" 462 | - service: input_text.set_value 463 | target: 464 | entity_id: input_text.sirius_now_playing 465 | data: 466 | value: "Sirius NFL Radio" 467 | sirius_maddog: 468 | alias: Sirius Mad Dog Sports 469 | sequence: 470 | - service: rest_command.play_sirius_station 471 | data: 472 | station_id: "8213" 473 | - service: input_text.set_value 474 | target: 475 | entity_id: input_text.sirius_now_playing 476 | data: 477 | value: "Mad Dog Sports Radio" 478 | sirius_foxnews: 479 | alias: Sirius Fox News 480 | sequence: 481 | - service: rest_command.play_sirius_station 482 | data: 483 | station_id: "9410" 484 | - service: input_text.set_value 485 | target: 486 | entity_id: input_text.sirius_now_playing 487 | data: 488 | value: "FOX News Headlines" 489 | sirius_npr: 490 | alias: Sirius NPR 491 | sequence: 492 | - service: rest_command.play_sirius_station 493 | data: 494 | station_id: "nprnow" 495 | - service: input_text.set_value 496 | target: 497 | entity_id: input_text.sirius_now_playing 498 | data: 499 | value: "NPR Now" 500 | sirius_howard100: 501 | alias: Sirius Howard 100 502 | sequence: 503 | - service: rest_command.play_sirius_station 504 | data: 505 | station_id: "howardstern100" 506 | - service: input_text.set_value 507 | target: 508 | entity_id: input_text.sirius_now_playing 509 | data: 510 | value: "Howard Stern 100" 511 | sirius_howard101: 512 | alias: Sirius Howard 101 513 | sequence: 514 | - service: rest_command.play_sirius_station 515 | data: 516 | station_id: "howardstern101" 517 | - service: input_text.set_value 518 | target: 519 | entity_id: input_text.sirius_now_playing 520 | data: 521 | value: "Howard Stern 101" 522 | sirius_laughusa: 523 | alias: Sirius Laugh USA 524 | sequence: 525 | - service: rest_command.play_sirius_station 526 | data: 527 | station_id: "laughbreak" 528 | - service: input_text.set_value 529 | target: 530 | entity_id: input_text.sirius_now_playing 531 | data: 532 | value: "Laugh USA" 533 | sirius_comedycentral: 534 | alias: Sirius Comedy Centry 535 | sequence: 536 | - service: rest_command.play_sirius_station 537 | data: 538 | station_id: "9356" 539 | - service: input_text.set_value 540 | target: 541 | entity_id: input_text.sirius_now_playing 542 | data: 543 | value: "Comedy Central Radio" 544 | sirius_rawdog: 545 | alias: Sirius Rawdog 546 | sequence: 547 | - service: rest_command.play_sirius_station 548 | data: 549 | station_id: "rawdog" 550 | - service: input_text.set_value 551 | target: 552 | entity_id: input_text.sirius_now_playing 553 | data: 554 | value: "Raw Dog Comedy Hits" 555 | sirius_comedyroundup: 556 | alias: Sirius Comedy Roundup 557 | sequence: 558 | - service: rest_command.play_sirius_station 559 | data: 560 | station_id: "blucollarcomedy" 561 | - service: input_text.set_value 562 | target: 563 | entity_id: input_text.sirius_now_playing 564 | data: 565 | value: "Comedy Roundup" 566 | sirius_comedygreats: 567 | alias: Sirius Comedy Greats 568 | sequence: 569 | - service: rest_command.play_sirius_station 570 | data: 571 | station_id: "9408" 572 | - service: input_text.set_value 573 | target: 574 | entity_id: input_text.sirius_now_playing 575 | data: 576 | value: "SiriusXM Comedy Greats" 577 | sirius_lolradio: 578 | alias: Sirius LOL Radio 579 | sequence: 580 | - service: rest_command.play_sirius_station 581 | data: 582 | station_id: "9469" 583 | - service: input_text.set_value 584 | target: 585 | entity_id: input_text.sirius_now_playing 586 | data: 587 | value: "Laugh Out Loud Radio" 588 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /src/arylic_amp.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * Interface to Arylic Amp v4 3 | * Version: 0.28 - Add disable IR settings option to disable IR command processing due to rogue signals 4 | * - Also added this as an MQTT command so it can be toggled from front end. 5 | * Last Update: 1/29/2023 6 | */ 7 | #include 8 | #include // https://github.com/espressif/arduino-esp32 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include // https://github.com/adafruit/Adafruit-GFX-Library 16 | #include // https://github.com/adafruit/Adafruit_SSD1306 17 | #include // https://github.com/Arduino-IRremote/Arduino-IRremote 18 | #include "Credentials.h" // File must exist in same folder as .ino. Edit as needed for project 19 | #include "Settings.h" // File must exist in same folder as .ino. Edit as needed for project 20 | 21 | //Display modes 22 | #define SOURCE 1 23 | #define VOLUME 2 24 | #define TITLE 3 25 | #define TRACK 4 26 | #define MUTE 5 27 | #define BLANK 6 //BLANK should always be last in list so that button click 'wraps' from last item to first 28 | 29 | //IR Commands 30 | struct IRCommands { 31 | uint16_t irCode; 32 | String uartCommand; 33 | }; 34 | 35 | /* UPDATE COMMAND FOR YOUR REMOTE HERE: 36 | first value is the code returned, second value is the UART command to issue 37 | UART commands that only accept a numeric value (e.g treble, bass and balance), use ++ for increase and -- for decrease and 38 | value will be increased or decreased by 1 within the permissible min/max values. 39 | Other commands may be added... just assure the UART command is valid. 40 | */ 41 | //============================== 42 | IRCommands remoteCommands[] = { 43 | {16, "VOL:-;"}, 44 | {17, "TRE:--;"}, 45 | {18, "BAS:--;"}, 46 | {19, "BAL:--;"}, 47 | {20, "VOL:+;"}, 48 | {21, "TRE:++;"}, 49 | {22, "BAS:++;"}, 50 | {23, "BAL:++;"}, 51 | {65, "POP;"}, //toggles pause/play 52 | {64, "SYS:STANDBY;"}, 53 | {68, "SRC:NET;"}, 54 | {69, "SRC:BT;"}, 55 | {88, "SRC:USB;"}, 56 | {89, "SRC:USBDAC;"}, 57 | {92, "MUT:T;"}, //toggles mute 58 | {93, "DISPMODE"} //This is not a UART command, but local to change display home mode 59 | }; 60 | //============================= 61 | 62 | //GLOBAL VARIABLES 63 | bool mqttConnected = false; //Will be enabled if defined and successful connnection made. This var should be checked upon any MQTT actin. 64 | long lastReconnectAttempt = 0; //If MQTT connected lost, attempt reconnenct 65 | uint16_t ota_time = ota_boot_time_window; 66 | bool initBoot = true; // Used to hide OTA Update on display on initial boot 67 | unsigned long currentMillis = 0; 68 | unsigned long standbyTimer = 0; 69 | 70 | //DISPLAY RELATED GLOBALS 71 | byte dispMode = bootDispMode; 72 | byte prevdispMode = bootDispMode; 73 | bool dispModeTempSource = true; 74 | bool standbyMode = false; 75 | unsigned long dispModeTemp_timer = 0; 76 | String dispSource = "NET"; 77 | byte dispVolume = 50; 78 | String dispMute = "OFF"; 79 | //Needed for IR remote command processing 80 | int dispBass = 0; 81 | int dispMidrange = 0; 82 | int dispTreble = 0; 83 | int dispBalance = 0; 84 | String dispTitle = "N/A"; 85 | String dispTrack = "N/A"; 86 | String dispArtist = "N/A"; 87 | String dispAlbum = "N/A"; 88 | 89 | //BUTTON DEBOUNCE (Rotary knob click) 90 | int dispBtnState; 91 | int lastBtnState = HIGH; 92 | unsigned long lastDebounceTime = 0; 93 | unsigned long debounceDelay = 100; 94 | 95 | 96 | //Setup Local Access point if enabled via WIFI Mode 97 | #if defined(WIFIMODE) && (WIFIMODE == 0 || WIFIMODE == 2) 98 | const char* APssid = AP_SSID; 99 | const char* APpassword = AP_PWD; 100 | #endif 101 | //Setup Wifi if enabled via WIFI Mode 102 | #if defined(WIFIMODE) && (WIFIMODE == 1 || WIFIMODE == 2) 103 | #include "Credentials.h" // Edit this file in the same directory as the .ino file and add your own credentials 104 | const char *ssid = SID; 105 | const char *password = PW; 106 | #endif 107 | //Setup MQTT if enabled - only available when WiFi is also enabled 108 | #if (WIFIMODE == 1 || WIFIMODE == 2) && (MQTTMODE == 1) // MQTT only available when on local wifi 109 | const char *mqttUser = MQTTUSERNAME; 110 | const char *mqttPW = MQTTPWD; 111 | const char *mqttClient = MQTTCLIENT; 112 | const char *mqttTopicSub = MQTT_TOPIC_SUB; 113 | //const char *mqttTopicPub = MQTT_TOPIC_PUB; 114 | //String strTopicPub = String(*mqttTopicPub); 115 | #endif 116 | 117 | WiFiClient espClient; 118 | PubSubClient client(espClient); 119 | WebServer server; 120 | //Define Display; 121 | Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); 122 | //Setup IR Receiver 123 | IRrecv irrecv(IR_RECV_PIN); 124 | decode_results irresults; 125 | 126 | // ============================================ 127 | // MAIN SETUP 128 | // ============================================ 129 | void setup() { 130 | // Serial monitor 131 | Serial.begin(115200); 132 | Serial.println("Booting..."); 133 | //Serial communication to amp 134 | Serial2.begin(115200, SERIAL_8N1, RX_PIN, TX_PIN); 135 | Serial2.setTimeout(1000); 136 | 137 | //---------------- 138 | //WiFi Connection 139 | //---------------- 140 | WiFi.setSleep(false); //Disable WiFi Sleep 141 | delay(200); 142 | // WiFi - AP Mode or both 143 | #if defined(WIFIMODE) && (WIFIMODE == 0 || WIFIMODE == 2) 144 | WiFi.hostname(WIFIHOSTNAME); 145 | WiFi.mode(WIFI_AP_STA); 146 | WiFi.softAP(APssid, APpassword); // IP is usually 192.168.4.1 147 | #endif 148 | // WiFi - Local network Mode or both 149 | #if defined(WIFIMODE) && (WIFIMODE == 1 || WIFIMODE == 2) 150 | byte count = 0; 151 | WiFi.begin(ssid, password); 152 | Serial.print("Connecting to WiFi"); 153 | while (WiFi.status() != WL_CONNECTED) { 154 | Serial.print("."); 155 | // Stop if cannot connect 156 | if (count >= 60) { 157 | // Could not connect to local WiFi 158 | Serial.println(); 159 | Serial.println("Could not connect to WiFi."); 160 | return; 161 | } 162 | delay(500); 163 | count++; 164 | } 165 | Serial.println(); 166 | Serial.println("Successfully connected to Wifi"); 167 | IPAddress ip = WiFi.localIP(); 168 | #endif 169 | //----------------------------------------- 170 | // Setup MQTT - only if enabled and on WiFi 171 | //----------------------------------------- 172 | #if defined(MQTTMODE) && (MQTTMODE == 1 && (WIFIMODE == 1 || WIFIMODE == 2)) 173 | byte mcount = 0; 174 | //char topicPub[32]; 175 | client.setServer(MQTTSERVER, MQTTPORT); 176 | client.setCallback(callback); 177 | Serial.print("Connecting to MQTT broker."); 178 | while (!client.connected( )) { 179 | Serial.print("."); 180 | client.connect(mqttClient, mqttUser, mqttPW); 181 | if (mcount >= 60) { 182 | Serial.println(); 183 | Serial.println("Could not connect to MQTT broker. MQTT disabled."); 184 | // Could not connect to MQTT broker 185 | return; 186 | } 187 | delay(500); 188 | mcount++; 189 | } 190 | mqttConnected = true; 191 | Serial.println(); 192 | Serial.println("Successfully connected to MQTT broker."); 193 | client.subscribe(MQTT_TOPIC_SUB"/#"); 194 | String outTopic = mqttTopicPub + "/mqtt"; 195 | client.publish(outTopic.c_str(), "connected", true); 196 | //v0.28 - output initial IRMode status from settings since this isn't a UART command 197 | outTopic = mqttTopicPub + "/irmode"; 198 | if (enableIR) { 199 | client.publish(outTopic.c_str(), "1"); 200 | } else { 201 | client.publish(outTopic.c_str(), "0"); 202 | } 203 | #endif 204 | //----------------------------- 205 | // Setup OTA Updates 206 | //----------------------------- 207 | ArduinoOTA.setHostname(OTA_HOSTNAME); 208 | ArduinoOTA.onStart([]() { 209 | String type; 210 | if (ArduinoOTA.getCommand() == U_FLASH) { 211 | type = "sketch"; 212 | } else { // U_FS 213 | type = "filesystem"; 214 | } 215 | // NOTE: if updating FS this would be the place to unmount FS using FS.end() 216 | }); 217 | ArduinoOTA.begin(); 218 | // Setup handlers for web calls for OTAUpdate and Restart 219 | server.on("/restart",[](){ 220 | server.send(200, "text/html", "

Restarting...

"); 221 | delay(1000); 222 | ESP.restart(); 223 | }); 224 | server.on("/otaupdate",[]() { 225 | server.send(200, "text/html", "

Ready for upload...

Start upload from IDE now

"); 226 | ota_flag = true; 227 | ota_time = ota_time_window; 228 | ota_time_elapsed = 0; 229 | }); 230 | server.begin(); 231 | 232 | // ------------------------------------------------------ 233 | // Initialize Display and show splash screen for 2 seconds 234 | // ------------------------------------------------------ 235 | display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDR); 236 | display.display(); 237 | delay(2000); // Pause for 2 seconds 238 | // Clear the buffer 239 | display.clearDisplay(); 240 | // Set button (rotary know) for switching display mode 241 | pinMode(DISP_PIN, INPUT_PULLUP); 242 | // Set button for wake (wake from Standby - simulate button press) 243 | //pinMode(WAKE_PIN, OUTPUT); 244 | //digitalWrite(WAKE_PIN, LOW); //Set to low 245 | // Initialize IR Receiver 246 | irrecv.enableIRIn(); 247 | 248 | Serial.println("Staring main loop..."); 249 | //Force status update 250 | Serial2.flush(); 251 | Serial2.write("STA;"); 252 | 253 | } 254 | 255 | // ============================================================= 256 | // *************** MQTT Message Processing ********************* 257 | // ============================================================= 258 | void callback(char* topic, byte* payload, unsigned int length) { 259 | #if defined(MQTTMODE) && (MQTTMODE == 1 && (WIFIMODE == 1 || WIFIMODE == 2)) 260 | 261 | payload[length] = '\0'; 262 | String message = (char*)payload; 263 | String uartMsg; 264 | bool hasPayload = false; 265 | /* 266 | * Add any commands submitted here 267 | * Example: 268 | * if (strcmp(topic, MQTT_TOPIC_SUB"/mode")==0) { 269 | * MyVal = message; 270 | * Do something with MyVal 271 | * return; 272 | * }; 273 | */ 274 | message.trim(); 275 | if (message.length() > 0) { 276 | hasPayload = true; 277 | } 278 | //Amp control and commands (UART) 279 | // These commands should be universal, regardless of current source 280 | if (strcmp(topic, MQTT_TOPIC_SUB"/source") == 0) { 281 | if (hasPayload) { 282 | uartMsg = "SRC:" + message + ";"; 283 | } else { 284 | uartMsg = "SRC;"; 285 | } 286 | Serial2.flush(); 287 | Serial2.write(uartMsg.c_str()); 288 | } else if (strcmp(topic, MQTT_TOPIC_SUB"/system") == 0) { 289 | //Use these with caution! Reset and recover could require reinitialization of your amp 290 | uartMsg = "SYS:" + message + ";"; 291 | Serial2.flush(); 292 | Serial2.write(uartMsg.c_str()); 293 | } else if (strcmp(topic, MQTT_TOPIC_SUB"/wifireset") == 0) { 294 | Serial2.flush(); 295 | Serial2.write("WRS;"); 296 | } else if (strcmp(topic, MQTT_TOPIC_SUB"/volume") == 0) { 297 | if (hasPayload) { 298 | uartMsg = "VOL:" + message + ";"; 299 | } else { 300 | uartMsg = "VOL;"; 301 | } 302 | Serial2.flush(); 303 | Serial2.write(uartMsg.c_str()); 304 | } else if (strcmp(topic, MQTT_TOPIC_SUB"/mute")==0) { 305 | if (hasPayload) { 306 | uartMsg = "MUT:" + message + ";"; 307 | } else { 308 | uartMsg = "MUT;"; 309 | } 310 | Serial2.flush(); 311 | Serial2.write(uartMsg.c_str()); 312 | } else if (strcmp(topic, MQTT_TOPIC_SUB"/bass") == 0) { 313 | if (hasPayload) { 314 | uartMsg = "BAS:" + message + ";"; 315 | } else { 316 | uartMsg = "BAS;"; 317 | } 318 | Serial2.flush(); 319 | Serial2.write(uartMsg.c_str()); 320 | } else if (strcmp(topic, MQTT_TOPIC_SUB"/midrange") == 0) { 321 | if (hasPayload) { 322 | uartMsg = "MID:" + message + ";"; 323 | } else { 324 | uartMsg = "MID:"; 325 | } 326 | Serial2.flush(); 327 | Serial2.write(uartMsg.c_str()); 328 | } else if (strcmp(topic, MQTT_TOPIC_SUB"/treble") == 0) { 329 | if (hasPayload) { 330 | uartMsg = "TRE:" + message + ";"; 331 | } else { 332 | uartMsg = "TRE;"; 333 | } 334 | Serial2.flush(); 335 | Serial2.write(uartMsg.c_str()); 336 | } else if (strcmp(topic, MQTT_TOPIC_SUB"/led") == 0) { 337 | if (hasPayload) { 338 | uartMsg = "LED:" + message + ";"; 339 | } else { 340 | uartMsg = "LED;"; 341 | } 342 | Serial2.flush(); 343 | Serial2.write(uartMsg.c_str()); 344 | } else if (strcmp(topic, MQTT_TOPIC_SUB"/beep") == 0) { 345 | if (hasPayload) { 346 | uartMsg = "BEP:" + message + ";"; 347 | } else { 348 | uartMsg = "BEP;"; 349 | } 350 | Serial2.flush(); 351 | Serial2.write(uartMsg.c_str()); 352 | } else if (strcmp(topic, MQTT_TOPIC_SUB"/voice") == 0) { 353 | if (hasPayload) { 354 | uartMsg = "PMT:" + message + ";"; 355 | } else { 356 | uartMsg = "PMT;"; 357 | } 358 | Serial2.flush(); 359 | Serial2.write(uartMsg.c_str()); 360 | } else if (strcmp(topic, MQTT_TOPIC_SUB"/virtbass") == 0) { 361 | if (hasPayload) { 362 | uartMsg = "VBS:" + message + ";"; 363 | } else { 364 | uartMsg = "VBS;"; 365 | } 366 | Serial2.flush(); 367 | Serial2.write(uartMsg.c_str()); 368 | } else if (strcmp(topic, MQTT_TOPIC_SUB"/pregain") == 0) { 369 | if (hasPayload) { 370 | uartMsg = "PRG:" + message + ";"; 371 | } else { 372 | uartMsg = "PRG;"; 373 | } 374 | Serial2.flush(); 375 | Serial2.write(uartMsg.c_str()); 376 | } else if (strcmp(topic, MQTT_TOPIC_SUB"/balance") == 0) { 377 | if (hasPayload) { 378 | uartMsg = "BAL:" + message + ";"; 379 | } else { 380 | uartMsg = "BAL;"; 381 | } 382 | Serial2.flush(); 383 | Serial2.write(uartMsg.c_str()); 384 | } else if (strcmp(topic, MQTT_TOPIC_SUB"/maxvol") == 0) { 385 | if (hasPayload) { 386 | uartMsg = "MXV:" + message + ";"; 387 | } else { 388 | uartMsg = "MXV;"; 389 | } 390 | Serial2.flush(); 391 | Serial2.write(uartMsg.c_str()); 392 | } else if (strcmp(topic, MQTT_TOPIC_SUB"/poweronsource") == 0) { 393 | if (hasPayload) { 394 | uartMsg = "POM:" + message + ";"; 395 | } else { 396 | uartMsg = "POM;"; 397 | } 398 | Serial2.flush(); 399 | Serial2.write(uartMsg.c_str()); 400 | } else if (strcmp(topic, MQTT_TOPIC_SUB"/preset") == 0) { 401 | if (hasPayload) { 402 | uartMsg = "PST:" + message + ";"; 403 | } else { 404 | uartMsg = "PST;"; 405 | } 406 | } else if (strcmp(topic, MQTT_TOPIC_SUB"/delay") == 0) { 407 | if (hasPayload) { 408 | uartMsg = "DLY:" + message + ";"; 409 | } else { 410 | uartMsg = "DLY;"; 411 | } 412 | } else if (strcmp(topic, MQTT_TOPIC_SUB"/autoswitch") == 0) { 413 | if (hasPayload) { 414 | uartMsg = "ASW:" + message + ";"; 415 | } else { 416 | uartMsg = "ASW;"; 417 | } 418 | } else if (strcmp(topic, MQTT_TOPIC_SUB"/syncvol") == 0) { 419 | if (hasPayload) { 420 | uartMsg = "VOS:" + message + ";"; 421 | } else { 422 | uartMsg = "VOS;"; 423 | } 424 | 425 | // These commands only work with certain sources (see documentation) 426 | } else if ((strcmp(topic, MQTT_TOPIC_SUB"/pause") == 0) || (strcmp(topic, MQTT_TOPIC_SUB"/play") == 0)) { 427 | Serial2.flush(); 428 | Serial2.write("POP;"); 429 | showTextParam("Command", "Pause/Play", 2); 430 | } else if (strcmp(topic, MQTT_TOPIC_SUB"/stop") == 0) { 431 | Serial2.flush(); 432 | Serial2.write("STP;"); 433 | showTextParam("Command", "STOP", 3); 434 | } else if (strcmp(topic, MQTT_TOPIC_SUB"/next") == 0) { 435 | Serial2.flush(); 436 | Serial2.write("NXT;"); 437 | showTextParam("Command", "NEXT", 3); 438 | } else if (strcmp(topic, MQTT_TOPIC_SUB"/previous") == 0) { 439 | Serial2.flush(); 440 | Serial2.write("PRE;"); 441 | showTextParam("Command", "PREV", 3); 442 | } else if (strcmp(topic, MQTT_TOPIC_SUB"/bluetooth") == 0) { 443 | if (hasPayload) { 444 | uartMsg = "BTC:" + message + ";"; 445 | } else { 446 | uartMsg = "BTC;"; 447 | } 448 | Serial2.flush(); 449 | Serial2.write(uartMsg.c_str()); 450 | } else if (strcmp(topic, MQTT_TOPIC_SUB"/loopmode") == 0) { 451 | uartMsg = "LPM:" + message + ";"; 452 | Serial2.flush(); 453 | Serial2.write(uartMsg.c_str()); 454 | 455 | 456 | //Amp Status updates requests - these commands only request a state update, which will be published under related /stat topics 457 | //Any payloads are ignored 458 | } else if (strcmp(topic, MQTT_TOPIC_SUB"/status") == 0) { 459 | Serial2.flush(); 460 | Serial2.write("STA;"); 461 | } else if (strcmp(topic, MQTT_TOPIC_SUB"/version") == 0) { 462 | Serial2.flush(); 463 | Serial2.write("VER;"); 464 | } else if (strcmp(topic, MQTT_TOPIC_SUB"/ethernet") == 0) { 465 | Serial2.flush(); 466 | Serial2.write("ETH;"); 467 | } else if (strcmp(topic, MQTT_TOPIC_SUB"/wifi") == 0) { 468 | Serial2.flush(); 469 | Serial2.write("WIF;"); 470 | } else if (strcmp(topic, MQTT_TOPIC_SUB"/internet") == 0) { 471 | Serial2.flush(); 472 | Serial2.write("WWW;"); 473 | } else if (strcmp(topic, MQTT_TOPIC_SUB"/elapsed") == 0) { //force elspsed time update (elapsed/total in msec) 474 | Serial2.flush(); 475 | Serial2.write("ELP;"); 476 | } else if (strcmp(topic, MQTT_TOPIC_SUB"/playing") == 0) { 477 | Serial2.flush(); 478 | Serial2.write("PLA;"); 479 | } else if (strcmp(topic, MQTT_TOPIC_SUB"/channel") == 0) { 480 | Serial2.flush(); 481 | Serial2.write("CHN;"); 482 | } else if (strcmp(topic,MQTT_TOPIC_SUB"/multiroom") == 0) { 483 | Serial2.flush(); 484 | Serial2.write("MRM;"); 485 | } else if (strcmp(topic, MQTT_TOPIC_SUB"/audio") == 0) { 486 | Serial2.flush(); 487 | Serial2.write("AUD;"); 488 | } else if (strcmp(topic, MQTT_TOPIC_SUB"/fixedvol") == 0) { 489 | Serial2.flush(); 490 | Serial2.write("VOF;"); 491 | } else if (strcmp(topic, MQTT_TOPIC_SUB"/tracknum") == 0) { 492 | Serial2.flush(); 493 | Serial2.write("PLI;"); 494 | } else if (strcmp(topic, MQTT_TOPIC_SUB"/devicename") == 0) { 495 | Serial2.flush(); 496 | Serial2.write("NAM;"); 497 | } else if (strcmp(topic, MQTT_TOPIC_SUB"/network") == 0) { 498 | Serial2.flush(); 499 | Serial2.write("NET;"); 500 | //This final command just passed the payload directly as a UART message - payload must formatted properly, including trailing semi-colon 501 | } else if (strcmp(topic, MQTT_TOPIC_SUB"/uart") == 0) { 502 | uartMsg = message; 503 | Serial2.flush(); 504 | Serial2.write(uartMsg.c_str()); 505 | 506 | //These are not UART commands, but other commands to enable/disable certain feature that are firmware only 507 | } else if (strcmp(topic, MQTT_TOPIC_SUB"/irmode") == 0) { 508 | if (hasPayload) { 509 | setIRMode(message.toInt()); 510 | } else { 511 | setIRMode(99); //just update status 512 | } 513 | } 514 | #endif 515 | }; 516 | // Reconnect to broker if connection lost (usually a result of a Home Assistant/broker/server reboot) 517 | boolean reconnect() { 518 | #if defined(MQTTMODE) && (MQTTMODE == 1 && (WIFIMODE == 1 || WIFIMODE == 2)) 519 | if (client.connect(mqttClient, mqttUser, mqttPW)) { 520 | // Once connected, publish an announcement... 521 | String outTopic = mqttTopicPub + "/mqtt"; 522 | client.publish(outTopic.c_str(), "connected", true); 523 | // ... and resubscribe 524 | client.subscribe(MQTT_TOPIC_SUB"/#"); 525 | } 526 | return client.connected(); 527 | #endif 528 | } 529 | 530 | // =============================================================== 531 | // MAIN LOOP 532 | // =============================================================== 533 | void loop() { 534 | //Handle OTA updates when OTA flag set via HTML call to http://ip_address/otaupdate 535 | 536 | if (ota_flag) { 537 | if (initBoot) { 538 | initBoot = false; 539 | } else { 540 | showOTAUpdate(); 541 | } 542 | uint16_t ota_time_start = millis(); 543 | while (ota_time_elapsed < ota_time) { 544 | ArduinoOTA.handle(); 545 | ota_time_elapsed = millis()-ota_time_start; 546 | delay(10); 547 | } 548 | ota_flag = false; 549 | } 550 | //Handle any web calls 551 | server.handleClient(); 552 | 553 | // Reconnect to MQTT if enabled and not connected 554 | #if defined(MQTTMODE) && (MQTTMODE == 1 && (WIFIMODE == 1 || WIFIMODE == 2)) 555 | if (!client.connected()) { 556 | long now = millis(); 557 | if (now - lastReconnectAttempt > 60000) { //attempt reconnect once per minute. Drop usually a result of Home Assistant/broker server restart 558 | lastReconnectAttempt = now; 559 | // Attempt to reconnect 560 | if (reconnect()) { 561 | lastReconnectAttempt = 0; 562 | } 563 | } 564 | } else { 565 | // Client connected 566 | client.loop(); 567 | } 568 | #endif 569 | // Process IR if enabled 570 | if (enableIR) { 571 | //Look for IR Code Received if enabled 572 | if (irrecv.decode()) { 573 | 574 | uint16_t ir_code = irrecv.decodedIRData.command; 575 | processIRCommand(ir_code); 576 | #if defined(MQTTMODE) && (MQTTMODE == 1 && (WIFIMODE == 1 || WIFIMODE == 2)) 577 | client.publish(MQTT_TOPIC_PUB"/ircode", String(ir_code).c_str(), true); 578 | #endif 579 | delay(250); 580 | 581 | irrecv.resume(); 582 | } 583 | } 584 | //Rotary Knob Click Process 585 | currentMillis = millis(); 586 | int curBtnState = digitalRead(DISP_PIN); 587 | //Debounce 588 | if (curBtnState != lastBtnState) { 589 | lastDebounceTime = currentMillis; 590 | } 591 | 592 | if ((currentMillis - lastDebounceTime) > debounceDelay) { 593 | if (curBtnState != dispBtnState) { 594 | dispBtnState = curBtnState; 595 | 596 | if (dispBtnState == LOW) { 597 | if (dispMode == BLANK) { 598 | dispMode = 1; 599 | } else { 600 | dispMode ++; 601 | } 602 | prevdispMode = dispMode; 603 | } 604 | } 605 | } 606 | lastBtnState = curBtnState; 607 | 608 | if (standbyMode && (currentMillis < standbyTimer)) { //Don't process incoming data for 5 seconds after putting in standby (keeps display off) 609 | if (Serial2.available() > 0) { 610 | String ignoreData = Serial2.readStringUntil('\n'); 611 | } 612 | } else { 613 | if (Serial2.available() > 0) { 614 | standbyMode = false; 615 | String ampData = Serial2.readStringUntil('\n'); 616 | ampData.trim(); 617 | //Handle power down of display when amp reports SYS:STANDBY 618 | if (ampData.indexOf("STANDBY") > 0) { 619 | while (Serial2.available() > 0) { 620 | ampData = Serial2.readStringUntil('\n'); 621 | delay(500); 622 | } 623 | display.clearDisplay(); 624 | display.display(); 625 | standbyMode = true; 626 | standbyTimer = currentMillis + 5000; 627 | //Publish message with source of STANDBY 628 | #if defined(MQTTMODE) && (MQTTMODE == 1 && (WIFIMODE == 1 || WIFIMODE == 2)) 629 | client.publish(MQTT_TOPIC_PUB"/source", "STANDBY", true); 630 | #endif 631 | } else { 632 | standbyMode = false; 633 | updateMQTT(ampData); 634 | #if defined(MQTTMODE) && (MQTTMODE == 1 && (WIFIMODE == 1 || WIFIMODE == 2)) 635 | client.publish(MQTT_TOPIC_PUB"/uart", ampData.c_str(), true); 636 | #endif 637 | } 638 | } 639 | } 640 | //Update display 641 | if (!standbyMode) { 642 | if (currentMillis > (dispModeTemp_timer + dispTempDisplay_duration)) { 643 | switch (dispMode) { 644 | case SOURCE: 645 | showSource(); 646 | break; 647 | case VOLUME: 648 | //showVolume(); 649 | showNumberParam("Volume", dispVolume); 650 | break; 651 | case MUTE: 652 | showMute(); 653 | break; 654 | case TITLE: 655 | //showTitle(); 656 | showTextParam("Title", dispTitle, 2); 657 | break; 658 | case TRACK: 659 | //showTrack(); 660 | showTextParam("Track Num", dispTrack, 3); 661 | break; 662 | default: 663 | display.clearDisplay(); 664 | display.display(); 665 | break; 666 | } 667 | dispModeTemp_timer = 0; 668 | dispModeTempSource = false; 669 | } 670 | } 671 | } 672 | 673 | // ==================================== 674 | // Misc Functions 675 | // ==================================== 676 | // This is needed to UART Hex-returned values (NAM, TIT, ART, ALB) 677 | String hexToAscii( String hex ) 678 | { 679 | uint16_t len = hex.length(); 680 | String ascii = ""; 681 | 682 | for ( uint16_t i = 0; i < len; i += 2 ) 683 | ascii += (char)strtol( hex.substring( i, i+2 ).c_str(), NULL, 16 ); 684 | 685 | return ascii; 686 | } 687 | 688 | // -------------------------------- 689 | // Handle enable/disable custom IR 690 | // (since this is not a UART command) 691 | // Updates both MQTT and display 692 | // --------------------------------- 693 | void setIRMode(byte statcode) { 694 | #if defined(MQTTMODE) && (MQTTMODE == 1 && (WIFIMODE == 1 || WIFIMODE == 2)) 695 | String topic = mqttTopicPub + "/irmode"; 696 | String payload = "0"; 697 | //If stat code > 0, enable IR otherwise disable 698 | if (statcode > 0) { 699 | enableIR = 1; 700 | payload = "1"; 701 | } else if (statcode == 0) { 702 | enableIR = 0; 703 | payload = "0"; 704 | //Clear last retained IR code (if any) 705 | client.publish((mqttTopicPub + "/ircode").c_str(), "0", true); 706 | } 707 | client.publish(topic.c_str(), payload.c_str(), true); 708 | showBooleanParam("IR Recvr", enableIR); 709 | 710 | #endif 711 | } 712 | 713 | // ------------------------- 714 | // Process IR codes received 715 | // ------------------------- 716 | void processIRCommand(uint16_t ircode) { 717 | for (int i=0; i -10) { 726 | tempCmd.replace("--", String(dispTreble - 1)); 727 | } else { 728 | tempCmd = "TRE:-10;"; 729 | } 730 | } else if (tempCmd.startsWith("TRE:++")) { 731 | if (dispTreble < 10) { 732 | tempCmd.replace("++", String(dispTreble + 1)); 733 | } else { 734 | tempCmd = "TRE:10;"; 735 | } 736 | //Bass 737 | } else if (tempCmd.startsWith("BAS:--")) { 738 | if (dispBass > -10) { 739 | tempCmd.replace("--", String(dispBass - 1)); 740 | } else { 741 | tempCmd = "BAS:-10;"; 742 | } 743 | } else if (tempCmd.startsWith("BAS:++")) { 744 | if (dispBass < 10) { 745 | tempCmd.replace("++", String(dispBass + 1)); 746 | } 747 | //Balance (-100 to 100) 748 | } else if (tempCmd.startsWith("BAL:--")) { 749 | if (dispBalance > -100 ) { 750 | tempCmd.replace("--", String(dispBalance - 1)); 751 | } else { 752 | tempCmd = "BAL:-100;"; 753 | } 754 | } else if (tempCmd.startsWith("BAL:++")) { 755 | if (dispBalance < 100) { 756 | tempCmd.replace("++", String(dispBalance + 1)); 757 | } else { 758 | tempCmd = "BAL:100"; 759 | } 760 | } 761 | 762 | if (tempCmd == "DISPMODE") { 763 | //this is a local command, not a UART command. Increase display mode by 1 764 | if (dispMode == BLANK) { 765 | dispMode = 1; 766 | } else { 767 | dispMode ++; 768 | } 769 | prevdispMode = dispMode; 770 | 771 | break; 772 | } else { 773 | Serial2.flush(); 774 | //Serial2.write((remoteCommands[i].uartCommand).c_str()); 775 | Serial2.write(tempCmd.c_str()); 776 | //Update display for those commands that do not return a UART value 777 | if (tempCmd == "POP;") { 778 | showTextParam("Command", "Pause/Play", 2); 779 | } else if (tempCmd == "STP;") { 780 | showTextParam("Command", "STOP", 3); 781 | } else if (tempCmd == "NXT;") { 782 | showTextParam("Command", "NEXT", 3); 783 | } else if (tempCmd == "PRE;") { 784 | showTextParam("Command", "PREV", 3); 785 | } 786 | break; 787 | } 788 | } 789 | } 790 | } 791 | 792 | // ==================================== 793 | // MQTT Message Processing 794 | // =================================== 795 | void updateMQTT(String uartData) { 796 | //Incoming data may be a single value or a list of semi-colon delimited values 797 | //All data returned consists of a three character topic, followed by a colon and the value 798 | //This proc also calls the process to update the display, so it should remain enabled even if MQTT disabled 799 | int dataLen = uartData.length(); 800 | int intSemiPos = uartData.indexOf(';'); 801 | int intLastPos = 0; 802 | String strTopic; 803 | String strMessage; 804 | String uartTopic; 805 | if (dataLen > 0) { 806 | uartTopic = uartData.substring(0,3); 807 | if (uartTopic == "STA") { 808 | //Must handle differently to split multiple values 809 | updateAmpStatus(uartData); 810 | } else { 811 | while (intSemiPos > 0) { 812 | if ((uartTopic == "NAM")) { //hexed value - must convert to ASCII 813 | strMessage = hexToAscii(uartData.substring(4, intSemiPos)); 814 | } else if ((uartTopic == "CHN") || (uartTopic == "MRM")) { 815 | strMessage = decodeUartValue(uartTopic, uartData.substring(4, intSemiPos)); 816 | } else { 817 | strMessage = uartData.substring(4, intSemiPos); //UART value/response 818 | } 819 | strTopic = mqttTopicPub + "/" + getFriendlyTopic(uartTopic, strMessage); //Get friendly name from UART message (topic) 820 | 821 | // strMessage = uartData.substring(4, intSemiPos); //UART value/response 822 | #if defined(MQTTMODE) && (MQTTMODE == 1 && (WIFIMODE == 1 || WIFIMODE == 2)) 823 | client.publish(strTopic.c_str(), strMessage.c_str(), true); 824 | #endif 825 | intLastPos = intSemiPos + 1; 826 | intSemiPos = uartData.indexOf(';', intLastPos); 827 | } 828 | } 829 | } 830 | } 831 | 832 | String getFriendlyTopic(String uartTopic, String uartValue) { 833 | //Gets friendly/topic name for MQTT Publish 834 | //Also updates display where applicable 835 | String outVal="unknown"; 836 | String dispVal = "?"; 837 | if (uartTopic == "VER") { 838 | outVal = "version"; 839 | showTextParam("Version", uartValue, 2); 840 | } else if (uartTopic == "WWW") { 841 | outVal = "internet"; 842 | //dispWWWconn = uartValue.toInt(); 843 | showBooleanParam("Internet", uartValue.toInt()); 844 | } else if (uartTopic == "AUD") { 845 | outVal = "audio"; 846 | //dispAudio = uartValue.toInt(); 847 | showBooleanParam("Audio", uartValue.toInt()); 848 | } else if (uartTopic == "SRC") { 849 | outVal = "source"; 850 | dispSource = uartValue; 851 | showSource(); 852 | } else if (uartTopic == "VOL") { 853 | outVal = "volume"; 854 | dispVolume = uartValue.toInt(); 855 | showNumberParam("Volume", dispVolume); 856 | //showVolume(); 857 | } else if (uartTopic == "MUT") { 858 | outVal = "mute"; 859 | if (uartValue == "1") { 860 | dispMute = "ON"; 861 | dispMode = MUTE; 862 | } else { 863 | dispMute = "OFF"; 864 | dispMode = prevdispMode; 865 | } 866 | showMute(); 867 | } else if (uartTopic == "BAS") { 868 | outVal = "bass"; 869 | dispBass = uartValue.toInt(); 870 | showNumberParam("Bass", dispBass); 871 | } else if (uartTopic == "MID") { 872 | outVal = "midrange"; 873 | dispMidrange = uartValue.toInt(); 874 | showNumberParam("Midrange", dispMidrange); 875 | } else if (uartTopic == "TRE") { 876 | outVal = "treble"; 877 | dispTreble = uartValue.toInt(); 878 | showNumberParam("Treble", dispTreble); 879 | } else if (uartTopic == "BTC") { 880 | outVal = "btconnect"; 881 | //dispBTconn = uartValue.toInt(); 882 | showBooleanParam("Bluetooth", uartValue.toInt()); 883 | } else if (uartTopic == "PLA") { 884 | outVal = "playing"; 885 | showBooleanParam("Playing", uartValue.toInt()); 886 | } else if (uartTopic == "CHN") { 887 | outVal = "channel"; 888 | showTextParam("Channel", uartValue, 2); 889 | } else if (uartTopic == "MRM") { 890 | outVal = "multiroom"; 891 | showTextParam("Multi-room", uartValue, 2); 892 | } else if (uartTopic == "LED") { 893 | outVal = "led"; 894 | //dispLED = uartValue.toInt(); 895 | showBooleanParam("LED", uartValue.toInt()); 896 | } else if (uartTopic == "BEP") { 897 | outVal = "beep"; 898 | //dispBeep = uartValue.toInt(); 899 | showBooleanParam("Key Beep", uartValue.toInt()); 900 | } else if (uartTopic == "VBS") { 901 | outVal = "virtbass"; 902 | //dispVirtbass = uartValue.toInt(); 903 | showBooleanParam("Virt. Bass", uartValue.toInt()); 904 | } else if (uartTopic == "LPM") { 905 | outVal = "loopmode"; 906 | //dispLoopmode = uartValue; 907 | showTextParam("Loop mode", uartValue, 2); 908 | } else if (uartTopic == "NET") { 909 | outVal = "network"; 910 | //dispNetwork = uartValue.toInt(); 911 | showBooleanParam("Network", uartValue.toInt()); 912 | } else if (uartTopic == "ETH") { 913 | outVal = "ethernet"; 914 | //dispETHconn = uartValue.toInt(); 915 | showBooleanParam("Ethernet", uartValue.toInt()); 916 | } else if (uartTopic == "WIF") { 917 | outVal = "wifi"; 918 | //dispWIFIconn = uartValue.toInt(); 919 | showBooleanParam("WIFI", uartValue.toInt()); 920 | } else if (uartTopic == "PMT") { 921 | outVal = "voice"; 922 | //dispVoice = uartValue.toInt(); 923 | showBooleanParam("Voice Pmpt", uartValue.toInt()); 924 | } else if (uartTopic == "PRG") { 925 | outVal = "pregain"; 926 | //dispPregain = uartValue.toInt(); 927 | showBooleanParam("Pregain", uartValue.toInt()); 928 | } else if (uartTopic == "DLY") { 929 | outVal = "delay"; 930 | showNumberParam("Delay", uartValue.toInt()); 931 | } else if (uartTopic == "MXV") { 932 | outVal = "maxvol"; 933 | //dispMaxvol = uartValue.toInt(); 934 | showNumberParam("Max. Vol", uartValue.toInt()); 935 | } else if (uartTopic == "ASW") { 936 | outVal = "autoswitch"; 937 | showBooleanParam("Auto-switch", uartValue.toInt()); 938 | } else if (uartTopic == "POM") { 939 | outVal = "poweronsource"; 940 | //dispPOM = uartValue; 941 | showTextParam("PowerOn Scr", uartValue, 2); 942 | } else if (uartTopic == "VND") { 943 | outVal = "vendor"; 944 | showTextParam("Vendor", uartValue, 2); 945 | } else if (uartTopic == "ELP") { 946 | outVal = "elasped"; 947 | showTextParam("Elaspsed", uartValue, 2); 948 | } else if (uartTopic == "VOS") { 949 | outVal = "syncvol"; 950 | showBooleanParam("Vol Sync", uartValue.toInt()); 951 | } else if (uartTopic == "BAL") { 952 | outVal = "balance"; 953 | dispBalance = uartValue.toInt(); 954 | showNumberParam("Balance", dispBalance); 955 | //showBalance(); 956 | } else if (uartTopic == "VOF") { 957 | outVal = "fixedvol"; 958 | showBooleanParam("Fixed Vol", uartValue.toInt()); 959 | } else if (uartTopic == "PLI") { 960 | outVal = "tracknum"; 961 | dispTrack = uartValue; 962 | showTextParam("Track Num", dispTrack, 2); 963 | //showTrack(); 964 | } else if (uartTopic == "PST") { 965 | outVal = "preset"; 966 | showNumberParam("Preset", uartValue.toInt()); 967 | 968 | } else if (uartTopic == "NAM") { 969 | outVal = "devicename"; 970 | showTextParam("Device name", uartValue, 2); 971 | } else if (uartTopic == "TIT") { 972 | outVal = "title"; 973 | dispTitle = uartValue; 974 | showTextParam("Title", dispTitle, 2); 975 | } else if (uartTopic == "ART") { 976 | outVal = "artist"; 977 | showTextParam("Artist", uartValue, 2); 978 | } else if (uartTopic == "ALB") { 979 | outVal = "album"; 980 | showTextParam("Album", uartValue, 2); 981 | } else { 982 | outVal = uartTopic; 983 | } 984 | 985 | return outVal; 986 | } 987 | 988 | String decodeUartValue(String uartTopic, String uartCode) { 989 | String retVal; 990 | if (uartTopic == "CHN") { 991 | if (uartCode == "S") { 992 | retVal = "Stereo"; 993 | } else if (uartCode = "L") { 994 | retVal = "Left"; 995 | } else if (uartCode = "R") { 996 | retVal = "Right"; 997 | } else { 998 | retVal = "Unknown"; 999 | } 1000 | } else if (uartTopic == "MRM") { 1001 | if (uartCode == "S") { 1002 | retVal = "Slave"; 1003 | } else if (uartCode == "M") { 1004 | retVal = "Master"; 1005 | } else { 1006 | retVal = "None"; 1007 | } 1008 | 1009 | } else { 1010 | retVal = "Unknown"; 1011 | } 1012 | return retVal; 1013 | } 1014 | 1015 | void updateAmpStatus(String statData) { 1016 | int delimPos = statData.indexOf(','); 1017 | int lastPos = 0; 1018 | String parmVal = ""; 1019 | #if defined(MQTTMODE) && (MQTTMODE == 1 && (WIFIMODE == 1 || WIFIMODE == 2)) 1020 | 1021 | if (delimPos > 0) { 1022 | //Source 1023 | parmVal = statData.substring(4, delimPos); 1024 | client.publish(MQTT_TOPIC_PUB"/source", parmVal.c_str(), true); 1025 | dispSource = parmVal; 1026 | lastPos = delimPos; 1027 | delimPos = statData.indexOf(',', lastPos + 1); 1028 | } 1029 | 1030 | if ((delimPos > 0) && (lastPos > 0)) { 1031 | //Mute 1032 | parmVal = statData.substring(lastPos+1, delimPos); 1033 | client.publish(MQTT_TOPIC_PUB"/mute", parmVal.c_str(), true); 1034 | lastPos = delimPos; 1035 | delimPos = statData.indexOf(',', lastPos + 1); 1036 | } 1037 | 1038 | if ((delimPos > 0) && (lastPos > 0)) { 1039 | //Volume 1040 | parmVal = statData.substring(lastPos+1, delimPos); 1041 | client.publish(MQTT_TOPIC_PUB"/volume", parmVal.c_str(), true); 1042 | lastPos = delimPos; 1043 | delimPos = statData.indexOf(',', lastPos + 1); 1044 | } 1045 | 1046 | if ((delimPos > 0) && (lastPos > 0)) { 1047 | //Treble 1048 | parmVal = statData.substring(lastPos+1, delimPos); 1049 | client.publish(MQTT_TOPIC_PUB"/treble", parmVal.c_str(), true); 1050 | lastPos = delimPos; 1051 | delimPos = statData.indexOf(',', lastPos + 1); 1052 | } 1053 | if ((delimPos > 0) && (lastPos > 0)) { 1054 | //Bass 1055 | parmVal = statData.substring(lastPos+1, delimPos); 1056 | client.publish(MQTT_TOPIC_PUB"/bass", parmVal.c_str(), true); 1057 | lastPos = delimPos; 1058 | delimPos = statData.indexOf(',', lastPos + 1); 1059 | } 1060 | 1061 | if ((delimPos > 0) && (lastPos > 0)) { 1062 | //Network 1063 | parmVal = statData.substring(lastPos+1, delimPos); 1064 | client.publish(MQTT_TOPIC_PUB"/network", parmVal.c_str(), true); 1065 | lastPos = delimPos; 1066 | delimPos = statData.indexOf(',', lastPos + 1); 1067 | } 1068 | 1069 | if ((delimPos > 0) && (lastPos > 0)) { 1070 | //Internet 1071 | parmVal = statData.substring(lastPos+1, delimPos); 1072 | client.publish(MQTT_TOPIC_PUB"/internet", parmVal.c_str(), true); 1073 | lastPos = delimPos; 1074 | delimPos = statData.indexOf(',', lastPos + 1); 1075 | } 1076 | 1077 | if ((delimPos > 0) && (lastPos > 0)) { 1078 | //Playing 1079 | parmVal = statData.substring(lastPos+1, delimPos); 1080 | client.publish(MQTT_TOPIC_PUB"/playing", parmVal.c_str(), true); 1081 | lastPos = delimPos; 1082 | delimPos = statData.indexOf(',', lastPos + 1); 1083 | } 1084 | 1085 | if ((delimPos > 0) && (lastPos > 0)) { 1086 | //LED 1087 | parmVal = statData.substring(lastPos+1, delimPos); 1088 | client.publish(MQTT_TOPIC_PUB"/led", parmVal.c_str(), true); 1089 | lastPos = delimPos; 1090 | delimPos = statData.indexOf(',', lastPos + 1); 1091 | } 1092 | //Upgrading flag not included here 1093 | #endif 1094 | } 1095 | 1096 | // =================================== 1097 | // Display related functions 1098 | // =================================== 1099 | void showOTAUpdate() { 1100 | display.clearDisplay(); 1101 | display.setTextSize(2); 1102 | display.setTextColor(WHITE); 1103 | display.invertDisplay(false); 1104 | display.setCursor(0, 0); 1105 | display.println("OTA Update"); 1106 | display.display(); 1107 | } 1108 | 1109 | void showTextParam(String parmName, String parmValue, byte textSize) { 1110 | if (!dispModeTempSource) { 1111 | display.clearDisplay(); 1112 | display.setTextSize(2); 1113 | display.setTextColor(WHITE); 1114 | display.invertDisplay(false); 1115 | display.setCursor(0, 0); 1116 | display.println(parmName); 1117 | display.setTextSize(textSize); 1118 | display.setTextColor(WHITE); 1119 | display.setCursor(0,25); 1120 | display.println(parmValue); 1121 | display.display(); 1122 | dispModeTemp_timer = millis(); 1123 | } 1124 | 1125 | } 1126 | 1127 | void showBooleanParam(String parmName, byte parmValue) { 1128 | if (!dispModeTempSource) { 1129 | display.clearDisplay(); 1130 | display.setTextSize(2); 1131 | display.setTextColor(WHITE); 1132 | display.invertDisplay(false); 1133 | display.setCursor(0, 0); 1134 | display.println(parmName); 1135 | display.setTextSize(3); 1136 | display.setTextColor(WHITE); 1137 | display.setCursor(40,25); 1138 | if (parmValue) { 1139 | display.println("ON"); 1140 | } else { 1141 | display.println("OFF"); 1142 | } 1143 | display.display(); 1144 | dispModeTemp_timer = millis(); 1145 | } 1146 | } 1147 | 1148 | void showNumberParam(String parmName, int parmValue) { 1149 | if (!dispModeTempSource) { 1150 | display.clearDisplay(); 1151 | display.setTextSize(2); 1152 | display.setTextColor(WHITE); 1153 | display.invertDisplay(false); 1154 | display.setCursor(0, 0); 1155 | display.println(parmName); 1156 | display.setTextSize(3); 1157 | display.setTextColor(WHITE); 1158 | display.setCursor(40,25); 1159 | display.println(parmValue); 1160 | display.display(); 1161 | dispModeTemp_timer = millis(); 1162 | } 1163 | 1164 | } 1165 | 1166 | void showSource() { 1167 | display.clearDisplay(); 1168 | display.setTextSize(2); 1169 | display.setTextColor(WHITE); 1170 | display.invertDisplay(false); 1171 | display.setCursor(0, 0); 1172 | display.println("Source"); 1173 | display.setTextSize(3); 1174 | display.setTextColor(WHITE); 1175 | display.setCursor(0,25); 1176 | display.println(dispSource); 1177 | display.display(); 1178 | dispModeTemp_timer = millis(); 1179 | dispModeTempSource = true; 1180 | } 1181 | 1182 | void showMute() { 1183 | if (!dispModeTempSource) { 1184 | display.clearDisplay(); 1185 | display.setTextSize(2); 1186 | display.setTextColor(WHITE); 1187 | display.setCursor(0, 0); 1188 | display.println("Mute"); 1189 | display.setTextSize(3); 1190 | display.setTextColor(WHITE); 1191 | display.setCursor(40,25); 1192 | display.println(dispMute); 1193 | if (dispMute == "ON") { 1194 | display.invertDisplay(true); 1195 | } else { 1196 | display.invertDisplay(false); 1197 | } 1198 | display.display(); 1199 | dispModeTemp_timer = millis(); 1200 | } 1201 | } 1202 | --------------------------------------------------------------------------------