-
-
Notifications
You must be signed in to change notification settings - Fork 428
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
RFC: YAML configuration #3666
Comments
Thinking from the perspective of rule templates and sharing I think allowing it all to be configured in one file would be a nice step towards supporting a bundle of configs (e.g. a collection of Items, Rules, and Widgets). I know the marketplace can't handle that (yet?) but even with git repos and forum posts that would make sharing a little easier. If we could support variables (like Ansible for an example) that could be very powerful from a sharing perspective. But I know, we need to walk before we run.
I don't know the full implication from the developer perspective, but from an end user perspective it sounds like a really good idea. Upgrades are a pain point.
Again, this might be jumping too far ahead, but I've always found it awkward that the only way to develop/test a rule template is to publish it in the marketplace. I would hope that this YamlModelRepository might provide a way to load a rule template locally without going through the marketplace. It would greatly improve the rule template development experience. |
Ok, I understand what you want to achieve. All the stuff monitorting the folders/files and YAML files loading will then be in one unique place in the
I would suggest to keep different files and different folders, like we have today.
That looks like a good idea. That would mean a different listener (different DTO class) for each version ? I can try to adapt my PR to add the class |
It would be nice to be able to put multiple things into one configuration file. You already put the item configuration in But one thing that needs to be improved is how things get loaded. If I define 100 items in the yaml and only change one then the whole 100 items get recreated and loose their state. There should be a very basic check:
Imho this is not needed and should at least be optional defaulting to the newest version. Concerning your example format - I think there is an easier way to model the links. items:
- name: Outdoor_temp
type: Number:Temperature
label: "Outdoor [%.1f °C]"
groupNames:
- gTemperatures
- gOutdoor
- gPersist5
# Option 1: allow link as a shortcut if it's only one link without a profile which makes "link" an optional entry
# User has to either use "link" or "links" but may never use both at the same time
link: "deconz:thing1:channel1"
# Option 2: links takes the channel name as a key and is a mapping
links:
# these two are equivalent
deconz:thing1:channel1:
deconz:thing1:channel1: null
# simple profile is key: value
deconz:thing2:channel2: MyScope:MyProfile
# parametrized profile is key: mapping
deconz:thing2:channel2:
name: MyScope:MyProfile
lower:15 Suggested Metadata example: Note that I reused
items:
- name: window_covering
type: Rollershutter
label: Window Rollershutter"
metadata:
homekit: "WindowCovering" # first line of *.items
homekit: # second line of *.items
value: "WindowCovering"
stop: "true"
stopSameDirection: "true"
|
I think that's a little more challenging than it might seem because the Items get unloaded/deleted when the original .items file gets unloaded. Then when the file gets reloaded the Items get created anew. In order to support this use case:
I believe, with my limited knowledge that would require changes to generic openHAB core stuff far above and beyond just adding a new model listener. That's not an argument for or against it, just pointing out it might be outside the scope of this issue as it will probably impact all the other file formats too.
Shouldn't the config match how the metadata is stored and used and even more importantly how it's shown in the Code tab of MainUI? If the proposal is to make the formatting changed in both places I'm good with that, though it probably means also changing the underlying JSON to support it which widens the impact of the change. Otherwise I think differences between what we see in MainUI and the YAML config files would be a net negative, even if the overall change is a simplification. |
Yes - it's more than just adding another listener but not as much as it seems. I agree that it's easier to just attach things to the existing code but if the goal is that in the long term yaml replaces the custom dsl formats then imho it makes sense to invest a little bit more time to remove the existing quirks and limitations.
Why not keep the existing code as is, freeze it and phase it out e.g. in OH5 or OH6?
Yes - showing the metadata the same way should be the goal. E.g. this homekit:
value: "WindowCovering"
stop: "true"
stopSameDirection: "true" will still be saved as {
"value": "WindowCovering",
"config": {
"stop": "true",
"stopSameDirection": "true"
}
} |
This is not yet included in the implementation I proposed in PR #3659 but it is very easy to add, I just have to compare old and new object. I am going to add this check. |
In my proposed implementation (coming from @J-N-K initial POC), this is no more a problem for the items in one file. The code detects the removed, added and updated items. |
One thing I have not yet implemented but could be done easily is to add a check (and reject) if several "things" with the same id are present either in the same file or different files. |
Awesome! 🚀 🚀 🚀
That's how I would have expected it.
Along with a proper error message this would also be a nice feature |
My understanding though is that the JSON to YAML in MainUI is generic. Therefore what you see in the MainUI code tab would be
which would be different from the YAML in the file. So either there would have to be a special branch of code in MainUI to transform the JSON first or manipulate the YAML just for the metadata code page or the JSON would have change to become
Both are significant amounts of work and the latter would be a breaking change. |
It is now implemented. |
It is now implemented for objects in the same file. I kept the code simple and the warning log only mentions that "X elements elements with same ids". |
What will happen? Will the last definition overwrite all previous ones? items:
item_name:
type: Rollershutter
label: Window Rollershutter" I like this format significantly better than the obj with the name attribute.
It's a one-liner in python and I suspect even with proper error handling it'll be less than 15 lines of (java) code. But aligning the GUI with files is not the only way this can be done: |
If you are talking about coding the MainUI page to show it differently, it's not Python nor Java but JavaScript and it means introducing logic to detect when the metadata is being shown and going down a different path than the standard path used everywhere else in MainUI. That's significantly more than just a one liner. If you are talking about changing the JSON representation of metadata, it may be a one liner, but it's a one liner that impacts everything in OH that reads and uses metadata today, including potentially end user's rules.
And users will see a YAML posted to the forum and blindly copy and paste it into the code tab somewhere and cry when it doesn't work because it's different for some unapparent reason. And yes, shame on them. But it still takes time from those who provide support on the forum dealing with the problem. I'm all for changing it either way or not changing it at all (it's just not that big of a deal). I am vehemently against there being two different YAML representations of metadata (or any other entity) though. Tiny little differences like these become land mines for users. |
No - the only place where it is shown is in the code tab, because that's the only place in the MainUI where the user sees the yaml.
This is not what I am suggesting
If the file format is (significantly?) different than the format code tab it'll become clear because not only the metadata representation is different but also the whole item representation.
As someone who works with lots of files it is a big deal. I can only appeal to all the hard working maintainers to not rush the new file format and not make it blindly a yaml representation of the json. At least evaluate some structures and let's think about what is nice to edit, share and work with. |
Such file containing objects with same ids is considered as invalid and all the file is ignored. |
IMHO, what is urgent for OH4.0 is to have the core feature for the future and the YAML file syntax for the semantic tags. That is the perimeter of my PR. |
Sounds good. I think it's enough to show the duplicate ids, the number doesn't matter.
Sounds like a plan 👍 |
And that code calls a generic library to convert the JSON to YAML. That'd no longer a generic call.
And as someone who spends probably as much time helping users on the forum that you do writing HABApp I still maintain that I have concerns. If the YAML is different in the two places it's going to be a big impact on my time and UI users' time. But I'll state it again, I'm not against changing the format. But I've seen no one chime in here and say "I'll make sure the YAML in these files and the YAML on the code tab match if we decide to differ." It's not even clear whether @ghys would allow such a custom set of logic in MainUI in the first place no matter how easy it is (he's rejected similar proposals for Blockly and his reasons are valid). All I request is that we ensure it is the same in both places. How or why that is enabled is not my concern.
I agree. But it's also worth the additional effort to consider the impacts to UI users, support on the forums, and maintainers. Decisions made here do have impacts on all these other people too. To reprise my statement when I've had this same discussion on openhab-js, it's not fair to impose on our least technically able users to advantage our most capable users. A balance needs to be reached and compromises on both sides need to be accommodated. Having two different YAML representations, be they similar or very different, is an imposition on those least capable users as it adds yet another thing they need to be aware of and figure out. It's an imposition on those who want to learn how to create file based configs because they can't just take what they see in MainUI and map that to the YAML file. It's na imposition to the documentation writers to have to deal with two different YAML syntaxes and document that there is a difference in the first place. And it's an imposition on the helpers on the forum who will have to deal with problems when users just blindly assume that the formats would be the same. Why wouldn't they? It's a reasonable assumption. |
Absolutely agreed! When we introduce a new YAML file format, the overall structure of the entities should match with the code tab of the UI to allow copy-and-paste.
I — as an UI maintainer together with Yannick — don‘t see a large problem with changing the format of the UIs code tab, even though that would invalidate examples from the forum (I have to admit that I haven‘t seen that much YAML code examples on the forum for sharing stuff with other users). However we really should keep in mind while designing a YAML file format that it also needs to be implemented in the UI that way, preferably without a ton of extra code. I also want to hear Yannick’s opinion before deciding anything on this side. Feel free to ping me and ask for help and advice for the UI side. |
That call will still be generic. The steps would be:
I agree with you - that would be ideal and should be the goal. These aren't mutually exclusive and again I agree with you and the points you make.
If we decide on a new format and we want to use it in the UI why not expose it through the rest api. |
That is not my argument. My argument is very much that making the formats different increases complexity, adds confusion, and increases the work for lots and lots of people (users, maintainers, documenters, and helpers) outside this discussion. And I take issue with the "very seldomly". It's only very seldomly used today because it's awkward and not well supported. If the formats are the same, I suspect it would become very frequently.
And your argument and mine are not against each other unless the format just changes to benefit only those who only use text files at the expense of everyone else. Again, I don't care what the format becomes. I only care that it's the same in the files and in the UI. That's it. Having two different YAML representations for stuff in OH would be a huge mistake in my somewhat informed opinion. And note: I'm really only talking about the stuff that you'd actually see in MainUI's code tabs. So, for example, all you see today for metadata is the "value" and the "config" so I only care that those are represented the same. The stuff that comes above those in the YAML hierarchy in the text file is not part of what I'm talking about. That stuff isn't shown in MainUI as YAML so I have no concerns about how that's formatted. |
kind: Thing
apiVersion: v1
UID: exec:command:MyThing1
name: MyThing1
thingTypeUID: exec:command ...
---
kind: Item
apiVersion: v1
name: MyItem1
type: Number ...
---
apiVersion: v1
kind: Link ...
---
apiVersion: v1
kind: Rule
uid: MyRule1 ...
|
This issue has been mentioned on openHAB Community. There might be relevant details there: https://community.openhab.org/t/oh4-transformationservice-of-type-js-is-unavailable/148786/16 |
This issue has been mentioned on openHAB Community. There might be relevant details there: https://community.openhab.org/t/what-is-the-most-common-ui-you-are-using/149096/31 |
@rkoshak : as I understand, @J-N-K considers my PR #3659 cannot be merged due to one of your suggestion here. In case you would like to have one unique file containing potentially any kind of OH data (is that really your request ?), that means it should contain many optional parts. When parsing the YAML file with a reference class, how do you specify in the reference class that each kind of objects is optional ? Is it technically possible with the YAML library we are relying on at your opinion @J-N-K ? I remember I tried to make the Today, we have files of each kind in different folders, something well structured. I understand you would like to allow a file containing items even in the folder "things" for example ?! That would require to scan all folders to find items, all folders to find sitemaps, ... etc Would you agree to keep things simple and avoid adding a lot of complexity for a very low user profit ? Then we could maybe merge my PR. One time again, I would also really appreciate @J-N-K if you confirm or not if it is technically doable with the YAML library we are using. |
Maybe I was not clear in what I believe does not work. Imagine I have a YAML containing a list of tags and I try to parse it with a class defining a list of tags and a list of items, the parsing will fail. The error will tell me that my file does not contain any item. |
I'm not 100% sure, but looking at FasterXML/jackson-dataformats-text#66 it should be possible. The file should be parsed to an array of This is very close to my original idea, because it allows examples containing items things for a binding together in a single file that can be used for both, textual configuration and import in the UI. |
Imho the multi document approach is not as good as having a node in the document especially e.g. for items. things:
exec:command:MyThing1:
thingTypeUID: exec:command ...
apiVersion: v1
items:
MyItem1:
apiVersion: v1
type: Number
MyItem2:
apiVersion: v1
type: Number
MyItem3:
apiVersion: v1
type: Number
transformations:
de.map: # an idea could also be to specify the type with a dot
type: map # or as a dedicated type entry, but then it would make sense to push the values one node down to have no reserved keys
open: offen
# or type of transformation and then name, e.g.
map:
de:
open: offen
|
This is a slightly different idea, but it might be integrated into this topic somehow? #3808 |
HABApp offers something similar, bit you can automatically generate items and set parameters based on your things. However I think the most easy way would be to provide a generator which then creates the new file format. |
That is close to what I'm asking for I think. The opportunities I see if this were possible include:
I am no expert on defining scemas (or what ever they are called in YAML) and frankly wasn't aware YAML even has them. But, based on my experience writing Ansible playbooks and tasks as well as my own sensorReporter, I know this is at least possible from the YAML side of things. You'd define a set of "root" tags: thing, item, widget, rule, metadata (perhaps). Then you'd have schemas to define what's allowed and required under each of this root tags like @spacemanspiff2007 describes. I don't know what library you are using nor how it works. I don't know if what Ansible does is odd or not. But my idea comes from my experience with Ansible. There has to be a way to make fields optional though. Every config format I've ever encountered, from XML to JSON to INI /Properties, etc. all support optional fields/elements. I cannot believe YAML as a format doesn't. It'd be next to useless for a lot of things.
Isn't the whole point of the version field to allow old configs to still work unchanged? I thought the whole version proposal was so that the configs could be marked with what version they are and it won't fail if it doesn't conform to the new version because the parser is smart enough to know the differences.
I would do away with the separate folders entirely, come up with a new folder structure, or leave it to the end users to decide what folder structure (or not) they want. There would be one config parser that would load all the files and kick out to the right subparser based on that first element. At least that's how I would design it and how I've seen it design in the past on other projects.
I don't think it's low profit. But I also don't want to stand in the way of PRs as long as the door is open to readdress this later. I do think it's important and valuable. |
Just to chime in, by adding the mapping with items:
item_name:
type: Rollershutter
label: Window Rollershutter"
............
things:
thing_name:
type: Rollershutter
label: Window Rollershutter"
............ |
I've thought about this some more and have created some examples for items and persistence. For some values there are two forms how things can be expressed:
After loading the yaml all short forms should be converted to the extended form in a post processing step. I tried to apply the following design principles:
Notes: Persistence, transformations and items is already very good. The syntax is very easy to learn and comes natural after writing a couple of definitions. Suggestion: # ----------------------------------------------------------------------------------------------------------------------
# things
# ----------------------------------------------------------------------------------------------------------------------
things:
# --------------------------------------------------------------------------------------------------------------------
# Example 1:
# Thing network:device:webcam "Webcam" @ "Living Room" [ hostname="192.168.0.2", timeout="5000", ... ]
# --------------------------------------------------------------------------------------------------------------------
network:device:webcam:
label: "Webcam"
location: "Living Room"
config:
hostname: "192.168.0.2"
timeout: 5000
# settings should be used if we finally separate the thing configuration from device configuration
# settings change something on the device, config defines how the thing connects to the device
settings:
...
# --------------------------------------------------------------------------------------------------------------------
# Example 2:
#
# Bridge hue:bridge:mybridge [ ipAddress="192.168.3.123" ] {
# Thing 0210 bulb1 [ lightId="1" ]
# Thing 0210 bulb2 [ lightId="2" ]
# }
# --------------------------------------------------------------------------------------------------------------------
hue:bridge:mybridge:
bridge: True # optional key, default is False or empty. "true or yes" (case insensitive) defines this as a bridge
config:
ipAddress: "192.168.3.123"
hue:0210:mybridge:bulb1:
bridge: hue:bridge:mybridge # references the defined bridge
config:
lightId: 1
hue:0210:mybridge:bulb2:
bridge: hue:bridge:mybridge
config:
lightId: 2
# --------------------------------------------------------------------------------------------------------------------
# Example 3:
#
# Bridge mqtt:broker:MyMQTTBroker [ host="192.168.178.50", secure=false, username="MyUserName", password="MyPassword"] {
# Thing topic sonoff_Dual_Thing "Light_Dual" @ "Sonoff" {
# Channels:
# Type switch : PowerSwitch1 [ stateTopic="stat/sonoff_dual/POWER1" , commandTopic="cmnd/sonoff_dual/POWER1", on="ON", off="OFF"]
# Trigger String : customChannel1 [ configParameter="Value" ]
# State Number : customChannel2 []
# }
# }
# --------------------------------------------------------------------------------------------------------------------
mqtt:broker:MyMQTTBroker:
bridge: True # optional key, default is False
config:
host: "192.168.178.50"
secure: false
username: MyUserName
password: MyPassword
sonoff_Dual_Thing:
bridge: mqtt:broker:MyMQTTBroker
label: "Light_Dual"
location: "Sonoff"
channels:
- type: switch
kind: type # optional, can be one of "trigger", "state" and "type", default is "state"
name: PowerSwitch1
config:
stateTopic: 'stat/sonoff_dual/POWER1'
commandTopic: 'cmnd/sonoff_dual/POWER1'
on: 'ON'
off: 'OFF'
- type: String
kind: trigger # optional, can be one of "trigger", "state" and "type", default is "state"
name: customChannel1
config:
configParameter: "value"
- type: Number
name: customChannel2
# --------------------------------------------------------------------------------------------------------------------
# Example 4:
#
# Thing yahooweather:weather:losangeles [ location=2442047, unit="us", refresh=120 ] {
# Channels:
# Type temperature: my_yesterday_temperature "Yesterday's Temperature"
# }
# --------------------------------------------------------------------------------------------------------------------
yahooweather:weather:losangeles:
config:
location: 2442047
unit: us
refresh: 120
channels:
- type: temperature
kind: type
name: my_yesterday_temperature
label: Yesterday's Temperature
# ----------------------------------------------------------------------------------------------------------------------
# items
# ----------------------------------------------------------------------------------------------------------------------
items:
# --------------------------------------------------------------------------------------------------------------------
# Example 1:
# Number Livingroom_Temperature "Temperature [%.1f °C]" <switch> (Livingroom, Temperatures)
# --------------------------------------------------------------------------------------------------------------------
Livingroom_Temperature:
type: Number
label: "Temperature [%.1f °C]"
icon: switch # todo: REST reports this as category, the docs refer to it as icon. Choose one and stick to it
groups:
- Livingroom
- Temperatures
# --------------------------------------------------------------------------------------------------------------------
# Example 2:
# Switch Garage_Gate {channel="xxx", autoupdate="false", expire="1h,command=STOP"}
# --------------------------------------------------------------------------------------------------------------------
Garage_Gate:
type: Switch
channel: xxx
# Both autoupdate and expire are core functionalities, so they should be set directly on the item even though
# they are implemented through metadata
autoupdate: false
expire: "1h"
# two forms for expire, one a single string for posting UNDEF and one extended form for posting arbitrary things
# This is something that has to be checked by the parser, e.g. the short form with only a string should automatically
# be transformed to the extended form like this (with the time key-value and one of command/update key values)
expire:
time: "1h"
command: "STOP" # sends a command
update: 'STOP' # posts an update
# another option would to always require the extended form which could be written like that
expire: {time: '1h'}
# --------------------------------------------------------------------------------------------------------------------
# Example 3:
# Number:Temperature Outdoor_Temperature { channel="openweathermap:weather-and-forecast:api:local:current#temperature" }
# --------------------------------------------------------------------------------------------------------------------
Outdoor_Temperature:
type: Number
measurement: Temperature # alternative to measurement could be "kind"
# short form if it's only one channel with no profile
channel: "openweathermap:weather-and-forecast:api:local:current#temperature"
# extended form which allows multiple channels with profiles
channels:
openweathermap:weather-and-forecast:api:local:current#temperature: {}
# --------------------------------------------------------------------------------------------------------------------
# Example 4:
# Switch Outdoor_Temperature_Low_Alert { channel="openweathermap:weather-and-forecast:api:local:current#temperature"
# [profile="system:hysteresis", lower="0 °C", inverted="true"] }
# --------------------------------------------------------------------------------------------------------------------
Outdoor_Temperature_Low_Alert:
type: Switch
channels:
openweathermap:weather-and-forecast:api:local:current#temperature:
# multiple profiles can be specified by name
system:hysteresis:
lower: "0 °C"
inverted: "true"
# --------------------------------------------------------------------------------------------------------------------
# Example 5:
# Switch MyFan "My Fan" { homekit="Fan.v2", alexa="Fan" }
# --------------------------------------------------------------------------------------------------------------------
MyFan:
type: Switch
label: My Fan
metadata:
homekit: Fan.v2
alexa: Fan # short form of metadata
# --------------------------------------------------------------------------------------------------------------------
# Example 6:
# Switch MyFan "My Fan" { homekit="Fan.v2", alexa="Fan" [ type="oscillating", speedSteps=3 ] }
# --------------------------------------------------------------------------------------------------------------------
MyFan2:
type: Switch
label: My Fan2
metadata:
homekit: Fan.v2 # short form of metadata
alexa: # extended form of metadata
Fan:
type: oscillating
speedSteps: 3
# --------------------------------------------------------------------------------------------------------------------
# Example 7:
# String Livingroom_Light_Connection "Livingroom Ceiling Light [MAP(error.map):%s]" <myerror>
# --------------------------------------------------------------------------------------------------------------------
Livingroom_Light_Connection:
type: String
label: # extended form to use the transformation
label: "Livingroom Ceiling Light [%s]"
transformation: error.map
icon: myerror # todo: REST reports this as category, the docs refer to it as icon. Choose one and stick to it
# ----------------------------------------------------------------------------------------------------------------------
# transformations
# ----------------------------------------------------------------------------------------------------------------------
transformations:
de:
type: map
default: 'default value' # It makes sense to define the default here and not in the values with an empty key which is very confusing
transformation:
open: open
# ----------------------------------------------------------------------------------------------------------------------
# persistence
# ----------------------------------------------------------------------------------------------------------------------
persistence:
# note that strategies and filter is pulled up above the used persistence service
strategies:
# user given name
every30s: "0/30 * * * * ?"
# Filters {
# exactlySomeState : = "ARMED", "UNARMED"
# fivepercent : > % 5
# }
filters:
exactlySomeState:
type: equals
values: # the key name is "values" here
- ARMED
- UNARMED
fivepercent:
type: relative threshold
value: 10 # key name is "value" for single value
# normal nodes
rrd4j:
- strategies:
- everyChange
- restoreOnStartup
filters:
- exactlySomeState
groups: # we use a dedicated group field instead of the *gRRD4j notation
- gRRD4j
items:
- MyItem1
# collapsed nodes
other_service:
- strategies: [everyChange, every30s]
groups: [gOtherService]
items: [MyItem2, MyItem2]
|
This issue has been mentioned on openHAB Community. There might be relevant details there: |
|
This issue has been mentioned on openHAB Community. There might be relevant details there: |
This issue has been mentioned on openHAB Community. There might be relevant details there: https://community.openhab.org/t/changing-items-metadata-very-frequently/155172/19 |
This issue has been mentioned on openHAB Community. There might be relevant details there: https://community.openhab.org/t/changing-items-metadata-very-frequently/155172/22 |
As discussed in #3636 we need configuration for user-defined tags. It was discussed that an easy to understand standard configuration format would be preferable over creating a new DSL. It was also suggested in different issues that it would be nice to be able to copy&paste from UI to files. Main UI uses a YAML representation for things and items, so this would be good choice. We already have a feature for Jackson's YAML parser, so no additional dependencies are needed for that.
However, there are a few things to discuss:
I would suggest to add a
YamlModelRepository
(similar to our XTextModelRepository
) that is an OSGi service that encapsulates all YAML processing and file processing. Consumer are OSGi services that implementYamlModelListener<T>
whereT
is a DTO that describes the format. The listeners are injected into theYamlModelRepository
and whenever a matching model (identified by the root name) is found the listener is notified.Example listener (not fully implemented):
Example DTO:
and the file:
The text was updated successfully, but these errors were encountered: