├── images ├── .gitkeep ├── image.png ├── image1.png ├── image2.png ├── image3.png ├── image4.png ├── image5_320x480.png ├── image6_320x480.png ├── image7_320x480.png ├── image8_320x480.png ├── sunton_openhasp_sonos_01.png ├── sunton_openhasp_sonos_02.png ├── sunton_openhasp_sonos_03.png ├── sunton_openhasp_sonos_04.png └── sunton_openhasp_sonos_05.png ├── openhasp.png ├── groups.yaml ├── gs-t3e.jsonl ├── wt32_01_plus.jsonl ├── sunton.jsonl ├── configuration.yaml ├── README.md └── automations.yaml /images/.gitkeep: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /openhasp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/htvekov/openHASP-Sonos-media-player/HEAD/openhasp.png -------------------------------------------------------------------------------- /images/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/htvekov/openHASP-Sonos-media-player/HEAD/images/image.png -------------------------------------------------------------------------------- /images/image1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/htvekov/openHASP-Sonos-media-player/HEAD/images/image1.png -------------------------------------------------------------------------------- /images/image2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/htvekov/openHASP-Sonos-media-player/HEAD/images/image2.png -------------------------------------------------------------------------------- /images/image3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/htvekov/openHASP-Sonos-media-player/HEAD/images/image3.png -------------------------------------------------------------------------------- /images/image4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/htvekov/openHASP-Sonos-media-player/HEAD/images/image4.png -------------------------------------------------------------------------------- /images/image5_320x480.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/htvekov/openHASP-Sonos-media-player/HEAD/images/image5_320x480.png -------------------------------------------------------------------------------- /images/image6_320x480.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/htvekov/openHASP-Sonos-media-player/HEAD/images/image6_320x480.png -------------------------------------------------------------------------------- /images/image7_320x480.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/htvekov/openHASP-Sonos-media-player/HEAD/images/image7_320x480.png -------------------------------------------------------------------------------- /images/image8_320x480.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/htvekov/openHASP-Sonos-media-player/HEAD/images/image8_320x480.png -------------------------------------------------------------------------------- /images/sunton_openhasp_sonos_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/htvekov/openHASP-Sonos-media-player/HEAD/images/sunton_openhasp_sonos_01.png -------------------------------------------------------------------------------- /images/sunton_openhasp_sonos_02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/htvekov/openHASP-Sonos-media-player/HEAD/images/sunton_openhasp_sonos_02.png -------------------------------------------------------------------------------- /images/sunton_openhasp_sonos_03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/htvekov/openHASP-Sonos-media-player/HEAD/images/sunton_openhasp_sonos_03.png -------------------------------------------------------------------------------- /images/sunton_openhasp_sonos_04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/htvekov/openHASP-Sonos-media-player/HEAD/images/sunton_openhasp_sonos_04.png -------------------------------------------------------------------------------- /images/sunton_openhasp_sonos_05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/htvekov/openHASP-Sonos-media-player/HEAD/images/sunton_openhasp_sonos_05.png -------------------------------------------------------------------------------- /groups.yaml: -------------------------------------------------------------------------------- 1 | # Leave this group unpopulated !! 2 | # This group will contain all available Sonos speakers and will be updated dynamically and upon HA start 3 | # Will always contain master speaker as first entity !! 4 | sonos_all_speakers: 5 | name: sonos_all_speakers 6 | 7 | # Define your initial master speaker here !! 8 | # This group will contain the openHASP active Sonos speaker group and will be updated dynamically and upon HA start 9 | sonos_all: 10 | name: sonos_all 11 | entities: 12 | - media_player.kokken 13 | 14 | # Define your initial openHASP plates entities here !! 15 | # This group will contain all available openHASP plates and will be updated dynamically and upon HA start 16 | # Disable HA automation "openhasp_sonos_change_available_openhasp_devices_group" if you don't want HA to dynamically 17 | # add all existing openHASP plates to the group. If automation is disabled, then add all the openHASP entities here 18 | # that are configured with the Sonos controller on page 2. 19 | hasp_sonos_devices: 20 | name: hasp_sonos_devices 21 | entities: 22 | - openhasp.t3e_02 23 | #- openhasp.sunton_01 24 | #- openhasp.wt32_01_plus 25 | -------------------------------------------------------------------------------- /gs-t3e.jsonl: -------------------------------------------------------------------------------- 1 | {"page":0,"comment":"---------- All Pages ----------"} 2 | {"id":01,"action":{"down":"page prev"},"obj":"btn","x":000,"y":400,"w":157,"h":80,"bg_opa":180,"bg_color":"#000000","border_color":"#C7BAA7","border_width":2,"radius":10,"radius1":10,"radius2":10,"text":"\ue141","text_color":"#978B7D","text_font":64} 3 | {"id":02,"action":{"down":"page back"},"obj":"btn","x":161,"y":400,"w":158,"h":80,"bg_opa":180,"bg_color":"#000000","border_color":"#C7BAA7","border_width":2,"radius":10,"radius1":10,"radius2":10,"text":"\ue2dc","text_color":"#978B7D","text_font":64} 4 | {"id":03,"action":{"down":"page next"},"obj":"btn","x":323,"y":400,"w":157,"h":80,"bg_opa":180,"bg_color":"#000000","border_color":"#C7BAA7","border_width":2,"radius":10,"radius1":10,"radius2":10,"text":"\ue142","text_color":"#978B7D","text_font":64} 5 | {"id":04,"obj":"roller","enabled":false,"click":false,"x":0,"y":-1,"w":480,"text_line_space":10,"bg_opa1":180,"bg_opa2":0,"border_width1":0,"val":0,"rows":1,"options":"ENERGY\nSONOS\nOFFICE\nWEATHER\nFAMILY\nNORDPOOL","text_font":24,"bg_color1":"#000000","text_color1":"#FFFFFF","bg_color":"#000000","text_color":"#000000","border_side":0,"radius":0} 6 | {"id":05,"obj":"label","enabled":false,"x":387,"y":5,"h":45,"w":90,"bg_opa":0,"text":"\ue50f 00.0°C","align":"right","bg_color":"#2C3E50","text_color":"#FFFFFF","text_font":24} 7 | {"id":06,"obj":"label","enabled":false,"x":50,"y":05,"w":250,"h":30,"bg_opa":0,"text":"00:00:00","template":"%H:%M:%S","bg_color":"#2C3E50","text_color":"white","align":"left","text_font":24} 8 | {"id":07,"obj":"label","enabled":false,"x":05,"y":0,"w":30,"h":45,"bg_opa":0,"text":"\ue5a9","bg_color":"#2C3E50","text_color":"#FFFFFF","text_font":16} 9 | {"id":08,"obj":"label","enabled":false,"x":20,"y":0,"w":30,"h":45,"bg_opa":0,"text":"\ue004","bg_color":"#2C3E50","text_color":"green","text_font":16} 10 | 11 | 12 | {"page":2,"comment":"---------- Page 2 ----------NOTE: OBJ: 89 is used in automation for Sunton devices !! "} 13 | {"id":39,"obj":"obj","enabled":true,"x":0,"y":0,"w":480,"h":480,"bg_opa":100,"border_width":0} 14 | {"id":40,"obj":"img","enabled":true,"x":0,"y":0,"w":480,"h":480,"parentid":39,"auto_size":0,"src":"L:/openhasp.png"} 15 | {"id":13,"obj":"obj","x":0,"y":44,"w":480,"h":61,"enabled":false,"bg_opa":100,"bg_color":"#000000","border_color":"#FFAC00","border_width":2,"radius":10,"radius1":10,"radius2":10} 16 | {"id":14,"obj":"label","x":35,"y":44,"w":440,"h":29,"bg_opa":0,"bg_color":"#000000","mode":"loop","text":"Title","text_color":"#C7BAA7","text_font":24} 17 | {"id":26,"obj":"label","x":35,"y":74,"w":440,"h":29,"bg_opa":0,"bg_color":"#000000","hidden":1,"mode":"loop","text":"Sonos Master - Sonos slaves","text_color":"#FFAC00","text_font":24} 18 | {"id":15,"obj":"label","x":35,"y":74,"w":440,"h":29,"bg_opa":0,"bg_color":"#000000","mode":"loop","text":"Artist","text_color":"#C7BAA7","text_font":24} 19 | {"id":41,"obj":"bar","enabled":false,"x":35,"y":73,"w":440,"h":4,"min":0,"max":100,"bg_color10":"#FFAC00","bg_opa":100,"border_opa":0,"pad_top":0,"pad_bottom":0,"pad_left":0,"pad_right":0} 20 | 21 | {"id":16,"obj":"btn","x":311,"y":107,"w":83,"h":79,"bg_opa":150,"bg_color":"#000000","border_color":"#FFAC00","border_width":2,"radius":10,"radius1":10,"radius2":10,"text":"\ue4ae","text_color":"#C7BAA7","text_font":64} 22 | {"id":17,"obj":"btn","x":311,"y":189,"w":168,"h":79,"bg_opa":150,"toggle":true,"bg_color":"#000000","border_color":"#FFAC00","border_width":2,"radius":10,"radius1":10,"radius2":10,"text":"\ue40a","text_color":"#C7BAA7","text_font":64} 23 | {"id":18,"obj":"btn","x":397,"y":107,"w":82,"h":79,"bg_opa":150,"bg_color":"#000000","border_color":"#FFAC00","border_width":2,"radius":10,"radius1":10,"radius2":10,"text":"\ue4ad","text_color":"#C7BAA7","text_font":64} 24 | {"id":25,"obj":"btn","toggle":true,"x":368,"y":271,"w":54,"h":59,"bg_opa":150,"bg_color":"#000000","border_color":"#FFAC00","border_width":2,"text":"\ue49e","text_color":"#C7BAA7","text_color1":"#FFAC00","text_font":48} 25 | {"id":27,"obj":"btn","toggle":false,"x":311,"y":271,"w":54,"h":59,"bg_opa":150,"bg_color":"#000000","border_color":"#FFAC00","border_width":2,"text":"\ue457","text_color":"#C7BAA7","text_font":48} 26 | {"id":24,"obj":"btn","toggle":true,"x":425,"y":271,"w":54,"h":59,"bg_opa":150,"bg_color":"#000000","border_color":"#FFAC00","border_width":2,"text":"\ue580","text_color":"#C7BAA7","text_color1":"#FF0000","text_font":48} 27 | 28 | {"id":95,"obj":"tabview","btn_pos":1,"y":106,"h":285,"text_font":20,"bg_opa":0,"bg_opa1":100,"bg_opa30":180,"hidden":1,"border_width":0} 29 | {"id":96,"obj":"tab","parentid":95,"text":"Sources"} 30 | {"id":97,"obj":"tab","parentid":95,"text":"Playlists"} 31 | {"id":94,"obj":"tab","parentid":95,"text":"Speakers"} 32 | {"id":92,"obj":"tab","parentid":95,"text":"Master"} 33 | {"id":93,"obj":"btnmatrix","parentid":94,"x":275,"y":005,"w":196,"h":170,"bg_opa":180,"bg_opa40":180,"toggle":0,"one_check":0,"text_font":40,"options":["#FF0000 Speaker 1#","#0000FF Speaker 2#","\n","#FFFF00 Speaker 3#"]} 34 | {"id":88,"obj":"btnmatrix","parentid":92,"x":275,"y":005,"h":170,"w":196,"bg_opa":180,"bg_opa40":180,"toggle":0,"one_check":0,"text_font":32,"options":["Exit"]} 35 | 36 | {"id":90,"obj":"label","x":45,"y":170,"w":220,"hidden":1,"shadow_width":50,"align":"center","mode":"break","enabled":true,"text_color":"#C7BAA7","pad_left":5,"pad_right":5,"pad_bottom":5,"bg_opa":180,"bg_color":"#000000","border_color":"#FFAC00","border_width":2,"radius":10,"radius1":10,"radius2":10} 37 | {"id":98,"obj":"dropdown","parentid":97,"x":165,"y":005,"w":306,"h":50,"hidden":0,"show_selected":1,"direction":0,"max_height":170,"bg_color":"#000000","border_color":"#C7BAA7","border_width":0,"text_color":"#FFAC00","text_font":24,"options":"","bg_opa":180,"open":1,"bg_opa40":100,"close":0} 38 | {"id":99,"obj":"dropdown","parentid":96,"x":005,"y":005,"w":200,"h":50,"hidden":0,"show_selected":1,"direction":0,"max_height":170,"bg_color":"#000000","border_color":"#C7BAA7","border_width":0,"text_color":"#FFAC00","text_font":24,"options":"","bg_opa":180,"open":1,"bg_opa40":100,"close":0} 39 | {"id":20,"obj":"slider","x":25,"y":350,"w":430,"h":35,"ext_click_v":20,"bg_opa":100,"bg_color":"#000000","border_color":"#FFAC00","border_width":2,"radius":5,"radius1":50,"radius2":5,"value_font":32,"value_color":"#FFFFFF","value_opa":255,"value_ofs_x":170,"value_ofs_y":-2,"min":0,"max":100,"val":0,"bg_color1":"#FFAC00","border_width1":2,"border_color1":"#FFAC00"} 40 | {"id":21,"obj":"label","x":2,"y":044,"w":33,"h":29,"click":0,"bg_opa":0,"bg_color":"#000000","border_color":"#C7BAA7","border_width":0,"text":"\ue75a","text_color":"#FFAC00","text_font":24} 41 | {"id":22,"obj":"label","x":2,"y":073,"w":33,"h":29,"click":0,"bg_opa":0,"bg_color":"#000000","border_color":"#C7BAA7","border_width":0,"text":"\ue004","text_color":"#FFAC00","text_font":24} 42 | {"id":23,"obj":"label","x":2,"y":073,"w":33,"h":29,"click":0,"hidden":1,"bg_opa":0,"bg_color":"#000000","border_color":"#C7BAA7","border_width":0,"text":"\ue4c3","text_color":"#FFAC00","text_font":24} 43 | -------------------------------------------------------------------------------- /wt32_01_plus.jsonl: -------------------------------------------------------------------------------- 1 | {"page":0,"comment":"---------- All Pages ----------"} 2 | {"id":01,"action":{"down":"page prev"},"obj":"btn","x":000,"y":400,"w":104,"h":80,"bg_opa":180,"bg_color":"#000000","border_color":"#C7BAA7","border_width":2,"radius":10,"radius1":10,"radius2":10,"text":"\ue141","text_color":"#978B7D","text_font":64} 3 | {"id":02,"action":{"down":"page back"},"obj":"btn","x":109,"y":400,"w":102,"h":80,"bg_opa":180,"bg_color":"#000000","border_color":"#C7BAA7","border_width":2,"radius":10,"radius1":10,"radius2":10,"text":"\ue2dc","text_color":"#978B7D","text_font":64} 4 | {"id":03,"action":{"down":"page next"},"obj":"btn","x":216,"y":400,"w":104,"h":80,"bg_opa":180,"bg_color":"#000000","border_color":"#C7BAA7","border_width":2,"radius":10,"radius1":10,"radius2":10,"text":"\ue142","text_color":"#978B7D","text_font":64} 5 | {"id":04,"obj":"roller","enabled":false,"click":false,"x":0,"y":0,"w":320,"bg_opa1":180,"bg_opa2":0,"border_width1":0,"val":0,"rows":1,"options":"ENERGY\nSONOS\nOFFICE\nPAGE 4\nPAGE 5\nPAGE 6","text_font":24,"bg_color1":"#000000","text_color1":"#FFFFFF","bg_color":"#000000","text_color":"#000000","border_side":0,"radius":0} 6 | {"id":05,"obj":"label","enabled":false,"x":227,"y":7,"h":45,"w":90,"bg_opa":0,"text":"\ue50f 00.0°C","align":"right","bg_color":"#2C3E50","text_color":"#FFFFFF","text_font":24} 7 | {"id":06,"obj":"label","enabled":false,"x":3,"y":7,"h":45,"w":62,"bg_opa":0,"text":"00:00","align":"left","bg_color":"#2C3E50","text_color":"#FFFFFF","text_font":24} 8 | {"id":07,"obj":"label","enabled":false,"x":60,"y":2,"w":30,"h":45,"bg_opa":0,"text":"\ue5a9","bg_color":"#2C3E50","text_color":"#FFFFFF","text_font":16} 9 | {"id":08,"obj":"label","enabled":false,"x":75,"y":2,"w":30,"h":45,"bg_opa":0,"text":"\ue004","bg_color":"#2C3E50","text_color":"green","text_font":16} 10 | 11 | 12 | {"page":2,"comment":"---------- Page 2 ----------NOTE: OBJ: 89 is used in automation for Sunton devices !! "} 13 | {"id":39,"obj":"obj","enabled":true,"x":0,"y":45,"w":320,"h":355,"bg_opa":100,"border_width":0} 14 | {"id":40,"obj":"img","enabled":true,"x":0,"y":0,"w":320,"h":320,"shadow_color":"#de8510","shadow_opa":150,"shadow_width":80,"shadow_spread":20,"parentid":39,"auto_size":0,"src":"L:/openhasp.png"} 15 | {"id":13,"obj":"obj","x":0,"y":46,"w":320,"h":61,"enabled":false,"bg_opa":100,"bg_color":"#000000","border_color":"#FFAC00","border_width":2,"radius":10,"radius1":10,"radius2":10} 16 | {"id":14,"obj":"label","x":35,"y":46,"w":282,"h":32,"bg_opa":0,"bg_color":"#000000","mode":"loop","text":"Title","text_color":"#C7BAA7","text_font":24} 17 | {"id":26,"obj":"label","x":35,"y":76,"w":282,"h":32,"bg_opa":0,"bg_color":"#000000","hidden":1,"mode":"loop","text":"Sonos Master - Sonos slaves","text_color":"#FFAC00","text_font":24} 18 | {"id":15,"obj":"label","x":35,"y":76,"w":282,"h":32,"bg_opa":0,"bg_color":"#000000","mode":"loop","text":"Artist","text_color":"#C7BAA7","text_font":24} 19 | {"id":41,"obj":"bar","enabled":false,"x":35,"y":75,"w":282,"h":4,"min":0,"max":100,"bg_color10":"#FFAC00","bg_opa":100,"border_opa":0,"pad_top":0,"pad_bottom":0,"pad_left":0,"pad_right":0} 20 | 21 | {"id":16,"obj":"btn","x":208,"y":109,"w":54,"h":59,"bg_opa":150,"bg_color":"#000000","border_color":"#FFAC00","border_width":2,"radius":10,"radius1":10,"radius2":10,"text":"\ue4ae","text_color":"#C7BAA7","text_font":64} 22 | {"id":17,"obj":"btn","x":208,"y":171,"w":111,"h":79,"bg_opa":150,"toggle":true,"bg_color":"#000000","border_color":"#FFAC00","border_width":2,"radius":10,"radius1":10,"radius2":10,"text":"\ue40a","text_color":"#C7BAA7","text_font":64} 23 | {"id":18,"obj":"btn","x":265,"y":109,"w":54,"h":59,"bg_opa":150,"bg_color":"#000000","border_color":"#FFAC00","border_width":2,"radius":10,"radius1":10,"radius2":10,"text":"\ue4ad","text_color":"#C7BAA7","text_font":64} 24 | {"id":25,"obj":"btn","toggle":true,"x":265,"y":253,"w":54,"h":59,"bg_opa":150,"bg_color":"#000000","border_color":"#FFAC00","border_width":2,"text":"\ue49e","text_color":"#C7BAA7","text_color1":"#FFAC00","text_font":48} 25 | {"id":27,"obj":"btn","toggle":false,"x":208,"y":253,"w":54,"h":59,"bg_opa":150,"bg_color":"#000000","border_color":"#FFAC00","border_width":2,"text":"\ue457","text_color":"#C7BAA7","text_font":48} 26 | {"id":24,"obj":"btn","toggle":true,"x":001,"y":337,"w":40,"h":40,"ext_click_v":20,"bg_opa":150,"bg_color":"#000000","border_color":"#FFAC00","border_width":2,"text":"\ue580","text_color":"#C7BAA7","text_color1":"#FF0000","text_font":32} 27 | 28 | {"id":95,"obj":"tabview","btn_pos":1,"y":108,"h":285,"text_font":20,"bg_opa":0,"bg_opa1":100,"bg_opa30":180,"hidden":1,"border_width":0} 29 | {"id":96,"obj":"tab","parentid":95,"text":"Sources"} 30 | {"id":97,"obj":"tab","parentid":95,"text":"Playlists"} 31 | {"id":94,"obj":"tab","parentid":95,"text":"Slaves"} 32 | {"id":92,"obj":"tab","parentid":95,"text":"Master"} 33 | {"id":93,"obj":"btnmatrix","parentid":94,"x":116,"y":005,"w":195,"h":160,"bg_opa":180,"bg_opa40":180,"toggle":0,"one_check":0,"text_font":32,"options":["#FF0000 Speaker 1#","#0000FF Speaker 2#","\n","#FFFF00 Speaker 3#"]} 34 | {"id":88,"obj":"btnmatrix","parentid":92,"x":116,"y":005,"h":160,"w":195,"bg_opa":180,"bg_opa40":180,"toggle":0,"one_check":0,"text_font":32,"options":["Exit"]} 35 | 36 | {"id":90,"obj":"label","x":45,"y":170,"w":220,"hidden":1,"shadow_width":50,"align":"center","mode":"break","enabled":true,"text_color":"#C7BAA7","pad_left":5,"pad_right":5,"pad_bottom":5,"bg_opa":180,"bg_color":"#000000","border_color":"#FFAC00","border_width":2,"radius":10,"radius1":10,"radius2":10} 37 | {"id":98,"obj":"dropdown","parentid":97,"x":060,"y":005,"w":250,"h":50,"hidden":0,"show_selected":1,"direction":0,"max_height":170,"bg_color":"#000000","border_color":"#C7BAA7","border_width":0,"text_color":"#FFAC00","text_font":24,"options":"","bg_opa":180,"open":1,"bg_opa40":100,"close":0} 38 | {"id":99,"obj":"dropdown","parentid":96,"x":005,"y":005,"w":250,"h":50,"hidden":0,"show_selected":1,"direction":0,"max_height":170,"bg_color":"#000000","border_color":"#C7BAA7","border_width":0,"text_color":"#FFAC00","text_font":24,"options":"","bg_opa":180,"open":1,"bg_opa40":100,"close":0} 39 | {"id":20,"obj":"slider","x":70,"y":340,"w":240,"h":35,"ext_click_v":10,"bg_opa":100,"bg_color":"#000000","border_color":"#FFAC00","border_width":2,"radius":5,"radius1":50,"radius2":5,"value_font":32,"value_color":"#FFFFFF","value_opa":255,"value_ofs_x":98,"value_ofs_y":-2,"min":0,"max":100,"val":0,"bg_color1":"#FFAC00","border_width1":2,"border_color1":"#FFAC00"} 40 | {"id":21,"obj":"label","x":2,"y":046,"w":33,"h":29,"click":0,"bg_opa":0,"bg_color":"#000000","border_color":"#C7BAA7","border_width":0,"text":"\ue75a","text_color":"#FFAC00","text_font":24} 41 | {"id":22,"obj":"label","x":2,"y":075,"w":33,"h":29,"click":0,"bg_opa":0,"bg_color":"#000000","border_color":"#C7BAA7","border_width":0,"text":"\ue004","text_color":"#FFAC00","text_font":24} 42 | {"id":23,"obj":"label","x":2,"y":075,"w":33,"h":29,"click":0,"hidden":1,"bg_opa":0,"bg_color":"#000000","border_color":"#C7BAA7","border_width":0,"text":"\ue4c3","text_color":"#FFAC00","text_font":24} 43 | -------------------------------------------------------------------------------- /sunton.jsonl: -------------------------------------------------------------------------------- 1 | {"page":0,"comment":"---------- All Pages ----------"} 2 | {"id":24,"action":{"down":"page 6"},"obj":"btn","x":000,"y":400,"w":110,"h":80,"bg_opa":180,"bg_color":"#000000","border_color":"#C7BAA7","border_width":2,"radius":10,"radius1":10,"radius2":10,"text":"\ued3e","text_color":"#978B7D","text_font":64} 3 | {"id":25,"action":{"down":"page 1"},"obj":"btn","x":115,"y":400,"w":110,"h":80,"bg_opa":180,"bg_color":"#000000","border_color":"#C7BAA7","border_width":2,"radius":10,"radius1":10,"radius2":10,"text":"\ufa74","text_color":"#978B7D","text_font":64} 4 | {"id":01,"action":{"down":"page prev"},"obj":"btn","x":230,"y":400,"w":110,"h":80,"bg_opa":180,"bg_color":"#000000","border_color":"#C7BAA7","border_width":2,"radius":10,"radius1":10,"radius2":10,"text":"\ue141","text_color":"#978B7D","text_font":64} 5 | {"id":02,"action":{"down":"page back"},"obj":"btn","x":345,"y":400,"w":110,"h":80,"bg_opa":180,"bg_color":"#000000","border_color":"#C7BAA7","border_width":2,"radius":10,"radius1":10,"radius2":10,"text":"\ue2dc","text_color":"#978B7D","text_font":64} 6 | {"id":03,"action":{"down":"page next"},"obj":"btn","x":460,"y":400,"w":110,"h":80,"bg_opa":180,"bg_color":"#000000","border_color":"#C7BAA7","border_width":2,"radius":10,"radius1":10,"radius2":10,"text":"\ue142","text_color":"#978B7D","text_font":64} 7 | {"id":26,"action":{"down":"page 2"},"obj":"btn","x":575,"y":400,"w":110,"h":80,"bg_opa":180,"bg_color":"#000000","border_color":"#C7BAA7","border_width":2,"radius":10,"radius1":10,"radius2":10,"text":"\ue4c3","text_color":"#978B7D","text_font":64} 8 | {"id":27,"action":{"down":"page 4"},"obj":"btn","x":690,"y":400,"w":110,"h":80,"bg_opa":180,"bg_color":"#000000","border_color":"#C7BAA7","border_width":2,"radius":10,"radius1":10,"radius2":10,"text":"\ue595","text_color":"#978B7D","text_font":64} 9 | 10 | {"id":04,"obj":"roller","enabled":false,"click":false,"x":0,"y":-1,"w":800,"text_line_space":10,"bg_opa1":180,"bg_opa2":0,"border_width1":0,"val":0,"rows":1,"options":"ENERGY\nSONOS\nOFFICE\nWEATHER\nFAMILY\nNORDPOOL","text_font":24,"bg_color1":"#000000","text_color1":"#FFFFFF","bg_color":"#000000","text_color":"#000000","border_side":0,"radius":0} 11 | {"id":05,"obj":"label","enabled":false,"x":707,"y":5,"h":30,"w":90,"bg_opa":0,"text":"00.0°C","align":"right","bg_color":"#2C3E50","text_color":"#FFFFFF","text_font":24} 12 | {"id":06,"obj":"label","enabled":false,"x":50,"y":5,"w":250,"h":30,"bg_opa":0,"text":"00:00:00","template":"%H:%M:%S %b %e, %Y","bg_color":"#2C3E50","text_color":"white","align":"left","text_font":24} 13 | {"id":07,"obj":"label","enabled":false,"x":05,"y":2,"w":30,"h":20,"bg_opa":0,"text":"\ue5a9","bg_color":"#2C3E50","text_color":"#FFFFFF","text_font":16} 14 | {"id":08,"obj":"label","enabled":false,"x":20,"y":2,"w":30,"h":20,"bg_opa":0,"text":"\ue004","bg_color":"#2C3E50","text_color":"green","text_font":16} 15 | 16 | 17 | {"page":2,"bg_grad_color":"#d58518","bg_color":"grey","border_width":0,"comment":"---------- Page 2 ----------"} 18 | {"id":39,"obj":"obj","enabled":true,"x":0,"y":43,"w":355,"h":355,"bg_opa":180,"clip_corner":1,"border_post":1,"border_color":"#FFAC00","border_width":2,"radius":10,"radius1":10,"radius2":10} 19 | {"id":40,"obj":"img","enabled":true,"x":0,"y":0,"w":355,"h":355,"parentid":39,"auto_size":1,"src":"L:/openhasp.png"} 20 | 21 | {"id":13,"obj":"obj","x":358,"y":43,"w":442,"h":64,"enabled":false,"bg_opa":100,"bg_color":"#000000","border_color":"#FFAC00","border_width":2,"radius":10,"radius1":10,"radius2":10} 22 | {"id":14,"obj":"label","enabled":false,"x":35,"y":-02,"w":399,"h":32,"parentid":13,"bg_opa":0,"bg_color":"#000000","mode":"loop","text":"Title","text_color":"#C7BAA7","text_font":24} 23 | {"id":26,"obj":"label","enabled":false,"x":35,"y":30,"w":399,"h":32,"parentid":13,"bg_opa":0,"bg_color":"#000000","hidden":1,"mode":"loop","text":"Sonos Master - Sonos slaves","text_color":"#FFAC00","text_font":24} 24 | {"id":15,"obj":"label","enabled":false,"x":35,"y":30,"w":399,"h":32,"parentid":13,"bg_opa":0,"bg_color":"#000000","mode":"loop","text":"Artist","text_color":"#C7BAA7","text_font":24} 25 | {"id":41,"obj":"bar","enabled":false,"x":35,"y":30,"w":385,"h":4,"parentid":13,"min":0,"max":100,"bg_color10":"#FFAC00","bg_opa":100,"border_opa":0,"pad_top":0,"pad_bottom":0,"pad_left":0,"pad_right":0} 26 | 27 | {"id":16,"obj":"btn","x":632,"y":110,"w":83,"h":79,"bg_opa":150,"bg_color":"#000000","border_color":"#FFAC00","border_width":2,"radius":10,"radius1":10,"radius2":10,"text":"\ue4ae","text_color":"#C7BAA7","text_font":64} 28 | {"id":17,"obj":"btn","x":632,"y":192,"w":168,"h":79,"bg_opa":150,"toggle":true,"bg_color":"#000000","border_color":"#FFAC00","border_width":2,"radius":10,"radius1":10,"radius2":10,"text":"\ue40a","text_color":"#C7BAA7","text_font":64} 29 | {"id":18,"obj":"btn","x":718,"y":110,"w":82,"h":79,"bg_opa":150,"bg_color":"#000000","border_color":"#FFAC00","border_width":2,"radius":10,"radius1":10,"radius2":10,"text":"\ue4ad","text_color":"#C7BAA7","text_font":64} 30 | {"id":25,"obj":"btn","toggle":true,"x":689,"y":274,"w":54,"h":59,"bg_opa":150,"bg_color":"#000000","border_color":"#FFAC00","border_width":2,"text":"\ue49e","text_color":"#C7BAA7","text_color1":"#FFAC00","text_font":48} 31 | {"id":27,"obj":"btn","toggle":false,"x":632,"y":274,"w":54,"h":59,"bg_opa":150,"bg_color":"#000000","border_color":"#FFAC00","border_width":2,"text":"\ue457","text_color":"#C7BAA7","text_font":48} 32 | {"id":24,"obj":"btn","toggle":true,"x":746,"y":274,"w":54,"h":59,"bg_opa":150,"bg_color":"#000000","border_color":"#FFAC00","border_width":2,"text":"\ue580","text_color":"#C7BAA7","text_color1":"#FF0000","text_font":48} 33 | 34 | {"id":95,"obj":"tabview","x":0,"y":0,"h":355,"w":355,"parentid":39,"text_font":32,"btn_pos":1,"bg_opa":0,"bg_opa1":100,"bg_opa30":180,"hidden":1,"border_width":0} 35 | {"id":96,"obj":"tab","parentid":95,"text":"Sources"} 36 | {"id":97,"obj":"tab","parentid":95,"text":"Playlists"} 37 | {"id":93,"obj":"btnmatrix","x":358,"y":192,"h":141,"w":260,"bg_opa":100,"bg_opa40":255,"toggle":0,"one_check":0,"text_font":32,"options":["#FF0000 Spk. 1#","#0000FF Spk. 2#","\n","#FFFF00 Spk. 3#","#0000FF Spk. 4#","\n","#FF0000 Spk. 5#","#0000FF Spk. 6#"],"border_width":2,"border_color":"#FFAC00"} 38 | {"id":88,"obj":"btnmatrix","x":358,"y":192,"h":141,"w":260,"hidden":1,"bg_opa":100,"bg_opa40":255,"toggle":0,"one_check":0,"text_font":28,"options":["Exit"],"border_width":4,"border_color":"#FFAC00"} 39 | {"id":89,"obj":"btn","x":358,"y":110,"h":79,"w":260,"toggle":false,"bg_opa":100,"bg_color":"#d58518","border_color":"#FFAC00","border_width":4,"radius":10,"radius1":10,"radius2":10,"text":"MASTER","align":"center","text_color":"#FFAC00","text_font":42,"value_str":"\ue30b\n\ue4c3","value_font":32,"value_line_space":-8,"value_ofs_y":0,"value_ofs_x":-108,"value_color":"#FFAC00"} 40 | {"id":90,"obj":"label","x":45,"y":170,"w":220,"hidden":1,"shadow_width":50,"align":"center","mode":"break","enabled":true,"text_color":"#C7BAA7","pad_left":5,"pad_right":5,"pad_bottom":5,"bg_opa":180,"bg_color":"#000000","border_color":"#FFAC00","border_width":2,"radius":10,"radius1":10,"radius2":10} 41 | {"id":98,"obj":"dropdown","parentid":97,"x":020,"y":005,"w":315,"h":50,"hidden":0,"show_selected":1,"direction":0,"max_height":225,"bg_color":"#000000","border_color":"#C7BAA7","border_width":0,"text_color":"#FFAC00","text_font":24,"options":"","bg_opa":180,"open":1,"bg_opa40":100,"close":0} 42 | {"id":99,"obj":"dropdown","parentid":96,"x":020,"y":005,"w":315,"h":50,"hidden":0,"show_selected":1,"direction":0,"max_height":225,"bg_color":"#000000","border_color":"#C7BAA7","border_width":0,"text_color":"#FFAC00","text_font":24,"options":"","bg_opa":180,"open":1,"bg_opa40":100,"close":0} 43 | {"id":20,"obj":"slider","x":385,"y":350,"w":395,"h":35,"ext_click_v":10,"bg_opa":100,"bg_color":"#000000","border_color":"#FFAC00","border_width":2,"radius":5,"radius1":50,"radius2":5,"value_font":32,"value_color":"#FFFFFF","value_opa":255,"value_ofs_x":170,"value_ofs_y":-2,"min":0,"max":100,"val":0,"bg_color1":"#FFAC00","border_width1":2,"border_color1":"#FFAC00"} 44 | {"id":21,"obj":"label","x":2,"y":000,"w":33,"h":32,"parentid":13,"click":0,"bg_opa":0,"bg_color":"#000000","border_color":"#C7BAA7","border_width":0,"text":"\ue75a","text_color":"#FFAC00","text_font":24} 45 | {"id":22,"obj":"label","x":2,"y":029,"w":33,"h":32,"parentid":13,"click":0,"bg_opa":0,"bg_color":"#000000","border_color":"#C7BAA7","border_width":0,"text":"\ue004","text_color":"#FFAC00","text_font":24} 46 | {"id":23,"obj":"label","x":2,"y":029,"w":33,"h":32,"parentid":13,"click":0,"hidden":1,"bg_opa":0,"bg_color":"#000000","border_color":"#C7BAA7","border_width":0,"text":"\ue4c3","text_color":"#FFAC00","text_font":24} 47 | 48 | 49 | -------------------------------------------------------------------------------- /configuration.yaml: -------------------------------------------------------------------------------- 1 | # OpenHASP plate config 2 | # Search for t3e_02 and replace with your own plate name 3 | # Plate configuration/automation rely on media player on page 2 in openHASP 4 | 5 | openhasp: 6 | t3e_02: 7 | objects: 8 | # Page 0 objects - Top status bar and navigation buttons 9 | # These object can all be deleted if not needed or altered to fit your needs 10 | - obj: "p0b4" 11 | properties: 12 | "val": "{{states('openhasp.t3e_02') | int(0) -1}}" 13 | - obj: "p0b5" 14 | properties: 15 | "text": "{% if states('sensor.outdoor_temperature') != 'unknown' %} {{ '%.1f' | format (states('sensor.outdoor_temperature') | round(1)) }}{{'\\u00b0'}}C {% else %} -.-{{'\\u00b0'}}C{% endif %}" 16 | - obj: "p0b7" 17 | properties: 18 | "text_color": "{% if -50 <= state_attr('openhasp.t3e_02','rssi') | int(0) %}green{% elif -51 > state_attr('openhasp.t3e_02','rssi') | int(0) >= -60 %}olive{% elif -61 > state_attr('openhasp.t3e_02','rssi') | int(0) >= -70 %}orange{% elif -71 > state_attr('openhasp.t3e_02','rssi') |int (0) >= -80 %}maroon{% else %}red{% endif %}" 19 | 20 | 21 | 22 | 23 | 24 | # Sonos support sensors - openHASP Sonos group multiple plates configuration 25 | sensor: 26 | - platform: template 27 | sensors: 28 | sonos_master_friendly: 29 | friendly_name: "Sonos Master Friendly" 30 | value_template: "{{ state_attr(state_attr('group.sonos_all', 'entity_id')[0], 'friendly_name') }}" 31 | 32 | sonos_slaves_friendly: 33 | friendly_name: "Sonos Slaves Friendly" 34 | value_template: >- 35 | {% for entity_id in state_attr("group.sonos_all", "entity_id")[1:] -%} 36 | {% set friendly_name = state_attr(entity_id, "friendly_name") %} 37 | {%- if loop.last %}{{ friendly_name }}{% else %}{{ friendly_name }}, {% endif -%} 38 | {%- endfor %} 39 | 40 | sonos_active_speakers: 41 | value_template: >- 42 | {{ states('sensor.sonos_master_friendly')}} {% if states('sensor.sonos_slaves_friendly') | length != 0%}{{"•"|e}} {{states('sensor.sonos_slaves_friendly')}}{% else %}{% endif %} 43 | 44 | # Check for Playlist (no source) or source is non TuneIn stations OR non 'DR' source and non TuneIn source and media_artist present(=not paused) 45 | # Split title if source starts with 'DR ' (All DR TuneIn radio stations) and keep first part of string before '/' as title 46 | # If source doesn't start with 'DR ' and media_title doesn't contain ' / ' in string, then skip the split to avoid source as title for DR radio stations when source is paused 47 | # ** Swap title/artist for all other non TuneIn sources if artist is present ** 48 | media_title: 49 | value_template: >- 50 | {% if (is_state('sensor.media_source', 'no source') and state_attr(state_attr('group.sonos_all', 'entity_id')[0], 'media_title') != None) or (state_attr(state_attr('group.sonos_all', 'entity_id')[0], 'media_title') != None and states(state_attr('group.sonos_all', 'entity_id')[0]) == 'playing' and states('sensor.media_source')[0:3] != 'DR ' and states('binary_sensor.sonos_media_flip_title_artist') == 'off') %} 51 | {{ state_attr(state_attr('group.sonos_all', 'entity_id')[0], 'media_title') }} 52 | {% elif states('sensor.media_source')[0:3] == 'DR ' and state_attr((state_attr('group.sonos_all', 'entity_id')[0]), 'media_title') != None and states(state_attr('group.sonos_all', 'entity_id')[0]) == 'playing' and (true if ' / ' in state_attr(state_attr('group.sonos_all', 'entity_id')[0], 'media_title') else false) %} 53 | {{ state_attr((state_attr('group.sonos_all', 'entity_id')[0]), 'media_title').split (' / ')[0] }} 54 | {% elif states('binary_sensor.sonos_media_flip_title_artist') == 'on' and state_attr(state_attr('group.sonos_all', 'entity_id')[0], 'media_artist') != None %} 55 | {{ state_attr(state_attr('group.sonos_all', 'entity_id')[0], 'media_artist') }} 56 | {% else %} 57 | --- 58 | {% endif %} 59 | 60 | # Swap title/artist if 'source' attribute is present (radio stations) or source is non TuneIn stations 61 | # Check for Playlist (no source) and split title if source starts with 'DR ' and title includes ' / ' (All DR TuneIn radio stations) 62 | # Keep last part of title string after '/' as artist 63 | media_artist: 64 | value_template: >- 65 | {% if (is_state('sensor.media_source', 'no source') and state_attr(state_attr('group.sonos_all', 'entity_id')[0], 'media_artist') != None) or (state_attr(state_attr('group.sonos_all', 'entity_id')[0], 'media_artist') != None and states('sensor.media_source')[0:3] != 'DR ' and states('binary_sensor.sonos_media_flip_title_artist') == 'off') %} 66 | {{ state_attr(state_attr('group.sonos_all', 'entity_id')[0], 'media_artist') }} 67 | {% elif states('sensor.media_source')[0:3] == 'DR ' and state_attr(state_attr('group.sonos_all', 'entity_id')[0], 'media_title') != None and states(state_attr('group.sonos_all', 'entity_id')[0]) == 'playing' and (true if ' / ' in state_attr(state_attr('group.sonos_all', 'entity_id')[0], 'media_title') else false) %} 68 | {{ state_attr((state_attr('group.sonos_all', 'entity_id')[0]), 'media_title').split (' / ')[1] }} 69 | {% elif states('binary_sensor.sonos_media_flip_title_artist') == 'on' and state_attr(state_attr('group.sonos_all', 'entity_id')[0], 'media_title') != None %} 70 | {{ state_attr(state_attr('group.sonos_all', 'entity_id')[0], 'media_title') }} 71 | {% else %} 72 | --- 73 | {% endif %} 74 | 75 | media_album_name: 76 | value_template: >- 77 | {% if state_attr(state_attr('group.sonos_all', 'entity_id')[0], 'media_album_name') %} 78 | {{ state_attr(state_attr('group.sonos_all', 'entity_id')[0], 'media_album_name') }} 79 | {% else %} 80 | --- No album name --- 81 | {% endif %} 82 | 83 | # Get media_source from Sonos favorites list (including any trailing '|' symbol) 84 | # Needed in order to match current source playing when using next/previous buttons for sources 85 | # If not in favorites list, then return current data from media channel attribute 86 | media_source: 87 | value_template: >- 88 | {% if state_attr(state_attr('group.sonos_all', 'entity_id')[0], 'media_channel') %} 89 | {% set ns = namespace(source='') %} 90 | {% for name in state_attr("sensor.sonos_favorites", "items").values() if name[0:] == state_attr(state_attr('group.sonos_all', 'entity_id')[0], 'media_channel') or name[0:-1] == state_attr(state_attr('group.sonos_all', 'entity_id')[0], 'media_channel') %} 91 | {% set ns.source = name %} 92 | {% endfor %} 93 | {% if ns.source !='' %} 94 | {{ ns.source }} 95 | {% else %} 96 | {{ state_attr(state_attr('group.sonos_all', 'entity_id')[0], 'media_channel') }} 97 | {% endif %} 98 | {% else %} 99 | no source 100 | {% endif %} 101 | 102 | sonos_volume: 103 | value_template: "{{ (state_attr(state_attr('group.sonos_all', 'entity_id')[0], 'volume_level')| float * 100) | int(0) }}" 104 | 105 | sonos_active_group_speakers: 106 | value_template: "{{ state_attr(state_attr('group.sonos_all', 'entity_id')[0], 'group_members') }}" 107 | 108 | # Combine all media players in an array and exclude current master speaker and unavailable speakers 109 | # Check group.sensor_all[0] for master speaker exclude 110 | sonos_available_slave_speakers: 111 | value_template: >- 112 | {% set ns = namespace(player="") %} 113 | {% for state in states.media_player if is_device_attr(state.entity_id, 'manufacturer', 'Sonos') and states(state.entity_id) != 'unavailable' and state.entity_id != state_attr('group.sonos_all', 'entity_id')[0] %} 114 | {% set ns.player = ns.player + state.entity_id %} 115 | {% if not loop.last %} 116 | {% set ns.player = ns.player + ',' %} 117 | {% endif %} 118 | {% endfor %} 119 | {{ ns.player }} 120 | 121 | sonos_repeat: 122 | value_template: "{{ state_attr(state_attr('group.sonos_all', 'entity_id')[0], 'repeat') }}" 123 | 124 | # Sensor for sensor.sonos_source index start for 'real' sources (skip playlists, songs etc.) 125 | # 190822: Revised to use new sensor.sonos_favorites as media_player source_list attribute has been deprecated 126 | sonos_source_index_start: 127 | value_template: >- 128 | {% set ns = namespace(counter=1) %} 129 | {%for sources in state_attr('sensor.sonos_favorites','items').values() if sources[0]== "*" %} 130 | {%-if not loop.last %} 131 | {%set ns.counter = ns.counter + 1%} 132 | {%-endif%} 133 | {%-endfor%} 134 | {{ ns.counter | int(0) }} 135 | 136 | hasp_sonos_master_state: 137 | value_template: "{{ states(state_attr('group.sonos_all', 'entity_id')[0]) }}" 138 | 139 | hasp_sonos_master_image: 140 | value_template: "{{ state_attr(state_attr('group.sonos_all', 'entity_id')[0], 'entity_picture') }}" 141 | 142 | hasp_sonos_media_duration: 143 | value_template: >- 144 | {% if state_attr(state_attr('group.sonos_all', 'entity_id')[0], 'media_duration') != None %} 145 | {{ state_attr(state_attr('group.sonos_all', 'entity_id')[0], 'media_duration') | int(0) }} 146 | {% else %} 147 | 0 148 | {% endif %} 149 | 150 | ### 260223 Added media position helper sensor 151 | hasp_sonos_media_position: 152 | value_template: > 153 | {% if state_attr(state_attr('group.sonos_all', 'entity_id')[0], 'media_position') != None %} 154 | {{ state_attr(state_attr('group.sonos_all', 'entity_id')[0], 'media_position') | int(0) }} 155 | {% else %} 156 | 0 157 | {% endif %} 158 | 159 | hasp_sonos_playlist_options: 160 | value_template: >- 161 | {%for sources in state_attr('sensor.sonos_favorites','items').values() if sources[0]=="*"%}{{sources[1:]+"\n"|e}}{%-if not loop.last%}{%-endif%}{%-endfor%} 162 | 163 | hasp_sonos_source_options: 164 | value_template: >- 165 | {%for sources in state_attr('sensor.sonos_favorites','items').values() if sources[0]!="*"%}{%if sources[-1]!="|"%}{{sources+"\n"}}{%else%}{{sources[0:-1]+"\n"}}{%endif%}{%endfor%} 166 | 167 | hasp_all_available_devices: 168 | value_template: > 169 | {% set ns = namespace(ad="") %} 170 | {% for state in states.openhasp if states(state.entity_id) != 'unavailable' %} 171 | {% set ns.ad = ns.ad + state.entity_id %} 172 | {% if not loop.last %} 173 | {% set ns.ad = ns.ad + ',' %} 174 | {% endif %} 175 | {% endfor %} 176 | {{ ns.ad }} 177 | 178 | hasp_sonos_devices: 179 | value_template: "{{ state_attr('group.hasp_sonos_devices', 'entity_id')[0] }}" 180 | 181 | mqtt: 182 | sensor: 183 | # Used in one condition template to return Sonos volume back to the plates 184 | name: "hasp_volume" 185 | state_topic: "hasp/+/state/p2b20" 186 | value_template: "{{ value_json.val }}" 187 | 188 | binary_sensor: 189 | - platform: template 190 | sensors: 191 | sonos_media_flip_title_artist: 192 | value_template: "{{ 'on' if states('sensor.media_source')[-1] =='|' else 'off' }}" 193 | sonos_volume_muted: 194 | value_template: "{{ state_attr(state_attr('group.sonos_all', 'entity_id')[0], 'is_volume_muted') }}" 195 | sonos_shuffle: 196 | value_template: "{{ state_attr(state_attr('group.sonos_all', 'entity_id')[0], 'shuffle') }}" 197 | sonos_tunein_is_source: 198 | value_template: "{{ state_attr(state_attr('group.sonos_all', 'entity_id')[0], 'media_content_id') is search('tunein', ignorecase=True) }}" 199 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # openHASP Sonos group player - multiple plates documentation v1.05 3 | 4 | ![T3E Sonos media player plate](https://github.com/htvekov/openHASP-Sonos-media-player/blob/main/images/image.png) ![](https://github.com/htvekov/openHASP-Sonos-media-player/blob/main/images/image6_320x480.png) 5 | ![](https://github.com/htvekov/openHASP-Sonos-media-player/blob/main/images/sunton_openhasp_sonos_01.png) 6 | 7 | ### Revision: 8 | - **1.05** (2023-02-27) 9 | Migration of the last CC entries to automations now completed. Configuration now only consists of sensors in configuration.yaml and logic in automations.yaml 10 | - **1.04** (2023-02-23) 11 | Configuration now supports multiple, simultanious plates with varying display width. openHASP entities are now added dynamically as well. Only need to enter one initial plate entity_id and one initial Sonos speaker entity. Automations will populate groups dynamically 12 | - **1.03** (2023-02-11) 13 | Added 480x800 res. display layout (Sunton 5 and 7" devices). Master speaker entity is now dynamic and can be altered directly from plate -> All groups or single players can be controlled and/or altered. 14 | 15 | - **1.02** (2023-01-30) 16 | Removed `zoom` service calls as Custom Component now support image upscaling. Requires openHASP Custom Component from `main` branch commit `b2ed186` or newer 17 | 18 | - **1.01** (2023-01-28) 19 | Added revised layout (**portrait** mode) for 320x480 resolution devices 20 | 21 | - **1.00** (2023-01-25) 22 | Removed all hardcoded media player entities from automations- and config yaml files. Designated master speaker `entity_id` now only need to be entered in `group.sonos_all` 23 | Cleaned up the hardcoded openHASP plate entities as well. Some nine entities are left in total, where only five of these are actually needed. 24 | Configuration prepared to run either multiple 320x480 or 480x480 res. devices without further alterations (besides the device entity_id) 25 | 26 | ### Prerequisities: 27 | - openHASP 0.6.4-dev 28 | - commit 7a83367 or newer (January 18th, 2023) 29 | - openHASP Custom Component installed in HA 30 | - `main` branch commit `b2ed186` or newer required (January 30th, 2023) 31 | - Custom Component is still needed, even though all logic has been migrated from CC entries to automations, as logic use the CC `push_image` function 32 | - Home Assistant with Sonos integration installed 33 | - Tested working with Home Assistant 2022.10.4 34 | - Sonos speakers + active Sonos app 35 | - Tested working with S1 (legacy) and S2 version devices 36 | - Setup with both S1 & S2 devices will work as well. But S1 devices can't join S2 groups and vice versa (Sonos restriction) 37 | - GS-T3E / WT32-SC01 (plus) / Sunton ESP32-48S035 / Sunton ESP32-8048S050 / Sunton ESP32-8048S070 or similar devices **with** PSram 38 | - Configuration can dynamically handle **both** multiple 320x480, 480x480 or 480x800 resolution displays 39 | - Even devices with different resolutions can be mixed and matched, as configuration now is fully dynamic 40 | 41 | ### Description: 42 | With this openHASP Sonos plate configuration, you’ll be able to control single or grouped Sonos speakers from multiple openHASP plates. Plates just have to share identical object mapping (page/object number). 43 | 44 | Approach was to make this setup as dynamic as possible. The original design with a designated hardcoded master speaker has now been abandoned and replaced with a fully dynamic group speaker configuration. Now it's possible to change both master- and slave speakers and jump between all active groups or single speakers by changing the Master Speaker. By design, all plates will always display the same chosen speaker group. 45 | 46 | I also wanted to keep mqtt 'chatter' to a minimum and have mainly used mqtt group topics and not entities for boths triggers and actions. Hence the configuration logic is entirely done as automations and not as Custom Component configuration. As configuration is quite elaborate, a multi plate Custom Component configuration for eg. five plates would also have exceeded more than 4000 lines ! 47 | 48 | This configuration only allows control of all speakers volume as a global group. So no individual volume setting is possible from plates. Joining new slave speakers to the group, will also set new speakers volume to match master speakers volume - before it’s joined. 49 | 50 | Configuration templates will split all Sonos Favourites in two groups, but will need a little help from you to determine what belongs to which group. All ‘non sources’ (playlists, songs, podcasts etc. from e.g. Spotify) should be renamed and prefixed with an asteriks ‘*’ (No extra spaces, just an asteriks). Renaming is easily done directly in the Sonos app or via Sonos web interface. 51 | 52 | Sources (radio stations) unfortunately doesn’t follow any strict rules on how to present media meta data. So some radio stations will unfortunately appear on plate with artist/title mixed up. Source in Sonos Favourites can be renamed and suffixed with a ‘|’ pipe symbol (No extra spaces, just a pipe symbol). Templates in config will seek out this pipe symbol and swap title/artist on plate display. Symbol will also be hidden in dropdown list, pop-up info etc. 53 | Special fix for handling danish national radio stations is hardcoded in templates: 54 | 55 | - Template search for specific string in `media_title` attribute is done as well. This is needed as quite a few danish broadcasters meta data has both title and artist combined in the `media_title` attribute - only separated with a ' [SPACE]/[SPACE]'. 56 | 57 | Example below: 58 | 59 | media_title: Elephant Woman / Blonde Redhead 60 | 61 | ### Keywords: 62 | - **Source/playlist album image** is displayed as background for the entire page. All other overlaying objects are drawn with varying opacity on top of the source/album image 63 | - **Next/previous buttons** 64 | - **In active playlist:** Select next/previous song 65 | - **When source is selected:** Skip to next/previous source in list. Will also skip to first source, if at last source or vice versa (loop through) 66 | - **Play/pause pop-up info** overlay (5 sec.) shows info about playlist- or source name, origin (TuneIn (somewhat irregular) and Spotify source detection) and playing status 67 | - **Source/playlist dropdown** list selection via image object button 68 | - Press and **hold** image object for tabview to appear 69 | - If you regret to change, then exit via either of the dropdown lists ‘arrow down’ symbols 70 | - **Master speaker selection** 71 | - ***480 x 800 resolution. layout*** 72 | - Press and **hold** Master speaker button until new button matrix appears 73 | - Button matrix with all available Sonos speakers shown in **blue**, will replace the slave speaker button matrix 74 | - Choose new master speaker or exit via **EXIT** button shown in **white** 75 | 76 | - ***320 x 480 and 480 x 480 resolution layouts*** 77 | - Found on **tabview** mentioned under Source/playlist dropdown 78 | - All available Sonos speakers are listed in **blue** (current master speaker excluded from list) 79 | - After selection, the entire dropdown menu and tabview will be closed automatically 80 | 81 | - **Slave speaker selection** 82 | - ***480 x 800 resolution. layout*** 83 | - Available slave speakers are listed in the button matrix displayed under the Master speaker button 84 | - Already grouped slave speakers are listed in **green** 85 | - Non grouped, available speakers are listed in **red** 86 | - Unavailable speakers will not be shown at all (dynamic group) 87 | - **Toggle** buttons to **join/unjoin** speakers 88 | 89 | - ***320 x 480 and 480 x 480 resolution layouts*** 90 | - on **tabview** mentioned under Source/playlist dropdown 91 | - Already grouped slave speakers are listed in **green** 92 | - Non grouped, available speakers are listed in **red** 93 | - Unavailable speakers will not be shown at all (dynamic group) 94 | - **Toggle** buttons to join/unjoin speakers 95 | - **Exit** selection tab via either of the dropdown lists **‘arrows’**. 96 | 97 | - **Shuffle/repeat** buttons and **progress bar** are **disabled** playing sources 98 | - Automation will update progress bar every 5 seconds while playing non sources. Progress bar is shown between artist and title objects 99 | - **Play/pause** button will play/pause (Sonos don’t use stop function. Only when idle) 100 | - **Volume mute** button will mute/unmute whole group of speakers 101 | - **Volume slider** will change volume setting for the whole group of speakers. Volume level indicated on the right - inside the slider. At high volume (above 75 on plate = 'real' volume at 60) indicator will be placed on the left side instead 102 | - **Bezier algorithm** has been implemented. This makes it much easier to adjust slider at **low level volume** compared to a 'standard' linear slider 103 | - Slider value 10, equals volume 5. Slider value 20, equals volume 11. Slider value 30, equals volume 18 etc. 104 | - **Title/artist** are displayed 105 | - **Album name** is displayed as suffix to title if it's available and not identical with title 106 | - Supports **swap of title/artist** by suffixing source in Sonos Fovourites with a ‘|‘ symbol 107 | - Primary- and active slave speakers friendly names are also displayed (active group) 108 | - Active speakers are displayed instead of artist, when **tabview is active** 109 | - On 480 x 800 resolution devices both master- and slave speakers are displayed at all times 110 | 111 | #### Configuration consists of four elements: 112 | - `groups.yaml` containing needed Home Assistant groups 113 | - `configuration.yaml` containing all support 'helper' sensors. Both 'standard'-, mqtt- and binary sensors. File also contains the optional page 0 objects 114 | - `automations.yaml` containing all needed logic 115 | - Device specific `jsonl` file containing all the objects 116 | 117 | ### Following needs to be done for config to work !! 118 | 119 | **Changes in Sonos app:** 120 | - **Mandatory:** Revise your Sonos Favourites so all playlists, songs, podcast etc. are **prefixed** with an asteriks **‘*’** character. Configuration will **need** this as key to sort favourites into sources / non sources groups 121 | - **Optional:** Revise Sonos sources that need media title/artist to be swapped, by adding a pipe symbol **‘|’** as **suffix** to source name in Sonos Favourites 122 | 123 | **Examples:** 124 | 125 | FV:2/44: '*New Music Friday Denmark' 126 | FV:2/47: '*The Ultimate Hit Mix' 127 | FV:2/49: Classic FM 128 | FV:2/54: Classic Rock| 129 | FV:2/63: DR Nyheder 130 | 131 | **Changes in configuration files:** 132 | - Copy all page 2 objects from either `sunton_jsonl`, `gs_t3e.jsonl` or `wt32_01_plus.jsonl` to your existing openHASP jsonl file. I've included my page 0 objects as well in the files, so you can copy the entire page layout if you want. 133 | - **480 x 800 pixels (landscape mode):** Use `sunton.jsonl` file 134 | - **480 x 480 pixels:** Use `gs_t3e.jsonl` file 135 | - **320 x 480 pixels (portrait mode):** Use `wt32_01_plus.jsonl` file 136 | - Populate `group.sonos_all` in `groups.yaml` file with **your** initial master speaker `entity_id` only. Remaining speaker entities will be populated dynamically 137 | - Populate `group.hasp_sonos_devices`in `groups.yaml` file with **your** primary openHASP plate `entity_id`. Group will be dynamically expanded and populated with all available openHASP entities - sorted in alphabetical order 138 | - `group.sonos_all_speakers` should be left untouched and unpopulated. It will be populated upon HA start and dynamically revised upon speaker availability change 139 | 140 | - Copy all sensors from `configuration.yaml` to your own configuration file. Note that there are three different sensor types, so take care to paste these in under the correct **sensor type slug** (sensor, mqtt and binary_sensor) 141 | - In top of `configuration.yaml` file, I've included my four page 0 Custom Component object entries, if you want to include these as well (not mandatory). Search for `t3e_02` (my plate name) and replace with **your** plate name. There are five hardcoded openHASP entities in the Custom Component part of the`configuration.yaml` file 142 | - The Custom Component configuration **slug entry** 143 | - Additional **four entries** in the **page 0 object templates** 144 | 145 | **Changes in Home Assistant:** 146 | 147 | - The Sonos integration has the favorites sensor disabled by default. You need to **enable** `sensor.sonos_favorites` for the configuration to work. Check [HA Sonos integration](https://www.home-assistant.io/integrations/sonos/) to find details on this 148 | - Local HA IP address sensor is needed for this configuration 149 | - Please add [this](https://www.home-assistant.io/integrations/local_ip/) simple integration in HA to expose needed `sensor.local_ip` 150 | - If you prefer to use hardcoded static IP address instead, then search for `{{states('sensor.local_ip')}}` in `automations.yaml` and replace with **your** HA local IP address 151 | 152 | **General:** 153 | 154 | - Merge the four needed configuration parts into your existing files. 155 | - Note that I’ve used **plate page 2** for this configuration ! 156 | - Upload the `openhasp.png` file to plate. Reboot HA and plate(s) and you’re ready ! 🙂 157 | 158 | #### openHASP design: 159 | 160 | - **UI Theme:** Hasp Dark 161 | - **Primary color:** R:222, G:113, B:16 162 | - **Secondary color:** R:131, G:80, B:0 163 | 164 | 165 | #### ’To do’ list: 166 | 167 | - 250 millisecond mandatory delay auto opening the dropdown menus is currently needed. Waiting for an openHASP fix by @fvanroie 168 | - General config clean-up (templates) 169 | - Add Spotify / TuneIn logo’s as small png overlays 170 | - Populate and update plate completely on plate reconnect and/or HA restart 171 | 172 | Suggestions, improvements, error reporting etc. are very welcome ! 🙂 173 | 174 | February, 2023 @htvekov 175 | 176 | ### Below, various images showing the tab views and the pop up info overlay object: 177 | 178 | ![T3E Source dropdown list](https://github.com/htvekov/openHASP-Sonos-media-player/blob/main/images/image1.png) 179 | ##### T3E source dropdown list 180 | 181 | ![T3E Playlist dropdown list](https://github.com/htvekov/openHASP-Sonos-media-player/blob/main/images/image2.png) 182 | ##### T3E playlist dropdown list 183 | 184 | ![T3E Slave speaker selection buttons](https://github.com/htvekov/openHASP-Sonos-media-player/blob/main/images/image3.png) 185 | ##### T3E slave speaker selection buttons 186 | 187 | ![T3E Slave speaker selection buttons](https://github.com/htvekov/openHASP-Sonos-media-player/blob/main/images/image4.png) 188 | ##### T3E pop-up info overlay object 189 | 190 | ![](https://github.com/htvekov/openHASP-Sonos-media-player/blob/main/images/image6_320x480.png) 191 | ##### WT32_SC01 Plus 320 x 480 res. layout 192 | 193 | ![](https://github.com/htvekov/openHASP-Sonos-media-player/blob/main/images/image7_320x480.png) 194 | ##### WT32_SC01 Plus source dropdown list 195 | 196 | ![](https://github.com/htvekov/openHASP-Sonos-media-player/blob/main/images/image8_320x480.png) 197 | ##### WT32_SC01 Plus slave speaker selection buttons 198 | 199 | ![](https://github.com/htvekov/openHASP-Sonos-media-player/blob/main/images/sunton_openhasp_sonos_01.png) 200 | ##### Sunton 480 x 800 res. layout 201 | 202 | ![](https://github.com/htvekov/openHASP-Sonos-media-player/blob/main/images/sunton_openhasp_sonos_02.png) 203 | ##### Sunton pop-up info overlay object 204 | 205 | ![](https://github.com/htvekov/openHASP-Sonos-media-player/blob/main/images/sunton_openhasp_sonos_03.png) 206 | ##### Sunton source dropdown list 207 | 208 | ![](https://github.com/htvekov/openHASP-Sonos-media-player/blob/main/images/sunton_openhasp_sonos_04.png) 209 | ##### Sunton Master speaker button and Master speaker button matrix 210 | 211 | ![](https://github.com/htvekov/openHASP-Sonos-media-player/blob/main/images/sunton_openhasp_sonos_05.png) 212 | ##### Sunton pop-up info overlay object 213 | 214 | 215 | 216 | 217 | 218 | -------------------------------------------------------------------------------- /automations.yaml: -------------------------------------------------------------------------------- 1 | 2 | # HA STARTUP - PUSH VARIOUS STATES TO PLATES 3 | - id: openhasp_sonos_ha_startup_update_objects 4 | alias: openHASP Sonos HA startup update objects 5 | mode: restart 6 | trigger: 7 | - platform: state 8 | entity_id: 9 | - group.hasp_sonos_devices 10 | attribute: entity_id 11 | for: 12 | seconds: 10 13 | - platform: homeassistant 14 | event: start 15 | variables: 16 | volume: > 17 | {{ ((states("sensor.sonos_volume") | int * 2) / (1 + (states("sensor.sonos_volume") | int / 100))) | int }} 18 | action: 19 | - service: mqtt.publish 20 | data: 21 | topic: hasp/plates/command/p2b20.val 22 | payload_template: "{{ volume }}" 23 | - service: mqtt.publish 24 | data: 25 | topic: hasp/plates/command/p2b20.value_str 26 | payload_template: "{{ volume }}" 27 | - service: mqtt.publish 28 | data: 29 | topic: hasp/plates/command/jsonl 30 | payload_template: > 31 | {"page":2,"id":89,"text":"{{ states('sensor.sonos_master_friendly') }}"} 32 | - service: mqtt.publish 33 | data: 34 | topic: hasp/plates/command/jsonl 35 | payload_template: > 36 | {"page":2,"id":93,"options":{% set ns=namespace(counter="") 37 | %}{% for sources in state_attr("group.sonos_all_speakers", "entity_id")[1:] 38 | %}{% set friendly = "#008000 " + state_attr((sources), "friendly_name") 39 | + "#" if sources in state_attr("group.sonos_all", "entity_id") else 40 | "#FF0000 " + state_attr((sources), "friendly_name") + "#" %}{% set ns.counter 41 | = ns.counter + '\"'+ friendly +'"' %}{% if not loop.last %}{% set ns.counter 42 | = ns.counter + "," + '"\\n"' + "," %}{% endif %}{% endfor %}[{{ns.counter}}]} 43 | - service: mqtt.publish 44 | data: 45 | topic: hasp/plates/command/jsonl 46 | payload_template: > 47 | {% if states("sensor.hasp_sonos_master_state") == "paused" %} 48 | {"page":2,"id":17,"val":0,"text":"\ue40a"} 49 | {% elif states("sensor.hasp_sonos_master_state") == "playing" %} 50 | {"page":2,"id":17,"val":1,"text":"#FFAC00 \ue3e4#"} 51 | {% else %} 52 | {"page":2,"id":17,"val":0,"text":"\ue4db"} 53 | {% endif %} 54 | - service: mqtt.publish 55 | data: 56 | topic: hasp/plates/command/p2b98.options 57 | payload: "{{ states('sensor.hasp_sonos_playlist_options') }}" 58 | - service: mqtt.publish 59 | data: 60 | topic: hasp/plates/command/p2b99.options 61 | payload: "{{ states('sensor.hasp_sonos_source_options') }}" 62 | 63 | 64 | # Update and populate group.hasp_sonos_devices dynamically with all available openHASP devices 65 | # Updates upon changes and HA restart 66 | - id: openhasp_sonos_change_available_openhasp_devices_group 67 | alias: openHASP Sonos change available openHASP devices group 68 | mode: restart 69 | trigger: 70 | - platform: state 71 | entity_id: sensor.hasp_all_available_devices 72 | - platform: homeassistant 73 | event: start 74 | action: 75 | - service: group.set 76 | data_template: 77 | object_id: hasp_sonos_devices 78 | entities: > 79 | {{ states("sensor.hasp_all_available_devices") }} 80 | 81 | 82 | # Update group.sonos_all upon HA start and change in Sonos speaker group 83 | - id: openhasp_sonos_change_sonos_active_group_speakers 84 | alias: openHASP Sonos change active group speakers 85 | mode: restart 86 | trigger: 87 | - platform: state 88 | entity_id: sensor.sonos_active_group_speakers 89 | - platform: homeassistant 90 | event: start 91 | action: 92 | - service: group.set 93 | data_template: 94 | object_id: sonos_all 95 | entities: "{{ state_attr(state_attr('group.sonos_all', 'entity_id')[0], 'group_members') | join(',') }}" 96 | 97 | 98 | # Populate group.sonos_all_speakers upon HA start and change in available speakers 99 | # entity_id[0] copied from group.sonos_all as master speaker. Remaining entities from dynamic speaker sensor 100 | - id: openhasp_sonos_change_available_slave_speakers_group 101 | alias: openHASP Sonos change available slave speakers group 102 | trigger: 103 | - platform: state 104 | entity_id: sensor.sonos_available_slave_speakers 105 | - platform: homeassistant 106 | event: start 107 | action: 108 | - service: group.set 109 | data_template: 110 | object_id: sonos_all_speakers 111 | entities: "{{ state_attr('group.sonos_all', 'entity_id')[0] }},{{ states('sensor.sonos_available_slave_speakers') }}" 112 | 113 | 114 | # Using group commands to push updates to ALL plates ! 115 | # Remember plates have to share same object mapping (page/object number) 116 | # Push changes on media_title- or sonos_active_speakers sensors to plate objects 117 | - id: openhasp_sonos_push_artist_title 118 | alias: openHASP Sonos push artist title 119 | mode: queued 120 | trigger: 121 | platform: state 122 | entity_id: 123 | - sensor.media_title 124 | - sensor.sonos_active_speakers 125 | action: 126 | - service: mqtt.publish 127 | data_template: 128 | payload: '{{ states(trigger.entity_id) }}' 129 | topic: > 130 | {% if (trigger.entity_id) == 'sensor.media_title' %} 131 | hasp/plates/command/p2b14.text 132 | {% else %} 133 | hasp/plates/command/p2b26.text 134 | {% endif %} 135 | 136 | 137 | # Push media_artist/media_album_name to plate 138 | # If media_title if not equal to media_album_name and media_album_name exists then add album name to artist string 139 | - id: openhasp_push_media_artist_media_album_name_to_plate 140 | alias: openHASP push media artist/media album name to plate 141 | mode: restart 142 | trigger: 143 | platform: state 144 | entity_id: 145 | - sensor.media_artist 146 | - sensor.media_album_name 147 | action: 148 | - service: mqtt.publish 149 | data_template: 150 | topic: hasp/plates/command/p2b15.text 151 | payload: > 152 | {% if is_state('sensor.media_album_name', '--- No album name ---') or states('sensor.media_title') == states('sensor.media_album_name') %} 153 | {{ states('sensor.media_artist') }} 154 | {% else %} 155 | {{ states('sensor.media_artist') }} {{"•"|e}} {{ states('sensor.media_album_name') }} 156 | {% endif %} 157 | 158 | 159 | # Upon change, push media source + state to plate(s) in separate pop-up window for 5 seconds 160 | # Mandatory: support sensor sensor.hasp_sonos_master_state 161 | # and group.hasp_sonos_devices 162 | # Revised 20230216. Completely dynamic now and support simultanious, mixed plates with varying display width 163 | - id: openhasp_sonos_push_source_media 164 | alias: openHASP Sonos push source media 165 | mode: restart 166 | trigger: 167 | platform: state 168 | entity_id: sensor.hasp_sonos_master_state 169 | to: 170 | - playing 171 | - paused 172 | condition: 173 | condition: and 174 | conditions: 175 | - condition: template 176 | value_template: "{{ states('sensor.media_source') != 'unavailable' }}" 177 | variables: 178 | source_type: > 179 | {% if states('sensor.media_source') != 'no source' %} 180 | {{ 'TuneIn' if states("binary_sensor.sonos_tunein_is_source") == 'on' else 'Source' }} 181 | {% elif state_attr(state_attr('group.sonos_all', 'entity_id')[0], 'media_playlist') != None %} 182 | {{ 'Spotify playlist' if state_attr(state_attr('group.sonos_all', 'entity_id')[0], 'media_content_id') is search('spotify', ignorecase=True) else 'Unknown playlist' }} 183 | {% else %} 184 | {{ 'Spotify song' if state_attr(state_attr('group.sonos_all', 'entity_id')[0], 'media_content_id') is search('spotify', ignorecase=True) else 'Unknown song' }} 185 | {% endif %} 186 | source_name_status: > 187 | {% if source_type == 'TuneIn' or source_type == "Source" %} 188 | \n{{ state_attr(state_attr('group.sonos_all', 'entity_id')[0], 'media_channel') }}\n#FFAC00 {{ states('sensor.hasp_sonos_master_state') }} 189 | {% elif source_type is not search('song', ignorecase=True) %} 190 | \n{{ state_attr(state_attr('group.sonos_all', 'entity_id')[0], 'media_playlist')}}\n#FFAC00 {{ states('sensor.hasp_sonos_master_state') }} 191 | {% else %} 192 | \n\n#FFAC00 {{ states('sensor.hasp_sonos_master_state') }} 193 | {% endif %} 194 | action: 195 | - repeat: 196 | for_each: > 197 | {{ state_attr("group.hasp_sonos_devices", "entity_id") | list }} 198 | sequence: 199 | - variables: 200 | w: "{{ state_attr((repeat.item), 'tftWidth') }}" 201 | - service: mqtt.publish 202 | data: 203 | topic: "hasp/{{ state_attr((repeat.item), 'node') }}/command/jsonl" 204 | payload: > 205 | {% if w == 800 %} 206 | {"page":2,"id":90,"hidden":0,"x":40,"y":153,"w":275,"text_font":32,"text":"#FFAC00 {{ source_type }}{{ source_name_status }}"} 207 | {% elif w == 480 %} 208 | {"page":2,"id":90,"hidden":0,"x":45,"y":160,"w":220,"text_font":32,"text":"#FFAC00 {{ source_type }}{{ source_name_status }}"} 209 | {% else %} 210 | {"page":2,"id":90,"hidden":0,"x":10,"y":157,"w":188,"text_font":28,"text":"#FFAC00 {{ source_type }}{{ source_name_status }}"} 211 | {% endif %} 212 | - delay: 213 | seconds: 5 214 | - service: mqtt.publish 215 | data: 216 | topic: hasp/plates/command 217 | payload: >- 218 | {"page":2,"id":90,"hidden":1} 219 | 220 | 221 | - id: openhasp_push_sonos_volume_muted 222 | alias: openHASP push Sonos volume muted 223 | mode: single 224 | trigger: 225 | - platform: state 226 | entity_id: binary_sensor.sonos_volume_muted 227 | - platform: homeassistant 228 | event: start 229 | - platform: state 230 | entity_id: 231 | - group.hasp_sonos_devices 232 | attribute: entity_id 233 | for: 234 | seconds: 10 235 | action: 236 | - service: mqtt.publish 237 | data_template: 238 | topic: hasp/plates/command/jsonl 239 | payload: > 240 | {% if is_state('binary_sensor.sonos_volume_muted', 'on') %} 241 | {"page":2,"id":24,"val":1,"text":"\ue75f"} 242 | {% else %} 243 | {"page":2,"id":24,"val":0,"text":"\ue57e"} 244 | {% endif %} 245 | 246 | 247 | ## 250223 New automation 248 | ## Moved from CC event part p2b24 249 | - id: openhasp_sonos_change_volume_muted 250 | alias: openHASP Sonos change volume muted 251 | mode: queued 252 | trigger: 253 | platform: mqtt 254 | topic: hasp/+/state/p2b24 255 | condition: 256 | - condition: template 257 | value_template: "{{ trigger.payload_json.event == 'up' }}" 258 | action: 259 | - service: media_player.volume_mute 260 | data: 261 | entity_id: group.sonos_all 262 | is_volume_muted: "{{ false if state_attr((state_attr('group.sonos_all', 'entity_id')[0]),'is_volume_muted') else true }}" 263 | 264 | 265 | ## 250223 New automation 266 | ## Moved from CC event part p2b25 267 | - id: openhasp_sonos_change_shuffle 268 | alias: openHASP Sonos change shuffle 269 | mode: queued 270 | trigger: 271 | platform: mqtt 272 | topic: hasp/+/state/p2b25 273 | condition: 274 | - condition: template 275 | value_template: "{{ trigger.payload_json.event == 'up' }}" 276 | action: 277 | - service: media_player.shuffle_set 278 | data: 279 | entity_id: group.sonos_all 280 | shuffle: "{{ false if state_attr((state_attr('group.sonos_all', 'entity_id')[0]),'shuffle') else true }}" 281 | 282 | 283 | - id: openhasp_push_sonos_shuffle 284 | alias: openHASP push Sonos shuffle 285 | mode: single 286 | trigger: 287 | - platform: state 288 | entity_id: binary_sensor.sonos_shuffle 289 | - platform: homeassistant 290 | event: start 291 | - platform: state 292 | entity_id: 293 | - group.hasp_sonos_devices 294 | attribute: entity_id 295 | for: 296 | seconds: 10 297 | action: 298 | - service: mqtt.publish 299 | data_template: 300 | topic: hasp/plates/command/jsonl 301 | payload: > 302 | {% if is_state('binary_sensor.sonos_shuffle', 'on') %} 303 | {"page":2,"id":25,"val":1,"text":"\ue49d"} 304 | {% else %} 305 | {"page":2,"id":25,"val":0,"text":"\ue49e"} 306 | {% endif %} 307 | 308 | 309 | ## 250223 New automation 310 | ## Moved from CC event part p2b27 311 | - id: openhasp_sonos_change_repeat 312 | alias: openHASP Sonos change repeat 313 | mode: queued 314 | trigger: 315 | platform: mqtt 316 | topic: hasp/+/state/p2b27 317 | condition: 318 | - condition: template 319 | value_template: "{{ trigger.payload_json.event == 'up' or trigger.payload_json.event == 'release' }}" 320 | action: 321 | - service: media_player.repeat_set 322 | data: 323 | entity_id: group.sonos_all 324 | repeat: > 325 | {% if is_state_attr(state_attr('group.sonos_all', 'entity_id')[0], 'repeat', 'one') %} 326 | all 327 | {% elif is_state_attr(state_attr('group.sonos_all', 'entity_id')[0], 'repeat', 'all') %} 328 | off 329 | {% elif is_state_attr(state_attr('group.sonos_all', 'entity_id')[0], 'repeat', 'off') %} 330 | one 331 | {% endif %} 332 | 333 | 334 | - id: openhasp_push_sonos_repeat 335 | alias: openHASP push Sonos repeat 336 | mode: single 337 | trigger: 338 | - platform: state 339 | entity_id: sensor.sonos_repeat 340 | - platform: homeassistant 341 | event: start 342 | - platform: state 343 | entity_id: 344 | - group.hasp_sonos_devices 345 | attribute: entity_id 346 | for: 347 | seconds: 10 348 | action: 349 | - service: mqtt.publish 350 | data_template: 351 | topic: hasp/plates/command/jsonl 352 | payload: > 353 | {% if is_state('sensor.sonos_repeat', 'one') %} 354 | {"page":2,"id":27,"text_color":"#FFAC00","text":"\ue458"} 355 | {% elif is_state('sensor.sonos_repeat', 'all') %} 356 | {"page":2,"id":27,"text_color":"#FFAC00","text":"\ue456"} 357 | {% else %} 358 | {"page":2,"id":27,"text_color":"#C7BAA7","text":"\ue457"} 359 | {% endif %} 360 | 361 | 362 | # Hide/show progress bar & enable/disable shuffle & repeat buttons when shifting from/to playlist as source 363 | # Wait one second before proceeding (HA's media entities can 'flicker' a bit upon change) 364 | # Only trigger on state changes to/from 0 on entity's media_duration. This is always 0 when non playlists are selected. 365 | - id: openhasp_sonos_hide_progress_bar 366 | alias: openHASP Sonos hide progress bar 367 | mode: restart 368 | trigger: 369 | - platform: state 370 | entity_id: sensor.hasp_sonos_media_duration 371 | to: '0' 372 | for: 373 | seconds: 1 374 | - platform: state 375 | entity_id: sensor.hasp_sonos_media_duration 376 | from: '0' 377 | action: 378 | - service: mqtt.publish 379 | data_template: 380 | topic: hasp/plates/command/jsonl 381 | payload_template: > 382 | {% if trigger.to_state.state == '0' %} 383 | {'page':2,'id':41,'hidden':1}{'page':2,'id':25,'enabled':0}{'page':2,'id':27,'enabled':0} 384 | {% else %} 385 | {'page':2,'id':41,'hidden':0}{'page':2,'id':25,'enabled':1}{'page':2,'id':27,'enabled':1} 386 | {% endif %} 387 | 388 | # 260223 New automation 389 | # Moved from CC properties part p2b41 390 | - id: openhasp_sonos_push_max_duration_to_plates 391 | alias: openHASP Sonos push max duration to plates 392 | mode: restart 393 | trigger: 394 | - platform: state 395 | entity_id: sensor.hasp_sonos_media_duration 396 | action: 397 | - service: mqtt.publish 398 | data: 399 | topic: hasp/plates/command/p2b41.max 400 | payload_template: "{{ states('sensor.hasp_sonos_media_duration') | int }}" 401 | 402 | 403 | # 260223 New automation 404 | # Moved from CC properties part p2b41 405 | - id: openhasp_sonos_push_media_position_to_plates 406 | alias: openHASP Sonos push media position to plates 407 | mode: restart 408 | trigger: 409 | - platform: state 410 | entity_id: sensor.hasp_sonos_media_position 411 | action: 412 | - service: mqtt.publish 413 | data: 414 | topic: hasp/plates/command/p2b41.val 415 | payload_template: "{{ states('sensor.hasp_sonos_media_position') | int }}" 416 | 417 | 418 | # Listen for changes from ALL plates (+ in topic) ! 419 | # Remember plates have to share same object mapping (page/object number) 420 | # Push volume value string setting immediately to all plates 421 | # Push volume setting from hasp to media_player_group 422 | # Plates are using a Bezier algorithm in order to adjust volume at low levels much easier that if using a linear slider 423 | # https://community.home-assistant.io/t/smooth-media-player-volume-fade-with-multiple-curve-functions/349950 424 | - id: openhasp_sonos_volume_receive_from_plates 425 | alias: openHASP Sonos volume - receive from plates 426 | mode: restart 427 | trigger: 428 | platform: mqtt 429 | topic: hasp/+/state/p2b20 430 | action: 431 | - service: media_player.volume_set 432 | data_template: 433 | entity_id: 434 | - group.sonos_all 435 | volume_level: '{{ trigger.payload_json.val | float / 100(1) / (1 + (1 - trigger.payload_json.val | float(1) / 100 )) }}' 436 | 437 | 438 | # Push Sonos volume to plates as 'converted' values. Both val and value_str are pushed 439 | # Plates are using a Bezier algorithm in order to adjust volume at low levels much easier that if using a linear slider 440 | # https://community.home-assistant.io/t/smooth-media-player-volume-fade-with-multiple-curve-functions/349950 441 | - id: openhasp_sonos_volume_push_to_plates 442 | alias: openHASP Sonos volume - push to plates 443 | mode: restart 444 | trigger: 445 | platform: state 446 | entity_id: sensor.sonos_volume 447 | condition: 448 | condition: template 449 | value_template: "{{ now().timestamp() - as_timestamp(states.sensor.hasp_volume.last_changed) > 1 }}" 450 | # Set offset for volume setting within the volume slider 451 | # Specific values needed, depending on slider width 452 | # 320 pixel width devices: -98 & 98 453 | # 480/800 pixel width devices: -170 & 170 454 | # sensor.sonos_volume = 60 equals value 75 on volume slider 455 | # Here we push sensor.sonos_volume (trigger.to_state_state) 456 | # Revised 20230213: Dynamic, individual value_ofs_x pushed to each plate 457 | action: 458 | - repeat: 459 | for_each: > 460 | {{ state_attr("group.hasp_sonos_devices", "entity_id") | list }} 461 | sequence: 462 | - variables: 463 | w: "{{ state_attr((repeat.item), 'tftWidth') }}" 464 | - service: mqtt.publish 465 | data: 466 | topic: "hasp/{{ state_attr((repeat.item), 'node') }}/command/p2b20.value_ofs_x" 467 | payload: > 468 | {% if trigger.to_state.state | int > 60 and (w == 480 or w == 800) %} 469 | -170 470 | {% elif trigger.to_state.state | int > 60 and w == 320 %} 471 | -98 472 | {% elif trigger.to_state.state | int <= 60 and (w == 480 or w == 800) %} 473 | 170 474 | {% else %} 475 | 98 476 | {% endif %} 477 | - variables: 478 | vol: "{{ ((trigger.to_state.state | int * 2) / (1 + (trigger.to_state.state | int / 100))) | int }}" 479 | - service: mqtt.publish 480 | data: 481 | topic: hasp/plates/command/jsonl 482 | payload: > 483 | {"page":2,"id":20,"val":{{ vol }}}{"page":2,"id":20,"value_str":{{ vol }}} 484 | 485 | 486 | # Push volume slider value to all plates upon slider release 487 | # Value pushed and shared is from the active plates operating slider - NOT HA's volume sensors state value 488 | # value_str is not updated on the other plates. Only upon local slider changes. So we have to update value_str as well 489 | # Revised 20230213: Dynamic, individual value_ofs_x pushed to each plate 490 | - id: openhasp_sonos_push_volume_slider_value_to_all_plates 491 | alias: openHASP Sonos push volume slider value to all plates 492 | mode: restart 493 | trigger: 494 | - platform: mqtt 495 | topic: hasp/+/state/p2b20 496 | condition: 497 | - condition: template 498 | value_template: "{{ trigger.payload_json.event == 'up' }}" 499 | # Set offset for volume setting within the volume slider 500 | # Specific values needed, depending on slider width 501 | # 320 pixel width devices: -98 & 98 502 | # 480/800 pixel width devices: -170 & 170 503 | # sensor.sonos_volume = 60 equals value 75 on volume slider 504 | # Here we directly push slider value from plate (trigger.payload_json_val) 505 | action: 506 | - repeat: 507 | for_each: > 508 | {{ state_attr("group.hasp_sonos_devices", "entity_id") | list }} 509 | sequence: 510 | - variables: 511 | w: "{{ state_attr((repeat.item), 'tftWidth') }}" 512 | - service: mqtt.publish 513 | data: 514 | topic: "hasp/{{ state_attr((repeat.item), 'node') }}/command/p2b20.value_ofs_x" 515 | payload: > 516 | {% if trigger.payload_json.val | int > 75 and (w == 480 or w == 800) %} 517 | -170 518 | {% elif trigger.payload_json.val | int > 75 and w == 320 %} 519 | -98 520 | {% elif trigger.payload_json.val | int <= 75 and (w == 480 or w == 800) %} 521 | 170 522 | {% else %} 523 | 98 524 | {% endif %} 525 | - service: mqtt.publish 526 | data: 527 | topic: hasp/plates/command/jsonl 528 | payload: > 529 | {"page":2,"id":20,"val":{{ trigger.payload_json.val }}}{"page":2,"id":20,"value_str":{{ trigger.payload_json.val }}} 530 | 531 | 532 | # Listen for changes from ALL plates (+ in topic) ! 533 | # Remember plates have to share same object mapping (page/object number) 534 | # More simple 'media_player.media_play_pause' service can't be used, as HA Sonos integration has 'issues/bugs' with this service, using specific slave speaker configurations 535 | # Should be retested when Sonos integration has been updated 536 | - id: openhasp_sonos_play_toggle 537 | alias: openHASP Sonos play toggle 538 | mode: single 539 | trigger: 540 | - platform: mqtt 541 | topic: hasp/+/state/p2b17 542 | condition: 543 | - condition: template 544 | value_template: "{{ trigger.payload_json.event == 'down' }}" 545 | action: 546 | service_template: >- 547 | {% if trigger.payload_json.val == 1 %} 548 | media_player.media_pause 549 | {% elif trigger.payload_json.val == 0 %} 550 | media_player.media_play 551 | {% else %} 552 | media_player.media_stop 553 | {% endif %} 554 | data: 555 | entity_id: 556 | - group.sonos_all 557 | 558 | 559 | # Push available slave speakers to plate with updated status (red or green) 560 | # Press slave speaker button to toggle speakers join/unjoin status 561 | # On 320 & 480 width devices, choose Sources or Playlists from tab and press 'arrow down' to end and close tab 562 | # 050223: Revised trigger from plate objects to slave speakers group/all available speakers entity_id change 563 | # 050223: Added update to primary speaker object (p2b89) 564 | - id: openhasp_sonos_slave_speakers_to_plate 565 | alias: openHASP Sonos slave speakers to plate 566 | mode: restart 567 | trigger: 568 | - platform: state 569 | entity_id: 570 | - group.sonos_all 571 | - group.sonos_all_speakers 572 | attribute: entity_id 573 | action: 574 | - service: mqtt.publish 575 | data: 576 | topic: hasp/plates/command/jsonl 577 | payload_template: > 578 | {"page":2,"id":89,"text":"{{ state_attr(state_attr('group.sonos_all_speakers', 'entity_id')[0], 'friendly_name') }}"} 579 | - service: mqtt.publish 580 | data: 581 | topic: hasp/plates/command/jsonl 582 | payload_template: > 583 | {"page":2,"id":93,"options":{% set ns=namespace(counter="") %}{% for sources in state_attr('group.sonos_all_speakers', 'entity_id')[1:] %}{% set friendly = "#008000 " + state_attr((sources), 'friendly_name') + '#' if sources in state_attr('group.sonos_all', 'entity_id') else "#FF0000 " + state_attr((sources), 'friendly_name') + '#' %}{% set ns.counter = ns.counter + '\"'+ friendly +'"' %}{% if not loop.last %}{% set ns.counter = ns.counter + ',' + '"\\n"' + ',' %}{% endif %}{% endfor %}[{{ns.counter}}]} 584 | 585 | 586 | - id: openhasp_sonos_slave_speakers_unjoin 587 | alias: openHASP Sonos slave speakers unjoin 588 | mode: queued 589 | trigger: 590 | platform: mqtt 591 | topic: hasp/+/state/p2b93 592 | variables: 593 | val: "{{ trigger.payload_json.val | int(0) }}" 594 | entity: "{{ state_attr('group.sonos_all_speakers', 'entity_id')[1:] }}" 595 | condition: 596 | condition: and 597 | conditions: 598 | - condition: template 599 | value_template: "{{ true if entity[val] in state_attr('group.sonos_all', 'entity_id') else false }}" 600 | - condition: template 601 | value_template: "{{ trigger.payload_json.event == 'down' }}" 602 | action: 603 | - service: media_player.unjoin 604 | data: 605 | entity_id: "{{ entity[val] }}" 606 | 607 | 608 | # Now uses sensor.sonos_volume which is the correct master speaker / HA volume 609 | # Join speakers one by one from toggle button on plate 610 | # Set mute status and volume to match master speaker BEFORE joining speaker ! 611 | - id: openhasp_sonos_slave_speakers_join 612 | alias: openHASP Sonos slave speakers join 613 | mode: queued 614 | trigger: 615 | platform: mqtt 616 | topic: hasp/+/state/p2b93 617 | variables: 618 | val: "{{ trigger.payload_json.val | int(0) }}" 619 | entity: "{{ state_attr('group.sonos_all_speakers', 'entity_id')[1:] }}" 620 | condition: 621 | condition: and 622 | conditions: 623 | - condition: template 624 | value_template: "{{ true if entity[val] not in state_attr('group.sonos_all', 'entity_id') else false }}" 625 | - condition: template 626 | value_template: "{{ trigger.payload_json.event == 'down' }}" 627 | action: 628 | - service: media_player.volume_mute 629 | data: 630 | entity_id: "{{ entity[val] }}" 631 | is_volume_muted: "{{ true if state_attr((state_attr('group.sonos_all', 'entity_id')[0]), 'is_volume_muted') else false }}" 632 | - service: media_player.volume_set 633 | data_template: 634 | entity_id: "{{ entity[val] }}" 635 | volume_level: "{{ states.sensor.sonos_volume.state | float(1) / 100}}" 636 | - service: media_player.join 637 | data: 638 | entity_id: "{{ state_attr('group.sonos_all', 'entity_id')[0] }}" 639 | group_members: "{{ entity[val] }}" 640 | 641 | 642 | - id: openhasp_sonos_change_master_speaker_btnmtrx 643 | alias: openHASP Sonos change master speaker btnmtrx 644 | mode: single 645 | trigger: 646 | - platform: mqtt 647 | topic: hasp/+/state/p2b89 648 | - platform: mqtt 649 | topic: hasp/+/state/p2b95 650 | # Check for either Sunton 7" device master speaker button 'hold' action or 651 | # tab menu 'Master' value (object p2b95) on all other devices 652 | condition: 653 | condition: template 654 | value_template: "{{ trigger.payload_json.event == 'hold' or (trigger.payload_json.val == 3 and trigger.topic.split('/')[-1] == 'p2b95') }}" 655 | # Create option list for master speaker button matrix 656 | # Skip first entity_id in group.sonos_all_speakers as this entry is always current master speaker 657 | variables: 658 | sas: > 659 | {% set ns=namespace(counter="") %} 660 | {% for sources in state_attr("group.sonos_all_speakers", "entity_id")[1:] %} 661 | {% set friendly = "#0000FF " + state_attr((sources), "friendly_name") + "#" if sources in state_attr("group.sonos_all_speakers", "entity_id") else "#FF0000 " + state_attr((sources), "friendly_name") + "#" %} 662 | {% set ns.counter = ns.counter + '\"'+ friendly +'"' %} 663 | {% if not loop.last %} 664 | {% set ns.counter = ns.counter + "," + '"\\n"' + "," %} 665 | {% endif %} 666 | {% endfor %} 667 | {{ ns.counter }} 668 | action: 669 | - service: mqtt.publish 670 | data: 671 | topic: "hasp/{{trigger.topic.split('/')[1]}}/command/jsonl" 672 | payload: > 673 | {% if state_attr(("openhasp." + trigger.topic.split('/')[1]), "tftWidth") == 800 %} 674 | {"page":2,"id":93,"hidden":1}{"page":2,"id":88,"hidden":0,"options":[{{sas}},"\n","#FFFFFF E X I T#"]} 675 | {% else %} 676 | {"page":2,"id":88,"hidden":0,"options":[{{sas}}]} 677 | {% endif %} 678 | 679 | 680 | - id: openhasp_sonos_choose_new_master_speaker_group 681 | alias: openHASP Sonos choose new master speaker group 682 | mode: single 683 | trigger: 684 | - platform: mqtt 685 | topic: hasp/+/state/p2b88 686 | condition: 687 | condition: template 688 | value_template: "{{ trigger.payload_json.event == 'down' }}" 689 | # Store chosen speaker in variable 690 | # Skip entity_id[0] in search = current master speaker (not included in button matrix) 691 | variables: 692 | # Store chosen speaker in variable. Data will be lost before automation has finished 693 | # Skip entity_id[0] in search = current master speaker (not included in button matrix) 694 | # Skip entity_id if chosen speaker is 'E X I T' 695 | cs: > 696 | {% if trigger.payload_json.text != '#FFFFFF E X I T#' %} 697 | {{ state_attr('group.sonos_all_speakers','entity_id')[trigger.payload_json.val + 1] }} 698 | {% endif %} 699 | # Check if chosen speaker already is part of existing group and store it 700 | # Data will be lost before automation has finished 701 | in_group: > 702 | {% set ns = namespace(ig = "not_in_group") %} 703 | {% for entity_id in state_attr("group.sonos_all", "entity_id") %} 704 | {% if entity_id == cs %} 705 | {% set ns.ig = entity_id %} 706 | {% endif %} 707 | {% endfor %} 708 | {{ true if ns.ig != "not_in_group" else false }} 709 | action: 710 | # If device is a 7" Sunton, then hide master speaker button matrix and unhide slave speaker button matrix 711 | # Any other devices just 'emulate' an 'arrow up' press in a dropdown menu and exit tab menu completely 712 | - service: mqtt.publish 713 | data: 714 | topic: "hasp/{{trigger.topic.split('/')[1]}}/command/jsonl" 715 | payload: > 716 | {% if state_attr(("openhasp." + trigger.topic.split('/')[1]), "tftWidth") == 800 %} 717 | {"page":2,"id":88,"hidden":1}{"page":2,"id":93,"hidden":0} 718 | {% else %} 719 | {'page':2,'id':95,'to_back':1,'hidden':1}{'page':2,'id':23,'hidden':1}{'page':2,'id':26,'hidden':1}{'page':2,'id':22,'hidden':0}{'page':2,'id':15,'hidden':0} 720 | {% endif %} 721 | # Skip last service calls if chosen speaker is 'E X I T' 722 | - condition: 723 | - condition: template 724 | value_template: "{{ trigger.payload_json.text != '#FFFFFF E X I T#' }}" 725 | # Choose new chosen master speaker (group) 726 | - service: group.set 727 | data_template: 728 | object_id: sonos_all 729 | entities: > 730 | {{ state_attr("group.sonos_all_speakers","entity_id")[trigger.payload_json.val + 1]}} 731 | # If chosen speaker is not 'E X I T' and chosen speaker already is part of existing group 732 | # then unjoin speaker from current group 733 | - if: 734 | - condition: template 735 | value_template: > 736 | {{ in_group }} 737 | then: 738 | - service: media_player.unjoin 739 | data_template: 740 | entity_id: "{{ cs }}" 741 | else: 742 | 743 | 744 | # Using group commands to push updates to ALL plates ! 745 | # Remember plates have to share same object mapping (page/object number) 746 | # Color code for button toggle value 'on' not set as static in jsonl on purpose. 747 | # This in order NOT to change color immediately on button press, but instead wait for attribute change to 'playing' 748 | - id: openhasp_sonos_set_play/pause/idle_icon 749 | alias: openHASP Sonos set play/pause/idle icon 750 | mode: parallel 751 | trigger: 752 | - platform: state 753 | # Dynamic entity_id trigger - Sonos master speaker state 754 | entity_id: 755 | - sensor.hasp_sonos_master_state 756 | to: 757 | - playing 758 | - paused 759 | - idle 760 | action: 761 | service: mqtt.publish 762 | data: 763 | topic: hasp/plates/command/jsonl 764 | payload_template: >- 765 | {% if trigger.to_state.state == 'paused' %} 766 | {"page":2,"id":17,"val":0,"text":"\ue40a"} 767 | {% elif trigger.to_state.state == 'playing' %} 768 | {"page":2,"id":17,"val":1,"text":"#FFAC00 \ue3e4#"} 769 | {% else %} 770 | {"page":2,"id":17,"val":0,"text":"\ue4db"} 771 | {% endif %} 772 | 773 | 774 | # Every five seconds - Push media_duration to plates when playing 775 | # Don't push update for Sonos sources (radio stations) - check for media_position attribute presence 776 | # When paused, page will get exact update from HA's media player state changes and update plates 777 | ## 0303023 Revised media_duration to cope with Spotify Radio 778 | - id: openhasp_update_media_duration 779 | alias: openHASP update media duration 780 | mode: single 781 | trigger: 782 | platform: time_pattern 783 | seconds: '/5' 784 | condition: 785 | condition: template 786 | value_template: "{{ (true if state_attr(state_attr('group.sonos_all','entity_id')[0], 'media_position') != None else false) and states('sensor.hasp_sonos_master_state') == 'playing' }}" 787 | action: 788 | service: mqtt.publish 789 | data_template: 790 | topic: hasp/plates/command/p2b41.val 791 | payload: >- 792 | {{ (as_timestamp(now(),0) - as_timestamp(state_attr(state_attr('group.sonos_all', 'entity_id')[0], 'media_position_updated_at'),0)) | round(0) + (state_attr(state_attr('group.sonos_all', 'entity_id')[0], 'media_position')) | int(0) }} 793 | 794 | 795 | # Listen for changes from ALL plates (+ in topic) ! 796 | # Remember plates have to share same object mapping (page/object number) 797 | # Only change for sources (radio stations) - not for playlists 798 | # 190822: Revised to use new sensor.sonos_favorites as source_list attribute has been deprecated 799 | # 240223: Revised to one combined automation for both previous/next events 800 | # 030323: Revised condition_template to cope with e.g. Spotify Radio 'channels' and treat these as a 'playlist' instead of a 'source' 801 | # It's possible to skip to next track when playing. Skipping when paused is not possible. 802 | # Previous track can't be selected 803 | - id: openhasp_sonos_prev_next_source 804 | alias: openHASP Sonos previous/next source 805 | mode: single 806 | trigger: 807 | - platform: mqtt 808 | topic: hasp/+/state/p2b18 809 | payload: '{"event":"down"}' 810 | - platform: mqtt 811 | topic: hasp/+/state/p2b16 812 | payload: '{"event":"down"}' 813 | condition: 814 | - condition: template 815 | value_template: "{{ true if state_attr(state_attr('group.sonos_all','entity_id')[0], 'media_duration') else false }}" 816 | action: 817 | - service: media_player.select_source 818 | data_template: 819 | entity_id: "{{ state_attr('group.sonos_all', 'entity_id')[0] }}" 820 | source: > 821 | {% set source_start = states('sensor.sonos_source_index_start') | int(0) %} 822 | {% set entity_id = state_attr('group.sonos_all', 'entity_id')[0] %} 823 | {% set current = states('sensor.media_source') %} 824 | {% set source_list = state_attr('sensor.sonos_favorites', 'items').values() | list %} 825 | {% set index = source_list.index(current) %} 826 | {% if trigger.topic.split('/')[3] == 'p2b18' %} 827 | {% set next = source_start if current == source_list[-1] else index + 1 %} 828 | {{ source_list[next] }} 829 | {% else %} 830 | {% set prev = -1 if current == source_list[source_start] else index - 1 %} 831 | {{ source_list[prev] }} 832 | {% endif %} 833 | 834 | 835 | # Push source state changes to dropdown objects on all plates ("show_selected":1 attribute on dropdown object) 836 | # Update current attribute, if source is in favourites list 837 | # Check if source is playlist as condition 838 | # Template will fail (warning) if source is not in favourites list due to use of .index() 839 | - id: openhasp_sonos_push_source_change_to_dropdown_object 840 | alias: openhasp Sonos push source change to dropdown object 841 | mode: single 842 | trigger: 843 | platform: state 844 | entity_id: sensor.media_source 845 | condition: 846 | - condition: template 847 | value_template: '{{ states("sensor.media_source") != "no source" }}' 848 | action: 849 | - service: mqtt.publish 850 | data_template: 851 | topic: hasp/plates/command/p2b99.val 852 | payload: > 853 | {% set source_start = states('sensor.sonos_source_index_start') | int(0) %} 854 | {% set entity_id = state_attr('group.sonos_all', 'entity_id')[0] %} 855 | {% set current = states('sensor.media_source') %} 856 | {% set source_list = state_attr('sensor.sonos_favorites', 'items').values() | list %} 857 | {% set index = source_list.index(current) %} 858 | {{ index - source_start }} 859 | 860 | 861 | # Next/previous track - Only change for playlists - not for sources (radio stations) 862 | - id: openhasp_sonos_next_previous_track 863 | alias: openHASP Sonos next/previous track 864 | mode: single 865 | trigger: 866 | - platform: mqtt 867 | topic: hasp/+/state/p2b16 868 | payload: '{"event":"down"}' 869 | - platform: mqtt 870 | topic: hasp/+/state/p2b18 871 | payload: '{"event":"down"}' 872 | condition: 873 | - condition: template 874 | value_template: "{{ states('sensor.media_source') == 'no source' }}" 875 | action: 876 | service_template: >- 877 | {{ 'media_player.media_previous_track' if trigger.topic.split('/')[3] == 'p2b16' else 'media_player.media_next_track' }} 878 | data: 879 | entity_id: "{{ state_attr('group.sonos_all', 'entity_id')[0] }}" 880 | 881 | 882 | ## 240223 New automation 883 | ## Moved from CC event part p2b98/p2b99 884 | - id: openhasp_sonos_select_source_from_dropdown_list 885 | alias: openHASP Sonos select source from dropdown list 886 | mode: queued 887 | trigger: 888 | - platform: mqtt 889 | topic: hasp/+/state/p2b98 890 | - platform: mqtt 891 | topic: hasp/+/state/p2b99 892 | condition: 893 | - condition: template 894 | value_template: "{{ trigger.payload_json.event == 'up' or trigger.payload_json.event == 'changed' }}" 895 | action: 896 | # In sequence: Hide and push tabview object to background 897 | # Hide speaker glyph & master and slave speaker text 898 | # Show Artist glyph & Artist/Artist & album text 899 | - service: mqtt.publish 900 | data: 901 | topic: "hasp/{{trigger.topic.split('/')[1]}}/command/jsonl" 902 | payload: "{'page':2,'id':95,'to_back':1,'hidden':1}{'page':2,'id':23,'hidden':1}{'page':2,'id':26,'hidden':1}{'page':2,'id':22,'hidden':0}{'page':2,'id':15,'hidden':0}" 903 | # Only 'changed' event should alter source selection 904 | - condition: template 905 | value_template: "{{ trigger.payload_json.event == 'changed' }}" 906 | # Calculate source_list indexed to be chosen from returned [val] + number of playlist 'sources' 907 | # Can't just use returned [text] as some sources ends with '|' if their title/artist attributes are to be flipped 908 | # Or 909 | # Show all sources (playlists) starting with '*'. 'Real' sources are without prefixed asterix ('*') and shown in separate tab (p2b99) 910 | # Show on plate without prefixed '*' and add again to source upon selection 911 | - service: media_player.select_source 912 | data: 913 | entity_id: "{{ state_attr('group.sonos_all', 'entity_id')[0] }}" 914 | source: > 915 | {% if trigger.topic.split('/')[3] == 'p2b98' %} 916 | *{{ trigger.payload_json.text }} 917 | {% else %} 918 | {{ (state_attr('sensor.sonos_favorites', 'items').values() | list)[states('sensor.sonos_source_index_start') | int + trigger.payload_json.val] }} 919 | {% endif %} 920 | 921 | 922 | ## 240223 New automation 923 | ## Moved from CC properties part p2b98 924 | # Show sources (playlists) on plate without preceeding '*' and add again to source upon selection 925 | - id: openhasp_sonos_push_playlist_updates_to_plates 926 | alias: openhasp Sonos push playlist updates to plates 927 | mode: single 928 | trigger: 929 | platform: state 930 | entity_id: sensor.hasp_sonos_playlist_options 931 | action: 932 | - service: mqtt.publish 933 | data: 934 | topic: hasp/plates/command/p2b98.options 935 | payload: "{{ states('sensor.hasp_sonos_playlist_options') }}" 936 | 937 | 938 | ## 240223 New automation 939 | ## Moved from CC properties part p2b99 940 | # Filter out all sources starting with '*'. These are playlists to be shown in separate tab (p2b98) 941 | - id: openhasp_sonos_push_source_list_updates_to_plates 942 | alias: openhasp Sonos push source list updates to plates 943 | mode: single 944 | trigger: 945 | platform: state 946 | entity_id: sensor.hasp_sonos_source_options 947 | action: 948 | - service: mqtt.publish 949 | data: 950 | topic: hasp/plates/command/p2b99.options 951 | payload: "{{ states('sensor.hasp_sonos_source_options') }}" 952 | 953 | 954 | # Dynamic support for both 320, 480 and 800 pixel width devices 955 | # Target entity_id is now also dynamic and templated. 956 | # Zoom service removed as push_image now supports upscaling of images if fitscreen argument is set in data field 957 | - id: openhasp_push_image_to_plate 958 | alias: openHASP push image to plate 959 | mode: restart 960 | trigger: 961 | - platform: state 962 | entity_id: sensor.hasp_sonos_master_image 963 | # Delay needed in order to remove some of the double images pushed to the plates (source still often generates 2 images though) 964 | for: 965 | milliseconds: 250 966 | - platform: homeassistant 967 | event: start 968 | - platform: state 969 | entity_id: 970 | - group.hasp_sonos_devices 971 | attribute: entity_id 972 | for: 973 | seconds: 10 974 | condition: 975 | - condition: template 976 | value_template: "{{ state_attr(state_attr('group.sonos_all', 'entity_id')[0], 'entity_picture') != None }}" 977 | # Push images for each openHASP entity in group.hasp_sonos_devices 978 | # with dynamic width/height from each device sensors tftWidth attribute 979 | # using local HA IP address from HA integration - Internal IP 980 | # Automation now supports full dynamic mix of different image sizes 981 | # Width/height for Sunton 7" device is set to 355 982 | # Image width/height for all other devices will be identical with device width 983 | action: 984 | - repeat: 985 | for_each: > 986 | {{ state_attr("group.hasp_sonos_devices", "entity_id") | list }} 987 | sequence: 988 | - variables: 989 | w: "{{ state_attr((repeat.item), 'tftWidth') }}" 990 | - service: openhasp.push_image 991 | data: 992 | obj: p2b40 993 | image: "http://{{states('sensor.local_ip')}}:8123{{state_attr(state_attr('group.sonos_all', 'entity_id')[0], 'entity_picture')}}" 994 | fitscreen: 1 995 | width: > 996 | {% if w == 800 %} 997 | 355 998 | {% else %} 999 | {{ w }} 1000 | {% endif %} 1001 | height: > 1002 | {% if w == 800 %} 1003 | 355 1004 | {% else %} 1005 | {{ w }} 1006 | {% endif %} 1007 | target: 1008 | entity_id: > 1009 | {{ repeat.item }} 1010 | 1011 | 1012 | # Open tabs, push source dropdown list to a specific plate and show active speakers on all plates 1013 | # Issue hidden:0 command to the specific plate only that triggered event 1014 | # Starts on first tab every time opened for all plates. 1015 | # Trigger on both image and underlying object (if no image is loaded to plate) 1016 | - id: openhasp_sonos_source_list 1017 | alias: openHASP Sonos source list 1018 | mode: single 1019 | trigger: 1020 | - platform: mqtt 1021 | topic: hasp/+/state/p2b40 1022 | payload: '{"event":"release"}' 1023 | - platform: mqtt 1024 | topic: hasp/+/state/p2b39 1025 | payload: '{"event":"release"}' 1026 | action: 1027 | - service: mqtt.publish 1028 | data: 1029 | topic: "hasp/{{trigger.topic.split('/')[1]}}/command/jsonl" 1030 | payload: "{'page':2,'id':95,'to_front':1,'hidden':0,'val':0}" 1031 | - service: mqtt.publish 1032 | data: 1033 | topic: "hasp/{{trigger.topic.split('/')[1]}}/command/jsonl" 1034 | payload: "{'page':2,'id':22,'hidden':1}{'page':2,'id':15,'hidden':1}{'page':2,'id':23,'hidden':0}{'page':2,'id':26,'hidden':0}" 1035 | 1036 | # TEMP NEEDED DELAY AS OPENHASP HAS ISSUES UNHIDING PARENT TAB AND OPENING DROPDOWN MENU RIGHT AFTER ! 1037 | - delay: 1038 | milliseconds: 250 1039 | # TEMP NEEDED DELAY AS OPENHASP HAS ISSUES UNHIDING PARENT TAB AND OPENING DROPDOWN MENU RIGHT AFTER ! 1040 | 1041 | - service: mqtt.publish 1042 | data: 1043 | topic: >- 1044 | hasp/{{trigger.topic.split('/')[1]}}/command 1045 | payload: >- 1046 | {"page":2,"id":99,"open":1} 1047 | 1048 | 1049 | # Only applicable for the plate doing the change - NOT all plates 1050 | - id: openhasp_sonos_source_list_change 1051 | alias: openHASP Sonos source list change 1052 | mode: single 1053 | trigger: 1054 | - platform: mqtt 1055 | topic: hasp/+/state/p2b95 1056 | condition: 1057 | - condition: template 1058 | value_template: "{{ trigger.payload_json.event == 'changed' }}" 1059 | action: 1060 | - service: mqtt.publish 1061 | data: 1062 | topic: >- 1063 | hasp/{{trigger.topic.split('/')[1]}}/command 1064 | payload: >- 1065 | {% if trigger.payload_json.val == 2 %} 1066 | {"page":2,"id":98,"close":1}{"page":2,"id":99,"close":1} 1067 | {% elif trigger.payload_json.val == 0 %} 1068 | {"page":2,"id":98,"close":1} 1069 | {% else %} 1070 | {"page":2,"id":99,"close":1} 1071 | {% endif %} 1072 | 1073 | # TEMP NEEDED DELAY AS OPENHASP HAS ISSUES UNHIDING PARENT TAB AND OPENING DROPDOWN MENU RIGHT AFTER ! 1074 | - delay: 1075 | milliseconds: 250 1076 | # TEMP NEEDED DELAY AS OPENHASP HAS ISSUES UNHIDING PARENT TAB AND OPENING DROPDOWN MENU RIGHT AFTER ! 1077 | 1078 | - service: mqtt.publish 1079 | data: 1080 | topic: >- 1081 | hasp/{{trigger.topic.split('/')[1]}}/command 1082 | payload: >- 1083 | {% if trigger.payload_json.val == 2 %} 1084 | {"page":2,"id":99,"close":1} 1085 | {% elif trigger.payload_json.val == 0 %} 1086 | {"page":2,"id":99,"open":1} 1087 | {% else %} 1088 | {"page":2,"id":98,"open":1} 1089 | {% endif %} 1090 | --------------------------------------------------------------------------------