Home » Archived » Eclipse SmartHome » Thoughts on a new Sitemap Concept
Thoughts on a new Sitemap Concept [message #1715906] |
Fri, 27 November 2015 14:34 |
Dennis Nobel Messages: 166 Registered: September 2014 |
Senior Member |
|
|
Hi,
I want to share some thoughts on a new sitemap concept based on some discussion I had with several people.
The current sitemap concept has a few problems:
* It´s main representation is an Xtext DSL, which brings a heavy dependency. The current idea in ESH is to remove all dependencies to the DSLs in the core and to the DSL as one optional way how to configure the system. This was already done for the Items and the Things.
* It is not editable via REST (mainly because of the previous point)
* It´s not really component-oriented (meaning definitions can be reused)
So I want to start with requirements for a new Sitemap concept:
1) should be represented as a plain Java model
2) editable via REST with a proper JSON model
3) should be modular and should allow to reuse components and define composition (like the rule engine)
4) it is easy to extend with specific features
5) it should allow to define how the UI represents the smart home, but should not invent a fully new UI framework like HTML
6) Different UIs can add specific properties to the Sitemap to allow specific arragnment or styles, that are not part of the core Item concept
Some terms for the new conepts:
1) Atom - An atom is the smallest component of a sitemap. Atoms are simple UI components like labels, buttons, sliders etc. (comparable to the Widget in the current Sitemap)
2) Container - A container groups and arranges multiple atoms and/or other containers (comparable to the Group or Frame in the current Sitemap)
3) Sitemap - The full definition of a system view Containing containers and Atoms
Atom:
The atom represents a UI component. The new sitemap concept comes with several so called "system" atoms that each default renderer MUST support. An atom defines a type ID, configurable properties and data points and/or action points. Data points and actions points is the binding between the sitemap and the the core Item model. The UI don´t have to know the Item model, it just works the data whereas the conversion is done on the server side. This is why there are two models: The Sitemap definition model and the Sitemap rendering model.
Example of an atom usage/definition:
{
id: "TemperatureLabel"
type: "system:Label"
properties: {
editable: true
},
data: {
text: "@Item:MyTemperatureItem"
}
}
Example of the resulting rendering model:
{
id: "TemperatureLabel"
type: "system:Label"
properties: {
editable: true
},
data: {
text: "20 °C"
}
}
The UI renderer does not need to know which item is bound, it just works with the data endpoint. It subscribes for events like "smarthome/sitemaps/mySitemap/TemperatureLabel/text" to receive text updates and sends a PUT to "rest/sitemaps/mySitemap/TemperatureLabel/text" if the label ws changed in the UI. The Java logic maps the Item and performs the according commands.
Example of the "Toggle Button atom":
Example of an atom usage/definition:
{
id: "MyToggle"
type: "system:ToggleButton"
data: {
state: "@Item:MySwitch"
}
}
Example of the resulting rendering model:
{
id: "TemperatureLabel"
type: "system:Label"
properties: {
editable: true
},
data: {
state: true
}
}
List of possible system atoms:
Atoms:
* Label
* Input
* Button
* ToggleButton
* Select
* MultiButton
* Slider
* Colorpicker
* Image
There might be of course more atoms. Every solution can also define new atoms in another namespace and use them in sitemaps. But default renderer will not be able to render them. It might be worth to think about Atom inheritance to allow fallback rendering in case a custom atom is used and the renderer does not know it.
Container:
Containers can arrange Atoms. Therefore there might be different containers for different layouts like a LinearContainer or a GridContainer. Another possibility is to just define one Container with layout arguments. Containers itself should be reused in order to allow composition.
Even a binding might define containers for it´s things, so that the UI can use them in order to come up with a good default interface for a thing.
Notes on DSL and compatibility:
It should still be possible to define a sitemap with an easy DSL syntax, as the JSON might be hard to specify. Maybe the existing DSLs can be used and transformed into the new sitemap concept.
These are just some thoughts and there is definitely a lot of discussion left.
Regards
Dennis
|
|
|
Re: Thoughts on a new Sitemap Concept [message #1718243 is a reply to message #1715906] |
Mon, 21 December 2015 14:33 |
Joerg Plewe Messages: 4 Registered: December 2015 |
Junior Member |
|
|
Hi all!
Based on that very discussion and Dennis' proposals, I designed and (partly) implemented a different approach for sitemap. In order to avoid naming confusion, I called it 'uimap'.
The design goals are:
* extendible
** allow to contain information for any special UI
** like shapes, sounds, layout options, special widget types, sorting
* few client logic
** server-side rendering
** e.g. no evaluation of visibility or color rules on client-side
* can create (generic) editor
* SSE update mechanics on widget basis
* fancy rendering options using server-side JS
* store virtually any information augmenting Item data
* render client-provided content (not persisted on the server) and provide events accordingly
A widget definition is supposed to look like this. Notice the extendible list of attributes per widget:
{
"name": "foo",
"widgettype": "Map",
"description": "Test Map Foo",
"children": [
{
"itemname": "yahooweather_weather_12834203",
"widgettype": "Frame",
"attributes": {
"label": {
"fixed": "Weather taken from Yahoo"
}
},
"children": [
{
"itemname": "yahooweather_weather_12834203_humidity",
"widgettype": "Text",
"attributes": {
"state": {
"mappings": [
{ "state": "0", "value": "dry" },
{ "state": "50", "value": "rather wet" },
{ "state": "100", "value": "wet" }
],
"pattern": "humindity: %d %%"
}
}
}
]
},
{
"widgettype": "Group",
"name": "Tesla",
"description": "how's the battery?",
"attributes": {
"label": { "fixed": "Tesla" },
"tesla_widget": { "fixed": "Tesla_Widget" }
},
"children": [
{
"itemname": "yahooweather_weather_12834203_humidity",
"widgettype": "Slider",
"attributes": {
"label": { "fixed": "Tesla Battery" },
"state": { "pattern": "%d %%" },
"tesla_widget": { "fixed": "Tesla_Loading_State_Widget" },
"tesla_label": { "pattern": "loaded up to %d" }
}
}
]
}
]
}
This will get rendered to:
{
"autocreated": false,
"uuid": "fcd8cd8a-3aeb-45ac-9352-63abd5f19172",
"name": "foo",
"widgettype": "Map",
"description": "Test Map Foo",
"children": [
{
"autocreated": false,
"uuid": "f72bd3a4-e697-4ddb-8312-c0728d7f6802",
"itemname": "yahooweather_weather_12834203",
"widgettype": "Frame",
"itemtype": "Group",
"itemlabel": "Yahoo weather Bocholt, Germany",
"itemstate": "UNDEF",
"item": {
"members": [],
"state": "UNDEF",
"type": "GroupItem",
"name": "yahooweather_weather_12834203",
"label": "Yahoo weather Bocholt, Germany",
"tags": [
"thing"
],
"groupNames": [
"home_group_0532d2ad"
]
},
"attributes": {
"label": {
"value": "Weather taken from Yahoo",
"fixed": "Weather taken from Yahoo"
}
},
"children": [
{
"autocreated": false,
"uuid": "c0b731ba-dfd7-4e6b-b7db-9b31a7e7da87",
"itemname": "yahooweather_weather_12834203_humidity",
"widgettype": "Text",
"itemtype": "Number",
"itemcategory": "Humidity",
"itemlabel": "Luftfeuchtigkeit",
"itemstate": "70 %",
"item": {
"state": "70",
"stateDescription": {
"pattern": "%d %%",
"readOnly": true,
"options": []
},
"type": "NumberItem",
"name": "yahooweather_weather_12834203_humidity",
"label": "Luftfeuchtigkeit",
"category": "Humidity",
"tags": [],
"groupNames": [
"yahooweather_weather_12834203"
]
},
"attributes": {
"state": {
"value": "humindity: 70 %",
"pattern": "humindity: %d %%",
"mappings": [
{
"state": "0",
"value": "dry"
},
{
"state": "50",
"value": "rather wet"
},
{
"state": "100",
"value": "wet"
}
]
}
}
},
{
"autocreated": true,
"uuid": "5ffc3ca9-a39a-400c-adda-fb544b36e850",
"itemname": "yahooweather_weather_12834203_pressure",
"widgettype": "Text",
"itemtype": "Number",
"itemcategory": "Pressure",
"itemlabel": "Luftdruck",
"itemstate": "1027,4 hPa",
"item": {
"state": "1027.4",
"stateDescription": {
"pattern": "%.1f hPa",
"readOnly": true,
"options": []
},
"type": "NumberItem",
"name": "yahooweather_weather_12834203_pressure",
"label": "Luftdruck",
"category": "Pressure",
"tags": [],
"groupNames": [
"yahooweather_weather_12834203"
]
},
"attributes": {
"label": {
"value": "Luftdruck",
"fixed": "Luftdruck"
},
"state": {
"value": "1027,4 hPa",
"pattern": "%.1f hPa"
}
}
},
{
"autocreated": true,
"uuid": "326130cb-f668-49bf-b2af-3717afd78c98",
"itemname": "yahooweather_weather_12834203_temperature",
"widgettype": "Text",
"itemtype": "Number",
"itemcategory": "Temperature",
"itemlabel": "Temperatur",
"itemstate": "6,0 °C",
"item": {
"state": "6",
"stateDescription": {
"pattern": "%.1f °C",
"readOnly": true,
"options": []
},
"type": "NumberItem",
"name": "yahooweather_weather_12834203_temperature",
"label": "Temperatur",
"category": "Temperature",
"tags": [],
"groupNames": [
"yahooweather_weather_12834203"
]
},
"attributes": {
"label": {
"value": "Temperatur",
"fixed": "Temperatur"
},
"state": {
"value": "6,0 °C",
"pattern": "%.1f °C"
}
}
}
]
},
{
"autocreated": false,
"uuid": "444037e7-8651-4b00-a3c0-30820ad1ad3b",
"name": "Tesla",
"description": "how's the battery?",
"widgettype": "Group",
"attributes": {
"label": {
"value": "Tesla",
"fixed": "Tesla"
},
"tesla_widget": {
"value": "Tesla_Widget",
"fixed": "Tesla_Widget"
}
},
"children": [
{
"autocreated": false,
"uuid": "4a8149c3-f240-429f-ad31-a98e271666f6",
"itemname": "yahooweather_weather_12834203_humidity",
"widgettype": "Slider",
"itemtype": "Number",
"itemcategory": "Humidity",
"itemlabel": "Luftfeuchtigkeit",
"itemstate": "70 %",
"item": {
"state": "70",
"stateDescription": {
"pattern": "%d %%",
"readOnly": true,
"options": []
},
"type": "NumberItem",
"name": "yahooweather_weather_12834203_humidity",
"label": "Luftfeuchtigkeit",
"category": "Humidity",
"tags": [],
"groupNames": [
"yahooweather_weather_12834203"
]
},
"attributes": {
"label": {
"value": "Tesla Battery",
"fixed": "Tesla Battery"
},
"state": {
"value": "70 %",
"pattern": "%d %%"
},
"tesla_widget": {
"value": "Tesla_Loading_State_Widget",
"fixed": "Tesla_Loading_State_Widget"
},
"tesla_label": {
"value": "loaded up to 70",
"pattern": "loaded up to %d"
}
}
}
]
}
]
}
A more elaborated description including REST APIs can be found here:
https://github.com/Universalhome/smarthome/blob/private/joergp/study_uimap_uuid_based/extensions/ui/org.eclipse.smarthome.ui.uimap/doc/uimap.md
Additionally, I provide implementations for two different REST approaches:
https://github.com/Universalhome/smarthome/tree/private/joergp/study_uimap/extensions/ui/org.eclipse.smarthome.ui.uimap
https://github.com/Universalhome/smarthome/tree/private/joergp/study_uimap_uuid_based/extensions/ui/org.eclipse.smarthome.ui.uimap
Personally, I favor the latter.
|
|
| |
Re: Thoughts on a new Sitemap Concept [message #1718530 is a reply to message #1718385] |
Sat, 26 December 2015 10:05 |
Joerg Plewe Messages: 4 Registered: December 2015 |
Junior Member |
|
|
Hi Karel,
thank you for your comments.
* In the demo implementation, the uimaps get persisted using the common Storage API of ESH via the REST Interface. The format is JSON as quoted in my initial post. Basically, every document rendered by the server can be modified and stored back.
Additionally it would be easy to deploy predefined scripts with the server as it is currently done with the sitemap definition files.
* IMHO a sitemap->uimap converter is doable with medium effort preserving the existing sitemap code. Sitemap Java objects are accessible on server-side. I (right now) do not see fundamental incompatibilities.
* Authorization is not included in this concept. IMHO it requires a separate discussion and a higher-level concept, for it should hold for the other REST Interfaces as well.
* Notifications are sent using common http-based SSE mechanics. If the client is a browser, using a EventSource object is easy.
Your comments put two points on the TODO list:
* load uimaps from server files on Startup (in addition to the Storage load), so that default uimaps can get deployed with the Server
* load uimaps from sitemap Definition
Merry xmas,
. J
karel goderis wrote on Wed, 23 December 2015 08:44Joerg,
Just some immediate thoughts
How will the sitemap be persisted? in a readable JSON format that can be potentially be edited by regular text editor?
Will there be a way to import/convert the existing .sitemap DSL? This is important/a-must-have as .sitemaps are invested interests from the user point of view
What about Notifications ? (see the proposal I made in a different post)
What about user authentication/authorisation?
What about remote capabilities? REST is is easy/Ok for sending updates back to ESH. How will Notifications be received by the UI end? will that mechanism will be http based (long-polling?) (e.g. thus easy to setup with respect to firewalls)
Karel
[Updated on: Sun, 27 December 2015 09:49] Report message to a moderator
|
|
|
Re: Thoughts on a new Sitemap Concept [message #1718563 is a reply to message #1718530] |
Sun, 27 December 2015 10:29 |
Karel Goderis Messages: 198 Registered: March 2014 |
Senior Member |
|
|
Joerg Plewe wrote on Sat, 26 December 2015 05:05Hi Karel,
* Notifications are sent using common http-based SSE mechanics. If the client is a browser, using a EventSource object is easy.
Beware, Notifications are not the same as Events. In the Notifications discussion it was argued that Notifications should or could be handled by users, using for example a dialog pop-up and so forth, interact with them, trigger a response (e.g. acknowledge the notification for example),...
In the initial code I developed for the Notification part, I did provide functionality to generate Notifications out of Events (Events are just another source of Notifications), and I did encapsulate Notifications in Events just for the purpose of shuttling them around the ESH runtime.
I am not sure that the web browser client (read: user) should have access to the Events themselves, but that is open for discussion of course.
Regards
K
|
|
|
Re: Thoughts on a new Sitemap Concept [message #1720397 is a reply to message #1718563] |
Mon, 18 January 2016 11:42 |
Stefan Bussweiler Messages: 6 Registered: January 2016 |
Junior Member |
|
|
Hi Joerg,
I had a brief look on your concept & implementation. These are my first thoughts and questions:
a) Dennis mentioned in the requirements section something like "should be modular and should allow to reuse components and define composition". Is it possible to reuse widget definitions like the "Atom" definitions (definition in namespaces, reuse definitions in sitemaps)?
b) Are there different layouts available in order to arrange the widgets? E.g. "Linear", "Grid".
c) Your solution allows the registration for state events of an entire widget. I would like to prefer an event subscription for certain "data points" as mentioned by Dennis. E.g. I want to receive events via topic "smarthome/sitemaps/mySitemap/TemperatureLabel/text" for text updates.
d) ... and if the label has been changed in the UI, I want to update it via PUT - "rest/sitemaps/mySitemap/TemperatureLabel/text"
e) As in the requirement section mentioned, it should be possible to add UI specific properties that are not part of the Item core concept. This requirement will be addressed with your widget attributes (as I understand it). My specialized UI requires the Thing status and the Thing-Properties of an Item. My widget definition can be something like:
{
"name": "MyItem",
"attributes": {
"label": { "fixed": "SomeLabel" },
"state": { "pattern": "%d %%" },
"thing-status": {},
"thing-properties": {}
}
}
What is the public Java API in order to achieve my requirements? Can you illustrate how to use the sitemap concept with extended attributes?
Thx,
Stefan
|
|
|
Re: Thoughts on a new Sitemap Concept [message #1720418 is a reply to message #1720397] |
Mon, 18 January 2016 14:26 |
Joerg Plewe Messages: 4 Registered: December 2015 |
Junior Member |
|
|
Hi Stefan,
thank you for your comments.
a) Yes, UIMap widget and 'Atom' can be the same thing. Any widget can be saved on it's own receiving a UUID and can be referred by any other widget. For top-Level maps also are just widgets with widgettype=Map, any widget can get reused by reference.
b) UIMap does not refer to layout explicitely for there is no common notion of how a layout can be addressed covering different UI implementations. Layout would be a candidate for a custom attribute whenever a UI requires it.
{
"name": "Livingroom",
"widgettype": "Frame",
"attributes": {
"label": { "fixed": "Livingroom" },
"layout": { "fixed": "grid" }
}
}
When UI implementations are implemented it might turn out there is a best practise for layout description that can then be delivered as a default e.g. for GroupItems. So far, it is not necessary to specify it. Any UI that needs layout Information can store it to the UIMap the way it's best.
c) Yeees ... at first I tended to follow Dennis' proposal to have a fine grained event registration allowing for a smarter client code design not requiring a client-side dispatcher. I like that better, too.
But: for most clients can be expected to be a web browser, they typically do not allow for many SSE endpoints (EventSource object in JS). Max. a handful. So I headed for accumulative approach for a whole widget structure as a technical tradeoff.
d) update the label and save the widget by it's UUID. Maybe the saving API needs some more control (recursive?)
e) I'm not sure what's the point. So far, UIMap widgets operate in Items reflecting the state of a Thing's channel. What do you mean by 'Thing state'?
Do you think it might make sense to alternatively refer to Things as well (have to look up Thing API?
{
"name": "Phillips HUE",
"widgettype": "Text",
"thingid": "PhilHUE1",
"attributes": {
"state": { "pattern": "State: %s" }
}
}
There is no Java API, just a REST API so far. Designing a UIMap means to setup one or many JSON documents and POST/PUT them to the server.
Does this make sense?
. Jörg
|
|
|
Re: Thoughts on a new Sitemap Concept [message #1720692 is a reply to message #1720418] |
Wed, 20 January 2016 10:11 |
Stefan Bussweiler Messages: 6 Registered: January 2016 |
Junior Member |
|
|
Hi Jörg,
thanks for your response.
Some more thoughts about my requirements regarding bullet e):
In my case the user interface only knows Items, no Things. But in some cases I want to display device specific information for an Item, e.g. the Device/Thing Status. In that case I want to provide a specialized widget/atom type.
The definition of my customized atom can look like:
{
"id": "MyToggleAtom",
"type": "some:ExtendedToggleButton",
"data": {
"state": "MySwitchItem",
"thing-status": "MySwitchItem"
}
}
The rendered atom:
{
"id": "MyToggleAtom",
"type": "some:ExtendedToggleButton",
"data": {
"state": "ON",
"thing-status": "ONLINE"
}
}
... and it would be nice If I can receive update events via the topics
"smarthome/sitemaps/mySitemap/MyToggleAtom/state"
and
"smarthome/sitemaps/mySitemap/MyToggleAtom/thing-status"
The sitemap "default" renderer only supports "system" atoms. For my customized atom/widget type (some:ExtendedToggleButton) I would provide some kind of a "SitemapAtomProvider". This class has to provide a implementation in order to render my specialized atom type. A so called "SitemapRenderManager" is a sitemap core component, which uses such providers to render specialized atoms. Therefore a provider must be registered as OSGi service.
What do you think about it?
Stefan
|
|
|
Re: Thoughts on a new Sitemap Concept [message #1720902 is a reply to message #1720692] |
Thu, 21 January 2016 17:56 |
Joerg Plewe Messages: 4 Registered: December 2015 |
Junior Member |
|
|
Hi Stefan!
As for showing Thing status (Thing.getStatus(), Thing.getStatusInfo() I suppose) in the UI. If the status is not reflected by a channel connectable to an Item, this sounds like an ESH design flaw and should be addressed with ESH core.
Would be feasible to do it in the UIMap though as a workaround.
As for using custom widgets, the solution in my proposal would look like this:
{
"name": "MyToggleAtom for Item",
"widgettype": "Switch",
"itemname": "MySwitchItem",
"attributes": {
"label": { "fixed": "ItemState" },
"state": {},
"some-widgettype": { "fixed": "ExtendedToggleButton" }
}
}
This way, the widget can get rendered by a generic UI, whereas your specialized UI knows to look for "some-widgettype".
In case we'd head for Thing status rendering as well, an extension of the widget definition comes to mind, exchanging the "itemname" against a "thinguid". In case only "itemname" is given, attributes get rendered against the Item, if "thinguid" is given, they get rendered against the Thing. (if both are given there is an error condition when trying to store that widget definition).
This would match the sematics of a Thing status being kind of an Item.
{
"name": "MyToggleAtom for Thing",
"widgettype": "Switch",
"thinguid": "mything",
"attributes": {
"label": { "fixed": "ThingState" },
"state": {},
"some-widgettype": { "fixed": "ExtendedToggleButton" }
}
}
To model your use case, both widget get grouped into a Groupwidget, which in turn can have a special type for your specific UI:
{
"name": "MyToggleAtom for Thing and Item",
"widgettype": "Group",
"attributes": {
"label": { "fixed": "Mystatus },
"some-widgettype": { "fixed": "ExtendedStatusGroup" }
},
"children": [
{
"name": "MyToggleAtom for Item",
"widgettype": "Switch",
"itemname": "MySwitchItem",
"attributes": {
"label": { "fixed": "ItemState" },
"state": {},
"some-widgettype": { "fixed": "ExtendedToggleButton" }
}
},
{
"name": "MyToggleAtom for Thing",
"widgettype": "Switch",
"thinguid": "mything",
"attributes": {
"label": { "fixed": "ThingState" },
"state": {},
"some-widgettype": { "fixed": "ExtendedToggleButton" }
}
}
]
}
Makes sense?
. J
|
|
| | |
Re: Thoughts on a new Sitemap Concept [message #1784271 is a reply to message #1780054] |
Sun, 25 March 2018 22:43 |
Flavio Costa Messages: 23 Registered: February 2018 |
Junior Member |
|
|
Hello,
Improving the sitemap concept is something I've been thinking for some time. Dennis has laid out some interesting ideas, so I tried to expand on the initial concepts. It's nice to see an implementation attempt here, but we probably need a very solid and agreed conceptual foundation before moving to actual code.
I'd like to start with the sample sitemap definition written in the current DSL that we find on the openHAB documentation:
sitemap demo label="My home automation" {
Frame label="Date" {
Text item=Date
}
Frame label="Demo" {
Switch item=Lights icon="light"
Text item=LR_Temperature label="Livingroom [%.1f °C]"
Group item=Heating
Text item=LR_Multimedia_Summary label="Multimedia [%s]" icon="video" {
Selection item=LR_TV_Channel mappings=[0="off", 1="DasErste", 2="BBC One", 3="Cartoon Network"]
Slider item=LR_TV_Volume
}
}
}
In my proposal, this same sitemap could be specified with the following JSON:
{
smarthome: {
id: "demo", label: "My home automation",
components: {
frame: {
label: "Date",
components: {
text: {item: "Date"}
}
},
frame: {
label: "Demo",
components: {
switch: {item: "Lights", icon: "light"},
text: {item: "LR_Temperature", label: "Livingroom", state: "%.1f °C"},
group: {item: "Heating"},
text: {item: "LR_Multimedia_Summary", label: "Multimedia", state: "%s", icon: "video",
components: {
selection: {item: "LR_TV_Channel", mappings: {0: "off", 1: "DasErste", 2: "BBC One", 3: "Cartoon Network"}},
slider: {item: "LR_TV_Volume"}
}
}
}
}
}
}
}
Except for the added "components" and "state" elements, this is almost a 1-to-1 mapping of the DSL to JSON. This approach can make it easier to understand the new concepts and how they can support backwards compatibility with the existing sitemap definition files. The system can easily parse a source file in the sitemap DSL and convert it to the JSON format, using the resulting structure for all further processing.
This is the expected visual rendering for this sitemap:
Below the resulting rendering model:
{
sitemap: {
type: "smarthome", data: {id: "demo", label: "My home automation"}, layout: "list", lang: "en",
components: {
container: {
type: "frame", data: "Date", style: "font-weight:bold;font-size:large", layout: "listcontrol",
components: {
atoms: {
{type: "icon", data: {id: "classic:text", value: "20160801000000"}, style: "width:16px;height:16px"},
{type: "label", data: "Today"}
{type: "text", data: "Monday, 01.Aug.2016", style: "font-weight:bold"}
}
}
},
container: {
type: "frame", data: "Demo", style: "font-weight:bold;font-size:large", layout: "listcontrol",
components: {
atoms: {
{type: "icon", data: {id: "classic:light", value: "OFF"}, style: "width:16px;height:16px"},
{type: "label", data: "Lights"},
{type: "switch", data: "OFF"}
},
atoms: {
{type: "icon", data: {id: "classic:temperature", value: "21.3"}, style: "width:16px;height:16px"},
{type: "label", data: "Livingroom"},
{type: "text", data: "21.3 °C", style: "font-weight:bold"}
},
atoms: {
{type: "icon", data: {id: "classic:group"}, style: "width:16px;height:16px"},
{type: "label", data: "Heating"},
{type: "group", data: "item:Heating"}
},
atoms: {
{type: "icon", data: {id: "classic:video"}, style: "width:16px;height:16px"},
{type: "label", data: "Multimedia"},
{type: "group", data: "components:4"}
}
}
}
}
}
}
While this data should contain all information to display the sitemap on the customer side, one decision to be made is whether new client-side code should be created to parse this, of if this should be further processed to generate output compatible with existing clients:
{
"name": "demo",
"label": "My home automation",
"link": "http://openhabserver:8080/rest/sitemaps/demo",
...
One possibility is to create this conversion logic and provide this backwards-compatible format via the existing REST API, and create a new API endpoint that would provide the raw rendering model for any clients capable to process it.
Let's go deeper into some aspects of the implementation: the basic concepts, the algorithm to generate the rendering model and how to reuse and extend the basic sitemap functionality.
Concepts
- Atom - The smallest composition unit of a sitemap. An atom cannot contain any other components (other than its own attributes). Atoms are usually composed to form more complex components that users can interact with (e.g. a widget containing an icon, a label and a switch control).
- Container - A logic set of components that provides hierarchical structure to the sitemap. Containers may be visually rendered (e.g. as a frame) or be completely seamless. The latter case is useful as a container may be used to specify attributes that are propagated to its child components.
- Component - An Atom or Container.
- Sitemap - The root container for all components arranged to provide a certain system view. As the sitemap is a container, it contains the mandatory "type" property of every component and the properties "data" and "layout" that can be specified on any container.
Rendering model
In order to convert a definition model into the respective rendering model, a SitemapRenderer service needs the following inputs:
- A well-formed sitemap definition model, usually provided as a text file created manually or via some visual editor.
- A sitemap style definition, specified as CSS.
- A sitemap prototype definition, specified as JSON with JSONPath references.
The style definition specifies how containers and atoms will be formatted. The approach used here is similar to the one adopted by JavaFX: while standard CSS syntax is used, it does not support some CSS layout properties such as float, position or overflow. The style actually provides a theme to format the sitemap components and it may be customized by the user. The disposition of the elements is determined by the "layout" attribute seen on the rendering model. This is different from the current sitemap solution, where colors, size, margins and fonts are decided by the client GUI. By following a style definition imposed on the server side, now the sitemap will have a more consistent look on the Basic UI, or on various mobile clients.
The prototype definition determines how the elements in the definition model will be restructured to form the component types in the rendering model. The prototypes can also be customized by the user, by editing the existing prototypes or creating new ones. This flexible abstraction layer allows component definitions reuse across the sitemap.
The following style file should generate output with formatting similar to the sitemap screenshot seen above:
/* Default theme */
container.frame {
font-weight: bold; font-size: large;
}
atom {
font-weight: initial; font-size: initial;
}
atom.text {
font-weight: bold;
}
atom.icon {
width: 16px;
height: 16px;
}
The CSS class name (for instance, .frame seen on the first definition above) needs to match the component type. The SitemapRenderer on the server side adds these style definitions to each component where they apply, so the client doesn't need to bother with stylesheet inheritance - each component specifies on its "style" attribute any formatting options to be applied. If there is no style for a certain component, then the client UI can decide its default formatting. Usage of standard CSS syntax and semantics make it easier to implement web browser-based UIs, which already include built-in support for CSS.
Here is an example of a prototype definition file that would work for the sitemap concepts used in the sample above:
smarthome: {
sitemap: {
type: "$",
data: {id: "$.id", label: "$.label"},
layout: "list",
lang: "$.lang,system:language",
components: { "$.components..*" }
}
}
frame: {
container: {
type: "$",
data: "$.label",
layout: "listcontrol",
components: { "$.components..*" }
}
}
seamless: {
container: {
type: "$",
layout: "listcontrol",
components: { "$.components..*" }
}
}
implicit: {
container: {
type: "$",
layout: "listcontrol",
components: { "$.components..*" }
}
}
text: {
atoms: {
{type: "icon", data: {id: "$.icon,item:icon,classic:$", value: "item:state"}},
{type: "label", data: "$.label,item:label"},
{
metadata: {
if: "$.[?(@.components)]",
then: {type: "group", data: "components:seqno"},
else: {type: "$", data: "item:state"}
}
}
}
}
slider: {
atoms: {
{type: "icon", data: {id: "$.icon,item:icon,classic:$", value: "item:state"}},
{type: "label", data: "$.label,item:label"},
{type: "$", data: "item:state"}
}
}
switch: {
atoms: {
{type: "icon", data: {id: "$.icon,item:icon,classic:$", value: "item:state"}},
{type: "label", data: "$.label,item:label"},
{type: "$", data: "item:state"}
}
}
group: {
atoms: {
{type: "icon", data: {id: "$.icon,item:icon,classic:$", value: "item:state"}},
{type: "label", data: "$.label,item:label"},
{type: "$", data: "components:seqno,item:id"}
}
}
selection: {
atoms: {
{type: "icon", data: {id: "$.icon,item:icon,classic:$", value: "item:state"}},
{type: "label", data: "$.label,item:label"},
{type: "$", data: "$.mappings"}
}
}
Let's analyze each line of the "sitemap" prototype to understand how it works:
When the "smarthome" element is found in the sitemap definition model, the renderer expands this prototype.
The name of the resulting element will be "sitemap".
The type property will be set to "smarthome". The "$" character is a JSONPath reference to the original element in the definition model, which happens to be "smarthome".
data: {id: "$.id", label: "$.label"},
The data property will be set to a JSON, containing an id and a label copied from the original definition element, also using JSONPath syntax.
The layout property does not come form the definition model, it is fixed in this prototype. The value "list" is a reference to the Material Design concept of a list, so this is informing the UI that it should arrange the components of the container as a list:
lang: "$.lang,system:language",
The lang property is passed to the client to indicate the language of the components in the container. It can be used, for instance, to change text alignment or even rearrange the atoms to better suit a language written from right-to-left, such as Hebrew or Arabic. Based on the specification in the prototype, the renderer will first try to read a lang attribute from the source element in the definition model. If absent, then it retrieves the language from the system settings (i.e. using the system: namespace).
components: { "$.components..*" }
The recursive descent operator (..) followed by the wildcard (*) tells the renderer to recursively retrieve all members contained under components (and expand the relevant prototypes as needed).
There are also some atoms under the "text" prototype using a special syntax that is worth explaining here:
{type: "icon", data: {id: "$.icon,item:icon,classic:$", value: "item:state"}},
The icon data will have an id and a value. The id will be the icon attribute in the definition model, or if it's missing then it's the icon attribute on the associated item (via the item: namespace), else it will be the icon "text" from the classic iconset (via the classic: namespace). The value attribute is always the state of the associated item.
metadata: {
if: "$.[?(@.components)]",
then: {type: "group", data: "components:seqno"},
else: {type: "$", data: "item:state"}
}
Once the prototype expander meets an element named "metadata", it knows that the elements immediately below it should not simply be sent to the output, put interpreted in a certain way to decide how the prototype is expanded. On the example above, if there is a member "components" on the definition model, the third (rightmost) atom will be rendered as a "group" atom; if absent, we render it as a regular "text" atom.
We can notice that the "Multimedia" element on our example sitemap does contain components, so it was rendered as a group with data "components:4" (meaning the fourth occurrence of "components" in the definition model). When the user clicks on the arrow on this component, the client makes a REST API call to the server which returns the sitemap view for that specific page, to display the contained components:
{
sitemap: {
type: "smarthome", data: {id: "demo", label: "My home automation"}, layout: "list", lang: "en",
components: {
container: {
type: "implicit", layout: "listcontrol",
components: {
atoms: {
{type: "icon", data: {id: "classic:screen", value: "0"}, style: "width:16px;height:16px"},
{type: "label", data: "Channel"},
{type: "selection", data: {0: "off", 1: "DasErste", 2: "BBC One", 3: "Cartoon Network"}}
},
atoms: {
{type: "icon", data: {id: "classic:soundvolume", value: "15"}, style: "width:16px;height:16px"},
{type: "label", data: "TV Volume"},
{type: "slider", data: "15"}
}
}
}
}
}
}
On a very high level, the main work performed by the SitemapRenderer is:
- Load the definition model and maintain a Java representation of it.
- Load the default style and prototypes that match the sitemap type.
- Convert the definition model into the rendering model by expanding the prototypes until the elements are resolved to the final component types. In this process, it should resolve any references based on the specified namespaces with a separate service that could be called something like NamespaceReferenceResolver.
- Augment the resulting components with the applicable CSS styles.
The namespaces mentioned in this example are:
- smarthome: any types that have no explicit namespace use the sitemap type as the default namespace (so type: "frame" internally resolves as type: "smarthome:frame").
- classic: icon in the classic iconset - it is actually the only namespace actually sent to the client, since all other references are resolved on the server side. The resolver for this namespace knows that it should not be replaced with a value on the server side, but sent as-is to the client. Then the client can make a separate HTTP request to the server to obtain the image represented by the icon.
- system: information about the system (e.g. default language or position).
- item: attributes from the item associated with the component.
- components: references to components in the sitemap. Components are a mandatory member of containers.
Now let's demonstrate "dynamic sitemap" features that match the existing capabilities of the sitemap:
{
smarthome: {
id: "dynamic", label: "My dynamic sitemap",
components: {
selection: {item: "LR_TV_Channel", mappings: {0: "off", 1: "DasErste", 2: "BBC One", 3: "Cartoon Network"}},
slider: {item: "LR_TV_Volume",
visibility: {
or: {
{value: "item:LR_TV_Power.state", eq: "ON"}
{value: "item:LR_TV_Channel.state", noteq: "0"}
}
}
labelcolor: {
{gteq: "50", color: "red"}
{gteq: "15", color: "orange"}
}
}
}
}
Below we can see the resulting rendering model - you will notice that the visibility and labelcolor definitions are implemented by the renderer as specific CSS styles (e.g. "display:none;color:orange"). Since style processing cannot be implemented via prototype expansion, we would need the Java implementation for the NamespaceReferenceResolver to apply the required logic once it sees smarthome:visibility and visibility:labelcolor elements.
{
sitemap: {
type: "smarthome", data: {id: "demo", label: "My home automation"}, layout: "list", lang: "en",
components: {
container: {
type: "implicit", layout: "listcontrol",
components: {
atoms: {
{type: "icon", data: {id: "classic:screen", value: "0"}, style: "width:16px;height:16px"},
{type: "label", data: "Channel"},
{type: "selection", data: {0: "off", 1: "DasErste", 2: "BBC One", 3: "Cartoon Network"}}
},
atoms: {
{type: "icon", data: {id: "classic:soundvolume", value: "15"}, style: "display:none;width:16px;height:16px"},
{type: "label", data: "TV Volume", style: "display:none;color:orange"},
{type: "slider", data: "15", style: "display:none"}
}
}
}
}
}
}
Extension and reuse
One virtue of the proposed solution is the fact that definitions can be added or changed declaratively.
Styles can be adjusted by editing the style file. Alternative style files can also be provided, giving the user the option to select in the UI which theme to use. The API endpoint that lists the available sitemaps can also return the available styles or themes available for each sitemap, for instance:
- smarthome.style: Default theme.
- smarthome-dark.style: Theme with dark background and white text.
- smarthome-tablet.style: Theme with bigger icons and text, optimized for big tablet screens.
New prototypes can be created, and they may also be based on existing prototypes. For instance, I may want to have all my smart bulbs to have the same icon and with intensity changed by predefined mapping options:
lightselection: {
selection: {
item: "$.item", icon: "light", mappings: {0: "Off", 50: "Medium", 100: "Bright"}}
}
When the renderer identifies that "lightselection" is mapped to another prototype "selection", it takes the intermediate structure and apply the second prototype to it, which will then generate the atoms and stop the processing of this element. Now we can use our new prototype on the sitemap definition model:
lightselection: {item: "GF_Hall"},
lightselection: {item: "GF_Dining_Room"}
The rendering model will have fully expanded containers and/or atoms:
components: {
atoms: {
{type: "icon", data: {id: "classic:light", value: "0"}, style: "width:16px;height:16px"},
{type: "label", data: "Hall"},
{type: "selection", data: {0: "Off", 50: "Medium", 100: "Bright"}}
},
atoms: {
{type: "icon", data: {id: "classic:light", value: "50"}, style: "width:16px;height:16px"},
{type: "label", data: "Dining Room"},
{type: "selection", data: {0: "Off", 50: "Medium", 100: "Bright"}}
}
}
Another possible customization via the prototypes would be to convert the standard sitemap layout into a dashboard-like interface: instead of "list" layout on the sitemap and "listcontrol" for the inner containers, the layouts "cardcollection" and "card" could be used respectively to display widgets in a very different way:
The prototype expansion functionality is actually generic enough that it could even be used for other purposes not in the sitemap scope, such as allowing the creation of reusable Item definitions.
Other possible new features that are not supported by the current sitemap could be:
- Defining component visibility based on more than one condition using "and" (the current sitemap only supports "or").
- Creating a vertical navigation sidebar consisting only on Icon atoms, or keeping a component always visible on top regardless of the page the user is in (e.g. an intrusion alert generated by a home security system).
- Decoupling the icon from the item state - the presented prototypes links dynamic icons to simulate the current sitemap implementation, but they can be changed to determine the icon to be displayed based on the state of an item not displayed in the sitemap.
- Disabling a component instead of hiding it.
- Adding a "Webcam pictures" to a sitemap which, once clicked, would open a page with "gridlist" layout to display all photos captured by a webcam in the last 24 hours.
- Associate an URL with a component, so once the user clicks it a web page is displayed on a new window.
- Create custom component types for specialized UIs (e.g. a date picker atom generated declaratively by a new prototype, it's the client that would have the burden to determine how this component should be displayed).
This proposal is still on a very abstract level, and some adjustments would likely be needed once certain issues became more obvious during an implementation attempt. Topics such as SSE and notifications are not addressed here yet. There are also some interesting features that I've left aside here, such as the possibility of using an indoor positioning system (e.g. FIND) to open the sitemap on a page that corresponds to the room the user currently is.
Anyway, I hope it spurs a renewed discussion on the topic so we can continue making progress.
[Updated on: Mon, 26 March 2018 12:30] Report message to a moderator
|
|
| | | |
Goto Forum:
Current Time: Wed Dec 11 00:10:52 GMT 2024
Powered by FUDForum. Page generated in 0.05783 seconds
|