diff --git a/create-eclipse-project.sh b/create-eclipse-project.sh index 72363d5c..003a9e84 100755 --- a/create-eclipse-project.sh +++ b/create-eclipse-project.sh @@ -1,6 +1,6 @@ #!/bin/bash -PYTHON_PATH=/opt/ros/indigo/lib/python2.7/dist-packages +PYTHON_PATH=/home/matzman/tmp/ros_catkin_ws/install_isolated/lib/python2.7/dist-packages # delete old files rm -rf devel devel_isolated build build_isolated install install_isolated diff --git a/src/hos_cosgi_devicerepository/src/hos/cosgi/service/devicerepository/DeviceRepositoryServiceImpl.cpp b/src/hos_cosgi_devicerepository/src/hos/cosgi/service/devicerepository/DeviceRepositoryServiceImpl.cpp index e688b330..6565178e 100644 --- a/src/hos_cosgi_devicerepository/src/hos/cosgi/service/devicerepository/DeviceRepositoryServiceImpl.cpp +++ b/src/hos_cosgi_devicerepository/src/hos/cosgi/service/devicerepository/DeviceRepositoryServiceImpl.cpp @@ -62,7 +62,6 @@ void DeviceImpl::handleCommandMsg(const Variant& msg, const timestamp_t& stamp) void DeviceImpl::sendCommand(const Variant& cmd) { - std::lock_guard lock(objMutex); parent->sendDeviceCommand(this, cmd); } diff --git a/src/hos_cosgi_sitemap/examples/test01.json b/src/hos_cosgi_sitemap/examples/test01.json index 0fb560e7..c0ee7f29 100644 --- a/src/hos_cosgi_sitemap/examples/test01.json +++ b/src/hos_cosgi_sitemap/examples/test01.json @@ -1,40 +1,291 @@ { "type": "sitemap/sitemap_template", "title": "TestApp", - "rootpage": { - "type": "sitemap/sitemap_template#/definitions/page", - "title": "Root Page", - "elements": [ - { - "type": "sitemap/sitemap_template#/definitions/text_element", - "label": "Switch0 Text", - "icon" : "some_icon01", - "format" : "Format: %1%", - "device": { - "id": "dummySwitch0", - "pointer": "/switch/value" - } - }, { - "type": "sitemap/sitemap_template#/definitions/switch_element", - "label": "Switch0", - "icon" : "some_icon02", - "format" : "bla: %1%", - "device": { - "id": "dummySwitch0", - "pointer": "/switch/value" - } - }, { - "type": "sitemap/sitemap_template#/definitions/slider_element", - "label": "Slider0", - "icon" : "some_icon03", - "device": { - "id": "dummyDimmer0", - "pointer": "/dimmer/value" + "rootpage": "page01", + "pages": { + "page01": { + "type": "sitemap/sitemap_template#/definitions/page", + "title": "Root Page", + "elements": [ + { + "type": "sitemap/sitemap_template#/definitions/frame_element", + "label": "Group 0", + "icon" : "some_icon01", + "elements" : [ + { + "type": "sitemap/sitemap_template#/definitions/text_element", + "label": "Switch0", + "icon" : "some_icon01", + "format" : "Value: %1%", + "linkedPage" : "page02", + "device": { + "id": "dummySwitch0", + "pointer": "/switch/value" + } + }, { + "type": "sitemap/sitemap_template#/definitions/switch_element", + "label": "Switch0", + "icon" : "some_icon02", + "linkedPage" : "page02", + "device": { + "id": "dummySwitch0", + "pointer": "/switch/value" + }, + "commandTemplate": { + "switch": { + "set": null + } + }, + "commandValuePointer": "/switch/set" + }, { + "type": "sitemap/sitemap_template#/definitions/text_element", + "label": "Slider0", + "icon" : "some_icon01", + "format" : "Value: %1%", + "device": { + "id": "dummyDimmer0", + "pointer": "/dimmer/value" + } + }, { + "type": "sitemap/sitemap_template#/definitions/slider_element", + "label": "Slider0", + "icon" : "some_icon03", + "device": { + "id": "dummyDimmer0", + "pointer": "/dimmer/value" + }, + "min": 0, + "max": 1, + "step": 0.1, + "commandTemplate": { + "dimmer": { + "set": null + } + }, + "commandValuePointer": "/dimmer/set" + } + ] }, - "min": 0, - "max": 1, - "step": 0.1 - } - ] + { + "type": "sitemap/sitemap_template#/definitions/frame_element", + "label": "Group 1", + "icon" : "some_icon01", + "elements" : [ + { + "type": "sitemap/sitemap_template#/definitions/text_element", + "label": "Switch1", + "icon" : "some_icon01", + "format" : "Value: %1%", + "device": { + "id": "dummySwitch1", + "pointer": "/switch/value" + } + }, { + "type": "sitemap/sitemap_template#/definitions/switch_element", + "label": "Switch1", + "icon" : "some_icon02", + "device": { + "id": "dummySwitch1", + "pointer": "/switch/value" + }, + "commandTemplate": { + "switch": { + "set": null + } + }, + "commandValuePointer": "/switch/set" + }, { + "type": "sitemap/sitemap_template#/definitions/text_element", + "label": "Slider1", + "icon" : "some_icon01", + "format" : "Value: %1%", + "device": { + "id": "dummyDimmer1", + "pointer": "/dimmer/value" + } + }, { + "type": "sitemap/sitemap_template#/definitions/slider_element", + "label": "Slider1", + "icon" : "some_icon03", + "device": { + "id": "dummyDimmer1", + "pointer": "/dimmer/value" + }, + "min": 0, + "max": 1, + "step": 0.1, + "commandTemplate": { + "dimmer": { + "set": null + } + }, + "commandValuePointer": "/dimmer/set" + } + ] + }, + { + "type": "sitemap/sitemap_template#/definitions/frame_element", + "label": "Group 2", + "icon" : "some_icon01", + "elements" : [ + { + "type": "sitemap/sitemap_template#/definitions/text_element", + "label": "Switch2", + "icon" : "some_icon01", + "format" : "Value: %1%", + "device": { + "id": "dummySwitch2", + "pointer": "/switch/value" + } + }, { + "type": "sitemap/sitemap_template#/definitions/switch_element", + "label": "Switch2", + "icon" : "some_icon02", + "device": { + "id": "dummySwitch2", + "pointer": "/switch/value" + }, + "commandTemplate": { + "switch": { + "set": null + } + }, + "commandValuePointer": "/switch/set" + }, { + "type": "sitemap/sitemap_template#/definitions/text_element", + "label": "Slider2", + "icon" : "some_icon01", + "format" : "Value: %1%", + "device": { + "id": "dummyDimmer2", + "pointer": "/dimmer/value" + } + }, { + "type": "sitemap/sitemap_template#/definitions/slider_element", + "label": "Slider2", + "icon" : "some_icon03", + "device": { + "id": "dummyDimmer2", + "pointer": "/dimmer/value" + }, + "min": 0, + "max": 1, + "step": 0.1, + "commandTemplate": { + "dimmer": { + "set": null + } + }, + "commandValuePointer": "/dimmer/set" + } + ] + }, + { + "type": "sitemap/sitemap_template#/definitions/frame_element", + "label": "Group 3", + "icon" : "some_icon01", + "elements" : [ + { + "type": "sitemap/sitemap_template#/definitions/text_element", + "label": "Switch3", + "icon" : "some_icon01", + "format" : "Value: %1%", + "device": { + "id": "dummySwitch3", + "pointer": "/switch/value" + } + }, { + "type": "sitemap/sitemap_template#/definitions/switch_element", + "label": "Switch3", + "icon" : "some_icon02", + "device": { + "id": "dummySwitch3", + "pointer": "/switch/value" + }, + "commandTemplate": { + "switch": { + "set": null + } + }, + "commandValuePointer": "/switch/set" + }, { + "type": "sitemap/sitemap_template#/definitions/text_element", + "label": "Slider3", + "icon" : "some_icon01", + "format" : "Value: %1%", + "device": { + "id": "dummyDimmer3", + "pointer": "/dimmer/value" + } + }, { + "type": "sitemap/sitemap_template#/definitions/slider_element", + "label": "Slider3", + "icon" : "some_icon03", + "device": { + "id": "dummyDimmer3", + "pointer": "/dimmer/value" + }, + "min": 0, + "max": 1, + "step": 0.1, + "commandTemplate": { + "dimmer": { + "set": null + } + }, + "commandValuePointer": "/dimmer/set" + } + ] + } + ] + }, + "page02": { + "type": "sitemap/sitemap_template#/definitions/page", + "title": "Page 2", + "elements": [ + { + "type": "sitemap/sitemap_template#/definitions/frame_element", + "label": "Some Text", + "icon" : "some_icon01", + "elements" : [ + { + "type": "sitemap/sitemap_template#/definitions/text_element", + "label": "Switch0 Text", + "icon" : "some_icon01", + "format" : "blabla: %1%", + "device": { + "id": "dummyDimmer1", + "pointer": "/dimmer/value" + }, + "linkedPage" : "page03" + } + ] + } + ] + }, + "page03": { + "type": "sitemap/sitemap_template#/definitions/page", + "title": "Page 3", + "elements": [ + { + "type": "sitemap/sitemap_template#/definitions/frame_element", + "label": "Some Text 2", + "icon" : "some_icon01", + "elements" : [ + { + "type": "sitemap/sitemap_template#/definitions/text_element", + "label": "Switch2 Text", + "icon" : "some_icon01", + "format" : "blabla: %1%", + "device": { + "id": "dummyDimmer3", + "pointer": "/dimmer/value" + }, + "linkedPage" : "page01" + } + ] + } + ] + } } } diff --git a/src/hos_cosgi_sitemap/examples/test02.json b/src/hos_cosgi_sitemap/examples/test02.json new file mode 100644 index 00000000..f6fa14a4 --- /dev/null +++ b/src/hos_cosgi_sitemap/examples/test02.json @@ -0,0 +1,166 @@ +{ + "type": "sitemap/sitemap_template", + "title": "Hue Lights App", + "rootpage": "page01", + "pages": { + "page01": { + "type": "sitemap/sitemap_template#/definitions/page", + "title": "My Room", + "elements": [ + { + "type": "sitemap/sitemap_template#/definitions/frame_element", + "label": "Lightstrips 1", + "icon" : "some_icon01", + "elements" : [ + { + "type": "sitemap/sitemap_template#/definitions/switch_element", + "label": "Lightstrips 1 Switch", + "icon" : "some_icon02", + "device": { + "id": "huelight1", + "pointer": "/switch/value" + }, + "commandTemplate": { + "switch": { + "set": null + } + }, + "commandValuePointer": "/switch/set" + }, { + "type": "sitemap/sitemap_template#/definitions/slider_element", + "label": "Lightstrips 1 Slider", + "icon" : "some_icon03", + "device": { + "id": "huelight1", + "pointer": "/brightness/value" + }, + "min": 0, + "max": 1, + "step": 0.1, + "commandTemplate": { + "brightness": { + "set": null + } + }, + "commandValuePointer": "/brightness/set" + } + ] + }, { + "type": "sitemap/sitemap_template#/definitions/frame_element", + "label": "Lightstrips 2", + "icon" : "some_icon01", + "elements" : [ + { + "type": "sitemap/sitemap_template#/definitions/switch_element", + "label": "Lightstrips 2 Switch", + "icon" : "some_icon02", + "device": { + "id": "huelight2", + "pointer": "/switch/value" + }, + "commandTemplate": { + "switch": { + "set": null + } + }, + "commandValuePointer": "/switch/set" + }, { + "type": "sitemap/sitemap_template#/definitions/slider_element", + "label": "Lightstrips 2 Slider", + "icon" : "some_icon03", + "device": { + "id": "huelight2", + "pointer": "/brightness/value" + }, + "min": 0, + "max": 1, + "step": 0.1, + "commandTemplate": { + "brightness": { + "set": null + } + }, + "commandValuePointer": "/brightness/set" + } + ] + }, { + "type": "sitemap/sitemap_template#/definitions/frame_element", + "label": "ZimmerLampe", + "icon" : "some_icon01", + "elements" : [ + { + "type": "sitemap/sitemap_template#/definitions/switch_element", + "label": "ZimmerLampe Switch", + "icon" : "some_icon02", + "device": { + "id": "huelight3", + "pointer": "/switch/value" + }, + "commandTemplate": { + "switch": { + "set": null + } + }, + "commandValuePointer": "/switch/set" + }, { + "type": "sitemap/sitemap_template#/definitions/slider_element", + "label": "ZimmerLampe Slider", + "icon" : "some_icon03", + "device": { + "id": "huelight3", + "pointer": "/brightness/value" + }, + "min": 0, + "max": 1, + "step": 0.1, + "commandTemplate": { + "brightness": { + "set": null + } + }, + "commandValuePointer": "/brightness/set" + } + ] + }, { + "type": "sitemap/sitemap_template#/definitions/frame_element", + "label": "TischLampe", + "icon" : "some_icon01", + "elements" : [ + { + "type": "sitemap/sitemap_template#/definitions/switch_element", + "label": "TischLampe Switch", + "icon" : "some_icon02", + "device": { + "id": "huelight4", + "pointer": "/switch/value" + }, + "commandTemplate": { + "switch": { + "set": null + } + }, + "commandValuePointer": "/switch/set" + }, { + "type": "sitemap/sitemap_template#/definitions/slider_element", + "label": "TischLampe Slider", + "icon" : "some_icon03", + "device": { + "id": "huelight4", + "pointer": "/brightness/value" + }, + "min": 0, + "max": 1, + "step": 0.1, + "commandTemplate": { + "brightness": { + "set": null + } + }, + "commandValuePointer": "/brightness/set" + } + ] + } + ] + } + } +} diff --git a/src/hos_cosgi_sitemap/include/hos_cosgi_sitemap/hos/cosgi/service/sitemap/SitemapService.h b/src/hos_cosgi_sitemap/include/hos_cosgi_sitemap/hos/cosgi/service/sitemap/SitemapService.h index 4c196642..3fc4bb47 100644 --- a/src/hos_cosgi_sitemap/include/hos_cosgi_sitemap/hos/cosgi/service/sitemap/SitemapService.h +++ b/src/hos_cosgi_sitemap/include/hos_cosgi_sitemap/hos/cosgi/service/sitemap/SitemapService.h @@ -59,6 +59,8 @@ public: virtual std::map> getSitemaps() noexcept = 0; virtual std::vector getSitemapNames() noexcept = 0; + virtual std::weak_ptr getDefaultSitemap() noexcept = 0; + }; diff --git a/src/hos_cosgi_sitemap/include/hos_cosgi_sitemap/hos/cosgi/service/sitemap/sitemap_template/SitemapTemplate.h b/src/hos_cosgi_sitemap/include/hos_cosgi_sitemap/hos/cosgi/service/sitemap/sitemap_template/SitemapTemplate.h index fcc69213..13210af7 100644 --- a/src/hos_cosgi_sitemap/include/hos_cosgi_sitemap/hos/cosgi/service/sitemap/sitemap_template/SitemapTemplate.h +++ b/src/hos_cosgi_sitemap/include/hos_cosgi_sitemap/hos/cosgi/service/sitemap/sitemap_template/SitemapTemplate.h @@ -20,20 +20,78 @@ class SitemapService; namespace sitemaptemplate { - +class SitemapTemplate; class Page; class Element; class AtomicElement; class CompositeElement; class Device; +/* + * Sitemap event type. + * + **/ +enum class SitemapEventType : int { + ALL_EVENTS = 1, + SITEMAP_ELEMENT_CHANGED = 2, + SITEMAP_INVALIDATED = 3 +}; + +/** + * Base class for sitemap events. + */ +class SitemapEvent { +public: + typedef std::chrono::system_clock::time_point timestamp_t; + + SitemapEventType eventType; + + SitemapTemplate* const sitemap; + + const timestamp_t timestamp; + + SitemapEvent(SitemapTemplate* sitemap, const SitemapEventType& type, const timestamp_t& timestamp) + : eventType(type), sitemap(sitemap), timestamp(timestamp) {} +}; + + +/** + * sitemap element changed event. + */ +class SitemapElementChangedEvent + : public SitemapEvent { +public: + std::vector> elements; + + SitemapElementChangedEvent(SitemapTemplate* sitemap, const timestamp_t& timestamp) + : SitemapEvent(sitemap,SitemapEventType::SITEMAP_ELEMENT_CHANGED, timestamp) {} +}; + + +/** + * sitemap invalidated event. + */ +class SitemapInvalidatedEvent + : public SitemapEvent { +public: + SitemapInvalidatedEvent(SitemapTemplate* sitemap, const timestamp_t& timestamp) + : SitemapEvent(sitemap,SitemapEventType::SITEMAP_INVALIDATED, timestamp) {} +}; class SitemapTemplate - : public virtual interface::Object { + : public virtual interface::Observer, + public virtual interface::ObservableObject { public: virtual SitemapService* getParent() noexcept = 0; + virtual size_t pageCount() noexcept = 0; + virtual std::weak_ptr getPage(const std::string& name) noexcept = 0; + virtual std::vector> getPages(const std::string& name) noexcept = 0; virtual std::weak_ptr getRootPage() noexcept = 0; + virtual const std::string& getRootPageName() noexcept = 0; + virtual std::weak_ptr getPageElement(size_t& uid) noexcept = 0; + + virtual const std::string& getName() noexcept = 0; virtual libvariant::Variant renderSitemap() = 0; }; @@ -44,7 +102,7 @@ class Page : public virtual interface::Object { public: virtual SitemapTemplate* getSiteMap() noexcept = 0; - virtual Page* getParent() noexcept = 0; + virtual const std::string& getName() noexcept = 0; virtual size_t elementCount() noexcept = 0; virtual std::weak_ptr getElement(size_t index) noexcept = 0; @@ -99,7 +157,8 @@ public: virtual const std::string& getFormat() noexcept = 0; virtual std::weak_ptr getDevice() noexcept = 0; virtual const libvariant::Variant& getAdditionalParams() noexcept = 0; - virtual std::weak_ptr getLinkedToPage() noexcept = 0; + virtual std::weak_ptr getLinkedPage() noexcept = 0; + virtual const std::string& getLinkedPageName() noexcept = 0; }; @@ -107,10 +166,12 @@ public: class Device : public virtual interface::Object { public: + virtual std::weak_ptr getParent() noexcept = 0; virtual const std::string& getDeviceID() noexcept = 0; virtual std::weak_ptr getDevice() noexcept = 0; virtual const std::string& getPointer() noexcept = 0; virtual bool deviceExists() noexcept = 0; + virtual void sendCommand(const libvariant::Variant& cmd) = 0; }; diff --git a/src/hos_cosgi_sitemap/launch/setup_sitemap.launch b/src/hos_cosgi_sitemap/launch/setup_sitemap.launch index c96e6881..05a0a67a 100644 --- a/src/hos_cosgi_sitemap/launch/setup_sitemap.launch +++ b/src/hos_cosgi_sitemap/launch/setup_sitemap.launch @@ -1,6 +1,7 @@ + diff --git a/src/hos_cosgi_sitemap/schema/sitemap/sitemap_template.json b/src/hos_cosgi_sitemap/schema/sitemap/sitemap_template.json index e875d8cd..570fe90f 100644 --- a/src/hos_cosgi_sitemap/schema/sitemap/sitemap_template.json +++ b/src/hos_cosgi_sitemap/schema/sitemap/sitemap_template.json @@ -13,12 +13,24 @@ "type": "string", "description": "" }, + "default": { + "type": "boolean", + "description": "" + }, + "pages": { + "type": "object", + "patternProperties": { + "[A-Za-z0-9_-]+": { + "$ref": "#/definitions/page" + } + } + }, "rootpage": { - "$ref": "#/definitions/page" + "type": "string" } }, "additionalProperties": false, - "required": ["title", "rootpage"], + "required": ["title", "pages", "rootpage"], "definitions": { @@ -109,7 +121,7 @@ "icon": { "type": "string" }, - "page": { + "linkedPage": { "type": "string" } } diff --git a/src/hos_cosgi_sitemap/src/hos/cosgi/service/sitemap/SitemapServiceImpl.cpp b/src/hos_cosgi_sitemap/src/hos/cosgi/service/sitemap/SitemapServiceImpl.cpp index 60cf2275..0f578849 100644 --- a/src/hos_cosgi_sitemap/src/hos/cosgi/service/sitemap/SitemapServiceImpl.cpp +++ b/src/hos_cosgi_sitemap/src/hos/cosgi/service/sitemap/SitemapServiceImpl.cpp @@ -103,13 +103,38 @@ void SitemapServiceImpl::addSitemap(const std::string& name, XmlRpc::XmlRpcValue throw std::runtime_error("title is not a string."); } std::string title = titleRaw.AsString(); - auto rootpage = value["rootpage"]; - if (!rootpage.IsMap()) { - throw std::runtime_error("rootpage is not a map."); + auto rootpageRaw = value["rootpage"]; + if (!rootpageRaw.IsString()) { + throw std::runtime_error("rootpage is not a string."); + } + std::string rootpage = rootpageRaw.AsString(); + bool isDefault = false; + if (value.Contains("default")) { + auto isDefaultRaw = value["default"]; + if (!isDefaultRaw.IsBool()) { + throw std::runtime_error("default is not a boolean."); + } + isDefault = isDefaultRaw.IsTrue(); + } + auto pages = value["pages"]; + if (!pages.IsMap()) { + throw std::runtime_error("pages is not a map."); + } + std::shared_ptr st = std::make_shared(*this, name, title); + for (auto page : pages.AsMap()) { + st->addPage(page.first, page.second); + } + if (!st->setRootPageName(rootpage)) { + ROS_WARN_STREAM("Sitemap " << st->getName() << " has unknown root page " << rootpage << "."); } - std::shared_ptr st = std::make_shared(*this, title); - st->createPage(rootpage); sitemapTemplates.insert({name, st}); + if (!sitemapDefault || isDefault) { + sitemapDefault = st; + } + repoService->addObserver(devicerepository::DeviceEventType::ALL_EVENTS, st.get()); + if (name.compare("default") == 0) { + ROS_WARN("There exists a sitemap with name \"default\"."); + } } else { std::stringstream ss; ss << "Could not validate sitemap config value: " << sr.PrettyPrintMessage(); @@ -146,6 +171,10 @@ std::vector SitemapServiceImpl::getSitemapNames() noexcept { } +std::weak_ptr SitemapServiceImpl::getDefaultSitemap() noexcept { + return std::static_pointer_cast(sitemapDefault); +} + } /* namespace sitemap */ } /* namespace service */ } /* namespace cosgi */ diff --git a/src/hos_cosgi_sitemap/src/hos/cosgi/service/sitemap/SitemapServiceImpl.h b/src/hos_cosgi_sitemap/src/hos/cosgi/service/sitemap/SitemapServiceImpl.h index 0ba91520..28c23e7f 100644 --- a/src/hos_cosgi_sitemap/src/hos/cosgi/service/sitemap/SitemapServiceImpl.h +++ b/src/hos_cosgi_sitemap/src/hos/cosgi/service/sitemap/SitemapServiceImpl.h @@ -38,6 +38,8 @@ private: bool initialized = false; std::map> sitemapTemplates; + std::shared_ptr sitemapDefault; + void init() noexcept; void addSitemap(const std::string& name, XmlRpc::XmlRpcValue& rawValue); @@ -72,6 +74,8 @@ public: virtual std::vector getSitemapNames() noexcept override; + virtual std::weak_ptr getDefaultSitemap() noexcept override; + /* Internal */ diff --git a/src/hos_cosgi_sitemap/src/hos/cosgi/service/sitemap/sitemap_template/SitemapTemplateImpl.cpp b/src/hos_cosgi_sitemap/src/hos/cosgi/service/sitemap/sitemap_template/SitemapTemplateImpl.cpp index c4e955e1..1805c47d 100644 --- a/src/hos_cosgi_sitemap/src/hos/cosgi/service/sitemap/sitemap_template/SitemapTemplateImpl.cpp +++ b/src/hos_cosgi_sitemap/src/hos/cosgi/service/sitemap/sitemap_template/SitemapTemplateImpl.cpp @@ -1,5 +1,6 @@ #include "SitemapTemplateImpl.h" +#include namespace hos { namespace cosgi { @@ -9,54 +10,6 @@ namespace sitemaptemplate { -std::atomic ElementImpl::nextID; - -std::shared_ptr SitemapTemplateImpl::createPage(const libvariant::Variant& param, PageImpl* parent) { - std::shared_ptr newPage; - if (!parent && rootPage) { - throw std::runtime_error("There is already a root page."); - } - auto titleRaw = param["title"]; - if (!titleRaw.IsString()) { - throw std::runtime_error("title is not a string."); - } - std::string title = titleRaw.AsString(); - auto elements = param["elements"]; - if (!elements.IsList()) { - throw std::runtime_error("elements is not a list."); - } - newPage = std::make_shared(*this, title, parent); - for ( auto e : elements.AsList()) { - newPage->createElement(e); - } - if (!parent) { - rootPage = newPage; - } - return newPage; -} - - -std::shared_ptr SitemapTemplateImpl::createDevice(const libvariant::Variant& param) { - auto idRaw = param["id"]; - if (!idRaw.IsString()) { - throw std::runtime_error("id is not a string."); - } - std::string id = idRaw.AsString(); - std::string pointer; - auto pointerRaw = param["pointer"]; - if (!pointerRaw.IsString()) { - throw std::runtime_error("pointer is not a string."); - } else if (pointerRaw.IsString()) { - pointer = pointerRaw.AsString(); - } - auto devRepo = parent.getDeviceRepository(); - auto device = devRepo->getDevice(id); - std::shared_ptr newDevice = std::make_shared(id, device, pointer); - return newDevice; -} - - - namespace { struct _elementTypeInfo { @@ -75,26 +28,85 @@ _elementTypeInfo elementTypeInfo[] = { } +std::atomic ElementImpl::nextID; +std::shared_ptr SitemapTemplateImpl::addPage(const std::string& name, const libvariant::Variant& pageRaw) { + auto titleRaw = pageRaw["title"]; + if (!titleRaw.IsString()) { + throw std::runtime_error("title is not a string."); + } + std::string title = titleRaw.AsString(); + auto elements = pageRaw["elements"]; + if (!elements.IsList()) { + throw std::runtime_error("elements is not a list."); + } + auto newPage = std::make_shared(*this, name, title); + for (auto element : elements.AsList()) { + newPage->addElement(element); + } + pages.insert({name, newPage}); + return newPage; +} -std::vector> PageImpl::getElements() noexcept { - std::vector> retval; - for (auto i : elements) { - retval.push_back(std::static_pointer_cast(i)); +std::weak_ptr SitemapTemplateImpl::getPage(const std::string& name) noexcept { + auto page = pages.find(name); + if (page == pages.end()) { + return std::weak_ptr(); + } else { + return page->second; + } +} + +std::vector> SitemapTemplateImpl::getPages(const std::string& name) noexcept { + std::vector> retval; + for (auto page : pages) { + retval.push_back(page.second); } return retval; } -std::vector> CompositeElementImpl::getElements() noexcept { - std::vector> retval; - for (auto i : elements) { - retval.push_back(std::static_pointer_cast(i)); +std::weak_ptr SitemapTemplateImpl::getRootPage() noexcept { + return getPage(rootPageName); +} + +bool SitemapTemplateImpl::setRootPageName(const std::string& name) noexcept { + rootPageName = name; + if (pages.find(name) != pages.end()) { + return true; } - return retval; + return false; +} + +std::weak_ptr SitemapTemplateImpl::getPageElement(size_t& uid) noexcept { + auto r = pageElements.find(uid); + if (r != pageElements.end()) { + return r->second; + } + return std::weak_ptr(); +} + +std::shared_ptr SitemapTemplateImpl::createDevice(const libvariant::Variant& param, const libvariant::Variant& commandTemplate, const std::string& commandValuePointer) { + auto idRaw = param["id"]; + if (!idRaw.IsString()) { + throw std::runtime_error("id is not a string."); + } + std::string id = idRaw.AsString(); + std::string pointer; + auto pointerRaw = param["pointer"]; + if (!pointerRaw.IsString()) { + throw std::runtime_error("pointer is not a string."); + } else if (pointerRaw.IsString()) { + pointer = pointerRaw.AsString(); + } + auto devRepo = parent.getDeviceRepository(); + auto device = devRepo->getDevice(id); + std::shared_ptr newDevice = std::make_shared(id, device, pointer, commandTemplate, commandValuePointer); + devices.insert({id, newDevice}); + return newDevice; } -std::shared_ptr PageImpl::createElement(const libvariant::Variant& param, CompositeElementImpl* parent) { +std::shared_ptr SitemapTemplateImpl::createElement(PageImpl& page, const libvariant::Variant& param, CompositeElementImpl* parent) { std::shared_ptr newElement; libvariant::Variant addParams = param.Copy(); auto typeRaw = param["type"]; @@ -134,13 +146,23 @@ std::shared_ptr PageImpl::createElement(const libvariant::Variant& } } if (info->isComposite) { + auto elements = param["elements"]; + if (elements.IsNull()) { + throw std::runtime_error("elements is missing."); + } else if (!elements.IsList()) { + throw std::runtime_error("elements is not an array."); + } + addParams.Erase("elements"); switch (info->type) { case ElementType::Frame: - newElement = std::make_shared(*this, info->typeString, label, icon, parent); + newElement = std::make_shared(page, info->typeString, label, icon, parent); break; default: throw std::runtime_error((boost::format("Registered element type %1% is unknown.") % info->typeName).str()); } + for (auto element : elements.AsList()) { + std::static_pointer_cast(newElement)->addElement(element); + } } else { std::string format; if (param.Contains("format")) { @@ -158,38 +180,120 @@ std::shared_ptr PageImpl::createElement(const libvariant::Variant& if (!deviceRaw.IsMap()) { throw std::runtime_error("device is not a map."); } else { - device = sitemap.createDevice(deviceRaw); + libvariant::Variant commandTemplate; + std::string commandValuePointer; + if (param.Contains("commandTemplate")) { + commandTemplate = param["commandTemplate"]; + addParams.Erase("commandTemplate"); + } + if (param.Contains("commandValuePointer") && param["commandValuePointer"].IsString()) { + commandValuePointer = param["commandValuePointer"].AsString(); + addParams.Erase("commandValuePointer"); + } + device = createDevice(deviceRaw, commandTemplate, commandValuePointer); addParams.Erase("device"); } } - libvariant::Variant pageRaw; - if (param.Contains("page")) { - pageRaw = param["page"]; - if (!pageRaw.IsMap()) { - throw std::runtime_error("page is not a map."); + libvariant::Variant linkedPageRaw; + if (param.Contains("linkedPage")) { + linkedPageRaw = param["linkedPage"]; + if (!linkedPageRaw.IsString()) { + throw std::runtime_error("linkedPage is not a string."); } else { - addParams.Erase("page"); + addParams.Erase("linkedPage"); } } std::shared_ptr newAElement; switch (info->type) { case ElementType::Text: - newAElement = std::make_shared(*this, info->typeString, label, icon, format, device, addParams, parent); + newAElement = std::make_shared(page, info->typeString, label, icon, format, device, addParams, parent); break; case ElementType::Slider: - newAElement = std::make_shared(*this, info->typeString, label, icon, format, device, addParams, parent); + newAElement = std::make_shared(page, info->typeString, label, icon, format, device, addParams, parent); break; case ElementType::Switch: - newAElement = std::make_shared(*this, info->typeString, label, icon, format, device, addParams, parent); + newAElement = std::make_shared(page, info->typeString, label, icon, format, device, addParams, parent); break; default: throw std::runtime_error((boost::format("Registered element type %1% is unknown.") % info->typeName).str()); } - if (pageRaw.IsMap()) { - newAElement->createLinkedToPage(pageRaw); + if (device) { + device->setParent(newAElement); + } + if (linkedPageRaw.IsString()) { + newAElement->setLinkedPage(linkedPageRaw.AsString()); } newElement = newAElement; } + pageElements.insert({newElement->getID(), newElement}); + return newElement; +} + +void SitemapTemplateImpl::notificationReceived(observable_type* o, const devicerepository::DeviceEventType& key, const devicerepository::DeviceEvent* e) noexcept { + SitemapElementChangedEvent event(this, std::chrono::system_clock::now()); + switch (e->eventType) { + case devicerepository::DeviceEventType::DEVICE_ADDED: + { + auto range = devices.equal_range(e->device->getDeviceID()); + for (auto i = range.first; i != range.second && i != devices.end(); i++) { + auto dev = i->second.lock(); + if (!dev->deviceExists()) { + dev->resolveDevice(); + event.elements.push_back(dev->getParent()); + } + } + } + break; + case devicerepository::DeviceEventType::DEVICE_REMOVED: + { + auto range = devices.equal_range(e->device->getDeviceID()); + for (auto i = range.first; i != range.second && i != devices.end(); i++) { + auto dev = i->second.lock(); + dev->resolveDevice(); + event.elements.push_back(dev->getParent()); + } + } + break; + case devicerepository::DeviceEventType::STATE_CHANGED: + { + auto range = devices.equal_range(e->device->getDeviceID()); + for (auto i = range.first; i != range.second && i != devices.end(); i++) { + auto dev = i->second.lock(); + event.elements.push_back(dev->getParent()); + } + } + break; + default: + break; + } + if (event.elements.size() > 0) { + notifyObservers(SitemapEventType::ALL_EVENTS, SitemapEventType::SITEMAP_ELEMENT_CHANGED, &event); + notifyObservers(SitemapEventType::SITEMAP_ELEMENT_CHANGED, &event); + } +} + + + + +std::vector> PageImpl::getElements() noexcept { + std::vector> retval; + for (auto i : elements) { + retval.push_back(std::static_pointer_cast(i)); + } + return retval; +} + +std::vector> CompositeElementImpl::getElements() noexcept { + std::vector> retval; + for (auto i : elements) { + retval.push_back(std::static_pointer_cast(i)); + } + return retval; +} + + +std::shared_ptr PageImpl::addElement(const libvariant::Variant& param, CompositeElementImpl* parent) { + std::shared_ptr newElement = sitemap.createElement(*this, param, parent); if (!parent) { elements.push_back(newElement); } @@ -198,33 +302,40 @@ std::shared_ptr PageImpl::createElement(const libvariant::Variant& -std::shared_ptr CompositeElementImpl::createElement(const libvariant::Variant& param) { - std::shared_ptr newElement = page.createElement(param, this); +std::shared_ptr CompositeElementImpl::addElement(const libvariant::Variant& param) { + std::shared_ptr newElement = page.addElement(param, this); elements.push_back(newElement); return newElement; } -std::shared_ptr AtomicElementImpl::createLinkedToPage(const libvariant::Variant& param) { - if (linkedToPage) { - throw std::runtime_error("There is already a linked page."); +bool AtomicElementImpl::setLinkedPage(const std::string& pagename) noexcept { + linkedPageName = pagename; + if (!this->page.getSiteMap()->getPage(linkedPageName).expired()) { + return true; } - linkedToPage = page.sitemap.createPage(param, &page); - return linkedToPage; + return false; } libvariant::Variant SitemapTemplateImpl::renderSitemap() { libvariant::Variant retval = libvariant::Variant::Map(); retval["title"] = title; - retval["rootpage"] = rootPage->renderSitemap(); + retval["name"] = name; retval["type"] = "sitemap/sitemap"; + retval["rootpage"] = rootPageName; + auto tmp = libvariant::Variant::Map(); + for (auto page : pages) { + tmp[page.first] = page.second->renderSitemap(); + } + retval["pages"] = tmp; return retval; } libvariant::Variant PageImpl::renderSitemap() { libvariant::Variant retval = libvariant::Variant::Map(); retval["title"] = title; + retval["name"] = name; libvariant::Variant elements = libvariant::Variant::List(); for (auto i : this->elements) { elements.Append(i->renderSitemap()); @@ -246,20 +357,27 @@ libvariant::Variant CompositeElementImpl::renderSitemap() { } retval["elements"] = elements; retval["type"] = typeString; + retval["uid"] = id; return retval; } + +std::weak_ptr AtomicElementImpl::getLinkedPage() noexcept { + return this->page.getSiteMap()->getPage(linkedPageName); +} + libvariant::Variant AtomicElementImpl::renderSitemap() { libvariant::Variant retval = libvariant::Variant::Map(); retval["label"] = label; if (!icon.empty()) { retval["icon"] = icon; } - if (linkedToPage) { - retval["page"] = linkedToPage->renderSitemap(); + if (!linkedPageName.empty()) { + retval["linkedPage"] = linkedPageName; } retval["value"] = _getDeviceValue(); retval["type"] = typeString; + retval["uid"] = id; retval.Merge(additionalParams); return retval; } @@ -267,7 +385,7 @@ libvariant::Variant AtomicElementImpl::renderSitemap() { libvariant::Variant AtomicElementImpl::_getDeviceValue() { libvariant::Variant devState; - if (device->deviceExists()) { + if (device && device->deviceExists()) { try { devState = device->getDevice().lock()->getCurrentState(); if (!device->getPointer().empty()) { @@ -280,7 +398,11 @@ libvariant::Variant AtomicElementImpl::_getDeviceValue() { devState = "NaN"; } if (!format.empty()) { - devState = (boost::format(format) % devState.AsString()).str(); + try { + devState = (boost::format(format) % devState.AsString()).str(); + } catch (boost::io::format_error& e) { + devState = format; + } } return devState; } @@ -321,6 +443,21 @@ libvariant::Variant SwitchElementImpl::_getDeviceValue() { return devState; } +void DeviceImpl::resolveDevice() noexcept { + if (!parent.expired()) { + auto devRepo = parent.lock()->getPage()->getSiteMap()->getParent()->getDeviceRepository(); + device = devRepo->getDevice(deviceID); + } +} + +void DeviceImpl::sendCommand(const libvariant::Variant& cmd) { + if (!device.expired() && commandTemplate.IsMap() && !commandValuePointer.empty()) { + std::cout << "DeviceImpl::sendCommand: " << libvariant::SerializeJSON(cmd) << std::endl; + auto msg = commandTemplate.Copy(); + msg.SetPath(commandValuePointer, cmd); + device.lock()->sendCommand(msg); + } +} } // namespace sitemaptemplate } // namespace sitemap diff --git a/src/hos_cosgi_sitemap/src/hos/cosgi/service/sitemap/sitemap_template/SitemapTemplateImpl.h b/src/hos_cosgi_sitemap/src/hos/cosgi/service/sitemap/sitemap_template/SitemapTemplateImpl.h index babebc55..b65f887e 100644 --- a/src/hos_cosgi_sitemap/src/hos/cosgi/service/sitemap/sitemap_template/SitemapTemplateImpl.h +++ b/src/hos_cosgi_sitemap/src/hos/cosgi/service/sitemap/sitemap_template/SitemapTemplateImpl.h @@ -3,6 +3,8 @@ // hos includes #include +#include +#include #include "../SitemapServiceImpl.h" #include // libvariant @@ -15,6 +17,22 @@ #include +namespace std { +/** + * Specialization of std::hash for DeviceEventType. + * + * Required by std::map. + */ +template <> +class hash { +public : + size_t operator()(const hos::cosgi::service::sitemap::sitemaptemplate::SitemapEventType& x) const { + return static_cast(x); + } +}; +} // namespace std + + namespace hos { namespace cosgi { namespace service { @@ -35,20 +53,37 @@ class DeviceImpl; class SitemapTemplateImpl : public virtual SitemapTemplate, - public implementation::Object { + public implementation::ObservableObject { private: SitemapServiceImpl& parent; + std::string name; std::string title; - std::shared_ptr rootPage; + std::map> pages; + std::map> pageElements; + std::string rootPageName; + std::multimap> devices; public: - SitemapTemplateImpl(SitemapServiceImpl& parent, const std::string& title) : parent(parent), title(title) {} + SitemapTemplateImpl(SitemapServiceImpl& parent, const std::string& name, const std::string& title) + : parent(parent), name(name), title(title) {} - std::shared_ptr createPage(const libvariant::Variant& param, PageImpl* parent = nullptr); - std::shared_ptr createDevice(const libvariant::Variant& param); + virtual void notificationReceived(observable_type* o, const devicerepository::DeviceEventType& key, const devicerepository::DeviceEvent* e) noexcept override; + + std::shared_ptr addPage(const std::string& name, const libvariant::Variant& page); + bool setRootPageName(const std::string& name) noexcept; + std::shared_ptr createDevice(const libvariant::Variant& param, const libvariant::Variant& commandTemplate, const std::string& commandValuePointer); + std::shared_ptr createElement(PageImpl& page, const libvariant::Variant& param, CompositeElementImpl* parent); virtual SitemapService* getParent() noexcept override { return dynamic_cast(&parent); } - virtual std::weak_ptr getRootPage() noexcept override { return std::static_pointer_cast(rootPage); }; + virtual size_t pageCount() noexcept override { return pages.size(); } + virtual std::weak_ptr getPage(const std::string& name) noexcept override; + virtual std::vector> getPages(const std::string& name) noexcept override; + virtual std::weak_ptr getRootPage() noexcept override; + virtual const std::string& getRootPageName() noexcept override { return rootPageName; } + virtual std::weak_ptr getPageElement(size_t& uid) noexcept override; + + + virtual const std::string& getName() noexcept { return name; }; virtual libvariant::Variant renderSitemap() override; }; @@ -61,18 +96,18 @@ class PageImpl friend class AtomicElementImpl; private: SitemapTemplateImpl& sitemap; - PageImpl* parent; + std::string name; std::string title; std::vector> elements; public: - PageImpl(SitemapTemplateImpl& sitemap, const std::string& title, PageImpl* parent = nullptr) - : sitemap(sitemap), parent(parent), title(title) {} + PageImpl(SitemapTemplateImpl& sitemap, const std::string& name, const std::string& title) + : sitemap(sitemap), name(name), title(title) {} virtual SitemapTemplate* getSiteMap() noexcept override { return static_cast(&sitemap); } - virtual Page* getParent() noexcept override { return static_cast(parent); } + virtual const std::string& getName() noexcept { return name; } - std::shared_ptr createElement(const libvariant::Variant& param, CompositeElementImpl* parent = nullptr); + std::shared_ptr addElement(const libvariant::Variant& param, CompositeElementImpl* parent = nullptr); virtual size_t elementCount() noexcept override { return elements.size(); } virtual std::weak_ptr getElement(size_t index) noexcept override @@ -135,7 +170,7 @@ public: CompositeElementImpl* parent = nullptr) : ElementImpl(page, true, etype, typeStr, parent, label, icon) {} - std::shared_ptr createElement(const libvariant::Variant& param); + std::shared_ptr addElement(const libvariant::Variant& param); virtual size_t elementCount() noexcept override { return elements.size(); } virtual std::weak_ptr getElement(size_t index) noexcept override { return std::static_pointer_cast(elements.at(index)); } @@ -153,7 +188,7 @@ protected: std::string format; std::shared_ptr device; libvariant::Variant additionalParams; - std::shared_ptr linkedToPage; + std::string linkedPageName; virtual libvariant::Variant _getDeviceValue(); @@ -163,12 +198,13 @@ public: CompositeElementImpl* parent = nullptr) : ElementImpl(page, false, etype, typeStr, parent, label, icon), format(format), device(device), additionalParams(additionalParams) {} - std::shared_ptr createLinkedToPage(const libvariant::Variant& param); + bool setLinkedPage(const std::string& pagename) noexcept; virtual const std::string& getFormat() noexcept override { return format; } virtual std::weak_ptr getDevice() noexcept override; virtual const libvariant::Variant& getAdditionalParams() noexcept override { return additionalParams; } - virtual std::weak_ptr getLinkedToPage() noexcept override { return linkedToPage; } + virtual std::weak_ptr getLinkedPage() noexcept override; + virtual const std::string& getLinkedPageName() noexcept override { return linkedPageName; } virtual libvariant::Variant renderSitemap() override; }; @@ -218,15 +254,24 @@ private: std::string deviceID; std::weak_ptr device; std::string pointer; + std::weak_ptr parent; + libvariant::Variant commandTemplate; + std::string commandValuePointer; public: - DeviceImpl(const std::string& deviceID, std::weak_ptr device, const std::string& pointer) - : deviceID(deviceID), device(device), pointer(pointer) {} + DeviceImpl(const std::string& deviceID, std::weak_ptr device, const std::string& pointer, const libvariant::Variant& commandTemplate, const std::string& commandValuePointer) + : deviceID(deviceID), device(device), pointer(pointer), commandTemplate(commandTemplate), commandValuePointer(commandValuePointer) {} + + virtual std::weak_ptr getParent() noexcept override { return parent; } + void setParent(std::weak_ptr parent) noexcept { this->parent = parent; } + void resolveDevice() noexcept; + void setDevice(std::weak_ptr device) noexcept { this->device = device; } virtual const std::string& getDeviceID() noexcept override { return deviceID; } virtual std::weak_ptr getDevice() noexcept override { return device; } virtual const std::string& getPointer() noexcept override { return pointer; } virtual bool deviceExists() noexcept override { return !device.expired(); } + virtual void sendCommand(const libvariant::Variant& cmd) override; }; diff --git a/src/hos_cosgi_sitemap/src/hos/cosgi/service/sitemap/webresource/SitemapHandler.cpp b/src/hos_cosgi_sitemap/src/hos/cosgi/service/sitemap/webresource/SitemapHandler.cpp index cd91e517..5800d22f 100644 --- a/src/hos_cosgi_sitemap/src/hos/cosgi/service/sitemap/webresource/SitemapHandler.cpp +++ b/src/hos_cosgi_sitemap/src/hos/cosgi/service/sitemap/webresource/SitemapHandler.cpp @@ -85,14 +85,23 @@ void SitemapHandler::__handleInfo(Poco::Net::HTTPServerRequest &req, Poco::Net:: for (auto s : sitemaps) { result.Append(s); } + Variant message = Variant::Map(); + message["sitemaps"] = result; + auto dsm = service->getDefaultSitemap(); + if (!dsm.expired()) { + message["default"] = dsm.lock()->getName(); + } - handleResponse(req, resp, form, result); + handleResponse(req, resp, form, message); // Single sitemap mode } else { // Get sitemap std::string sitemapName = pathPieces[matchPieces.size()]; auto sitemap = service->getSitemap(sitemapName); + if (sitemap.expired() && sitemapName.compare("default") == 0) { + sitemap = service->getDefaultSitemap(); + } if (!sitemap.expired()) { // Get path from url and append pointer parameter std::stringstream path; @@ -108,6 +117,15 @@ void SitemapHandler::__handleInfo(Poco::Net::HTTPServerRequest &req, Poco::Net:: } // Render sitemap Variant result = sitemap.lock()->renderSitemap(); + std::string pathStr = path.str(); + if (!pathStr.empty()) { + if (result.HasPath(pathStr)) { + result = result.GetPath(pathStr); + } else { + throw webserver::webresource::WebResourceHandlerException((boost::format("Unknown path \"%1%\" for sitemap \"%2%\".") % path % sitemapName).str(), + Poco::Net::HTTPResponse::HTTPStatus::HTTP_NOT_FOUND); + } + } // Return device info handleResponse(req, resp, form, result); } else { @@ -119,14 +137,131 @@ void SitemapHandler::__handleInfo(Poco::Net::HTTPServerRequest &req, Poco::Net:: void SitemapHandler::__handleCommand(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp, SitemapAPIMethod method, Net::HTMLForm& form) { - throw webserver::webresource::WebResourceHandlerException("ToDo. Implement that shit.", - Poco::Net::HTTPResponse::HTTPStatus::HTTP_NOT_IMPLEMENTED); + std::vector pathPieces, matchPieces; + matchUri.getPathSegments(matchPieces); + Poco::URI uri(req.getURI()); + uri.getPathSegments(pathPieces); + + // No sitemap given, error + if (pathPieces.size() <= matchPieces.size()) { + throw webserver::webresource::WebResourceHandlerException("Missing sitemap.", + Poco::Net::HTTPResponse::HTTPStatus::HTTP_BAD_REQUEST); + } + + // Get sitemap and do stuff + std::string sitemapName = pathPieces[matchPieces.size()]; + std::weak_ptr sitemap = service->getSitemap(sitemapName); + if (!sitemap.expired()) { + + Variant result; + std::string msg; + if (form.has("value")) { + msg = form.get("value"); + } + if (!msg.empty()) { + result = libvariant::DeserializeYAML(msg); + if (result.Contains("eventtype")) { + auto eventtype = result["eventtype"].AsString(); + if (eventtype == "sitemap_element_command") { + auto uid = result["uid"].AsUnsigned(); + auto value = result["value"]; + auto pe = sitemap.lock()->getPageElement(uid); + if (!pe.expired()) { + auto spe = pe.lock(); + if (!spe->isComposite()) { + auto ae = std::dynamic_pointer_cast(spe); + auto dev = ae->getDevice(); + if (!dev.expired()) { + dev.lock()->sendCommand(value); + handleResponse(req, resp, form, std::string("OK: Command executed")); + } else { + throw webserver::webresource::WebResourceHandlerException("No device associated with element.", + Poco::Net::HTTPResponse::HTTPStatus::HTTP_INTERNAL_SERVER_ERROR); + } + } else { + throw webserver::webresource::WebResourceHandlerException("Cannot send a command to a composite element.", + Poco::Net::HTTPResponse::HTTPStatus::HTTP_BAD_REQUEST); + } + } else { + throw webserver::webresource::WebResourceHandlerException((boost::format("Couldn't find element uid \"%1%\"") % uid).str(), + Poco::Net::HTTPResponse::HTTPStatus::HTTP_NOT_FOUND); + } + } else { + throw webserver::webresource::WebResourceHandlerException((boost::format("Unknown eventtype \"%1%\"") % eventtype).str(), + Poco::Net::HTTPResponse::HTTPStatus::HTTP_BAD_REQUEST); + } + } else { + throw webserver::webresource::WebResourceHandlerException("Missing eventtype.", + Poco::Net::HTTPResponse::HTTPStatus::HTTP_BAD_REQUEST); + } + } else { + throw webserver::webresource::WebResourceHandlerException("Missing msg.", + Poco::Net::HTTPResponse::HTTPStatus::HTTP_BAD_REQUEST); + } + } else { + throw webserver::webresource::WebResourceHandlerException((boost::format("Could not find sitemap \"%1%\"") % sitemapName).str(), + Poco::Net::HTTPResponse::HTTPStatus::HTTP_NOT_FOUND); + } } void SitemapHandler::__handleEvent(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp, Net::HTMLForm& form, bool websocket) { - throw webserver::webresource::WebResourceHandlerException("ToDo. Implement that shit.", - Poco::Net::HTTPResponse::HTTPStatus::HTTP_NOT_IMPLEMENTED); + std::vector pathPieces, matchPieces; + matchUri.getPathSegments(matchPieces); + Poco::URI uri(req.getURI()); + uri.getPathSegments(pathPieces); + + // No sitemap given, error + if (pathPieces.size() <= matchPieces.size()) { + throw webserver::webresource::WebResourceHandlerException("Missing sitemap.", + Poco::Net::HTTPResponse::HTTPStatus::HTTP_BAD_REQUEST); + } + + // Get sitemap and do stuff + std::string sitemapName = pathPieces[matchPieces.size()]; + std::weak_ptr sitemap = service->getSitemap(sitemapName); + if (!sitemap.expired()) { + // Get client ID + std::string clientID; + if (form.has("clientid")) { + clientID = form.get("clientid"); + } else if (req.has("Client-Id")) { + clientID = req.get("Client-Id"); + } else if (req.has("X-Client-Id")) { + clientID = req.get("X-Client-Id"); + } + // Get path from url and append pointer parameter + std::stringstream path; + for (int i = matchPieces.size() + 2; i < pathPieces.size(); i++) { + path << "/" << pathPieces[i]; + } + std::string pointer; + if (getPointerParameter(pointer, form)) { + if (pointer.size() > 0 && pointer[0] != '/') { + path << "/"; + } + path << pointer; + } + // Get event queue + auto eventQueue = parent->getEventQueue(clientID, service); + if (!eventQueue->setHandler(this, &req, &resp)) { + throw webserver::webresource::WebResourceHandlerException((boost::format("Client id \"%1%\" already in use.") % clientID).str(), + Poco::Net::HTTPResponse::HTTPStatus::HTTP_BAD_REQUEST); + } + std::cout << "Got EventQueue \"" << eventQueue->clientID << "\"" << std::endl; + eventQueue->checkRegistration(sitemaptemplate::SitemapEventType::ALL_EVENTS, sitemap.lock().get()); + if (websocket) { + __handleEventWebSocket(req, resp, form, eventQueue.get(), parent); + } else { + __handleEventLongPolling(req, resp, form, eventQueue.get(), parent); + } + eventQueue->unsetHandler(); + } else { + throw webserver::webresource::WebResourceHandlerException((boost::format("Could not find sitemap \"%1%\"") % sitemapName).str(), + Poco::Net::HTTPResponse::HTTPStatus::HTTP_NOT_FOUND); + } + /*throw webserver::webresource::WebResourceHandlerException("ToDo. Implement that shit.", + Poco::Net::HTTPResponse::HTTPStatus::HTTP_NOT_IMPLEMENTED);*/ } @@ -160,6 +295,62 @@ SitemapAPIMethod SitemapHandler::getSitemapAPIMethod(Net::HTTPServerRequest &req +void SitemapEventQueue::checkRegistration(sitemaptemplate::SitemapEventType eventType, sitemaptemplate::SitemapTemplate* sitemap) { + if (!registeredToEvent || eventType != this->eventType || sitemap != this->sitemap) { + unregisterEvents(); + registerEvents(eventType, sitemap); + } +} + +void SitemapEventQueue::registerEvents(sitemaptemplate::SitemapEventType eventType, sitemaptemplate::SitemapTemplate* sitemap) { + std::lock_guard lock(_mutex); + if (!registeredToEvent) { + std::cout << "SitemapEventQueue::registerEvents" << std::endl; + sitemap->addObserver(eventType, this); + this->sitemap = sitemap; + this->eventType = eventType; + registeredToEvent = true; + } +} + +void SitemapEventQueue::unregisterEvents() { + std::lock_guard lock(_mutex); + if (registeredToEvent) { + std::cout << "SitemapEventQueue::unregisterEvents" << std::endl; + if (sitemap) { + sitemap->deleteObserver(this); + } + registeredToEvent = false; + } +} + +void SitemapEventQueue::notificationReceived( interface::Observer::observable_type* o, + const sitemaptemplate::SitemapEventType& key, + const sitemaptemplate::SitemapEvent* e) noexcept { + Variant event; + switch (key) { + case sitemaptemplate::SitemapEventType::SITEMAP_INVALIDATED: + event["eventtype"] = "sitemap_invalidated"; + break; + case sitemaptemplate::SitemapEventType::SITEMAP_ELEMENT_CHANGED: + { + const sitemaptemplate::SitemapElementChangedEvent* event2 = static_cast(e); + event["eventtype"] = "sitemap_element_changed"; + auto elements = Variant::List(); + for (auto el : event2->elements) { + elements.push_back(el.lock()->renderSitemap()); + } + event["elements"] = elements; + } + break; + default: + break; + } + if (!event.IsNull()) { + pushEvent(event); + } +} + } // namespace webresource } // namespace sitemap } // namespace service diff --git a/src/hos_cosgi_sitemap/src/hos/cosgi/service/sitemap/webresource/SitemapHandler.h b/src/hos_cosgi_sitemap/src/hos/cosgi/service/sitemap/webresource/SitemapHandler.h index 69e7c799..dfd59a4f 100644 --- a/src/hos_cosgi_sitemap/src/hos/cosgi/service/sitemap/webresource/SitemapHandler.h +++ b/src/hos_cosgi_sitemap/src/hos/cosgi/service/sitemap/webresource/SitemapHandler.h @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -40,8 +41,42 @@ enum class SitemapAPIMethod { }; +typedef webserver::webresource::WebResourceEventQueue< + interface::Observer, + SitemapHandlerFactory, + SitemapHandler +> SitemapEventQueueBase; + +class SitemapEventQueue + : public SitemapEventQueueBase { +public: + sitemaptemplate::SitemapEventType eventType = sitemaptemplate::SitemapEventType::ALL_EVENTS; + SitemapService* const sitemapService; + sitemaptemplate::SitemapTemplate* sitemap = nullptr; + + SitemapEventQueue( const std::string& clientID, + SitemapHandlerFactory* handlerFactory, + SitemapService* sitemapService ) + : WebResourceEventQueue(clientID, handlerFactory), sitemapService(sitemapService) {} + virtual ~SitemapEventQueue() { + unregisterEvents(); + } + + void checkRegistration(sitemaptemplate::SitemapEventType eventType, sitemaptemplate::SitemapTemplate* sitemap); + void registerEvents(sitemaptemplate::SitemapEventType eventType, sitemaptemplate::SitemapTemplate* sitemap = nullptr); + void unregisterEvents(); + + virtual void notificationReceived( interface::Observer::observable_type* o, + const sitemaptemplate::SitemapEventType& key, + const sitemaptemplate::SitemapEvent* e) noexcept override; +}; + + + + + class SitemapHandlerFactory - : public webserver::webresource::WebResourceHandlerFactoryImpl { + : public webserver::webresource::WebResourceHandlerFactoryImpl { private: SitemapService* service; diff --git a/src/hos_cosgi_testwebapp/webroot/draft01.css b/src/hos_cosgi_testwebapp/webroot/backup/draft01.css similarity index 100% rename from src/hos_cosgi_testwebapp/webroot/draft01.css rename to src/hos_cosgi_testwebapp/webroot/backup/draft01.css diff --git a/src/hos_cosgi_testwebapp/webroot/draft01.html b/src/hos_cosgi_testwebapp/webroot/backup/draft01.html similarity index 100% rename from src/hos_cosgi_testwebapp/webroot/draft01.html rename to src/hos_cosgi_testwebapp/webroot/backup/draft01.html diff --git a/src/hos_cosgi_testwebapp/webroot/draft02.css b/src/hos_cosgi_testwebapp/webroot/backup/draft02.css similarity index 100% rename from src/hos_cosgi_testwebapp/webroot/draft02.css rename to src/hos_cosgi_testwebapp/webroot/backup/draft02.css diff --git a/src/hos_cosgi_testwebapp/webroot/draft02.html b/src/hos_cosgi_testwebapp/webroot/backup/draft02.html similarity index 100% rename from src/hos_cosgi_testwebapp/webroot/draft02.html rename to src/hos_cosgi_testwebapp/webroot/backup/draft02.html diff --git a/src/hos_cosgi_testwebapp/webroot/hello.html b/src/hos_cosgi_testwebapp/webroot/backup/hello.html similarity index 100% rename from src/hos_cosgi_testwebapp/webroot/hello.html rename to src/hos_cosgi_testwebapp/webroot/backup/hello.html diff --git a/src/hos_cosgi_testwebapp/webroot/index.html b/src/hos_cosgi_testwebapp/webroot/backup/index.html similarity index 100% rename from src/hos_cosgi_testwebapp/webroot/index.html rename to src/hos_cosgi_testwebapp/webroot/backup/index.html diff --git a/src/hos_cosgi_testwebapp/webroot/rule01.html b/src/hos_cosgi_testwebapp/webroot/backup/rule01.html similarity index 100% rename from src/hos_cosgi_testwebapp/webroot/rule01.html rename to src/hos_cosgi_testwebapp/webroot/backup/rule01.html diff --git a/src/hos_cosgi_testwebapp/webroot/test04.css b/src/hos_cosgi_testwebapp/webroot/backup/test04.css similarity index 100% rename from src/hos_cosgi_testwebapp/webroot/test04.css rename to src/hos_cosgi_testwebapp/webroot/backup/test04.css diff --git a/src/hos_cosgi_testwebapp/webroot/test04.html b/src/hos_cosgi_testwebapp/webroot/backup/test04.html similarity index 100% rename from src/hos_cosgi_testwebapp/webroot/test04.html rename to src/hos_cosgi_testwebapp/webroot/backup/test04.html diff --git a/src/hos_cosgi_testwebapp/webroot/websocket.html b/src/hos_cosgi_testwebapp/webroot/backup/websocket.html similarity index 100% rename from src/hos_cosgi_testwebapp/webroot/websocket.html rename to src/hos_cosgi_testwebapp/webroot/backup/websocket.html diff --git a/src/hos_cosgi_testwebapp/webroot/widgettest01.css b/src/hos_cosgi_testwebapp/webroot/backup/widgettest01.css similarity index 100% rename from src/hos_cosgi_testwebapp/webroot/widgettest01.css rename to src/hos_cosgi_testwebapp/webroot/backup/widgettest01.css diff --git a/src/hos_cosgi_testwebapp/webroot/widgettest01.html b/src/hos_cosgi_testwebapp/webroot/backup/widgettest01.html similarity index 100% rename from src/hos_cosgi_testwebapp/webroot/widgettest01.html rename to src/hos_cosgi_testwebapp/webroot/backup/widgettest01.html diff --git a/src/hos_cosgi_testwebapp/webroot/libs/hos/sitemap/Page.js b/src/hos_cosgi_testwebapp/webroot/libs/hos/sitemap/Page.js new file mode 100644 index 00000000..40343d2a --- /dev/null +++ b/src/hos_cosgi_testwebapp/webroot/libs/hos/sitemap/Page.js @@ -0,0 +1,68 @@ +define([ + "dojo/_base/config", + 'dojo/_base/declare', + "dojo/Deferred", + "dojo/dom", + "dojo/dom-construct", + "dijit/layout/ContentPane", + "hos/sitemap/PageElementFactory" +], function(config, declare, Deferred, dom, domConstruct, ContentPane, PageElementFactory) { + + /* + * Class representing a page. + */ + var PageClass = declare(null, { + + /* + * constructor. + * + * sitemap: parent sitemap. + * + * name: page name. + * + * data: page json object received from server + */ + constructor: function(/*hos/sitemap/Sitemap*/ sitemap, /*string*/ name, /*json object*/ data) { + this.sitemap = sitemap; + this.name = name; + this.title = data["title"]; + this.elements = []; + var _this = this; + data["elements"].forEach(function(data) { + var element = PageElementFactory.createPageElement(_this, data); + _this.elements.push(element); + _this.sitemap.pageElements[element.uid] = element; + }); + }, + + /* returns string representation for debugging. */ + toString: function() { + var str = "Page \"" + this.name + "\" {
\n"; + this.elements.forEach(function(e) { + str += e.toString() + "
\n"; + }); + str += "} "; + return str; + }, + + /* + * Build the DOM. + */ + buildDom: function() { + var nodes = [] + this.elements.forEach(function(e) { + nodes.push(e.domNode); + }); + this.widget = new ContentPane({ + title: this.title, + id: this.name, + content: nodes, + style: "height: 100%; width: 100%;" + }); + this.domNode = this.widget.domNode; + } + + }); + + return PageClass; +}); diff --git a/src/hos_cosgi_testwebapp/webroot/libs/hos/sitemap/PageElementFactory.js b/src/hos_cosgi_testwebapp/webroot/libs/hos/sitemap/PageElementFactory.js new file mode 100644 index 00000000..7d0625c2 --- /dev/null +++ b/src/hos_cosgi_testwebapp/webroot/libs/hos/sitemap/PageElementFactory.js @@ -0,0 +1,56 @@ +define([ + "dojo/_base/config", + 'dojo/_base/declare', + "dojo/Deferred", + "dojo/dom", + "dojo/dom-construct", + "hos/sitemap/pageelements/FrameElement", + "hos/sitemap/pageelements/TextElement", + "hos/sitemap/pageelements/SwitchElement", + "hos/sitemap/pageelements/SliderElement", + "hos/sitemap/pageelements/ErrorElement", +], function( + config, + declare, + Deferred, + dom, + domConstruct, + FrameElement, + TextElement, + SwitchElement, + SliderElement, + ErrorElement +) { + + /* + * Class representing a page element factory. + */ + var ElementFactoryClass = declare(null, { + + /* + * Creates and returns a page element. + */ + createPageElement: function(/*hos/sitemap/Page*/ page, /*json object*/ data, /*hos/sitemap/pageelements/CompositeElement*/ parent) { + if (typeof(parent)==='undefined') parent = null; + var element = null; + if (data["type"] == "sitemap/sitemap#/definitions/frame_element") { + element = new FrameElement(page, data, this, parent); + } else if (data["type"] == "sitemap/sitemap#/definitions/text_element") { + element = new TextElement(page, data, this, parent); + } else if (data["type"] == "sitemap/sitemap#/definitions/switch_element") { + element = new SwitchElement(page, data, this, parent); + } else if (data["type"] == "sitemap/sitemap#/definitions/slider_element") { + element = new SliderElement(page, data, this, parent); + } else { + element = new ErrorElement(page, data, this, parent); + } + element.buildDom(); + return element; + } + + }); + + /* Create and return factory instance. */ + var factory = new ElementFactoryClass(); + return factory; +}); diff --git a/src/hos_cosgi_testwebapp/webroot/libs/hos/sitemap/Sitemap.js b/src/hos_cosgi_testwebapp/webroot/libs/hos/sitemap/Sitemap.js new file mode 100644 index 00000000..fee1a682 --- /dev/null +++ b/src/hos_cosgi_testwebapp/webroot/libs/hos/sitemap/Sitemap.js @@ -0,0 +1,213 @@ +define([ + "dojo/_base/config", + 'dojo/_base/declare', + 'dojo/_base/lang', + "dojo/dom", + "dojo/dom-construct", + "dojo/Deferred", + "dojox/uuid/generateRandomUuid", + "hos/sitemap/Page", + "hos/sitemap/widgets/SitemapWidget", + "dojox/socket", + "dojox/socket/Reconnect", + "dojo/request/xhr" +], function( + config, + declare, + lang, + dom, + domConstruct, + Deferred, + generateRandomUuid, + Page, + SitemapWidget, + Socket, + Reconnect, + xhr +) { + + /* + * Class representing a sitemap. + */ + var SitemapClass = declare(null, { + + /* + * Constructor. + * + * api: parent API. + * + * name: sitemap name. + * + * data: sitemap json object received from server + */ + constructor: function(/*hos.sitemap.SitemapAPI*/ api, /*string*/ name, /*json object*/ data) { + this.api = api; + this.name = name; + // parse json data + this.rootPageName = data["rootpage"]; + this.title = data["title"]; + // Create pages + this.pages = {} + var _pages = data["pages"]; + for (var _pageName in _pages) { + if (_pages.hasOwnProperty(_pageName)) { + var _page = new Page(this, _pageName, _pages[_pageName]); + this.pages[_pageName] = _page + } + } + this.buildDom(); + }, + + /* + * Dictionary containing all page elements. + */ + pageElements: {}, + + /* + * Stack containing name of visited pages. + * Needed for bread crumbs and back button. + */ + pageNameStack: [], + + /* returns string representation for debugging. */ + toString: function() { + var str = "Sitemap \"" + this.name + "\" {
\n"; + for (var pageName in this.pages) { + str += this.pages[pageName].toString() + "
\n"; + } + str += "} "; + return str; + }, + + /* + * Build the DOM. + */ + buildDom: function() { + this.widget = new SitemapWidget({sitemap: this}); + for (var pageName in this.pages) { + var page = this.pages[pageName]; + page.buildDom(); + this.widget.stackContainer.addChild(page.widget); + } + this.domNode = this.widget.domNode; + }, + + /* + * Startup function. + */ + startup: function() { + this.widget.startup(); + this.goToRootPage(); + this.enableUpdates(); + }, + + /* + * Update the bread crumbs in the ui. + */ + updateBreadCrumbs: function() { + var str = "> " + this.rootPageName; + this.pageNameStack.forEach(function(e) { + str += " > " + e; + }); + this.widget.setBreadCrumbs(str); + }, + + /* + * Go to page with given name. + */ + goToPage: function(/*string*/ name) { + this.pageNameStack.push(name); + this.widget.stackContainer.selectChild(name, true); + this.updateBreadCrumbs(); + }, + + /* + * Go to root page. + */ + goToRootPage: function() { + this.pageNameStack = []; + this.widget.stackContainer.selectChild(this.rootPageName); + this.updateBreadCrumbs(); + }, + + /* + * Go one page back. + */ + goBack: function() { + if (this.pageNameStack.length > 0) { + this.pageNameStack.pop(); + if (this.pageNameStack.length > 0) { + this.goToPage(this.pageNameStack.pop()); + } else { + this.goToRootPage(); + } + return true; + } else { + return false; + } + }, + + /* + * Connect to server and listen for updates. + */ + enableUpdates: function() { + this._updateSocketOpened = false; + this._updateSocketClientId = generateRandomUuid(); + this._updateSocket = Reconnect(new Socket({ + url: config.hos.sitemapUrl + "/" + this.name + '?onevent=all&clientid=' + this._updateSocketClientId, + headers: { + "On-Event": "changes", + /*"Connection-Timeout": 60000,*/ + "Client-Id": 1234 + }, + transport: xhr.post + }),{}); + this._updateSocket.on("open", lang.hitch(this, function(event){ + this._socketOpened = true; + console.log("Socket Opened ", event.message); + })); + this._updateSocket.on("close", lang.hitch(this, function(event){ + this._socketOpened = false; + console.log("Socket Closed ", event.message); + })); + this._updateSocket.on("error", lang.hitch(this, function(event){ + this._socketOpened = false; + console.log("Socket Error ", event.message); + })); + this._updateSocket.on("message", lang.hitch(this, function(event) { + var data = JSON.parse(event.data); + console.log("Message: " + event); + //console.log("Message: " + event.data); + if (data && data["eventtype"] == "sitemap_element_changed") { + var elements = data["elements"]; + elements.forEach(lang.hitch(this, function(e) { + var uid = e["uid"]; + if (uid in this.pageElements) { + var el = this.pageElements[uid]; + el.updateData(e); + } + })); + } + })); + }, + + /* + * Send command. + */ + sendPageElementCommand: function(uid, value) { + if (this._updateSocket) { + var msg = {eventtype: "sitemap_element_command", uid: uid, value: value}; + xhr.post(config.hos.sitemapUrl + "/" + this.name, { + data: {method: 'command', value: JSON.stringify(msg)} + }).then(function(data){ + console.log("sendPageElementCommand ok: " + data) + }, function(err){ + console.log("sendPageElementCommand error: " + err) + }); + } + } + + }); + + return SitemapClass; +}); diff --git a/src/hos_cosgi_testwebapp/webroot/libs/hos/sitemap/SitemapAPI.js b/src/hos_cosgi_testwebapp/webroot/libs/hos/sitemap/SitemapAPI.js new file mode 100644 index 00000000..0eabd862 --- /dev/null +++ b/src/hos_cosgi_testwebapp/webroot/libs/hos/sitemap/SitemapAPI.js @@ -0,0 +1,98 @@ +define([ + "dojo/_base/config", + 'dojo/_base/declare', + "dojo/request/xhr", + "dojo/Deferred", + "hos/sitemap/Sitemap" +], function(config, declare, xhr, Deferred, Sitemap) { + + /* + * Class representing the sitemap API. + */ + var ApiClass = declare(null, { + + /* + * Constructor. + */ + constructor: function(/*string*/ baseUrl) { + this.baseUrl = baseUrl; + }, + + /* + * Returns name of default sitemap. + * + * returns: Promise(string) + */ + getDefaultSitemapName: function() { + var deferred = new Deferred(); + xhr.get(this.baseUrl, { + handleAs: "json" + }).then(function(data){ + deferred.resolve(data["default"]); + }, function(err){ + deferred.reject(err); + }); + return deferred; + }, + + /* + * Returns an array containing the names of all available sitemaps. + * + * returns: Promise(array(string)) + */ + getSitemapNames: function() { + var deferred = new Deferred(); + xhr.get(this.baseUrl, { + handleAs: "json" + }).then(function(data){ + deferred.resolve(data["sitemaps"]); + }, function(err){ + deferred.reject(err); + }); + return deferred; + }, + + /* + * Returns the sitemap with given name. + * + * returns: Promise(hos.sitemap.Sitemap) + */ + getSitemap: function(/*string*/ name) { + var deferred = new Deferred(); + var _this = this + xhr.get(this.baseUrl + "/" + name, { + handleAs: "json" + }).then(function(data){ + var sitemap = new Sitemap(_this, name, data) + deferred.resolve(sitemap); + }, function(err){ + deferred.reject(err); + }); + return deferred; + }, + + /* + * Returns the default sitemap. + * + * returns: Promise(hos.sitemap.Sitemap) + */ + getDefaultSitemap: function() { + var deferred = new Deferred(); + var _this = this + this.getDefaultSitemapName().then(function(name){ + _this.getSitemap(name).then(function(sitemap){ + deferred.resolve(sitemap); + }, function(err){ + deferred.reject(err); + }); + }, function(err){ + deferred.reject(err); + }); + return deferred; + } + }); + + /* Create and returns API instance. */ + var api = new ApiClass(config.hos.sitemapUrl); + return api; +}); \ No newline at end of file diff --git a/src/hos_cosgi_testwebapp/webroot/libs/hos/sitemap/pageelements/AtomicElement.js b/src/hos_cosgi_testwebapp/webroot/libs/hos/sitemap/pageelements/AtomicElement.js new file mode 100644 index 00000000..4df16465 --- /dev/null +++ b/src/hos_cosgi_testwebapp/webroot/libs/hos/sitemap/pageelements/AtomicElement.js @@ -0,0 +1,47 @@ +define([ + "dojo/_base/config", + 'dojo/_base/declare', + "dojo/Deferred", + "dojo/dom", + "dojo/dom-construct", + "hos/sitemap/pageelements/ElementBase" +], function(config, declare, Deferred, dom, domConstruct, ElementBase) { + + return declare([ElementBase], { + + /* + * constructor. + * + * page: parent page. + * + * data: page element json object received from server + * + * factory: reference to page element factory + * + * parent: parent page element + */ + constructor: function(/*hos.sitemap.Page*/ page, /*json object*/ data, /*hos.sitemap.PageElementFactory*/ factory, /*hos/sitemap/pageelements/CompositeElement*/ parent) { + this.linkedPageName = data["linkedPage"]; + this.value = data["value"]; + }, + + /* + * Go to linked page. + */ + goToLinkedPage: function() { + if (this.linkedPageName) { + this.page.sitemap.goToPage(this.linkedPageName); + } + }, + + /* + * Update data. + */ + updateData: function(data) { + this.inherited(arguments); + this.value = data["value"]; + } + + }); + +}); diff --git a/src/hos_cosgi_testwebapp/webroot/libs/hos/sitemap/pageelements/CompositeElement.js b/src/hos_cosgi_testwebapp/webroot/libs/hos/sitemap/pageelements/CompositeElement.js new file mode 100644 index 00000000..3cc6b504 --- /dev/null +++ b/src/hos_cosgi_testwebapp/webroot/libs/hos/sitemap/pageelements/CompositeElement.js @@ -0,0 +1,34 @@ +define([ + "dojo/_base/config", + 'dojo/_base/declare', + "dojo/Deferred", + "dojo/dom", + "dojo/dom-construct", + "hos/sitemap/pageelements/ElementBase" +], function(config, declare, Deferred, dom, domConstruct, ElementBase) { + + return declare([ElementBase], { + + /* + * constructor. + * + * page: parent page. + * + * data: page element json object received from server + * + * factory: reference to page element factory + * + * parent: parent page element + */ + constructor: function(/*hos.sitemap.Page*/ page, /*json object*/ data, /*hos.sitemap.PageElementFactory*/ factory, /*hos/sitemap/pageelements/CompositeElement*/ parent) { + this.elements = [] + var _this = this; + data["elements"].forEach(function(data) { + var element = factory.createPageElement(page, data, _this) + _this.page.sitemap.pageElements[element.uid] = element; + _this.elements.push(element) + }); + } + }); + +}); \ No newline at end of file diff --git a/src/hos_cosgi_testwebapp/webroot/libs/hos/sitemap/pageelements/ElementBase.js b/src/hos_cosgi_testwebapp/webroot/libs/hos/sitemap/pageelements/ElementBase.js new file mode 100644 index 00000000..86509ade --- /dev/null +++ b/src/hos_cosgi_testwebapp/webroot/libs/hos/sitemap/pageelements/ElementBase.js @@ -0,0 +1,58 @@ +define([ + "dojo/_base/config", + 'dojo/_base/declare', + "dojo/Deferred", + "dojo/dom", + "dojo/dom-construct" +], function(config, declare, Deferred, dom, domConstruct) { + + return declare(null, { + + /* + * constructor. + * + * page: parent page. + * + * data: page element json object received from server + * + * factory: reference to page element factory + * + * parent: parent page element + */ + constructor: function(/*hos.sitemap.Page*/ page, /*json object*/ data, /*hos.sitemap.PageElementFactory*/ factory, /*hos/sitemap/pageelements/CompositeElement*/ parent) { + this.page = page; + this.parent = parent; + this.factory = factory; + this.uid = data["uid"]; + this.type = data["type"]; + this.icon = data["icon"]; + this.label = data["label"]; + }, + + /* returns string representation for debugging. */ + toString: function() { + return " **Unknown(" + this.uid + ")** "; + }, + + /* + * Build the DOM. + */ + buildDom: function() { + this.domNode = domConstruct.toDom(this.toString()); + }, + + /* + * Update data. + */ + updateData: function(data) { + this.label = data["label"]; + }, + + /* + * Update DOM. + */ + updateDom: function() { + } + }); + +}); diff --git a/src/hos_cosgi_testwebapp/webroot/libs/hos/sitemap/pageelements/ErrorElement.js b/src/hos_cosgi_testwebapp/webroot/libs/hos/sitemap/pageelements/ErrorElement.js new file mode 100644 index 00000000..17e5479c --- /dev/null +++ b/src/hos_cosgi_testwebapp/webroot/libs/hos/sitemap/pageelements/ErrorElement.js @@ -0,0 +1,20 @@ +define([ + "dojo/_base/config", + 'dojo/_base/declare', + "dojo/Deferred", + "dojo/dom", + "dojo/dom-construct", + "hos/sitemap/pageelements/AtomicElement" +], function(config, declare, Deferred, dom, domConstruct, AtomicElement) { + + return declare([AtomicElement], { + + /* returns string representation for debugging. */ + toString: function() { + var str = " ErrorElement (" + this.uid + ") "; + return str; + } + + }); + +}); diff --git a/src/hos_cosgi_testwebapp/webroot/libs/hos/sitemap/pageelements/FrameElement.js b/src/hos_cosgi_testwebapp/webroot/libs/hos/sitemap/pageelements/FrameElement.js new file mode 100644 index 00000000..0ea47a13 --- /dev/null +++ b/src/hos_cosgi_testwebapp/webroot/libs/hos/sitemap/pageelements/FrameElement.js @@ -0,0 +1,40 @@ +define([ + "dojo/_base/config", + 'dojo/_base/declare', + "dojo/Deferred", + "dojo/dom", + "dojo/dom-construct", + "hos/sitemap/pageelements/CompositeElement", + "dijit/TitlePane" +], function(config, declare, Deferred, dom, domConstruct, CompositeElement, TitlePane) { + + return declare([CompositeElement], { + + /* returns string representation for debugging. */ + toString: function() { + var str = " FrameElement (" + this.uid + "){
\n"; + this.elements.forEach(function(e) { + str += e.toString() + "
\n"; + }); + str += "} "; + return str; + }, + + /* + * Build the DOM. + */ + buildDom: function() { + var nodes = [] + this.elements.forEach(function(e) { + nodes.push(e.domNode); + }); + this.widget = new TitlePane({ + title:this.label, + content:nodes + }); + this.domNode = this.widget.domNode + } + + }); + +}); diff --git a/src/hos_cosgi_testwebapp/webroot/libs/hos/sitemap/pageelements/SliderElement.js b/src/hos_cosgi_testwebapp/webroot/libs/hos/sitemap/pageelements/SliderElement.js new file mode 100644 index 00000000..6817552a --- /dev/null +++ b/src/hos_cosgi_testwebapp/webroot/libs/hos/sitemap/pageelements/SliderElement.js @@ -0,0 +1,100 @@ +define([ + "dojo/_base/config", + 'dojo/_base/declare', + 'dojo/_base/lang', + "dojo/Deferred", + "dojo/dom", + "dojo/dom-construct", + "hos/sitemap/pageelements/AtomicElement", + "hos/sitemap/pageelements/widgets/ElementWidget", + "dojox/mobile/Slider" +], function(config, declare, lang, Deferred, dom, domConstruct, AtomicElement, ElementWidget, Slider) { + + return declare([AtomicElement], { + + /* + * constructor. + * + * page: parent page. + * + * data: page element json object received from server + * + * factory: reference to page element factory + * + * parent: parent page element + */ + constructor: function(/*hos.sitemap.Page*/ page, /*json object*/ data, /*hos.sitemap.PageElementFactory*/ factory, /*hos/sitemap/pageelements/CompositeElement*/ parent) { + this.min = data["min"]; + this.max = data["max"]; + }, + + /* returns string representation for debugging. */ + toString: function() { + var str = " SliderElement (" + this.uid + ") "; + return str; + }, + + /* + * Set Slider value. + */ + setSliderValue: function(value) { + this.value = value/100; + this.page.sitemap.sendPageElementCommand(this.uid, this.value); + }, + + /* + * Build the DOM. + */ + buildDom: function() { + this.widget = new ElementWidget({baseClass: "SliderElementWidget"}); + /*this.sliderWidget = HorizontalSlider({ + minimum: this.min, + maximum: this.max, + value: this.value, + onChange: lang.hitch(this, this.setSliderValue) + });*/ + this.sliderWidget = Slider({ + minimum: this.min*100, + maximum: this.max*100, + value: this.value*100, + style: "width: 95%;", + onChange: lang.hitch(this, this.setSliderValue) + }); + this.widget.setValue(this.sliderWidget.domNode); + this.updateDom(); + this.domNode = this.widget.domNode; + }, + + /* + * Update data. + */ + updateData: function(data) { + this.inherited(arguments); + this.updateDom(); + }, + + /* + * Update DOM. + */ + updateDom: function() { + if (typeof(this.linkedPageName) != "undefined") { + var tmp = domConstruct.create("a",{ + href: "javascript:void(0)", + innerHTML: "" + this.label + "", + onclick: lang.hitch(this, this.goToLinkedPage) + }); + this.widget.setLabel(tmp); + var tmp2 = domConstruct.create("div",{ + innerHTML: " >> ", + onclick: lang.hitch(this, this.goToLinkedPage) + }); + this.widget.setLink(tmp2); + } else { + this.widget.setLabel(domConstruct.toDom("" + this.label + "")); + } + this.sliderWidget.set("value", this.value*100); + } + + }); + +}); diff --git a/src/hos_cosgi_testwebapp/webroot/libs/hos/sitemap/pageelements/SwitchElement.js b/src/hos_cosgi_testwebapp/webroot/libs/hos/sitemap/pageelements/SwitchElement.js new file mode 100644 index 00000000..31c71798 --- /dev/null +++ b/src/hos_cosgi_testwebapp/webroot/libs/hos/sitemap/pageelements/SwitchElement.js @@ -0,0 +1,84 @@ +define([ + "dojo/_base/config", + "dojo/_base/lang", + "dojo/_base/declare", + "dojo/Deferred", + "dojo/dom", + "dojo/dom-construct", + "hos/sitemap/pageelements/AtomicElement", + "hos/sitemap/pageelements/widgets/ElementWidget", + "dojox/mobile/Switch" +], function(config, lang, declare, Deferred, dom, domConstruct, AtomicElement, ElementWidget, Switch) { + + return declare([AtomicElement], { + + /* returns string representation for debugging. */ + toString: function() { + var str = " SwitchElement (" + this.uid + ") "; + return str; + }, + + /* + * Build the DOM. + */ + buildDom: function() { + this.widget = new ElementWidget({baseClass: "SwitchElementWidget"}); + this.buttonWidget = Switch({ + }); + this.buttonWidget.on("stateChanged", lang.hitch(this, this.setSwitch)); + this.widget.setValue(this.buttonWidget.domNode); + this.updateDom(); + this.domNode = this.widget.domNode; + }, + + /* + * Toggle switch. + */ + toggleSwitch: function() { + this.value = !this.value; + this.updateDom(); + this.page.sitemap.sendPageElementCommand(this.uid, this.value); + }, + + /* + * Set switch. + */ + setSwitch: function(value) { + this.value = (value == "on"); + this.updateDom(); + this.page.sitemap.sendPageElementCommand(this.uid, this.value); + }, + + /* + * Update data. + */ + updateData: function(data) { + this.inherited(arguments); + this.updateDom(); + }, + + /* + * Update DOM. + */ + updateDom: function() { + if (typeof(this.linkedPageName) != "undefined") { + var tmp = domConstruct.create("a",{ + href: "javascript:void(0)", + innerHTML: "" + this.label + "", + onclick: lang.hitch(this, this.goToLinkedPage) + }); + this.widget.setLabel(tmp); + var tmp2 = domConstruct.create("div",{ + innerHTML: " >> ", + onclick: lang.hitch(this, this.goToLinkedPage) + }); + this.widget.setLink(tmp2); + } else { + this.widget.setLabel(domConstruct.toDom("" + this.label + "")); + } + this.buttonWidget.set("value", this.value ? "on" : "off"); + } + + }); + +}); \ No newline at end of file diff --git a/src/hos_cosgi_testwebapp/webroot/libs/hos/sitemap/pageelements/TextElement.js b/src/hos_cosgi_testwebapp/webroot/libs/hos/sitemap/pageelements/TextElement.js new file mode 100644 index 00000000..f06b6b32 --- /dev/null +++ b/src/hos_cosgi_testwebapp/webroot/libs/hos/sitemap/pageelements/TextElement.js @@ -0,0 +1,61 @@ +define([ + "dojo/_base/config", + 'dojo/_base/declare', + "dojo/_base/lang", + "dojo/Deferred", + "dojo/dom", + "dojo/dom-construct", + "hos/sitemap/pageelements/AtomicElement", + "hos/sitemap/pageelements/widgets/ElementWidget" +], function(config, declare, lang, Deferred, dom, domConstruct, AtomicElement, ElementWidget) { + + return declare([AtomicElement], { + + /* returns string representation for debugging. */ + toString: function() { + var str = " TextElement (" + this.uid + ") "; + return str; + }, + + /* + * Build the DOM. + */ + buildDom: function() { + this.widget= new ElementWidget({baseClass: "TextElementWidget"}); + this.updateDom(); + this.domNode = this.widget.domNode; + }, + + /* + * Update data. + */ + updateData: function(data) { + this.inherited(arguments); + this.updateDom(); + }, + + /* + * Update DOM. + */ + updateDom: function() { + if (typeof(this.linkedPageName) != "undefined") { + var tmp = domConstruct.create("a",{ + href: "javascript:void(0)", + innerHTML: "" + this.label + "", + onclick: lang.hitch(this, this.goToLinkedPage) + }); + this.widget.setLabel(tmp); + var tmp2 = domConstruct.create("div",{ + innerHTML: " >> ", + onclick: lang.hitch(this, this.goToLinkedPage) + }); + this.widget.setLink(tmp2); + } else { + this.widget.setLabel(domConstruct.toDom("" + this.label + "")); + } + this.widget.setValue(domConstruct.toDom("" + this.value + "")); + } + + }); + +}); diff --git a/src/hos_cosgi_testwebapp/webroot/libs/hos/sitemap/pageelements/widgets/ElementWidget.js b/src/hos_cosgi_testwebapp/webroot/libs/hos/sitemap/pageelements/widgets/ElementWidget.js new file mode 100644 index 00000000..79e72d4f --- /dev/null +++ b/src/hos_cosgi_testwebapp/webroot/libs/hos/sitemap/pageelements/widgets/ElementWidget.js @@ -0,0 +1,31 @@ +define([ + "dojo/_base/declare", + "dojo/dom", + "dojo/dom-construct", + "dijit/_WidgetBase", + "dijit/_TemplatedMixin", + "dojo/text!./templates/ElementWidget.html" +], function(declare, dom, domConstruct, _WidgetBase, _TemplatedMixin, template) { + + return declare([_WidgetBase, _TemplatedMixin], { + + templateString: template, + + setLabel: function(label) { + domConstruct.empty(this.domLabelNode); + domConstruct.place(label, this.domLabelNode); + }, + + setValue: function(value) { + domConstruct.empty(this.domValueNode); + domConstruct.place(value, this.domValueNode); + }, + + setLink: function(link) { + domConstruct.empty(this.domLinkNode); + domConstruct.place(link, this.domLinkNode); + } + + }); + +}); diff --git a/src/hos_cosgi_testwebapp/webroot/libs/hos/sitemap/pageelements/widgets/templates/ElementWidget.html b/src/hos_cosgi_testwebapp/webroot/libs/hos/sitemap/pageelements/widgets/templates/ElementWidget.html new file mode 100644 index 00000000..9081b14f --- /dev/null +++ b/src/hos_cosgi_testwebapp/webroot/libs/hos/sitemap/pageelements/widgets/templates/ElementWidget.html @@ -0,0 +1,10 @@ +
+ + + + + + + +
+
\ No newline at end of file diff --git a/src/hos_cosgi_testwebapp/webroot/libs/hos/sitemap/widgets/SitemapWidget.js b/src/hos_cosgi_testwebapp/webroot/libs/hos/sitemap/widgets/SitemapWidget.js new file mode 100644 index 00000000..5073b1dc --- /dev/null +++ b/src/hos_cosgi_testwebapp/webroot/libs/hos/sitemap/widgets/SitemapWidget.js @@ -0,0 +1,86 @@ +define([ + "dojo/_base/declare", + "dojo/_base/lang", + "dojo/dom", + "dojo/dom-construct", + "dijit/_WidgetBase", + "dijit/layout/BorderContainer", + "dijit/layout/StackContainer", + "dijit/layout/ContentPane", + "dijit/form/Button" +], function(declare, lang, dom, domConstruct, _WidgetBase, BorderContainer, StackContainer, ContentPane, Button) { + + return declare([_WidgetBase], { + + /* + * buildRendering function. + */ + buildRendering: function() { + if(!this._rendered){ + + // create a BorderContainer as the top widget in the hierarchy + this.bc = new BorderContainer({ + style: "width: 100%; height: 100%;" + + }); + + // create a ContentPane as the left pane in the BorderContainer + /*this.cp1 = new ContentPane({ + region: "left", + splitter: true, + style: "width: 150px;", + content: "

Sitemap/Navigation

  • ToDo
" + }); + this.bc.addChild(this.cp1);*/ + + this.stackContainer = StackContainer(); + this.backButton = new Button({ + label: "Back", + onClick: lang.hitch(this, function(){ + this.sitemap.goBack(); + }) + }); + this.homeButton = new Button({ + label: "Home", + onClick: lang.hitch(this, function(){ + this.sitemap.goToRootPage(); + }) + }); + // create a ContentPane as the center pane in the BorderContainer + this.cp2 = new ContentPane({ + region: "center", + content: [this.stackContainer.domNode, this.backButton.domNode, this.homeButton.domNode] + }); + this.bc.addChild(this.cp2); + + var contentNode = domConstruct.toDom("Home Operating System:
bread crumbs here
"); + this.breadCrumbNode = dom.byId("breadCrumbNode", contentNode) + // create a ContentPane as the top pane in the BorderContainer + this.cp3 = new ContentPane({ + region: "top", + content: contentNode + }); + this.bc.addChild(this.cp3); + + this.domNode = this.bc.domNode; + } + this.inherited(arguments); + this._rendered = true; + }, + + /* + * startup function. + */ + startup: function() { + this.bc.startup(); + }, + + /* + * Set bread crumbs. + */ + setBreadCrumbs: function(/*string*/ text) { + this.breadCrumbNode.innerHTML=text + } + }); + +}); diff --git a/src/hos_cosgi_testwebapp/webroot/php/dir01/index.php b/src/hos_cosgi_testwebapp/webroot/php/dir01/index.php deleted file mode 100644 index df2bc391..00000000 --- a/src/hos_cosgi_testwebapp/webroot/php/dir01/index.php +++ /dev/null @@ -1,6 +0,0 @@ - \ No newline at end of file diff --git a/src/hos_cosgi_testwebapp/webroot/sitemap01.css b/src/hos_cosgi_testwebapp/webroot/sitemap01.css new file mode 100644 index 00000000..58e26776 --- /dev/null +++ b/src/hos_cosgi_testwebapp/webroot/sitemap01.css @@ -0,0 +1,33 @@ + + + +html, body { + height: 100%; + margin: 0; + overflow: hidden; + padding: 0; +} + +#appLayout { + height: 100%; +} +#leftCol { + width: 14em; +} + +.claro .demoLayout .edgePanel { + background-color: #d0e9fc; +} + +#viewsChart { + width: 550px; + height: 550px; +} + + + +.claro .dijitToggleButtonChecked .dijitButtonNode { + background-color: #0090ff; + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2) inset; + transition-duration: 0.1s; +} diff --git a/src/hos_cosgi_testwebapp/webroot/sitemap01.html b/src/hos_cosgi_testwebapp/webroot/sitemap01.html new file mode 100644 index 00000000..5376c8d3 --- /dev/null +++ b/src/hos_cosgi_testwebapp/webroot/sitemap01.html @@ -0,0 +1,65 @@ + + + + + Sitemap Test 01 + + + + + + + + + + + + + + + + + +
+ + + + diff --git a/src/hos_cosgi_testwebapp/webroot/sitemap02.html b/src/hos_cosgi_testwebapp/webroot/sitemap02.html new file mode 100644 index 00000000..32e73b4a --- /dev/null +++ b/src/hos_cosgi_testwebapp/webroot/sitemap02.html @@ -0,0 +1,65 @@ + + + + + Sitemap Test 01 + + + + + + + + + + + + + + + + + +
+ + + + diff --git a/src/hos_philips_hue/schema/hue/huelight/command.json b/src/hos_philips_hue/schema/hue/huelight/command.json index 7ce2ece1..e67f2978 100644 --- a/src/hos_philips_hue/schema/hue/huelight/command.json +++ b/src/hos_philips_hue/schema/hue/huelight/command.json @@ -33,7 +33,7 @@ } }, "additionalProperties": false, - "required": [ "type", "set" ] + "required": [ "set" ] }, "effect": { "description": "", @@ -49,7 +49,7 @@ } }, "additionalProperties": false, - "required": [ "type", "set" ] + "required": [ "set" ] }, "advproperties": { "description": "", @@ -64,15 +64,15 @@ } }, "additionalProperties": true, - "required": [ "type" ] + "required": [] } }, "additionalProperties": false, "anyOf": [ - { "required": [ "type", "switch" ] }, - { "required": [ "type", "brightness" ] }, - { "required": [ "type", "color" ] }, - { "required": [ "type", "alert" ] }, - { "required": [ "type", "effect" ] } + { "required": [ "switch" ] }, + { "required": [ "brightness" ] }, + { "required": [ "color" ] }, + { "required": [ "alert" ] }, + { "required": [ "effect" ] } ] } \ No newline at end of file