Line data Source code
1 : // Webthing-CPP
2 : // SPDX-FileCopyrightText: 2023-present Benno Waldhauer
3 : // SPDX-License-Identifier: MIT
4 :
5 : #pragma once
6 :
7 : #include <string>
8 : #include <bw/webthing/errors.hpp>
9 : #include <bw/webthing/json_validator.hpp>
10 : #include <bw/webthing/utils.hpp>
11 : #include <bw/webthing/value.hpp>
12 :
13 : namespace bw::webthing {
14 :
15 : template<class T> class Property;
16 :
17 : template<class T>
18 30 : json property_status_message(const Property<T>& property)
19 : {
20 240 : return json({
21 : {"messageType", "propertyStatus"},
22 : {"data", property_value_object(property)}
23 120 : });
24 180 : }
25 :
26 : template<class T>
27 45 : json property_value_object(const Property<T>& property)
28 : {
29 45 : auto value_as_json = property.get_value() ? json(*property.get_value()) : json();
30 225 : return json({{property.get_name(), value_as_json}});
31 180 : }
32 :
33 : class PropertyBase
34 : {
35 : public:
36 :
37 29 : PropertyBase(std::string name, json metadata, bool wraps_double)
38 29 : : name(name)
39 29 : , metadata(metadata)
40 29 : , wraps_double(wraps_double)
41 : {
42 29 : if(this->metadata.type() != json::value_t::object)
43 3 : throw PropertyError("Only json::object is allowed as meta data.");
44 :
45 28 : href = "/properties/" + this->name;
46 32 : }
47 :
48 59 : virtual ~PropertyBase() = default;
49 : virtual json get_property_value_object() const = 0;
50 :
51 17 : json as_property_description() const
52 : {
53 17 : json description = metadata;
54 :
55 17 : if(!description["links"].is_array())
56 17 : description["links"] = json::array();
57 :
58 136 : description["links"] += {{"rel", "property"}, {"href", href_prefix + href}};
59 :
60 17 : return description;
61 119 : }
62 :
63 29 : void set_href_prefix(std::string prefix)
64 : {
65 29 : href_prefix = prefix;
66 29 : }
67 :
68 : std::string get_href() const
69 : {
70 : return href_prefix + href;
71 : }
72 :
73 62 : std::string get_name() const
74 : {
75 62 : return name;
76 : }
77 :
78 1 : json get_metadata() const
79 : {
80 1 : return metadata;
81 : }
82 :
83 24 : template<class T> std::optional<T> get_value() const
84 : {
85 24 : return dynamic_cast<const Property<T>&>(*this).get_value();
86 : }
87 :
88 36 : template<class T> void set_value(T value)
89 : {
90 : try{
91 30 : if(wraps_double && !std::is_same_v<T, double>)
92 1 : return set_value(try_static_cast<double>(value));
93 :
94 35 : auto property = dynamic_cast<Property<T>&>(*this);
95 34 : property.set_value(value);
96 31 : }
97 14 : catch(std::bad_cast&)
98 : {
99 12 : throw PropertyError("Property value type not matching");
100 : }
101 : }
102 :
103 : protected:
104 : std::string name;
105 : std::string href_prefix;
106 : std::string href;
107 : json metadata;
108 : const bool wraps_double;
109 : };
110 :
111 : typedef std::function<void (json)> PropertyChangedCallback;
112 :
113 : template<class T>
114 : class Property : public PropertyBase
115 : {
116 : public:
117 29 : Property(PropertyChangedCallback changed_callback, std::string name, std::shared_ptr<Value<T>> value, json metadata = json::object())
118 : : PropertyBase(name, metadata, std::is_same_v<T, double>)
119 28 : , property_change_callback(changed_callback)
120 31 : , value(value)
121 : {
122 : // Add value change observer to notify the Thing about a property change.
123 28 : if(property_change_callback)
124 58 : this->value->add_observer([&](auto v){property_change_callback(property_status_message(*this));});
125 28 : }
126 :
127 : // Validate new proptery value before setting it.
128 31 : void validate_value(const T& value) const
129 : {
130 31 : if(metadata.contains("readOnly"))
131 : {
132 2 : auto json_ro = metadata["readOnly"];
133 2 : bool read_only = json_ro.is_boolean() && json_ro.template get<bool>();
134 2 : if(read_only)
135 3 : throw PropertyError("Read-only property");
136 2 : }
137 :
138 : try
139 : {
140 35 : validate_value_by_scheme(value, metadata);
141 : }
142 10 : catch(std::exception& ex)
143 : {
144 15 : throw PropertyError("Invalid property value - " + std::string(ex.what()));
145 : }
146 25 : }
147 :
148 15 : json get_property_value_object() const
149 : {
150 15 : return property_value_object(*this);
151 : }
152 :
153 : // Get the current property value.
154 114 : std::optional<T> get_value() const
155 : {
156 114 : return value->get();
157 : }
158 :
159 : // Set the current value of the property.
160 : // throws PropertyError If value could not be set.
161 31 : void set_value(T value)
162 : {
163 31 : this->validate_value(value);
164 25 : this->value->set(value);
165 25 : }
166 :
167 : private:
168 : std::shared_ptr<Value<T>> value;
169 : PropertyChangedCallback property_change_callback;
170 : };
171 :
172 : } // bw::webthing
|