SqueezeControl custom widget

EDIT: New updated version online (01.02.2023)

Hi,

I want to share my first Custom Widget for MainUI…
It’s a fully configurable widget which loads data dynamically to the selected player.

It is intended to work with the existing Squeezebox Binding

Here’s how the widget looks on my Smartphone

How to Use

You have to pay attention to a few things before usage.

the according player item-names should follow this structure:

  • Prefix + Playername + Suffix

e.g.

...
SqueezeLivingroomTitle
...
SqueezeBedroomArtist
...
SqueezeBathroomGenre
etc.

you have to fill the widget configuration

and provide some information there:

  • your prefix
  • your suffixes
  • a comma separated list of your available players
  • an item for storing player selection e.g. SqueezePlayerSelect or similar (you have to create one and use the item type String)
  • the item which provides your server’s favorite-list

after that you will be able to control all your squeezebox players with one widget :wink:

Resources

and here is the yaml code

uid: SqueezeControl
tags:
  - card
  - marketplace:123932
props:
  parameters:
    - defaultValue: Squeeze
      description: Title for the widget
      label: Static Widget Title
      name: title
      required: false
      type: TEXT
    - defaultValue: Squeeze
      description: Prefix for the Items
      label: Item Prefix
      name: prefix
      required: true
      type: TEXT
    - context: item
      defaultValue: SqueezePlayer
      description: Item for Playerselection
      label: Player Selector Item
      name: selected
      required: true
      type: TEXT
    - context: item
      defaultValue: SqueezeServerListFavorite
      description: Item for Serverlist
      label: Serverlist Item
      name: favoriteslist
      required: true
      type: TEXT
    - defaultValue: Cover
      description: Suffix for CoverArt
      label: CoverArt Suffix
      name: coverart
      required: true
      type: TEXT
    - defaultValue: Artist
      description: Suffix for Artist
      label: Artist Suffix
      name: artist
      required: true
      type: TEXT
    - defaultValue: Album
      description: Suffix for Album
      label: Album Suffix
      name: album
      required: true
      type: TEXT
    - defaultValue: Title
      description: Suffix for Title
      label: Title Suffix
      name: title
      required: true
      type: TEXT
    - defaultValue: Duration
      description: Suffix for Duration
      label: Duration Suffix
      name: duration
      required: true
      type: TEXT
    - defaultValue: Control
      description: Suffix for Player Control
      label: Control Suffix
      name: control
      required: true
      type: TEXT
    - defaultValue: Volume
      description: Suffix for Volume
      label: Volume Suffix
      name: volume
      required: true
      type: TEXT
    - defaultValue: Shuffle
      description: Suffix for Shuffle
      label: Shuffle Suffix
      name: shuffle
      required: true
      type: TEXT
    - defaultValue: Repeat
      description: Suffix for Repeat
      label: Repeat Suffix
      name: repeat
      required: true
      type: TEXT
    - defaultValue: Sleep
      description: Suffix for Sleepmode
      label: Sleepmode Suffix
      name: sleepmode
      required: true
      type: TEXT
    - defaultValue: Power
      description: Suffix for Power
      label: Power Suffix
      name: power
      required: true
      type: TEXT
    - defaultValue: PlayFavorite
      description: Suffix for Favorites
      label: Favorites Suffix
      name: favorites
      required: true
      type: TEXT
    - description: List of known Players (comma separated)
      label: Available Players
      name: playerlist
      required: true
      type: TEXT
  parameterGroups: []
timestamp: Feb 1, 2023, 6:53:07 PM
component: f7-card
config: {}
slots:
  default:
    - component: f7-row
      slots:
        default:
          - component: f7-col
            slots:
              default:
                - component: oh-label-card
                  config:
                    action: options
                    actionFeedback: =items[props.selected].state
                    actionItem: =props.selected
                    actionOptions: =props.playerlist
                    height: auto
                    item: =props.selected
                    width: 100%
    - component: f7-row
      slots:
        default:
          - component: oh-image
            config:
              item: =props.prefix+items[props.selected].state+props.coverart
              style:
                height: auto
                width: 100%
    - component: f7-row
      config:
        class:
          - justify-content-left
      slots:
        default:
          - component: f7-col
            slots:
              default:
                - component: Label
                  config:
                    class:
                      - text-align-right
                      - margin-top
                      - margin-right
                    text: =items[props.prefix+items[props.selected].state+props.artist].state
    - component: f7-row
      config:
        class:
          - justify-content-left
      slots:
        default:
          - component: f7-col
            slots:
              default:
                - component: Label
                  config:
                    class:
                      - text-align-right
                      - margin-right
                    text: =items[props.prefix+items[props.selected].state+props.title].state
    - component: f7-row
      config:
        class:
          - justify-content-left
      slots:
        default:
          - component: f7-col
            slots:
              default:
                - component: Label
                  config:
                    class:
                      - text-align-right
                      - margin-right
                    text: =items[props.prefix+items[props.selected].state+props.album].state
    - component: f7-row
      config:
        class:
          - justify-content-left
      slots:
        default:
          - component: f7-col
            slots:
              default:
                - component: Label
                  config:
                    class:
                      - text-align-right
                      - margin-right
                    text: =Math.trunc(Number(items[props.prefix+items[props.selected].state+props.duration].state.replace(' s',''))/60) + ":" + (Number(items[props.prefix+items[props.selected].state+props.duration].state.replace(' s','')) % 60).toString().padStart(2,'0')
    - component: f7-row
      config:
        class:
          - padding-top
          - padding-bottom
      slots:
        default:
          - component: f7-col
            slots:
              default:
                - component: oh-player-controls
                  config:
                    item: =props.prefix+items[props.selected].state+props.control
    - component: f7-row
      config:
        class:
          - padding-top
          - padding-bottom
      slots:
        default:
          - component: f7-col
            slots:
              default:
                - component: oh-slider
                  config:
                    item: =props.prefix+items[props.selected].state+props.volume
                    label: Volume
                    max: 100
                    min: 0
                    step: 2
                    unit: "%"
    - component: f7-row
      config:
        class:
          - padding-top
          - padding-bottom
        height: 30px
      slots:
        default:
          - component: f7-col
            slots:
              default:
                - component: oh-label-card
                  config:
                    action: options
                    actionFeedback: =items[props.prefix+items[props.selected].state+props.shuffle].state
                    actionItem: =props.prefix+items[props.selected].state+props.shuffle
                    actionOptions: 0=Off,1=Shuffle Songs,2=Shuffle Albums
                    fontSize: -6
                    item: =props.prefix+items[props.selected].state+props.shuffle
          - component: f7-col
            slots:
              default:
                - component: oh-label-card
                  config:
                    action: options
                    actionFeedback: =items[props.prefix+items[props.selected].state+props.repeat].state
                    actionItem: =props.prefix+items[props.selected].state+props.repeat
                    actionOptions: 0=Off,1=Repeat Song,2=Repeat Playlist
                    fontSize: -6
                    item: =props.prefix+items[props.selected].state+props.repeat
          - component: f7-col
            slots:
              default:
                - component: oh-label-card
                  config:
                    action: options
                    actionFeedback: =items[props.prefix+items[props.selected].state+props.sleepmode].state
                    actionItem: =props.prefix+items[props.selected].state+props.sleepmode
                    actionOptions: 0=Off,15=15 Minutes,30=30 Minutes,45=45 Minutes,60=60 Minutes,90=90 Minutes
                    fontSize: -6
                    item: =props.prefix+items[props.selected].state+props.sleepmode
                    label: Sleepmode
    - component: f7-row
      config:
        class:
          - padding-top
          - padding-bottom
      slots:
        default:
          - component: f7-col
            slots:
              default:
                - component: oh-button
                  config:
                    action: command
                    actionCommand: toggle
                    actionFeedback: Power
                    actionItem: =props.prefix+items[props.selected].state+props.power
                    class:
                      - margin-top
                    color: red
                    fill: true
                    iconF7: power
                    text: POWER
    - component: f7-row
      slots:
        default:
          - component: f7-col
            slots:
              default:
                - component: oh-label-card
                  config:
                    action: options
                    actionFeedback: =items[props.prefix+items[props.selected].state+props.favorites].state
                    actionItem: =props.prefix+items[props.selected].state+props.favorites
                    actionOptions: =items[props.favoriteslist].state
                    height: auto
                    item: =props.prefix+items[props.selected].state+props.favorites
                    width: 100%

I hope I provided all information needed - if not feel free to ask…

5 Likes

I like the player selector feature. I’ll have to revisit my Chromecast widget and add something like that.

Thanks for posting!

1 Like

you’re welcome :wink:
since my widget is based on yours this should be a nobrainer…

have a nice day

Continuing the discussion from Squeeze Control Custom Widget:

Dear Dan

Looks outstanding

A silly question from a beginner.

The item for the players is from type string and all players are hardcoded in it ?

Merry Christmas from snowy Austria

Karl

1 Like

Hi Karl,

thanks for the interest in my custom widget…

edit:

I read your post again and now I’m thinking you’re referring to the “player selector item” which is a dummy item not connected to a channel, and this item has to be of a string-type to handle the player names as strings. here you’re right! It takes the player-names without prefix/suffix. These are defined in the widgets configuration in a comma seperated list.

In my example (see below) the name for the player in this list would be “Livingroom”…

the list in the widgets configuration would look something like this:

Livingroom, Bedroom, Kitchen, Bathroom

this is my item-definition:

String      SqueezePlayer       "Squeezeplayer"                 (gSqueeze)

nothing fancy just a dummy…


original post:
the answer to your question (if I got you right) is no… each player acts as a thing which can have many items to control… and these items are from different types e.g. “duration” is a number item-type while “stop” is a switch item-type… the squeezeboxserver works as bridge…

I don’t know if you’re using a textual or UI driven configuration, but if there are any further questions that are not covered by my initial post, or you don’t understand a single behavior, feel free to ask again :wink:

In my textual config it looks like this:

Bridge squeezebox:squeezeboxserver:musix "Squeezebox - Server Musix" @ "Basement" [ ipAddress="192.168.xxx.xxx", webport=9000, cliport=9090, userId="userxxx", password="passwordxxx" ]
{
    Thing squeezeboxplayer  livingroom    "Squeezebox - ShieldTV" @ "Living Room"   [ mac="2e:26:c8:xx:xx:xx", notificationTimeout=30, notificationVolume=35 ] {
        Channels:
            Player : control
            Dimmer : volume
            Number : currentPlayingTime
            Number : playListIndex
            Number : duration
            Number : sleep
            Number : currentPlaylistShuffle
            Number : currentPlaylistRepeat
            String : remotetitle
            String : title
            String : album
            String : artist
            String : genre
            String : year
            String : numberPlaylistTracks
            Image  : coverartdata 
            String : playFavorite
            String : stream
            String : sync
            String : unsync
            Switch : power
            Switch : mute
            Switch : stop
            Switch : playPause
            Switch : next
            Switch : prev
    }
    Thing squeezeboxplayer  studio... etc

just to give you an idea

the items are reflecting the channels of the thing:

// Livingroom
Player  SqueezeLivingroomControl                "Livingroom"                            (gSqueezeShieldTV)  { channel="squeezebox:squeezeboxplayer:musix:livingroom:control" }
Dimmer  SqueezeLivingroomVolume                 "Volume [%s]"                           (gSqueezeShieldTV)  { channel="squeezebox:squeezeboxplayer:musix:livingroom:volume" }
Number:Time  SqueezeLivingroomCurrPlaytime           "Playing Time [JS(mmss.js):%s]"         (gSqueezeShieldTV)  { channel="squeezebox:squeezeboxplayer:musix:livingroom:currentPlayingTime" }
Number  SqueezeLivingroomPlaylistIndex          "Playlist Index"                        (gSqueezeShieldTV)  { channel="squeezebox:squeezeboxplayer:musix:livingroom:playListIndex" }
Number:Time  SqueezeLivingroomDuration          "Duration [JS(mmss.js):%s]"             (gSqueezeShieldTV)  { channel="squeezebox:squeezeboxplayer:musix:livingroom:duration" }
Number  SqueezeLivingroomSleep                  "Sleeptimer [%.0f]"                     (gSqueezeShieldTV)  { channel="squeezebox:squeezeboxplayer:musix:livingroom:sleep" }
Number  SqueezeLivingroomShuffle                "Shuffle Mode"                          (gSqueezeShieldTV)  { channel="squeezebox:squeezeboxplayer:musix:livingroom:currentPlaylistShuffle" }
Number  SqueezeLivingroomRepeat                 "Repeat Mode"                           (gSqueezeShieldTV)  { channel="squeezebox:squeezeboxplayer:musix:livingroom:currentPlaylistRepeat" }
String  SqueezeLivingroomRemoteTitle            "Remote Title"                          (gSqueezeShieldTV)  { channel="squeezebox:squeezeboxplayer:musix:livingroom:remotetitle" }
String  SqueezeLivingroomTitle                  ""                              <none>  (gSqueezeShieldTV)  { channel="squeezebox:squeezeboxplayer:musix:livingroom:title" }
String  SqueezeLivingroomAlbum                  ""                              <none>  (gSqueezeShieldTV)  { channel="squeezebox:squeezeboxplayer:musix:livingroom:album" }
String  SqueezeLivingroomArtist                 ""                              <none>  (gSqueezeShieldTV)  { channel="squeezebox:squeezeboxplayer:musix:livingroom:artist" }
String  SqueezeLivingroomGenre                  ""                              <none>  (gSqueezeShieldTV)  { channel="squeezebox:squeezeboxplayer:musix:livingroom:genre" }
String  SqueezeLivingroomYear                   ""                              <none>  (gSqueezeShieldTV)  { channel="squeezebox:squeezeboxplayer:musix:livingroom:year" }
String  SqueezeLivingroomPlaylistTracks         ""                              <none>  (gSqueezeShieldTV)  { channel="squeezebox:squeezeboxplayer:musix:livingroom:numberPlaylistTracks" }
Image   SqueezeLivingroomCover                  "Cover Art[%s]"                         (gSqueezeShieldTV)  { channel="squeezebox:squeezeboxplayer:musix:livingroom:coverartdata" }
String  SqueezeLivingroomPlayFavorite           "Play Favorites [%s]"                   (gSqueezeShieldTV)  { channel="squeezebox:squeezeboxplayer:musix:livingroom:playFavorite" }
String  SqueezeLivingroomStreamURL              "Stream URL"                            (gSqueezeShieldTV)  { channel="squeezebox:squeezeboxplayer:musix:livingroom:stream" }
String  SqueezeLivingroomSyncPlayer             "Sync Player"                           (gSqueezeShieldTV)  { channel="squeezebox:squeezeboxplayer:musix:livingroom:sync" }
String  SqueezeLivingroomUnsyncPlayer           "Unsync Player"                         (gSqueezeShieldTV)  { channel="squeezebox:squeezeboxplayer:musix:livingroom:unsync" }
Switch  SqueezeLivingroomPower                  "Power"                                 (gSqueezeShieldTV)  { channel="squeezebox:squeezeboxplayer:musix:livingroom:power" }
Switch  SqueezeLivingroomMute                   "Mute"                                  (gSqueezeShieldTV)  { channel="squeezebox:squeezeboxplayer:musix:livingroom:mute" }
Switch  SqueezeLivingroomStop                   "Stop"                                  (gSqueezeShieldTV)  { channel="squeezebox:squeezeboxplayer:musix:livingroom:stop" }
Switch  SqueezeLivingroomPlaypause              "Play/Pause"                            (gSqueezeShieldTV)  { channel="squeezebox:squeezeboxplayer:musix:livingroom:playPause" }
Switch  SqueezeLivingroomNext                   "Next"                                  (gSqueezeShieldTV)  { channel="squeezebox:squeezeboxplayer:musix:livingroom:next" }
Switch  SqueezeLivingroomPrevious               "Previous"                              (gSqueezeShieldTV)  { channel="squeezebox:squeezeboxplayer:musix:livingroom:prev" }

and this configuration for every player you’re using in your setup…
make sure to read my post again, maybe it makes more sence to you now…

merry christmas :evergreen_tree:

cheers Dan

1 Like

Thanks Dan

I think now i understood. The dummy item was the hint. So only define a string item without channeldefinition .

Will test it with my squeeze configuration.
i use 6 players in my house so this is wonderfull widget where you can choose the player.
Like this prefix and suffix idea. Again great work

Karl

Great Karl!

and you can add a rule to switch the displayed player automatically at certain times with this dummy item.
something like

kitchen player at morning
e.g.

rule "Activate Kitchen Player at 08.00" 
when
	Time cron "0 8 0 * * ? *"
then
        SqueezePlayer.sendCommand("Kitchen")
end

ahh by the way, there is a nice plugin available for squeezeboxserver
you have to activate this plugin first at the servers settings page…
its called

Geräte Gruppen (v0.15.0) by philippe_44

where you can manage groups, and each group you create acts as a dummy squeezebox player with own unique generated mac address which you can add to your openhab squeeze things…
then you are able to control multiple players simultaniously with one dummy player - very nice feature, and you can name these dummy players whatever you like e.g. PARTY GROUP for all known players or whatever you like!

example:

    Thing squeezeboxplayer  partygroup    "Squeezegroup - Party" @ "Home"      [ mac="02:00:e6:xx:xx:xx", notificationTimeout=30, notificationVolume=35 ]  {
        Channels:
            Player : control
            Dimmer : volume
            Number : currentPlayingTime
            Number : playListIndex
            Number : duration
            Number : sleep
            Number : currentPlaylistShuffle
            Number : currentPlaylistRepeat
            String : remotetitle
            String : title
            String : album
            String : artist
            String : genre
            String : year
            String : numberPlaylistTracks
            Image  : coverartdata 
            String : playFavorite
            String : stream
            String : sync
            String : unsync
            Switch : power
            Switch : mute
            Switch : stop
            Switch : playPause
            Switch : next
            Switch : prev
    }

you can find the player(group) mac adresses at the squeezeboxserver’s info page

cheers Dan

That sounds really great. To understand this all i think i have to rethink al lot about that. The good thing is i remember how to add plugin on the squeeze server :slight_smile:
But first step is to get your widget running.
With OH3 i started to do everything in UI and not textuel config.

But sometin i think things are going easier to understand and find in textual config as i have at my old (openhab 2.4 version)
Maybe i recycle my squeeze textual config .
But i am a little bit angry to mix textual and UI config in one setup.
Do you have only textual config ?

Karl

My server :

UID: squeezebox:squeezeboxserver:NAS
label: SqueezeBox Server NAS
thingTypeUID: squeezebox:squeezeboxserver
configuration:
ip and so on....

one of my player config:

UID: squeezebox:squeezeboxplayer:NAS:Bad
label: SqueezeBox Player Bad
thingTypeUID: squeezebox:squeezeboxplayer
configuration:
mac: and so on....

out of the 2.5 docu for textual thing configuration i get :

What is the name of your thing ?

squeezboxplayer or squeezboxplayer livingroom ?
i don´t find the “:” as i have in my config for identification the right thing .

As i understand UID: has to be unique

Karl

yes I like it more that way… :wink:

I think it’s not easier to use this fancy UI stuff for configuration… But I use semantic model & widgets from the new UI… I never got warm with PaperUI with OH2 and now I stick with files…

squeezebox is the name of the binding, squeezeboxplayer is the type of the thing and livingroom is the name of the player

but to make this clear: you can name your players whatever you want in thing config, but you have to use the corresponding Item name (without prefix & suffix) for the SqueezPlayer selector item list, not the name from the thing!

example:
Dimmer SqueezeLivingroomVolume “Volume [%s]” (gSqueezeShie…

Another hint: the squeezbox binding uses the player’s mac-addresses as unique identifiers.

I would have used your old textual config, because it’s way easier to mass-rename your items to fit to my widget preconditions instead of changing every single item in UI…

that’s why I like files, they are easier to handle

but that’s only my opinion :wink:

This is exact what i also loved with textual config.
But one single false letter and often you search very long to find this.
Structered in a good way in textfiles is for my easier as search it in the UI configuration

I don´t know that you can use sematic model in text config als.

The model was for my the point to change to UI config.
Think i have to rethink about it before i do too much in UI.

Is it “sure” that the text config will stay also in the next versions or will everything go in direction UI.
Thats also a critical question for me to decide.

Thanks to take the time to lead me on the way to better understanding.

But all in all openhab is great!!! but a lot to learn

I use LCN as bus system for all my lights in my home so it´s really easy to integrate in openhab same for my VELUX windows and shutters. And now i am working to get squeezbox visualised in a good way. Player things are online. Have to look how i can recycle my textual config for squeezboxes from the 2.4 version.

Karl

my thinking is, as long there’s no announcement about cancelling the use of config files i’ll stick with them as long as possible. but even if the devs decide to discontinue the parsing of text files there surely would be some time to react until it’s not working with files anymore. So you don’t have to panic about this imo…

That´s good news.
Then i will overthink textual config.

Items is what i don´t get warm in UI (don´t remember on the exact naming as i don´t adapt oH config seldom only in my rare spare time) I search always the exact item name in a huge list.
It gives me more comfort to have all items in different .item files where i can find easily the naming.

You said that you use semantic model:

? in the UI or
is a possibility to do it in textual form.

Hi Karl,

I use it in config files…
to give you an idea, you have to use groups to reflect your architecture
you can do this in item files…

my file is named model.items

//HOUSE MODEL
Group                   gHouse               "House"                                                        ["Apartment"]

//INDOOR
Group                   gIndoor               "Indoor"                                    (gHouse)          ["Indoor"]

//GROUND FLOOR
Group                   gGroundFloor          "Groundfloor"           <groundfloor>       (gIndoor)               ["GroundFloor"]

Group                   gBathroom             "Bathroom"              <bath>              (gGroundFloor)          ["Bathroom"]

…and so on, I think you get it…
and then you add your items to the semantic groups you created :wink:
more information on the semantic model here

1 Like

:grinning: :grinning: :grinning:
pretty easy

Thank

Just to add a few things to the textual config discussion:

  • text configs are not going away any time soon
  • new features in OH 3 and beyond are not guaranteed to have a text file equivalent (e.g. custom widgets, rule templates)
  • the symantic model is implemented using tags and Group membership so of course can be implemented in. items files; but you have to be careful to use the right tags in the right context
  • there is no such thing as renaming an Item. The names is the unique identifier and in text files what happens is the old item is deleted and a new one is created. This can leave a lot of trash laying around like orphaned links, rules, widgets, etc. which might cause problems
  • don’t forget the developer sidebar when working with anything in MainUI. You can pin the Items, Things, Rules and Widgets you are working with at the moment right there for easy reference
  • in general, the UI has lots of tools built in to make managing the configs easier which people do not take advantage of, and then pan the UI approach over all for being too much point and click. And even with the point and click it might be too much point and click, but you will be trading a self documenting interface where it’s almost impossible to make a syntax error for a text file with the need to have to keep looking at references and docs to get right and occasionally fighting syntax errors. It’s a trade-off.
1 Like

ok, but I think this is more a problem with database handling than with textual config or renaming… :wink:

I like the new UI but I think the same like @Karl1 stated, the Item and things list in UI is pure pain to work with, text files can be more easily structured and are way better to work with…
And I don’t like the rule engine very much… but maybe it’s better to discuss this elsewhere…

That’s why I brought up the developer sidebar and other tools. No one should be working primarily though the Items and Things list. I agree, that would be painful, but that’s not the only option.

Dear Dan

So now i am back to implement my squeezboxes. In the meantime i setup from scratch a new Raspi 4 with openhabian V1.71 (great work) based on Bullseye. Main reason for this was LOG4J thing which should in my understanding no longer be a problem in this new distribution.

The only tricky thing was get the LCN Binding working as the bridge used a software (PCHK) which need wiring PI and this is now discontinued. But lucky found a Pi version on the webside of the author for Raspi 4B and with this version i got it working.

Have done the config for squeeze now textual. :wink:

In your item config you use a map transformation. JS(mmss.js) where the formatting is done i think
But i don´t have skills to do this im my config also.

Number:Time  SqueezeWintergartenDuration          "Duration [JS(mmss.js):%s]"    

Found the solution from mhilbush
and placed a file mmss.js into the conf/transform directory

(function(i){ 
    var s = ~~(i%60);
    return (~~(i/60) + ':' + ((s < 10) ? '0' : '') + s);
})(input)

But the result is still showing only in seconds.
JS transformation ADDON is installed.

Thanks a lot for your time

Karl