#include "stdafx.h"
#include "WebServer.h"
#include "WebServerHelper.h"
#include <iostream>
#include <fstream>
#include <stdarg.h>
#include "mainworker.h"
#include "Helper.h"
#include "localtime_r.h"
#include "EventSystem.h"
#include "HTMLSanitizer.h"
#include "dzVents.h"
#include "../httpclient/HTTPClient.h"
#include "../hardware/hardwaretypes.h"
#include "../hardware/1Wire.h"
#include "../hardware/OTGWBase.h"
#ifdef WITH_OPENZWAVE
#include "../hardware/OpenZWave.h"
#endif
#include "../hardware/EnOceanESP2.h"
#include "../hardware/EnOceanESP3.h"
#include "../hardware/Wunderground.h"
#include "../hardware/DarkSky.h"
#include "../hardware/AccuWeather.h"
#include "../hardware/OpenWeatherMap.h"
#include "../hardware/Buienradar.h"
#include "../hardware/Meteorologisk.h"
#include "../hardware/Kodi.h"
#include "../hardware/Limitless.h"
#include "../hardware/LogitechMediaServer.h"
#include "../hardware/MySensorsBase.h"
#include "../hardware/RFXBase.h"
#include "../hardware/RFLinkBase.h"
#include "../hardware/SysfsGpio.h"
#include "../hardware/HEOS.h"
#include "../hardware/eHouseTCP.h"
#include "../hardware/USBtin.h"
#include "../hardware/USBtin_MultiblocV8.h"
#include "../hardware/AirconWithMe.h"
#include "../hardware/ZiBlueBase.h"
#ifdef WITH_GPIO
#include "../hardware/Gpio.h"
#include "../hardware/GpioPin.h"
#endif // WITH_GPIO
#include "../hardware/Tellstick.h"
#include "../webserver/Base64.h"
#include "../smtpclient/SMTPClient.h"
#include <json/json.h>
#include "../main/json_helper.h"
#include "Logger.h"
#include "SQLHelper.h"
#include "../push/BasePush.h"
#include <algorithm>
#ifdef ENABLE_PYTHON
#include "../hardware/plugins/Plugins.h"
#endif

#ifndef WIN32
#include <sys/utsname.h>
#include <dirent.h>
#else
#include "../msbuild/WindowsHelper.h"
#include "dirent_windows.h"
#endif
#include "../notifications/NotificationHelper.h"
#include "../main/LuaHandler.h"

#define __STDC_FORMAT_MACROS
#include <inttypes.h>

#define round(a) (int)(a + .5)

extern std::string szStartupFolder;
extern std::string szUserDataFolder;
extern std::string szWWWFolder;

extern std::string szAppVersion;
extern std::string szAppHash;
extern std::string szAppDate;
extern std::string szPyVersion;

extern bool g_bUseUpdater;

extern time_t m_StartTime;

extern bool g_bDontCacheWWW;

struct _tGuiLanguage
{
	const char *szShort;
	const char *szLong;
};

namespace
{
	constexpr std::array<std::pair<const char *, const char *>, 36> guiLanguage{ {
		{ "en", "English" },   { "sq", "Albanian" },   { "ar", "Arabic" },   { "bs", "Bosnian" },      { "bg", "Bulgarian" }, { "ca", "Catalan" },
		{ "zh", "Chinese" },   { "cs", "Czech" },      { "da", "Danish" },   { "nl", "Dutch" },	       { "et", "Estonian" },  { "de", "German" },
		{ "el", "Greek" },     { "fr", "French" },     { "fi", "Finnish" },  { "he", "Hebrew" },       { "hu", "Hungarian" }, { "is", "Icelandic" },
		{ "it", "Italian" },   { "lt", "Lithuanian" }, { "lv", "Latvian" },  { "mk", "Macedonian" },   { "no", "Norwegian" }, { "fa", "Persian" },
		{ "pl", "Polish" },    { "pt", "Portuguese" }, { "ro", "Romanian" }, { "ru", "Russian" },      { "sr", "Serbian" },   { "sk", "Slovak" },
		{ "sl", "Slovenian" }, { "es", "Spanish" },    { "sv", "Swedish" },  { "zh_TW", "Taiwanese" }, { "tr", "Turkish" },   { "uk", "Ukrainian" },
	} };
} // namespace

extern http::server::CWebServerHelper m_webservers;

namespace http
{
	namespace server
	{

		CWebServer::CWebServer()
		{
			m_pWebEm = nullptr;
			m_bDoStop = false;
#ifdef WITH_OPENZWAVE
			m_ZW_Hwidx = -1;
#endif
		}

		CWebServer::~CWebServer()
		{
			// RK, we call StopServer() instead of just deleting m_pWebEm. The Do_Work thread might still be accessing that object
			StopServer();
		}

		void CWebServer::Do_Work()
		{
			bool exception_thrown = false;
			while (!m_bDoStop)
			{
				exception_thrown = false;
				try
				{
					if (m_pWebEm)
					{
						m_pWebEm->Run();
					}
				}
				catch (std::exception &e)
				{
					_log.Log(LOG_ERROR, "WebServer(%s) exception occurred : '%s'", m_server_alias.c_str(), e.what());
					exception_thrown = true;
				}
				catch (...)
				{
					_log.Log(LOG_ERROR, "WebServer(%s) unknown exception occurred", m_server_alias.c_str());
					exception_thrown = true;
				}
				if (exception_thrown)
				{
					_log.Log(LOG_STATUS, "WebServer(%s) restart server in 5 seconds", m_server_alias.c_str());
					sleep_milliseconds(5000); // prevents from an exception flood
					continue;
				}
				break;
			}
			_log.Log(LOG_STATUS, "WebServer(%s) stopped", m_server_alias.c_str());
		}

		void CWebServer::ReloadCustomSwitchIcons()
		{
			m_custom_light_icons.clear();
			m_custom_light_icons_lookup.clear();
			std::string sLine;

			// First get them from the switch_icons.txt file
			std::ifstream infile;
			std::string switchlightsfile = szWWWFolder + "/switch_icons.txt";
			infile.open(switchlightsfile.c_str());
			if (infile.is_open())
			{
				int index = 0;
				while (!infile.eof())
				{
					getline(infile, sLine);
					if (!sLine.empty())
					{
						std::vector<std::string> results;
						StringSplit(sLine, ";", results);
						if (results.size() == 3)
						{
							_tCustomIcon cImage;
							cImage.idx = index++;
							cImage.RootFile = results[0];
							cImage.Title = results[1];
							cImage.Description = results[2];
							m_custom_light_icons.push_back(cImage);
							m_custom_light_icons_lookup[cImage.idx] = m_custom_light_icons.size() - 1;
						}
					}
				}
				infile.close();
			}
			// Now get them from the database (idx 100+)
			std::vector<std::vector<std::string>> result;
			result = m_sql.safe_query("SELECT ID,Base,Name,Description FROM CustomImages");
			if (!result.empty())
			{
				int ii = 0;
				for (const auto &sd : result)
				{
					int ID = atoi(sd[0].c_str());

					_tCustomIcon cImage;
					cImage.idx = 100 + ID;
					cImage.RootFile = sd[1];
					cImage.Title = sd[2];
					cImage.Description = sd[3];

					std::string IconFile16 = cImage.RootFile + ".png";
					std::string IconFile48On = cImage.RootFile + "48_On.png";
					std::string IconFile48Off = cImage.RootFile + "48_Off.png";

					std::map<std::string, std::string> _dbImageFiles;
					_dbImageFiles["IconSmall"] = szWWWFolder + "/images/" + IconFile16;
					_dbImageFiles["IconOn"] = szWWWFolder + "/images/" + IconFile48On;
					_dbImageFiles["IconOff"] = szWWWFolder + "/images/" + IconFile48Off;

					// Check if files are on disk, else add them
					for (const auto &db : _dbImageFiles)
					{
						std::string TableField = db.first;
						std::string IconFile = db.second;

						if (!file_exist(IconFile.c_str()))
						{
							// Does not exists, extract it from the database and add it
							std::vector<std::vector<std::string>> result2;
							result2 = m_sql.safe_queryBlob("SELECT %s FROM CustomImages WHERE ID=%d", TableField.c_str(), ID);
							if (!result2.empty())
							{
								std::ofstream file;
								file.open(IconFile.c_str(), std::ios::out | std::ios::binary);
								if (!file.is_open())
									return;

								file << result2[0][0];
								file.close();
							}
						}
					}

					m_custom_light_icons.push_back(cImage);
					m_custom_light_icons_lookup[cImage.idx] = m_custom_light_icons.size() - 1;
					ii++;
				}
			}
		}

		bool CWebServer::StartServer(server_settings &settings, const std::string &serverpath, const bool bIgnoreUsernamePassword)
		{
			m_server_alias = (settings.is_secure() == true) ? "SSL" : "HTTP";

			if (!settings.is_enabled())
				return true;

			ReloadCustomSwitchIcons();

			int tries = 0;
			bool exception = false;

			//_log.Log(LOG_STATUS, "CWebServer::StartServer() : settings : %s", settings.to_string().c_str());
			do
			{
				try
				{
					exception = false;
					m_pWebEm = new http::server::cWebem(settings, serverpath);
				}
				catch (std::exception &e)
				{
					exception = true;
					switch (tries)
					{
						case 0:
							_log.Log(LOG_STATUS, "WebServer(%s) startup failed on address %s with port: %s: %s, trying ::", m_server_alias.c_str(),
								 settings.listening_address.c_str(), settings.listening_port.c_str(), e.what());
							settings.listening_address = "::";
							break;
						case 1:
							_log.Log(LOG_STATUS, "WebServer(%s) startup failed on address %s with port: %s: %s, trying 0.0.0.0", m_server_alias.c_str(),
								 settings.listening_address.c_str(), settings.listening_port.c_str(), e.what());
							settings.listening_address = "0.0.0.0";
							break;
						case 2:
							_log.Log(LOG_ERROR, "WebServer(%s) startup failed on address %s with port: %s: %s", m_server_alias.c_str(), settings.listening_address.c_str(),
								 settings.listening_port.c_str(), e.what());
							if (atoi(settings.listening_port.c_str()) < 1024)
								_log.Log(LOG_ERROR, "WebServer(%s) check privileges for opening ports below 1024", m_server_alias.c_str());
							else
								_log.Log(LOG_ERROR, "WebServer(%s) check if no other application is using port: %s", m_server_alias.c_str(),
									 settings.listening_port.c_str());
							return false;
					}
					tries++;
				}
			} while (exception);

			_log.Log(LOG_STATUS, "WebServer(%s) started on address: %s with port %s", m_server_alias.c_str(), settings.listening_address.c_str(), settings.listening_port.c_str());

			m_pWebEm->SetDigistRealm("Domoticz.com");
			m_pWebEm->SetSessionStore(this);

			if (!bIgnoreUsernamePassword)
			{
				LoadUsers();

				std::string WebLocalNetworks;
				int nValue;
				if (m_sql.GetPreferencesVar("WebLocalNetworks", nValue, WebLocalNetworks))
				{
					std::vector<std::string> strarray;
					StringSplit(WebLocalNetworks, ";", strarray);
					for (const auto &str : strarray)
						m_pWebEm->AddLocalNetworks(str);
					// add local hostname
					m_pWebEm->AddLocalNetworks("");
				}
			}

			std::string WebRemoteProxyIPs;
			int nValue;
			if (m_sql.GetPreferencesVar("WebRemoteProxyIPs", nValue, WebRemoteProxyIPs))
			{
				std::vector<std::string> strarray;
				StringSplit(WebRemoteProxyIPs, ";", strarray);
				for (const auto &str : strarray)
					m_pWebEm->AddRemoteProxyIPs(str);
			}

			// register callbacks
			m_pWebEm->RegisterIncludeCode("switchtypes", [this](auto &&content_part) { DisplaySwitchTypesCombo(content_part); });
			m_pWebEm->RegisterIncludeCode("metertypes", [this](auto &&content_part) { DisplayMeterTypesCombo(content_part); });
			m_pWebEm->RegisterIncludeCode("timertypes", [this](auto &&content_part) { DisplayTimerTypesCombo(content_part); });
			m_pWebEm->RegisterIncludeCode("combolanguage", [this](auto &&content_part) { DisplayLanguageCombo(content_part); });

			m_pWebEm->RegisterPageCode("/json.htm", [this](auto &&session, auto &&req, auto &&rep) { GetJSonPage(session, req, rep); });
			// These 'Pages' should probably be 'moved' to become Command codes handled by the 'json.htm API', so we get all API calls through one entry point
			// And why .php or .cgi while all these commands are NOT handled by a PHP or CGI processor but by Domoticz ?? Legacy? Rename these?
			m_pWebEm->RegisterPageCode("/logincheck", [this](auto &&session, auto &&req, auto &&rep) { PostLoginCheck(session, req, rep); }, true);
			m_pWebEm->RegisterPageCode("/uploadcustomicon", [this](auto &&session, auto &&req, auto &&rep) { Post_UploadCustomIcon(session, req, rep); });
			m_pWebEm->RegisterPageCode("/storesettings", [this](auto &&session, auto &&req, auto &&rep) { PostSettings(session, req, rep); });
			m_pWebEm->RegisterPageCode("/backupdatabase.php", [this](auto &&session, auto &&req, auto &&rep) { GetDatabaseBackup(session, req, rep); });
			m_pWebEm->RegisterPageCode("/camsnapshot.jpg", [this](auto &&session, auto &&req, auto &&rep) { GetCameraSnapshot(session, req, rep); });
			m_pWebEm->RegisterPageCode("/raspberry.cgi", [this](auto &&session, auto &&req, auto &&rep) { GetInternalCameraSnapshot(session, req, rep); });
			m_pWebEm->RegisterPageCode("/uvccapture.cgi", [this](auto &&session, auto &&req, auto &&rep) { GetInternalCameraSnapshot(session, req, rep); });
			// Maybe handle these differently? (Or remove)
			m_pWebEm->RegisterPageCode("/images/floorplans/plan", [this](auto &&session, auto &&req, auto &&rep) { GetFloorplanImage(session, req, rep); });
			m_pWebEm->RegisterPageCode("/html5.appcache", [this](auto &&session, auto &&req, auto &&rep) { GetAppCache(session, req, rep); });
			// End of 'Pages' to be moved...

			m_pWebEm->RegisterActionCode("setrfxcommode", [this](auto &&session, auto &&req, auto &&redirect_uri) { SetRFXCOMMode(session, req, redirect_uri); });
			m_pWebEm->RegisterActionCode("rfxupgradefirmware", [this](auto &&session, auto &&req, auto &&redirect_uri) { RFXComUpgradeFirmware(session, req, redirect_uri); });
			RegisterCommandCode(
				"rfxfirmwaregetpercentage", [this](auto &&session, auto &&req, auto &&root) { Cmd_RFXComGetFirmwarePercentage(session, req, root); }, true);
			m_pWebEm->RegisterActionCode("setrego6xxtype", [this](auto &&session, auto &&req, auto &&redirect_uri) { SetRego6XXType(session, req, redirect_uri); });
			m_pWebEm->RegisterActionCode("sets0metertype", [this](auto &&session, auto &&req, auto &&redirect_uri) { SetS0MeterType(session, req, redirect_uri); });
			m_pWebEm->RegisterActionCode("setlimitlesstype", [this](auto &&session, auto &&req, auto &&redirect_uri) { SetLimitlessType(session, req, redirect_uri); });

			m_pWebEm->RegisterActionCode("uploadfloorplanimage", [this](auto &&session, auto &&req, auto &&redirect_uri) { UploadFloorplanImage(session, req, redirect_uri); });

			m_pWebEm->RegisterActionCode("setopenthermsettings", [this](auto &&session, auto &&req, auto &&redirect_uri) { SetOpenThermSettings(session, req, redirect_uri); });
			RegisterCommandCode(
				"sendopenthermcommand", [this](auto &&session, auto &&req, auto &&root) { Cmd_SendOpenThermCommand(session, req, root); }, true);

			m_pWebEm->RegisterActionCode("reloadpiface", [this](auto &&session, auto &&req, auto &&redirect_uri) { ReloadPiFace(session, req, redirect_uri); });
			m_pWebEm->RegisterActionCode("setcurrentcostmetertype", [this](auto &&session, auto &&req, auto &&redirect_uri) { SetCurrentCostUSBType(session, req, redirect_uri); });
			m_pWebEm->RegisterActionCode("restoredatabase", [this](auto &&session, auto &&req, auto &&redirect_uri) { RestoreDatabase(session, req, redirect_uri); });
			m_pWebEm->RegisterActionCode("sbfspotimportolddata", [this](auto &&session, auto &&req, auto &&redirect_uri) { SBFSpotImportOldData(session, req, redirect_uri); });

			m_pWebEm->RegisterActionCode("event_create", [this](auto &&session, auto &&req, auto &&redirect_uri) { EventCreate(session, req, redirect_uri); });

			RegisterCommandCode(
				"getlanguage", [this](auto &&session, auto &&req, auto &&root) { Cmd_GetLanguage(session, req, root); }, true);
			RegisterCommandCode(
				"getlanguages", [this](auto &&session, auto &&req, auto &&root) { Cmd_GetLanguages(session, req, root); }, true);
			RegisterCommandCode(
				"getthemes", [this](auto &&session, auto &&req, auto &&root) { Cmd_GetThemes(session, req, root); }, true);
			RegisterCommandCode(
				"gettitle", [this](auto &&session, auto &&req, auto &&root) { Cmd_GetTitle(session, req, root); }, true);
			RegisterCommandCode(
				"logincheck", [this](auto &&session, auto &&req, auto &&root) { Cmd_LoginCheck(session, req, root); }, true);
			RegisterCommandCode(
				"getversion", [this](auto &&session, auto &&req, auto &&root) { Cmd_GetVersion(session, req, root); }, true);
			RegisterCommandCode(
				"getauth", [this](auto &&session, auto &&req, auto &&root) { Cmd_GetAuth(session, req, root); }, true);
			RegisterCommandCode(
				"getuptime", [this](auto &&session, auto &&req, auto &&root) { Cmd_GetUptime(session, req, root); }, true);

			RegisterCommandCode("storesettings", [this](auto &&session, auto &&req, auto &&root) { Cmd_PostSettings(session, req, root); });
			RegisterCommandCode("getlog", [this](auto &&session, auto &&req, auto &&root) { Cmd_GetLog(session, req, root); });
			RegisterCommandCode("clearlog", [this](auto &&session, auto &&req, auto &&root) { Cmd_ClearLog(session, req, root); });
			RegisterCommandCode("gethardwaretypes", [this](auto &&session, auto &&req, auto &&root) { Cmd_GetHardwareTypes(session, req, root); });
			RegisterCommandCode("addhardware", [this](auto &&session, auto &&req, auto &&root) { Cmd_AddHardware(session, req, root); });
			RegisterCommandCode("updatehardware", [this](auto &&session, auto &&req, auto &&root) { Cmd_UpdateHardware(session, req, root); });
			RegisterCommandCode("deletehardware", [this](auto &&session, auto &&req, auto &&root) { Cmd_DeleteHardware(session, req, root); });

			RegisterCommandCode("addcamera", [this](auto &&session, auto &&req, auto &&root) { Cmd_AddCamera(session, req, root); });
			RegisterCommandCode("updatecamera", [this](auto &&session, auto &&req, auto &&root) { Cmd_UpdateCamera(session, req, root); });
			RegisterCommandCode("deletecamera", [this](auto &&session, auto &&req, auto &&root) { Cmd_DeleteCamera(session, req, root); });

			RegisterCommandCode("wolgetnodes", [this](auto &&session, auto &&req, auto &&root) { Cmd_WOLGetNodes(session, req, root); });
			RegisterCommandCode("woladdnode", [this](auto &&session, auto &&req, auto &&root) { Cmd_WOLAddNode(session, req, root); });
			RegisterCommandCode("wolupdatenode", [this](auto &&session, auto &&req, auto &&root) { Cmd_WOLUpdateNode(session, req, root); });
			RegisterCommandCode("wolremovenode", [this](auto &&session, auto &&req, auto &&root) { Cmd_WOLRemoveNode(session, req, root); });
			RegisterCommandCode("wolclearnodes", [this](auto &&session, auto &&req, auto &&root) { Cmd_WOLClearNodes(session, req, root); });

			RegisterCommandCode("mysensorsgetnodes", [this](auto &&session, auto &&req, auto &&root) { Cmd_MySensorsGetNodes(session, req, root); });
			RegisterCommandCode("mysensorsgetchilds", [this](auto &&session, auto &&req, auto &&root) { Cmd_MySensorsGetChilds(session, req, root); });
			RegisterCommandCode("mysensorsupdatenode", [this](auto &&session, auto &&req, auto &&root) { Cmd_MySensorsUpdateNode(session, req, root); });
			RegisterCommandCode("mysensorsremovenode", [this](auto &&session, auto &&req, auto &&root) { Cmd_MySensorsRemoveNode(session, req, root); });
			RegisterCommandCode("mysensorsremovechild", [this](auto &&session, auto &&req, auto &&root) { Cmd_MySensorsRemoveChild(session, req, root); });
			RegisterCommandCode("mysensorsupdatechild", [this](auto &&session, auto &&req, auto &&root) { Cmd_MySensorsUpdateChild(session, req, root); });

			RegisterCommandCode("pingersetmode", [this](auto &&session, auto &&req, auto &&root) { Cmd_PingerSetMode(session, req, root); });
			RegisterCommandCode("pingergetnodes", [this](auto &&session, auto &&req, auto &&root) { Cmd_PingerGetNodes(session, req, root); });
			RegisterCommandCode("pingeraddnode", [this](auto &&session, auto &&req, auto &&root) { Cmd_PingerAddNode(session, req, root); });
			RegisterCommandCode("pingerupdatenode", [this](auto &&session, auto &&req, auto &&root) { Cmd_PingerUpdateNode(session, req, root); });
			RegisterCommandCode("pingerremovenode", [this](auto &&session, auto &&req, auto &&root) { Cmd_PingerRemoveNode(session, req, root); });
			RegisterCommandCode("pingerclearnodes", [this](auto &&session, auto &&req, auto &&root) { Cmd_PingerClearNodes(session, req, root); });

			RegisterCommandCode("kodisetmode", [this](auto &&session, auto &&req, auto &&root) { Cmd_KodiSetMode(session, req, root); });
			RegisterCommandCode("kodigetnodes", [this](auto &&session, auto &&req, auto &&root) { Cmd_KodiGetNodes(session, req, root); });
			RegisterCommandCode("kodiaddnode", [this](auto &&session, auto &&req, auto &&root) { Cmd_KodiAddNode(session, req, root); });
			RegisterCommandCode("kodiupdatenode", [this](auto &&session, auto &&req, auto &&root) { Cmd_KodiUpdateNode(session, req, root); });
			RegisterCommandCode("kodiremovenode", [this](auto &&session, auto &&req, auto &&root) { Cmd_KodiRemoveNode(session, req, root); });
			RegisterCommandCode("kodiclearnodes", [this](auto &&session, auto &&req, auto &&root) { Cmd_KodiClearNodes(session, req, root); });
			RegisterCommandCode("kodimediacommand", [this](auto &&session, auto &&req, auto &&root) { Cmd_KodiMediaCommand(session, req, root); });

			RegisterCommandCode("panasonicsetmode", [this](auto &&session, auto &&req, auto &&root) { Cmd_PanasonicSetMode(session, req, root); });
			RegisterCommandCode("panasonicgetnodes", [this](auto &&session, auto &&req, auto &&root) { Cmd_PanasonicGetNodes(session, req, root); });
			RegisterCommandCode("panasonicaddnode", [this](auto &&session, auto &&req, auto &&root) { Cmd_PanasonicAddNode(session, req, root); });
			RegisterCommandCode("panasonicupdatenode", [this](auto &&session, auto &&req, auto &&root) { Cmd_PanasonicUpdateNode(session, req, root); });
			RegisterCommandCode("panasonicremovenode", [this](auto &&session, auto &&req, auto &&root) { Cmd_PanasonicRemoveNode(session, req, root); });
			RegisterCommandCode("panasonicclearnodes", [this](auto &&session, auto &&req, auto &&root) { Cmd_PanasonicClearNodes(session, req, root); });
			RegisterCommandCode("panasonicmediacommand", [this](auto &&session, auto &&req, auto &&root) { Cmd_PanasonicMediaCommand(session, req, root); });

			RegisterCommandCode("heossetmode", [this](auto &&session, auto &&req, auto &&root) { Cmd_HEOSSetMode(session, req, root); });
			RegisterCommandCode("heosmediacommand", [this](auto &&session, auto &&req, auto &&root) { Cmd_HEOSMediaCommand(session, req, root); });

			RegisterCommandCode("onkyoeiscpcommand", [this](auto &&session, auto &&req, auto &&root) { Cmd_OnkyoEiscpCommand(session, req, root); });

			RegisterCommandCode("bleboxsetmode", [this](auto &&session, auto &&req, auto &&root) { Cmd_BleBoxSetMode(session, req, root); });
			RegisterCommandCode("bleboxgetnodes", [this](auto &&session, auto &&req, auto &&root) { Cmd_BleBoxGetNodes(session, req, root); });
			RegisterCommandCode("bleboxaddnode", [this](auto &&session, auto &&req, auto &&root) { Cmd_BleBoxAddNode(session, req, root); });
			RegisterCommandCode("bleboxremovenode", [this](auto &&session, auto &&req, auto &&root) { Cmd_BleBoxRemoveNode(session, req, root); });
			RegisterCommandCode("bleboxclearnodes", [this](auto &&session, auto &&req, auto &&root) { Cmd_BleBoxClearNodes(session, req, root); });
			RegisterCommandCode("bleboxautosearchingnodes", [this](auto &&session, auto &&req, auto &&root) { Cmd_BleBoxAutoSearchingNodes(session, req, root); });
			RegisterCommandCode("bleboxupdatefirmware", [this](auto &&session, auto &&req, auto &&root) { Cmd_BleBoxUpdateFirmware(session, req, root); });

			RegisterCommandCode("lmssetmode", [this](auto &&session, auto &&req, auto &&root) { Cmd_LMSSetMode(session, req, root); });
			RegisterCommandCode("lmsgetnodes", [this](auto &&session, auto &&req, auto &&root) { Cmd_LMSGetNodes(session, req, root); });
			RegisterCommandCode("lmsgetplaylists", [this](auto &&session, auto &&req, auto &&root) { Cmd_LMSGetPlaylists(session, req, root); });
			RegisterCommandCode("lmsmediacommand", [this](auto &&session, auto &&req, auto &&root) { Cmd_LMSMediaCommand(session, req, root); });
			RegisterCommandCode("lmsdeleteunuseddevices", [this](auto &&session, auto &&req, auto &&root) { Cmd_LMSDeleteUnusedDevices(session, req, root); });

			RegisterCommandCode("savefibarolinkconfig", [this](auto &&session, auto &&req, auto &&root) { Cmd_SaveFibaroLinkConfig(session, req, root); });
			RegisterCommandCode("getfibarolinkconfig", [this](auto &&session, auto &&req, auto &&root) { Cmd_GetFibaroLinkConfig(session, req, root); });
			RegisterCommandCode("getfibarolinks", [this](auto &&session, auto &&req, auto &&root) { Cmd_GetFibaroLinks(session, req, root); });
			RegisterCommandCode("savefibarolink", [this](auto &&session, auto &&req, auto &&root) { Cmd_SaveFibaroLink(session, req, root); });
			RegisterCommandCode("deletefibarolink", [this](auto &&session, auto &&req, auto &&root) { Cmd_DeleteFibaroLink(session, req, root); });

			RegisterCommandCode("saveinfluxlinkconfig", [this](auto &&session, auto &&req, auto &&root) { Cmd_SaveInfluxLinkConfig(session, req, root); });
			RegisterCommandCode("getinfluxlinkconfig", [this](auto &&session, auto &&req, auto &&root) { Cmd_GetInfluxLinkConfig(session, req, root); });
			RegisterCommandCode("getinfluxlinks", [this](auto &&session, auto &&req, auto &&root) { Cmd_GetInfluxLinks(session, req, root); });
			RegisterCommandCode("saveinfluxlink", [this](auto &&session, auto &&req, auto &&root) { Cmd_SaveInfluxLink(session, req, root); });
			RegisterCommandCode("deleteinfluxlink", [this](auto &&session, auto &&req, auto &&root) { Cmd_DeleteInfluxLink(session, req, root); });

			RegisterCommandCode("savehttplinkconfig", [this](auto &&session, auto &&req, auto &&root) { Cmd_SaveHttpLinkConfig(session, req, root); });
			RegisterCommandCode("gethttplinkconfig", [this](auto &&session, auto &&req, auto &&root) { Cmd_GetHttpLinkConfig(session, req, root); });
			RegisterCommandCode("gethttplinks", [this](auto &&session, auto &&req, auto &&root) { Cmd_GetHttpLinks(session, req, root); });
			RegisterCommandCode("savehttplink", [this](auto &&session, auto &&req, auto &&root) { Cmd_SaveHttpLink(session, req, root); });
			RegisterCommandCode("deletehttplink", [this](auto &&session, auto &&req, auto &&root) { Cmd_DeleteHttpLink(session, req, root); });

			RegisterCommandCode("savegooglepubsublinkconfig", [this](auto &&session, auto &&req, auto &&root) { Cmd_SaveGooglePubSubLinkConfig(session, req, root); });
			RegisterCommandCode("getgooglepubsublinkconfig", [this](auto &&session, auto &&req, auto &&root) { Cmd_GetGooglePubSubLinkConfig(session, req, root); });
			RegisterCommandCode("getgooglepubsublinks", [this](auto &&session, auto &&req, auto &&root) { Cmd_GetGooglePubSubLinks(session, req, root); });
			RegisterCommandCode("savegooglepubsublink", [this](auto &&session, auto &&req, auto &&root) { Cmd_SaveGooglePubSubLink(session, req, root); });
			RegisterCommandCode("deletegooglepubsublink", [this](auto &&session, auto &&req, auto &&root) { Cmd_DeleteGooglePubSubLink(session, req, root); });

			RegisterCommandCode("getdevicevalueoptions", [this](auto &&session, auto &&req, auto &&root) { Cmd_GetDeviceValueOptions(session, req, root); });
			RegisterCommandCode("getdevicevalueoptionwording", [this](auto &&session, auto &&req, auto &&root) { Cmd_GetDeviceValueOptionWording(session, req, root); });

			RegisterCommandCode("adduservariable", [this](auto &&session, auto &&req, auto &&root) { Cmd_AddUserVariable(session, req, root); });
			RegisterCommandCode("updateuservariable", [this](auto &&session, auto &&req, auto &&root) { Cmd_UpdateUserVariable(session, req, root); });
			RegisterCommandCode("deleteuservariable", [this](auto &&session, auto &&req, auto &&root) { Cmd_DeleteUserVariable(session, req, root); });
			RegisterCommandCode("getuservariables", [this](auto &&session, auto &&req, auto &&root) { Cmd_GetUserVariables(session, req, root); });
			RegisterCommandCode("getuservariable", [this](auto &&session, auto &&req, auto &&root) { Cmd_GetUserVariable(session, req, root); });

			RegisterCommandCode("allownewhardware", [this](auto &&session, auto &&req, auto &&root) { Cmd_AllowNewHardware(session, req, root); });

			RegisterCommandCode("addplan", [this](auto &&session, auto &&req, auto &&root) { Cmd_AddPlan(session, req, root); });
			RegisterCommandCode("updateplan", [this](auto &&session, auto &&req, auto &&root) { Cmd_UpdatePlan(session, req, root); });
			RegisterCommandCode("deleteplan", [this](auto &&session, auto &&req, auto &&root) { Cmd_DeletePlan(session, req, root); });
			RegisterCommandCode("getunusedplandevices", [this](auto &&session, auto &&req, auto &&root) { Cmd_GetUnusedPlanDevices(session, req, root); });
			RegisterCommandCode("addplanactivedevice", [this](auto &&session, auto &&req, auto &&root) { Cmd_AddPlanActiveDevice(session, req, root); });
			RegisterCommandCode("getplandevices", [this](auto &&session, auto &&req, auto &&root) { Cmd_GetPlanDevices(session, req, root); });
			RegisterCommandCode("deleteplandevice", [this](auto &&session, auto &&req, auto &&root) { Cmd_DeletePlanDevice(session, req, root); });
			RegisterCommandCode("setplandevicecoords", [this](auto &&session, auto &&req, auto &&root) { Cmd_SetPlanDeviceCoords(session, req, root); });
			RegisterCommandCode("deleteallplandevices", [this](auto &&session, auto &&req, auto &&root) { Cmd_DeleteAllPlanDevices(session, req, root); });
			RegisterCommandCode("changeplanorder", [this](auto &&session, auto &&req, auto &&root) { Cmd_ChangePlanOrder(session, req, root); });
			RegisterCommandCode("changeplandeviceorder", [this](auto &&session, auto &&req, auto &&root) { Cmd_ChangePlanDeviceOrder(session, req, root); });

			RegisterCommandCode("gettimerplans", [this](auto &&session, auto &&req, auto &&root) { Cmd_GetTimerPlans(session, req, root); });
			RegisterCommandCode("addtimerplan", [this](auto &&session, auto &&req, auto &&root) { Cmd_AddTimerPlan(session, req, root); });
			RegisterCommandCode("updatetimerplan", [this](auto &&session, auto &&req, auto &&root) { Cmd_UpdateTimerPlan(session, req, root); });
			RegisterCommandCode("deletetimerplan", [this](auto &&session, auto &&req, auto &&root) { Cmd_DeleteTimerPlan(session, req, root); });
			RegisterCommandCode("duplicatetimerplan", [this](auto &&session, auto &&req, auto &&root) { Cmd_DuplicateTimerPlan(session, req, root); });

			RegisterCommandCode("getactualhistory", [this](auto &&session, auto &&req, auto &&root) { Cmd_GetActualHistory(session, req, root); });
			RegisterCommandCode("getnewhistory", [this](auto &&session, auto &&req, auto &&root) { Cmd_GetNewHistory(session, req, root); });

			RegisterCommandCode("getconfig", [this](auto &&session, auto &&req, auto &&root) { Cmd_GetConfig(session, req, root); }, true);
			RegisterCommandCode("getlocation", [this](auto &&session, auto &&req, auto &&root) { Cmd_GetLocation(session, req, root); });
			RegisterCommandCode("getforecastconfig", [this](auto &&session, auto &&req, auto &&root) { Cmd_GetForecastConfig(session, req, root); });
			RegisterCommandCode("sendnotification", [this](auto &&session, auto &&req, auto &&root) { Cmd_SendNotification(session, req, root); });
			RegisterCommandCode("emailcamerasnapshot", [this](auto &&session, auto &&req, auto &&root) { Cmd_EmailCameraSnapshot(session, req, root); });
			RegisterCommandCode("udevice", [this](auto &&session, auto &&req, auto &&root) { Cmd_UpdateDevice(session, req, root); });
			RegisterCommandCode("udevices", [this](auto &&session, auto &&req, auto &&root) { Cmd_UpdateDevices(session, req, root); });
			RegisterCommandCode("thermostatstate", [this](auto &&session, auto &&req, auto &&root) { Cmd_SetThermostatState(session, req, root); });
			RegisterCommandCode("system_shutdown", [this](auto &&session, auto &&req, auto &&root) { Cmd_SystemShutdown(session, req, root); });
			RegisterCommandCode("system_reboot", [this](auto &&session, auto &&req, auto &&root) { Cmd_SystemReboot(session, req, root); });
			RegisterCommandCode("execute_script", [this](auto &&session, auto &&req, auto &&root) { Cmd_ExcecuteScript(session, req, root); });
			RegisterCommandCode("getcosts", [this](auto &&session, auto &&req, auto &&root) { Cmd_GetCosts(session, req, root); });
			RegisterCommandCode("checkforupdate", [this](auto &&session, auto &&req, auto &&root) { Cmd_CheckForUpdate(session, req, root); });
			RegisterCommandCode("downloadupdate", [this](auto &&session, auto &&req, auto &&root) { Cmd_DownloadUpdate(session, req, root); });
			RegisterCommandCode("downloadready", [this](auto &&session, auto &&req, auto &&root) { Cmd_DownloadReady(session, req, root); });
			RegisterCommandCode("update_application", [this](auto &&session, auto &&req, auto &&root) { Cmd_UpdateApplication(session, req, root); });
			RegisterCommandCode("deletedatapoint", [this](auto &&session, auto &&req, auto &&root) { Cmd_DeleteDataPoint(session, req, root); });
			RegisterCommandCode("deletedaterange", [this](auto &&session, auto &&req, auto &&root) { Cmd_DeleteDateRange(session, req, root); });
			RegisterCommandCode("customevent", [this](auto &&session, auto &&req, auto &&root) { Cmd_CustomEvent(session, req, root); });

			RegisterCommandCode("setactivetimerplan", [this](auto &&session, auto &&req, auto &&root) { Cmd_SetActiveTimerPlan(session, req, root); });
			RegisterCommandCode("addtimer", [this](auto &&session, auto &&req, auto &&root) { Cmd_AddTimer(session, req, root); });
			RegisterCommandCode("updatetimer", [this](auto &&session, auto &&req, auto &&root) { Cmd_UpdateTimer(session, req, root); });
			RegisterCommandCode("deletetimer", [this](auto &&session, auto &&req, auto &&root) { Cmd_DeleteTimer(session, req, root); });
			RegisterCommandCode("enabletimer", [this](auto &&session, auto &&req, auto &&root) { Cmd_EnableTimer(session, req, root); });
			RegisterCommandCode("disabletimer", [this](auto &&session, auto &&req, auto &&root) { Cmd_DisableTimer(session, req, root); });
			RegisterCommandCode("cleartimers", [this](auto &&session, auto &&req, auto &&root) { Cmd_ClearTimers(session, req, root); });

			RegisterCommandCode("addscenetimer", [this](auto &&session, auto &&req, auto &&root) { Cmd_AddSceneTimer(session, req, root); });
			RegisterCommandCode("updatescenetimer", [this](auto &&session, auto &&req, auto &&root) { Cmd_UpdateSceneTimer(session, req, root); });
			RegisterCommandCode("deletescenetimer", [this](auto &&session, auto &&req, auto &&root) { Cmd_DeleteSceneTimer(session, req, root); });
			RegisterCommandCode("enablescenetimer", [this](auto &&session, auto &&req, auto &&root) { Cmd_EnableSceneTimer(session, req, root); });
			RegisterCommandCode("disablescenetimer", [this](auto &&session, auto &&req, auto &&root) { Cmd_DisableSceneTimer(session, req, root); });
			RegisterCommandCode("clearscenetimers", [this](auto &&session, auto &&req, auto &&root) { Cmd_ClearSceneTimers(session, req, root); });
			RegisterCommandCode("getsceneactivations", [this](auto &&session, auto &&req, auto &&root) { Cmd_GetSceneActivations(session, req, root); });
			RegisterCommandCode("addscenecode", [this](auto &&session, auto &&req, auto &&root) { Cmd_AddSceneCode(session, req, root); });
			RegisterCommandCode("removescenecode", [this](auto &&session, auto &&req, auto &&root) { Cmd_RemoveSceneCode(session, req, root); });
			RegisterCommandCode("clearscenecodes", [this](auto &&session, auto &&req, auto &&root) { Cmd_ClearSceneCodes(session, req, root); });
			RegisterCommandCode("renamescene", [this](auto &&session, auto &&req, auto &&root) { Cmd_RenameScene(session, req, root); });

			RegisterCommandCode("setsetpoint", [this](auto &&session, auto &&req, auto &&root) { Cmd_SetSetpoint(session, req, root); });
			RegisterCommandCode("addsetpointtimer", [this](auto &&session, auto &&req, auto &&root) { Cmd_AddSetpointTimer(session, req, root); });
			RegisterCommandCode("updatesetpointtimer", [this](auto &&session, auto &&req, auto &&root) { Cmd_UpdateSetpointTimer(session, req, root); });
			RegisterCommandCode("deletesetpointtimer", [this](auto &&session, auto &&req, auto &&root) { Cmd_DeleteSetpointTimer(session, req, root); });
			RegisterCommandCode("enablesetpointtimer", [this](auto &&session, auto &&req, auto &&root) { Cmd_EnableSetpointTimer(session, req, root); });
			RegisterCommandCode("disablesetpointtimer", [this](auto &&session, auto &&req, auto &&root) { Cmd_DisableSetpointTimer(session, req, root); });
			RegisterCommandCode("clearsetpointtimers", [this](auto &&session, auto &&req, auto &&root) { Cmd_ClearSetpointTimers(session, req, root); });

			RegisterCommandCode("serial_devices", [this](auto &&session, auto &&req, auto &&root) { Cmd_GetSerialDevices(session, req, root); });
			RegisterCommandCode("devices_list", [this](auto &&session, auto &&req, auto &&root) { Cmd_GetDevicesList(session, req, root); });
			RegisterCommandCode("devices_list_onoff", [this](auto &&session, auto &&req, auto &&root) { Cmd_GetDevicesListOnOff(session, req, root); });

			RegisterCommandCode("registerhue", [this](auto &&session, auto &&req, auto &&root) { Cmd_PhilipsHueRegister(session, req, root); });

			RegisterCommandCode("getcustomiconset", [this](auto &&session, auto &&req, auto &&root) { Cmd_GetCustomIconSet(session, req, root); });
			RegisterCommandCode("deletecustomicon", [this](auto &&session, auto &&req, auto &&root) { Cmd_DeleteCustomIcon(session, req, root); });
			RegisterCommandCode("updatecustomicon", [this](auto &&session, auto &&req, auto &&root) { Cmd_UpdateCustomIcon(session, req, root); });

			RegisterCommandCode("renamedevice", [this](auto &&session, auto &&req, auto &&root) { Cmd_RenameDevice(session, req, root); });
			RegisterCommandCode("setdevused", [this](auto &&session, auto &&req, auto &&root) { Cmd_SetDeviceUsed(session, req, root); });

			RegisterCommandCode("addlogmessage", [this](auto &&session, auto &&req, auto &&root) { Cmd_AddLogMessage(session, req, root); });
			RegisterCommandCode("clearshortlog", [this](auto &&session, auto &&req, auto &&root) { Cmd_ClearShortLog(session, req, root); });
			RegisterCommandCode("vacuumdatabase", [this](auto &&session, auto &&req, auto &&root) { Cmd_VacuumDatabase(session, req, root); });

			RegisterCommandCode("addmobiledevice", [this](auto &&session, auto &&req, auto &&root) { Cmd_AddMobileDevice(session, req, root); });
			RegisterCommandCode("updatemobiledevice", [this](auto &&session, auto &&req, auto &&root) { Cmd_UpdateMobileDevice(session, req, root); });
			RegisterCommandCode("deletemobiledevice", [this](auto &&session, auto &&req, auto &&root) { Cmd_DeleteMobileDevice(session, req, root); });

			RegisterCommandCode("addyeelight", [this](auto &&session, auto &&req, auto &&root) { Cmd_AddYeeLight(session, req, root); });

			RegisterCommandCode("addArilux", [this](auto &&session, auto &&req, auto &&root) { Cmd_AddArilux(session, req, root); });

			RegisterCommandCode("tellstickApplySettings", [this](auto &&session, auto &&req, auto &&root) { Cmd_TellstickApplySettings(session, req, root); });

			RegisterRType("graph", [this](auto &&session, auto &&req, auto &&root) { RType_HandleGraph(session, req, root); });
			RegisterRType("lightlog", [this](auto &&session, auto &&req, auto &&root) { RType_LightLog(session, req, root); });
			RegisterRType("textlog", [this](auto &&session, auto &&req, auto &&root) { RType_TextLog(session, req, root); });
			RegisterRType("scenelog", [this](auto &&session, auto &&req, auto &&root) { RType_SceneLog(session, req, root); });
			RegisterRType("settings", [this](auto &&session, auto &&req, auto &&root) { RType_Settings(session, req, root); });
			RegisterRType("events", [this](auto &&session, auto &&req, auto &&root) { RType_Events(session, req, root); });

			RegisterRType("hardware", [this](auto &&session, auto &&req, auto &&root) { RType_Hardware(session, req, root); });
			RegisterRType("devices", [this](auto &&session, auto &&req, auto &&root) { RType_Devices(session, req, root); });
			RegisterRType("deletedevice", [this](auto &&session, auto &&req, auto &&root) { RType_DeleteDevice(session, req, root); });
			RegisterRType("cameras", [this](auto &&session, auto &&req, auto &&root) { RType_Cameras(session, req, root); });
			RegisterRType("cameras_user", [this](auto &&session, auto &&req, auto &&root) { RType_CamerasUser(session, req, root); });
			RegisterRType("users", [this](auto &&session, auto &&req, auto &&root) { RType_Users(session, req, root); });
			RegisterRType("mobiles", [this](auto &&session, auto &&req, auto &&root) { RType_Mobiles(session, req, root); });

			RegisterRType("timers", [this](auto &&session, auto &&req, auto &&root) { RType_Timers(session, req, root); });
			RegisterRType("scenetimers", [this](auto &&session, auto &&req, auto &&root) { RType_SceneTimers(session, req, root); });
			RegisterRType("setpointtimers", [this](auto &&session, auto &&req, auto &&root) { RType_SetpointTimers(session, req, root); });

			RegisterRType("gettransfers", [this](auto &&session, auto &&req, auto &&root) { RType_GetTransfers(session, req, root); });
			RegisterRType("transferdevice", [this](auto &&session, auto &&req, auto &&root) { RType_TransferDevice(session, req, root); });
			RegisterRType("notifications", [this](auto &&session, auto &&req, auto &&root) { RType_Notifications(session, req, root); });
			RegisterRType("schedules", [this](auto &&session, auto &&req, auto &&root) { RType_Schedules(session, req, root); });
			RegisterRType("getshareduserdevices", [this](auto &&session, auto &&req, auto &&root) { RType_GetSharedUserDevices(session, req, root); });
			RegisterRType("setshareduserdevices", [this](auto &&session, auto &&req, auto &&root) { RType_SetSharedUserDevices(session, req, root); });
			RegisterRType("setused", [this](auto &&session, auto &&req, auto &&root) { RType_SetUsed(session, req, root); });
			RegisterRType("scenes", [this](auto &&session, auto &&req, auto &&root) { RType_Scenes(session, req, root); });
			RegisterRType("addscene", [this](auto &&session, auto &&req, auto &&root) { RType_AddScene(session, req, root); });
			RegisterRType("deletescene", [this](auto &&session, auto &&req, auto &&root) { RType_DeleteScene(session, req, root); });
			RegisterRType("updatescene", [this](auto &&session, auto &&req, auto &&root) { RType_UpdateScene(session, req, root); });
			RegisterRType("createvirtualsensor", [this](auto &&session, auto &&req, auto &&root) { RType_CreateMappedSensor(session, req, root); });
			RegisterRType("createdevice", [this](auto &&session, auto &&req, auto &&root) { RType_CreateDevice(session, req, root); });

			RegisterRType("createevohomesensor", [this](auto &&session, auto &&req, auto &&root) { RType_CreateEvohomeSensor(session, req, root); });
			RegisterRType("bindevohome", [this](auto &&session, auto &&req, auto &&root) { RType_BindEvohome(session, req, root); });
			RegisterRType("createrflinkdevice", [this](auto &&session, auto &&req, auto &&root) { RType_CreateRFLinkDevice(session, req, root); });

			RegisterRType("custom_light_icons", [this](auto &&session, auto &&req, auto &&root) { RType_CustomLightIcons(session, req, root); });
			RegisterRType("plans", [this](auto &&session, auto &&req, auto &&root) { RType_Plans(session, req, root); });
			RegisterRType("floorplans", [this](auto &&session, auto &&req, auto &&root) { RType_FloorPlans(session, req, root); });

#ifdef WITH_OPENZWAVE
			// ZWave
			RegisterCommandCode("updatezwavenode", [this](auto &&session, auto &&req, auto &&root) { Cmd_ZWaveUpdateNode(session, req, root); });
			RegisterCommandCode("deletezwavenode", [this](auto &&session, auto &&req, auto &&root) { Cmd_ZWaveDeleteNode(session, req, root); });
			RegisterCommandCode("zwaveinclude", [this](auto &&session, auto &&req, auto &&root) { Cmd_ZWaveInclude(session, req, root); });
			RegisterCommandCode("zwaveexclude", [this](auto &&session, auto &&req, auto &&root) { Cmd_ZWaveExclude(session, req, root); });

			RegisterCommandCode("zwaveisnodeincluded", [this](auto &&session, auto &&req, auto &&root) { Cmd_ZWaveIsNodeIncluded(session, req, root); });
			RegisterCommandCode("zwaveisnodeexcluded", [this](auto &&session, auto &&req, auto &&root) { Cmd_ZWaveIsNodeExcluded(session, req, root); });
			RegisterCommandCode("zwaveisnodereplaced", [this](auto &&session, auto &&req, auto &&root) { Cmd_ZWaveIsNodeReplaced(session, req, root); });
			RegisterCommandCode("zwaveishasnodefaileddone", [this](auto &&session, auto &&req, auto &&root) { Cmd_ZWaveIsHasNodeFailedDone(session, req, root); });

			RegisterCommandCode("zwavesoftreset", [this](auto &&session, auto &&req, auto &&root) { Cmd_ZWaveSoftReset(session, req, root); });
			RegisterCommandCode("zwavehardreset", [this](auto &&session, auto &&req, auto &&root) { Cmd_ZWaveHardReset(session, req, root); });
			RegisterCommandCode("zwavenetworkheal", [this](auto &&session, auto &&req, auto &&root) { Cmd_ZWaveNetworkHeal(session, req, root); });
			RegisterCommandCode("zwavenodeheal", [this](auto &&session, auto &&req, auto &&root) { Cmd_ZWaveNodeHeal(session, req, root); });
			RegisterCommandCode("zwavenetworkinfo", [this](auto &&session, auto &&req, auto &&root) { Cmd_ZWaveNetworkInfo(session, req, root); });
			RegisterCommandCode("zwaveremovegroupnode", [this](auto &&session, auto &&req, auto &&root) { Cmd_ZWaveRemoveGroupNode(session, req, root); });
			RegisterCommandCode("zwaveaddgroupnode", [this](auto &&session, auto &&req, auto &&root) { Cmd_ZWaveAddGroupNode(session, req, root); });
			RegisterCommandCode("zwavegroupinfo", [this](auto &&session, auto &&req, auto &&root) { Cmd_ZWaveGroupInfo(session, req, root); });
			RegisterCommandCode("zwavecancel", [this](auto &&session, auto &&req, auto &&root) { Cmd_ZWaveCancel(session, req, root); });
			RegisterCommandCode("applyzwavenodeconfig", [this](auto &&session, auto &&req, auto &&root) { Cmd_ApplyZWaveNodeConfig(session, req, root); });
			RegisterCommandCode("zwavehasnodefailed", [this](auto &&session, auto &&req, auto &&root) { Cmd_ZWaveHasNodeFailed(session, req, root); });
			RegisterCommandCode("zwavereplacefailednode", [this](auto &&session, auto &&req, auto &&root) { Cmd_ZWaveReplaceFailedNode(session, req, root); });
			RegisterCommandCode("requestzwavenodeconfig", [this](auto &&session, auto &&req, auto &&root) { Cmd_ZWaveRequestNodeConfig(session, req, root); });
			RegisterCommandCode("requestzwavenodeinfo", [this](auto &&session, auto &&req, auto &&root) { Cmd_ZWaveRequestNodeInfo(session, req, root); });
			RegisterCommandCode("zwavestatecheck", [this](auto &&session, auto &&req, auto &&root) { Cmd_ZWaveStateCheck(session, req, root); });
			RegisterCommandCode("zwavereceiveconfigurationfromothercontroller",
					    [this](auto &&session, auto &&req, auto &&root) { Cmd_ZWaveReceiveConfigurationFromOtherController(session, req, root); });
			RegisterCommandCode("zwavesendconfigurationtosecondcontroller",
					    [this](auto &&session, auto &&req, auto &&root) { Cmd_ZWaveSendConfigurationToSecondaryController(session, req, root); });
			RegisterCommandCode("zwavetransferprimaryrole", [this](auto &&session, auto &&req, auto &&root) { Cmd_ZWaveTransferPrimaryRole(session, req, root); });
			RegisterCommandCode("zwavestartusercodeenrollmentmode", [this](auto &&session, auto &&req, auto &&root) { Cmd_ZWaveSetUserCodeEnrollmentMode(session, req, root); });
			RegisterCommandCode("zwavegetusercodes", [this](auto &&session, auto &&req, auto &&root) { Cmd_ZWaveGetNodeUserCodes(session, req, root); });
			RegisterCommandCode("zwaveremoveusercode", [this](auto &&session, auto &&req, auto &&root) { Cmd_ZWaveRemoveUserCode(session, req, root); });
			RegisterCommandCode("zwavegetbatterylevels", [this](auto &&session, auto &&req, auto &&root) { Cmd_ZWaveGetBatteryLevels(session, req, root); });

			m_pWebEm->RegisterPageCode("/zwavegetconfig.php", [this](auto &&session, auto &&req, auto &&rep) { ZWaveGetConfigFile(session, req, rep); });

			m_pWebEm->RegisterPageCode("/ozwcp/poll.xml", [this](auto &&session, auto &&req, auto &&rep) { ZWaveCPPollXml(session, req, rep); });
			m_pWebEm->RegisterPageCode("/ozwcp/cp.html", [this](auto &&session, auto &&req, auto &&rep) { ZWaveCPIndex(session, req, rep); });
			m_pWebEm->RegisterPageCode("/ozwcp/confparmpost.html", [this](auto &&session, auto &&req, auto &&rep) { ZWaveCPNodeGetConf(session, req, rep); });
			m_pWebEm->RegisterPageCode("/ozwcp/refreshpost.html", [this](auto &&session, auto &&req, auto &&rep) { ZWaveCPNodeGetValues(session, req, rep); });
			m_pWebEm->RegisterPageCode("/ozwcp/valuepost.html", [this](auto &&session, auto &&req, auto &&rep) { ZWaveCPNodeSetValue(session, req, rep); });
			m_pWebEm->RegisterPageCode("/ozwcp/buttonpost.html", [this](auto &&session, auto &&req, auto &&rep) { ZWaveCPNodeSetButton(session, req, rep); });
			m_pWebEm->RegisterPageCode("/ozwcp/admpost.html", [this](auto &&session, auto &&req, auto &&rep) { ZWaveCPAdminCommand(session, req, rep); });
			m_pWebEm->RegisterPageCode("/ozwcp/nodepost.html", [this](auto &&session, auto &&req, auto &&rep) { ZWaveCPNodeChange(session, req, rep); });
			m_pWebEm->RegisterPageCode("/ozwcp/thpost.html", [this](auto &&session, auto &&req, auto &&rep) { ZWaveCPTestHeal(session, req, rep); });
			m_pWebEm->RegisterPageCode("/ozwcp/topopost.html", [this](auto &&session, auto &&req, auto &&rep) { ZWaveCPGetTopo(session, req, rep); });
			m_pWebEm->RegisterPageCode("/ozwcp/statpost.html", [this](auto &&session, auto &&req, auto &&rep) { ZWaveCPGetStats(session, req, rep); });
			m_pWebEm->RegisterPageCode("/ozwcp/grouppost.html", [this](auto &&session, auto &&req, auto &&rep) { ZWaveCPSetGroup(session, req, rep); });
			//
			// pollpost.html
			RegisterRType("openzwavenodes", [this](auto &&session, auto &&req, auto &&root) { RType_OpenZWaveNodes(session, req, root); });
#endif

			m_pWebEm->RegisterWhitelistURLString("/html5.appcache");
			m_pWebEm->RegisterWhitelistURLString("/images/floorplans/plan");

			// Start normal worker thread
			m_bDoStop = false;
			m_thread = std::make_shared<std::thread>([this] { Do_Work(); });
			std::string server_name = "WebServer_" + settings.listening_port;
			SetThreadName(m_thread->native_handle(), server_name.c_str());
			return (m_thread != nullptr);
		}

		void CWebServer::StopServer()
		{
			m_bDoStop = true;
			try
			{
				if (m_pWebEm == nullptr)
					return;
				m_pWebEm->Stop();
				if (m_thread)
				{
					m_thread->join();
					m_thread.reset();
				}
				delete m_pWebEm;
				m_pWebEm = nullptr;
			}
			catch (...)
			{
			}
		}

		void CWebServer::SetWebCompressionMode(const _eWebCompressionMode gzmode)
		{
			if (m_pWebEm == nullptr)
				return;
			m_pWebEm->SetWebCompressionMode(gzmode);
		}

		void CWebServer::SetAuthenticationMethod(const _eAuthenticationMethod amethod)
		{
			if (m_pWebEm == nullptr)
				return;
			m_pWebEm->SetAuthenticationMethod(amethod);
		}

		void CWebServer::SetWebTheme(const std::string &themename)
		{
			if (m_pWebEm == nullptr)
				return;
			m_pWebEm->SetWebTheme(themename);
		}

		void CWebServer::SetWebRoot(const std::string &webRoot)
		{
			if (m_pWebEm == nullptr)
				return;
			m_pWebEm->SetWebRoot(webRoot);
		}

		void CWebServer::RegisterCommandCode(const char *idname, const webserver_response_function &ResponseFunction, bool bypassAuthentication)
		{
			m_webcommands.insert(std::pair<std::string, webserver_response_function>(std::string(idname), ResponseFunction));
			if (bypassAuthentication)
			{
				m_pWebEm->RegisterWhitelistCommandsString(idname);
			}
		}

		void CWebServer::RegisterRType(const char *idname, const webserver_response_function &ResponseFunction)
		{
			m_webrtypes.insert(std::pair<std::string, webserver_response_function>(std::string(idname), ResponseFunction));
		}

		void CWebServer::HandleRType(const std::string &rtype, WebEmSession &session, const request &req, Json::Value &root)
		{
			auto pf = m_webrtypes.find(rtype);
			if (pf != m_webrtypes.end())
			{
				pf->second(session, req, root);
			}
		}

		void CWebServer::GetAppCache(WebEmSession &session, const request &req, reply &rep)
		{
			std::string response;
			if (g_bDontCacheWWW)
			{
				return;
			}
			// Return the appcache file (dynamically generated)
			std::string sLine;
			std::string filename = szWWWFolder + "/html5.appcache";

			std::string sWebTheme = "default";
			m_sql.GetPreferencesVar("WebTheme", sWebTheme);

			// Get Dynamic Theme Files
			std::map<std::string, int> _ThemeFiles;
			GetDirFilesRecursive(szWWWFolder + "/styles/" + sWebTheme + "/", _ThemeFiles);

			// Get Dynamic Floorplan Images from database
			std::map<std::string, int> _FloorplanFiles;
			std::vector<std::vector<std::string>> result;
			result = m_sql.safe_query("SELECT ID FROM Floorplans ORDER BY [Order]");
			if (!result.empty())
			{
				for (const auto &sd : result)
				{
					std::string ImageURL = "images/floorplans/plan?idx=" + sd[0];
					_FloorplanFiles[ImageURL] = 1;
				}
			}

			std::ifstream is(filename.c_str());
			if (is)
			{
				while (!is.eof())
				{
					getline(is, sLine);
					if (!sLine.empty())
					{
						if (sLine.find("#BuildHash") != std::string::npos)
						{
							stdreplace(sLine, "#BuildHash", szAppHash);
						}
						else if (sLine.find("#ThemeFiles") != std::string::npos)
						{
							response += "#Theme=" + sWebTheme + '\n';
							// Add all theme files
							for (const auto &file : _ThemeFiles)
							{
								std::string tfname = file.first.substr(szWWWFolder.size() + 1);
								response += tfname + '\n';
							}
							continue;
						}
						else if (sLine.find("#Floorplans") != std::string::npos)
						{
							// Add all floorplans
							for (const auto &file : _FloorplanFiles)
							{
								std::string tfname = file.first;
								response += tfname + '\n';
							}
							continue;
						}
						else if (sLine.find("#SwitchIcons") != std::string::npos)
						{
							// Add database switch icons
							for (const auto &db : m_custom_light_icons)
							{
								if (db.idx >= 100)
								{
									std::string IconFile16 = db.RootFile + ".png";
									std::string IconFile48On = db.RootFile + "48_On.png";
									std::string IconFile48Off = db.RootFile + "48_Off.png";

									response += "images/" + CURLEncode::URLEncode(IconFile16) + '\n';
									response += "images/" + CURLEncode::URLEncode(IconFile48On) + '\n';
									response += "images/" + CURLEncode::URLEncode(IconFile48Off) + '\n';
								}
							}
						}
					}
					response += sLine + '\n';
				}
			}
			reply::set_content(&rep, response);
		}

		void CWebServer::GetJSonPage(WebEmSession &session, const request &req, reply &rep)
		{
			Json::Value root;
			root["status"] = "ERR";

			std::string rtype = request::findValue(&req, "type");
			if (rtype == "command")
			{
				std::string cparam = request::findValue(&req, "param");
				if (cparam.empty())
				{
					cparam = request::findValue(&req, "dparam");
					if (cparam.empty())
					{
						goto exitjson;
					}
				}
				if (cparam == "dologout")
				{
					session.forcelogin = true;
					root["status"] = "OK";
					root["title"] = "Logout";
					goto exitjson;
				}
				_log.Debug(DEBUG_WEBSERVER, "WEBS GetJSon :%s :%s ", cparam.c_str(), req.uri.c_str());
				HandleCommand(cparam, session, req, root);
			} //(rtype=="command")
			else
			{
				HandleRType(rtype, session, req, root);
			}
		exitjson:
			std::string jcallback = request::findValue(&req, "jsoncallback");
			if (jcallback.empty())
			{
				reply::set_content(&rep, root.toStyledString());
				return;
			}
			reply::set_content(&rep, "var data=" + root.toStyledString() + '\n' + jcallback + "(data);");
		}

		void CWebServer::Cmd_GetLanguage(WebEmSession &session, const request &req, Json::Value &root)
		{
			std::string sValue;
			if (m_sql.GetPreferencesVar("Language", sValue))
			{
				root["status"] = "OK";
				root["title"] = "GetLanguage";
				root["language"] = sValue;
			}
		}

		void CWebServer::Cmd_GetLanguages(WebEmSession &session, const request &req, Json::Value &root)
		{
			root["title"] = "GetLanguages";
			for (auto &lang : guiLanguage)
			{
				root["result"][lang.first] = lang.second;
			}
			root["status"] = "OK";
		}

		void CWebServer::Cmd_GetThemes(WebEmSession &session, const request &req, Json::Value &root)
		{
			root["status"] = "OK";
			root["title"] = "GetThemes";
			m_mainworker.GetAvailableWebThemes();
			int ii = 0;
			for (const auto &theme : m_mainworker.m_webthemes)
			{
				root["result"][ii]["theme"] = theme;
				ii++;
			}
		}

		void CWebServer::Cmd_GetTitle(WebEmSession &session, const request &req, Json::Value &root)
		{
			std::string sValue;
			root["status"] = "OK";
			root["title"] = "GetTitle";
			if (m_sql.GetPreferencesVar("Title", sValue))
				root["Title"] = sValue;
			else
				root["Title"] = "Domoticz";
		}

		// Depricated : This 'page' should not be used anymore. Use command instead
		void CWebServer::PostLoginCheck(WebEmSession &session, const request &req, reply &rep)
		{
			_log.Log(LOG_NORM, "Depricated: Page LoginCheck! Use command instead!");

			Json::Value root;
			Cmd_LoginCheck(session, req, root);

			std::string jcallback = request::findValue(&req, "jsoncallback");
			if (jcallback.empty())
			{
				reply::set_content(&rep, root.toStyledString());
				return;
			}
			reply::set_content(&rep, "var data=" + root.toStyledString() + '\n' + jcallback + "(data);");
		}

		void CWebServer::Cmd_LoginCheck(WebEmSession &session, const request &req, Json::Value &root)
		{
			std::string tmpusrname = request::findValue(&req, "username");
			std::string tmpusrpass = request::findValue(&req, "password");
			if ((tmpusrname.empty()) || (tmpusrpass.empty()))
				return;

			std::string rememberme = request::findValue(&req, "rememberme");

			std::string usrname;
			std::string usrpass;
			if (request_handler::url_decode(tmpusrname, usrname))
			{
				if (request_handler::url_decode(tmpusrpass, usrpass))
				{
					usrname = base64_decode(usrname);
					int iUser = FindUser(usrname.c_str());
					if (iUser == -1)
					{
						// log brute force attack
						_log.Log(LOG_ERROR, "Failed login attempt from %s for user '%s' !", session.remote_host.c_str(), usrname.c_str());
						return;
					}
					if (m_users[iUser].Password != usrpass)
					{
						// log brute force attack
						_log.Log(LOG_ERROR, "Failed login attempt from %s for '%s' !", session.remote_host.c_str(), m_users[iUser].Username.c_str());
						return;
					}
					_log.Log(LOG_STATUS, "Login successful from %s for user '%s'", session.remote_host.c_str(), m_users[iUser].Username.c_str());
					root["status"] = "OK";
					root["version"] = szAppVersion;
					root["title"] = "logincheck";
					session.isnew = true;
					session.username = m_users[iUser].Username;
					session.rights = m_users[iUser].userrights;
					session.rememberme = (rememberme == "true");
					root["user"] = session.username;
					root["rights"] = session.rights;
				}
			}
		}

		void CWebServer::Cmd_GetHardwareTypes(WebEmSession &session, const request &req, Json::Value &root)
		{
			if (session.rights != 2)
			{
				session.reply_status = reply::forbidden;
				return; // Only admin user allowed
			}

			root["status"] = "OK";
			root["title"] = "GetHardwareTypes";
			std::map<std::string, int> _htypes;
			for (int ii = 0; ii < HTYPE_END; ii++)
			{
				bool bDoAdd = true;
#ifndef _DEBUG
#ifdef WIN32
				if ((ii == HTYPE_RaspberryBMP085) || (ii == HTYPE_RaspberryHTU21D) || (ii == HTYPE_RaspberryTSL2561) || (ii == HTYPE_RaspberryPCF8574) ||
				    (ii == HTYPE_RaspberryBME280) || (ii == HTYPE_RaspberryMCP23017))
				{
					bDoAdd = false;
				}
				else
				{
#ifndef WITH_LIBUSB
					if ((ii == HTYPE_VOLCRAFTCO20) || (ii == HTYPE_TE923))
					{
						bDoAdd = false;
					}
#endif
				}
#endif
#endif
#ifndef WITH_OPENZWAVE
				if (ii == HTYPE_OpenZWave)
					bDoAdd = false;
#endif
#ifndef WITH_GPIO
				if (ii == HTYPE_RaspberryGPIO)
				{
					bDoAdd = false;
				}

				if (ii == HTYPE_SysfsGpio)
				{
					bDoAdd = false;
				}
#endif
				if (ii == HTYPE_PythonPlugin)
					bDoAdd = false;

				if (bDoAdd)
					_htypes[Hardware_Type_Desc(ii)] = ii;
			}

			// return a sorted hardware list
			int ii = 0;
			for (const auto &type : _htypes)
			{
				root["result"][ii]["idx"] = type.second;
				root["result"][ii]["name"] = type.first;
				ii++;
			}

#ifdef ENABLE_PYTHON
			// Append Plugin list as well
			PluginList(root["result"]);
#endif
		}

		void CWebServer::Cmd_AddHardware(WebEmSession &session, const request &req, Json::Value &root)
		{
			if (session.rights != 2)
			{
				session.reply_status = reply::forbidden;
				return; // Only admin user allowed
			}

			std::string name = HTMLSanitizer::Sanitize(CURLEncode::URLDecode(request::findValue(&req, "name")));
			std::string senabled = request::findValue(&req, "enabled");
			std::string shtype = request::findValue(&req, "htype");
			std::string loglevel = request::findValue(&req, "loglevel");
			std::string address = HTMLSanitizer::Sanitize(request::findValue(&req, "address"));
			std::string sport = request::findValue(&req, "port");
			std::string username = HTMLSanitizer::Sanitize(CURLEncode::URLDecode(request::findValue(&req, "username")));
			std::string password = HTMLSanitizer::Sanitize(CURLEncode::URLDecode(request::findValue(&req, "password")));
			std::string extra = CURLEncode::URLDecode(request::findValue(&req, "extra"));
			std::string sdatatimeout = request::findValue(&req, "datatimeout");
			if ((name.empty()) || (senabled.empty()) || (shtype.empty()))
				return;
			_eHardwareTypes htype = (_eHardwareTypes)atoi(shtype.c_str());

			int iDataTimeout = atoi(sdatatimeout.c_str());
			int mode1 = 0;
			int mode2 = 0;
			int mode3 = 0;
			int mode4 = 0;
			int mode5 = 0;
			int mode6 = 0;
			int port = atoi(sport.c_str());
			uint32_t iLogLevelEnabled = (uint32_t)atoi(loglevel.c_str());
			std::string mode1Str = request::findValue(&req, "Mode1");
			if (!mode1Str.empty())
			{
				mode1 = atoi(mode1Str.c_str());
			}
			std::string mode2Str = request::findValue(&req, "Mode2");
			if (!mode2Str.empty())
			{
				mode2 = atoi(mode2Str.c_str());
			}
			std::string mode3Str = request::findValue(&req, "Mode3");
			if (!mode3Str.empty())
			{
				mode3 = atoi(mode3Str.c_str());
			}
			std::string mode4Str = request::findValue(&req, "Mode4");
			if (!mode4Str.empty())
			{
				mode4 = atoi(mode4Str.c_str());
			}
			std::string mode5Str = request::findValue(&req, "Mode5");
			if (!mode5Str.empty())
			{
				mode5 = atoi(mode5Str.c_str());
			}
			std::string mode6Str = request::findValue(&req, "Mode6");
			if (!mode6Str.empty())
			{
				mode6 = atoi(mode6Str.c_str());
			}

			if (IsSerialDevice(htype))
			{
				// USB/System
				if (sport.empty())
					return; // need to have a serial port

				if (htype == HTYPE_TeleinfoMeter)
				{
					// Teleinfo always has decimals. Chances to have a P1 and a Teleinfo device on the same
					// Domoticz instance are very low as both are national standards (NL and FR)
					m_sql.UpdatePreferencesVar("SmartMeterType", 0);
				}
			}
			else if (IsNetworkDevice(htype))
			{
				// Lan
				if (address.empty() || port == 0)
					return;

				if (htype == HTYPE_MySensorsMQTT || htype == HTYPE_MQTT)
				{
					std::string modeqStr = request::findValue(&req, "mode1");
					if (!modeqStr.empty())
					{
						mode1 = atoi(modeqStr.c_str());
					}
				}

				if (htype == HTYPE_ECODEVICES || htype == HTYPE_TeleinfoMeterTCP)
				{
					// EcoDevices and Teleinfo always have decimals. Chances to have a P1 and a EcoDevice/Teleinfo
					//  device on the same Domoticz instance are very low as both are national standards (NL and FR)
					m_sql.UpdatePreferencesVar("SmartMeterType", 0);
				}
			}
			else if (htype == HTYPE_DomoticzInternal)
			{
				// DomoticzInternal cannot be added manually
				return;
			}
			else if (htype == HTYPE_Domoticz)
			{
				// Remote Domoticz
				if (address.empty() || port == 0)
					return;
			}
			else if (htype == HTYPE_TE923)
			{
				// all fine here!
			}
			else if (htype == HTYPE_VOLCRAFTCO20)
			{
				// all fine here!
			}
			else if (htype == HTYPE_System)
			{
				// There should be only one
				std::vector<std::vector<std::string>> result;
				result = m_sql.safe_query("SELECT ID FROM Hardware WHERE (Type==%d)", HTYPE_System);
				if (!result.empty())
					return;
			}
			else if (htype == HTYPE_1WIRE)
			{
				// all fine here!
			}
			else if (htype == HTYPE_Rtl433)
			{
				// all fine here!
			}
			else if (htype == HTYPE_Pinger)
			{
				// all fine here!
			}
			else if (htype == HTYPE_Kodi)
			{
				// all fine here!
			}
			else if (htype == HTYPE_PanasonicTV)
			{
				// all fine here!
			}
			else if (htype == HTYPE_LogitechMediaServer)
			{
				// all fine here!
			}
			else if (htype == HTYPE_RaspberryBMP085)
			{
				// all fine here!
			}
			else if (htype == HTYPE_RaspberryHTU21D)
			{
				// all fine here!
			}
			else if (htype == HTYPE_RaspberryTSL2561)
			{
				// all fine here!
			}
			else if (htype == HTYPE_RaspberryBME280)
			{
				// all fine here!
			}
			else if (htype == HTYPE_RaspberryMCP23017)
			{
				// all fine here!
			}
			else if (htype == HTYPE_Dummy)
			{
				// all fine here!
			}
			else if (htype == HTYPE_Tellstick)
			{
				// all fine here!
			}
			else if (htype == HTYPE_EVOHOME_SCRIPT || htype == HTYPE_EVOHOME_SERIAL || htype == HTYPE_EVOHOME_WEB || htype == HTYPE_EVOHOME_TCP)
			{
				// all fine here!
			}
			else if (htype == HTYPE_PiFace)
			{
				// all fine here!
			}
			else if (htype == HTYPE_HTTPPOLLER)
			{
				// all fine here!
			}
			else if (htype == HTYPE_BleBox)
			{
				// all fine here!
			}
			else if (htype == HTYPE_HEOS)
			{
				// all fine here!
			}
			else if (htype == HTYPE_Yeelight)
			{
				// all fine here!
			}
			else if (htype == HTYPE_XiaomiGateway)
			{
				// all fine here!
			}
			else if (htype == HTYPE_Arilux)
			{
				// all fine here!
			}
			else if (htype == HTYPE_USBtinGateway)
			{
				// All fine here
			}
			else if (htype == HTYPE_BuienRadar)
			{
				// All fine here
			}
			else if ((htype == HTYPE_Wunderground) || (htype == HTYPE_DarkSky) || (htype == HTYPE_AccuWeather) || (htype == HTYPE_OpenWeatherMap) || (htype == HTYPE_ICYTHERMOSTAT) ||
				 (htype == HTYPE_TOONTHERMOSTAT) || (htype == HTYPE_AtagOne) || (htype == HTYPE_PVOUTPUT_INPUT) || (htype == HTYPE_NEST) || (htype == HTYPE_ANNATHERMOSTAT) ||
				 (htype == HTYPE_THERMOSMART) || (htype == HTYPE_Tado) || (htype == HTYPE_Tesla) || (htype == HTYPE_Mercedes) || (htype == HTYPE_Netatmo))
			{
				if ((username.empty()) || (password.empty()))
					return;
			}
			else if (htype == HTYPE_SolarEdgeAPI)
			{
				if ((username.empty()))
					return;
			}
			else if (htype == HTYPE_Nest_OAuthAPI)
			{
				if ((username.empty()) && (extra == "||"))
					return;
			}
			else if (htype == HTYPE_SBFSpot)
			{
				if (username.empty())
					return;
			}
			else if (htype == HTYPE_HARMONY_HUB)
			{
				if ((address.empty() || port == 0))
					return;
			}
			else if (htype == HTYPE_Philips_Hue)
			{
				if ((username.empty()) || (address.empty() || port == 0))
					return;
				if (port == 0)
					port = 80;
			}
			else if (htype == HTYPE_WINDDELEN)
			{
				std::string mill_id = request::findValue(&req, "Mode1");
				if ((mill_id.empty()) || (sport.empty()))

					return;
				mode1 = atoi(mill_id.c_str());
			}
			else if (htype == HTYPE_Honeywell)
			{
				// all fine here!
			}
			else if (htype == HTYPE_RaspberryGPIO)
			{
				// all fine here!
			}
			else if (htype == HTYPE_SysfsGpio)
			{
				// all fine here!
			}
			else if (htype == HTYPE_OpenWebNetTCP)
			{
				// All fine here
			}
			else if (htype == HTYPE_Daikin)
			{
				// All fine here
			}
			else if (htype == HTYPE_GoodweAPI)
			{
				if (username.empty())
					return;
			}
			else if (htype == HTYPE_PythonPlugin)
			{
				// All fine here
			}
			else if (htype == HTYPE_RaspberryPCF8574)
			{
				// All fine here
			}
			else if (htype == HTYPE_OpenWebNetUSB)
			{
				// All fine here
			}
			else if (htype == HTYPE_IntergasInComfortLAN2RF)
			{
				// All fine here
			}
			else if (htype == HTYPE_EnphaseAPI)
			{
				// All fine here
			}
			else if (htype == HTYPE_EcoCompteur)
			{
				// all fine here!
			}
			else if (htype == HTYPE_Meteorologisk)
			{
				// all fine here!
			}
			else if (htype == HTYPE_AirconWithMe)
			{
				// all fine here!
			}
			else
				return;

			root["status"] = "OK";
			root["title"] = "AddHardware";

			std::vector<std::vector<std::string>> result;

			if (htype == HTYPE_Domoticz)
			{
				if (password.size() != 32)
				{
					password = GenerateMD5Hash(password);
				}
			}
			else if ((htype == HTYPE_S0SmartMeterUSB) || (htype == HTYPE_S0SmartMeterTCP))
			{
				extra = "0;1000;0;1000;0;1000;0;1000;0;1000";
			}
			else if (htype == HTYPE_Pinger)
			{
				mode1 = 30;
				mode2 = 1000;
			}
			else if (htype == HTYPE_Kodi)
			{
				mode1 = 30;
				mode2 = 1000;
			}
			else if (htype == HTYPE_PanasonicTV)
			{
				mode1 = 30;
				mode2 = 1000;
			}
			else if (htype == HTYPE_LogitechMediaServer)
			{
				mode1 = 30;
				mode2 = 1000;
			}
			else if (htype == HTYPE_HEOS)
			{
				mode1 = 30;
				mode2 = 1000;
			}
			else if (htype == HTYPE_Tellstick)
			{
				mode1 = 4;
				mode2 = 500;
			}

			if (htype == HTYPE_HTTPPOLLER)
			{
				m_sql.safe_query("INSERT INTO Hardware (Name, Enabled, Type, LogLevel, Address, Port, SerialPort, Username, Password, Extra, Mode1, Mode2, Mode3, Mode4, Mode5, Mode6, "
						 "DataTimeout) VALUES ('%q',%d, %d, %d,'%q',%d,'%q','%q','%q','%q','%q','%q', '%q', '%q', '%q', '%q', %d)",
						 name.c_str(), (senabled == "true") ? 1 : 0, htype, iLogLevelEnabled, address.c_str(), port, sport.c_str(), username.c_str(), password.c_str(),
						 extra.c_str(), mode1Str.c_str(), mode2Str.c_str(), mode3Str.c_str(), mode4Str.c_str(), mode5Str.c_str(), mode6Str.c_str(), iDataTimeout);
			}
			else if (htype == HTYPE_PythonPlugin)
			{
				sport = request::findValue(&req, "serialport");
				m_sql.safe_query("INSERT INTO Hardware (Name, Enabled, Type, LogLevel, Address, Port, SerialPort, Username, Password, Extra, Mode1, Mode2, Mode3, Mode4, Mode5, Mode6, "
						 "DataTimeout) VALUES ('%q',%d, %d, %d,'%q',%d,'%q','%q','%q','%q','%q','%q', '%q', '%q', '%q', '%q', %d)",
						 name.c_str(), (senabled == "true") ? 1 : 0, htype, iLogLevelEnabled, address.c_str(), port, sport.c_str(), username.c_str(), password.c_str(),
						 extra.c_str(), mode1Str.c_str(), mode2Str.c_str(), mode3Str.c_str(), mode4Str.c_str(), mode5Str.c_str(), mode6Str.c_str(), iDataTimeout);
			}
			else if ((htype == HTYPE_RFXtrx433) || (htype == HTYPE_RFXtrx868))
			{
				// No Extra field here, handled in CWebServer::SetRFXCOMMode
				m_sql.safe_query("INSERT INTO Hardware (Name, Enabled, Type, LogLevel, Address, Port, SerialPort, Username, Password, Mode1, Mode2, Mode3, Mode4, Mode5, Mode6, "
						 "DataTimeout) VALUES ('%q',%d, %d, %d,'%q',%d,'%q','%q','%q',%d,%d,%d,%d,%d,%d,%d)",
						 name.c_str(), (senabled == "true") ? 1 : 0, htype, iLogLevelEnabled, address.c_str(), port, sport.c_str(), username.c_str(), password.c_str(), mode1,
						 mode2, mode3, mode4, mode5, mode6, iDataTimeout);
				extra = "0";
			}
			else
			{
				m_sql.safe_query("INSERT INTO Hardware (Name, Enabled, Type, LogLevel, Address, Port, SerialPort, Username, Password, Extra, Mode1, Mode2, Mode3, Mode4, Mode5, Mode6, "
						 "DataTimeout) VALUES ('%q',%d, %d, %d,'%q',%d,'%q','%q','%q','%q',%d,%d,%d,%d,%d,%d,%d)",
						 name.c_str(), (senabled == "true") ? 1 : 0, htype, iLogLevelEnabled, address.c_str(), port, sport.c_str(), username.c_str(), password.c_str(),
						 extra.c_str(), mode1, mode2, mode3, mode4, mode5, mode6, iDataTimeout);
			}

			// add the device for real in our system
			result = m_sql.safe_query("SELECT MAX(ID) FROM Hardware");
			if (!result.empty())
			{
				std::vector<std::string> sd = result[0];
				int ID = atoi(sd[0].c_str());

				root["idx"] = sd[0].c_str(); // OTO output the created ID for easier management on the caller side (if automated)

				m_mainworker.AddHardwareFromParams(ID, name, (senabled == "true") ? true : false, htype, iLogLevelEnabled, address, port, sport, username, password, extra, mode1,
								   mode2, mode3, mode4, mode5, mode6, iDataTimeout, true);
			}
		}

		void CWebServer::Cmd_UpdateHardware(WebEmSession &session, const request &req, Json::Value &root)
		{
			if (session.rights != 2)
			{
				session.reply_status = reply::forbidden;
				return; // Only admin user allowed
			}

			std::string idx = request::findValue(&req, "idx");
			if (idx.empty())
				return;
			std::string name = HTMLSanitizer::Sanitize(CURLEncode::URLDecode(request::findValue(&req, "name")));
			std::string senabled = request::findValue(&req, "enabled");
			std::string shtype = request::findValue(&req, "htype");
			std::string loglevel = request::findValue(&req, "loglevel");
			std::string address = HTMLSanitizer::Sanitize(request::findValue(&req, "address"));
			std::string sport = request::findValue(&req, "port");
			std::string username = HTMLSanitizer::Sanitize(CURLEncode::URLDecode(request::findValue(&req, "username")));
			std::string password = CURLEncode::URLDecode(request::findValue(&req, "password"));
			std::string extra = HTMLSanitizer::Sanitize(CURLEncode::URLDecode(request::findValue(&req, "extra")));
			std::string sdatatimeout = request::findValue(&req, "datatimeout");

			if ((name.empty()) || (senabled.empty()) || (shtype.empty()))
				return;

			int mode1 = atoi(request::findValue(&req, "Mode1").c_str());
			int mode2 = atoi(request::findValue(&req, "Mode2").c_str());
			int mode3 = atoi(request::findValue(&req, "Mode3").c_str());
			int mode4 = atoi(request::findValue(&req, "Mode4").c_str());
			int mode5 = atoi(request::findValue(&req, "Mode5").c_str());
			int mode6 = atoi(request::findValue(&req, "Mode6").c_str());

			bool bEnabled = (senabled == "true") ? true : false;

			_eHardwareTypes htype = (_eHardwareTypes)atoi(shtype.c_str());
			int iDataTimeout = atoi(sdatatimeout.c_str());

			int port = atoi(sport.c_str());
			uint32_t iLogLevelEnabled = (uint32_t)atoi(loglevel.c_str());

			bool bIsSerial = false;

			if (IsSerialDevice(htype))
			{
				// USB/System
				bIsSerial = true;
				if (bEnabled)
				{
					if (sport.empty())
						return; // need to have a serial port
				}
			}
			else if ((htype == HTYPE_RFXLAN) || (htype == HTYPE_P1SmartMeterLAN) || (htype == HTYPE_YouLess) || (htype == HTYPE_OpenThermGatewayTCP) || (htype == HTYPE_LimitlessLights) ||
				 (htype == HTYPE_SolarEdgeTCP) || (htype == HTYPE_WOL) || (htype == HTYPE_S0SmartMeterTCP) || (htype == HTYPE_ECODEVICES) || (htype == HTYPE_Mochad) ||
				 (htype == HTYPE_MySensorsTCP) || (htype == HTYPE_MySensorsMQTT) || (htype == HTYPE_MQTT) || (htype == HTYPE_TTN_MQTT) || (htype == HTYPE_FRITZBOX) ||
				 (htype == HTYPE_ETH8020) || (htype == HTYPE_Sterbox) || (htype == HTYPE_KMTronicTCP) || (htype == HTYPE_KMTronicUDP) || (htype == HTYPE_SOLARMAXTCP) ||
				 (htype == HTYPE_RelayNet) || (htype == HTYPE_SatelIntegra) || (htype == HTYPE_eHouseTCP) || (htype == HTYPE_RFLINKTCP) ||
				 (htype == HTYPE_Comm5TCP || (htype == HTYPE_Comm5SMTCP) || (htype == HTYPE_CurrentCostMeterLAN)) || (htype == HTYPE_NefitEastLAN) ||
				 (htype == HTYPE_DenkoviHTTPDevices) || (htype == HTYPE_DenkoviTCPDevices) || (htype == HTYPE_Ec3kMeterTCP) || (htype == HTYPE_MultiFun) ||
				 (htype == HTYPE_ZIBLUETCP) || (htype == HTYPE_OnkyoAVTCP) || (htype == HTYPE_OctoPrint) || (htype == HTYPE_TeleinfoMeterTCP))
			{
				// Lan
				if (address.empty())
					return;
			}
			else if (htype == HTYPE_DomoticzInternal)
			{
				// DomoticzInternal cannot be updated
				return;
			}
			else if (htype == HTYPE_Domoticz)
			{
				// Remote Domoticz
				if (address.empty())
					return;
			}
			else if (htype == HTYPE_System)
			{
				// There should be only one, and with this ID
				std::vector<std::vector<std::string>> result;
				result = m_sql.safe_query("SELECT ID FROM Hardware WHERE (Type==%d)", HTYPE_System);
				if (!result.empty())
				{
					int hID = atoi(result[0][0].c_str());
					int aID = atoi(idx.c_str());
					if (hID != aID)
						return;
				}
			}
			else if (htype == HTYPE_TE923)
			{
				// All fine here
			}
			else if (htype == HTYPE_VOLCRAFTCO20)
			{
				// All fine here
			}
			else if (htype == HTYPE_1WIRE)
			{
				// All fine here
			}
			else if (htype == HTYPE_Pinger)
			{
				// All fine here
			}
			else if (htype == HTYPE_Kodi)
			{
				// All fine here
			}
			else if (htype == HTYPE_PanasonicTV)
			{
				// All fine here
			}
			else if (htype == HTYPE_LogitechMediaServer)
			{
				// All fine here
			}
			else if (htype == HTYPE_RaspberryBMP085)
			{
				// All fine here
			}
			else if (htype == HTYPE_RaspberryHTU21D)
			{
				// All fine here
			}
			else if (htype == HTYPE_RaspberryTSL2561)
			{
				// All fine here
			}
			else if (htype == HTYPE_RaspberryBME280)
			{
				// All fine here
			}
			else if (htype == HTYPE_RaspberryMCP23017)
			{
				// all fine here!
			}
			else if (htype == HTYPE_Dummy)
			{
				// All fine here
			}
			else if (htype == HTYPE_EVOHOME_SCRIPT || htype == HTYPE_EVOHOME_SERIAL || htype == HTYPE_EVOHOME_WEB || htype == HTYPE_EVOHOME_TCP)
			{
				// All fine here
			}
			else if (htype == HTYPE_PiFace)
			{
				// All fine here
			}
			else if (htype == HTYPE_HTTPPOLLER)
			{
				// all fine here!
			}
			else if (htype == HTYPE_BleBox)
			{
				// All fine here
			}
			else if (htype == HTYPE_HEOS)
			{
				// All fine here
			}
			else if (htype == HTYPE_Yeelight)
			{
				// All fine here
			}
			else if (htype == HTYPE_XiaomiGateway)
			{
				// All fine here
			}
			else if (htype == HTYPE_Arilux)
			{
				// All fine here
			}
			else if (htype == HTYPE_USBtinGateway)
			{
				// All fine here
			}
			else if (htype == HTYPE_BuienRadar)
			{
				// All fine here
			}
			else if ((htype == HTYPE_Wunderground) || (htype == HTYPE_DarkSky) || (htype == HTYPE_AccuWeather) || (htype == HTYPE_OpenWeatherMap) || (htype == HTYPE_ICYTHERMOSTAT) ||
				 (htype == HTYPE_TOONTHERMOSTAT) || (htype == HTYPE_AtagOne) || (htype == HTYPE_PVOUTPUT_INPUT) || (htype == HTYPE_NEST) || (htype == HTYPE_ANNATHERMOSTAT) ||
				 (htype == HTYPE_THERMOSMART) || (htype == HTYPE_Tado) || (htype == HTYPE_Tesla) || (htype == HTYPE_Mercedes) || (htype == HTYPE_Netatmo))
			{
				if ((username.empty()) || (password.empty()))
					return;
			}
			else if (htype == HTYPE_SolarEdgeAPI)
			{
				if ((username.empty()))
					return;
			}
			else if (htype == HTYPE_Nest_OAuthAPI)
			{
				if ((username.empty()) && (extra == "||"))
					return;
			}
			else if (htype == HTYPE_HARMONY_HUB)
			{
				if ((address.empty()))
					return;
			}
			else if (htype == HTYPE_Philips_Hue)
			{
				if ((username.empty()) || (address.empty()))
					return;
				if (port == 0)
					port = 80;
			}
			else if (htype == HTYPE_RaspberryGPIO)
			{
				// all fine here!
			}
			else if (htype == HTYPE_SysfsGpio)
			{
				// all fine here!
			}
			else if (htype == HTYPE_Rtl433)
			{
				// all fine here!
			}
			else if (htype == HTYPE_Daikin)
			{
				// all fine here!
			}
			else if (htype == HTYPE_SBFSpot)
			{
				if (username.empty())
					return;
			}
			else if (htype == HTYPE_WINDDELEN)
			{
				std::string mill_id = request::findValue(&req, "Mode1");
				if ((mill_id.empty()) || (sport.empty()))
					return;
			}
			else if (htype == HTYPE_Honeywell)
			{
				// All fine here
			}
			else if (htype == HTYPE_OpenWebNetTCP)
			{
				// All fine here
			}
			else if (htype == HTYPE_PythonPlugin)
			{
				// All fine here
			}
			else if (htype == HTYPE_GoodweAPI)
			{
				if (username.empty())
				{
					return;
				}
			}
			else if (htype == HTYPE_RaspberryPCF8574)
			{
				// All fine here
			}
			else if (htype == HTYPE_OpenWebNetUSB)
			{
				// All fine here
			}
			else if (htype == HTYPE_IntergasInComfortLAN2RF)
			{
				// All fine here
			}
			else if (htype == HTYPE_EnphaseAPI)
			{
				// all fine here!
			}
			else if (htype == HTYPE_Meteorologisk)
			{
				// all fine here!
			}
			else if (htype == HTYPE_AirconWithMe)
			{
				// all fine here!
			}
			else
				return;

			std::string mode1Str;
			std::string mode2Str;
			std::string mode3Str;
			std::string mode4Str;
			std::string mode5Str;
			std::string mode6Str;

			root["status"] = "OK";
			root["title"] = "UpdateHardware";

			if (htype == HTYPE_Domoticz)
			{
				if (password.size() != 32)
				{
					password = GenerateMD5Hash(password);
				}
			}

			if ((bIsSerial) && (!bEnabled) && (sport.empty()))
			{
				// just disable the device
				m_sql.safe_query("UPDATE Hardware SET Enabled=%d WHERE (ID == '%q')", (bEnabled == true) ? 1 : 0, idx.c_str());
			}
			else
			{
				if (htype == HTYPE_HTTPPOLLER)
				{
					m_sql.safe_query("UPDATE Hardware SET Name='%q', Enabled=%d, Type=%d, LogLevel=%d, Address='%q', Port=%d, SerialPort='%q', Username='%q', Password='%q', "
							 "Extra='%q', DataTimeout=%d WHERE (ID == '%q')",
							 name.c_str(), (senabled == "true") ? 1 : 0, htype, iLogLevelEnabled, address.c_str(), port, sport.c_str(), username.c_str(), password.c_str(),
							 extra.c_str(), iDataTimeout, idx.c_str());
				}
				else if (htype == HTYPE_PythonPlugin)
				{
					mode1Str = request::findValue(&req, "Mode1");
					mode2Str = request::findValue(&req, "Mode2");
					mode3Str = request::findValue(&req, "Mode3");
					mode4Str = request::findValue(&req, "Mode4");
					mode5Str = request::findValue(&req, "Mode5");
					mode6Str = request::findValue(&req, "Mode6");
					sport = request::findValue(&req, "serialport");
					m_sql.safe_query("UPDATE Hardware SET Name='%q', Enabled=%d, Type=%d, LogLevel=%d, Address='%q', Port=%d, SerialPort='%q', Username='%q', Password='%q', "
							 "Extra='%q', Mode1='%q', Mode2='%q', Mode3='%q', Mode4='%q', Mode5='%q', Mode6='%q', DataTimeout=%d WHERE (ID == '%q')",
							 name.c_str(), (senabled == "true") ? 1 : 0, htype, iLogLevelEnabled, address.c_str(), port, sport.c_str(), username.c_str(), password.c_str(),
							 extra.c_str(), mode1Str.c_str(), mode2Str.c_str(), mode3Str.c_str(), mode4Str.c_str(), mode5Str.c_str(), mode6Str.c_str(), iDataTimeout,
							 idx.c_str());
				}
				else if ((htype == HTYPE_RFXtrx433) || (htype == HTYPE_RFXtrx868))
				{
					// No Extra field here, handled in CWebServer::SetRFXCOMMode
					m_sql.safe_query("UPDATE Hardware SET Name='%q', Enabled=%d, Type=%d, LogLevel=%d, Address='%q', Port=%d, SerialPort='%q', Username='%q', Password='%q', "
							 "Mode1=%d, Mode2=%d, Mode3=%d, Mode4=%d, Mode5=%d, Mode6=%d, DataTimeout=%d WHERE (ID == '%q')",
							 name.c_str(), (bEnabled == true) ? 1 : 0, htype, iLogLevelEnabled, address.c_str(), port, sport.c_str(), username.c_str(), password.c_str(),
							 mode1, mode2, mode3, mode4, mode5, mode6, iDataTimeout, idx.c_str());
					std::vector<std::vector<std::string>> result;
					result = m_sql.safe_query("SELECT Extra FROM Hardware WHERE ID=%q", idx.c_str());
					if (!result.empty())
						extra = result[0][0];
				}
				else
				{
					m_sql.safe_query("UPDATE Hardware SET Name='%q', Enabled=%d, Type=%d, LogLevel=%d, Address='%q', Port=%d, SerialPort='%q', Username='%q', Password='%q', "
							 "Extra='%q', Mode1=%d, Mode2=%d, Mode3=%d, Mode4=%d, Mode5=%d, Mode6=%d, DataTimeout=%d WHERE (ID == '%q')",
							 name.c_str(), (bEnabled == true) ? 1 : 0, htype, iLogLevelEnabled, address.c_str(), port, sport.c_str(), username.c_str(), password.c_str(),
							 extra.c_str(), mode1, mode2, mode3, mode4, mode5, mode6, iDataTimeout, idx.c_str());
				}
			}

			// re-add the device in our system
			int ID = atoi(idx.c_str());
			m_mainworker.AddHardwareFromParams(ID, name, bEnabled, htype, iLogLevelEnabled, address, port, sport, username, password, extra, mode1, mode2, mode3, mode4, mode5, mode6,
							   iDataTimeout, true);
		}

		void CWebServer::Cmd_GetDeviceValueOptions(WebEmSession &session, const request &req, Json::Value &root)
		{
			if (session.rights != 2)
			{
				session.reply_status = reply::forbidden;
				return; // Only admin user allowed
			}
			std::string idx = request::findValue(&req, "idx");
			if (idx.empty())
				return;
			std::vector<std::vector<std::string>> devresult;
			devresult = m_sql.safe_query("SELECT Type, SubType FROM DeviceStatus WHERE (ID=='%q')", idx.c_str());
			if (!devresult.empty())
			{
				int devType = std::stoi(devresult[0][0]);
				int devSubType = std::stoi(devresult[0][1]);
				std::vector<std::string> result;
				result = CBasePush::DropdownOptions(devType, devSubType);
				int ii = 0;
				for (const auto &ddOption : result)
				{
					root["result"][ii]["Value"] = ii + 1;
					root["result"][ii]["Wording"] = ddOption.c_str();
					ii++;
				}
			}
			root["status"] = "OK";
			root["title"] = "GetDeviceValueOptions";
		}

		void CWebServer::Cmd_GetDeviceValueOptionWording(WebEmSession &session, const request &req, Json::Value &root)
		{
			if (session.rights != 2)
			{
				session.reply_status = reply::forbidden;
				return; // Only admin user allowed
			}
			std::string idx = request::findValue(&req, "idx");
			std::string pos = request::findValue(&req, "pos");
			if ((idx.empty()) || (pos.empty()))
				return;
			std::string wording;
			std::vector<std::vector<std::string>> devresult;
			devresult = m_sql.safe_query("SELECT Type, SubType FROM DeviceStatus WHERE (ID=='%q')", idx.c_str());
			if (!devresult.empty())
			{
				int devType = std::stoi(devresult[0][0]);
				int devSubType = std::stoi(devresult[0][1]);
				wording = CBasePush::DropdownOptionsValue(devType, devSubType, std::stoi(pos));
			}
			root["wording"] = wording;
			root["status"] = "OK";
			root["title"] = "GetDeviceValueOptions";
		}

		void CWebServer::Cmd_AddUserVariable(WebEmSession &session, const request &req, Json::Value &root)
		{
			if (session.rights != 2)
			{
				session.reply_status = reply::forbidden;
				_log.Log(LOG_ERROR, "User: %s tried to add a uservariable!", session.username.c_str());
				return; // Only admin user allowed
			}
			std::string variablename = HTMLSanitizer::Sanitize(request::findValue(&req, "vname"));
			std::string variablevalue = request::findValue(&req, "vvalue");
			std::string variabletype = request::findValue(&req, "vtype");

			root["title"] = "AddUserVariable";
			root["status"] = "ERR";

			if (!std::isdigit(variabletype[0]))
			{
				stdlower(variabletype);
				if (variabletype == "integer")
					variabletype = "0";
				else if (variabletype == "float")
					variabletype = "1";
				else if (variabletype == "string")
					variabletype = "2";
				else if (variabletype == "date")
					variabletype = "3";
				else if (variabletype == "time")
					variabletype = "4";
				else
				{
					root["message"] = "Invalid variabletype " + variabletype;
					return;
				}
			}

			if ((variablename.empty()) || (variabletype.empty()) ||
			    ((variabletype != "0") && (variabletype != "1") && (variabletype != "2") && (variabletype != "3") && (variabletype != "4")) ||
			    ((variablevalue.empty()) && (variabletype != "2")))
			{
				root["message"] = "Invalid variabletype " + variabletype;
				return;
			}

			std::string errorMessage;
			if (!m_sql.AddUserVariable(variablename, (const _eUsrVariableType)atoi(variabletype.c_str()), variablevalue, errorMessage))
			{
				root["message"] = errorMessage;
			}
			else
			{
				root["status"] = "OK";
			}
		}

		void CWebServer::Cmd_DeleteUserVariable(WebEmSession &session, const request &req, Json::Value &root)
		{
			if (session.rights != 2)
			{
				_log.Log(LOG_ERROR, "User: %s tried to delete a uservariable!", session.username.c_str());
				session.reply_status = reply::forbidden;
				return; // Only admin user allowed
			}
			std::string idx = request::findValue(&req, "idx");
			if (idx.empty())
				return;

			m_sql.DeleteUserVariable(idx);
			root["status"] = "OK";
			root["title"] = "DeleteUserVariable";
		}

		void CWebServer::Cmd_UpdateUserVariable(WebEmSession &session, const request &req, Json::Value &root)
		{
			if (session.rights != 2)
			{
				_log.Log(LOG_ERROR, "User: %s tried to update a uservariable!", session.username.c_str());
				session.reply_status = reply::forbidden;
				return; // Only admin user allowed
			}

			std::string idx = request::findValue(&req, "idx");
			std::string variablename = HTMLSanitizer::Sanitize(request::findValue(&req, "vname"));
			std::string variablevalue = request::findValue(&req, "vvalue");
			std::string variabletype = request::findValue(&req, "vtype");

			root["title"] = "UpdateUserVariable";
			root["status"] = "ERR";

			if (!std::isdigit(variabletype[0]))
			{
				stdlower(variabletype);
				if (variabletype == "integer")
					variabletype = "0";
				else if (variabletype == "float")
					variabletype = "1";
				else if (variabletype == "string")
					variabletype = "2";
				else if (variabletype == "date")
					variabletype = "3";
				else if (variabletype == "time")
					variabletype = "4";
				else
				{
					root["message"] = "Invalid variabletype " + variabletype;
					return;
				}
			}

			if ((variablename.empty()) || (variabletype.empty()) ||
			    ((variabletype != "0") && (variabletype != "1") && (variabletype != "2") && (variabletype != "3") && (variabletype != "4")) ||
			    ((variablevalue.empty()) && (variabletype != "2")))
			{
				root["message"] = "Invalid variabletype " + variabletype;
				return;
			}

			std::vector<std::vector<std::string>> result;
			if (idx.empty())
			{
				result = m_sql.safe_query("SELECT ID FROM UserVariables WHERE Name='%q'", variablename.c_str());
				if (result.empty())
				{
					root["message"] = "Uservariable " + variablename + " does not exist";
					return;
				}
				idx = result[0][0];
			}

			result = m_sql.safe_query("SELECT Name, ValueType FROM UserVariables WHERE ID='%q'", idx.c_str());
			if (result.empty())
			{
				root["message"] = "Uservariable " + variablename + " does not exist";
				return;
			}

			bool bTypeNameChanged = false;
			if (variablename != result[0][0])
				bTypeNameChanged = true; // new name
			else if (variabletype != result[0][1])
				bTypeNameChanged = true; // new type

			std::string errorMessage;
			if (!m_sql.UpdateUserVariable(idx, variablename, (const _eUsrVariableType)atoi(variabletype.c_str()), variablevalue, !bTypeNameChanged, errorMessage))
			{
				root["message"] = errorMessage;
			}
			else
			{
				root["status"] = "OK";
				if (bTypeNameChanged)
				{
					if (m_sql.m_bEnableEventSystem)
						m_mainworker.m_eventsystem.GetCurrentUserVariables();
				}
			}
		}

		void CWebServer::Cmd_GetUserVariables(WebEmSession &session, const request &req, Json::Value &root)
		{
			std::vector<std::vector<std::string>> result;
			result = m_sql.safe_query("SELECT ID, Name, ValueType, Value, LastUpdate FROM UserVariables");
			int ii = 0;
			for (const auto &sd : result)
			{
				root["result"][ii]["idx"] = sd[0];
				root["result"][ii]["Name"] = sd[1];
				root["result"][ii]["Type"] = sd[2];
				root["result"][ii]["Value"] = sd[3];
				root["result"][ii]["LastUpdate"] = sd[4];
				ii++;
			}
			root["status"] = "OK";
			root["title"] = "GetUserVariables";
		}

		void CWebServer::Cmd_GetUserVariable(WebEmSession &session, const request &req, Json::Value &root)
		{
			std::string idx = request::findValue(&req, "idx");
			if (idx.empty())
				return;

			int iVarID = atoi(idx.c_str());

			std::vector<std::vector<std::string>> result;
			result = m_sql.safe_query("SELECT ID, Name, ValueType, Value, LastUpdate FROM UserVariables WHERE (ID==%d)", iVarID);
			int ii = 0;
			for (const auto &sd : result)
			{
				root["result"][ii]["idx"] = sd[0];
				root["result"][ii]["Name"] = sd[1];
				root["result"][ii]["Type"] = sd[2];
				root["result"][ii]["Value"] = sd[3];
				root["result"][ii]["LastUpdate"] = sd[4];
				ii++;
			}
			root["status"] = "OK";
			root["title"] = "GetUserVariable";
		}

		void CWebServer::Cmd_AllowNewHardware(WebEmSession &session, const request &req, Json::Value &root)
		{
			if (session.rights != 2)
			{
				session.reply_status = reply::forbidden;
				return; // Only admin user allowed
			}
			std::string sTimeout = request::findValue(&req, "timeout");
			if (sTimeout.empty())
				return;
			root["status"] = "OK";
			root["title"] = "AllowNewHardware";

			m_sql.AllowNewHardwareTimer(atoi(sTimeout.c_str()));
		}

		void CWebServer::Cmd_DeleteHardware(WebEmSession &session, const request &req, Json::Value &root)
		{
			if (session.rights != 2)
			{
				session.reply_status = reply::forbidden;
				return; // Only admin user allowed
			}

			std::string idx = request::findValue(&req, "idx");
			if (idx.empty())
				return;
			int hwID = atoi(idx.c_str());

			CDomoticzHardwareBase *pBaseHardware = m_mainworker.GetHardware(hwID);
			if ((pBaseHardware != nullptr) && (pBaseHardware->HwdType == HTYPE_DomoticzInternal))
			{
				// DomoticzInternal cannot be removed
				return;
			}

			root["status"] = "OK";
			root["title"] = "DeleteHardware";

			m_mainworker.RemoveDomoticzHardware(hwID);
			m_sql.DeleteHardware(idx);
		}

		void CWebServer::Cmd_GetLog(WebEmSession &session, const request &req, Json::Value &root)
		{
			root["status"] = "OK";
			root["title"] = "GetLog";

			time_t lastlogtime = 0;
			std::string slastlogtime = request::findValue(&req, "lastlogtime");
			if (!slastlogtime.empty())
			{
				std::stringstream s_str(slastlogtime);
				s_str >> lastlogtime;
			}

			_eLogLevel lLevel = LOG_NORM;
			std::string sloglevel = request::findValue(&req, "loglevel");
			if (!sloglevel.empty())
			{
				lLevel = (_eLogLevel)atoi(sloglevel.c_str());
			}

			std::list<CLogger::_tLogLineStruct> logmessages = _log.GetLog(lLevel);
			int ii = 0;
			for (const auto &msg : logmessages)
			{
				if (msg.logtime > lastlogtime)
				{
					std::stringstream szLogTime;
					szLogTime << msg.logtime;
					root["LastLogTime"] = szLogTime.str();
					root["result"][ii]["level"] = static_cast<int>(msg.level);
					root["result"][ii]["message"] = msg.logmessage;
					ii++;
				}
			}
		}

		void CWebServer::Cmd_ClearLog(WebEmSession &session, const request &req, Json::Value &root)
		{
			root["status"] = "OK";
			root["title"] = "ClearLog";
			_log.ClearLog();
		}

		// Plan Functions
		void CWebServer::Cmd_AddPlan(WebEmSession &session, const request &req, Json::Value &root)
		{
			if (session.rights != 2)
			{
				session.reply_status = reply::forbidden;
				return; // Only admin user allowed
			}

			std::string name = HTMLSanitizer::Sanitize(request::findValue(&req, "name"));
			if (name.empty())
			{
				session.reply_status = reply::bad_request;
			}

			root["status"] = "OK";
			root["title"] = "AddPlan";
			m_sql.safe_query("INSERT INTO Plans (Name) VALUES ('%q')", name.c_str());
			std::vector<std::vector<std::string>> result;
			result = m_sql.safe_query("SELECT MAX(ID) FROM Plans");
			if (!result.empty())
			{
				std::vector<std::string> sd = result[0];
				int ID = atoi(sd[0].c_str());

				root["idx"] = ID; // OTO output the created ID for easier management on the caller side (if automated)
			}
		}

		void CWebServer::Cmd_UpdatePlan(WebEmSession &session, const request &req, Json::Value &root)
		{
			if (session.rights != 2)
			{
				session.reply_status = reply::forbidden;
				return; // Only admin user allowed
			}

			std::string idx = request::findValue(&req, "idx");
			if (idx.empty())
				return;
			std::string name = HTMLSanitizer::Sanitize(request::findValue(&req, "name"));
			if (name.empty())
			{
				session.reply_status = reply::bad_request;
				return;
			}

			root["status"] = "OK";
			root["title"] = "UpdatePlan";

			m_sql.safe_query("UPDATE Plans SET Name='%q' WHERE (ID == '%q')", name.c_str(), idx.c_str());
		}

		void CWebServer::Cmd_DeletePlan(WebEmSession &session, const request &req, Json::Value &root)
		{
			if (session.rights != 2)
			{
				session.reply_status = reply::forbidden;
				return; // Only admin user allowed
			}

			std::string idx = request::findValue(&req, "idx");
			if (idx.empty())
				return;
			root["status"] = "OK";
			root["title"] = "DeletePlan";
			m_sql.safe_query("DELETE FROM DeviceToPlansMap WHERE (PlanID == '%q')", idx.c_str());
			m_sql.safe_query("DELETE FROM Plans WHERE (ID == '%q')", idx.c_str());
		}

		void CWebServer::Cmd_GetUnusedPlanDevices(WebEmSession &session, const request &req, Json::Value &root)
		{
			root["status"] = "OK";
			root["title"] = "GetUnusedPlanDevices";
			std::string sunique = request::findValue(&req, "unique");
			if (sunique.empty())
				return;
			int iUnique = (sunique == "true") ? 1 : 0;
			int ii = 0;

			std::vector<std::vector<std::string>> result;
			std::vector<std::vector<std::string>> result2;
			result = m_sql.safe_query("SELECT T1.[ID], T1.[Name], T1.[Type], T1.[SubType], T2.[Name] AS HardwareName FROM DeviceStatus as T1, Hardware as T2 WHERE (T1.[Used]==1) AND "
						  "(T2.[ID]==T1.[HardwareID]) ORDER BY T2.[Name], T1.[Name]");
			if (!result.empty())
			{
				for (const auto &sd : result)
				{
					bool bDoAdd = true;
					if (iUnique)
					{
						result2 = m_sql.safe_query("SELECT ID FROM DeviceToPlansMap WHERE (DeviceRowID=='%q') AND (DevSceneType==0)", sd[0].c_str());
						bDoAdd = (result2.empty());
					}
					if (bDoAdd)
					{
						int _dtype = atoi(sd[2].c_str());
						std::string Name = "[" + sd[4] + "] " + sd[1] + " (" + RFX_Type_Desc(_dtype, 1) + "/" + RFX_Type_SubType_Desc(_dtype, atoi(sd[3].c_str())) + ")";
						root["result"][ii]["type"] = 0;
						root["result"][ii]["idx"] = sd[0];
						root["result"][ii]["Name"] = Name;
						ii++;
					}
				}
			}
			// Add Scenes
			result = m_sql.safe_query("SELECT ID, Name FROM Scenes ORDER BY Name");
			if (!result.empty())
			{
				for (const auto &sd : result)
				{
					bool bDoAdd = true;
					if (iUnique)
					{
						result2 = m_sql.safe_query("SELECT ID FROM DeviceToPlansMap WHERE (DeviceRowID=='%q') AND (DevSceneType==1)", sd[0].c_str());
						bDoAdd = (result2.empty());
					}
					if (bDoAdd)
					{
						root["result"][ii]["type"] = 1;
						root["result"][ii]["idx"] = sd[0];
						std::string sname = "[Scene] " + sd[1];
						root["result"][ii]["Name"] = sname;
						ii++;
					}
				}
			}
		}

		void CWebServer::Cmd_AddPlanActiveDevice(WebEmSession &session, const request &req, Json::Value &root)
		{
			if (session.rights != 2)
			{
				session.reply_status = reply::forbidden;
				return; // Only admin user allowed
			}

			std::string idx = request::findValue(&req, "idx");
			std::string sactivetype = request::findValue(&req, "activetype");
			std::string activeidx = request::findValue(&req, "activeidx");
			if ((idx.empty()) || (sactivetype.empty()) || (activeidx.empty()))
				return;
			root["status"] = "OK";
			root["title"] = "AddPlanActiveDevice";

			int activetype = atoi(sactivetype.c_str());

			// check if it is not already there
			std::vector<std::vector<std::string>> result;
			result = m_sql.safe_query("SELECT ID FROM DeviceToPlansMap WHERE (DeviceRowID=='%q') AND (DevSceneType==%d) AND (PlanID=='%q')", activeidx.c_str(), activetype, idx.c_str());
			if (result.empty())
			{
				m_sql.safe_query("INSERT INTO DeviceToPlansMap (DevSceneType,DeviceRowID, PlanID) VALUES (%d,'%q','%q')", activetype, activeidx.c_str(), idx.c_str());
			}
		}

		void CWebServer::Cmd_GetPlanDevices(WebEmSession &session, const request &req, Json::Value &root)
		{
			std::string idx = request::findValue(&req, "idx");
			if (idx.empty())
				return;
			root["status"] = "OK";
			root["title"] = "GetPlanDevices";

			std::vector<std::vector<std::string>> result;
			result = m_sql.safe_query("SELECT ID, DevSceneType, DeviceRowID, [Order] FROM DeviceToPlansMap WHERE (PlanID=='%q') ORDER BY [Order]", idx.c_str());
			if (!result.empty())
			{
				int ii = 0;
				for (const auto &sd : result)
				{
					std::string ID = sd[0];
					int DevSceneType = atoi(sd[1].c_str());
					std::string DevSceneRowID = sd[2];

					std::string Name;
					if (DevSceneType == 0)
					{
						std::vector<std::vector<std::string>> result2;
						result2 = m_sql.safe_query("SELECT Name FROM DeviceStatus WHERE (ID=='%q')", DevSceneRowID.c_str());
						if (!result2.empty())
						{
							Name = result2[0][0];
						}
					}
					else
					{
						std::vector<std::vector<std::string>> result2;
						result2 = m_sql.safe_query("SELECT Name FROM Scenes WHERE (ID=='%q')", DevSceneRowID.c_str());
						if (!result2.empty())
						{
							Name = "[Scene] " + result2[0][0];
						}
					}
					if (!Name.empty())
					{
						root["result"][ii]["idx"] = ID;
						root["result"][ii]["devidx"] = DevSceneRowID;
						root["result"][ii]["type"] = DevSceneType;
						root["result"][ii]["DevSceneRowID"] = DevSceneRowID;
						root["result"][ii]["order"] = sd[3];
						root["result"][ii]["Name"] = Name;
						ii++;
					}
				}
			}
		}

		void CWebServer::Cmd_DeletePlanDevice(WebEmSession &session, const request &req, Json::Value &root)
		{
			if (session.rights != 2)
			{
				session.reply_status = reply::forbidden;
				return; // Only admin user allowed
			}
			std::string idx = request::findValue(&req, "idx");
			if (idx.empty())
				return;
			root["status"] = "OK";
			root["title"] = "DeletePlanDevice";
			m_sql.safe_query("DELETE FROM DeviceToPlansMap WHERE (ID == '%q')", idx.c_str());
		}

		void CWebServer::Cmd_SetPlanDeviceCoords(WebEmSession &session, const request &req, Json::Value &root)
		{
			std::string idx = request::findValue(&req, "idx");
			std::string planidx = request::findValue(&req, "planidx");
			std::string xoffset = request::findValue(&req, "xoffset");
			std::string yoffset = request::findValue(&req, "yoffset");
			std::string type = request::findValue(&req, "DevSceneType");
			if ((idx.empty()) || (planidx.empty()) || (xoffset.empty()) || (yoffset.empty()))
				return;
			if (type != "1")
				type = "0"; // 0 = Device, 1 = Scene/Group
			root["status"] = "OK";
			root["title"] = "SetPlanDeviceCoords";
			m_sql.safe_query("UPDATE DeviceToPlansMap SET [XOffset] = '%q', [YOffset] = '%q' WHERE (DeviceRowID='%q') and (PlanID='%q') and (DevSceneType='%q')", xoffset.c_str(),
					 yoffset.c_str(), idx.c_str(), planidx.c_str(), type.c_str());
			_log.Log(LOG_STATUS, "(Floorplan) Device '%s' coordinates set to '%s,%s' in plan '%s'.", idx.c_str(), xoffset.c_str(), yoffset.c_str(), planidx.c_str());
		}

		void CWebServer::Cmd_DeleteAllPlanDevices(WebEmSession &session, const request &req, Json::Value &root)
		{
			if (session.rights != 2)
			{
				session.reply_status = reply::forbidden;
				return; // Only admin user allowed
			}
			std::string idx = request::findValue(&req, "idx");
			if (idx.empty())
				return;
			root["status"] = "OK";
			root["title"] = "DeleteAllPlanDevices";
			m_sql.safe_query("DELETE FROM DeviceToPlansMap WHERE (PlanID == '%q')", idx.c_str());
		}

		void CWebServer::Cmd_ChangePlanOrder(WebEmSession &session, const request &req, Json::Value &root)
		{
			std::string idx = request::findValue(&req, "idx");
			if (idx.empty())
				return;
			std::string sway = request::findValue(&req, "way");
			if (sway.empty())
				return;
			bool bGoUp = (sway == "0");

			std::string aOrder, oID, oOrder;

			std::vector<std::vector<std::string>> result;
			result = m_sql.safe_query("SELECT [Order] FROM Plans WHERE (ID=='%q')", idx.c_str());
			if (result.empty())
				return;
			aOrder = result[0][0];

			if (!bGoUp)
			{
				// Get next device order
				result = m_sql.safe_query("SELECT ID, [Order] FROM Plans WHERE ([Order]>'%q') ORDER BY [Order] ASC", aOrder.c_str());
				if (result.empty())
					return;
				oID = result[0][0];
				oOrder = result[0][1];
			}
			else
			{
				// Get previous device order
				result = m_sql.safe_query("SELECT ID, [Order] FROM Plans WHERE ([Order]<'%q') ORDER BY [Order] DESC", aOrder.c_str());
				if (result.empty())
					return;
				oID = result[0][0];
				oOrder = result[0][1];
			}
			// Swap them
			root["status"] = "OK";
			root["title"] = "ChangePlanOrder";

			m_sql.safe_query("UPDATE Plans SET [Order] = '%q' WHERE (ID='%q')", oOrder.c_str(), idx.c_str());
			m_sql.safe_query("UPDATE Plans SET [Order] = '%q' WHERE (ID='%q')", aOrder.c_str(), oID.c_str());
		}

		void CWebServer::Cmd_ChangePlanDeviceOrder(WebEmSession &session, const request &req, Json::Value &root)
		{
			std::string planid = request::findValue(&req, "planid");
			std::string idx = request::findValue(&req, "idx");
			std::string sway = request::findValue(&req, "way");
			if ((planid.empty()) || (idx.empty()) || (sway.empty()))
				return;
			bool bGoUp = (sway == "0");

			std::string aOrder, oID, oOrder;

			std::vector<std::vector<std::string>> result;
			result = m_sql.safe_query("SELECT [Order] FROM DeviceToPlansMap WHERE ((ID=='%q') AND (PlanID=='%q'))", idx.c_str(), planid.c_str());
			if (result.empty())
				return;
			aOrder = result[0][0];

			if (!bGoUp)
			{
				// Get next device order
				result = m_sql.safe_query("SELECT ID, [Order] FROM DeviceToPlansMap WHERE (([Order]>'%q') AND (PlanID=='%q')) ORDER BY [Order] ASC", aOrder.c_str(), planid.c_str());
				if (result.empty())
					return;
				oID = result[0][0];
				oOrder = result[0][1];
			}
			else
			{
				// Get previous device order
				result = m_sql.safe_query("SELECT ID, [Order] FROM DeviceToPlansMap WHERE (([Order]<'%q') AND (PlanID=='%q')) ORDER BY [Order] DESC", aOrder.c_str(), planid.c_str());
				if (result.empty())
					return;
				oID = result[0][0];
				oOrder = result[0][1];
			}
			// Swap them
			root["status"] = "OK";
			root["title"] = "ChangePlanOrder";

			m_sql.safe_query("UPDATE DeviceToPlansMap SET [Order] = '%q' WHERE (ID='%q')", oOrder.c_str(), idx.c_str());
			m_sql.safe_query("UPDATE DeviceToPlansMap SET [Order] = '%q' WHERE (ID='%q')", aOrder.c_str(), oID.c_str());
		}

		void CWebServer::Cmd_GetVersion(WebEmSession &session, const request &req, Json::Value &root)
		{
			root["status"] = "OK";
			root["title"] = "GetVersion";
			root["version"] = szAppVersion;
			root["hash"] = szAppHash;
			root["build_time"] = szAppDate;
			CdzVents *dzvents = CdzVents::GetInstance();
			root["dzvents_version"] = dzvents->GetVersion();
			root["python_version"] = szPyVersion;

			if (session.rights != 2)
			{
				// only admin users will receive the update notification
				root["UseUpdate"] = false;
				root["HaveUpdate"] = false;
			}
			else
			{
				root["UseUpdate"] = g_bUseUpdater;
				root["HaveUpdate"] = m_mainworker.IsUpdateAvailable(false);
				root["DomoticzUpdateURL"] = m_mainworker.m_szDomoticzUpdateURL;
				root["SystemName"] = m_mainworker.m_szSystemName;
				root["Revision"] = m_mainworker.m_iRevision;
			}
		}

		void CWebServer::Cmd_GetAuth(WebEmSession &session, const request &req, Json::Value &root)
		{
			root["status"] = "OK";
			root["title"] = "GetAuth";
			if (session.rights != -1)
			{
				root["version"] = szAppVersion;
			}
			root["user"] = session.username;
			root["rights"] = session.rights;
		}

		void CWebServer::Cmd_GetUptime(WebEmSession &session, const request &req, Json::Value &root)
		{
			// this is used in the about page, we are going to round the seconds a bit to display nicer
			time_t atime = mytime(nullptr);
			time_t tuptime = atime - m_StartTime;
			// round to 5 seconds (nicer in about page)
			tuptime = ((tuptime / 5) * 5) + 5;
			int days, hours, minutes, seconds;
			days = (int)(tuptime / 86400);
			tuptime -= (days * 86400);
			hours = (int)(tuptime / 3600);
			tuptime -= (hours * 3600);
			minutes = (int)(tuptime / 60);
			tuptime -= (minutes * 60);
			seconds = (int)tuptime;
			root["status"] = "OK";
			root["title"] = "GetUptime";
			root["days"] = days;
			root["hours"] = hours;
			root["minutes"] = minutes;
			root["seconds"] = seconds;
		}

		void CWebServer::Cmd_GetActualHistory(WebEmSession &session, const request &req, Json::Value &root)
		{
			root["status"] = "OK";
			root["title"] = "GetActualHistory";

			std::string historyfile = szUserDataFolder + "History.txt";

			std::ifstream infile;
			int ii = 0;
			infile.open(historyfile.c_str());
			std::string sLine;
			if (infile.is_open())
			{
				while (!infile.eof())
				{
					getline(infile, sLine);
					root["LastLogTime"] = "";
					if (sLine.find("Version ") == 0)
						root["result"][ii]["level"] = 1;
					else
						root["result"][ii]["level"] = 0;
					root["result"][ii]["message"] = sLine;
					ii++;
				}
			}
		}

		void CWebServer::Cmd_GetNewHistory(WebEmSession &session, const request &req, Json::Value &root)
		{
			root["status"] = "OK";
			root["title"] = "GetNewHistory";

			std::string historyfile;
			int nValue;
			m_sql.GetPreferencesVar("ReleaseChannel", nValue);
			bool bIsBetaChannel = (nValue != 0);

			std::string szHistoryURL = "https://www.domoticz.com/download.php?channel=stable&type=history";
			if (bIsBetaChannel)
			{
				utsname my_uname;
				if (uname(&my_uname) < 0)
					return;

				std::string systemname = my_uname.sysname;
				std::string machine = my_uname.machine;
				std::transform(systemname.begin(), systemname.end(), systemname.begin(), ::tolower);

				if (machine == "armv6l")
				{
					// Seems like old arm systems can also use the new arm build
					machine = "armv7l";
				}

				if (((machine != "armv6l") && (machine != "armv7l") && (systemname != "windows") && (machine != "x86_64") && (machine != "aarch64")) ||
				    (strstr(my_uname.release, "ARCH+") != nullptr))
					szHistoryURL = "https://www.domoticz.com/download.php?channel=beta&type=history";
				else
					szHistoryURL = "https://www.domoticz.com/download.php?channel=beta&type=history&system=" + systemname + "&machine=" + machine;
			}
			if (!HTTPClient::GET(szHistoryURL, historyfile))
			{
				historyfile = "Unable to get Online History document !!";
			}

			std::istringstream stream(historyfile);
			std::string sLine;
			int ii = 0;
			while (std::getline(stream, sLine))
			{
				root["LastLogTime"] = "";
				if (sLine.find("Version ") == 0)
					root["result"][ii]["level"] = 1;
				else
					root["result"][ii]["level"] = 0;
				root["result"][ii]["message"] = sLine;
				ii++;
			}
		}

		void CWebServer::Cmd_GetConfig(WebEmSession &session, const request &req, Json::Value &root)
		{
			root["status"] = "OK";
			root["title"] = "GetConfig";

			bool bHaveUser = (!session.username.empty());
			// int urights = 3;
			unsigned long UserID = 0;
			if (bHaveUser)
			{
				int iUser = FindUser(session.username.c_str());
				if (iUser != -1)
				{
					// urights = static_cast<int>(m_users[iUser].userrights);
					UserID = m_users[iUser].ID;
				}
			}

			int nValue;
			std::string sValue;

			if (m_sql.GetPreferencesVar("Language", sValue))
			{
				root["language"] = sValue;
			}
			if (m_sql.GetPreferencesVar("DegreeDaysBaseTemperature", sValue))
			{
				root["DegreeDaysBaseTemperature"] = atof(sValue.c_str());
			}

			nValue = 0;
			int iDashboardType = 0;
			m_sql.GetPreferencesVar("DashboardType", iDashboardType);
			root["DashboardType"] = iDashboardType;
			m_sql.GetPreferencesVar("MobileType", nValue);
			root["MobileType"] = nValue;

			nValue = 1;
			m_sql.GetPreferencesVar("5MinuteHistoryDays", nValue);
			root["FiveMinuteHistoryDays"] = nValue;

			nValue = 1;
			m_sql.GetPreferencesVar("ShowUpdateEffect", nValue);
			root["result"]["ShowUpdatedEffect"] = (nValue == 1);

			root["AllowWidgetOrdering"] = m_sql.m_bAllowWidgetOrdering;

			root["WindScale"] = m_sql.m_windscale * 10.0F;
			root["WindSign"] = m_sql.m_windsign;
			root["TempScale"] = m_sql.m_tempscale;
			root["TempSign"] = m_sql.m_tempsign;

#ifndef NOCLOUD
			bool bEnableTabProxy = request::get_req_header(&req, "X-From-MyDomoticz") != nullptr;
#else
			bool bEnableTabProxy = false;
#endif
			int bEnableTabDashboard = 1;
			int bEnableTabFloorplans = 1;
			int bEnableTabLight = 1;
			int bEnableTabScenes = 1;
			int bEnableTabTemp = 1;
			int bEnableTabWeather = 1;
			int bEnableTabUtility = 1;
			int bEnableTabCustom = 1;

			std::vector<std::vector<std::string>> result;

			if ((UserID != 0) && (UserID != 10000))
			{
				result = m_sql.safe_query("SELECT TabsEnabled FROM Users WHERE (ID==%lu)", UserID);
				if (!result.empty())
				{
					int TabsEnabled = atoi(result[0][0].c_str());
					bEnableTabLight = (TabsEnabled & (1 << 0));
					bEnableTabScenes = (TabsEnabled & (1 << 1));
					bEnableTabTemp = (TabsEnabled & (1 << 2));
					bEnableTabWeather = (TabsEnabled & (1 << 3));
					bEnableTabUtility = (TabsEnabled & (1 << 4));
					bEnableTabCustom = (TabsEnabled & (1 << 5));
					bEnableTabFloorplans = (TabsEnabled & (1 << 6));
				}
			}
			else
			{
				m_sql.GetPreferencesVar("EnableTabFloorplans", bEnableTabFloorplans);
				m_sql.GetPreferencesVar("EnableTabLights", bEnableTabLight);
				m_sql.GetPreferencesVar("EnableTabScenes", bEnableTabScenes);
				m_sql.GetPreferencesVar("EnableTabTemp", bEnableTabTemp);
				m_sql.GetPreferencesVar("EnableTabWeather", bEnableTabWeather);
				m_sql.GetPreferencesVar("EnableTabUtility", bEnableTabUtility);
				m_sql.GetPreferencesVar("EnableTabCustom", bEnableTabCustom);
			}
			if (iDashboardType == 3)
			{
				// Floorplan , no need to show a tab floorplan
				bEnableTabFloorplans = 0;
			}
			root["result"]["EnableTabProxy"] = bEnableTabProxy;
			root["result"]["EnableTabDashboard"] = bEnableTabDashboard != 0;
			root["result"]["EnableTabFloorplans"] = bEnableTabFloorplans != 0;
			root["result"]["EnableTabLights"] = bEnableTabLight != 0;
			root["result"]["EnableTabScenes"] = bEnableTabScenes != 0;
			root["result"]["EnableTabTemp"] = bEnableTabTemp != 0;
			root["result"]["EnableTabWeather"] = bEnableTabWeather != 0;
			root["result"]["EnableTabUtility"] = bEnableTabUtility != 0;
			root["result"]["EnableTabCustom"] = bEnableTabCustom != 0;

			if (bEnableTabCustom)
			{
				// Add custom templates
				DIR *lDir;
				struct dirent *ent;
				std::string templatesFolder = szWWWFolder + "/templates";
				int iFile = 0;
				if ((lDir = opendir(templatesFolder.c_str())) != nullptr)
				{
					while ((ent = readdir(lDir)) != nullptr)
					{
						std::string filename = ent->d_name;
						size_t pos = filename.find(".htm");
						if (pos != std::string::npos)
						{
							std::string shortfile = filename.substr(0, pos);
							root["result"]["templates"][iFile++] = shortfile;
						}
						// Same thing for URLs
						pos = filename.find(".url");
						if (pos != std::string::npos)
						{
							std::string url;
							std::string shortfile = filename.substr(0, pos);
							// First get the URL from the file
							std::ifstream urlfile;
							urlfile.open((templatesFolder + "/" + filename).c_str());
							if (urlfile.is_open())
							{
								getline(urlfile, url);
								urlfile.close();
								// Pass URL in results
								root["result"]["urls"][shortfile] = url;
							}
						}
					}
					closedir(lDir);
				}
			}
		}

		// Could now be obsolete as only 1 usage was found in Forecast screen, which now uses other command
		void CWebServer::Cmd_GetLocation(WebEmSession &session, const request &req, Json::Value &root)
		{
			if (session.rights == -1)
			{
				session.reply_status = reply::forbidden;
				return; // Only auth user allowed
			}
			root["title"] = "GetLocation";
			std::string Latitude = "1";
			std::string Longitude = "1";
			std::string sValue;
			if (m_sql.GetPreferencesVar("Location", sValue))
			{
				std::vector<std::string> strarray;
				StringSplit(sValue, ";", strarray);

				if (strarray.size() == 2)
				{
					Latitude = strarray[0];
					Longitude = strarray[1];
					root["status"] = "OK";
				}
			}
			root["Latitude"] = Latitude;
			root["Longitude"] = Longitude;
		}

		void CWebServer::Cmd_GetForecastConfig(WebEmSession &session, const request &req, Json::Value &root)
		{
			if (session.rights == -1)
			{
				session.reply_status = reply::forbidden;
				return; // Only auth user allowed
			}

			std::string Latitude = "1";
			std::string Longitude = "1";
			std::string sValue, sFURL, forecast_url;
			std::stringstream ss, sURL;
			uint8_t iSucces = 0;
			bool iFrame = true;

			root["title"] = "GetForecastConfig";
			root["status"] = "Error";

			if (m_sql.GetPreferencesVar("Location", sValue))
			{
				std::vector<std::string> strarray;
				StringSplit(sValue, ";", strarray);

				if (strarray.size() == 2)
				{
					Latitude = strarray[0];
					Longitude = strarray[1];
					iSucces++;
				}
				root["Latitude"] = Latitude;
				root["Longitude"] = Longitude;
				sValue = "";
				sValue.clear();
			}

			root["Forecasthardware"] = 0;
			int iValue = 0;
			if (m_sql.GetPreferencesVar("ForecastHardwareID", iValue))
			{
				root["Forecasthardware"] = iValue;
			}

			if (root["Forecasthardware"] > 0)
			{
				int iHardwareID = root["Forecasthardware"].asInt();
				CDomoticzHardwareBase *pHardware = m_mainworker.GetHardware(iHardwareID);
				if (pHardware != nullptr)
				{
					if (pHardware->HwdType == HTYPE_OpenWeatherMap)
					{
						root["Forecasthardwaretype"] = HTYPE_OpenWeatherMap;
						COpenWeatherMap *pWHardware = reinterpret_cast<COpenWeatherMap *>(pHardware);
						forecast_url = pWHardware->GetForecastURL();
						if (!forecast_url.empty())
						{
							sFURL = forecast_url;
							iFrame = false;
						}
						Json::Value forecast_data = pWHardware->GetForecastData();
						if (!forecast_data.empty())
						{
							root["Forecastdata"] = forecast_data;
						}
					}
					else if (pHardware->HwdType == HTYPE_BuienRadar)
					{
						root["Forecasthardwaretype"] = HTYPE_BuienRadar;
						CBuienRadar *pWHardware = reinterpret_cast<CBuienRadar *>(pHardware);
						forecast_url = pWHardware->GetForecastURL();
						if (!forecast_url.empty())
						{
							sFURL = forecast_url;
						}
					}
					else
					{
						root["Forecasthardware"] = 0; // reset to 0
					}
				}
				else
				{
					_log.Debug(DEBUG_WEBSERVER, "Forecastconfig: Could not find hardware (not active?) for ID %s!", root["Forecasthardware"].asString().c_str());
					root["Forecasthardware"] = 0; // reset to 0
				}
			}

			if (root["Forecasthardware"] == 0 && iSucces == 1)
			{
				// No forecast device, but we have geo coords, so enough for fallback
				iSucces++;
			}
			else if (!sFURL.empty())
			{
				root["Forecasturl"] = sFURL;
				iSucces++;
			}

			if (iSucces == 2)
			{
				root["status"] = "OK";
			}
		}

		void CWebServer::Cmd_SendNotification(WebEmSession &session, const request &req, Json::Value &root)
		{
			std::string subject = request::findValue(&req, "subject");
			std::string body = request::findValue(&req, "body");
			std::string subsystem = request::findValue(&req, "subsystem");
			if ((subject.empty()) || (body.empty()))
				return;
			if (subsystem.empty())
				subsystem = NOTIFYALL;
			// Add to queue
			if (m_notifications.SendMessage(0, std::string(""), subsystem, subject, body, std::string(""), 1, std::string(""), false))
			{
				root["status"] = "OK";
			}
			root["title"] = "SendNotification";
		}

		void CWebServer::Cmd_EmailCameraSnapshot(WebEmSession &session, const request &req, Json::Value &root)
		{
			std::string camidx = request::findValue(&req, "camidx");
			std::string subject = request::findValue(&req, "subject");
			if ((camidx.empty()) || (subject.empty()))
				return;
			// Add to queue
			m_sql.AddTaskItem(_tTaskItem::EmailCameraSnapshot(1, camidx, subject));
			root["status"] = "OK";
			root["title"] = "Email Camera Snapshot";
		}

		void CWebServer::Cmd_UpdateDevice(WebEmSession &session, const request &req, Json::Value &root)
		{
			std::string Username = "Admin";
			if (!session.username.empty())
				Username = session.username;

			if (session.rights < 1)
			{
				session.reply_status = reply::forbidden;
				return; // only user or higher allowed
			}

			std::string idx = request::findValue(&req, "idx");

			if (!IsIdxForUser(&session, atoi(idx.c_str())))
			{
				_log.Log(LOG_ERROR, "User: %s tried to update an Unauthorized device!", session.username.c_str());
				session.reply_status = reply::forbidden;
				return;
			}

			std::string hid = request::findValue(&req, "hid");
			std::string did = request::findValue(&req, "did");
			std::string dunit = request::findValue(&req, "dunit");
			std::string dtype = request::findValue(&req, "dtype");
			std::string dsubtype = request::findValue(&req, "dsubtype");

			std::string nvalue = request::findValue(&req, "nvalue");
			std::string svalue = request::findValue(&req, "svalue");
			std::string ptrigger = request::findValue(&req, "parsetrigger");

			bool parseTrigger = (ptrigger != "false");

			if ((nvalue.empty() && svalue.empty()))
			{
				return;
			}

			int signallevel = 12;
			int batterylevel = 255;

			if (idx.empty())
			{
				// No index supplied, check if raw parameters where supplied
				if ((hid.empty()) || (did.empty()) || (dunit.empty()) || (dtype.empty()) || (dsubtype.empty()))
					return;
			}
			else
			{
				// Get the raw device parameters
				std::vector<std::vector<std::string>> result;
				result = m_sql.safe_query("SELECT HardwareID, DeviceID, Unit, Type, SubType FROM DeviceStatus WHERE (ID=='%q')", idx.c_str());
				if (result.empty())
					return;
				hid = result[0][0];
				did = result[0][1];
				dunit = result[0][2];
				dtype = result[0][3];
				dsubtype = result[0][4];
			}

			int HardwareID = atoi(hid.c_str());
			std::string DeviceID = did;
			int unit = atoi(dunit.c_str());
			int devType = atoi(dtype.c_str());
			int subType = atoi(dsubtype.c_str());

			// uint64_t ulIdx = std::strtoull(idx.c_str(), nullptr, 10);

			int invalue = atoi(nvalue.c_str());

			std::string sSignalLevel = request::findValue(&req, "rssi");
			if (!sSignalLevel.empty())
			{
				signallevel = atoi(sSignalLevel.c_str());
			}
			std::string sBatteryLevel = request::findValue(&req, "battery");
			if (!sBatteryLevel.empty())
			{
				batterylevel = atoi(sBatteryLevel.c_str());
			}
			std::string szUpdateUser = Username + " (IP: " + session.remote_host + ")";
			if (m_mainworker.UpdateDevice(HardwareID, DeviceID, unit, devType, subType, invalue, svalue, szUpdateUser, signallevel, batterylevel, parseTrigger))
			{
				root["status"] = "OK";
				root["title"] = "Update Device";
			}
		}

		void CWebServer::Cmd_UpdateDevices(WebEmSession &session, const request &req, Json::Value &root)
		{
			std::string script = request::findValue(&req, "script");
			if (script.empty())
			{
				return;
			}
			std::string content = req.content;

			std::vector<std::string> allParameters;

			// Keep the url content on the right of the '?'
			std::vector<std::string> allParts;
			StringSplit(req.uri, "?", allParts);
			if (!allParts.empty())
			{
				// Split all url parts separated by a '&'
				StringSplit(allParts[1], "&", allParameters);
			}

			CLuaHandler luaScript;
			bool ret = luaScript.executeLuaScript(script, content, allParameters);
			if (ret)
			{
				root["status"] = "OK";
				root["title"] = "Update Device";
			}
		}

		void CWebServer::Cmd_CustomEvent(WebEmSession &session, const request &req, Json::Value &root)
		{
			if (session.rights < 1)
			{
				session.reply_status = reply::forbidden;
				return; // only user or higher allowed
			}
			Json::Value eventInfo;
			eventInfo["name"] = request::findValue(&req, "event");
			if (!req.content.empty())
				eventInfo["data"] = req.content.c_str(); // data from POST
			else
				eventInfo["data"] = request::findValue(&req, "data"); // data in URL

			if (eventInfo["name"].empty())
			{
				return;
			}

			m_mainworker.m_notificationsystem.Notify(Notification::DZ_CUSTOM, Notification::STATUS_INFO, JSonToRawString(eventInfo));

			root["status"] = "OK";
			root["title"] = "Custom Event";
		}

		void CWebServer::Cmd_SetThermostatState(WebEmSession &session, const request &req, Json::Value &root)
		{
			std::string sstate = request::findValue(&req, "state");
			std::string idx = request::findValue(&req, "idx");
			std::string name = HTMLSanitizer::Sanitize(request::findValue(&req, "name"));

			if ((idx.empty()) || (sstate.empty()))
				return;
			int iState = atoi(sstate.c_str());

			int urights = 3;
			bool bHaveUser = (!session.username.empty());
			if (bHaveUser)
			{
				int iUser = FindUser(session.username.c_str());
				if (iUser != -1)
				{
					urights = static_cast<int>(m_users[iUser].userrights);
					_log.Log(LOG_STATUS, "User: %s initiated a Thermostat State change command", m_users[iUser].Username.c_str());
				}
			}
			if (urights < 1)
				return;

			root["status"] = "OK";
			root["title"] = "Set Thermostat State";
			_log.Log(LOG_NORM, "Setting Thermostat State....");
			m_mainworker.SetThermostatState(idx, iState);
		}

		void CWebServer::Cmd_SystemShutdown(WebEmSession &session, const request &req, Json::Value &root)
		{
			if (session.rights != 2)
			{
				session.reply_status = reply::forbidden;
				return; // Only admin user allowed
			}
#ifdef WIN32
			int ret = system("shutdown -s -f -t 1 -d up:125:1");
#else
			int ret = system("sudo shutdown -h now");
#endif
			if (ret != 0)
			{
				_log.Log(LOG_ERROR, "Error executing shutdown command. returned: %d", ret);
				return;
			}
			root["title"] = "SystemShutdown";
			root["status"] = "OK";
		}

		void CWebServer::Cmd_SystemReboot(WebEmSession &session, const request &req, Json::Value &root)
		{
			if (session.rights != 2)
			{
				session.reply_status = reply::forbidden;
				return; // Only admin user allowed
			}
#ifdef WIN32
			int ret = system("shutdown -r -f -t 1 -d up:125:1");
#else
			int ret = system("sudo shutdown -r now");
#endif
			if (ret != 0)
			{
				_log.Log(LOG_ERROR, "Error executing reboot command. returned: %d", ret);
				return;
			}
			root["title"] = "SystemReboot";
			root["status"] = "OK";
		}

		void CWebServer::Cmd_ExcecuteScript(WebEmSession &session, const request &req, Json::Value &root)
		{
			if (session.rights != 2)
			{
				session.reply_status = reply::forbidden;
				return; // Only admin user allowed
			}
			std::string scriptname = request::findValue(&req, "scriptname");
			if (scriptname.empty())
				return;
			if (scriptname.find("..") != std::string::npos)
				return;
#ifdef WIN32
			scriptname = szUserDataFolder + "scripts\\" + scriptname;
#else
			scriptname = szUserDataFolder + "scripts/" + scriptname;
#endif
			if (!file_exist(scriptname.c_str()))
				return;
			std::string script_params = request::findValue(&req, "scriptparams");
			std::string strparm = szUserDataFolder;
			if (!script_params.empty())
			{
				if (!strparm.empty())
					strparm += " " + script_params;
				else
					strparm = script_params;
			}
			std::string sdirect = request::findValue(&req, "direct");
			if (sdirect == "true")
			{
				_log.Log(LOG_STATUS, "Executing script: %s", scriptname.c_str());
#ifdef WIN32
				ShellExecute(NULL, "open", scriptname.c_str(), strparm.c_str(), NULL, SW_SHOWNORMAL);
#else
				std::string lscript = scriptname + " " + strparm;
				int ret = system(lscript.c_str());
				if (ret != 0)
				{
					_log.Log(LOG_ERROR, "Error executing script command (%s). returned: %d", lscript.c_str(), ret);
					return;
				}
#endif
			}
			else
			{
				// add script to background worker
				m_sql.AddTaskItem(_tTaskItem::ExecuteScript(0.2F, scriptname, strparm));
			}
			root["title"] = "ExecuteScript";
			root["status"] = "OK";
		}

		// Only for Unix systems
		void CWebServer::Cmd_UpdateApplication(WebEmSession &session, const request &req, Json::Value &root)
		{
			if (session.rights != 2)
			{
				session.reply_status = reply::forbidden;
				return; // Only admin user allowed
			}
#ifdef WIN32
#ifndef _DEBUG
			return;
#endif
#endif
			int nValue;
			m_sql.GetPreferencesVar("ReleaseChannel", nValue);
			bool bIsBetaChannel = (nValue != 0);

			std::string scriptname(szStartupFolder);
			scriptname += (bIsBetaChannel) ? "updatebeta" : "updaterelease";
			// run script in background
			std::string lscript = scriptname + " &";
			int ret = system(lscript.c_str());
			root["title"] = "UpdateApplication";
			root["status"] = "OK";
		}

		void CWebServer::Cmd_GetCosts(WebEmSession &session, const request &req, Json::Value &root)
		{
			std::string idx = request::findValue(&req, "idx");
			if (idx.empty())
				return;
			char szTmp[100];
			std::vector<std::vector<std::string>> result;
			result = m_sql.safe_query("SELECT Type, SubType, nValue, sValue FROM DeviceStatus WHERE (ID=='%q')", idx.c_str());
			if (!result.empty())
			{
				std::vector<std::string> sd = result[0];

				int nValue = 0;
				root["status"] = "OK";
				root["title"] = "GetElectraCosts";
				m_sql.GetPreferencesVar("CostEnergy", nValue);
				root["CostEnergy"] = nValue;
				m_sql.GetPreferencesVar("CostEnergyT2", nValue);
				root["CostEnergyT2"] = nValue;
				m_sql.GetPreferencesVar("CostEnergyR1", nValue);
				root["CostEnergyR1"] = nValue;
				m_sql.GetPreferencesVar("CostEnergyR2", nValue);
				root["CostEnergyR2"] = nValue;
				m_sql.GetPreferencesVar("CostGas", nValue);
				root["CostGas"] = nValue;
				m_sql.GetPreferencesVar("CostWater", nValue);
				root["CostWater"] = nValue;

				int tValue = 1000;
				if (m_sql.GetPreferencesVar("MeterDividerWater", tValue))
				{
					root["DividerWater"] = float(tValue);
				}

				unsigned char dType = atoi(sd[0].c_str());
				// unsigned char subType = atoi(sd[1].c_str());
				// nValue = (unsigned char)atoi(sd[2].c_str());
				std::string sValue = sd[3];

				if (dType == pTypeP1Power)
				{
					// also provide the counter values

					std::vector<std::string> splitresults;
					StringSplit(sValue, ";", splitresults);
					if (splitresults.size() != 6)
						return;

					float EnergyDivider = 1000.0F;
					if (m_sql.GetPreferencesVar("MeterDividerEnergy", tValue))
					{
						EnergyDivider = float(tValue);
					}

					unsigned long long powerusage1 = std::strtoull(splitresults[0].c_str(), nullptr, 10);
					unsigned long long powerusage2 = std::strtoull(splitresults[1].c_str(), nullptr, 10);
					unsigned long long powerdeliv1 = std::strtoull(splitresults[2].c_str(), nullptr, 10);
					unsigned long long powerdeliv2 = std::strtoull(splitresults[3].c_str(), nullptr, 10);
					// unsigned long long usagecurrent = std::strtoull(splitresults[4].c_str(), nullptr, 10);
					// unsigned long long delivcurrent = std::strtoull(splitresults[5].c_str(), nullptr, 10);

					powerdeliv1 = (powerdeliv1 < 10) ? 0 : powerdeliv1;
					powerdeliv2 = (powerdeliv2 < 10) ? 0 : powerdeliv2;

					sprintf(szTmp, "%.03f", float(powerusage1) / EnergyDivider);
					root["CounterT1"] = szTmp;
					sprintf(szTmp, "%.03f", float(powerusage2) / EnergyDivider);
					root["CounterT2"] = szTmp;
					sprintf(szTmp, "%.03f", float(powerdeliv1) / EnergyDivider);
					root["CounterR1"] = szTmp;
					sprintf(szTmp, "%.03f", float(powerdeliv2) / EnergyDivider);
					root["CounterR2"] = szTmp;
				}
			}
		}

		void CWebServer::Cmd_CheckForUpdate(WebEmSession &session, const request &req, Json::Value &root)
		{
			bool bHaveUser = (!session.username.empty());
			int urights = 3;
			if (bHaveUser)
			{
				int iUser = FindUser(session.username.c_str());
				if (iUser != -1)
					urights = static_cast<int>(m_users[iUser].userrights);
			}
			root["statuscode"] = urights;

			root["status"] = "OK";
			root["title"] = "CheckForUpdate";
			root["HaveUpdate"] = false;
			root["Revision"] = m_mainworker.m_iRevision;

			if (session.rights != 2)
			{
				session.reply_status = reply::forbidden;
				return; // Only admin users may update
			}

			bool bIsForced = (request::findValue(&req, "forced") == "true");

			if (!bIsForced)
			{
				int nValue = 0;
				m_sql.GetPreferencesVar("UseAutoUpdate", nValue);
				if (nValue != 1)
				{
					return;
				}
			}

			root["HaveUpdate"] = m_mainworker.IsUpdateAvailable(bIsForced);
			root["DomoticzUpdateURL"] = m_mainworker.m_szDomoticzUpdateURL;
			root["SystemName"] = m_mainworker.m_szSystemName;
			root["Revision"] = m_mainworker.m_iRevision;
		}

		void CWebServer::Cmd_DownloadUpdate(WebEmSession &session, const request &req, Json::Value &root)
		{
			if (!m_mainworker.StartDownloadUpdate())
				return;
			root["status"] = "OK";
			root["title"] = "DownloadUpdate";
		}

		void CWebServer::Cmd_DownloadReady(WebEmSession &session, const request &req, Json::Value &root)
		{
			if (!m_mainworker.m_bHaveDownloadedDomoticzUpdate)
				return;
			root["status"] = "OK";
			root["title"] = "DownloadReady";
			root["downloadok"] = (m_mainworker.m_bHaveDownloadedDomoticzUpdateSuccessFull) ? true : false;
		}

		void CWebServer::Cmd_DeleteDateRange(WebEmSession &session, const request &req, Json::Value &root)
		{
			if (session.rights != 2)
			{
				session.reply_status = reply::forbidden;
				return; // Only admin user allowed
			}
			const std::string idx = request::findValue(&req, "idx");
			const std::string fromDate = request::findValue(&req, "fromdate");
			const std::string toDate = request::findValue(&req, "todate");
			if ((idx.empty()) || (fromDate.empty() || toDate.empty()))
				return;
			root["status"] = "OK";
			root["title"] = "deletedaterange";
			m_sql.DeleteDateRange(idx.c_str(), fromDate, toDate);
		}

		void CWebServer::Cmd_DeleteDataPoint(WebEmSession &session, const request &req, Json::Value &root)
		{
			if (session.rights != 2)
			{
				session.reply_status = reply::forbidden;
				return; // Only admin user allowed
			}
			const std::string idx = request::findValue(&req, "idx");
			const std::string Date = request::findValue(&req, "date");

			if ((idx.empty()) || (Date.empty()))
				return;

			root["status"] = "OK";
			root["title"] = "deletedatapoint";
			m_sql.DeleteDataPoint(idx.c_str(), Date);
		}

		bool CWebServer::IsIdxForUser(const WebEmSession *pSession, const int Idx)
		{
			if (pSession->rights == 2)
				return true;
			if (pSession->rights == 0)
				return false; // viewer
			// User
			int iUser = FindUser(pSession->username.c_str());
			if ((iUser < 0) || (iUser >= (int)m_users.size()))
				return false;

			if (m_users[iUser].TotSensors == 0)
				return true; // all sensors

			std::vector<std::vector<std::string>> result =
				m_sql.safe_query("SELECT DeviceRowID FROM SharedDevices WHERE (SharedUserID == '%d') AND (DeviceRowID == '%d')", m_users[iUser].ID, Idx);
			return (!result.empty());
		}

		void CWebServer::HandleCommand(const std::string &cparam, WebEmSession &session, const request &req, Json::Value &root)
		{
			auto pf = m_webcommands.find(cparam);
			if (pf != m_webcommands.end())
			{
				pf->second(session, req, root);
				return;
			}

			std::vector<std::vector<std::string>> result;
			char szTmp[300];

			bool bHaveUser = (!session.username.empty());
			/*
						int iUser = -1;
						if (bHaveUser)
						{
							iUser = FindUser(session.username.c_str());
						}
			*/
			if (cparam == "deleteallsubdevices")
			{
				if (session.rights < 2)
				{
					session.reply_status = reply::forbidden;
					return; // Only admin user allowed
				}

				std::string idx = request::findValue(&req, "idx");
				if (idx.empty())
					return;
				root["status"] = "OK";
				root["title"] = "DeleteAllSubDevices";
				result = m_sql.safe_query("DELETE FROM LightSubDevices WHERE (ParentID == '%q')", idx.c_str());
			}
			else if (cparam == "deletesubdevice")
			{
				if (session.rights < 2)
				{
					session.reply_status = reply::forbidden;
					return; // Only admin user allowed
				}

				std::string idx = request::findValue(&req, "idx");
				if (idx.empty())
					return;
				root["status"] = "OK";
				root["title"] = "DeleteSubDevice";
				result = m_sql.safe_query("DELETE FROM LightSubDevices WHERE (ID == '%q')", idx.c_str());
			}
			else if (cparam == "addsubdevice")
			{
				if (session.rights < 2)
				{
					session.reply_status = reply::forbidden;
					return; // Only admin user allowed
				}

				std::string idx = request::findValue(&req, "idx");
				std::string subidx = request::findValue(&req, "subidx");
				if ((idx.empty()) || (subidx.empty()))
					return;
				if (idx == subidx)
					return;

				// first check if it is not already a sub device
				result = m_sql.safe_query("SELECT ID FROM LightSubDevices WHERE (DeviceRowID=='%q') AND (ParentID =='%q')", subidx.c_str(), idx.c_str());
				if (result.empty())
				{
					root["status"] = "OK";
					root["title"] = "AddSubDevice";
					// no it is not, add it
					result = m_sql.safe_query("INSERT INTO LightSubDevices (DeviceRowID, ParentID) VALUES ('%q','%q')", subidx.c_str(), idx.c_str());
				}
			}
			else if (cparam == "addscenedevice")
			{
				if (session.rights < 2)
				{
					session.reply_status = reply::forbidden;
					return; // Only admin user allowed
				}

				std::string idx = request::findValue(&req, "idx");
				std::string devidx = request::findValue(&req, "devidx");
				std::string isscene = request::findValue(&req, "isscene");
				std::string scommand = request::findValue(&req, "command");
				int ondelay = atoi(request::findValue(&req, "ondelay").c_str());
				int offdelay = atoi(request::findValue(&req, "offdelay").c_str());

				if ((idx.empty()) || (devidx.empty()) || (isscene.empty()))
					return;
				int level = -1;
				if (request::hasValue(&req, "level"))
					level = atoi(request::findValue(&req, "level").c_str());
				std::string color = _tColor(request::findValue(&req, "color")).toJSONString(); // Parse the color to detect incorrectly formatted color data

				unsigned char command = 0;
				result = m_sql.safe_query("SELECT HardwareID, DeviceID, Unit, Type, SubType, SwitchType, Options FROM DeviceStatus WHERE (ID=='%q')", devidx.c_str());
				if (!result.empty())
				{
					int dType = atoi(result[0][3].c_str());
					int sType = atoi(result[0][4].c_str());
					_eSwitchType switchtype = (_eSwitchType)atoi(result[0][5].c_str());
					std::map<std::string, std::string> options = m_sql.BuildDeviceOptions(result[0][6]);
					GetLightCommand(dType, sType, switchtype, scommand, command, options);
				}

				// first check if this device is not the scene code!
				result = m_sql.safe_query("SELECT Activators, SceneType FROM Scenes WHERE (ID=='%q')", idx.c_str());
				if (!result.empty())
				{
					// int SceneType = atoi(result[0][1].c_str());

					std::vector<std::string> arrayActivators;
					StringSplit(result[0][0], ";", arrayActivators);
					for (const auto &sCodeCmd : arrayActivators)
					{
						std::vector<std::string> arrayCode;
						StringSplit(sCodeCmd, ":", arrayCode);

						std::string sID = arrayCode[0];
						std::string sCode;
						if (arrayCode.size() == 2)
						{
							sCode = arrayCode[1];
						}

						if (sID == devidx)
						{
							return; // Group does not work with separate codes, so already there
						}
					}
				}
				// first check if it is not already a part of this scene/group (with the same OnDelay)
				if (isscene == "true")
				{
					result = m_sql.safe_query(
						"SELECT ID FROM SceneDevices WHERE (DeviceRowID=='%q') AND (SceneRowID =='%q') AND (OnDelay == %d) AND (OffDelay == %d) AND (Cmd == %d)",
						devidx.c_str(), idx.c_str(), ondelay, offdelay, command);
				}
				else
				{
					result = m_sql.safe_query("SELECT ID FROM SceneDevices WHERE (DeviceRowID=='%q') AND (SceneRowID =='%q') AND (OnDelay == %d)", devidx.c_str(), idx.c_str(),
								  ondelay);
				}
				if (result.empty())
				{
					root["status"] = "OK";
					root["title"] = "AddSceneDevice";
					// no it is not, add it
					if (isscene == "true")
					{
						m_sql.safe_query("INSERT INTO SceneDevices (DeviceRowID, SceneRowID, Cmd, Level, Color, OnDelay, OffDelay) VALUES ('%q','%q',%d,%d,'%q',%d,%d)",
								 devidx.c_str(), idx.c_str(), command, level, color.c_str(), ondelay, offdelay);
					}
					else
					{
						m_sql.safe_query("INSERT INTO SceneDevices (DeviceRowID, SceneRowID, Level, Color, OnDelay, OffDelay) VALUES ('%q','%q',%d,'%q',%d,%d)", devidx.c_str(),
								 idx.c_str(), level, color.c_str(), ondelay, offdelay);
					}
					if (m_sql.m_bEnableEventSystem)
						m_mainworker.m_eventsystem.GetCurrentScenesGroups();
				}
			}
			else if (cparam == "updatescenedevice")
			{
				if (session.rights < 2)
				{
					session.reply_status = reply::forbidden;
					return; // Only admin user allowed
				}

				std::string idx = request::findValue(&req, "idx");
				std::string devidx = request::findValue(&req, "devidx");
				std::string scommand = request::findValue(&req, "command");
				int ondelay = atoi(request::findValue(&req, "ondelay").c_str());
				int offdelay = atoi(request::findValue(&req, "offdelay").c_str());

				if ((idx.empty()) || (devidx.empty()))
					return;

				unsigned char command = 0;

				result = m_sql.safe_query("SELECT HardwareID, DeviceID, Unit, Type, SubType, SwitchType, Options FROM DeviceStatus WHERE (ID=='%q')", devidx.c_str());
				if (!result.empty())
				{
					int dType = atoi(result[0][3].c_str());
					int sType = atoi(result[0][4].c_str());
					_eSwitchType switchtype = (_eSwitchType)atoi(result[0][5].c_str());
					std::map<std::string, std::string> options = m_sql.BuildDeviceOptions(result[0][6]);
					GetLightCommand(dType, sType, switchtype, scommand, command, options);
				}
				int level = -1;
				if (request::hasValue(&req, "level"))
					level = atoi(request::findValue(&req, "level").c_str());
				std::string color = _tColor(request::findValue(&req, "color")).toJSONString(); // Parse the color to detect incorrectly formatted color data
				root["status"] = "OK";
				root["title"] = "UpdateSceneDevice";
				result = m_sql.safe_query("UPDATE SceneDevices SET Cmd=%d, Level=%d, Color='%q', OnDelay=%d, OffDelay=%d  WHERE (ID == '%q')", command, level, color.c_str(), ondelay,
							  offdelay, idx.c_str());
			}
			else if (cparam == "deletescenedevice")
			{
				if (session.rights < 2)
				{
					session.reply_status = reply::forbidden;
					return; // Only admin user allowed
				}

				std::string idx = request::findValue(&req, "idx");
				if (idx.empty())
					return;
				root["status"] = "OK";
				root["title"] = "DeleteSceneDevice";
				m_sql.safe_query("DELETE FROM SceneDevices WHERE (ID == '%q')", idx.c_str());
				m_sql.safe_query("DELETE FROM CamerasActiveDevices WHERE (DevSceneType==1) AND (DevSceneRowID == '%q')", idx.c_str());
				if (m_sql.m_bEnableEventSystem)
					m_mainworker.m_eventsystem.GetCurrentScenesGroups();
			}
			else if (cparam == "getsubdevices")
			{
				std::string idx = request::findValue(&req, "idx");
				if (idx.empty())
					return;

				root["status"] = "OK";
				root["title"] = "GetSubDevices";
				result = m_sql.safe_query("SELECT a.ID, b.Name FROM LightSubDevices a, DeviceStatus b WHERE (a.ParentID=='%q') AND (b.ID == a.DeviceRowID)", idx.c_str());
				if (!result.empty())
				{
					int ii = 0;
					for (const auto &sd : result)
					{
						root["result"][ii]["ID"] = sd[0];
						root["result"][ii]["Name"] = sd[1];
						ii++;
					}
				}
			}
			else if (cparam == "getscenedevices")
			{
				std::string idx = request::findValue(&req, "idx");
				std::string isscene = request::findValue(&req, "isscene");

				if ((idx.empty()) || (isscene.empty()))
					return;

				root["status"] = "OK";
				root["title"] = "GetSceneDevices";

				result = m_sql.safe_query("SELECT a.ID, b.Name, a.DeviceRowID, b.Type, b.SubType, b.nValue, b.sValue, a.Cmd, a.Level, b.ID, a.[Order], a.Color, a.OnDelay, a.OffDelay, "
							  "b.SwitchType FROM SceneDevices a, DeviceStatus b WHERE (a.SceneRowID=='%q') AND (b.ID == a.DeviceRowID) ORDER BY a.[Order]",
							  idx.c_str());
				if (!result.empty())
				{
					int ii = 0;
					for (const auto &sd : result)
					{
						root["result"][ii]["ID"] = sd[0];
						root["result"][ii]["Name"] = sd[1];
						root["result"][ii]["DevID"] = sd[2];
						root["result"][ii]["DevRealIdx"] = sd[9];
						root["result"][ii]["Order"] = atoi(sd[10].c_str());
						root["result"][ii]["OnDelay"] = atoi(sd[12].c_str());
						root["result"][ii]["OffDelay"] = atoi(sd[13].c_str());

						_eSwitchType switchtype = (_eSwitchType)atoi(sd[14].c_str());

						unsigned char devType = atoi(sd[3].c_str());

						// switchtype seemed not to be used down with the GetLightStatus command,
						// causing RFY to go wrong, fixing here
						if (devType != pTypeRFY)
							switchtype = STYPE_OnOff;

						unsigned char subType = atoi(sd[4].c_str());
						// unsigned char nValue = (unsigned char)atoi(sd[5].c_str());
						std::string sValue = sd[6];
						int command = atoi(sd[7].c_str());
						int level = atoi(sd[8].c_str());

						std::string lstatus;
						int llevel = 0;
						bool bHaveDimmer = false;
						bool bHaveGroupCmd = false;
						int maxDimLevel = 0;
						GetLightStatus(devType, subType, switchtype, command, sValue, lstatus, llevel, bHaveDimmer, maxDimLevel, bHaveGroupCmd);
						root["result"][ii]["Command"] = lstatus;
						root["result"][ii]["Level"] = level;
						root["result"][ii]["Color"] = _tColor(sd[11]).toJSONString();
						root["result"][ii]["Type"] = RFX_Type_Desc(devType, 1);
						root["result"][ii]["SubType"] = RFX_Type_SubType_Desc(devType, subType);
						ii++;
					}
				}
			}
			else if (cparam == "changescenedeviceorder")
			{
				if (session.rights < 2)
				{
					session.reply_status = reply::forbidden;
					return; // Only admin user allowed
				}

				std::string idx = request::findValue(&req, "idx");
				if (idx.empty())
					return;
				std::string sway = request::findValue(&req, "way");
				if (sway.empty())
					return;
				bool bGoUp = (sway == "0");

				std::string aScene, aOrder, oID, oOrder;

				// Get actual device order
				result = m_sql.safe_query("SELECT SceneRowID, [Order] FROM SceneDevices WHERE (ID=='%q')", idx.c_str());
				if (result.empty())
					return;
				aScene = result[0][0];
				aOrder = result[0][1];

				if (!bGoUp)
				{
					// Get next device order
					result =
						m_sql.safe_query("SELECT ID, [Order] FROM SceneDevices WHERE (SceneRowID=='%q' AND [Order]>'%q') ORDER BY [Order] ASC", aScene.c_str(), aOrder.c_str());
					if (result.empty())
						return;
					oID = result[0][0];
					oOrder = result[0][1];
				}
				else
				{
					// Get previous device order
					result = m_sql.safe_query("SELECT ID, [Order] FROM SceneDevices WHERE (SceneRowID=='%q' AND [Order]<'%q') ORDER BY [Order] DESC", aScene.c_str(),
								  aOrder.c_str());
					if (result.empty())
						return;
					oID = result[0][0];
					oOrder = result[0][1];
				}
				// Swap them
				root["status"] = "OK";
				root["title"] = "ChangeSceneDeviceOrder";

				result = m_sql.safe_query("UPDATE SceneDevices SET [Order] = '%q' WHERE (ID='%q')", oOrder.c_str(), idx.c_str());
				result = m_sql.safe_query("UPDATE SceneDevices SET [Order] = '%q' WHERE (ID='%q')", aOrder.c_str(), oID.c_str());
			}
			else if (cparam == "deleteallscenedevices")
			{
				if (session.rights < 2)
				{
					session.reply_status = reply::forbidden;
					return; // Only admin user allowed
				}

				std::string idx = request::findValue(&req, "idx");
				if (idx.empty())
					return;
				root["status"] = "OK";
				root["title"] = "DeleteAllSceneDevices";
				result = m_sql.safe_query("DELETE FROM SceneDevices WHERE (SceneRowID == %q)", idx.c_str());
			}
			else if (cparam == "getmanualhardware")
			{
				// used by Add Manual Light/Switch dialog
				root["status"] = "OK";
				root["title"] = "GetHardware";
				result = m_sql.safe_query("SELECT ID, Name, Type FROM Hardware ORDER BY ID ASC");
				if (!result.empty())
				{
					int ii = 0;
					for (const auto &sd : result)
					{
						int ID = atoi(sd[0].c_str());
						std::string Name = sd[1];
						_eHardwareTypes Type = (_eHardwareTypes)atoi(sd[2].c_str());

						if ((Type == HTYPE_RFXLAN) || (Type == HTYPE_RFXtrx315) || (Type == HTYPE_RFXtrx433) || (Type == HTYPE_RFXtrx868) || (Type == HTYPE_EnOceanESP2) ||
						    (Type == HTYPE_EnOceanESP3) || (Type == HTYPE_Dummy) || (Type == HTYPE_Tellstick) || (Type == HTYPE_EVOHOME_SCRIPT) ||
						    (Type == HTYPE_EVOHOME_SERIAL) || (Type == HTYPE_EVOHOME_WEB) || (Type == HTYPE_EVOHOME_TCP) || (Type == HTYPE_RaspberryGPIO) ||
						    (Type == HTYPE_RFLINKUSB) || (Type == HTYPE_RFLINKTCP) || (Type == HTYPE_ZIBLUEUSB) || (Type == HTYPE_ZIBLUETCP) || (Type == HTYPE_OpenWebNetTCP) ||
						    (Type == HTYPE_OpenWebNetUSB) || (Type == HTYPE_SysfsGpio) || (Type == HTYPE_USBtinGateway))
						{
							root["result"][ii]["idx"] = ID;
							root["result"][ii]["Name"] = Name;
							ii++;
						}
					}
				}
			}
			else if (cparam == "getgpio")
			{
				// used by Add Manual Light/Switch dialog
				root["title"] = "GetGpio";
#ifdef WITH_GPIO
				std::vector<CGpioPin> pins = CGpio::GetPinList();
				if (pins.empty())
				{
					root["status"] = "ERROR";
					root["result"][0]["idx"] = 0;
					root["result"][0]["Name"] = "GPIO INIT ERROR";
				}
				else
				{
					int ii = 0;
					for (auto &pin : pins)
					{
						root["status"] = "OK";
						root["result"][ii]["idx"] = pin.GetPin();
						root["result"][ii]["Name"] = pin.ToString();
						ii++;
					}
				}
#else
				root["status"] = "OK";
				root["result"][0]["idx"] = 0;
				root["result"][0]["Name"] = "N/A";
#endif
			}
			else if (cparam == "getsysfsgpio")
			{
				// used by Add Manual Light/Switch dialog
				root["title"] = "GetSysfsGpio";
#ifdef WITH_GPIO
				std::vector<int> gpio_ids = CSysfsGpio::GetGpioIds();
				std::vector<std::string> gpio_names = CSysfsGpio::GetGpioNames();

				if (gpio_ids.empty())
				{
					root["status"] = "ERROR";
					root["result"][0]["idx"] = 0;
					root["result"][0]["Name"] = "No sysfs-gpio exports";
				}
				else
				{
					for (int ii = 0; ii < gpio_ids.size(); ii++)
					{
						root["status"] = "OK";
						root["result"][ii]["idx"] = gpio_ids[ii];
						root["result"][ii]["Name"] = gpio_names[ii];
					}
				}
#else
				root["status"] = "OK";
				root["result"][0]["idx"] = 0;
				root["result"][0]["Name"] = "N/A";
#endif
			}
			else if (cparam == "getlightswitches")
			{
				root["status"] = "OK";
				root["title"] = "GetLightSwitches";
				result = m_sql.safe_query("SELECT ID, Name, Type, SubType, Used, SwitchType, Options FROM DeviceStatus ORDER BY Name");
				if (!result.empty())
				{
					int ii = 0;
					for (const auto &sd : result)
					{
						std::string ID = sd[0];
						std::string Name = sd[1];
						int Type = atoi(sd[2].c_str());
						int SubType = atoi(sd[3].c_str());
						int used = atoi(sd[4].c_str());
						_eSwitchType switchtype = (_eSwitchType)atoi(sd[5].c_str());
						std::map<std::string, std::string> options = m_sql.BuildDeviceOptions(sd[6]);
						bool bdoAdd = false;
						switch (Type)
						{
							case pTypeLighting1:
							case pTypeLighting2:
							case pTypeLighting3:
							case pTypeLighting4:
							case pTypeLighting5:
							case pTypeLighting6:
							case pTypeFan:
							case pTypeColorSwitch:
							case pTypeSecurity1:
							case pTypeSecurity2:
							case pTypeEvohome:
							case pTypeEvohomeRelay:
							case pTypeCurtain:
							case pTypeBlinds:
							case pTypeRFY:
							case pTypeChime:
							case pTypeThermostat2:
							case pTypeThermostat3:
							case pTypeThermostat4:
							case pTypeRemote:
							case pTypeRadiator1:
							case pTypeGeneralSwitch:
							case pTypeHomeConfort:
							case pTypeFS20:
							case pTypeHunter:
								bdoAdd = true;
								if (!used)
								{
									bdoAdd = false;
									// bool bIsSubDevice = false;
									std::vector<std::vector<std::string>> resultSD;
									resultSD = m_sql.safe_query("SELECT ID FROM LightSubDevices WHERE (DeviceRowID=='%q')", sd[0].c_str());
									if (!resultSD.empty())
										bdoAdd = true;
								}
								if ((Type == pTypeRadiator1) && (SubType != sTypeSmartwaresSwitchRadiator))
									bdoAdd = false;
								if (bdoAdd)
								{
									int idx = atoi(ID.c_str());
									if (!IsIdxForUser(&session, idx))
										continue;
									root["result"][ii]["idx"] = ID;
									root["result"][ii]["Name"] = Name;
									root["result"][ii]["Type"] = RFX_Type_Desc(Type, 1);
									root["result"][ii]["SubType"] = RFX_Type_SubType_Desc(Type, SubType);
									bool bIsDimmer = (
										(switchtype == STYPE_Dimmer)
										|| (switchtype == STYPE_BlindsPercentage)
										|| (switchtype == STYPE_BlindsPercentageInverted)
										|| (switchtype == STYPE_BlindsPercentageWithStop)
										|| (switchtype == STYPE_BlindsPercentageInvertedWithStop)
										|| (switchtype == STYPE_Selector)
										);
									root["result"][ii]["IsDimmer"] = bIsDimmer;

									std::string dimmerLevels = "none";

									if (bIsDimmer)
									{
										std::stringstream ss;

										if (switchtype == STYPE_Selector)
										{
											std::map<std::string, std::string> selectorStatuses;
											GetSelectorSwitchStatuses(options, selectorStatuses);
											bool levelOffHidden = (options["LevelOffHidden"] == "true");
											for (int i = 0; i < (int)selectorStatuses.size(); i++)
											{
												if (levelOffHidden && (i == 0))
												{
													continue;
												}
												if ((levelOffHidden && (i > 1)) || (i > 0))
												{
													ss << ",";
												}
												ss << i * 10;
											}
										}
										else
										{
											int nValue = 0;
											std::string sValue;
											std::string lstatus;
											int llevel = 0;
											bool bHaveDimmer = false;
											int maxDimLevel = 0;
											bool bHaveGroupCmd = false;

											GetLightStatus(Type, SubType, switchtype, nValue, sValue, lstatus, llevel, bHaveDimmer, maxDimLevel,
												       bHaveGroupCmd);

											for (int i = 0; i <= maxDimLevel; i++)
											{
												if (i != 0)
												{
													ss << ",";
												}
												ss << (int)float((100.0F / float(maxDimLevel)) * i);
											}
										}
										dimmerLevels = ss.str();
									}
									root["result"][ii]["DimmerLevels"] = dimmerLevels;
									ii++;
								}
								break;
						}
					}
				}
			}
			else if (cparam == "getlightswitchesscenes")
			{
				root["status"] = "OK";
				root["title"] = "GetLightSwitchesScenes";
				int ii = 0;

				// First List/Switch Devices
				result = m_sql.safe_query("SELECT ID, Name, Type, SubType, Used FROM DeviceStatus ORDER BY Name");
				if (!result.empty())
				{
					for (const auto &sd : result)
					{
						std::string ID = sd[0];
						std::string Name = sd[1];
						int Type = atoi(sd[2].c_str());
						int SubType = atoi(sd[3].c_str());
						int used = atoi(sd[4].c_str());
						if (used)
						{
							switch (Type)
							{
								case pTypeLighting1:
								case pTypeLighting2:
								case pTypeLighting3:
								case pTypeLighting4:
								case pTypeLighting5:
								case pTypeLighting6:
								case pTypeFan:
								case pTypeColorSwitch:
								case pTypeSecurity1:
								case pTypeSecurity2:
								case pTypeEvohome:
								case pTypeEvohomeRelay:
								case pTypeCurtain:
								case pTypeBlinds:
								case pTypeRFY:
								case pTypeChime:
								case pTypeThermostat2:
								case pTypeThermostat3:
								case pTypeThermostat4:
								case pTypeRemote:
								case pTypeGeneralSwitch:
								case pTypeHomeConfort:
								case pTypeFS20:
								case pTypeHunter:
									root["result"][ii]["type"] = 0;
									root["result"][ii]["idx"] = ID;
									root["result"][ii]["Name"] = "[Light/Switch] " + Name;
									ii++;
									break;
								case pTypeRadiator1:
									if (SubType == sTypeSmartwaresSwitchRadiator)
									{
										root["result"][ii]["type"] = 0;
										root["result"][ii]["idx"] = ID;
										root["result"][ii]["Name"] = "[Light/Switch] " + Name;
										ii++;
									}
									break;
							}
						}
					}
				} // end light/switches

				// Add Scenes
				result = m_sql.safe_query("SELECT ID, Name FROM Scenes ORDER BY Name");
				if (!result.empty())
				{
					for (const auto &sd : result)
					{
						std::string ID = sd[0];
						std::string Name = sd[1];

						root["result"][ii]["type"] = 1;
						root["result"][ii]["idx"] = ID;
						root["result"][ii]["Name"] = "[Scene] " + Name;
						ii++;
					}
				} // end light/switches
			}
			else if (cparam == "getcamactivedevices")
			{
				std::string idx = request::findValue(&req, "idx");
				if (idx.empty())
					return;
				root["status"] = "OK";
				root["title"] = "GetCameraActiveDevices";
				// First List/Switch Devices
				result = m_sql.safe_query("SELECT ID, DevSceneType, DevSceneRowID, DevSceneWhen, DevSceneDelay FROM CamerasActiveDevices WHERE (CameraRowID=='%q') ORDER BY ID",
							  idx.c_str());
				if (!result.empty())
				{
					int ii = 0;
					for (const auto &sd : result)
					{
						std::string ID = sd[0];
						int DevSceneType = atoi(sd[1].c_str());
						std::string DevSceneRowID = sd[2];
						int DevSceneWhen = atoi(sd[3].c_str());
						int DevSceneDelay = atoi(sd[4].c_str());

						std::string Name;
						if (DevSceneType == 0)
						{
							std::vector<std::vector<std::string>> result2;
							result2 = m_sql.safe_query("SELECT Name FROM DeviceStatus WHERE (ID=='%q')", DevSceneRowID.c_str());
							if (!result2.empty())
							{
								Name = "[Light/Switches] " + result2[0][0];
							}
						}
						else
						{
							std::vector<std::vector<std::string>> result2;
							result2 = m_sql.safe_query("SELECT Name FROM Scenes WHERE (ID=='%q')", DevSceneRowID.c_str());
							if (!result2.empty())
							{
								Name = "[Scene] " + result2[0][0];
							}
						}
						if (!Name.empty())
						{
							root["result"][ii]["idx"] = ID;
							root["result"][ii]["type"] = DevSceneType;
							root["result"][ii]["DevSceneRowID"] = DevSceneRowID;
							root["result"][ii]["when"] = DevSceneWhen;
							root["result"][ii]["delay"] = DevSceneDelay;
							root["result"][ii]["Name"] = Name;
							ii++;
						}
					}
				}
			}
			else if (cparam == "addcamactivedevice")
			{
				if (session.rights < 2)
				{
					session.reply_status = reply::forbidden;
					return; // Only admin user allowed
				}

				std::string idx = request::findValue(&req, "idx");
				std::string activeidx = request::findValue(&req, "activeidx");
				std::string sactivetype = request::findValue(&req, "activetype");
				std::string sactivewhen = request::findValue(&req, "activewhen");
				std::string sactivedelay = request::findValue(&req, "activedelay");

				if ((idx.empty()) || (activeidx.empty()) || (sactivetype.empty()) || (sactivewhen.empty()) || (sactivedelay.empty()))
				{
					return;
				}

				int activetype = atoi(sactivetype.c_str());
				int activewhen = atoi(sactivewhen.c_str());
				int activedelay = atoi(sactivedelay.c_str());

				// first check if it is not already a Active Device
				result = m_sql.safe_query("SELECT ID FROM CamerasActiveDevices WHERE (CameraRowID=='%q')"
							  " AND (DevSceneType==%d) AND (DevSceneRowID=='%q')"
							  " AND (DevSceneWhen==%d)",
							  idx.c_str(), activetype, activeidx.c_str(), activewhen);
				if (result.empty())
				{
					root["status"] = "OK";
					root["title"] = "AddCameraActiveDevice";
					// no it is not, add it
					result =
						m_sql.safe_query("INSERT INTO CamerasActiveDevices (CameraRowID, DevSceneType, DevSceneRowID, DevSceneWhen, DevSceneDelay) VALUES ('%q',%d,'%q',%d,%d)",
								 idx.c_str(), activetype, activeidx.c_str(), activewhen, activedelay);
					m_mainworker.m_cameras.ReloadCameras();
				}
			}
			else if (cparam == "deleteamactivedevice")
			{
				if (session.rights < 2)
				{
					session.reply_status = reply::forbidden;
					return; // Only admin user allowed
				}

				std::string idx = request::findValue(&req, "idx");
				if (idx.empty())
					return;
				root["status"] = "OK";
				root["title"] = "DeleteCameraActiveDevice";
				result = m_sql.safe_query("DELETE FROM CamerasActiveDevices WHERE (ID == '%q')", idx.c_str());
				m_mainworker.m_cameras.ReloadCameras();
			}
			else if (cparam == "deleteallactivecamdevices")
			{
				if (session.rights < 2)
				{
					session.reply_status = reply::forbidden;
					return; // Only admin user allowed
				}

				std::string idx = request::findValue(&req, "idx");
				if (idx.empty())
					return;
				root["status"] = "OK";
				root["title"] = "DeleteAllCameraActiveDevices";
				result = m_sql.safe_query("DELETE FROM CamerasActiveDevices WHERE (CameraRowID == '%q')", idx.c_str());
				m_mainworker.m_cameras.ReloadCameras();
			}
			else if (cparam == "testnotification")
			{
				if (session.rights < 2)
				{
					session.reply_status = reply::forbidden;
					return; // Only admin user allowed
				}

				std::string notification_Title = "Domoticz test";
				std::string notification_Message = "Domoticz test message!";
				std::string subsystem = request::findValue(&req, "subsystem");

				std::string extraData = request::findValue(&req, "extradata");

				m_notifications.ConfigFromGetvars(req, false);
				if (m_notifications.SendMessage(0, std::string(""), subsystem, notification_Title, notification_Message, extraData, 1, std::string(""), false))
				{
					root["status"] = "OK";
				}
				/* we need to reload the config, because the values that were set were only for testing */
				m_notifications.LoadConfig();
			}
			else if (cparam == "testswitch")
			{
				std::string Username = "Admin";
				if (!session.username.empty())
					Username = session.username;
				if (session.rights < 2)
				{
					session.reply_status = reply::forbidden;
					return; // Only admin user allowed
				}

				std::string hwdid = request::findValue(&req, "hwdid");
				std::string sswitchtype = request::findValue(&req, "switchtype");
				std::string slighttype = request::findValue(&req, "lighttype");

				if ((hwdid.empty()) || (sswitchtype.empty()) || (slighttype.empty()))
					return;
				_eSwitchType switchtype = (_eSwitchType)atoi(sswitchtype.c_str());
				int lighttype = atoi(slighttype.c_str());
				int dtype;
				int subtype = 0;
				std::string sunitcode;
				std::string devid;

				if (lighttype == 70)
				{
					// EnOcean (Lighting2 with Base_ID offset)
					dtype = pTypeLighting2;
					subtype = sTypeAC;
					std::string sgroupcode = request::findValue(&req, "groupcode");
					sunitcode = request::findValue(&req, "unitcode");
					int iUnitTest = atoi(sunitcode.c_str()); // only First Rocker_ID at the moment, gives us 128 devices we can control, should be enough!
					if ((sunitcode.empty()) || (sgroupcode.empty()) || ((iUnitTest < 1) || (iUnitTest > 128)))
						return;
					sunitcode = sgroupcode; // Button A or B
					CDomoticzHardwareBase *pBaseHardware = reinterpret_cast<CDomoticzHardwareBase *>(m_mainworker.GetHardware(atoi(hwdid.c_str())));
					if (pBaseHardware == nullptr)
						return;
					if ((pBaseHardware->HwdType != HTYPE_EnOceanESP2) && (pBaseHardware->HwdType != HTYPE_EnOceanESP3) && (pBaseHardware->HwdType != HTYPE_USBtinGateway))
						return;
					unsigned long rID = 0;
					if (pBaseHardware->HwdType == HTYPE_EnOceanESP2)
					{
						CEnOceanESP2 *pEnoceanHardware = reinterpret_cast<CEnOceanESP2 *>(pBaseHardware);
						rID = pEnoceanHardware->m_id_base + iUnitTest;
					}
					else if (pBaseHardware->HwdType == HTYPE_EnOceanESP3)
					{
						CEnOceanESP3 *pEnoceanHardware = reinterpret_cast<CEnOceanESP3 *>(pBaseHardware);
						rID = pEnoceanHardware->m_id_base + iUnitTest;
					}
					else if (pBaseHardware->HwdType == HTYPE_USBtinGateway) // Like EnOcean (Lighting2 with Base_ID offset)
					{
						USBtin *pUSBtinHardware = reinterpret_cast<USBtin *>(pBaseHardware);
						// base ID calculate in the USBtinharwade dependant of the CAN Layer !
						// for exemple see MultiblocV8 layer...
						rID = pUSBtinHardware->switch_id_base;
						std::stringstream ssunitcode;
						ssunitcode << iUnitTest;
						sunitcode = ssunitcode.str();
					}
					// convert to hex, and we have our ID
					std::stringstream s_strid;
					s_strid << std::hex << std::uppercase << rID;
					devid = s_strid.str();
				}
				else if (lighttype == 68)
				{
#ifdef WITH_GPIO
					dtype = pTypeLighting1;
					subtype = sTypeIMPULS;
					devid = "0";
					sunitcode = request::findValue(&req, "unitcode"); // Unit code = GPIO number

					if (sunitcode.empty())
					{
						root["status"] = "ERROR";
						root["message"] = "No GPIO number given";
						return;
					}
					CGpio *pGpio = reinterpret_cast<CGpio *>(m_mainworker.GetHardware(atoi(hwdid.c_str())));
					if (pGpio == nullptr)
					{
						root["status"] = "ERROR";
						root["message"] = "Could not retrieve GPIO hardware pointer";
						return;
					}
					if (pGpio->HwdType != HTYPE_RaspberryGPIO)
					{
						root["status"] = "ERROR";
						root["message"] = "Given hardware is not GPIO";
						return;
					}
					CGpioPin *pPin = CGpio::GetPPinById(atoi(sunitcode.c_str()));
					if (pPin == nullptr)
					{
						root["status"] = "ERROR";
						root["message"] = "Given pin does not exist on this GPIO hardware";
						return;
					}
					if (pPin->GetIsInput())
					{
						root["status"] = "ERROR";
						root["message"] = "Given pin is not configured for output";
						return;
					}
#else
					root["status"] = "ERROR";
					root["message"] = "GPIO support is disabled";
					return;
#endif
				}
				else if (lighttype == 69)
				{
#ifdef WITH_GPIO

					sunitcode = request::findValue(&req, "unitcode"); // sysfs-gpio number
					int unitcode = atoi(sunitcode.c_str());
					dtype = pTypeLighting2;
					subtype = sTypeAC;
					std::string sswitchtype = request::findValue(&req, "switchtype");
					_eSwitchType switchtype = (_eSwitchType)atoi(sswitchtype.c_str());

					std::string id = request::findValue(&req, "id");
					if ((id.empty()) || (sunitcode.empty()))
					{
						return;
					}
					devid = id;

					if (sunitcode.empty())
					{
						root["status"] = "ERROR";
						root["message"] = "No GPIO number given";
						return;
					}

					CSysfsGpio *pSysfsGpio = reinterpret_cast<CSysfsGpio *>(m_mainworker.GetHardware(atoi(hwdid.c_str())));

					if (pSysfsGpio == nullptr)
					{
						root["status"] = "ERROR";
						root["message"] = "Could not retrieve SysfsGpio hardware pointer";
						return;
					}

					if (pSysfsGpio->HwdType != HTYPE_SysfsGpio)
					{
						root["status"] = "ERROR";
						root["message"] = "Given hardware is not SysfsGpio";
						return;
					}
#else
					root["status"] = "ERROR";
					root["message"] = "GPIO support is disabled";
					return;
#endif
				}
				else if (lighttype < 20)
				{
					dtype = pTypeLighting1;
					subtype = lighttype;
					std::string shousecode = request::findValue(&req, "housecode");
					sunitcode = request::findValue(&req, "unitcode");
					if ((shousecode.empty()) || (sunitcode.empty()))
						return;
					devid = shousecode;
				}
				else if (lighttype < 30)
				{
					dtype = pTypeLighting2;
					subtype = lighttype - 20;
					std::string id = request::findValue(&req, "id");
					sunitcode = request::findValue(&req, "unitcode");
					if ((id.empty()) || (sunitcode.empty()))
						return;
					devid = id;
				}
				else if (lighttype < 70)
				{
					dtype = pTypeLighting5;
					subtype = lighttype - 50;
					if (lighttype == 59)
						subtype = sTypeIT;
					std::string id = request::findValue(&req, "id");
					sunitcode = request::findValue(&req, "unitcode");
					if ((id.empty()) || (sunitcode.empty()))
						return;
					if ((subtype != sTypeEMW100) && (subtype != sTypeLivolo) && (subtype != sTypeLivolo1to10) && (subtype != sTypeRGB432W) && (subtype != sTypeIT))
						devid = "00" + id;
					else
						devid = id;
				}
				else
				{
					if (lighttype == 100)
					{
						// Chime/ByronSX
						dtype = pTypeChime;
						subtype = sTypeByronSX;
						std::string id = request::findValue(&req, "id");
						sunitcode = request::findValue(&req, "unitcode");
						if ((id.empty()) || (sunitcode.empty()))
							return;
						int iUnitCode = atoi(sunitcode.c_str()) - 1;
						switch (iUnitCode)
						{
							case 0:
								iUnitCode = chime_sound0;
								break;
							case 1:
								iUnitCode = chime_sound1;
								break;
							case 2:
								iUnitCode = chime_sound2;
								break;
							case 3:
								iUnitCode = chime_sound3;
								break;
							case 4:
								iUnitCode = chime_sound4;
								break;
							case 5:
								iUnitCode = chime_sound5;
								break;
							case 6:
								iUnitCode = chime_sound6;
								break;
							case 7:
								iUnitCode = chime_sound7;
								break;
						}
						sprintf(szTmp, "%d", iUnitCode);
						sunitcode = szTmp;
						devid = id;
					}
					else if (lighttype == 101)
					{
						// Curtain Harrison
						dtype = pTypeCurtain;
						subtype = sTypeHarrison;
						std::string shousecode = request::findValue(&req, "housecode");
						sunitcode = request::findValue(&req, "unitcode");
						if ((shousecode.empty()) || (sunitcode.empty()))
							return;
						devid = shousecode;
					}
					else if (lighttype == 102)
					{
						// RFY
						dtype = pTypeRFY;
						subtype = sTypeRFY;
						std::string id = request::findValue(&req, "id");
						sunitcode = request::findValue(&req, "unitcode");
						if ((id.empty()) || (sunitcode.empty()))
							return;
						devid = id;
					}
					else if (lighttype == 103)
					{
						// Meiantech
						dtype = pTypeSecurity1;
						subtype = sTypeMeiantech;
						std::string id = request::findValue(&req, "id");
						if ((id.empty()))
							return;
						devid = id;
						sunitcode = "0";
					}
					else if (lighttype == 104)
					{
						// HE105
						dtype = pTypeThermostat2;
						subtype = sTypeHE105;
						sunitcode = request::findValue(&req, "unitcode");
						if (sunitcode.empty())
							return;
						// convert to hex, and we have our Unit Code
						std::stringstream s_strid;
						s_strid << std::hex << std::uppercase << sunitcode;
						int iUnitCode;
						s_strid >> iUnitCode;
						sprintf(szTmp, "%d", iUnitCode);
						sunitcode = szTmp;
						devid = "1";
					}
					else if (lighttype == 105)
					{
						// ASA
						dtype = pTypeRFY;
						subtype = sTypeASA;
						std::string id = request::findValue(&req, "id");
						sunitcode = request::findValue(&req, "unitcode");
						if ((id.empty()) || (sunitcode.empty()))
							return;
						devid = id;
					}
					else if (lighttype == 106)
					{
						// Blyss
						dtype = pTypeLighting6;
						subtype = sTypeBlyss;
						std::string sgroupcode = request::findValue(&req, "groupcode");
						sunitcode = request::findValue(&req, "unitcode");
						std::string id = request::findValue(&req, "id");
						if ((sgroupcode.empty()) || (sunitcode.empty()) || (id.empty()))
							return;
						devid = id + sgroupcode;
					}
					else if (lighttype == 107)
					{
						// RFY2
						dtype = pTypeRFY;
						subtype = sTypeRFY2;
						std::string id = request::findValue(&req, "id");
						sunitcode = request::findValue(&req, "unitcode");
						if ((id.empty()) || (sunitcode.empty()))
							return;
						devid = id;
					}
					else if ((lighttype >= 200) && (lighttype < 300))
					{
						dtype = pTypeBlinds;
						subtype = lighttype - 200;
						std::string id = request::findValue(&req, "id");
						sunitcode = request::findValue(&req, "unitcode");
						if ((id.empty()) || (sunitcode.empty()))
							return;
						int iUnitCode = atoi(sunitcode.c_str());
						sprintf(szTmp, "%d", iUnitCode);
						sunitcode = szTmp;
						devid = id;
					}
					else if (lighttype == 301)
					{
						// Smartwares Radiator
						dtype = pTypeRadiator1;
						subtype = sTypeSmartwaresSwitchRadiator;
						std::string id = request::findValue(&req, "id");
						sunitcode = request::findValue(&req, "unitcode");
						if ((id.empty()) || (sunitcode.empty()))
							return;
						devid = id;
					}
					else if (lighttype == 302)
					{
						// Home Confort
						dtype = pTypeHomeConfort;
						subtype = sTypeHomeConfortTEL010;
						std::string id = request::findValue(&req, "id");

						std::string shousecode = request::findValue(&req, "housecode");
						sunitcode = request::findValue(&req, "unitcode");
						if ((id.empty()) || (shousecode.empty()) || (sunitcode.empty()))
							return;

						int iUnitCode = atoi(sunitcode.c_str());
						sprintf(szTmp, "%d", iUnitCode);
						sunitcode = szTmp;
						sprintf(szTmp, "%02X", atoi(shousecode.c_str()));
						shousecode = szTmp;
						devid = id + shousecode;
					}
					else if (lighttype == 303)
					{
						// Selector Switch
						dtype = pTypeGeneralSwitch;
						subtype = sSwitchTypeSelector;
						std::string id = request::findValue(&req, "id");
						sunitcode = request::findValue(&req, "unitcode");
						if ((id.empty()) || (sunitcode.empty()))
							return;
						devid = id;
					}
					else if (lighttype == 304)
					{
						// Itho CVE RFT
						dtype = pTypeFan;
						subtype = sTypeItho;
						std::string id = request::findValue(&req, "id");
						if (id.empty())
							return;
						devid = id;
						sunitcode = "0";
					}
					else if (lighttype == 305)
					{
						// Lucci Air/DC
						dtype = pTypeFan;
						subtype = sTypeLucciAir;
						std::string id = request::findValue(&req, "id");
						if (id.empty())
							return;
						devid = id;
						sunitcode = "0";
					}
					else if (lighttype == 306)
					{
						// Lucci Air DC
						dtype = pTypeFan;
						subtype = sTypeLucciAirDC;
						std::string id = request::findValue(&req, "id");
						if (id.empty())
							return;
						devid = id;
						sunitcode = "0";
					}
					else if (lighttype == 307)
					{
						// Westinghouse
						dtype = pTypeFan;
						subtype = sTypeWestinghouse;
						std::string id = request::findValue(&req, "id");
						if (id.empty())
							return;
						devid = id;
						sunitcode = "0";
					}
					else if (lighttype == 308)
					{
						// Casafan
						dtype = pTypeFan;
						subtype = sTypeCasafan;
						std::string id = request::findValue(&req, "id");
						if (id.empty())
							return;
						devid = id;
						sunitcode = "0";
					}
					else if (lighttype == 309)
					{
						// FT1211R
						dtype = pTypeFan;
						subtype = sTypeFT1211R;
						std::string id = request::findValue(&req, "id");
						if (id.empty())
							return;
						devid = id;
						sunitcode = "0";
					}
					else if (lighttype == 310)
					{
						// Falmec
						dtype = pTypeFan;
						subtype = sTypeFalmec;
						std::string id = request::findValue(&req, "id");
						if (id.empty())
							return;
						devid = id;
						sunitcode = "0";
					}
					else if (lighttype == 311)
					{
						// Lucci Air DC II
						dtype = pTypeFan;
						subtype = sTypeLucciAirDCII;
						std::string id = request::findValue(&req, "id");
						if (id.empty())
							return;
						devid = id;
						sunitcode = "0";
					}
					else if (lighttype == 312)
					{
						// Itho ECO
						dtype = pTypeFan;
						subtype = sTypeIthoECO;
						std::string id = request::findValue(&req, "id");
						if (id.empty())
							return;
						devid = id;
						sunitcode = "0";
					}
					else if (lighttype == 313)
					{
						// Novy
						dtype = pTypeFan;
						subtype = sTypeNovy;
						std::string id = request::findValue(&req, "id");
						if (id.empty())
							return;
						devid = id;
						sunitcode = "0";
					}
					else if (lighttype == 400)
					{
						// Openwebnet Bus Blinds
						dtype = pTypeGeneralSwitch;
						subtype = sSwitchTypeAC;
						devid = request::findValue(&req, "id");
						sunitcode = request::findValue(&req, "unitcode");
						if ((devid.empty()) || (sunitcode.empty()))
							return;
					}
					else if (lighttype == 401)
					{
						// Openwebnet Bus Lights
						dtype = pTypeGeneralSwitch;
						subtype = sSwitchTypeAC;
						devid = request::findValue(&req, "id");
						sunitcode = request::findValue(&req, "unitcode");
						if ((devid.empty()) || (sunitcode.empty()))
							return;
					}
					else if (lighttype == 402)
					{
						// Openwebnet Bus Auxiliary
						dtype = pTypeGeneralSwitch;
						subtype = sSwitchTypeAC;
						devid = request::findValue(&req, "id");
						sunitcode = request::findValue(&req, "unitcode");
						if ((devid.empty()) || (sunitcode.empty()))
							return;
					}
					else if (lighttype == 403)
					{
						// Openwebnet Zigbee Blinds
						dtype = pTypeGeneralSwitch;
						subtype = sSwitchBlindsT2;
						devid = request::findValue(&req, "id");
						sunitcode = request::findValue(&req, "unitcode");
						if ((devid.empty()) || (sunitcode.empty()))
							return;
					}
					else if (lighttype == 404)
					{
						// Light Openwebnet Zigbee
						dtype = pTypeGeneralSwitch;
						subtype = sSwitchLightT2;
						devid = request::findValue(&req, "id");
						sunitcode = request::findValue(&req, "unitcode");
						if ((devid.empty()) || (sunitcode.empty()))
							return;
					}
					else if ((lighttype == 405) || (lighttype == 406))
					{
						// Openwebnet Dry Contact / IR Detection
						dtype = pTypeGeneralSwitch;
						subtype = sSwitchContactT1;
						devid = request::findValue(&req, "id");
						sunitcode = request::findValue(&req, "unitcode");
						if ((devid.empty()) || (sunitcode.empty()))
							return;
					}
					else if (lighttype == 407)
					{
						// Openwebnet Bus Custom
						dtype = pTypeGeneralSwitch;
						subtype = sSwitchTypeAC;
						devid = request::findValue(&req, "id");
						sunitcode = request::findValue(&req, "unitcode");
						std::string StrParam1 = request::findValue(&req, "StrParam1");
						if ((devid.empty()) || (sunitcode.empty()) || (StrParam1.empty()))
						{
							root["message"] = "Some field empty or not valid.";
							return;
						}
					}
				}
				// ----------- If needed convert to GeneralSwitch type (for o.a. RFlink) -----------
				CDomoticzHardwareBase *pBaseHardware = reinterpret_cast<CDomoticzHardwareBase *>(m_mainworker.GetHardware(atoi(hwdid.c_str())));
				if (pBaseHardware != nullptr)
				{
					if ((pBaseHardware->HwdType == HTYPE_RFLINKUSB) || (pBaseHardware->HwdType == HTYPE_RFLINKTCP))
					{
						ConvertToGeneralSwitchType(devid, dtype, subtype);
					}
				}
				// -----------------------------------------------

				root["status"] = "OK";
				root["message"] = "OK";
				root["title"] = "TestSwitch";
				std::vector<std::string> sd;

				sd.push_back(hwdid);
				sd.push_back(devid);
				sd.push_back(sunitcode);
				sprintf(szTmp, "%d", dtype);
				sd.push_back(szTmp);
				sprintf(szTmp, "%d", subtype);
				sd.push_back(szTmp);
				sprintf(szTmp, "%d", switchtype);
				sd.push_back(szTmp);
				sd.push_back(""); // AddjValue2
				sd.push_back(""); // nValue
				sd.push_back(""); // sValue
				sd.push_back(""); // Name
				sd.push_back(""); // Options

				std::string switchcmd = "On";
				int level = 0;
				if (lighttype == 70)
				{
					// Special EnOcean case, if it is a dimmer, set a dim value
					if (switchtype == STYPE_Dimmer)
						level = 5;
				}
				std::string szSwitchUser = Username + " (IP: " + session.remote_host + ")";
				m_mainworker.SwitchLightInt(sd, switchcmd, level, NoColor, true, Username);
			}
			else if (cparam == "addswitch")
			{
				if (session.rights < 2)
				{
					session.reply_status = reply::forbidden;
					return; // Only admin user allowed
				}

				std::string hwdid = request::findValue(&req, "hwdid");
				std::string name = HTMLSanitizer::Sanitize(request::findValue(&req, "name"));
				std::string sswitchtype = request::findValue(&req, "switchtype");
				std::string slighttype = request::findValue(&req, "lighttype");
				std::string maindeviceidx = request::findValue(&req, "maindeviceidx");
				std::string deviceoptions;

				if ((hwdid.empty()) || (sswitchtype.empty()) || (slighttype.empty()) || (name.empty()))
					return;
				_eSwitchType switchtype = (_eSwitchType)atoi(sswitchtype.c_str());
				int lighttype = atoi(slighttype.c_str());
				int dtype = 0;
				int subtype = 0;
				std::string sunitcode;
				std::string devid;
				std::string StrParam1;

#ifdef ENABLE_PYTHON
				// check if HW is plugin
				{
					result = m_sql.safe_query("SELECT Type FROM Hardware WHERE (ID == '%q')", hwdid.c_str());
					if (!result.empty())
					{
						std::vector<std::string> sd = result[0];
						_eHardwareTypes Type = (_eHardwareTypes)atoi(sd[0].c_str());
						if (Type == HTYPE_PythonPlugin)
						{
							// Not allowed to add device to plugin HW (plugin framework does not use key column "ID" but instead uses column "unit" as key)
							_log.Log(LOG_ERROR, "CWebServer::HandleCommand addswitch: Not allowed to add device owned by plugin %u!", atoi(hwdid.c_str()));
							root["message"] = "Not allowed to add switch to plugin HW!";
							return;
						}
					}
				}
#endif

				if (lighttype == 70)
				{
					// EnOcean (Lighting2 with Base_ID offset)
					dtype = pTypeLighting2;
					subtype = sTypeAC;
					sunitcode = request::findValue(&req, "unitcode");
					std::string sgroupcode = request::findValue(&req, "groupcode");
					int iUnitTest = atoi(sunitcode.c_str()); // gives us 128 devices we can control, should be enough!
					if ((sunitcode.empty()) || (sgroupcode.empty()) || ((iUnitTest < 1) || (iUnitTest > 128)))
						return;
					sunitcode = sgroupcode; // Button A/B
					CDomoticzHardwareBase *pBaseHardware = reinterpret_cast<CDomoticzHardwareBase *>(m_mainworker.GetHardware(atoi(hwdid.c_str())));
					if (pBaseHardware == nullptr)
						return;
					if ((pBaseHardware->HwdType != HTYPE_EnOceanESP2) && (pBaseHardware->HwdType != HTYPE_EnOceanESP3) && (pBaseHardware->HwdType != HTYPE_USBtinGateway))
						return;
					unsigned long rID = 0;
					if (pBaseHardware->HwdType == HTYPE_EnOceanESP2)
					{
						CEnOceanESP2 *pEnoceanHardware = reinterpret_cast<CEnOceanESP2 *>(pBaseHardware);
						if (pEnoceanHardware->m_id_base == 0)
						{
							root["message"] = "BaseID not found, is the hardware running?";
							return;
						}
						rID = pEnoceanHardware->m_id_base + iUnitTest;
					}
					else if (pBaseHardware->HwdType == HTYPE_EnOceanESP3)
					{
						CEnOceanESP3 *pEnoceanHardware = reinterpret_cast<CEnOceanESP3 *>(pBaseHardware);
						if (pEnoceanHardware->m_id_base == 0)
						{
							root["message"] = "BaseID not found, is the hardware running?";
							return;
						}
						rID = pEnoceanHardware->m_id_base + iUnitTest;
					}
					else if (pBaseHardware->HwdType == HTYPE_USBtinGateway)
					{
						USBtin *pUSBtinHardware = reinterpret_cast<USBtin *>(pBaseHardware);
						rID = pUSBtinHardware->switch_id_base;
						std::stringstream ssunitcode;
						ssunitcode << iUnitTest;
						sunitcode = ssunitcode.str();
					}
					// convert to hex, and we have our ID
					std::stringstream s_strid;
					s_strid << std::hex << std::uppercase << rID;
					devid = s_strid.str();
				}
				else if (lighttype == 68)
				{
#ifdef WITH_GPIO
					dtype = pTypeLighting1;
					subtype = sTypeIMPULS;
					devid = "0";
					sunitcode = request::findValue(&req, "unitcode"); // Unit code = GPIO number

					if (sunitcode.empty())
					{
						return;
					}
					CGpio *pGpio = reinterpret_cast<CGpio *>(m_mainworker.GetHardware(atoi(hwdid.c_str())));
					if (pGpio == nullptr)
					{
						return;
					}
					if (pGpio->HwdType != HTYPE_RaspberryGPIO)
					{
						return;
					}
					CGpioPin *pPin = CGpio::GetPPinById(atoi(sunitcode.c_str()));
					if (pPin == nullptr)
					{
						return;
					}
#else
					return;
#endif
				}
				else if (lighttype == 69)
				{
#ifdef WITH_GPIO
					dtype = pTypeLighting2;
					subtype = sTypeAC;
					devid = "0";
					sunitcode = request::findValue(&req, "unitcode"); // sysfs-gpio number
					int unitcode = atoi(sunitcode.c_str());
					std::string sswitchtype = request::findValue(&req, "switchtype");
					_eSwitchType switchtype = (_eSwitchType)atoi(sswitchtype.c_str());
					std::string id = request::findValue(&req, "id");
					CSysfsGpio::RequestDbUpdate(unitcode);

					if ((id.empty()) || (sunitcode.empty()))
					{
						return;
					}
					devid = id;

					CSysfsGpio *pSysfsGpio = reinterpret_cast<CSysfsGpio *>(m_mainworker.GetHardware(atoi(hwdid.c_str())));

					if ((pSysfsGpio == nullptr) || (pSysfsGpio->HwdType != HTYPE_SysfsGpio))
					{
						return;
					}
#else
					return;
#endif
				}
				else if (lighttype < 20)
				{
					dtype = pTypeLighting1;
					subtype = lighttype;
					std::string shousecode = request::findValue(&req, "housecode");
					sunitcode = request::findValue(&req, "unitcode");
					if ((shousecode.empty()) || (sunitcode.empty()))
						return;
					devid = shousecode;
				}
				else if (lighttype < 30)
				{
					dtype = pTypeLighting2;
					subtype = lighttype - 20;
					std::string id = request::findValue(&req, "id");
					sunitcode = request::findValue(&req, "unitcode");
					if ((id.empty()) || (sunitcode.empty()))
						return;
					devid = id;
				}
				else if (lighttype < 70)
				{
					dtype = pTypeLighting5;
					subtype = lighttype - 50;
					if (lighttype == 59)
						subtype = sTypeIT;
					std::string id = request::findValue(&req, "id");
					sunitcode = request::findValue(&req, "unitcode");
					if ((id.empty()) || (sunitcode.empty()))
						return;
					if ((subtype != sTypeEMW100) && (subtype != sTypeLivolo) && (subtype != sTypeLivolo1to10) && (subtype != sTypeRGB432W) && (subtype != sTypeLightwaveRF) &&
					    (subtype != sTypeIT))
						devid = "00" + id;
					else
						devid = id;
				}
				else if (lighttype == 101)
				{
					// Curtain Harrison
					dtype = pTypeCurtain;
					subtype = sTypeHarrison;
					std::string shousecode = request::findValue(&req, "housecode");
					sunitcode = request::findValue(&req, "unitcode");
					if ((shousecode.empty()) || (sunitcode.empty()))
						return;
					devid = shousecode;
				}
				else if (lighttype == 102)
				{
					// RFY
					dtype = pTypeRFY;
					subtype = sTypeRFY;
					std::string id = request::findValue(&req, "id");
					sunitcode = request::findValue(&req, "unitcode");
					if ((id.empty()) || (sunitcode.empty()))
						return;
					devid = id;
				}
				else if (lighttype == 103)
				{
					// Meiantech
					dtype = pTypeSecurity1;
					subtype = sTypeMeiantech;
					std::string id = request::findValue(&req, "id");
					if ((id.empty()))
						return;
					devid = id;
					sunitcode = "0";
				}
				else if (lighttype == 104)
				{
					// HE105
					dtype = pTypeThermostat2;
					subtype = sTypeHE105;
					sunitcode = request::findValue(&req, "unitcode");
					if (sunitcode.empty())
						return;
					// convert to hex, and we have our Unit Code
					std::stringstream s_strid;
					s_strid << std::hex << std::uppercase << sunitcode;
					int iUnitCode;
					s_strid >> iUnitCode;
					sprintf(szTmp, "%d", iUnitCode);
					sunitcode = szTmp;
					devid = "1";
				}
				else if (lighttype == 105)
				{
					// ASA
					dtype = pTypeRFY;
					subtype = sTypeASA;
					std::string id = request::findValue(&req, "id");
					sunitcode = request::findValue(&req, "unitcode");
					if ((id.empty()) || (sunitcode.empty()))
						return;
					devid = id;
				}
				else if (lighttype == 106)
				{
					// Blyss
					dtype = pTypeLighting6;
					subtype = sTypeBlyss;
					std::string sgroupcode = request::findValue(&req, "groupcode");
					sunitcode = request::findValue(&req, "unitcode");
					std::string id = request::findValue(&req, "id");
					if ((sgroupcode.empty()) || (sunitcode.empty()) || (id.empty()))
						return;
					devid = id + sgroupcode;
				}
				else if (lighttype == 107)
				{
					// RFY2
					dtype = pTypeRFY;
					subtype = sTypeRFY2;
					std::string id = request::findValue(&req, "id");
					sunitcode = request::findValue(&req, "unitcode");
					if ((id.empty()) || (sunitcode.empty()))
						return;
					devid = id;
				}
				else
				{
					if (lighttype == 100)
					{
						// Chime/ByronSX
						dtype = pTypeChime;
						subtype = sTypeByronSX;
						std::string id = request::findValue(&req, "id");
						sunitcode = request::findValue(&req, "unitcode");
						if ((id.empty()) || (sunitcode.empty()))
							return;
						int iUnitCode = atoi(sunitcode.c_str()) - 1;
						switch (iUnitCode)
						{
							case 0:
								iUnitCode = chime_sound0;
								break;
							case 1:
								iUnitCode = chime_sound1;
								break;
							case 2:
								iUnitCode = chime_sound2;
								break;
							case 3:
								iUnitCode = chime_sound3;
								break;
							case 4:
								iUnitCode = chime_sound4;
								break;
							case 5:
								iUnitCode = chime_sound5;
								break;
							case 6:
								iUnitCode = chime_sound6;
								break;
							case 7:
								iUnitCode = chime_sound7;
								break;
						}
						sprintf(szTmp, "%d", iUnitCode);
						sunitcode = szTmp;
						devid = id;
					}
					else if ((lighttype >= 200) && (lighttype < 300))
					{
						dtype = pTypeBlinds;
						subtype = lighttype - 200;
						std::string id = request::findValue(&req, "id");
						sunitcode = request::findValue(&req, "unitcode");
						if ((id.empty()) || (sunitcode.empty()))
							return;
						int iUnitCode = atoi(sunitcode.c_str());
						sprintf(szTmp, "%d", iUnitCode);
						sunitcode = szTmp;
						devid = id;
					}
					else if (lighttype == 301)
					{
						// Smartwares Radiator
						std::string id = request::findValue(&req, "id");
						sunitcode = request::findValue(&req, "unitcode");
						if ((id.empty()) || (sunitcode.empty()))
							return;
						devid = id;

						// For this device, we will also need to add a Radiator type, do that first
						dtype = pTypeRadiator1;
						subtype = sTypeSmartwares;

						// check if switch is unique
						result = m_sql.safe_query("SELECT Name FROM DeviceStatus WHERE (HardwareID=='%q' AND DeviceID=='%q' AND Unit=='%q' AND Type==%d AND SubType==%d)",
									  hwdid.c_str(), devid.c_str(), sunitcode.c_str(), dtype, subtype);
						if (!result.empty())
						{
							root["message"] = "Switch already exists!";
							return;
						}
						bool bActEnabledState = m_sql.m_bAcceptNewHardware;
						m_sql.m_bAcceptNewHardware = true;
						std::string devname;
						m_sql.UpdateValue(atoi(hwdid.c_str()), devid.c_str(), atoi(sunitcode.c_str()), dtype, subtype, 0, -1, 0, "20.5", devname);
						m_sql.m_bAcceptNewHardware = bActEnabledState;

						// set name and switchtype
						result = m_sql.safe_query("SELECT ID FROM DeviceStatus WHERE (HardwareID=='%q' AND DeviceID=='%q' AND Unit=='%q' AND Type==%d AND SubType==%d)",
									  hwdid.c_str(), devid.c_str(), sunitcode.c_str(), dtype, subtype);
						if (result.empty())
						{
							root["message"] = "Error finding switch in Database!?!?";
							return;
						}
						std::string ID = result[0][0];

						m_sql.safe_query("UPDATE DeviceStatus SET Used=1, Name='%q', SwitchType=%d WHERE (ID == '%q')", name.c_str(), switchtype, ID.c_str());

						// Now continue to insert the switch
						dtype = pTypeRadiator1;
						subtype = sTypeSmartwaresSwitchRadiator;
					}
					else if (lighttype == 302)
					{
						// Home Confort
						dtype = pTypeHomeConfort;
						subtype = sTypeHomeConfortTEL010;
						std::string id = request::findValue(&req, "id");

						std::string shousecode = request::findValue(&req, "housecode");
						sunitcode = request::findValue(&req, "unitcode");
						if ((id.empty()) || (shousecode.empty()) || (sunitcode.empty()))
							return;

						int iUnitCode = atoi(sunitcode.c_str());
						sprintf(szTmp, "%d", iUnitCode);
						sunitcode = szTmp;
						sprintf(szTmp, "%02X", atoi(shousecode.c_str()));
						shousecode = szTmp;
						devid = id + shousecode;
					}
					else if (lighttype == 303)
					{
						// Selector Switch
						dtype = pTypeGeneralSwitch;
						subtype = sSwitchTypeSelector;
						std::string id = request::findValue(&req, "id");
						sunitcode = request::findValue(&req, "unitcode");
						if ((id.empty()) || (sunitcode.empty()))
							return;
						devid = "0" + id;
						switchtype = STYPE_Selector;
						if (!deviceoptions.empty())
						{
							deviceoptions.append(";");
						}
						deviceoptions.append("SelectorStyle:0;LevelNames:Off|Level1|Level2|Level3");
					}
					else if (lighttype == 304)
					{
						// Itho CVE RFT
						dtype = pTypeFan;
						subtype = sTypeItho;
						std::string id = request::findValue(&req, "id");
						if (id.empty())
							return;
						devid = id;
						sunitcode = "0";
					}
					else if (lighttype == 305)
					{
						// Lucci Air
						dtype = pTypeFan;
						subtype = sTypeLucciAir;
						std::string id = request::findValue(&req, "id");
						if (id.empty())
							return;
						devid = id;
						sunitcode = "0";
					}
					else if (lighttype == 306)
					{
						// Lucci Air DC
						dtype = pTypeFan;
						subtype = sTypeLucciAirDC;
						std::string id = request::findValue(&req, "id");
						if (id.empty())
							return;
						devid = id;
						sunitcode = "0";
					}
					else if (lighttype == 307)
					{
						// Westinghouse
						dtype = pTypeFan;
						subtype = sTypeWestinghouse;
						std::string id = request::findValue(&req, "id");
						if (id.empty())
							return;
						devid = id;
						sunitcode = "0";
					}
					else if (lighttype == 308)
					{
						// Casafan
						dtype = pTypeFan;
						subtype = sTypeCasafan;
						std::string id = request::findValue(&req, "id");
						if (id.empty())
							return;
						devid = id;
						sunitcode = "0";
					}
					else if (lighttype == 309)
					{
						// FT1211R
						dtype = pTypeFan;
						subtype = sTypeFT1211R;
						std::string id = request::findValue(&req, "id");
						if (id.empty())
							return;
						devid = id;
						sunitcode = "0";
					}
					else if (lighttype == 310)
					{
						// Falmec
						dtype = pTypeFan;
						subtype = sTypeFalmec;
						std::string id = request::findValue(&req, "id");
						if (id.empty())
							return;
						devid = id;
						sunitcode = "0";
					}
					else if (lighttype == 311)
					{
						// Lucci Air DC II
						dtype = pTypeFan;
						subtype = sTypeLucciAirDCII;
						std::string id = request::findValue(&req, "id");
						if (id.empty())
							return;
						devid = id;
						sunitcode = "0";
					}
					else if (lighttype == 312)
					{
						// Itho ECO
						dtype = pTypeFan;
						subtype = sTypeIthoECO;
						std::string id = request::findValue(&req, "id");
						if (id.empty())
							return;
						devid = id;
						sunitcode = "0";
					}
					else if (lighttype == 313)
					{
						// Novy
						dtype = pTypeFan;
						subtype = sTypeNovy;
						std::string id = request::findValue(&req, "id");
						if (id.empty())
							return;
						devid = id;
						sunitcode = "0";
					}
					else if (lighttype == 400)
					{
						// Openwebnet Bus Blinds
						dtype = pTypeGeneralSwitch;
						subtype = sSwitchTypeAC;
						devid = request::findValue(&req, "id");
						sunitcode = request::findValue(&req, "unitcode");
						if ((devid.empty()) || (sunitcode.empty()))
							return;
					}
					else if (lighttype == 401)
					{
						// Openwebnet Bus Lights
						dtype = pTypeGeneralSwitch;
						subtype = sSwitchTypeAC;
						devid = request::findValue(&req, "id");
						sunitcode = request::findValue(&req, "unitcode");
						if ((devid.empty()) || (sunitcode.empty()))
							return;
					}
					else if (lighttype == 402)
					{
						// Openwebnet Bus Auxiliary
						dtype = pTypeGeneralSwitch;
						subtype = sSwitchTypeAC;
						devid = request::findValue(&req, "id");
						sunitcode = request::findValue(&req, "unitcode");
						if ((devid.empty()) || (sunitcode.empty()))
							return;
					}
					else if (lighttype == 403)
					{
						// Openwebnet Zigbee Blinds
						dtype = pTypeGeneralSwitch;
						subtype = sSwitchBlindsT2;
						devid = request::findValue(&req, "id");
						sunitcode = request::findValue(&req, "unitcode");
						if ((devid.empty()) || (sunitcode.empty()))
							return;
					}
					else if (lighttype == 404)
					{
						// Openwebnet Zigbee Lights
						dtype = pTypeGeneralSwitch;
						subtype = sSwitchLightT2;
						devid = request::findValue(&req, "id");
						sunitcode = request::findValue(&req, "unitcode");
						if ((devid.empty()) || (sunitcode.empty()))
							return;
					}
					else if ((lighttype == 405) || (lighttype == 406))
					{
						// Openwebnet Dry Contact / IR Detection
						dtype = pTypeGeneralSwitch;
						subtype = sSwitchContactT1;
						devid = request::findValue(&req, "id");
						sunitcode = request::findValue(&req, "unitcode");
						if ((devid.empty()) || (sunitcode.empty()))
							return;
					}
					else if (lighttype == 407)
					{
						// Openwebnet Bus Custom
						dtype = pTypeGeneralSwitch;
						subtype = sSwitchTypeAC;
						devid = request::findValue(&req, "id");
						sunitcode = request::findValue(&req, "unitcode");
						StrParam1 = request::findValue(&req, "StrParam1");
						_log.Log(LOG_STATUS, "COpenWebNetTCP: custom command: '%s'", StrParam1.c_str());
						if ((devid.empty()) || (sunitcode.empty()) || (StrParam1.empty()))
						{
							root["message"] = "Some field empty or not valid.";
							return;
						}
					}
				}

				// check if switch is unique
				result = m_sql.safe_query("SELECT Name FROM DeviceStatus WHERE (HardwareID=='%q' AND DeviceID=='%q' AND Unit=='%q' AND Type==%d AND SubType==%d)", hwdid.c_str(),
							  devid.c_str(), sunitcode.c_str(), dtype, subtype);
				if (!result.empty())
				{
					root["message"] = "Switch already exists!";
					return;
				}

				// ----------- If needed convert to GeneralSwitch type (for o.a. RFlink) -----------
				CDomoticzHardwareBase *pBaseHardware = m_mainworker.GetHardware(atoi(hwdid.c_str()));
				if (pBaseHardware != nullptr)
				{
					if ((pBaseHardware->HwdType == HTYPE_RFLINKUSB) || (pBaseHardware->HwdType == HTYPE_RFLINKTCP))
					{
						ConvertToGeneralSwitchType(devid, dtype, subtype);
					}
				}
				// -----------------------------------------------

				bool bActEnabledState = m_sql.m_bAcceptNewHardware;
				m_sql.m_bAcceptNewHardware = true;
				std::string devname;
				m_sql.UpdateValue(atoi(hwdid.c_str()), devid.c_str(), atoi(sunitcode.c_str()), dtype, subtype, 0, -1, 0, devname);
				m_sql.m_bAcceptNewHardware = bActEnabledState;

				// set name and switchtype
				result = m_sql.safe_query("SELECT ID FROM DeviceStatus WHERE (HardwareID=='%q' AND DeviceID=='%q' AND Unit=='%q' AND Type==%d AND SubType==%d)", hwdid.c_str(),
							  devid.c_str(), sunitcode.c_str(), dtype, subtype);
				if (result.empty())
				{
					root["message"] = "Error finding switch in Database!?!?";
					return;
				}
				std::string ID = result[0][0];

				m_sql.safe_query("UPDATE DeviceStatus SET Used=1, Name='%q', SwitchType=%d WHERE (ID == '%q')", name.c_str(), switchtype, ID.c_str());

				if (lighttype == 407)
				{
					// Openwebnet Bus Custom
					m_sql.safe_query("UPDATE DeviceStatus SET StrParam1='%s' WHERE (ID == '%q')", StrParam1.c_str(), ID.c_str());
				}

				m_mainworker.m_eventsystem.GetCurrentStates();

				// Set device options
				m_sql.SetDeviceOptions(atoi(ID.c_str()), m_sql.BuildDeviceOptions(deviceoptions, false));

				if (!maindeviceidx.empty())
				{
					if (maindeviceidx != ID)
					{
						// this is a sub device for another light/switch
						// first check if it is not already a sub device
						result = m_sql.safe_query("SELECT ID FROM LightSubDevices WHERE (DeviceRowID=='%q') AND (ParentID =='%q')", ID.c_str(), maindeviceidx.c_str());
						if (result.empty())
						{
							// no it is not, add it
							result = m_sql.safe_query("INSERT INTO LightSubDevices (DeviceRowID, ParentID) VALUES ('%q','%q')", ID.c_str(), maindeviceidx.c_str());
						}
					}
				}

				root["status"] = "OK";
				root["title"] = "AddSwitch";
			}
			else if (cparam == "getnotificationtypes")
			{
				if (session.rights < 2)
				{
					session.reply_status = reply::forbidden;
					return; // Only admin user allowed
				}

				std::string idx = request::findValue(&req, "idx");
				if (idx.empty())
					return;
				// First get Device Type/SubType
				result = m_sql.safe_query("SELECT Type, SubType, SwitchType FROM DeviceStatus WHERE (ID == '%q')", idx.c_str());
				if (result.empty())
					return;

				root["status"] = "OK";
				root["title"] = "GetNotificationTypes";
				unsigned char dType = atoi(result[0][0].c_str());
				unsigned char dSubType = atoi(result[0][1].c_str());
				unsigned char switchtype = atoi(result[0][2].c_str());

				int ii = 0;
				if ((dType == pTypeLighting1) || (dType == pTypeLighting2) || (dType == pTypeLighting3) || (dType == pTypeLighting4) || (dType == pTypeLighting5) ||
				    (dType == pTypeLighting6) || (dType == pTypeColorSwitch) || (dType == pTypeSecurity1) || (dType == pTypeSecurity2) || (dType == pTypeEvohome) ||
				    (dType == pTypeEvohomeRelay) || (dType == pTypeCurtain) || (dType == pTypeBlinds) || (dType == pTypeRFY) || (dType == pTypeChime) || (dType == pTypeThermostat2) ||
				    (dType == pTypeThermostat3) || (dType == pTypeThermostat4) || (dType == pTypeRemote) || (dType == pTypeGeneralSwitch) || (dType == pTypeHomeConfort) ||
				    (dType == pTypeFS20) || ((dType == pTypeRadiator1) && (dSubType == sTypeSmartwaresSwitchRadiator)))
				{
					if (switchtype != STYPE_PushOff)
					{
						root["result"][ii]["val"] = NTYPE_SWITCH_ON;
						root["result"][ii]["text"] = Notification_Type_Desc(NTYPE_SWITCH_ON, 0);
						root["result"][ii]["ptag"] = Notification_Type_Desc(NTYPE_SWITCH_ON, 1);
						ii++;
					}
					if (switchtype != STYPE_PushOn)
					{
						root["result"][ii]["val"] = NTYPE_SWITCH_OFF;
						root["result"][ii]["text"] = Notification_Type_Desc(NTYPE_SWITCH_OFF, 0);
						root["result"][ii]["ptag"] = Notification_Type_Desc(NTYPE_SWITCH_OFF, 1);
						ii++;
					}
					if (switchtype == STYPE_Media)
					{
						std::string idx = request::findValue(&req, "idx");

						result = m_sql.safe_query("SELECT HardwareID FROM DeviceStatus WHERE (ID=='%q')", idx.c_str());
						if (!result.empty())
						{
							std::string hdwid = result[0][0];
							CDomoticzHardwareBase *pBaseHardware = reinterpret_cast<CDomoticzHardwareBase *>(m_mainworker.GetHardware(atoi(hdwid.c_str())));
							if (pBaseHardware != nullptr)
							{
								_eHardwareTypes type = pBaseHardware->HwdType;
								root["result"][ii]["val"] = NTYPE_PAUSED;
								root["result"][ii]["text"] = Notification_Type_Desc(NTYPE_PAUSED, 0);
								root["result"][ii]["ptag"] = Notification_Type_Desc(NTYPE_PAUSED, 1);
								ii++;
								if (type == HTYPE_Kodi)
								{
									root["result"][ii]["val"] = NTYPE_AUDIO;
									root["result"][ii]["text"] = Notification_Type_Desc(NTYPE_AUDIO, 0);
									root["result"][ii]["ptag"] = Notification_Type_Desc(NTYPE_AUDIO, 1);
									ii++;
									root["result"][ii]["val"] = NTYPE_VIDEO;
									root["result"][ii]["text"] = Notification_Type_Desc(NTYPE_VIDEO, 0);
									root["result"][ii]["ptag"] = Notification_Type_Desc(NTYPE_VIDEO, 1);
									ii++;
									root["result"][ii]["val"] = NTYPE_PHOTO;
									root["result"][ii]["text"] = Notification_Type_Desc(NTYPE_PHOTO, 0);
									root["result"][ii]["ptag"] = Notification_Type_Desc(NTYPE_PHOTO, 1);
									ii++;
								}
								if (type == HTYPE_LogitechMediaServer)
								{
									root["result"][ii]["val"] = NTYPE_PLAYING;
									root["result"][ii]["text"] = Notification_Type_Desc(NTYPE_PLAYING, 0);
									root["result"][ii]["ptag"] = Notification_Type_Desc(NTYPE_PLAYING, 1);
									ii++;
									root["result"][ii]["val"] = NTYPE_STOPPED;
									root["result"][ii]["text"] = Notification_Type_Desc(NTYPE_STOPPED, 0);
									root["result"][ii]["ptag"] = Notification_Type_Desc(NTYPE_STOPPED, 1);
									ii++;
								}
								if (type == HTYPE_HEOS)
								{
									root["result"][ii]["val"] = NTYPE_PLAYING;
									root["result"][ii]["text"] = Notification_Type_Desc(NTYPE_PLAYING, 0);
									root["result"][ii]["ptag"] = Notification_Type_Desc(NTYPE_PLAYING, 1);
									ii++;
									root["result"][ii]["val"] = NTYPE_STOPPED;
									root["result"][ii]["text"] = Notification_Type_Desc(NTYPE_STOPPED, 0);
									root["result"][ii]["ptag"] = Notification_Type_Desc(NTYPE_STOPPED, 1);
									ii++;
								}
							}
						}
					}
				}
				if (((dType == pTypeTEMP) || (dType == pTypeTEMP_HUM) || (dType == pTypeTEMP_HUM_BARO) || (dType == pTypeTEMP_BARO) || (dType == pTypeEvohomeZone) ||
				     (dType == pTypeEvohomeWater) || (dType == pTypeThermostat1) || (dType == pTypeRego6XXTemp) || ((dType == pTypeRFXSensor) && (dSubType == sTypeRFXSensorTemp))) ||
				    ((dType == pTypeUV) && (dSubType == sTypeUV3)) || ((dType == pTypeWIND) && (dSubType == sTypeWIND4)) || ((dType == pTypeWIND) && (dSubType == sTypeWINDNoTemp)) ||
				    ((dType == pTypeGeneral) && (dSubType == sTypeSystemTemp)))
				{
					root["result"][ii]["val"] = NTYPE_TEMPERATURE;
					root["result"][ii]["text"] = Notification_Type_Desc(NTYPE_TEMPERATURE, 0);
					root["result"][ii]["ptag"] = Notification_Type_Desc(NTYPE_TEMPERATURE, 1);
					ii++;
				}
				if ((dType == pTypeHUM) || (dType == pTypeTEMP_HUM) || (dType == pTypeTEMP_HUM_BARO))
				{
					root["result"][ii]["val"] = NTYPE_HUMIDITY;
					root["result"][ii]["text"] = Notification_Type_Desc(NTYPE_HUMIDITY, 0);
					root["result"][ii]["ptag"] = Notification_Type_Desc(NTYPE_HUMIDITY, 1);
					ii++;
				}
				if ((dType == pTypeTEMP_HUM) || (dType == pTypeTEMP_HUM_BARO))
				{
					root["result"][ii]["val"] = NTYPE_DEWPOINT;
					root["result"][ii]["text"] = Notification_Type_Desc(NTYPE_DEWPOINT, 0);
					root["result"][ii]["ptag"] = Notification_Type_Desc(NTYPE_DEWPOINT, 1);
					ii++;
				}
				if (dType == pTypeRAIN)
				{
					root["result"][ii]["val"] = NTYPE_RAIN;
					root["result"][ii]["text"] = Notification_Type_Desc(NTYPE_RAIN, 0);
					root["result"][ii]["ptag"] = Notification_Type_Desc(NTYPE_RAIN, 1);
					ii++;
				}
				if (dType == pTypeWIND)
				{
					root["result"][ii]["val"] = NTYPE_WIND;
					root["result"][ii]["text"] = Notification_Type_Desc(NTYPE_WIND, 0);
					root["result"][ii]["ptag"] = Notification_Type_Desc(NTYPE_WIND, 1);
					ii++;
				}
				if (dType == pTypeUV)
				{
					root["result"][ii]["val"] = NTYPE_UV;
					root["result"][ii]["text"] = Notification_Type_Desc(NTYPE_UV, 0);
					root["result"][ii]["ptag"] = Notification_Type_Desc(NTYPE_UV, 1);
					ii++;
				}
				if ((dType == pTypeTEMP_HUM_BARO) || (dType == pTypeBARO) || (dType == pTypeTEMP_BARO))
				{
					root["result"][ii]["val"] = NTYPE_BARO;
					root["result"][ii]["text"] = Notification_Type_Desc(NTYPE_BARO, 0);
					root["result"][ii]["ptag"] = Notification_Type_Desc(NTYPE_BARO, 1);
					ii++;
				}
				if (((dType == pTypeRFXMeter) && (dSubType == sTypeRFXMeterCount)) || ((dType == pTypeGeneral) && (dSubType == sTypeCounterIncremental)) || (dType == pTypeYouLess) ||
				    ((dType == pTypeRego6XXValue) && (dSubType == sTypeRego6XXCounter)))
				{
					if ((switchtype == MTYPE_ENERGY) || (switchtype == MTYPE_ENERGY_GENERATED))
					{
						root["result"][ii]["val"] = NTYPE_TODAYENERGY;
						root["result"][ii]["text"] = Notification_Type_Desc(NTYPE_TODAYENERGY, 0);
						root["result"][ii]["ptag"] = Notification_Type_Desc(NTYPE_TODAYENERGY, 1);
					}
					else if (switchtype == MTYPE_GAS)
					{
						root["result"][ii]["val"] = NTYPE_TODAYGAS;
						root["result"][ii]["text"] = Notification_Type_Desc(NTYPE_TODAYGAS, 0);
						root["result"][ii]["ptag"] = Notification_Type_Desc(NTYPE_TODAYGAS, 1);
					}
					else if (switchtype == MTYPE_COUNTER)
					{
						root["result"][ii]["val"] = NTYPE_TODAYCOUNTER;
						root["result"][ii]["text"] = Notification_Type_Desc(NTYPE_TODAYCOUNTER, 0);
						root["result"][ii]["ptag"] = Notification_Type_Desc(NTYPE_TODAYCOUNTER, 1);
					}
					else
					{
						// water (same as gas)
						root["result"][ii]["val"] = NTYPE_TODAYGAS;
						root["result"][ii]["text"] = Notification_Type_Desc(NTYPE_TODAYGAS, 0);
						root["result"][ii]["ptag"] = Notification_Type_Desc(NTYPE_TODAYGAS, 1);
					}
					ii++;
				}
				if (dType == pTypeYouLess)
				{
					root["result"][ii]["val"] = NTYPE_USAGE;
					root["result"][ii]["text"] = Notification_Type_Desc(NTYPE_USAGE, 0);
					root["result"][ii]["ptag"] = Notification_Type_Desc(NTYPE_USAGE, 1);
					ii++;
				}
				if (dType == pTypeAirQuality)
				{
					root["result"][ii]["val"] = NTYPE_USAGE;
					root["result"][ii]["text"] = Notification_Type_Desc(NTYPE_USAGE, 0);
					root["result"][ii]["ptag"] = Notification_Type_Desc(NTYPE_USAGE, 1);
					ii++;
				}
				else if ((dType == pTypeGeneral) && ((dSubType == sTypeSoilMoisture) || (dSubType == sTypeLeafWetness)))
				{
					root["result"][ii]["val"] = NTYPE_USAGE;
					root["result"][ii]["text"] = Notification_Type_Desc(NTYPE_USAGE, 0);
					root["result"][ii]["ptag"] = Notification_Type_Desc(NTYPE_USAGE, 1);
					ii++;
				}
				if ((dType == pTypeGeneral) && (dSubType == sTypeVisibility))
				{
					root["result"][ii]["val"] = NTYPE_USAGE;
					root["result"][ii]["text"] = Notification_Type_Desc(NTYPE_USAGE, 0);
					root["result"][ii]["ptag"] = Notification_Type_Desc(NTYPE_USAGE, 1);
					ii++;
				}
				if ((dType == pTypeGeneral) && (dSubType == sTypeDistance))
				{
					root["result"][ii]["val"] = NTYPE_USAGE;
					root["result"][ii]["text"] = Notification_Type_Desc(NTYPE_USAGE, 0);
					root["result"][ii]["ptag"] = Notification_Type_Desc(NTYPE_USAGE, 1);
					ii++;
				}
				if ((dType == pTypeGeneral) && (dSubType == sTypeSolarRadiation))
				{
					root["result"][ii]["val"] = NTYPE_USAGE;
					root["result"][ii]["text"] = Notification_Type_Desc(NTYPE_USAGE, 0);
					root["result"][ii]["ptag"] = Notification_Type_Desc(NTYPE_USAGE, 1);
					ii++;
				}
				if ((dType == pTypeGeneral) && (dSubType == sTypeVoltage))
				{
					root["result"][ii]["val"] = NTYPE_USAGE;
					root["result"][ii]["text"] = Notification_Type_Desc(NTYPE_USAGE, 0);
					root["result"][ii]["ptag"] = Notification_Type_Desc(NTYPE_USAGE, 1);
					ii++;
				}
				if ((dType == pTypeGeneral) && (dSubType == sTypeCurrent))
				{
					root["result"][ii]["val"] = NTYPE_USAGE;
					root["result"][ii]["text"] = Notification_Type_Desc(NTYPE_USAGE, 0);
					root["result"][ii]["ptag"] = Notification_Type_Desc(NTYPE_USAGE, 1);
					ii++;
				}
				if ((dType == pTypeGeneral) && (dSubType == sTypePressure))
				{
					root["result"][ii]["val"] = NTYPE_USAGE;
					root["result"][ii]["text"] = Notification_Type_Desc(NTYPE_USAGE, 0);
					root["result"][ii]["ptag"] = Notification_Type_Desc(NTYPE_USAGE, 1);
					ii++;
				}
				if ((dType == pTypeGeneral) && (dSubType == sTypeBaro))
				{
					root["result"][ii]["val"] = NTYPE_USAGE;
					root["result"][ii]["text"] = Notification_Type_Desc(NTYPE_USAGE, 0);
					root["result"][ii]["ptag"] = Notification_Type_Desc(NTYPE_USAGE, 1);
					ii++;
				}
				if (dType == pTypeLux)
				{
					root["result"][ii]["val"] = NTYPE_USAGE;
					root["result"][ii]["text"] = Notification_Type_Desc(NTYPE_USAGE, 0);
					root["result"][ii]["ptag"] = Notification_Type_Desc(NTYPE_USAGE, 1);
					ii++;
				}
				if ((dType == pTypeGeneral) && (dSubType == sTypeSoundLevel))
				{
					root["result"][ii]["val"] = NTYPE_USAGE;
					root["result"][ii]["text"] = Notification_Type_Desc(NTYPE_USAGE, 0);
					root["result"][ii]["ptag"] = Notification_Type_Desc(NTYPE_USAGE, 1);
					ii++;
				}
				if (dType == pTypeWEIGHT)
				{
					root["result"][ii]["val"] = NTYPE_USAGE;
					root["result"][ii]["text"] = Notification_Type_Desc(NTYPE_USAGE, 0);
					root["result"][ii]["ptag"] = Notification_Type_Desc(NTYPE_USAGE, 1);
					ii++;
				}
				if (dType == pTypeUsage)
				{
					root["result"][ii]["val"] = NTYPE_USAGE;
					root["result"][ii]["text"] = Notification_Type_Desc(NTYPE_USAGE, 0);
					root["result"][ii]["ptag"] = Notification_Type_Desc(NTYPE_USAGE, 1);
					ii++;
				}
				if ((dType == pTypeENERGY) || ((dType == pTypeGeneral) && (dSubType == sTypeKwh)))
				{
					root["result"][ii]["val"] = NTYPE_USAGE;
					root["result"][ii]["text"] = Notification_Type_Desc(NTYPE_USAGE, 0);
					root["result"][ii]["ptag"] = Notification_Type_Desc(NTYPE_USAGE, 1);
					ii++;
				}
				if (dType == pTypePOWER)
				{
					root["result"][ii]["val"] = NTYPE_USAGE;
					root["result"][ii]["text"] = Notification_Type_Desc(NTYPE_USAGE, 0);
					root["result"][ii]["ptag"] = Notification_Type_Desc(NTYPE_USAGE, 1);
					ii++;
				}
				if ((dType == pTypeCURRENT) && (dSubType == sTypeELEC1))
				{
					root["result"][ii]["val"] = NTYPE_AMPERE1;
					root["result"][ii]["text"] = Notification_Type_Desc(NTYPE_AMPERE1, 0);
					root["result"][ii]["ptag"] = Notification_Type_Desc(NTYPE_AMPERE1, 1);
					ii++;
					root["result"][ii]["val"] = NTYPE_AMPERE2;
					root["result"][ii]["text"] = Notification_Type_Desc(NTYPE_AMPERE2, 0);
					root["result"][ii]["ptag"] = Notification_Type_Desc(NTYPE_AMPERE2, 1);
					ii++;
					root["result"][ii]["val"] = NTYPE_AMPERE3;
					root["result"][ii]["text"] = Notification_Type_Desc(NTYPE_AMPERE3, 0);
					root["result"][ii]["ptag"] = Notification_Type_Desc(NTYPE_AMPERE3, 1);
					ii++;
				}
				if ((dType == pTypeCURRENTENERGY) && (dSubType == sTypeELEC4))
				{
					root["result"][ii]["val"] = NTYPE_AMPERE1;
					root["result"][ii]["text"] = Notification_Type_Desc(NTYPE_AMPERE1, 0);
					root["result"][ii]["ptag"] = Notification_Type_Desc(NTYPE_AMPERE1, 1);
					ii++;
					root["result"][ii]["val"] = NTYPE_AMPERE2;
					root["result"][ii]["text"] = Notification_Type_Desc(NTYPE_AMPERE2, 0);
					root["result"][ii]["ptag"] = Notification_Type_Desc(NTYPE_AMPERE2, 1);
					ii++;
					root["result"][ii]["val"] = NTYPE_AMPERE3;
					root["result"][ii]["text"] = Notification_Type_Desc(NTYPE_AMPERE3, 0);
					root["result"][ii]["ptag"] = Notification_Type_Desc(NTYPE_AMPERE3, 1);
					ii++;
				}
				if (dType == pTypeP1Power)
				{
					root["result"][ii]["val"] = NTYPE_USAGE;
					root["result"][ii]["text"] = Notification_Type_Desc(NTYPE_USAGE, 0);
					root["result"][ii]["ptag"] = Notification_Type_Desc(NTYPE_USAGE, 1);
					ii++;
					root["result"][ii]["val"] = NTYPE_TODAYENERGY;
					root["result"][ii]["text"] = Notification_Type_Desc(NTYPE_TODAYENERGY, 0);
					root["result"][ii]["ptag"] = Notification_Type_Desc(NTYPE_TODAYENERGY, 1);
					ii++;
				}
				if (dType == pTypeP1Gas)
				{
					root["result"][ii]["val"] = NTYPE_TODAYGAS;
					root["result"][ii]["text"] = Notification_Type_Desc(NTYPE_TODAYGAS, 0);
					root["result"][ii]["ptag"] = Notification_Type_Desc(NTYPE_TODAYGAS, 1);
					ii++;
				}
				if ((dType == pTypeThermostat) && (dSubType == sTypeThermSetpoint))
				{
					root["result"][ii]["val"] = NTYPE_TEMPERATURE;
					root["result"][ii]["text"] = Notification_Type_Desc(NTYPE_TEMPERATURE, 0);
					root["result"][ii]["ptag"] = Notification_Type_Desc(NTYPE_TEMPERATURE, 1);
					ii++;
				}
				if (dType == pTypeEvohomeZone)
				{
					root["result"][ii]["val"] = NTYPE_TEMPERATURE;
					root["result"][ii]["text"] = Notification_Type_Desc(NTYPE_SETPOINT, 0); // FIXME NTYPE_SETPOINT implementation?
					root["result"][ii]["ptag"] = Notification_Type_Desc(NTYPE_SETPOINT, 1);
					ii++;
				}
				if ((dType == pTypeRFXSensor) && ((dSubType == sTypeRFXSensorAD) || (dSubType == sTypeRFXSensorVolt)))
				{
					root["result"][ii]["val"] = NTYPE_USAGE;
					root["result"][ii]["text"] = Notification_Type_Desc(NTYPE_USAGE, 0);
					root["result"][ii]["ptag"] = Notification_Type_Desc(NTYPE_USAGE, 1);
					ii++;
				}
				if ((dType == pTypeGeneral) && (dSubType == sTypePercentage))
				{
					root["result"][ii]["val"] = NTYPE_PERCENTAGE;
					root["result"][ii]["text"] = Notification_Type_Desc(NTYPE_PERCENTAGE, 0);
					root["result"][ii]["ptag"] = Notification_Type_Desc(NTYPE_PERCENTAGE, 1);
					ii++;
				}
				if ((dType == pTypeGeneral) && (dSubType == sTypeWaterflow))
				{
					root["result"][ii]["val"] = NTYPE_USAGE;
					root["result"][ii]["text"] = Notification_Type_Desc(NTYPE_USAGE, 0);
					root["result"][ii]["ptag"] = Notification_Type_Desc(NTYPE_USAGE, 1);
					ii++;
				}
				if ((dType == pTypeGeneral) && (dSubType == sTypeCustom))
				{
					root["result"][ii]["val"] = NTYPE_USAGE;
					root["result"][ii]["text"] = Notification_Type_Desc(NTYPE_USAGE, 0);
					root["result"][ii]["ptag"] = Notification_Type_Desc(NTYPE_USAGE, 1);
					ii++;
				}
				if ((dType == pTypeGeneral) && (dSubType == sTypeFan))
				{
					root["result"][ii]["val"] = NTYPE_RPM;
					root["result"][ii]["text"] = Notification_Type_Desc(NTYPE_RPM, 0);
					root["result"][ii]["ptag"] = Notification_Type_Desc(NTYPE_RPM, 1);
					ii++;
				}
				if ((dType == pTypeGeneral) && (dSubType == sTypeAlert))
				{
					root["result"][ii]["val"] = NTYPE_USAGE;
					root["result"][ii]["text"] = Notification_Type_Desc(NTYPE_USAGE, 0);
					root["result"][ii]["ptag"] = Notification_Type_Desc(NTYPE_USAGE, 1);
					ii++;
				}
				if ((dType == pTypeGeneral) && (dSubType == sTypeZWaveAlarm))
				{
					root["result"][ii]["val"] = NTYPE_VALUE;
					root["result"][ii]["text"] = Notification_Type_Desc(NTYPE_VALUE, 0);
					root["result"][ii]["ptag"] = Notification_Type_Desc(NTYPE_VALUE, 1);
					ii++;
				}
				if ((dType == pTypeRego6XXValue) && (dSubType == sTypeRego6XXStatus))
				{
					root["result"][ii]["val"] = NTYPE_SWITCH_ON;
					root["result"][ii]["text"] = Notification_Type_Desc(NTYPE_SWITCH_ON, 0);
					root["result"][ii]["ptag"] = Notification_Type_Desc(NTYPE_SWITCH_ON, 1);
					ii++;
					root["result"][ii]["val"] = NTYPE_SWITCH_OFF;
					root["result"][ii]["text"] = Notification_Type_Desc(NTYPE_SWITCH_OFF, 0);
					root["result"][ii]["ptag"] = Notification_Type_Desc(NTYPE_SWITCH_OFF, 1);
					ii++;
				}
				if (!IsLightOrSwitch(dType, dSubType))
				{
					root["result"][ii]["val"] = NTYPE_LASTUPDATE;
					root["result"][ii]["text"] = Notification_Type_Desc(NTYPE_LASTUPDATE, 0);
					root["result"][ii]["ptag"] = Notification_Type_Desc(NTYPE_LASTUPDATE, 1);
					ii++;
				}
			}
			else if (cparam == "addnotification")
			{
				if (session.rights < 2)
				{
					session.reply_status = reply::forbidden;
					return; // Only admin user allowed
				}

				std::string idx = request::findValue(&req, "idx");
				if (idx.empty())
					return;

				std::string stype = request::findValue(&req, "ttype");
				std::string swhen = request::findValue(&req, "twhen");
				std::string svalue = request::findValue(&req, "tvalue");
				std::string scustommessage = request::findValue(&req, "tmsg");
				std::string sactivesystems = request::findValue(&req, "tsystems");
				std::string spriority = request::findValue(&req, "tpriority");
				std::string ssendalways = request::findValue(&req, "tsendalways");
				std::string srecovery = (request::findValue(&req, "trecovery") == "true") ? "1" : "0";

				if ((stype.empty()) || (swhen.empty()) || (svalue.empty()) || (spriority.empty()) || (ssendalways.empty()) || (srecovery.empty()))
					return;

				_eNotificationTypes ntype = (_eNotificationTypes)atoi(stype.c_str());
				std::string ttype = Notification_Type_Desc(ntype, 1);
				if ((ntype == NTYPE_SWITCH_ON) || (ntype == NTYPE_SWITCH_OFF) || (ntype == NTYPE_DEWPOINT))
				{
					if ((ntype == NTYPE_SWITCH_ON) && (swhen == "2"))
					{ // '='
						unsigned char twhen = '=';
						sprintf(szTmp, "%s;%c;%s", ttype.c_str(), twhen, svalue.c_str());
					}
					else
						strcpy(szTmp, ttype.c_str());
				}
				else
				{
					std::string twhen;
					if (swhen == "0")
						twhen = ">";
					else if (swhen == "1")
						twhen = ">=";
					else if (swhen == "2")
						twhen = "=";
					else if (swhen == "3")
						twhen = "!=";
					else if (swhen == "4")
						twhen = "<=";
					else
						twhen = "<";
					sprintf(szTmp, "%s;%s;%s;%s", ttype.c_str(), twhen.c_str(), svalue.c_str(), srecovery.c_str());
				}
				int priority = atoi(spriority.c_str());
				bool bOK = m_notifications.AddNotification(idx, szTmp, scustommessage, sactivesystems, priority, (ssendalways == "true") ? true : false);
				if (bOK)
				{
					root["status"] = "OK";
					root["title"] = "AddNotification";
				}
			}
			else if (cparam == "updatenotification")
			{
				if (session.rights < 2)
				{
					session.reply_status = reply::forbidden;
					return; // Only admin user allowed
				}

				std::string idx = request::findValue(&req, "idx");
				std::string devidx = request::findValue(&req, "devidx");
				if ((idx.empty()) || (devidx.empty()))
					return;

				std::string stype = request::findValue(&req, "ttype");
				std::string swhen = request::findValue(&req, "twhen");
				std::string svalue = request::findValue(&req, "tvalue");
				std::string scustommessage = request::findValue(&req, "tmsg");
				std::string sactivesystems = request::findValue(&req, "tsystems");
				std::string spriority = request::findValue(&req, "tpriority");
				std::string ssendalways = request::findValue(&req, "tsendalways");
				std::string srecovery = (request::findValue(&req, "trecovery") == "true") ? "1" : "0";

				if ((stype.empty()) || (swhen.empty()) || (svalue.empty()) || (spriority.empty()) || (ssendalways.empty()) || srecovery.empty())
					return;
				root["status"] = "OK";
				root["title"] = "UpdateNotification";

				std::string recoverymsg;
				if ((srecovery == "1") && (m_notifications.CustomRecoveryMessage(strtoull(idx.c_str(), nullptr, 0), recoverymsg, true)))
				{
					scustommessage.append(";;");
					scustommessage.append(recoverymsg);
				}
				// delete old record
				m_notifications.RemoveNotification(idx);

				_eNotificationTypes ntype = (_eNotificationTypes)atoi(stype.c_str());
				std::string ttype = Notification_Type_Desc(ntype, 1);
				if ((ntype == NTYPE_SWITCH_ON) || (ntype == NTYPE_SWITCH_OFF) || (ntype == NTYPE_DEWPOINT))
				{
					if ((ntype == NTYPE_SWITCH_ON) && (swhen == "2"))
					{ // '='
						unsigned char twhen = '=';
						sprintf(szTmp, "%s;%c;%s", ttype.c_str(), twhen, svalue.c_str());
					}
					else
						strcpy(szTmp, ttype.c_str());
				}
				else
				{
					std::string twhen;
					if (swhen == "0")
						twhen = ">";
					else if (swhen == "1")
						twhen = ">=";
					else if (swhen == "2")
						twhen = "=";
					else if (swhen == "3")
						twhen = "!=";
					else if (swhen == "4")
						twhen = "<=";
					else
						twhen = "<";
					sprintf(szTmp, "%s;%s;%s;%s", ttype.c_str(), twhen.c_str(), svalue.c_str(), srecovery.c_str());
				}
				int priority = atoi(spriority.c_str());
				m_notifications.AddNotification(devidx, szTmp, scustommessage, sactivesystems, priority, (ssendalways == "true") ? true : false);
			}
			else if (cparam == "deletenotification")
			{
				if (session.rights < 2)
				{
					session.reply_status = reply::forbidden;
					return; // Only admin user allowed
				}

				std::string idx = request::findValue(&req, "idx");
				if (idx.empty())
					return;

				root["status"] = "OK";
				root["title"] = "DeleteNotification";

				m_notifications.RemoveNotification(idx);
			}
			else if (cparam == "switchdeviceorder")
			{
				if (session.rights < 2)
				{
					session.reply_status = reply::forbidden;
					return; // Only admin user allowed
				}

				std::string idx1 = request::findValue(&req, "idx1");
				std::string idx2 = request::findValue(&req, "idx2");
				if ((idx1.empty()) || (idx2.empty()))
					return;
				std::string sroomid = request::findValue(&req, "roomid");
				int roomid = atoi(sroomid.c_str());

				std::string Order1, Order2;
				if (roomid == 0)
				{
					// get device order 1
					result = m_sql.safe_query("SELECT [Order] FROM DeviceStatus WHERE (ID == '%q')", idx1.c_str());
					if (result.empty())
						return;
					Order1 = result[0][0];

					// get device order 2
					result = m_sql.safe_query("SELECT [Order] FROM DeviceStatus WHERE (ID == '%q')", idx2.c_str());
					if (result.empty())
						return;
					Order2 = result[0][0];

					root["status"] = "OK";
					root["title"] = "SwitchDeviceOrder";

					if (atoi(Order1.c_str()) < atoi(Order2.c_str()))
					{
						m_sql.safe_query("UPDATE DeviceStatus SET [Order] = [Order]+1 WHERE ([Order] >= '%q' AND [Order] < '%q')", Order1.c_str(), Order2.c_str());
					}
					else
					{
						m_sql.safe_query("UPDATE DeviceStatus SET [Order] = [Order]-1 WHERE ([Order] > '%q' AND [Order] <= '%q')", Order2.c_str(), Order1.c_str());
					}

					m_sql.safe_query("UPDATE DeviceStatus SET [Order] = '%q' WHERE (ID == '%q')", Order1.c_str(), idx2.c_str());
				}
				else
				{
					// change order in a room
					// get device order 1
					result = m_sql.safe_query("SELECT [Order] FROM DeviceToPlansMap WHERE (DeviceRowID == '%q') AND (PlanID==%d)", idx1.c_str(), roomid);
					if (result.empty())
						return;
					Order1 = result[0][0];

					// get device order 2
					result = m_sql.safe_query("SELECT [Order] FROM DeviceToPlansMap WHERE (DeviceRowID == '%q') AND (PlanID==%d)", idx2.c_str(), roomid);
					if (result.empty())
						return;
					Order2 = result[0][0];

					root["status"] = "OK";
					root["title"] = "SwitchDeviceOrder";

					if (atoi(Order1.c_str()) < atoi(Order2.c_str()))
					{
						m_sql.safe_query("UPDATE DeviceToPlansMap SET [Order] = [Order]+1 WHERE ([Order] >= '%q' AND [Order] < '%q') AND (PlanID==%d)", Order1.c_str(),
								 Order2.c_str(), roomid);
					}
					else
					{
						m_sql.safe_query("UPDATE DeviceToPlansMap SET [Order] = [Order]-1 WHERE ([Order] > '%q' AND [Order] <= '%q') AND (PlanID==%d)", Order2.c_str(),
								 Order1.c_str(), roomid);
					}

					m_sql.safe_query("UPDATE DeviceToPlansMap SET [Order] = '%q' WHERE (DeviceRowID == '%q') AND (PlanID==%d)", Order1.c_str(), idx2.c_str(), roomid);
				}
			}
			else if (cparam == "switchsceneorder")
			{
				if (session.rights < 2)
				{
					session.reply_status = reply::forbidden;
					return; // Only admin user allowed
				}

				std::string idx1 = request::findValue(&req, "idx1");
				std::string idx2 = request::findValue(&req, "idx2");
				if ((idx1.empty()) || (idx2.empty()))
					return;

				std::string Order1, Order2;
				// get device order 1
				result = m_sql.safe_query("SELECT [Order] FROM Scenes WHERE (ID == '%q')", idx1.c_str());
				if (result.empty())
					return;
				Order1 = result[0][0];

				// get device order 2
				result = m_sql.safe_query("SELECT [Order] FROM Scenes WHERE (ID == '%q')", idx2.c_str());
				if (result.empty())
					return;
				Order2 = result[0][0];

				root["status"] = "OK";
				root["title"] = "SwitchSceneOrder";

				if (atoi(Order1.c_str()) < atoi(Order2.c_str()))
				{
					m_sql.safe_query("UPDATE Scenes SET [Order] = [Order]+1 WHERE ([Order] >= '%q' AND [Order] < '%q')", Order1.c_str(), Order2.c_str());
				}
				else
				{
					m_sql.safe_query("UPDATE Scenes SET [Order] = [Order]-1 WHERE ([Order] > '%q' AND [Order] <= '%q')", Order2.c_str(), Order1.c_str());
				}

				m_sql.safe_query("UPDATE Scenes SET [Order] = '%q' WHERE (ID == '%q')", Order1.c_str(), idx2.c_str());
			}
			else if (cparam == "clearnotifications")
			{
				if (session.rights < 2)
				{
					session.reply_status = reply::forbidden;
					return; // Only admin user allowed
				}

				std::string idx = request::findValue(&req, "idx");
				if (idx.empty())
					return;

				root["status"] = "OK";
				root["title"] = "ClearNotification";

				m_notifications.RemoveDeviceNotifications(idx);
			}
			else if (cparam == "adduser")
			{
				if (session.rights < 2)
				{
					session.reply_status = reply::forbidden;
					return; // Only admin user allowed
				}

				std::string senabled = request::findValue(&req, "enabled");
				std::string username = request::findValue(&req, "username");
				std::string password = request::findValue(&req, "password");
				std::string srights = request::findValue(&req, "rights");
				std::string sRemoteSharing = request::findValue(&req, "RemoteSharing");
				std::string sTabsEnabled = request::findValue(&req, "TabsEnabled");
				if ((senabled.empty()) || (username.empty()) || (password.empty()) || (srights.empty()) || (sRemoteSharing.empty()) || (sTabsEnabled.empty()))
					return;
				int rights = atoi(srights.c_str());
				if (rights != 2)
				{
					if (!FindAdminUser())
					{
						root["message"] = "Add a Admin user first! (Or enable Settings/Website Protection)";
						return;
					}
				}
				// Check for duplicate user name
				result = m_sql.safe_query("SELECT ID FROM Users WHERE (Username == '%q')", base64_encode(username).c_str());
				if (!result.empty())
				{
					root["message"] = "Duplicate Username!";
					return;
				}
				root["status"] = "OK";
				root["title"] = "AddUser";
				m_sql.safe_query("INSERT INTO Users (Active, Username, Password, Rights, RemoteSharing, TabsEnabled) VALUES (%d,'%q','%q','%d','%d','%d')",
						 (senabled == "true") ? 1 : 0, base64_encode(username).c_str(), password.c_str(), rights, (sRemoteSharing == "true") ? 1 : 0,
						 atoi(sTabsEnabled.c_str()));
				LoadUsers();
			}
			else if (cparam == "updateuser")
			{
				if (session.rights < 2)
				{
					session.reply_status = reply::forbidden;
					return; // Only admin user allowed
				}

				std::string idx = request::findValue(&req, "idx");
				if (idx.empty())
					return;
				std::string senabled = request::findValue(&req, "enabled");
				std::string username = request::findValue(&req, "username");
				std::string password = request::findValue(&req, "password");
				std::string srights = request::findValue(&req, "rights");
				std::string sRemoteSharing = request::findValue(&req, "RemoteSharing");
				std::string sTabsEnabled = request::findValue(&req, "TabsEnabled");
				if ((senabled.empty()) || (username.empty()) || (password.empty()) || (srights.empty()) || (sRemoteSharing.empty()) || (sTabsEnabled.empty()))
					return;
				int rights = atoi(srights.c_str());
				if (rights != 2)
				{
					if (!FindAdminUser())
					{
						root["message"] = "Add a Admin user first! (Or enable Settings/Website Protection)";
						return;
					}
				}
				std::string sHashedUsername = base64_encode(username);

				// Check for duplicate user name
				result = m_sql.safe_query("SELECT ID FROM Users WHERE (Username == '%q')", base64_encode(username).c_str());
				if (!result.empty())
				{
					std::string oidx = result[0][0];
					if (oidx != idx)
					{
						root["message"] = "Duplicate Username!";
						return;
					}
				}

				// Invalid user's sessions if username or password has changed
				std::string sOldUsername;
				std::string sOldPassword;
				result = m_sql.safe_query("SELECT Username, Password FROM Users WHERE (ID == '%q')", idx.c_str());
				if (result.size() == 1)
				{
					sOldUsername = result[0][0];
					sOldPassword = result[0][1];
				}
				if ((sHashedUsername != sOldUsername) || (password != sOldPassword))
					RemoveUsersSessions(sOldUsername, session);

				root["status"] = "OK";
				root["title"] = "UpdateUser";
				m_sql.safe_query("UPDATE Users SET Active=%d, Username='%q', Password='%q', Rights=%d, RemoteSharing=%d, TabsEnabled=%d WHERE (ID == '%q')",
						 (senabled == "true") ? 1 : 0, sHashedUsername.c_str(), password.c_str(), rights, (sRemoteSharing == "true") ? 1 : 0, atoi(sTabsEnabled.c_str()),
						 idx.c_str());
				LoadUsers();
			}
			else if (cparam == "deleteuser")
			{
				if (session.rights < 2)
				{
					session.reply_status = reply::forbidden;
					return; // Only admin user allowed
				}

				std::string idx = request::findValue(&req, "idx");
				if (idx.empty())
					return;

				root["status"] = "OK";
				root["title"] = "DeleteUser";

				// Remove user's sessions
				result = m_sql.safe_query("SELECT Username FROM Users WHERE (ID == '%q')", idx.c_str());
				if (result.size() == 1)
				{
					RemoveUsersSessions(result[0][0], session);
				}

				m_sql.safe_query("DELETE FROM Users WHERE (ID == '%q')", idx.c_str());

				m_sql.safe_query("DELETE FROM SharedDevices WHERE (SharedUserID == '%q')", idx.c_str());

				LoadUsers();
			}
			else if (cparam == "clearlightlog")
			{
				if (session.rights < 2)
				{
					session.reply_status = reply::forbidden;
					return; // Only admin user allowed
				}

				std::string idx = request::findValue(&req, "idx");
				if (idx.empty())
					return;
				// First get Device Type/SubType
				result = m_sql.safe_query("SELECT Type, SubType FROM DeviceStatus WHERE (ID == '%q')", idx.c_str());
				if (result.empty())
					return;

				unsigned char dType = atoi(result[0][0].c_str());
				unsigned char dSubType = atoi(result[0][1].c_str());

				if ((dType != pTypeLighting1) && (dType != pTypeLighting2) && (dType != pTypeLighting3) && (dType != pTypeLighting4) && (dType != pTypeLighting5) &&
				    (dType != pTypeLighting6) && (dType != pTypeFan) && (dType != pTypeColorSwitch) && (dType != pTypeSecurity1) && (dType != pTypeSecurity2) &&
				    (dType != pTypeEvohome) && (dType != pTypeEvohomeRelay) && (dType != pTypeCurtain) && (dType != pTypeBlinds) && (dType != pTypeRFY) && (dType != pTypeChime) &&
				    (dType != pTypeThermostat2) && (dType != pTypeThermostat3) && (dType != pTypeThermostat4) && (dType != pTypeRemote) && (dType != pTypeGeneralSwitch) &&
				    (dType != pTypeHomeConfort) && (dType != pTypeFS20) && (!((dType == pTypeRadiator1) && (dSubType == sTypeSmartwaresSwitchRadiator))) &&
				    (!((dType == pTypeGeneral) && (dSubType == sTypeTextStatus))) && (!((dType == pTypeGeneral) && (dSubType == sTypeAlert))) && (dType != pTypeHunter))
					return; // no light device! we should not be here!

				root["status"] = "OK";
				root["title"] = "ClearLightLog";

				result = m_sql.safe_query("DELETE FROM LightingLog WHERE (DeviceRowID=='%q')", idx.c_str());
			}
			else if (cparam == "clearscenelog")
			{
				if (session.rights < 2)
				{
					session.reply_status = reply::forbidden;
					return; // Only admin user allowed
				}

				std::string idx = request::findValue(&req, "idx");
				if (idx.empty())
					return;
				root["status"] = "OK";
				root["title"] = "ClearSceneLog";

				result = m_sql.safe_query("DELETE FROM SceneLog WHERE (SceneRowID=='%q')", idx.c_str());
			}
			else if (cparam == "learnsw")
			{
				if (session.rights < 2)
				{
					session.reply_status = reply::forbidden;
					return; // Only admin user allowed
				}

				m_sql.AllowNewHardwareTimer(5);
				m_sql.m_LastSwitchID = "";
				bool bReceivedSwitch = false;
				unsigned char cntr = 0;
				while ((!bReceivedSwitch) && (cntr < 50)) // wait for max. 5 seconds
				{
					if (!m_sql.m_LastSwitchID.empty())
					{
						bReceivedSwitch = true;
						break;
					}
					// sleep 100ms
					sleep_milliseconds(100);
					cntr++;
				}
				if (bReceivedSwitch)
				{
					// check if used
					result = m_sql.safe_query("SELECT Name, Used, nValue FROM DeviceStatus WHERE (ID==%" PRIu64 ")", m_sql.m_LastSwitchRowID);
					if (!result.empty())
					{
						root["status"] = "OK";
						root["title"] = "LearnSW";
						root["ID"] = m_sql.m_LastSwitchID;
						root["idx"] = Json::Value::UInt64(m_sql.m_LastSwitchRowID);
						root["Name"] = result[0][0];
						root["Used"] = atoi(result[0][1].c_str());
						root["Cmd"] = atoi(result[0][2].c_str());
					}
				}
			} // learnsw
			else if (cparam == "makefavorite")
			{
				if (session.rights < 1)
				{
					session.reply_status = reply::forbidden;
					return; // Only admin user allowed
				}
				std::string idx = request::findValue(&req, "idx");
				std::string sisfavorite = request::findValue(&req, "isfavorite");
				if ((idx.empty()) || (sisfavorite.empty()))
					return;
				int isfavorite = atoi(sisfavorite.c_str());

				root["status"] = "OK";
				root["title"] = "MakeFavorite";

				const int iUser = FindUser(session.username.c_str());
				if (iUser != -1)
				{
					const _eUserRights urights = m_users[iUser].userrights;
					if ((urights != URIGHTS_ADMIN) && (m_users[iUser].ID != 0xFFFF))
					{
						m_sql.safe_query("UPDATE SharedDevices SET Favorite=%d WHERE (DeviceRowID == '%q') AND (SharedUserID == %d)", isfavorite, idx.c_str(),
								 m_users[iUser].ID);
						return;
					}
				}
				m_sql.safe_query("UPDATE DeviceStatus SET Favorite=%d WHERE (ID == '%q')", isfavorite, idx.c_str());
			} // makefavorite
			else if (cparam == "makescenefavorite")
			{
				if (session.rights < 2)
				{
					session.reply_status = reply::forbidden;
					return; // Only admin user allowed
				}

				std::string idx = request::findValue(&req, "idx");
				std::string sisfavorite = request::findValue(&req, "isfavorite");
				if ((idx.empty()) || (sisfavorite.empty()))
					return;
				int isfavorite = atoi(sisfavorite.c_str());
				m_sql.safe_query("UPDATE Scenes SET Favorite=%d WHERE (ID == '%q')", isfavorite, idx.c_str());
				root["status"] = "OK";
				root["title"] = "MakeSceneFavorite";
			} // makescenefavorite
			else if (cparam == "resetsecuritystatus")
			{
				std::string idx = request::findValue(&req, "idx");
				std::string switchcmd = request::findValue(&req, "switchcmd");

				if ((idx.empty()) || (switchcmd.empty()))
					return;

				root["status"] = "OK";
				root["title"] = "ResetSecurityStatus";

				int nValue = -1;

				// Change to generic *Security_Status_Desc lookup...

				if (switchcmd == "Panic End")
				{
					nValue = 7;
				}
				else if (switchcmd == "Normal")
				{
					nValue = 0;
				}

				if (nValue >= 0)
				{
					m_sql.safe_query("UPDATE DeviceStatus SET nValue=%d WHERE (ID == '%q')", nValue, idx.c_str());
					root["status"] = "OK";
					root["title"] = "SwitchLight";
				}
			}
			else if (cparam == "verifypasscode")
			{
				std::string passcode = request::findValue(&req, "passcode");
				if (passcode.empty())
					return;
				// Check if passcode is correct
				passcode = GenerateMD5Hash(passcode);
				std::string rpassword;
				int nValue = 1;
				m_sql.GetPreferencesVar("ProtectionPassword", nValue, rpassword);
				if (passcode == rpassword)
				{
					root["title"] = "VerifyPasscode";
					root["status"] = "OK";
					return;
				}
			}
			else if (cparam == "switchmodal")
			{
				int urights = 3;
				if (bHaveUser)
				{
					int iUser = -1;
					iUser = FindUser(session.username.c_str());
					if (iUser != -1)
					{
						urights = (int)m_users[iUser].userrights;
						_log.Log(LOG_STATUS, "User: %s initiated a modal command", m_users[iUser].Username.c_str());
					}
				}
				if (urights < 1)
					return;

				std::string idx = request::findValue(&req, "idx");
				std::string switchcmd = request::findValue(&req, "status");
				std::string until = request::findValue(&req, "until");	    // optional until date / time as applicable
				std::string action = request::findValue(&req, "action");    // Run action or not (update status only)
				std::string onlyonchange = request::findValue(&req, "ooc"); // No update unless the value changed (check if updated)
				// The on action is used to call a script to update the real device so we only want to use it when altering the status in the Domoticz Web Client
				// If we're posting the status from the real device to domoticz we don't want to run the on action script ("action"!=1) to avoid loops and contention
				//""... we only want to log a change (and trigger an event) when the status has actually changed ("ooc"==1) i.e. suppress non transient updates
				if ((idx.empty()) || (switchcmd.empty()))
					return;

				std::string passcode = request::findValue(&req, "passcode");
				if (!passcode.empty())
				{
					// Check if passcode is correct
					passcode = GenerateMD5Hash(passcode);
					std::string rpassword;
					int nValue = 1;
					m_sql.GetPreferencesVar("ProtectionPassword", nValue, rpassword);
					if (passcode != rpassword)
					{
						root["title"] = "Modal";
						root["status"] = "ERROR";
						root["message"] = "WRONG CODE";
						return;
					}
				}

				if (m_mainworker.SwitchEvoModal(idx, switchcmd, action, onlyonchange, until) ==
				    true) // FIXME we need to return a status of already set / no update if ooc=="1" and no status update was performed
				{
					root["status"] = "OK";
					root["title"] = "Modal";
				}
			}
			else if (cparam == "switchlight")
			{
				if (session.rights < 1)
				{
					session.reply_status = reply::forbidden;
					return; // Only user/admin allowed
				}
				std::string Username = "Admin";
				if (!session.username.empty())
					Username = session.username;

				std::string idx = request::findValue(&req, "idx");

				std::string switchcmd = request::findValue(&req, "switchcmd");
				std::string level = "-1";
				if (switchcmd == "Set Level")
					level = request::findValue(&req, "level");
				std::string onlyonchange = request::findValue(&req, "ooc"); // No update unless the value changed (check if updated)
				_log.Debug(DEBUG_WEBSERVER, "WEBS switchlight idx:%s switchcmd:%s level:%s", idx.c_str(), switchcmd.c_str(), level.c_str());
				std::string passcode = request::findValue(&req, "passcode");
				if ((idx.empty()) || (switchcmd.empty()) || ((switchcmd == "Set Level") && (level.empty())))
					return;

				result = m_sql.safe_query("SELECT [Protected],[Name] FROM DeviceStatus WHERE (ID = '%q')", idx.c_str());
				if (result.empty())
				{
					// Switch not found!
					_log.Log(LOG_ERROR, "User: %s, switch not found (idx=%s)!", Username.c_str(), idx.c_str());
					return;
				}
				bool bIsProtected = atoi(result[0][0].c_str()) != 0;
				std::string sSwitchName = result[0][1];
				if (session.rights == 1)
				{
					if (!IsIdxForUser(&session, atoi(idx.c_str())))
					{
						_log.Log(LOG_ERROR, "User: %s initiated a Unauthorized switch command!", Username.c_str());
						session.reply_status = reply::forbidden;
						return;
					}
				}

				std::string szSwitchUser = Username + " (IP: " + session.remote_host + ")";

				if (bIsProtected)
				{
					if (passcode.empty())
					{
						// Switch is protected, but no passcode has been
						root["title"] = "SwitchLight";
						root["status"] = "ERROR";
						root["message"] = "WRONG CODE";
						return;
					}
					// Check if passcode is correct
					passcode = GenerateMD5Hash(passcode);
					std::string rpassword;
					int nValue = 1;
					m_sql.GetPreferencesVar("ProtectionPassword", nValue, rpassword);
					if (passcode != rpassword)
					{
						_log.Log(LOG_ERROR, "User: %s initiated a switch command (Wrong code!)", szSwitchUser.c_str());
						root["title"] = "SwitchLight";
						root["status"] = "ERROR";
						root["message"] = "WRONG CODE";
						return;
					}
				}

				_log.Log(LOG_STATUS, "User: %s initiated a switch command (%s/%s/%s)", szSwitchUser.c_str(), idx.c_str(), sSwitchName.c_str(), switchcmd.c_str());

				root["title"] = "SwitchLight";
				if (m_mainworker.SwitchLight(idx, switchcmd, level, "-1", onlyonchange, 0, szSwitchUser) == true)
				{
					root["status"] = "OK";
				}
				else
				{
					root["status"] = "ERROR";
					root["message"] = "Error sending switch command, check device/hardware (idx=" + idx + ") !";
				}
			} //(rtype=="switchlight")
			else if (cparam == "switchscene")
			{
				if (session.rights < 1)
				{
					session.reply_status = reply::forbidden;
					return; // Only user/admin allowed
				}
				std::string Username = "Admin";
				if (!session.username.empty())
					Username = session.username;

				std::string szSwitchUser = Username + " (IP: " + session.remote_host + ")";

				std::string idx = request::findValue(&req, "idx");
				std::string switchcmd = request::findValue(&req, "switchcmd");
				std::string passcode = request::findValue(&req, "passcode");
				if ((idx.empty()) || (switchcmd.empty()))
					return;

				result = m_sql.safe_query("SELECT [Protected] FROM Scenes WHERE (ID = '%q')", idx.c_str());
				if (result.empty())
				{
					// Scene/Group not found!
					_log.Log(LOG_ERROR, "User: %s, scene not found (idx=%s)!", szSwitchUser.c_str(), idx.c_str());
					return;
				}
				bool bIsProtected = atoi(result[0][0].c_str()) != 0;
				if (bIsProtected)
				{
					if (passcode.empty())
					{
						root["title"] = "SwitchScene";
						root["status"] = "ERROR";
						root["message"] = "WRONG CODE";
						return;
					}
					// Check if passcode is correct
					passcode = GenerateMD5Hash(passcode);
					std::string rpassword;
					int nValue = 1;
					m_sql.GetPreferencesVar("ProtectionPassword", nValue, rpassword);
					if (passcode != rpassword)
					{
						root["title"] = "SwitchScene";
						root["status"] = "ERROR";
						root["message"] = "WRONG CODE";
						_log.Log(LOG_ERROR, "User: %s initiated a scene/group command (Wrong code!)", szSwitchUser.c_str());
						return;
					}
				}
				_log.Log(LOG_STATUS, "User: %s initiated a scene/group command", szSwitchUser.c_str());

				if (m_mainworker.SwitchScene(idx, switchcmd, szSwitchUser) == true)
				{
					root["status"] = "OK";
					root["title"] = "SwitchScene";
				}
			} //(rtype=="switchscene")
			else if (cparam == "getSunRiseSet")
			{
				if (!m_mainworker.m_LastSunriseSet.empty())
				{
					std::vector<std::string> strarray;
					StringSplit(m_mainworker.m_LastSunriseSet, ";", strarray);
					if (strarray.size() == 10)
					{
						struct tm loctime;
						time_t now = mytime(nullptr);

						localtime_r(&now, &loctime);
						// strftime(szTmp, 80, "%b %d %Y %X", &loctime);
						strftime(szTmp, 80, "%Y-%m-%d %X", &loctime);

						root["status"] = "OK";
						root["title"] = "getSunRiseSet";
						root["ServerTime"] = szTmp;
						root["Sunrise"] = strarray[0];
						root["Sunset"] = strarray[1];
						root["SunAtSouth"] = strarray[2];
						root["CivTwilightStart"] = strarray[3];
						root["CivTwilightEnd"] = strarray[4];
						root["NautTwilightStart"] = strarray[5];
						root["NautTwilightEnd"] = strarray[6];
						root["AstrTwilightStart"] = strarray[7];
						root["AstrTwilightEnd"] = strarray[8];
						root["DayLength"] = strarray[9];
					}
				}
			}
			else if (cparam == "getServerTime")
			{

				struct tm loctime;
				time_t now = mytime(nullptr);

				localtime_r(&now, &loctime);
				// strftime(szTmp, 80, "%b %d %Y %X", &loctime);
				strftime(szTmp, 80, "%Y-%m-%d %X", &loctime);

				root["status"] = "OK";
				root["title"] = "getServerTime";
				root["ServerTime"] = szTmp;
			}
			else if (cparam == "getsecstatus")
			{
				root["status"] = "OK";
				root["title"] = "GetSecStatus";

				int secstatus = 0;
				m_sql.GetPreferencesVar("SecStatus", secstatus);
				root["secstatus"] = secstatus;

				int secondelay = 30;
				m_sql.GetPreferencesVar("SecOnDelay", secondelay);
				root["secondelay"] = secondelay;
			}
			else if (cparam == "setsecstatus")
			{
				std::string ssecstatus = request::findValue(&req, "secstatus");
				std::string seccode = request::findValue(&req, "seccode");
				if ((ssecstatus.empty()) || (seccode.empty()))
				{
					root["message"] = "WRONG CODE";
					return;
				}
				root["title"] = "SetSecStatus";
				std::string rpassword;
				int nValue = 1;
				m_sql.GetPreferencesVar("SecPassword", nValue, rpassword);
				if (seccode != rpassword)
				{
					root["status"] = "ERROR";
					root["message"] = "WRONG CODE";
					return;
				}
				root["status"] = "OK";
				int iSecStatus = atoi(ssecstatus.c_str());
				m_mainworker.UpdateDomoticzSecurityStatus(iSecStatus);
			}
			else if (cparam == "setcolbrightnessvalue")
			{
				if (session.rights < 1)
				{
					session.reply_status = reply::forbidden;
					return; // Only user/admin allowed
				}

				std::string Username = "Admin";
				if (!session.username.empty())
					Username = session.username;

				std::string idx = request::findValue(&req, "idx");

				if (idx.empty())
				{
					return;
				}
				uint64_t ID = std::strtoull(idx.c_str(), nullptr, 10);
				_tColor color;

				std::string json = request::findValue(&req, "color");
				std::string hex = request::findValue(&req, "hex");
				std::string hue = request::findValue(&req, "hue");
				std::string sat = request::findValue(&req, "sat");
				std::string brightness = request::findValue(&req, "brightness");
				std::string iswhite = request::findValue(&req, "iswhite");

				int ival = 100;
				float brightnessAdj = 1.0F;

				if (!json.empty())
				{
					color = _tColor(json);
					if (color.mode == ColorModeRGB)
					{
						// Normalize RGB to full brightness
						float hsb[3];
						int r, g, b;
						rgb2hsb(color.r, color.g, color.b, hsb);
						hsb2rgb(hsb[0] * 360.0F, hsb[1], 1.0F, r, g, b, 255);
						color.r = r;
						color.g = g;
						color.b = b;
						brightnessAdj = hsb[2];
					}

					//_log.Debug(DEBUG_WEBSERVER, "setcolbrightnessvalue: json: %s, color: '%s', bri: '%s'", json.c_str(), color.toString().c_str(), brightness.c_str());
				}
				else if (!hex.empty())
				{
					uint64_t ihex = hexstrtoui64(hex);
					//_log.Debug(DEBUG_WEBSERVER, "setcolbrightnessvalue: hex: '%s', ihex: %" PRIx64 ", bri: '%s', iswhite: '%s'", hex.c_str(), ihex, brightness.c_str(),
					//iswhite.c_str());
					uint8_t r = 0;
					uint8_t g = 0;
					uint8_t b = 0;
					uint8_t cw = 0;
					uint8_t ww = 0;
					switch (hex.length())
					{
						case 6: // RGB
							r = (uint8_t)((ihex & 0x0000FF0000) >> 16);
							g = (uint8_t)((ihex & 0x000000FF00) >> 8);
							b = (uint8_t)ihex & 0xFF;
							float hsb[3];
							int tr, tg, tb; // tmp of 'int' type so can be passed as references to hsb2rgb
							rgb2hsb(r, g, b, hsb);
							// Normalize RGB to full brightness
							hsb2rgb(hsb[0] * 360.0F, hsb[1], 1.0F, tr, tg, tb, 255);
							r = tr;
							g = tg;
							b = tb;
							brightnessAdj = hsb[2];
							// Backwards compatibility: set iswhite for unsaturated colors
							iswhite = (hsb[1] < (20.0 / 255.0)) ? "true" : "false";
							color = _tColor(r, g, b, cw, ww, ColorModeRGB);
							break;
						case 8: // RGB_WW
							r = (uint8_t)((ihex & 0x00FF000000) >> 24);
							g = (uint8_t)((ihex & 0x0000FF0000) >> 16);
							b = (uint8_t)((ihex & 0x000000FF00) >> 8);
							ww = (uint8_t)ihex & 0xFF;
							color = _tColor(r, g, b, cw, ww, ColorModeCustom);
							break;
						case 10: // RGB_CW_WW
							r = (uint8_t)((ihex & 0xFF00000000) >> 32);
							g = (uint8_t)((ihex & 0x00FF000000) >> 24);
							b = (uint8_t)((ihex & 0x0000FF0000) >> 16);
							cw = (uint8_t)((ihex & 0x000000FF00) >> 8);
							ww = (uint8_t)ihex & 0xFF;
							color = _tColor(r, g, b, cw, ww, ColorModeCustom);
							break;
					}
					if (iswhite == "true")
						color.mode = ColorModeWhite;
					//_log.Debug(DEBUG_WEBSERVER, "setcolbrightnessvalue: rgbww: %02x%02x%02x%02x%02x, color: '%s'", r, g, b, cw, ww, color.toString().c_str());
				}
				else if (!hue.empty())
				{
					int r, g, b;

					// convert hue to RGB
					float iHue = float(atof(hue.c_str()));
					float iSat = 100.0F;
					if (!sat.empty())
						iSat = float(atof(sat.c_str()));
					hsb2rgb(iHue, iSat / 100.0F, 1.0F, r, g, b, 255);

					color = _tColor(r, g, b, 0, 0, ColorModeRGB);
					if (iswhite == "true")
						color.mode = ColorModeWhite;
					//_log.Debug(DEBUG_WEBSERVER, "setcolbrightnessvalue2: hue: %f, rgb: %02x%02x%02x, color: '%s'", iHue, r, g, b, color.toString().c_str());
				}

				if (color.mode == ColorModeNone)
				{
					return;
				}

				if (!brightness.empty())
					ival = atoi(brightness.c_str());
				ival = int(ival * brightnessAdj);
				ival = std::max(ival, 0);
				ival = std::min(ival, 100);

				_log.Log(LOG_STATUS, "setcolbrightnessvalue: ID: %" PRIx64 ", bri: %d, color: '%s'", ID, ival, color.toString().c_str());
				std::string szSwitchUser = Username + " (IP: " + session.remote_host + ")";
				m_mainworker.SwitchLight(ID, "Set Color", (unsigned char)ival, color, false, 0, szSwitchUser);

				root["status"] = "OK";
				root["title"] = "SetColBrightnessValue";
			}
			else if (cparam.find("setkelvinlevel") == 0)
			{
				if (session.rights < 1)
				{
					session.reply_status = reply::forbidden;
					return; // Only user/admin allowed
				}

				std::string Username = "Admin";
				if (!session.username.empty())
					Username = session.username;

				root["status"] = "OK";
				root["title"] = "Set Kelvin Level";

				std::string idx = request::findValue(&req, "idx");

				if (idx.empty())
				{
					return;
				}

				uint64_t ID = std::strtoull(idx.c_str(), nullptr, 10);

				std::string kelvin = request::findValue(&req, "kelvin");
				double ival = atof(kelvin.c_str());
				ival = std::max(ival, 0.0);
				ival = std::min(ival, 100.0);
				_tColor color = _tColor(round(ival * 255.0F / 100.0F), ColorModeTemp);
				_log.Log(LOG_STATUS, "setkelvinlevel: t: %f, color: '%s'", ival, color.toString().c_str());

				std::string szSwitchUser = Username + " (IP: " + session.remote_host + ")";
				m_mainworker.SwitchLight(ID, "Set Color", -1, color, false, 0, szSwitchUser);
			}
			else if (cparam == "brightnessup")
			{
				if (session.rights < 1)
				{
					session.reply_status = reply::forbidden;
					return; // Only user/admin allowed
				}

				std::string Username = "Admin";
				if (!session.username.empty())
					Username = session.username;

				root["status"] = "OK";
				root["title"] = "Set brightness up!";

				std::string idx = request::findValue(&req, "idx");

				if (idx.empty())
				{
					return;
				}

				uint64_t ID = std::strtoull(idx.c_str(), nullptr, 10);
				std::string szSwitchUser = Username + " (IP: " + session.remote_host + ")";
				m_mainworker.SwitchLight(ID, "Bright Up", 0, NoColor, false, 0, szSwitchUser);
			}
			else if (cparam == "brightnessdown")
			{
				if (session.rights < 1)
				{
					session.reply_status = reply::forbidden;
					return; // Only user/admin allowed
				}

				std::string Username = "Admin";
				if (!session.username.empty())
					Username = session.username;

				root["status"] = "OK";
				root["title"] = "Set brightness down!";

				std::string idx = request::findValue(&req, "idx");

				if (idx.empty())
				{
					return;
				}

				uint64_t ID = std::strtoull(idx.c_str(), nullptr, 10);
				std::string szSwitchUser = Username + " (IP: " + session.remote_host + ")";
				m_mainworker.SwitchLight(ID, "Bright Down", 0, NoColor, false, 0, szSwitchUser);
			}
			else if (cparam == "discomode")
			{
				if (session.rights < 1)
				{
					session.reply_status = reply::forbidden;
					return; // Only user/admin allowed
				}

				std::string Username = "Admin";
				if (!session.username.empty())
					Username = session.username;

				root["status"] = "OK";
				root["title"] = "Set to last known disco mode!";

				std::string idx = request::findValue(&req, "idx");

				if (idx.empty())
				{
					return;
				}

				uint64_t ID = std::strtoull(idx.c_str(), nullptr, 10);
				std::string szSwitchUser = Username + " (IP: " + session.remote_host + ")";
				m_mainworker.SwitchLight(ID, "Disco Mode", 0, NoColor, false, 0, szSwitchUser);
			}
			else if (cparam.find("discomodenum") == 0 && cparam != "discomode" && cparam.size() == 13)
			{
				if (session.rights < 1)
				{
					session.reply_status = reply::forbidden;
					return; // Only user/admin allowed
				}

				std::string Username = "Admin";
				if (!session.username.empty())
					Username = session.username;

				root["status"] = "OK";
				root["title"] = "Set to disco mode!";

				std::string idx = request::findValue(&req, "idx");

				if (idx.empty())
				{
					return;
				}

				uint64_t ID = std::strtoull(idx.c_str(), nullptr, 10);
				char szTmp[40];
				sprintf(szTmp, "Disco Mode %s", cparam.substr(12).c_str());
				std::string szSwitchUser = Username + " (IP: " + session.remote_host + ")";
				m_mainworker.SwitchLight(ID, szTmp, 0, NoColor, false, 0, szSwitchUser);
			}
			else if (cparam == "discoup")
			{
				if (session.rights < 1)
				{
					session.reply_status = reply::forbidden;
					return; // Only user/admin allowed
				}

				std::string Username = "Admin";
				if (!session.username.empty())
					Username = session.username;

				root["status"] = "OK";
				root["title"] = "Set to next disco mode!";

				std::string idx = request::findValue(&req, "idx");

				if (idx.empty())
				{
					return;
				}

				uint64_t ID = std::strtoull(idx.c_str(), nullptr, 10);
				std::string szSwitchUser = Username + " (IP: " + session.remote_host + ")";
				m_mainworker.SwitchLight(ID, "Disco Up", 0, NoColor, false, 0, szSwitchUser);
			}
			else if (cparam == "discodown")
			{
				if (session.rights < 1)
				{
					session.reply_status = reply::forbidden;
					return; // Only user/admin allowed
				}

				std::string Username = "Admin";
				if (!session.username.empty())
					Username = session.username;

				root["status"] = "OK";
				root["title"] = "Set to previous disco mode!";

				std::string idx = request::findValue(&req, "idx");

				if (idx.empty())
				{
					return;
				}

				uint64_t ID = std::strtoull(idx.c_str(), nullptr, 10);
				std::string szSwitchUser = Username + " (IP: " + session.remote_host + ")";
				m_mainworker.SwitchLight(ID, "Disco Down", 0, NoColor, false, 0, szSwitchUser);
			}
			else if (cparam == "speedup")
			{
				if (session.rights < 1)
				{
					session.reply_status = reply::forbidden;
					return; // Only user/admin allowed
				}

				std::string Username = "Admin";
				if (!session.username.empty())
					Username = session.username;

				root["status"] = "OK";
				root["title"] = "Set disco speed up!";

				std::string idx = request::findValue(&req, "idx");

				if (idx.empty())
				{
					return;
				}

				uint64_t ID = std::strtoull(idx.c_str(), nullptr, 10);
				std::string szSwitchUser = Username + " (IP: " + session.remote_host + ")";
				m_mainworker.SwitchLight(ID, "Speed Up", 0, NoColor, false, 0, szSwitchUser);
			}
			else if (cparam == "speeduplong")
			{
				if (session.rights < 1)
				{
					session.reply_status = reply::forbidden;
					return; // Only user/admin allowed
				}

				std::string Username = "Admin";
				if (!session.username.empty())
					Username = session.username;

				root["status"] = "OK";
				root["title"] = "Set speed long!";

				std::string idx = request::findValue(&req, "idx");

				if (idx.empty())
				{
					return;
				}

				uint64_t ID = std::strtoull(idx.c_str(), nullptr, 10);
				std::string szSwitchUser = Username + " (IP: " + session.remote_host + ")";
				m_mainworker.SwitchLight(ID, "Speed Up Long", 0, NoColor, false, 0, szSwitchUser);
			}
			else if (cparam == "speeddown")
			{
				if (session.rights < 1)
				{
					session.reply_status = reply::forbidden;
					return; // Only user/admin allowed
				}

				std::string Username = "Admin";
				if (!session.username.empty())
					Username = session.username;

				root["status"] = "OK";
				root["title"] = "Set disco speed down!";

				std::string idx = request::findValue(&req, "idx");

				if (idx.empty())
				{
					return;
				}

				uint64_t ID = std::strtoull(idx.c_str(), nullptr, 10);
				std::string szSwitchUser = Username + " (IP: " + session.remote_host + ")";
				m_mainworker.SwitchLight(ID, "Speed Down", 0, NoColor, false, 0, szSwitchUser);
			}
			else if (cparam == "speedmin")
			{
				if (session.rights < 1)
				{
					session.reply_status = reply::forbidden;
					return; // Only user/admin allowed
				}

				std::string Username = "Admin";
				if (!session.username.empty())
					Username = session.username;

				root["status"] = "OK";
				root["title"] = "Set disco speed minimal!";

				std::string idx = request::findValue(&req, "idx");

				if (idx.empty())
				{
					return;
				}

				uint64_t ID = std::strtoull(idx.c_str(), nullptr, 10);
				std::string szSwitchUser = Username + " (IP: " + session.remote_host + ")";
				m_mainworker.SwitchLight(ID, "Speed Minimal", 0, NoColor, false, 0, szSwitchUser);
			}
			else if (cparam == "speedmax")
			{
				if (session.rights < 1)
				{
					session.reply_status = reply::forbidden;
					return; // Only user/admin allowed
				}

				std::string Username = "Admin";
				if (!session.username.empty())
					Username = session.username;

				root["status"] = "OK";
				root["title"] = "Set disco speed maximal!";

				std::string idx = request::findValue(&req, "idx");

				if (idx.empty())
				{
					return;
				}

				uint64_t ID = std::strtoull(idx.c_str(), nullptr, 10);
				std::string szSwitchUser = Username + " (IP: " + session.remote_host + ")";
				m_mainworker.SwitchLight(ID, "Speed Maximal", 0, NoColor, false, 0, szSwitchUser);
			}
			else if (cparam == "warmer")
			{
				if (session.rights < 1)
				{
					session.reply_status = reply::forbidden;
					return; // Only user/admin allowed
				}

				std::string Username = "Admin";
				if (!session.username.empty())
					Username = session.username;

				root["status"] = "OK";
				root["title"] = "Set Kelvin up!";

				std::string idx = request::findValue(&req, "idx");

				if (idx.empty())
				{
					return;
				}

				uint64_t ID = std::strtoull(idx.c_str(), nullptr, 10);
				std::string szSwitchUser = Username + " (IP: " + session.remote_host + ")";
				m_mainworker.SwitchLight(ID, "Warmer", 0, NoColor, false, 0, szSwitchUser);
			}
			else if (cparam == "cooler")
			{
				if (session.rights < 1)
				{
					session.reply_status = reply::forbidden;
					return; // Only user/admin allowed
				}

				std::string Username = "Admin";
				if (!session.username.empty())
					Username = session.username;

				root["status"] = "OK";
				root["title"] = "Set Kelvin down!";

				std::string idx = request::findValue(&req, "idx");

				if (idx.empty())
				{
					return;
				}

				uint64_t ID = std::strtoull(idx.c_str(), nullptr, 10);
				std::string szSwitchUser = Username + " (IP: " + session.remote_host + ")";
				m_mainworker.SwitchLight(ID, "Cooler", 0, NoColor, false, 0, szSwitchUser);
			}
			else if (cparam == "fulllight")
			{
				if (session.rights < 1)
				{
					session.reply_status = reply::forbidden;
					return; // Only user/admin allowed
				}

				std::string Username = "Admin";
				if (!session.username.empty())
					Username = session.username;

				root["status"] = "OK";
				root["title"] = "Set Full!";

				std::string idx = request::findValue(&req, "idx");

				if (idx.empty())
				{
					return;
				}

				uint64_t ID = std::strtoull(idx.c_str(), nullptr, 10);
				std::string szSwitchUser = Username + " (IP: " + session.remote_host + ")";
				m_mainworker.SwitchLight(ID, "Set Full", 0, NoColor, false, 0, szSwitchUser);
			}
			else if (cparam == "nightlight")
			{
				if (session.rights < 1)
				{
					session.reply_status = reply::forbidden;
					return; // Only user/admin allowed
				}

				std::string Username = "Admin";
				if (!session.username.empty())
					Username = session.username;

				root["status"] = "OK";
				root["title"] = "Set to nightlight!";

				std::string idx = request::findValue(&req, "idx");

				if (idx.empty())
				{
					return;
				}

				uint64_t ID = std::strtoull(idx.c_str(), nullptr, 10);
				std::string szSwitchUser = Username + " (IP: " + session.remote_host + ")";
				m_mainworker.SwitchLight(ID, "Set Night", 0, NoColor, false, 0, szSwitchUser);
			}
			else if (cparam == "whitelight")
			{
				if (session.rights < 1)
				{
					session.reply_status = reply::forbidden;
					return; // Only user/admin allowed
				}

				std::string Username = "Admin";
				if (!session.username.empty())
					Username = session.username;

				root["status"] = "OK";
				root["title"] = "Set to clear white!";

				std::string idx = request::findValue(&req, "idx");

				if (idx.empty())
				{
					return;
				}

				uint64_t ID = std::strtoull(idx.c_str(), nullptr, 10);
				// TODO: Change to color with mode=ColorModeWhite and level=100?
				std::string szSwitchUser = Username + " (IP: " + session.remote_host + ")";
				m_mainworker.SwitchLight(ID, "Set White", 0, NoColor, false, 0, szSwitchUser);
			}
			else if (cparam == "getfloorplanimages")
			{
				root["status"] = "OK";
				root["title"] = "GetFloorplanImages";

				bool bReturnUnused = atoi(request::findValue(&req, "unused").c_str()) != 0;

				if (!bReturnUnused)
					result = m_sql.safe_query("SELECT ID, Name, ScaleFactor FROM Floorplans ORDER BY [Name]");
				else
					result = m_sql.safe_query("SELECT ID, Name, ScaleFactor FROM Floorplans WHERE ID NOT IN(SELECT FloorplanID FROM Plans)");
				if (!result.empty())
				{
					int ii = 0;
					for (const auto &sd : result)
					{
						root["result"][ii]["idx"] = sd[0];
						root["result"][ii]["name"] = sd[1];
						root["result"][ii]["scalefactor"] = sd[2];
						ii++;
					}
				}
			}
			else if (cparam == "updatefloorplan")
			{
				if (session.rights < 2)
				{
					session.reply_status = reply::forbidden;
					return; // Only admin user allowed
				}

				std::string idx = request::findValue(&req, "idx");
				if (idx.empty())
					return;
				std::string name = HTMLSanitizer::Sanitize(request::findValue(&req, "name"));
				std::string scalefactor = request::findValue(&req, "scalefactor");
				if ((name.empty()) || (scalefactor.empty()))
					return;

				root["status"] = "OK";
				root["title"] = "UpdateFloorplan";

				m_sql.safe_query("UPDATE Floorplans SET Name='%q',ScaleFactor='%q' WHERE (ID == '%q')", name.c_str(), scalefactor.c_str(), idx.c_str());
			}
			else if (cparam == "deletefloorplan")
			{
				if (session.rights < 2)
				{
					session.reply_status = reply::forbidden;
					return; // Only admin user allowed
				}

				std::string idx = request::findValue(&req, "idx");
				if (idx.empty())
					return;
				root["status"] = "OK";
				root["title"] = "DeleteFloorplan";
				m_sql.safe_query("UPDATE DeviceToPlansMap SET XOffset=0,YOffset=0 WHERE (PlanID IN (SELECT ID from Plans WHERE (FloorplanID == '%q')))", idx.c_str());
				m_sql.safe_query("UPDATE Plans SET FloorplanID=0,Area='' WHERE (FloorplanID == '%q')", idx.c_str());
				m_sql.safe_query("DELETE FROM Floorplans WHERE (ID == '%q')", idx.c_str());
			}
			else if (cparam == "changefloorplanorder")
			{
				if (session.rights < 2)
				{
					session.reply_status = reply::forbidden;
					return; // Only admin user allowed
				}

				std::string idx = request::findValue(&req, "idx");
				if (idx.empty())
					return;
				std::string sway = request::findValue(&req, "way");
				if (sway.empty())
					return;
				bool bGoUp = (sway == "0");

				std::string aOrder, oID, oOrder;

				result = m_sql.safe_query("SELECT [Order] FROM Floorplans WHERE (ID=='%q')", idx.c_str());
				if (result.empty())
					return;
				aOrder = result[0][0];

				if (!bGoUp)
				{
					// Get next device order
					result = m_sql.safe_query("SELECT ID, [Order] FROM Floorplans WHERE ([Order]>'%q') ORDER BY [Order] ASC", aOrder.c_str());
					if (result.empty())
						return;
					oID = result[0][0];
					oOrder = result[0][1];
				}
				else
				{
					// Get previous device order
					result = m_sql.safe_query("SELECT ID, [Order] FROM Floorplans WHERE ([Order]<'%q') ORDER BY [Order] DESC", aOrder.c_str());
					if (result.empty())
						return;
					oID = result[0][0];
					oOrder = result[0][1];
				}
				// Swap them
				root["status"] = "OK";
				root["title"] = "ChangeFloorPlanOrder";

				m_sql.safe_query("UPDATE Floorplans SET [Order] = '%q' WHERE (ID='%q')", oOrder.c_str(), idx.c_str());
				m_sql.safe_query("UPDATE Floorplans SET [Order] = '%q' WHERE (ID='%q')", aOrder.c_str(), oID.c_str());
			}
			else if (cparam == "getunusedfloorplanplans")
			{
				if (session.rights < 2)
				{
					session.reply_status = reply::forbidden;
					return; // Only admin user allowed
				}

				root["status"] = "OK";
				root["title"] = "GetUnusedFloorplanPlans";
				int ii = 0;

				result = m_sql.safe_query("SELECT ID, Name FROM Plans WHERE (FloorplanID==0) ORDER BY Name");
				if (!result.empty())
				{
					for (const auto &sd : result)
					{
						root["result"][ii]["type"] = 0;
						root["result"][ii]["idx"] = sd[0];
						root["result"][ii]["Name"] = sd[1];
						ii++;
					}
				}
			}
			else if (cparam == "getfloorplanplans")
			{
				std::string idx = request::findValue(&req, "idx");
				if (idx.empty())
					return;
				root["status"] = "OK";
				root["title"] = "GetFloorplanPlans";
				int ii = 0;
				result = m_sql.safe_query("SELECT ID, Name, Area FROM Plans WHERE (FloorplanID=='%q') ORDER BY Name", idx.c_str());
				if (!result.empty())
				{
					for (const auto &sd : result)
					{
						root["result"][ii]["idx"] = sd[0];
						root["result"][ii]["Name"] = sd[1];
						root["result"][ii]["Area"] = sd[2];
						ii++;
					}
				}
			}
			else if (cparam == "addfloorplanplan")
			{
				if (session.rights < 2)
				{
					session.reply_status = reply::forbidden;
					return; // Only admin user allowed
				}

				std::string idx = request::findValue(&req, "idx");
				std::string planidx = request::findValue(&req, "planidx");
				if ((idx.empty()) || (planidx.empty()))
					return;
				root["status"] = "OK";
				root["title"] = "AddFloorplanPlan";

				m_sql.safe_query("UPDATE Plans SET FloorplanID='%q' WHERE (ID == '%q')", idx.c_str(), planidx.c_str());
				_log.Log(LOG_STATUS, "(Floorplan) Plan '%s' added to floorplan '%s'.", planidx.c_str(), idx.c_str());
			}
			else if (cparam == "updatefloorplanplan")
			{
				if (session.rights < 2)
				{
					session.reply_status = reply::forbidden;
					return; // Only admin user allowed
				}

				std::string planidx = request::findValue(&req, "planidx");
				std::string planarea = request::findValue(&req, "area");
				if (planidx.empty())
					return;
				root["status"] = "OK";
				root["title"] = "UpdateFloorplanPlan";

				m_sql.safe_query("UPDATE Plans SET Area='%q' WHERE (ID == '%q')", planarea.c_str(), planidx.c_str());
				_log.Log(LOG_STATUS, "(Floorplan) Plan '%s' floor area updated to '%s'.", planidx.c_str(), planarea.c_str());
			}
			else if (cparam == "deletefloorplanplan")
			{
				if (session.rights < 2)
				{
					session.reply_status = reply::forbidden;
					return; // Only admin user allowed
				}

				std::string idx = request::findValue(&req, "idx");
				if (idx.empty())
					return;
				root["status"] = "OK";
				root["title"] = "DeleteFloorplanPlan";
				m_sql.safe_query("UPDATE DeviceToPlansMap SET XOffset=0,YOffset=0 WHERE (PlanID == '%q')", idx.c_str());
				_log.Log(LOG_STATUS, "(Floorplan) Device coordinates reset for plan '%s'.", idx.c_str());
				m_sql.safe_query("UPDATE Plans SET FloorplanID=0,Area='' WHERE (ID == '%q')", idx.c_str());
				_log.Log(LOG_STATUS, "(Floorplan) Plan '%s' floorplan data reset.", idx.c_str());
			}
		}

		void CWebServer::DisplaySwitchTypesCombo(std::string &content_part)
		{
			char szTmp[200];

			std::map<std::string, int> _switchtypes;

			for (int ii = 0; ii < STYPE_END; ii++)
			{
				_switchtypes[Switch_Type_Desc((_eSwitchType)ii)] = ii;
			}
			// return a sorted list
			for (const auto &type : _switchtypes)
			{
				sprintf(szTmp, "<option value=\"%d\">%s</option>\n", type.second, type.first.c_str());
				content_part += szTmp;
			}
		}

		void CWebServer::DisplayMeterTypesCombo(std::string &content_part)
		{
			char szTmp[200];
			for (int ii = 0; ii < MTYPE_END; ii++)
			{
				sprintf(szTmp, "<option value=\"%d\">%s</option>\n", ii, Meter_Type_Desc((_eMeterType)ii));
				content_part += szTmp;
			}
		}

		void CWebServer::DisplayLanguageCombo(std::string &content_part)
		{
			// return a sorted list
			std::map<std::string, std::string> _ltypes;
			char szTmp[200];
			for (auto &lang : guiLanguage)
			{
				_ltypes[lang.second] = lang.first;
			}
			for (const auto &type : _ltypes)
			{
				sprintf(szTmp, "<option value=\"%s\">%s</option>\n", type.second.c_str(), type.first.c_str());
				content_part += szTmp;
			}
		}

		void CWebServer::DisplayTimerTypesCombo(std::string &content_part)
		{
			char szTmp[200];
			for (int ii = 0; ii < TTYPE_END; ii++)
			{
				sprintf(szTmp, "<option data-i18n=\"%s\" value=\"%d\">%s</option>\n", Timer_Type_Desc(ii), ii, Timer_Type_Desc(ii));
				content_part += szTmp;
			}
		}

		void CWebServer::LoadUsers()
		{
			ClearUserPasswords();
			std::string WebUserName, WebPassword;
			int nValue = 0;
			if (m_sql.GetPreferencesVar("WebUserName", nValue, WebUserName))
			{
				if (m_sql.GetPreferencesVar("WebPassword", nValue, WebPassword))
				{
					if ((!WebUserName.empty()) && (!WebPassword.empty()))
					{
						WebUserName = base64_decode(WebUserName);
						// WebPassword = WebPassword;
						AddUser(10000, WebUserName, WebPassword, URIGHTS_ADMIN, 0xFFFF);

						std::vector<std::vector<std::string>> result;
						result = m_sql.safe_query("SELECT ID, Active, Username, Password, Rights, TabsEnabled FROM Users");
						if (!result.empty())
						{
							for (const auto &sd : result)
							{
								int bIsActive = static_cast<int>(atoi(sd[1].c_str()));
								if (bIsActive)
								{
									unsigned long ID = (unsigned long)atol(sd[0].c_str());

									std::string username = base64_decode(sd[2]);
									std::string password = sd[3];

									_eUserRights rights = (_eUserRights)atoi(sd[4].c_str());
									int activetabs = atoi(sd[5].c_str());

									AddUser(ID, username, password, rights, activetabs);
								}
							}
						}
					}
				}
			}
			m_mainworker.LoadSharedUsers();
		}

		void CWebServer::AddUser(const unsigned long ID, const std::string &username, const std::string &password, const int userrights, const int activetabs)
		{
			std::vector<std::vector<std::string>> result = m_sql.safe_query("SELECT COUNT(*) FROM SharedDevices WHERE (SharedUserID == '%d')", ID);
			if (result.empty())
				return;

			_tWebUserPassword wtmp;
			wtmp.ID = ID;
			wtmp.Username = username;
			wtmp.Password = password;
			wtmp.userrights = (_eUserRights)userrights;
			wtmp.ActiveTabs = activetabs;
			wtmp.TotSensors = atoi(result[0][0].c_str());
			m_users.push_back(wtmp);

			m_pWebEm->AddUserPassword(ID, username, password, (_eUserRights)userrights, activetabs);
		}

		void CWebServer::ClearUserPasswords()
		{
			m_users.clear();
			m_pWebEm->ClearUserPasswords();
		}

		int CWebServer::FindUser(const char *szUserName)
		{
			int iUser = 0;
			for (const auto &user : m_users)
			{
				if (user.Username == szUserName)
					return iUser;
				iUser++;
			}
			return -1;
		}

		bool CWebServer::FindAdminUser()
		{
			return std::any_of(m_users.begin(), m_users.end(), [](const _tWebUserPassword &user) { return user.userrights == URIGHTS_ADMIN; });
		}

		// Depricated : This 'page' should not be used anymore. Use command instead
		void CWebServer::PostSettings(WebEmSession &session, const request &req, reply &rep)
		{
			_log.Log(LOG_NORM, "Depricated: Page StoreSettings! Use command instead!");

			Json::Value root;
			Cmd_PostSettings(session, req, root);

			std::string jcallback = request::findValue(&req, "jsoncallback");
			if (jcallback.empty())
			{
				reply::set_content(&rep, root.toStyledString());
				return;
			}
			reply::set_content(&rep, "var data=" + root.toStyledString() + '\n' + jcallback + "(data);");
		}

		// PostSettings
		void CWebServer::Cmd_PostSettings(WebEmSession &session, const request &req, Json::Value &root)
		{
			if (session.rights != 2)
			{
				session.reply_status = reply::forbidden;
				return; // Only admin user allowed
			}

			root["title"] = "StoreSettings";
			root["status"] = "ERR";

			uint16_t cntSettings = 0;

			try {

				/* Start processing the simple ones */
				/* -------------------------------- */

				m_sql.UpdatePreferencesVar("DashboardType", atoi(request::findValue(&req, "DashboardType").c_str())); cntSettings++;
				m_sql.UpdatePreferencesVar("MobileType", atoi(request::findValue(&req, "MobileType").c_str())); cntSettings++;
				m_sql.UpdatePreferencesVar("ReleaseChannel", atoi(request::findValue(&req, "ReleaseChannel").c_str())); cntSettings++;
				m_sql.UpdatePreferencesVar("LightHistoryDays", atoi(request::findValue(&req, "LightHistoryDays").c_str())); cntSettings++;
				m_sql.UpdatePreferencesVar("5MinuteHistoryDays", atoi(request::findValue(&req, "ShortLogDays").c_str())); cntSettings++;
				m_sql.UpdatePreferencesVar("ElectricVoltage", atoi(request::findValue(&req, "ElectricVoltage").c_str())); cntSettings++;
				m_sql.UpdatePreferencesVar("CM113DisplayType", atoi(request::findValue(&req, "CM113DisplayType").c_str())); cntSettings++;
				m_sql.UpdatePreferencesVar("MaxElectricPower", atoi(request::findValue(&req, "MaxElectricPower").c_str())); cntSettings++;
				m_sql.UpdatePreferencesVar("DoorbellCommand", atoi(request::findValue(&req, "DoorbellCommand").c_str())); cntSettings++;
				m_sql.UpdatePreferencesVar("SmartMeterType", atoi(request::findValue(&req, "SmartMeterType").c_str())); cntSettings++;
				m_sql.UpdatePreferencesVar("SecOnDelay", atoi(request::findValue(&req, "SecOnDelay").c_str())); cntSettings++;
				m_sql.UpdatePreferencesVar("FloorplanPopupDelay", atoi(request::findValue(&req, "FloorplanPopupDelay").c_str())); cntSettings++;
				m_sql.UpdatePreferencesVar("FloorplanActiveOpacity", atoi(request::findValue(&req, "FloorplanActiveOpacity").c_str())); cntSettings++;
				m_sql.UpdatePreferencesVar("FloorplanInactiveOpacity", atoi(request::findValue(&req, "FloorplanInactiveOpacity").c_str())); cntSettings++;
				m_sql.UpdatePreferencesVar("OneWireSensorPollPeriod", atoi(request::findValue(&req, "OneWireSensorPollPeriod").c_str())); cntSettings++;
				m_sql.UpdatePreferencesVar("OneWireSwitchPollPeriod", atoi(request::findValue(&req, "OneWireSwitchPollPeriod").c_str())); cntSettings++;

				m_sql.UpdatePreferencesVar("UseAutoUpdate", (request::findValue(&req, "checkforupdates") == "on" ? 1 : 0)); cntSettings++;
				m_sql.UpdatePreferencesVar("UseAutoBackup", (request::findValue(&req, "enableautobackup") == "on" ? 1 : 0)); cntSettings++;
				m_sql.UpdatePreferencesVar("EnableTabFloorplans", (request::findValue(&req, "EnableTabFloorplans") == "on" ? 1 : 0)); cntSettings++;
				m_sql.UpdatePreferencesVar("EnableTabLights", (request::findValue(&req, "EnableTabLights") == "on" ? 1 : 0)); cntSettings++;
				m_sql.UpdatePreferencesVar("EnableTabTemp", (request::findValue(&req, "EnableTabTemp") == "on" ? 1 : 0)); cntSettings++;
				m_sql.UpdatePreferencesVar("EnableTabWeather", (request::findValue(&req, "EnableTabWeather") == "on" ? 1 : 0)); cntSettings++;
				m_sql.UpdatePreferencesVar("EnableTabUtility", (request::findValue(&req, "EnableTabUtility") == "on" ? 1 : 0)); cntSettings++;
				m_sql.UpdatePreferencesVar("EnableTabScenes", (request::findValue(&req, "EnableTabScenes") == "on" ? 1 : 0)); cntSettings++;
				m_sql.UpdatePreferencesVar("EnableTabCustom", (request::findValue(&req, "EnableTabCustom") == "on" ? 1 : 0)); cntSettings++;
				m_sql.UpdatePreferencesVar("HideDisabledHardwareSensors", (request::findValue(&req, "HideDisabledHardwareSensors") == "on" ? 1 : 0)); cntSettings++;
				m_sql.UpdatePreferencesVar("ShowUpdateEffect", (request::findValue(&req, "ShowUpdateEffect") == "on" ? 1 : 0)); cntSettings++;
				m_sql.UpdatePreferencesVar("FloorplanFullscreenMode", (request::findValue(&req, "FloorplanFullscreenMode") == "on" ? 1 : 0)); cntSettings++;
				m_sql.UpdatePreferencesVar("FloorplanAnimateZoom", (request::findValue(&req, "FloorplanAnimateZoom") == "on" ? 1 : 0)); cntSettings++;
				m_sql.UpdatePreferencesVar("FloorplanShowSensorValues", (request::findValue(&req, "FloorplanShowSensorValues") == "on" ? 1 : 0)); cntSettings++;
				m_sql.UpdatePreferencesVar("FloorplanShowSwitchValues", (request::findValue(&req, "FloorplanShowSwitchValues") == "on" ? 1 : 0)); cntSettings++;
				m_sql.UpdatePreferencesVar("FloorplanShowSceneNames", (request::findValue(&req, "FloorplanShowSwitchValues") == "on" ? 1 : 0)); cntSettings++;
				m_sql.UpdatePreferencesVar("IFTTTEnabled", (request::findValue(&req, "IFTTTEnabled") == "on" ? 1 : 0)); cntSettings++;

				m_sql.UpdatePreferencesVar("Language", request::findValue(&req, "Language")); cntSettings++;
				m_sql.UpdatePreferencesVar("DegreeDaysBaseTemperature", request::findValue(&req, "DegreeDaysBaseTemperature")); cntSettings++;

				m_sql.UpdatePreferencesVar("FloorplanRoomColour", CURLEncode::URLDecode(request::findValue(&req, "FloorplanRoomColour"))); cntSettings++;
				m_sql.UpdatePreferencesVar("IFTTTAPI", base64_encode(request::findValue(&req, "IFTTTAPI"))); cntSettings++;

				m_sql.UpdatePreferencesVar("Title", (request::findValue(&req, "Title").empty()) ? "Domoticz" : request::findValue(&req, "Title")); cntSettings++;

				/* More complex ones that need additional processing */
				/* ------------------------------------------------- */

				float CostEnergy = static_cast<float>(atof(request::findValue(&req, "CostEnergy").c_str()));
				m_sql.UpdatePreferencesVar("CostEnergy", int(CostEnergy * 10000.0F)); cntSettings++;
				float CostEnergyT2 = static_cast<float>(atof(request::findValue(&req, "CostEnergyT2").c_str()));
				m_sql.UpdatePreferencesVar("CostEnergyT2", int(CostEnergyT2 * 10000.0F)); cntSettings++;
				float CostEnergyR1 = static_cast<float>(atof(request::findValue(&req, "CostEnergyR1").c_str()));
				m_sql.UpdatePreferencesVar("CostEnergyR1", int(CostEnergyR1 * 10000.0F)); cntSettings++;
				float CostEnergyR2 = static_cast<float>(atof(request::findValue(&req, "CostEnergyR2").c_str()));
				m_sql.UpdatePreferencesVar("CostEnergyR2", int(CostEnergyR2 * 10000.0F)); cntSettings++;
				float CostGas = static_cast<float>(atof(request::findValue(&req, "CostGas").c_str()));
				m_sql.UpdatePreferencesVar("CostGas", int(CostGas * 10000.0F)); cntSettings++;
				float CostWater = static_cast<float>(atof(request::findValue(&req, "CostWater").c_str()));
				m_sql.UpdatePreferencesVar("CostWater", int(CostWater * 10000.0F)); cntSettings++;

				int EnergyDivider = atoi(request::findValue(&req, "EnergyDivider").c_str());
				if (EnergyDivider < 1)
					EnergyDivider = 1000;
				m_sql.UpdatePreferencesVar("MeterDividerEnergy", EnergyDivider); cntSettings++;
				int GasDivider = atoi(request::findValue(&req, "GasDivider").c_str());
				if (GasDivider < 1)
					GasDivider = 100;
				m_sql.UpdatePreferencesVar("MeterDividerGas", GasDivider); cntSettings++;
				int WaterDivider = atoi(request::findValue(&req, "WaterDivider").c_str());
				if (WaterDivider < 1)
					WaterDivider = 100;
				m_sql.UpdatePreferencesVar("MeterDividerWater", WaterDivider); cntSettings++;

				int sensortimeout = atoi(request::findValue(&req, "SensorTimeout").c_str());
				if (sensortimeout < 10)
					sensortimeout = 10;
				m_sql.UpdatePreferencesVar("SensorTimeout", sensortimeout); cntSettings++;

				std::string RaspCamParams = request::findValue(&req, "RaspCamParams");
				if ((!RaspCamParams.empty()) && (IsArgumentSecure(RaspCamParams)))
					m_sql.UpdatePreferencesVar("RaspCamParams", RaspCamParams);
				cntSettings++;

				std::string UVCParams = request::findValue(&req, "UVCParams");
				if ((!UVCParams.empty()) && (IsArgumentSecure(UVCParams)))
					m_sql.UpdatePreferencesVar("UVCParams", UVCParams);
				cntSettings++;

				/* Also update m_sql.variables */
				/* --------------------------- */

				int iShortLogInterval = atoi(request::findValue(&req, "ShortLogInterval").c_str());
				if (iShortLogInterval < 1)
					iShortLogInterval = 5;
				m_sql.m_ShortLogInterval = iShortLogInterval;
				m_sql.UpdatePreferencesVar("ShortLogInterval", m_sql.m_ShortLogInterval); cntSettings++;

				m_sql.m_bLogEventScriptTrigger = (request::findValue(&req, "LogEventScriptTrigger") == "on" ? 1 : 0);
				m_sql.UpdatePreferencesVar("LogEventScriptTrigger", m_sql.m_bLogEventScriptTrigger); cntSettings++;

				m_sql.m_bAllowWidgetOrdering = (request::findValue(&req, "AllowWidgetOrdering") == "on" ? 1 : 0);
				m_sql.UpdatePreferencesVar("AllowWidgetOrdering", m_sql.m_bAllowWidgetOrdering); cntSettings++;

				int iEnableNewHardware = (request::findValue(&req, "AcceptNewHardware") == "on" ? 1 : 0);
				m_sql.m_bAcceptNewHardware = (iEnableNewHardware == 1);
				m_sql.UpdatePreferencesVar("AcceptNewHardware", m_sql.m_bAcceptNewHardware); cntSettings++;

				int nUnit = atoi(request::findValue(&req, "WindUnit").c_str());
				m_sql.m_windunit = (_eWindUnit)nUnit;
				m_sql.UpdatePreferencesVar("WindUnit", m_sql.m_windunit); cntSettings++;

				nUnit = atoi(request::findValue(&req, "TempUnit").c_str());
				m_sql.m_tempunit = (_eTempUnit)nUnit;
				m_sql.UpdatePreferencesVar("TempUnit", m_sql.m_tempunit); cntSettings++;

				nUnit = atoi(request::findValue(&req, "WeightUnit").c_str());
				m_sql.m_weightunit = (_eWeightUnit)nUnit;
				m_sql.UpdatePreferencesVar("WeightUnit", m_sql.m_weightunit); cntSettings++;

				m_sql.SetUnitsAndScale();

				/* Update Preferences and call other functions as well due to changes */
				/* ------------------------------------------------------------------ */

				std::string Latitude = request::findValue(&req, "Latitude");
				std::string Longitude = request::findValue(&req, "Longitude");
				if ((!Latitude.empty()) && (!Longitude.empty()))
				{
					std::string LatLong = Latitude + ";" + Longitude;
					m_sql.UpdatePreferencesVar("Location", LatLong);
					m_mainworker.GetSunSettings();
				}
				cntSettings++;

				std::string AuthenticationMethod = request::findValue(&req, "AuthenticationMethod");
				_eAuthenticationMethod amethod = (_eAuthenticationMethod)atoi(AuthenticationMethod.c_str());
				m_sql.UpdatePreferencesVar("AuthenticationMethod", static_cast<int>(amethod));
				m_pWebEm->SetAuthenticationMethod(amethod);
				cntSettings++;

				// Check new and get old username/password
				std::string WebUserName = base64_encode(CURLEncode::URLDecode(request::findValue(&req, "WebUserName")));
				std::string WebPassword = CURLEncode::URLDecode(request::findValue(&req, "WebPassword"));

				std::string sOldWebLogin;
				std::string sOldWebPassword;
				m_sql.GetPreferencesVar("WebUserName", sOldWebLogin);
				m_sql.GetPreferencesVar("WebPassword", sOldWebPassword);

				bool bHaveAdminUserPasswordChange = false;

				if ((WebUserName == sOldWebLogin) && (WebPassword.empty()))
				{
					// All is OK, no changes
				}
				else if (WebUserName.empty() || WebPassword.empty())
				{
					// If no Admin User/Password is specified, we clear them
					if ((!sOldWebLogin.empty()) || (!sOldWebPassword.empty()))
						bHaveAdminUserPasswordChange = true;
					WebUserName = "";
					WebPassword = "";
				}
				else
				{
					if ((WebUserName != sOldWebLogin) || (WebPassword != sOldWebPassword))
					{
						bHaveAdminUserPasswordChange = true;
					}
				}

				// Invalid sessions of WebUser when the username or password has been changed
				if (bHaveAdminUserPasswordChange)
				{
					if (!sOldWebLogin.empty())
						RemoveUsersSessions(sOldWebLogin, session);
					m_sql.UpdatePreferencesVar("WebUserName", WebUserName);
					m_sql.UpdatePreferencesVar("WebPassword", WebPassword);
				}
				m_webservers.LoadUsers();
				cntSettings++;
				cntSettings++;

				std::string WebLocalNetworks = CURLEncode::URLDecode(request::findValue(&req, "WebLocalNetworks"));
				std::string WebRemoteProxyIPs = CURLEncode::URLDecode(request::findValue(&req, "WebRemoteProxyIPs"));
				m_sql.UpdatePreferencesVar("WebLocalNetworks", WebLocalNetworks);
				m_sql.UpdatePreferencesVar("WebRemoteProxyIPs", WebRemoteProxyIPs);

				m_webservers.ReloadLocalNetworksAndProxyIPs();
				cntSettings++;
				cntSettings++;

				if (session.username.empty())
				{
					// Local network could be changed so lets force a check here
					session.rights = -1;
				}

				std::string SecPassword = request::findValue(&req, "SecPassword");
				SecPassword = CURLEncode::URLDecode(SecPassword);
				if (SecPassword.size() != 32)
				{
					SecPassword = GenerateMD5Hash(SecPassword);
				}
				m_sql.UpdatePreferencesVar("SecPassword", SecPassword);
				cntSettings++;

				std::string ProtectionPassword = request::findValue(&req, "ProtectionPassword");
				ProtectionPassword = CURLEncode::URLDecode(ProtectionPassword);
				if (ProtectionPassword.size() != 32)
				{
					ProtectionPassword = GenerateMD5Hash(ProtectionPassword);
				}
				m_sql.UpdatePreferencesVar("ProtectionPassword", ProtectionPassword);
				cntSettings++;

				std::string SendErrorsAsNotification = request::findValue(&req, "SendErrorsAsNotification");
				int iSendErrorsAsNotification = (SendErrorsAsNotification == "on" ? 1 : 0);
				m_sql.UpdatePreferencesVar("SendErrorsAsNotification", iSendErrorsAsNotification);
				_log.ForwardErrorsToNotificationSystem(iSendErrorsAsNotification != 0);
				cntSettings++;

				int rnOldvalue = 0;
				int rnvalue = 0;

				m_sql.GetPreferencesVar("ActiveTimerPlan", rnOldvalue);
				rnvalue = atoi(request::findValue(&req, "ActiveTimerPlan").c_str());
				if (rnOldvalue != rnvalue)
				{
					m_sql.UpdatePreferencesVar("ActiveTimerPlan", rnvalue);
					m_sql.m_ActiveTimerPlan = rnvalue;
					m_mainworker.m_scheduler.ReloadSchedules();
				}
				cntSettings++;

				rnOldvalue = 0;
				m_sql.GetPreferencesVar("NotificationSensorInterval", rnOldvalue);
				rnvalue = atoi(request::findValue(&req, "NotificationSensorInterval").c_str());
				if (rnOldvalue != rnvalue)
				{
					m_sql.UpdatePreferencesVar("NotificationSensorInterval", rnvalue);
					m_notifications.ReloadNotifications();
				}
				cntSettings++;

				rnOldvalue = 0;
				m_sql.GetPreferencesVar("NotificationSwitchInterval", rnOldvalue);
				rnvalue = atoi(request::findValue(&req, "NotificationSwitchInterval").c_str());
				if (rnOldvalue != rnvalue)
				{
					m_sql.UpdatePreferencesVar("NotificationSwitchInterval", rnvalue);
					m_notifications.ReloadNotifications();
				}
				cntSettings++;

				rnOldvalue = 0;
				m_sql.GetPreferencesVar("EnableEventScriptSystem", rnOldvalue);
				std::string EnableEventScriptSystem = request::findValue(&req, "EnableEventScriptSystem");
				int iEnableEventScriptSystem = (EnableEventScriptSystem == "on" ? 1 : 0);
				m_sql.UpdatePreferencesVar("EnableEventScriptSystem", iEnableEventScriptSystem);
				m_sql.m_bEnableEventSystem = (iEnableEventScriptSystem == 1);
				if (iEnableEventScriptSystem != rnOldvalue)
				{
					m_mainworker.m_eventsystem.SetEnabled(m_sql.m_bEnableEventSystem);
					m_mainworker.m_eventsystem.StartEventSystem();
				}
				cntSettings++;

				std::string EnableEventSystemFullURLLog = request::findValue(&req, "EventSystemLogFullURL");
				m_sql.m_bEnableEventSystemFullURLLog = EnableEventSystemFullURLLog == "on" ? true : false;
				m_sql.UpdatePreferencesVar("EventSystemLogFullURL", (int)m_sql.m_bEnableEventSystemFullURLLog);
				cntSettings++;

				rnOldvalue = 0;
				m_sql.GetPreferencesVar("DisableDzVentsSystem", rnOldvalue);
				std::string DisableDzVentsSystem = request::findValue(&req, "DisableDzVentsSystem");
				int iDisableDzVentsSystem = (DisableDzVentsSystem == "on" ? 0 : 1);
				m_sql.UpdatePreferencesVar("DisableDzVentsSystem", iDisableDzVentsSystem);
				m_sql.m_bDisableDzVentsSystem = (iDisableDzVentsSystem == 1);
				if (m_sql.m_bEnableEventSystem && !iDisableDzVentsSystem && iDisableDzVentsSystem != rnOldvalue)
				{
					m_mainworker.m_eventsystem.LoadEvents();
					m_mainworker.m_eventsystem.GetCurrentStates();
				}
				cntSettings++;
				m_sql.UpdatePreferencesVar("DzVentsLogLevel", atoi(request::findValue(&req, "DzVentsLogLevel").c_str()));
				cntSettings++;

				rnOldvalue = 0;
				m_sql.GetPreferencesVar("RemoteSharedPort", rnOldvalue);
				m_sql.UpdatePreferencesVar("RemoteSharedPort", atoi(request::findValue(&req, "RemoteSharedPort").c_str()));
				m_sql.GetPreferencesVar("RemoteSharedPort", rnvalue);

				if (rnvalue != rnOldvalue)
				{
					m_mainworker.m_sharedserver.StopServer();
					if (rnvalue != 0)
					{
						char szPort[100];
						sprintf(szPort, "%d", rnvalue);
						m_mainworker.m_sharedserver.StartServer("::", szPort);
						m_mainworker.LoadSharedUsers();
					}
				}
				cntSettings++;

				rnOldvalue = 0;
				m_sql.GetPreferencesVar("RandomTimerFrame", rnOldvalue);
				rnvalue = atoi(request::findValue(&req, "RandomSpread").c_str());
				if (rnOldvalue != rnvalue)
				{
					m_sql.UpdatePreferencesVar("RandomTimerFrame", rnvalue);
					m_mainworker.m_scheduler.ReloadSchedules();
				}
				cntSettings++;

				rnOldvalue = 0;
				int batterylowlevel = atoi(request::findValue(&req, "BatterLowLevel").c_str());
				if (batterylowlevel > 100)
					batterylowlevel = 100;
				m_sql.GetPreferencesVar("BatteryLowNotification", rnOldvalue);
				m_sql.UpdatePreferencesVar("BatteryLowNotification", batterylowlevel);
				if ((rnOldvalue != batterylowlevel) && (batterylowlevel != 0))
					m_sql.CheckBatteryLow();
				cntSettings++;

				/* Update the Theme */

				std::string SelectedTheme = request::findValue(&req, "Themes");
				m_sql.UpdatePreferencesVar("WebTheme", SelectedTheme);
				m_pWebEm->SetWebTheme(SelectedTheme);
				cntSettings++;

#ifndef NOCLOUD
				std::string md_userid, md_password, pf_userid, pf_password;
				int md_subsystems, pf_subsystems;
				m_sql.GetPreferencesVar("MyDomoticzUserId", pf_userid);
				m_sql.GetPreferencesVar("MyDomoticzPassword", pf_password);
				m_sql.GetPreferencesVar("MyDomoticzSubsystems", pf_subsystems);
				md_userid = CURLEncode::URLDecode(request::findValue(&req, "MyDomoticzUserId"));
				md_password = CURLEncode::URLDecode(request::findValue(&req, "MyDomoticzPassword"));
				md_subsystems = (request::findValue(&req, "SubsystemHttp").empty() ? 0 : 1) + (request::findValue(&req, "SubsystemShared").empty() ? 0 : 2) +
						(request::findValue(&req, "SubsystemApps").empty() ? 0 : 4);
				if (md_userid != pf_userid || md_password != pf_password || md_subsystems != pf_subsystems)
				{
					m_sql.UpdatePreferencesVar("MyDomoticzUserId", md_userid);
					if (md_password != pf_password)
					{
						md_password = base64_encode(md_password);
						m_sql.UpdatePreferencesVar("MyDomoticzPassword", md_password);
					}
					m_sql.UpdatePreferencesVar("MyDomoticzSubsystems", md_subsystems);
					m_webservers.RestartProxy();
				}
				cntSettings++;
				cntSettings++;
				cntSettings++;
#endif
				/* To wrap up everything */
				m_notifications.ConfigFromGetvars(req, true);
				m_notifications.LoadConfig();

#ifdef ENABLE_PYTHON
				// Signal plugins to update Settings dictionary
				PluginLoadConfig();
#endif
				root["status"] = "OK";
			}
			catch(const std::exception &e)
			{
				std::stringstream errmsg;
				errmsg << "Error occured during processing of POSTed settings (" << e.what() << ") after processing " << cntSettings << " settings!";
				root["errmsg"] = errmsg.str();
				_log.Log(LOG_ERROR, errmsg.str());
			}
			catch(...)
			{
				std::stringstream errmsg;
				errmsg << "Error occured during processing of POSTed settings after processing " << cntSettings << " settings!";
				root["errmsg"] = errmsg.str();
				_log.Log(LOG_ERROR, errmsg.str());
			}
			std::string msg = "Processed " + std::to_string(cntSettings) + " settings!";
			root["msg"] = msg;
		}

		void CWebServer::RestoreDatabase(WebEmSession &session, const request &req, std::string &redirect_uri)
		{
			redirect_uri = "/index.html";
			if (session.rights != 2)
			{
				session.reply_status = reply::forbidden;
				return; // Only admin user allowed
			}

			std::string dbasefile = request::findValue(&req, "dbasefile");
			if (dbasefile.empty())
			{
				return;
			}

			m_mainworker.StopDomoticzHardware();

			m_sql.RestoreDatabase(dbasefile);
			m_mainworker.AddAllDomoticzHardware();
		}

		struct _tHardwareListInt
		{
			std::string Name;
			int HardwareTypeVal;
			std::string HardwareType;
			bool Enabled;
			std::string Mode1; // Used to flag DimmerType as relative for some old LimitLessLight type bulbs
			std::string Mode2; // Used to flag DimmerType as relative for some old LimitLessLight type bulbs
		} tHardwareList;

		void CWebServer::GetJSonDevices(Json::Value &root, const std::string &rused, const std::string &rfilter, const std::string &order, const std::string &rowid, const std::string &planID,
						const std::string &floorID, const bool bDisplayHidden, const bool bDisplayDisabled, const bool bFetchFavorites, const time_t LastUpdate,
						const std::string &username, const std::string &hardwareid)
		{
			std::vector<std::vector<std::string>> result;

			time_t now = mytime(nullptr);
			struct tm tm1;
			localtime_r(&now, &tm1);
			struct tm tLastUpdate;
			localtime_r(&now, &tLastUpdate);

			const time_t iLastUpdate = LastUpdate - 1;

			int SensorTimeOut = 60;
			m_sql.GetPreferencesVar("SensorTimeout", SensorTimeOut);

			// Get All Hardware ID's/Names, need them later
			std::map<int, _tHardwareListInt> _hardwareNames;
			result = m_sql.safe_query("SELECT ID, Name, Enabled, Type, Mode1, Mode2 FROM Hardware");
			if (!result.empty())
			{
				for (const auto &sd : result)
				{
					_tHardwareListInt tlist;
					int ID = atoi(sd[0].c_str());
					tlist.Name = sd[1];
					tlist.Enabled = (atoi(sd[2].c_str()) != 0);
					tlist.HardwareTypeVal = atoi(sd[3].c_str());
#ifndef ENABLE_PYTHON
					tlist.HardwareType = Hardware_Type_Desc(tlist.HardwareTypeVal);
#else
					if (tlist.HardwareTypeVal != HTYPE_PythonPlugin)
					{
						tlist.HardwareType = Hardware_Type_Desc(tlist.HardwareTypeVal);
					}
					else
					{
						tlist.HardwareType = PluginHardwareDesc(ID);
					}
#endif
					tlist.Mode1 = sd[4];
					tlist.Mode2 = sd[5];
					_hardwareNames[ID] = tlist;
				}
			}

			root["ActTime"] = static_cast<int>(now);

			char szTmp[300];

			if (!m_mainworker.m_LastSunriseSet.empty())
			{
				std::vector<std::string> strarray;
				StringSplit(m_mainworker.m_LastSunriseSet, ";", strarray);
				if (strarray.size() == 10)
				{
					// strftime(szTmp, 80, "%b %d %Y %X", &tm1);
					strftime(szTmp, 80, "%Y-%m-%d %X", &tm1);
					root["ServerTime"] = szTmp;
					root["Sunrise"] = strarray[0];
					root["Sunset"] = strarray[1];
					root["SunAtSouth"] = strarray[2];
					root["CivTwilightStart"] = strarray[3];
					root["CivTwilightEnd"] = strarray[4];
					root["NautTwilightStart"] = strarray[5];
					root["NautTwilightEnd"] = strarray[6];
					root["AstrTwilightStart"] = strarray[7];
					root["AstrTwilightEnd"] = strarray[8];
					root["DayLength"] = strarray[9];
				}
			}

			char szOrderBy[50];
			std::string szQuery;
			bool isAlpha = true;
			const std::string orderBy = order;
			for (char i : orderBy)
			{
				if (!isalpha(i))
				{
					isAlpha = false;
				}
			}
			if (order.empty() || (!isAlpha))
			{
				strcpy(szOrderBy, "A.[Order],A.LastUpdate DESC");
			}
			else
			{
				sprintf(szOrderBy, "A.[Order],A.%%s ASC");
			}

			unsigned char tempsign = m_sql.m_tempsign[0];

			bool bHaveUser = false;
			int iUser = -1;
			unsigned int totUserDevices = 0;
			bool bShowScenes = true;
			bHaveUser = (!username.empty());
			if (bHaveUser)
			{
				iUser = FindUser(username.c_str());
				if (iUser != -1)
				{
					_eUserRights urights = m_users[iUser].userrights;
					if (urights != URIGHTS_ADMIN)
					{
						result = m_sql.safe_query("SELECT COUNT(*) FROM SharedDevices WHERE (SharedUserID == %lu)", m_users[iUser].ID);
						if (!result.empty())
						{
							totUserDevices = (unsigned int)std::stoi(result[0][0]);
						}
						bShowScenes = (m_users[iUser].ActiveTabs & (1 << 1)) != 0;
					}
				}
			}

			std::set<std::string> _HiddenDevices;
			bool bAllowDeviceToBeHidden = false;

			int ii = 0;
			if (rfilter == "all")
			{
				if ((bShowScenes) && ((rused == "all") || (rused == "true")))
				{
					// add scenes
					if (!rowid.empty())
						result = m_sql.safe_query("SELECT A.ID, A.Name, A.nValue, A.LastUpdate, A.Favorite, A.SceneType,"
									  " A.Protected, B.XOffset, B.YOffset, B.PlanID, A.Description"
									  " FROM Scenes as A"
									  " LEFT OUTER JOIN DeviceToPlansMap as B ON (B.DeviceRowID==a.ID) AND (B.DevSceneType==1)"
									  " WHERE (A.ID=='%q')",
									  rowid.c_str());
					else if ((!planID.empty()) && (planID != "0"))
						result = m_sql.safe_query("SELECT A.ID, A.Name, A.nValue, A.LastUpdate, A.Favorite, A.SceneType,"
									  " A.Protected, B.XOffset, B.YOffset, B.PlanID, A.Description"
									  " FROM Scenes as A, DeviceToPlansMap as B WHERE (B.PlanID=='%q')"
									  " AND (B.DeviceRowID==a.ID) AND (B.DevSceneType==1) ORDER BY B.[Order]",
									  planID.c_str());
					else if ((!floorID.empty()) && (floorID != "0"))
						result = m_sql.safe_query("SELECT A.ID, A.Name, A.nValue, A.LastUpdate, A.Favorite, A.SceneType,"
									  " A.Protected, B.XOffset, B.YOffset, B.PlanID, A.Description"
									  " FROM Scenes as A, DeviceToPlansMap as B, Plans as C"
									  " WHERE (C.FloorplanID=='%q') AND (C.ID==B.PlanID) AND (B.DeviceRowID==a.ID)"
									  " AND (B.DevSceneType==1) ORDER BY B.[Order]",
									  floorID.c_str());
					else
					{
						szQuery = ("SELECT A.ID, A.Name, A.nValue, A.LastUpdate, A.Favorite, A.SceneType,"
							   " A.Protected, B.XOffset, B.YOffset, B.PlanID, A.Description"
							   " FROM Scenes as A"
							   " LEFT OUTER JOIN DeviceToPlansMap as B ON (B.DeviceRowID==a.ID) AND (B.DevSceneType==1)"
							   " ORDER BY ");
						szQuery += szOrderBy;
						result = m_sql.safe_query(szQuery.c_str(), order.c_str());
					}

					if (!result.empty())
					{
						for (const auto &sd : result)
						{
							unsigned char favorite = atoi(sd[4].c_str());
							// Check if we only want favorite devices
							if ((bFetchFavorites) && (!favorite))
								continue;

							std::string sLastUpdate = sd[3];

							if (iLastUpdate != 0)
							{
								time_t cLastUpdate;
								ParseSQLdatetime(cLastUpdate, tLastUpdate, sLastUpdate, tm1.tm_isdst);
								if (cLastUpdate <= iLastUpdate)
									continue;
							}

							int nValue = atoi(sd[2].c_str());

							unsigned char scenetype = atoi(sd[5].c_str());
							int iProtected = atoi(sd[6].c_str());

							std::string sSceneName = sd[1];
							if (!bDisplayHidden && sSceneName[0] == '$')
							{
								continue;
							}

							if (scenetype == 0)
							{
								root["result"][ii]["Type"] = "Scene";
								root["result"][ii]["TypeImg"] = "scene";
								root["result"][ii]["Image"] = "Push";
							}
							else
							{
								root["result"][ii]["Type"] = "Group";
								root["result"][ii]["TypeImg"] = "group";
							}

							// has this scene/group already been seen, now with different plan?
							// assume results are ordered such that same device is adjacent
							// if the idx and the Type are equal (type to prevent matching against Scene with same idx)
							std::string thisIdx = sd[0];

							if ((ii > 0) && thisIdx == root["result"][ii - 1]["idx"].asString())
							{
								std::string typeOfThisOne = root["result"][ii]["Type"].asString();
								if (typeOfThisOne == root["result"][ii - 1]["Type"].asString())
								{
									root["result"][ii - 1]["PlanIDs"].append(atoi(sd[9].c_str()));
									continue;
								}
							}

							root["result"][ii]["idx"] = sd[0];
							root["result"][ii]["Name"] = sSceneName;
							root["result"][ii]["Description"] = sd[10];
							root["result"][ii]["Favorite"] = favorite;
							root["result"][ii]["Protected"] = (iProtected != 0);
							root["result"][ii]["LastUpdate"] = sLastUpdate;
							root["result"][ii]["PlanID"] = sd[9].c_str();
							Json::Value jsonArray;
							jsonArray.append(atoi(sd[9].c_str()));
							root["result"][ii]["PlanIDs"] = jsonArray;

							if (nValue == 0)
								root["result"][ii]["Status"] = "Off";
							else if (nValue == 1)
								root["result"][ii]["Status"] = "On";
							else
								root["result"][ii]["Status"] = "Mixed";
							root["result"][ii]["Data"] = root["result"][ii]["Status"];
							uint64_t camIDX = m_mainworker.m_cameras.IsDevSceneInCamera(1, sd[0]);
							root["result"][ii]["UsedByCamera"] = (camIDX != 0) ? true : false;
							if (camIDX != 0)
							{
								std::stringstream scidx;
								scidx << camIDX;
								root["result"][ii]["CameraIdx"] = scidx.str();
							}
							root["result"][ii]["XOffset"] = atoi(sd[7].c_str());
							root["result"][ii]["YOffset"] = atoi(sd[8].c_str());
							ii++;
						}
					}
				}
			}

			char szData[320];
			if (totUserDevices == 0)
			{
				// All
				if (!rowid.empty())
				{
					//_log.Log(LOG_STATUS, "Getting device with id: %s", rowid.c_str());
					result = m_sql.safe_query("SELECT A.ID, A.DeviceID, A.Unit, A.Name, A.Used, A.Type, A.SubType,"
								  " A.SignalLevel, A.BatteryLevel, A.nValue, A.sValue,"
								  " A.LastUpdate, A.Favorite, A.SwitchType, A.HardwareID,"
								  " A.AddjValue, A.AddjMulti, A.AddjValue2, A.AddjMulti2,"
								  " A.LastLevel, A.CustomImage, A.StrParam1, A.StrParam2,"
								  " A.Protected, IFNULL(B.XOffset,0), IFNULL(B.YOffset,0), IFNULL(B.PlanID,0), A.Description,"
								  " A.Options, A.Color "
								  "FROM DeviceStatus A LEFT OUTER JOIN DeviceToPlansMap as B ON (B.DeviceRowID==a.ID) "
								  "WHERE (A.ID=='%q')",
								  rowid.c_str());
				}
				else if ((!planID.empty()) && (planID != "0"))
					result = m_sql.safe_query("SELECT A.ID, A.DeviceID, A.Unit, A.Name, A.Used,"
								  " A.Type, A.SubType, A.SignalLevel, A.BatteryLevel,"
								  " A.nValue, A.sValue, A.LastUpdate, A.Favorite,"
								  " A.SwitchType, A.HardwareID, A.AddjValue,"
								  " A.AddjMulti, A.AddjValue2, A.AddjMulti2,"
								  " A.LastLevel, A.CustomImage, A.StrParam1,"
								  " A.StrParam2, A.Protected, B.XOffset, B.YOffset,"
								  " B.PlanID, A.Description,"
								  " A.Options, A.Color "
								  "FROM DeviceStatus as A, DeviceToPlansMap as B "
								  "WHERE (B.PlanID=='%q') AND (B.DeviceRowID==a.ID)"
								  " AND (B.DevSceneType==0) ORDER BY B.[Order]",
								  planID.c_str());
				else if ((!floorID.empty()) && (floorID != "0"))
					result = m_sql.safe_query("SELECT A.ID, A.DeviceID, A.Unit, A.Name, A.Used,"
								  " A.Type, A.SubType, A.SignalLevel, A.BatteryLevel,"
								  " A.nValue, A.sValue, A.LastUpdate, A.Favorite,"
								  " A.SwitchType, A.HardwareID, A.AddjValue,"
								  " A.AddjMulti, A.AddjValue2, A.AddjMulti2,"
								  " A.LastLevel, A.CustomImage, A.StrParam1,"
								  " A.StrParam2, A.Protected, B.XOffset, B.YOffset,"
								  " B.PlanID, A.Description,"
								  " A.Options, A.Color "
								  "FROM DeviceStatus as A, DeviceToPlansMap as B,"
								  " Plans as C "
								  "WHERE (C.FloorplanID=='%q') AND (C.ID==B.PlanID)"
								  " AND (B.DeviceRowID==a.ID) AND (B.DevSceneType==0) "
								  "ORDER BY B.[Order]",
								  floorID.c_str());
				else
				{
					if (!bDisplayHidden)
					{
						// Build a list of Hidden Devices
						result = m_sql.safe_query("SELECT ID FROM Plans WHERE (Name=='$Hidden Devices')");
						if (!result.empty())
						{
							std::string pID = result[0][0];
							result = m_sql.safe_query("SELECT DeviceRowID FROM DeviceToPlansMap WHERE (PlanID=='%q') AND (DevSceneType==0)", pID.c_str());
							if (!result.empty())
							{
								for (const auto &r : result)
								{
									_HiddenDevices.insert(r[0]);
								}
							}
						}
						bAllowDeviceToBeHidden = true;
					}

					if (order.empty() || (!isAlpha))
						strcpy(szOrderBy, "A.[Order],A.LastUpdate DESC");
					else
					{
						sprintf(szOrderBy, "A.[Order],A.%%s ASC");
					}
					//_log.Log(LOG_STATUS, "Getting all devices: order by %s ", szOrderBy);
					if (!hardwareid.empty())
					{
						szQuery = ("SELECT A.ID, A.DeviceID, A.Unit, A.Name, A.Used,A.Type, A.SubType,"
							   " A.SignalLevel, A.BatteryLevel, A.nValue, A.sValue,"
							   " A.LastUpdate, A.Favorite, A.SwitchType, A.HardwareID,"
							   " A.AddjValue, A.AddjMulti, A.AddjValue2, A.AddjMulti2,"
							   " A.LastLevel, A.CustomImage, A.StrParam1, A.StrParam2,"
							   " A.Protected, IFNULL(B.XOffset,0), IFNULL(B.YOffset,0), IFNULL(B.PlanID,0), A.Description,"
							   " A.Options, A.Color "
							   "FROM DeviceStatus as A LEFT OUTER JOIN DeviceToPlansMap as B "
							   "ON (B.DeviceRowID==a.ID) AND (B.DevSceneType==0) "
							   "WHERE (A.HardwareID == %q) "
							   "ORDER BY ");
						szQuery += szOrderBy;
						result = m_sql.safe_query(szQuery.c_str(), hardwareid.c_str(), order.c_str());
					}
					else
					{
						szQuery = ("SELECT A.ID, A.DeviceID, A.Unit, A.Name, A.Used,A.Type, A.SubType,"
							   " A.SignalLevel, A.BatteryLevel, A.nValue, A.sValue,"
							   " A.LastUpdate, A.Favorite, A.SwitchType, A.HardwareID,"
							   " A.AddjValue, A.AddjMulti, A.AddjValue2, A.AddjMulti2,"
							   " A.LastLevel, A.CustomImage, A.StrParam1, A.StrParam2,"
							   " A.Protected, IFNULL(B.XOffset,0), IFNULL(B.YOffset,0), IFNULL(B.PlanID,0), A.Description,"
							   " A.Options, A.Color "
							   "FROM DeviceStatus as A LEFT OUTER JOIN DeviceToPlansMap as B "
							   "ON (B.DeviceRowID==a.ID) AND (B.DevSceneType==0) "
							   "ORDER BY ");
						szQuery += szOrderBy;
						result = m_sql.safe_query(szQuery.c_str(), order.c_str());
					}
				}
			}
			else
			{
				if (iUser == -1)
				{
					return;
				}
				// Specific devices
				if (!rowid.empty())
				{
					//_log.Log(LOG_STATUS, "Getting device with id: %s for user %lu", rowid.c_str(), m_users[iUser].ID);
					result = m_sql.safe_query("SELECT A.ID, A.DeviceID, A.Unit, A.Name, A.Used,"
								  " A.Type, A.SubType, A.SignalLevel, A.BatteryLevel,"
								  " A.nValue, A.sValue, A.LastUpdate, B.Favorite,"
								  " A.SwitchType, A.HardwareID, A.AddjValue,"
								  " A.AddjMulti, A.AddjValue2, A.AddjMulti2,"
								  " A.LastLevel, A.CustomImage, A.StrParam1,"
								  " A.StrParam2, A.Protected, 0 as XOffset,"
								  " 0 as YOffset, 0 as PlanID, A.Description,"
								  " A.Options, A.Color "
								  "FROM DeviceStatus as A, SharedDevices as B "
								  "WHERE (B.DeviceRowID==a.ID)"
								  " AND (B.SharedUserID==%lu) AND (A.ID=='%q')",
								  m_users[iUser].ID, rowid.c_str());
				}
				else if ((!planID.empty()) && (planID != "0"))
					result = m_sql.safe_query("SELECT A.ID, A.DeviceID, A.Unit, A.Name, A.Used,"
								  " A.Type, A.SubType, A.SignalLevel, A.BatteryLevel,"
								  " A.nValue, A.sValue, A.LastUpdate, B.Favorite,"
								  " A.SwitchType, A.HardwareID, A.AddjValue,"
								  " A.AddjMulti, A.AddjValue2, A.AddjMulti2,"
								  " A.LastLevel, A.CustomImage, A.StrParam1,"
								  " A.StrParam2, A.Protected, C.XOffset,"
								  " C.YOffset, C.PlanID, A.Description,"
								  " A.Options, A.Color "
								  "FROM DeviceStatus as A, SharedDevices as B,"
								  " DeviceToPlansMap as C "
								  "WHERE (C.PlanID=='%q') AND (C.DeviceRowID==a.ID)"
								  " AND (B.DeviceRowID==a.ID) "
								  "AND (B.SharedUserID==%lu) ORDER BY C.[Order]",
								  planID.c_str(), m_users[iUser].ID);
				else if ((!floorID.empty()) && (floorID != "0"))
					result = m_sql.safe_query("SELECT A.ID, A.DeviceID, A.Unit, A.Name, A.Used,"
								  " A.Type, A.SubType, A.SignalLevel, A.BatteryLevel,"
								  " A.nValue, A.sValue, A.LastUpdate, B.Favorite,"
								  " A.SwitchType, A.HardwareID, A.AddjValue,"
								  " A.AddjMulti, A.AddjValue2, A.AddjMulti2,"
								  " A.LastLevel, A.CustomImage, A.StrParam1,"
								  " A.StrParam2, A.Protected, C.XOffset, C.YOffset,"
								  " C.PlanID, A.Description,"
								  " A.Options, A.Color "
								  "FROM DeviceStatus as A, SharedDevices as B,"
								  " DeviceToPlansMap as C, Plans as D "
								  "WHERE (D.FloorplanID=='%q') AND (D.ID==C.PlanID)"
								  " AND (C.DeviceRowID==a.ID) AND (B.DeviceRowID==a.ID)"
								  " AND (B.SharedUserID==%lu) ORDER BY C.[Order]",
								  floorID.c_str(), m_users[iUser].ID);
				else
				{
					if (!bDisplayHidden)
					{
						// Build a list of Hidden Devices
						result = m_sql.safe_query("SELECT ID FROM Plans WHERE (Name=='$Hidden Devices')");
						if (!result.empty())
						{
							std::string pID = result[0][0];
							result = m_sql.safe_query("SELECT DeviceRowID FROM DeviceToPlansMap WHERE (PlanID=='%q')  AND (DevSceneType==0)", pID.c_str());
							if (!result.empty())
							{
								for (const auto &r : result)
								{
									_HiddenDevices.insert(r[0]);
								}
							}
						}
						bAllowDeviceToBeHidden = true;
					}

					if (order.empty() || (!isAlpha))
					{
						strcpy(szOrderBy, "A.[Order],A.LastUpdate DESC");
					}
					else
					{
						sprintf(szOrderBy, "A.[Order],A.%%s ASC");
					}
					// _log.Log(LOG_STATUS, "Getting all devices for user %lu", m_users[iUser].ID);
					szQuery = ("SELECT A.ID, A.DeviceID, A.Unit, A.Name, A.Used,"
						   " A.Type, A.SubType, A.SignalLevel, A.BatteryLevel,"
						   " A.nValue, A.sValue, A.LastUpdate, B.Favorite,"
						   " A.SwitchType, A.HardwareID, A.AddjValue,"
						   " A.AddjMulti, A.AddjValue2, A.AddjMulti2,"
						   " A.LastLevel, A.CustomImage, A.StrParam1,"
						   " A.StrParam2, A.Protected, IFNULL(C.XOffset,0),"
						   " IFNULL(C.YOffset,0), IFNULL(C.PlanID,0), A.Description,"
						   " A.Options, A.Color "
						   "FROM DeviceStatus as A, SharedDevices as B "
						   "LEFT OUTER JOIN DeviceToPlansMap as C  ON (C.DeviceRowID==A.ID)"
						   "WHERE (B.DeviceRowID==A.ID)"
						   " AND (B.SharedUserID==%lu) ORDER BY ");
					szQuery += szOrderBy;
					result = m_sql.safe_query(szQuery.c_str(), m_users[iUser].ID, order.c_str());
				}
			}

			if (!result.empty())
			{
				for (const auto &sd : result)
				{
					unsigned char favorite = atoi(sd[12].c_str());
					bool bIsInPlan = !planID.empty() && (planID != "0");

					// Check if we only want favorite devices
					if (!bIsInPlan)
					{
						if ((bFetchFavorites) && (!favorite))
							continue;
					}

					std::string sDeviceName = sd[3];

					if (!bDisplayHidden)
					{
						if (_HiddenDevices.find(sd[0]) != _HiddenDevices.end())
							continue;
						if (sDeviceName[0] == '$')
						{
							if (bAllowDeviceToBeHidden)
								continue;
							if (!planID.empty())
								sDeviceName = sDeviceName.substr(1);
						}
					}
					int hardwareID = atoi(sd[14].c_str());
					auto hItt = _hardwareNames.find(hardwareID);
					bool bIsHardwareDisabled = true;
					if (hItt != _hardwareNames.end())
					{
						// ignore sensors where the hardware is disabled
						if ((!bDisplayDisabled) && (!(*hItt).second.Enabled))
							continue;
						bIsHardwareDisabled = !(*hItt).second.Enabled;
					}

					unsigned int dType = atoi(sd[5].c_str());
					unsigned int dSubType = atoi(sd[6].c_str());
					unsigned int used = atoi(sd[4].c_str());
					int nValue = atoi(sd[9].c_str());
					std::string sValue = sd[10];
					std::string sLastUpdate = sd[11];
					if (sLastUpdate.size() > 19)
						sLastUpdate = sLastUpdate.substr(0, 19);

					if (iLastUpdate != 0)
					{
						time_t cLastUpdate;
						ParseSQLdatetime(cLastUpdate, tLastUpdate, sLastUpdate, tm1.tm_isdst);
						if (cLastUpdate <= iLastUpdate)
							continue;
					}

					_eSwitchType switchtype = (_eSwitchType)atoi(sd[13].c_str());
					_eMeterType metertype = (_eMeterType)switchtype;
					double AddjValue = atof(sd[15].c_str());
					double AddjMulti = atof(sd[16].c_str());
					double AddjValue2 = atof(sd[17].c_str());
					double AddjMulti2 = atof(sd[18].c_str());
					int LastLevel = atoi(sd[19].c_str());
					int CustomImage = atoi(sd[20].c_str());
					std::string strParam1 = base64_encode(sd[21]);
					std::string strParam2 = base64_encode(sd[22]);
					int iProtected = atoi(sd[23].c_str());

					std::string Description = sd[27];
					std::string sOptions = sd[28];
					std::string sColor = sd[29];
					std::map<std::string, std::string> options = m_sql.BuildDeviceOptions(sOptions);

					struct tm ntime;
					time_t checktime;
					ParseSQLdatetime(checktime, ntime, sLastUpdate, tm1.tm_isdst);
					bool bHaveTimeout = (now - checktime >= SensorTimeOut * 60);

					if (dType == pTypeTEMP_RAIN)
						continue; // dont want you for now

					if ((rused == "true") && (!used))
						continue;

					if ((rused == "false") && (used))
						continue;
					if (!rfilter.empty())
					{
						if (rfilter == "light")
						{
							if ((dType != pTypeLighting1) && (dType != pTypeLighting2) && (dType != pTypeLighting3) && (dType != pTypeLighting4) &&
							    (dType != pTypeLighting5) && (dType != pTypeLighting6) && (dType != pTypeFan) && (dType != pTypeColorSwitch) && (dType != pTypeSecurity1) &&
							    (dType != pTypeSecurity2) && (dType != pTypeEvohome) && (dType != pTypeEvohomeRelay) && (dType != pTypeCurtain) && (dType != pTypeBlinds) &&
							    (dType != pTypeRFY) && (dType != pTypeChime) && (dType != pTypeThermostat2) && (dType != pTypeThermostat3) && (dType != pTypeThermostat4) &&
							    (dType != pTypeRemote) && (dType != pTypeGeneralSwitch) && (dType != pTypeHomeConfort) && (dType != pTypeChime) && (dType != pTypeFS20) &&
							    (!((dType == pTypeRego6XXValue) && (dSubType == sTypeRego6XXStatus))) &&
							    (!((dType == pTypeRadiator1) && (dSubType == sTypeSmartwaresSwitchRadiator))) && (dType != pTypeHunter))
								continue;
						}
						else if (rfilter == "temp")
						{
							if ((dType != pTypeTEMP) && (dType != pTypeHUM) && (dType != pTypeTEMP_HUM) && (dType != pTypeTEMP_HUM_BARO) && (dType != pTypeTEMP_BARO) &&
							    (dType != pTypeEvohomeZone) && (dType != pTypeEvohomeWater) && (!((dType == pTypeWIND) && (dSubType == sTypeWIND4))) &&
							    (!((dType == pTypeUV) && (dSubType == sTypeUV3))) && (!((dType == pTypeGeneral) && (dSubType == sTypeSystemTemp))) &&
							    (dType != pTypeThermostat1) && (!((dType == pTypeRFXSensor) && (dSubType == sTypeRFXSensorTemp))) && (dType != pTypeRego6XXTemp))
								continue;
						}
						else if (rfilter == "weather")
						{
							if ((dType != pTypeWIND) && (dType != pTypeRAIN) && (dType != pTypeTEMP_HUM_BARO) && (dType != pTypeTEMP_BARO) && (dType != pTypeUV) &&
							    (!((dType == pTypeGeneral) && (dSubType == sTypeVisibility))) && (!((dType == pTypeGeneral) && (dSubType == sTypeBaro))) &&
							    (!((dType == pTypeGeneral) && (dSubType == sTypeSolarRadiation))))
								continue;
						}
						else if (rfilter == "utility")
						{
							if ((dType != pTypeRFXMeter) && (!((dType == pTypeRFXSensor) && (dSubType == sTypeRFXSensorAD))) &&
							    (!((dType == pTypeRFXSensor) && (dSubType == sTypeRFXSensorVolt))) && (!((dType == pTypeGeneral) && (dSubType == sTypeVoltage))) &&
							    (!((dType == pTypeGeneral) && (dSubType == sTypeCurrent))) && (!((dType == pTypeGeneral) && (dSubType == sTypeTextStatus))) &&
							    (!((dType == pTypeGeneral) && (dSubType == sTypeAlert))) && (!((dType == pTypeGeneral) && (dSubType == sTypePressure))) &&
							    (!((dType == pTypeGeneral) && (dSubType == sTypeSoilMoisture))) && (!((dType == pTypeGeneral) && (dSubType == sTypeLeafWetness))) &&
							    (!((dType == pTypeGeneral) && (dSubType == sTypePercentage))) && (!((dType == pTypeGeneral) && (dSubType == sTypeWaterflow))) &&
							    (!((dType == pTypeGeneral) && (dSubType == sTypeCustom))) && (!((dType == pTypeGeneral) && (dSubType == sTypeFan))) &&
							    (!((dType == pTypeGeneral) && (dSubType == sTypeSoundLevel))) && (!((dType == pTypeGeneral) && (dSubType == sTypeZWaveClock))) &&
							    (!((dType == pTypeGeneral) && (dSubType == sTypeZWaveThermostatMode))) &&
							    (!((dType == pTypeGeneral) && (dSubType == sTypeZWaveThermostatFanMode))) &&
							    (!((dType == pTypeGeneral) && (dSubType == sTypeZWaveThermostatOperatingState))) &&
							    (!((dType == pTypeGeneral) && (dSubType == sTypeDistance))) && (!((dType == pTypeGeneral) && (dSubType == sTypeCounterIncremental))) &&
							    (!((dType == pTypeGeneral) && (dSubType == sTypeManagedCounter))) && (!((dType == pTypeGeneral) && (dSubType == sTypeKwh))) &&
							    (dType != pTypeCURRENT) && (dType != pTypeCURRENTENERGY) && (dType != pTypeENERGY) && (dType != pTypePOWER) && (dType != pTypeP1Power) &&
							    (dType != pTypeP1Gas) && (dType != pTypeYouLess) && (dType != pTypeAirQuality) && (dType != pTypeLux) && (dType != pTypeUsage) &&
							    (!((dType == pTypeRego6XXValue) && (dSubType == sTypeRego6XXCounter))) &&
							    (!((dType == pTypeThermostat) && (dSubType == sTypeThermSetpoint))) && (dType != pTypeWEIGHT) &&
							    (!((dType == pTypeRadiator1) && (dSubType == sTypeSmartwares))))
								continue;
						}
						else if (rfilter == "wind")
						{
							if ((dType != pTypeWIND))
								continue;
						}
						else if (rfilter == "rain")
						{
							if ((dType != pTypeRAIN))
								continue;
						}
						else if (rfilter == "uv")
						{
							if ((dType != pTypeUV))
								continue;
						}
						else if (rfilter == "baro")
						{
							if ((dType != pTypeTEMP_HUM_BARO) && (dType != pTypeTEMP_BARO))
								continue;
						}
						else if (rfilter == "zwavealarms")
						{
							if (!((dType == pTypeGeneral) && (dSubType == sTypeZWaveAlarm)))
								continue;
						}
					}

					// has this device already been seen, now with different plan?
					// assume results are ordered such that same device is adjacent
					// if the idx and the Type are equal (type to prevent matching against Scene with same idx)
					std::string thisIdx = sd[0];
					int devIdx = atoi(thisIdx.c_str());

					if ((ii > 0) && thisIdx == root["result"][ii - 1]["idx"].asString())
					{
						std::string typeOfThisOne = RFX_Type_Desc(dType, 1);
						if (typeOfThisOne == root["result"][ii - 1]["Type"].asString())
						{
							root["result"][ii - 1]["PlanIDs"].append(atoi(sd[26].c_str()));
							continue;
						}
					}

					root["result"][ii]["HardwareID"] = hardwareID;
					if (_hardwareNames.find(hardwareID) == _hardwareNames.end())
					{
						root["result"][ii]["HardwareName"] = "Unknown?";
						root["result"][ii]["HardwareTypeVal"] = 0;
						root["result"][ii]["HardwareType"] = "Unknown?";
					}
					else
					{
						root["result"][ii]["HardwareName"] = _hardwareNames[hardwareID].Name;
						root["result"][ii]["HardwareTypeVal"] = _hardwareNames[hardwareID].HardwareTypeVal;
						root["result"][ii]["HardwareType"] = _hardwareNames[hardwareID].HardwareType;
					}
					root["result"][ii]["HardwareDisabled"] = bIsHardwareDisabled;

					root["result"][ii]["idx"] = sd[0];
					root["result"][ii]["Protected"] = (iProtected != 0);

					CDomoticzHardwareBase *pHardware = m_mainworker.GetHardware(hardwareID);
					if (pHardware != nullptr)
					{
						if (pHardware->HwdType == HTYPE_SolarEdgeAPI)
						{
							int seSensorTimeOut = 60 * 24 * 60;
							bHaveTimeout = (now - checktime >= seSensorTimeOut * 60);
						}
						else if (pHardware->HwdType == HTYPE_Wunderground)
						{
							CWunderground *pWHardware = reinterpret_cast<CWunderground *>(pHardware);
							std::string forecast_url = pWHardware->GetForecastURL();
							if (!forecast_url.empty())
							{
								root["result"][ii]["forecast_url"] = base64_encode(forecast_url);
							}
						}
						else if (pHardware->HwdType == HTYPE_DarkSky)
						{
							CDarkSky *pWHardware = reinterpret_cast<CDarkSky *>(pHardware);
							std::string forecast_url = pWHardware->GetForecastURL();
							if (!forecast_url.empty())
							{
								root["result"][ii]["forecast_url"] = base64_encode(forecast_url);
							}
						}
						else if (pHardware->HwdType == HTYPE_AccuWeather)
						{
							CAccuWeather *pWHardware = reinterpret_cast<CAccuWeather *>(pHardware);
							std::string forecast_url = pWHardware->GetForecastURL();
							if (!forecast_url.empty())
							{
								root["result"][ii]["forecast_url"] = base64_encode(forecast_url);
							}
						}
						else if (pHardware->HwdType == HTYPE_OpenWeatherMap)
						{
							COpenWeatherMap *pWHardware = reinterpret_cast<COpenWeatherMap *>(pHardware);
							std::string forecast_url = pWHardware->GetForecastURL();
							if (!forecast_url.empty())
							{
								root["result"][ii]["forecast_url"] = base64_encode(forecast_url);
							}
						}
						else if (pHardware->HwdType == HTYPE_BuienRadar)
						{
							CBuienRadar *pWHardware = reinterpret_cast<CBuienRadar *>(pHardware);
							std::string forecast_url = pWHardware->GetForecastURL();
							if (!forecast_url.empty())
							{
								root["result"][ii]["forecast_url"] = base64_encode(forecast_url);
							}
						}
						else if (pHardware->HwdType == HTYPE_Meteorologisk)
						{
							CMeteorologisk *pWHardware = reinterpret_cast<CMeteorologisk *>(pHardware);
							std::string forecast_url = pWHardware->GetForecastURL();
							if (!forecast_url.empty())
							{
								root["result"][ii]["forecast_url"] = base64_encode(forecast_url);
							}
						}
					}

					if ((pHardware != nullptr) && (pHardware->HwdType == HTYPE_PythonPlugin))
					{
						// Device ID special formatting should not be applied to Python plugins
						root["result"][ii]["ID"] = sd[1];
					}
					else
					{
						if ((dType == pTypeTEMP) || (dType == pTypeTEMP_BARO) || (dType == pTypeTEMP_HUM) || (dType == pTypeTEMP_HUM_BARO) || (dType == pTypeBARO) ||
						    (dType == pTypeHUM) || (dType == pTypeWIND) || (dType == pTypeRAIN) || (dType == pTypeUV) || (dType == pTypeCURRENT) ||
						    (dType == pTypeCURRENTENERGY) || (dType == pTypeENERGY) || (dType == pTypeRFXMeter) || (dType == pTypeAirQuality) || (dType == pTypeRFXSensor) ||
						    (dType == pTypeP1Power) || (dType == pTypeP1Gas))
						{
							root["result"][ii]["ID"] = is_number(sd[1]) ? std_format("%04X", (unsigned int)atoi(sd[1].c_str())) : sd[1];
						}
						else
						{
							root["result"][ii]["ID"] = sd[1];
						}
					}
					root["result"][ii]["Unit"] = atoi(sd[2].c_str());
					root["result"][ii]["Type"] = RFX_Type_Desc(dType, 1);
					root["result"][ii]["SubType"] = RFX_Type_SubType_Desc(dType, dSubType);
					root["result"][ii]["TypeImg"] = RFX_Type_Desc(dType, 2);
					root["result"][ii]["Name"] = sDeviceName;
					root["result"][ii]["Description"] = Description;
					root["result"][ii]["Used"] = used;
					root["result"][ii]["Favorite"] = favorite;

					int iSignalLevel = atoi(sd[7].c_str());
					if (iSignalLevel < 12)
						root["result"][ii]["SignalLevel"] = iSignalLevel;
					else
						root["result"][ii]["SignalLevel"] = "-";
					root["result"][ii]["BatteryLevel"] = atoi(sd[8].c_str());
					root["result"][ii]["LastUpdate"] = sLastUpdate;
					root["result"][ii]["CustomImage"] = CustomImage;
					root["result"][ii]["XOffset"] = sd[24].c_str();
					root["result"][ii]["YOffset"] = sd[25].c_str();
					root["result"][ii]["PlanID"] = sd[26].c_str();
					Json::Value jsonArray;
					jsonArray.append(atoi(sd[26].c_str()));
					root["result"][ii]["PlanIDs"] = jsonArray;
					root["result"][ii]["AddjValue"] = AddjValue;
					root["result"][ii]["AddjMulti"] = AddjMulti;
					root["result"][ii]["AddjValue2"] = AddjValue2;
					root["result"][ii]["AddjMulti2"] = AddjMulti2;

					std::stringstream s_data;
					s_data << int(nValue) << ", " << sValue;
					root["result"][ii]["Data"] = s_data.str();

					root["result"][ii]["Notifications"] = (m_notifications.HasNotifications(sd[0]) == true) ? "true" : "false";
					root["result"][ii]["ShowNotifications"] = true;

					bool bHasTimers = false;

					if ((dType == pTypeLighting1) || (dType == pTypeLighting2) || (dType == pTypeLighting3) || (dType == pTypeLighting4) || (dType == pTypeLighting5) ||
					    (dType == pTypeLighting6) || (dType == pTypeFan) || (dType == pTypeColorSwitch) || (dType == pTypeCurtain) || (dType == pTypeBlinds) ||
					    (dType == pTypeRFY) || (dType == pTypeChime) || (dType == pTypeThermostat2) || (dType == pTypeThermostat3) || (dType == pTypeThermostat4) ||
					    (dType == pTypeRemote) || (dType == pTypeGeneralSwitch) || (dType == pTypeHomeConfort) || (dType == pTypeFS20) ||
					    ((dType == pTypeRadiator1) && (dSubType == sTypeSmartwaresSwitchRadiator)) || ((dType == pTypeRego6XXValue) && (dSubType == sTypeRego6XXStatus)) ||
					    (dType == pTypeHunter))
					{
						// add light details
						bHasTimers = m_sql.HasTimers(sd[0]);

						bHaveTimeout = false;
#ifdef WITH_OPENZWAVE
						if (pHardware != nullptr)
						{
							if (pHardware->HwdType == HTYPE_OpenZWave)
							{
								COpenZWave *pZWave = reinterpret_cast<COpenZWave *>(pHardware);
								unsigned long ID;
								std::stringstream s_strid;
								s_strid << std::hex << sd[1];
								s_strid >> ID;
								int nodeID = (ID & 0x0000FF00) >> 8;
								bHaveTimeout = pZWave->HasNodeFailed(nodeID);
							}
						}
#endif
						root["result"][ii]["HaveTimeout"] = bHaveTimeout;

						std::string lstatus;
						int llevel = 0;
						bool bHaveDimmer = false;
						bool bHaveGroupCmd = false;
						int maxDimLevel = 0;

						GetLightStatus(dType, dSubType, switchtype, nValue, sValue, lstatus, llevel, bHaveDimmer, maxDimLevel, bHaveGroupCmd);

						root["result"][ii]["Status"] = lstatus;
						root["result"][ii]["StrParam1"] = strParam1;
						root["result"][ii]["StrParam2"] = strParam2;

						std::string IconFile = "Light";
						auto ittIcon = m_custom_light_icons_lookup.find(CustomImage);
						if (ittIcon != m_custom_light_icons_lookup.end())
						{
							IconFile = m_custom_light_icons[ittIcon->second].RootFile;
						}
						root["result"][ii]["Image"] = IconFile;

						if (switchtype == STYPE_Dimmer)
						{
							root["result"][ii]["Level"] = LastLevel;
							int iLevel = round((float(maxDimLevel) / 100.0F) * LastLevel);
							root["result"][ii]["LevelInt"] = iLevel;
							if ((dType == pTypeColorSwitch) || (dType == pTypeLighting5 && dSubType == sTypeTRC02) ||
							    (dType == pTypeLighting5 && dSubType == sTypeTRC02_2) || (dType == pTypeGeneralSwitch && dSubType == sSwitchTypeTRC02) ||
							    (dType == pTypeGeneralSwitch && dSubType == sSwitchTypeTRC02_2))
							{
								_tColor color(sColor);
								std::string jsonColor = color.toJSONString();
								root["result"][ii]["Color"] = jsonColor;
								llevel = LastLevel;
								if (lstatus == "Set Level" || lstatus == "Set Color")
								{
									sprintf(szTmp, "Set Level: %d %%", LastLevel);
									root["result"][ii]["Status"] = szTmp;
								}
							}
						}
						else
						{
							root["result"][ii]["Level"] = llevel;
							root["result"][ii]["LevelInt"] = atoi(sValue.c_str());
						}
						root["result"][ii]["HaveDimmer"] = bHaveDimmer;
						std::string DimmerType = "none";
						if (switchtype == STYPE_Dimmer)
						{
							DimmerType = "abs";
							if (_hardwareNames.find(hardwareID) != _hardwareNames.end())
							{
								// Milight V4/V5 bridges do not support absolute dimming for RGB or CW_WW lights
								if (_hardwareNames[hardwareID].HardwareTypeVal == HTYPE_LimitlessLights &&
								    atoi(_hardwareNames[hardwareID].Mode2.c_str()) != CLimitLess::LBTYPE_V6 &&
								    (atoi(_hardwareNames[hardwareID].Mode1.c_str()) == sTypeColor_RGB ||
								     atoi(_hardwareNames[hardwareID].Mode1.c_str()) == sTypeColor_White ||
								     atoi(_hardwareNames[hardwareID].Mode1.c_str()) == sTypeColor_CW_WW))
								{
									DimmerType = "rel";
								}
							}
						}
						root["result"][ii]["DimmerType"] = DimmerType;
						root["result"][ii]["MaxDimLevel"] = maxDimLevel;
						root["result"][ii]["HaveGroupCmd"] = bHaveGroupCmd;
						root["result"][ii]["SwitchType"] = Switch_Type_Desc(switchtype);
						root["result"][ii]["SwitchTypeVal"] = switchtype;
						uint64_t camIDX = m_mainworker.m_cameras.IsDevSceneInCamera(0, sd[0]);
						root["result"][ii]["UsedByCamera"] = (camIDX != 0) ? true : false;
						if (camIDX != 0)
						{
							std::stringstream scidx;
							scidx << camIDX;
							root["result"][ii]["CameraIdx"] = scidx.str();
						}

						bool bIsSubDevice = false;
						std::vector<std::vector<std::string>> resultSD;
						resultSD = m_sql.safe_query("SELECT ID FROM LightSubDevices WHERE (DeviceRowID=='%q')", sd[0].c_str());
						bIsSubDevice = (!resultSD.empty());

						root["result"][ii]["IsSubDevice"] = bIsSubDevice;

						if (switchtype == STYPE_Doorbell)
						{
							root["result"][ii]["TypeImg"] = "doorbell";
							root["result"][ii]["Status"] = ""; //"Pressed";
						}
						else if (switchtype == STYPE_DoorContact)
						{
							if (CustomImage == 0)
							{
								root["result"][ii]["Image"] = "Door";
							}
							root["result"][ii]["TypeImg"] = "door";
							bool bIsOn = IsLightSwitchOn(lstatus);
							root["result"][ii]["InternalState"] = (bIsOn == true) ? "Open" : "Closed";
							if (bIsOn)
							{
								lstatus = "Open";
							}
							else
							{
								lstatus = "Closed";
							}
							root["result"][ii]["Status"] = lstatus;
						}
						else if (switchtype == STYPE_DoorLock)
						{
							if (CustomImage == 0)
							{
								root["result"][ii]["Image"] = "Door";
							}
							root["result"][ii]["TypeImg"] = "door";
							bool bIsOn = IsLightSwitchOn(lstatus);
							root["result"][ii]["InternalState"] = (bIsOn == true) ? "Locked" : "Unlocked";
							if (bIsOn)
							{
								lstatus = "Locked";
							}
							else
							{
								lstatus = "Unlocked";
							}
							root["result"][ii]["Status"] = lstatus;
						}
						else if (switchtype == STYPE_DoorLockInverted)
						{
							if (CustomImage == 0)
							{
								root["result"][ii]["Image"] = "Door";
							}
							root["result"][ii]["TypeImg"] = "door";
							bool bIsOn = IsLightSwitchOn(lstatus);
							root["result"][ii]["InternalState"] = (bIsOn == true) ? "Unlocked" : "Locked";
							if (bIsOn)
							{
								lstatus = "Unlocked";
							}
							else
							{
								lstatus = "Locked";
							}
							root["result"][ii]["Status"] = lstatus;
						}
						else if (switchtype == STYPE_PushOn)
						{
							if (CustomImage == 0)
							{
								root["result"][ii]["Image"] = "Push";
							}
							root["result"][ii]["TypeImg"] = "push";
							root["result"][ii]["Status"] = "";
							root["result"][ii]["InternalState"] = (IsLightSwitchOn(lstatus) == true) ? "On" : "Off";
						}
						else if (switchtype == STYPE_PushOff)
						{
							if (CustomImage == 0)
							{
								root["result"][ii]["Image"] = "Push";
							}
							root["result"][ii]["TypeImg"] = "push";
							root["result"][ii]["Status"] = "";
							root["result"][ii]["TypeImg"] = "pushoff";
						}
						else if (switchtype == STYPE_X10Siren)
							root["result"][ii]["TypeImg"] = "siren";
						else if (switchtype == STYPE_SMOKEDETECTOR)
						{
							root["result"][ii]["TypeImg"] = "smoke";
							root["result"][ii]["SwitchTypeVal"] = STYPE_SMOKEDETECTOR;
							root["result"][ii]["SwitchType"] = Switch_Type_Desc(STYPE_SMOKEDETECTOR);
						}
						else if (switchtype == STYPE_Contact)
						{
							if (CustomImage == 0)
							{
								root["result"][ii]["Image"] = "Contact";
							}
							root["result"][ii]["TypeImg"] = "contact";
							bool bIsOn = IsLightSwitchOn(lstatus);
							if (bIsOn)
							{
								lstatus = "Open";
							}
							else
							{
								lstatus = "Closed";
							}
							root["result"][ii]["Status"] = lstatus;
						}
						else if (switchtype == STYPE_Media)
						{
							if ((pHardware != nullptr) && (pHardware->HwdType == HTYPE_LogitechMediaServer))
								root["result"][ii]["TypeImg"] = "LogitechMediaServer";
							else
								root["result"][ii]["TypeImg"] = "Media";
							root["result"][ii]["Status"] = Media_Player_States((_eMediaStatus)nValue);
							lstatus = sValue;
						}
						else if ((switchtype == STYPE_Blinds) || (switchtype == STYPE_VenetianBlindsUS) || (switchtype == STYPE_VenetianBlindsEU))
						{
							root["result"][ii]["TypeImg"] = "blinds";
							if ((lstatus == "On") || (lstatus == "Close inline relay"))
							{
								lstatus = "Closed";
							}
							else if ((lstatus == "Stop") || (lstatus == "Stop inline relay"))
							{
								lstatus = "Stopped";
							}
							else
							{
								lstatus = "Open";
							}
							root["result"][ii]["Status"] = lstatus;
						}
						else if (switchtype == STYPE_BlindsInverted)
						{
							root["result"][ii]["TypeImg"] = "blinds";
							if (lstatus == "On")
							{
								lstatus = "Open";
							}
							else
							{
								lstatus = "Closed";
							}
							root["result"][ii]["Status"] = lstatus;
						}
						else if (
							(switchtype == STYPE_BlindsPercentage)
							|| (switchtype == STYPE_BlindsPercentageInverted)
							|| (switchtype == STYPE_BlindsPercentageWithStop)
							|| (switchtype == STYPE_BlindsPercentageInvertedWithStop)
							)
						{
							root["result"][ii]["TypeImg"] = "blinds";
							root["result"][ii]["Level"] = LastLevel;
							int iLevel = round((float(maxDimLevel) / 100.0F) * LastLevel);
							root["result"][ii]["LevelInt"] = iLevel;
/*
							if ((iLevel > 0) && (iLevel < maxDimLevel))
							{
								lstatus = std_format("%d %%", iLevel);
							}
							else if (lstatus == "On")
*/
							if (lstatus == "On")
							{
								lstatus = ((switchtype == STYPE_BlindsPercentage) || (switchtype == STYPE_BlindsPercentageWithStop)) ? "Closed" : "Open";
							}
							else if (lstatus == "Off")
							{
								lstatus = ((switchtype == STYPE_BlindsPercentage) || (switchtype == STYPE_BlindsPercentageWithStop)) ? "Open" : "Closed";
							}
							else if (lstatus == "Stop")
							{
								lstatus = "Stopped";
							}
							root["result"][ii]["Status"] = lstatus;
						}
						else if (switchtype == STYPE_Dimmer)
						{
							root["result"][ii]["TypeImg"] = "dimmer";
						}
						else if (switchtype == STYPE_Motion)
						{
							root["result"][ii]["TypeImg"] = "motion";
						}
						else if (switchtype == STYPE_Selector)
						{
							std::string selectorStyle = options["SelectorStyle"];
							std::string levelOffHidden = options["LevelOffHidden"];
							std::string levelNames = options["LevelNames"];
							std::string levelActions = options["LevelActions"];
							if (selectorStyle.empty())
							{
								selectorStyle.assign("0"); // default is 'button set'
							}
							if (levelOffHidden.empty())
							{
								levelOffHidden.assign("false"); // default is 'not hidden'
							}
							if (levelNames.empty())
							{
								levelNames.assign("Off"); // default is Off only
							}
							root["result"][ii]["TypeImg"] = "Light";
							root["result"][ii]["SelectorStyle"] = atoi(selectorStyle.c_str());
							root["result"][ii]["LevelOffHidden"] = (levelOffHidden == "true");
							root["result"][ii]["LevelNames"] = base64_encode(levelNames);
							root["result"][ii]["LevelActions"] = base64_encode(levelActions);
						}
						sprintf(szData, "%s", lstatus.c_str());
						root["result"][ii]["Data"] = szData;
					}
					else if (dType == pTypeSecurity1)
					{
						std::string lstatus;
						int llevel = 0;
						bool bHaveDimmer = false;
						bool bHaveGroupCmd = false;
						int maxDimLevel = 0;

						GetLightStatus(dType, dSubType, switchtype, nValue, sValue, lstatus, llevel, bHaveDimmer, maxDimLevel, bHaveGroupCmd);

						root["result"][ii]["Status"] = lstatus;
						root["result"][ii]["HaveDimmer"] = bHaveDimmer;
						root["result"][ii]["MaxDimLevel"] = maxDimLevel;
						root["result"][ii]["HaveGroupCmd"] = bHaveGroupCmd;
						root["result"][ii]["SwitchType"] = "Security";
						root["result"][ii]["SwitchTypeVal"] = switchtype; // was 0?;
						root["result"][ii]["TypeImg"] = "security";
						root["result"][ii]["StrParam1"] = strParam1;
						root["result"][ii]["StrParam2"] = strParam2;
						root["result"][ii]["Protected"] = (iProtected != 0);

						if ((dSubType == sTypeKD101) || (dSubType == sTypeSA30) || (dSubType == sTypeRM174RF) || (switchtype == STYPE_SMOKEDETECTOR))
						{
							root["result"][ii]["SwitchTypeVal"] = STYPE_SMOKEDETECTOR;
							root["result"][ii]["TypeImg"] = "smoke";
							root["result"][ii]["SwitchType"] = Switch_Type_Desc(STYPE_SMOKEDETECTOR);
						}
						sprintf(szData, "%s", lstatus.c_str());
						root["result"][ii]["Data"] = szData;
						root["result"][ii]["HaveTimeout"] = false;
					}
					else if (dType == pTypeSecurity2)
					{
						std::string lstatus;
						int llevel = 0;
						bool bHaveDimmer = false;
						bool bHaveGroupCmd = false;
						int maxDimLevel = 0;

						GetLightStatus(dType, dSubType, switchtype, nValue, sValue, lstatus, llevel, bHaveDimmer, maxDimLevel, bHaveGroupCmd);

						root["result"][ii]["Status"] = lstatus;
						root["result"][ii]["HaveDimmer"] = bHaveDimmer;
						root["result"][ii]["MaxDimLevel"] = maxDimLevel;
						root["result"][ii]["HaveGroupCmd"] = bHaveGroupCmd;
						root["result"][ii]["SwitchType"] = "Security";
						root["result"][ii]["SwitchTypeVal"] = switchtype; // was 0?;
						root["result"][ii]["TypeImg"] = "security";
						root["result"][ii]["StrParam1"] = strParam1;
						root["result"][ii]["StrParam2"] = strParam2;
						root["result"][ii]["Protected"] = (iProtected != 0);
						sprintf(szData, "%s", lstatus.c_str());
						root["result"][ii]["Data"] = szData;
						root["result"][ii]["HaveTimeout"] = false;
					}
					else if (dType == pTypeEvohome || dType == pTypeEvohomeRelay)
					{
						std::string lstatus;
						int llevel = 0;
						bool bHaveDimmer = false;
						bool bHaveGroupCmd = false;
						int maxDimLevel = 0;

						GetLightStatus(dType, dSubType, switchtype, nValue, sValue, lstatus, llevel, bHaveDimmer, maxDimLevel, bHaveGroupCmd);

						root["result"][ii]["Status"] = lstatus;
						root["result"][ii]["HaveDimmer"] = bHaveDimmer;
						root["result"][ii]["MaxDimLevel"] = maxDimLevel;
						root["result"][ii]["HaveGroupCmd"] = bHaveGroupCmd;
						root["result"][ii]["SwitchType"] = "evohome";
						root["result"][ii]["SwitchTypeVal"] = switchtype; // was 0?;
						root["result"][ii]["TypeImg"] = "override_mini";
						root["result"][ii]["StrParam1"] = strParam1;
						root["result"][ii]["StrParam2"] = strParam2;
						root["result"][ii]["Protected"] = (iProtected != 0);

						sprintf(szData, "%s", lstatus.c_str());
						root["result"][ii]["Data"] = szData;
						root["result"][ii]["HaveTimeout"] = false;

						if (dType == pTypeEvohomeRelay)
						{
							root["result"][ii]["SwitchType"] = "TPI";
							root["result"][ii]["Level"] = llevel;
							root["result"][ii]["LevelInt"] = atoi(sValue.c_str());
							if (root["result"][ii]["Unit"].asInt() > 100)
								root["result"][ii]["Protected"] = true;

							sprintf(szData, "%s: %d", lstatus.c_str(), atoi(sValue.c_str()));
							root["result"][ii]["Data"] = szData;
						}
					}
					else if ((dType == pTypeEvohomeZone) || (dType == pTypeEvohomeWater))
					{
						root["result"][ii]["HaveTimeout"] = bHaveTimeout;
						root["result"][ii]["TypeImg"] = "override_mini";

						std::vector<std::string> strarray;
						StringSplit(sValue, ";", strarray);
						if (strarray.size() >= 3)
						{
							int i = 0;
							double tempCelcius = atof(strarray[i++].c_str());
							double temp = ConvertTemperature(tempCelcius, tempsign);
							double tempSetPoint;
							root["result"][ii]["Temp"] = temp;
							if (dType == pTypeEvohomeWater && (strarray[i] == "Off" || strarray[i] == "On"))
							{
								root["result"][ii]["State"] = strarray[i++];
							}
							else
							{
								tempCelcius = atof(strarray[i++].c_str());
								tempSetPoint = ConvertTemperature(tempCelcius, tempsign);
								root["result"][ii]["SetPoint"] = tempSetPoint;
							}

							std::string strstatus = strarray[i++];
							root["result"][ii]["Status"] = strstatus;

							if ((dType == pTypeEvohomeZone || dType == pTypeEvohomeWater) && strarray.size() >= 4)
							{
								root["result"][ii]["Until"] = strarray[i++];
							}
							if (dType == pTypeEvohomeZone)
							{
								if (tempCelcius == 325.1)
									sprintf(szTmp, "Off");
								else
									sprintf(szTmp, "%.1f %c", tempSetPoint, tempsign);
								if (strarray.size() >= 4)
									sprintf(szData, "%.1f %c, (%s), %s until %s", temp, tempsign, szTmp, strstatus.c_str(), strarray[3].c_str());
								else
									sprintf(szData, "%.1f %c, (%s), %s", temp, tempsign, szTmp, strstatus.c_str());
							}
							else if (strarray.size() >= 4)
								sprintf(szData, "%.1f %c, %s, %s until %s", temp, tempsign, strarray[1].c_str(), strstatus.c_str(), strarray[3].c_str());
							else
								sprintf(szData, "%.1f %c, %s, %s", temp, tempsign, strarray[1].c_str(), strstatus.c_str());
							root["result"][ii]["Data"] = szData;
							root["result"][ii]["HaveTimeout"] = bHaveTimeout;
						}
					}
					else if ((dType == pTypeTEMP) || (dType == pTypeRego6XXTemp))
					{
						double tvalue = ConvertTemperature(atof(sValue.c_str()), tempsign);
						root["result"][ii]["Temp"] = tvalue;
						sprintf(szData, "%.1f %c", tvalue, tempsign);
						root["result"][ii]["Data"] = szData;
						root["result"][ii]["HaveTimeout"] = bHaveTimeout;

						_tTrendCalculator::_eTendencyType tstate = _tTrendCalculator::_eTendencyType::TENDENCY_UNKNOWN;
						uint64_t tID = ((uint64_t)(hardwareID & 0x7FFFFFFF) << 32) | (devIdx & 0x7FFFFFFF);
						if (m_mainworker.m_trend_calculator.find(tID) != m_mainworker.m_trend_calculator.end())
						{
							tstate = m_mainworker.m_trend_calculator[tID].m_state;
						}
						root["result"][ii]["trend"] = (int)tstate;
					}
					else if (dType == pTypeThermostat1)
					{
						std::vector<std::string> strarray;
						StringSplit(sValue, ";", strarray);
						if (strarray.size() == 4)
						{
							double tvalue = ConvertTemperature(atof(strarray[0].c_str()), tempsign);
							root["result"][ii]["Temp"] = tvalue;
							sprintf(szData, "%.1f %c", tvalue, tempsign);
							root["result"][ii]["Data"] = szData;
							root["result"][ii]["HaveTimeout"] = bHaveTimeout;
						}
					}
					else if ((dType == pTypeRFXSensor) && (dSubType == sTypeRFXSensorTemp))
					{
						double tvalue = ConvertTemperature(atof(sValue.c_str()), tempsign);
						root["result"][ii]["Temp"] = tvalue;
						sprintf(szData, "%.1f %c", tvalue, tempsign);
						root["result"][ii]["Data"] = szData;
						root["result"][ii]["TypeImg"] = "temperature";
						root["result"][ii]["HaveTimeout"] = bHaveTimeout;
						_tTrendCalculator::_eTendencyType tstate = _tTrendCalculator::_eTendencyType::TENDENCY_UNKNOWN;
						uint64_t tID = ((uint64_t)(hardwareID & 0x7FFFFFFF) << 32) | (devIdx & 0x7FFFFFFF);
						if (m_mainworker.m_trend_calculator.find(tID) != m_mainworker.m_trend_calculator.end())
						{
							tstate = m_mainworker.m_trend_calculator[tID].m_state;
						}
						root["result"][ii]["trend"] = (int)tstate;
					}
					else if (dType == pTypeHUM)
					{
						root["result"][ii]["Humidity"] = nValue;
						root["result"][ii]["HumidityStatus"] = RFX_Humidity_Status_Desc(atoi(sValue.c_str()));
						sprintf(szData, "Humidity %d %%", nValue);
						root["result"][ii]["Data"] = szData;
						root["result"][ii]["HaveTimeout"] = bHaveTimeout;
					}
					else if (dType == pTypeTEMP_HUM)
					{
						std::vector<std::string> strarray;
						StringSplit(sValue, ";", strarray);
						if (strarray.size() == 3)
						{
							double tempCelcius = atof(strarray[0].c_str());
							double temp = ConvertTemperature(tempCelcius, tempsign);
							int humidity = atoi(strarray[1].c_str());

							root["result"][ii]["Temp"] = temp;
							root["result"][ii]["Humidity"] = humidity;
							root["result"][ii]["HumidityStatus"] = RFX_Humidity_Status_Desc(atoi(strarray[2].c_str()));
							sprintf(szData, "%.1f %c, %d %%", temp, tempsign, atoi(strarray[1].c_str()));
							root["result"][ii]["Data"] = szData;
							root["result"][ii]["HaveTimeout"] = bHaveTimeout;

							// Calculate dew point

							sprintf(szTmp, "%.2f", ConvertTemperature(CalculateDewPoint(tempCelcius, humidity), tempsign));
							root["result"][ii]["DewPoint"] = szTmp;

							_tTrendCalculator::_eTendencyType tstate = _tTrendCalculator::_eTendencyType::TENDENCY_UNKNOWN;
							uint64_t tID = ((uint64_t)(hardwareID & 0x7FFFFFFF) << 32) | (devIdx & 0x7FFFFFFF);
							if (m_mainworker.m_trend_calculator.find(tID) != m_mainworker.m_trend_calculator.end())
							{
								tstate = m_mainworker.m_trend_calculator[tID].m_state;
							}
							root["result"][ii]["trend"] = (int)tstate;
						}
					}
					else if (dType == pTypeTEMP_HUM_BARO)
					{
						std::vector<std::string> strarray;
						StringSplit(sValue, ";", strarray);
						if (strarray.size() == 5)
						{
							double tempCelcius = atof(strarray[0].c_str());
							double temp = ConvertTemperature(tempCelcius, tempsign);
							int humidity = atoi(strarray[1].c_str());

							root["result"][ii]["Temp"] = temp;
							root["result"][ii]["Humidity"] = humidity;
							root["result"][ii]["HumidityStatus"] = RFX_Humidity_Status_Desc(atoi(strarray[2].c_str()));
							root["result"][ii]["Forecast"] = atoi(strarray[4].c_str());

							sprintf(szTmp, "%.2f", ConvertTemperature(CalculateDewPoint(tempCelcius, humidity), tempsign));
							root["result"][ii]["DewPoint"] = szTmp;

							if (dSubType == sTypeTHBFloat)
							{
								root["result"][ii]["Barometer"] = atof(strarray[3].c_str());
								root["result"][ii]["ForecastStr"] = RFX_WSForecast_Desc(atoi(strarray[4].c_str()));
							}
							else
							{
								root["result"][ii]["Barometer"] = atoi(strarray[3].c_str());
								root["result"][ii]["ForecastStr"] = RFX_Forecast_Desc(atoi(strarray[4].c_str()));
							}
							if (dSubType == sTypeTHBFloat)
							{
								sprintf(szData, "%.1f %c, %d %%, %.1f hPa", temp, tempsign, atoi(strarray[1].c_str()), atof(strarray[3].c_str()));
							}
							else
							{
								sprintf(szData, "%.1f %c, %d %%, %d hPa", temp, tempsign, atoi(strarray[1].c_str()), atoi(strarray[3].c_str()));
							}
							root["result"][ii]["Data"] = szData;
							root["result"][ii]["HaveTimeout"] = bHaveTimeout;

							_tTrendCalculator::_eTendencyType tstate = _tTrendCalculator::_eTendencyType::TENDENCY_UNKNOWN;
							uint64_t tID = ((uint64_t)(hardwareID & 0x7FFFFFFF) << 32) | (devIdx & 0x7FFFFFFF);
							if (m_mainworker.m_trend_calculator.find(tID) != m_mainworker.m_trend_calculator.end())
							{
								tstate = m_mainworker.m_trend_calculator[tID].m_state;
							}
							root["result"][ii]["trend"] = (int)tstate;
						}
					}
					else if (dType == pTypeTEMP_BARO)
					{
						std::vector<std::string> strarray;
						StringSplit(sValue, ";", strarray);
						if (strarray.size() >= 3)
						{
							double tvalue = ConvertTemperature(atof(strarray[0].c_str()), tempsign);
							root["result"][ii]["Temp"] = tvalue;
							int forecast = atoi(strarray[2].c_str());
							root["result"][ii]["Forecast"] = forecast;
							root["result"][ii]["ForecastStr"] = BMP_Forecast_Desc(forecast);
							root["result"][ii]["Barometer"] = atof(strarray[1].c_str());

							sprintf(szData, "%.1f %c, %.1f hPa", tvalue, tempsign, atof(strarray[1].c_str()));
							root["result"][ii]["Data"] = szData;
							root["result"][ii]["HaveTimeout"] = bHaveTimeout;

							_tTrendCalculator::_eTendencyType tstate = _tTrendCalculator::_eTendencyType::TENDENCY_UNKNOWN;
							uint64_t tID = ((uint64_t)(hardwareID & 0x7FFFFFFF) << 32) | (devIdx & 0x7FFFFFFF);
							if (m_mainworker.m_trend_calculator.find(tID) != m_mainworker.m_trend_calculator.end())
							{
								tstate = m_mainworker.m_trend_calculator[tID].m_state;
							}
							root["result"][ii]["trend"] = (int)tstate;
						}
					}
					else if (dType == pTypeUV)
					{
						std::vector<std::string> strarray;
						StringSplit(sValue, ";", strarray);
						if (strarray.size() == 2)
						{
							float UVI = static_cast<float>(atof(strarray[0].c_str()));
							root["result"][ii]["UVI"] = strarray[0];
							if (dSubType == sTypeUV3)
							{
								double tvalue = ConvertTemperature(atof(strarray[1].c_str()), tempsign);

								root["result"][ii]["Temp"] = tvalue;
								sprintf(szData, "%.1f UVI, %.1f&deg; %c", UVI, tvalue, tempsign);

								_tTrendCalculator::_eTendencyType tstate = _tTrendCalculator::_eTendencyType::TENDENCY_UNKNOWN;
								uint64_t tID = ((uint64_t)(hardwareID & 0x7FFFFFFF) << 32) | (devIdx & 0x7FFFFFFF);
								if (m_mainworker.m_trend_calculator.find(tID) != m_mainworker.m_trend_calculator.end())
								{
									tstate = m_mainworker.m_trend_calculator[tID].m_state;
								}
								root["result"][ii]["trend"] = (int)tstate;
							}
							else
							{
								sprintf(szData, "%.1f UVI", UVI);
							}
							root["result"][ii]["Data"] = szData;
							root["result"][ii]["HaveTimeout"] = bHaveTimeout;
						}
					}
					else if (dType == pTypeWIND)
					{
						std::vector<std::string> strarray;
						StringSplit(sValue, ";", strarray);
						if (strarray.size() == 6)
						{
							root["result"][ii]["Direction"] = atof(strarray[0].c_str());
							root["result"][ii]["DirectionStr"] = strarray[1];

							if (dSubType != sTypeWIND5)
							{
								int intSpeed = atoi(strarray[2].c_str());
								if (m_sql.m_windunit != WINDUNIT_Beaufort)
								{
									sprintf(szTmp, "%.1f", float(intSpeed) * m_sql.m_windscale);
								}
								else
								{
									float windms = float(intSpeed) * 0.1F;
									sprintf(szTmp, "%d", MStoBeaufort(windms));
								}
								root["result"][ii]["Speed"] = szTmp;
							}

							// if (dSubType!=sTypeWIND6) //problem in RFXCOM firmware? gust=speed?
							{
								int intGust = atoi(strarray[3].c_str());
								if (m_sql.m_windunit != WINDUNIT_Beaufort)
								{
									sprintf(szTmp, "%.1f", float(intGust) * m_sql.m_windscale);
								}
								else
								{
									float gustms = float(intGust) * 0.1F;
									sprintf(szTmp, "%d", MStoBeaufort(gustms));
								}
								root["result"][ii]["Gust"] = szTmp;
							}
							if ((dSubType == sTypeWIND4) || (dSubType == sTypeWINDNoTemp))
							{
								if (dSubType == sTypeWIND4)
								{
									double tvalue = ConvertTemperature(atof(strarray[4].c_str()), tempsign);
									root["result"][ii]["Temp"] = tvalue;
								}
								double tvalue = ConvertTemperature(atof(strarray[5].c_str()), tempsign);
								root["result"][ii]["Chill"] = tvalue;

								_tTrendCalculator::_eTendencyType tstate = _tTrendCalculator::_eTendencyType::TENDENCY_UNKNOWN;
								uint64_t tID = ((uint64_t)(hardwareID & 0x7FFFFFFF) << 32) | (devIdx & 0x7FFFFFFF);
								if (m_mainworker.m_trend_calculator.find(tID) != m_mainworker.m_trend_calculator.end())
								{
									tstate = m_mainworker.m_trend_calculator[tID].m_state;
								}
								root["result"][ii]["trend"] = (int)tstate;
							}
							root["result"][ii]["Data"] = sValue;
							root["result"][ii]["HaveTimeout"] = bHaveTimeout;
						}
					}
					else if (dType == pTypeRAIN)
					{
						std::vector<std::string> strarray;
						StringSplit(sValue, ";", strarray);
						if (strarray.size() == 2)
						{
							// get lowest value of today, and max rate
							time_t now = mytime(nullptr);
							struct tm ltime;
							localtime_r(&now, &ltime);
							char szDate[40];
							sprintf(szDate, "%04d-%02d-%02d", ltime.tm_year + 1900, ltime.tm_mon + 1, ltime.tm_mday);

							std::vector<std::vector<std::string>> result2;

							if (dSubType == sTypeRAINWU || dSubType == sTypeRAINByRate)
							{
								result2 = m_sql.safe_query("SELECT Total, Rate FROM Rain WHERE (DeviceRowID='%q' AND Date>='%q') ORDER BY ROWID DESC LIMIT 1",
											   sd[0].c_str(), szDate);
							}
							else
							{
								result2 = m_sql.safe_query("SELECT MIN(Total), MAX(Total) FROM Rain WHERE (DeviceRowID='%q' AND Date>='%q')", sd[0].c_str(), szDate);
							}

							if (!result2.empty())
							{
								double total_real = 0;
								float rate = 0;
								std::vector<std::string> sd2 = result2[0];

								if (dSubType == sTypeRAINWU || dSubType == sTypeRAINByRate)
								{
									total_real = atof(sd2[0].c_str());
								}
								else
								{
									double total_min = atof(sd2[0].c_str());
									double total_max = atof(strarray[1].c_str());
									total_real = total_max - total_min;
								}

								total_real *= AddjMulti;
								if (dSubType == sTypeRAINByRate)
								{
									rate = static_cast<float>(atof(sd2[1].c_str()) / 10000.0F);
								}
								else
								{
									rate = (static_cast<float>(atof(strarray[0].c_str())) / 100.0F) * float(AddjMulti);
								}

								sprintf(szTmp, "%.1f", total_real);
								root["result"][ii]["Rain"] = szTmp;
								sprintf(szTmp, "%g", rate);
								root["result"][ii]["RainRate"] = szTmp;
								root["result"][ii]["Data"] = sValue;
								root["result"][ii]["HaveTimeout"] = bHaveTimeout;
							}
							else
							{
								root["result"][ii]["Rain"] = "0";
								root["result"][ii]["RainRate"] = "0";
								root["result"][ii]["Data"] = "0";
								root["result"][ii]["HaveTimeout"] = bHaveTimeout;
							}
						}
					}
					else if (dType == pTypeRFXMeter)
					{
						std::string ValueQuantity = options["ValueQuantity"];
						std::string ValueUnits = options["ValueUnits"];

						if (ValueQuantity.empty())
						{
							ValueQuantity.assign("Count");
						}
						if (ValueUnits.empty())
						{
							ValueUnits.assign("");
						}

						// get value of today
						time_t now = mytime(nullptr);
						struct tm ltime;
						localtime_r(&now, &ltime);
						char szDate[40];
						sprintf(szDate, "%04d-%02d-%02d", ltime.tm_year + 1900, ltime.tm_mon + 1, ltime.tm_mday);

						std::vector<std::vector<std::string>> result2;
						strcpy(szTmp, "0");
						result2 = m_sql.safe_query("SELECT MIN(Value) FROM Meter WHERE (DeviceRowID='%q' AND Date>='%q')", sd[0].c_str(), szDate);
						if (!result2.empty())
						{
							std::vector<std::string> sd2 = result2[0];

							uint64_t total_min = std::stoull(sd2[0]);
							uint64_t total_max = std::stoull(sValue);
							uint64_t total_real = total_max - total_min;
							sprintf(szTmp, "%" PRIu64, total_real);

							float divider = m_sql.GetCounterDivider(int(metertype), int(dType), float(AddjValue2));

							float musage = 0.0F;
							switch (metertype)
							{
								case MTYPE_ENERGY:
								case MTYPE_ENERGY_GENERATED:
									musage = float(total_real) / divider;
									sprintf(szTmp, "%.3f kWh", musage);
									break;
								case MTYPE_GAS:
									musage = float(total_real) / divider;
									sprintf(szTmp, "%.3f m3", musage);
									break;
								case MTYPE_WATER:
									musage = float(total_real) / (divider / 1000.0F);
									sprintf(szTmp, "%d Liter", round(musage));
									break;
								case MTYPE_COUNTER:
									musage = float(total_real) / divider;
									sprintf(szTmp, "%g", musage);
									if (!ValueUnits.empty())
									{
										strcat(szTmp, " ");
										strcat(szTmp, ValueUnits.c_str());
									}
									break;
								default:
									strcpy(szTmp, "?");
									break;
							}
						}
						root["result"][ii]["CounterToday"] = szTmp;

						root["result"][ii]["SwitchTypeVal"] = metertype;
						root["result"][ii]["HaveTimeout"] = bHaveTimeout;
						root["result"][ii]["ValueQuantity"] = "";
						root["result"][ii]["ValueUnits"] = "";

						double meteroffset = AddjValue;
						float divider = m_sql.GetCounterDivider(int(metertype), int(dType), float(AddjValue2));

						double dvalue = static_cast<double>(atof(sValue.c_str()));

						switch (metertype)
						{
							case MTYPE_ENERGY:
							case MTYPE_ENERGY_GENERATED:
								sprintf(szTmp, "%.3f kWh", meteroffset + (dvalue / divider));
								root["result"][ii]["Data"] = szTmp;
								root["result"][ii]["Counter"] = szTmp;
								break;
							case MTYPE_GAS:
								sprintf(szTmp, "%.3f m3", meteroffset + (dvalue / divider));
								root["result"][ii]["Data"] = szTmp;
								root["result"][ii]["Counter"] = szTmp;
								break;
							case MTYPE_WATER:
								sprintf(szTmp, "%.3f m3", meteroffset + (dvalue / divider));
								root["result"][ii]["Data"] = szTmp;
								root["result"][ii]["Counter"] = szTmp;
								break;
							case MTYPE_COUNTER:
								sprintf(szTmp, "%g %s", meteroffset + (dvalue / divider), ValueUnits.c_str());
								root["result"][ii]["Data"] = szTmp;
								root["result"][ii]["Counter"] = szTmp;
								root["result"][ii]["ValueQuantity"] = ValueQuantity;
								root["result"][ii]["ValueUnits"] = ValueUnits;
								break;
							default:
								root["result"][ii]["Data"] = "?";
								root["result"][ii]["Counter"] = "?";
								root["result"][ii]["ValueQuantity"] = ValueQuantity;
								root["result"][ii]["ValueUnits"] = ValueUnits;
								break;
						}
					}
					else if ((dType == pTypeGeneral) && (dSubType == sTypeCounterIncremental))
					{
						std::string ValueQuantity = options["ValueQuantity"];
						std::string ValueUnits = options["ValueUnits"];
						if (ValueQuantity.empty())
						{
							ValueQuantity.assign("Count");
						}
						if (ValueUnits.empty())
						{
							ValueUnits.assign("");
						}
						float divider = m_sql.GetCounterDivider(int(metertype), int(dType), float(AddjValue2));

						// get value of today
						time_t now = mytime(nullptr);
						struct tm ltime;
						localtime_r(&now, &ltime);
						char szDate[40];
						sprintf(szDate, "%04d-%02d-%02d", ltime.tm_year + 1900, ltime.tm_mon + 1, ltime.tm_mday);

						std::vector<std::vector<std::string>> result2;
						strcpy(szTmp, "0");
						result2 = m_sql.safe_query("SELECT MIN(Value) FROM Meter WHERE (DeviceRowID='%q' AND Date>='%q')", sd[0].c_str(), szDate);
						if (!result2.empty())
						{
							std::vector<std::string> sd2 = result2[0];

							uint64_t total_min = std::stoull(sd2[0]);
							uint64_t total_max = std::stoull(sValue);
							uint64_t total_real = total_max - total_min;
							sprintf(szTmp, "%" PRIu64, total_real);

							float musage = 0;
							switch (metertype)
							{
								case MTYPE_ENERGY:
								case MTYPE_ENERGY_GENERATED:
									musage = float(total_real) / divider;
									sprintf(szTmp, "%.3f kWh", musage);
									break;
								case MTYPE_GAS:
									musage = float(total_real) / divider;
									sprintf(szTmp, "%.3f m3", musage);
									break;
								case MTYPE_WATER:
									musage = float(total_real) / divider;
									sprintf(szTmp, "%.3f m3", musage);
									break;
								case MTYPE_COUNTER:
									sprintf(szTmp, "%g", float(total_real) / divider);
									if (!ValueUnits.empty())
									{
										strcat(szTmp, " ");
										strcat(szTmp, ValueUnits.c_str());
									}
									break;
								default:
									strcpy(szTmp, "0");
									break;
							}
						}
						root["result"][ii]["Counter"] = sValue;
						root["result"][ii]["CounterToday"] = szTmp;
						root["result"][ii]["SwitchTypeVal"] = metertype;
						root["result"][ii]["HaveTimeout"] = bHaveTimeout;
						root["result"][ii]["TypeImg"] = "counter";
						root["result"][ii]["ValueQuantity"] = "";
						root["result"][ii]["ValueUnits"] = "";
						double dvalue = static_cast<double>(atof(sValue.c_str()));
						double meteroffset = AddjValue;

						switch (metertype)
						{
							case MTYPE_ENERGY:
							case MTYPE_ENERGY_GENERATED:
								sprintf(szTmp, "%.3f kWh", meteroffset + (dvalue / divider));
								root["result"][ii]["Data"] = szTmp;
								root["result"][ii]["Counter"] = szTmp;
								break;
							case MTYPE_GAS:
								sprintf(szTmp, "%.3f m3", meteroffset + (dvalue / divider));
								root["result"][ii]["Data"] = szTmp;
								root["result"][ii]["Counter"] = szTmp;
								break;
							case MTYPE_WATER:
								sprintf(szTmp, "%.3f m3", meteroffset + (dvalue / divider));
								root["result"][ii]["Data"] = szTmp;
								root["result"][ii]["Counter"] = szTmp;
								break;
							case MTYPE_COUNTER:
								sprintf(szTmp, "%g %s", meteroffset + (dvalue / divider), ValueUnits.c_str());
								root["result"][ii]["Data"] = szTmp;
								root["result"][ii]["Counter"] = szTmp;
								root["result"][ii]["ValueQuantity"] = ValueQuantity;
								root["result"][ii]["ValueUnits"] = ValueUnits;
								break;
							default:
								root["result"][ii]["Data"] = "?";
								root["result"][ii]["Counter"] = "?";
								root["result"][ii]["ValueQuantity"] = ValueQuantity;
								root["result"][ii]["ValueUnits"] = ValueUnits;
								break;
						}
					}
					else if ((dType == pTypeGeneral) && (dSubType == sTypeManagedCounter))
					{
						std::string ValueQuantity = options["ValueQuantity"];
						std::string ValueUnits = options["ValueUnits"];
						if (ValueQuantity.empty())
						{
							ValueQuantity.assign("Count");
						}
						if (ValueUnits.empty())
						{
							ValueUnits.assign("");
						}
						float divider = m_sql.GetCounterDivider(int(metertype), int(dType), float(AddjValue2));

						std::vector<std::string> splitresults;
						StringSplit(sValue, ";", splitresults);
						double dvalue;
						if (splitresults.size() < 2)
						{
							dvalue = static_cast<double>(atof(sValue.c_str()));
						}
						else
						{
							dvalue = static_cast<double>(atof(splitresults[1].c_str()));
							if (dvalue < 0.0)
							{
								dvalue = static_cast<double>(atof(splitresults[0].c_str()));
							}
						}
						root["result"][ii]["Data"] = root["result"][ii]["Counter"];

						root["result"][ii]["SwitchTypeVal"] = metertype;
						root["result"][ii]["HaveTimeout"] = bHaveTimeout;
						root["result"][ii]["TypeImg"] = "counter";
						root["result"][ii]["ValueQuantity"] = "";
						root["result"][ii]["ValueUnits"] = "";
						root["result"][ii]["ShowNotifications"] = false;
						double meteroffset = AddjValue;

						switch (metertype)
						{
							case MTYPE_ENERGY:
							case MTYPE_ENERGY_GENERATED:
								sprintf(szTmp, "%.3f kWh", meteroffset + (dvalue / divider));
								root["result"][ii]["Data"] = szTmp;
								root["result"][ii]["Counter"] = szTmp;
								break;
							case MTYPE_GAS:
								sprintf(szTmp, "%.3f m3", meteroffset + (dvalue / divider));
								root["result"][ii]["Data"] = szTmp;
								root["result"][ii]["Counter"] = szTmp;
								break;
							case MTYPE_WATER:
								sprintf(szTmp, "%.3f m3", meteroffset + (dvalue / divider));
								root["result"][ii]["Data"] = szTmp;
								root["result"][ii]["Counter"] = szTmp;
								break;
							case MTYPE_COUNTER:
								sprintf(szTmp, "%g %s", meteroffset + (dvalue / divider), ValueUnits.c_str());
								root["result"][ii]["Data"] = szTmp;
								root["result"][ii]["Counter"] = szTmp;
								root["result"][ii]["ValueQuantity"] = ValueQuantity;
								root["result"][ii]["ValueUnits"] = ValueUnits;
								break;
							default:
								root["result"][ii]["Data"] = "?";
								root["result"][ii]["Counter"] = "?";
								root["result"][ii]["ValueQuantity"] = ValueQuantity;
								root["result"][ii]["ValueUnits"] = ValueUnits;
								break;
						}
					}
					else if (dType == pTypeYouLess)
					{
						std::string ValueQuantity = options["ValueQuantity"];
						std::string ValueUnits = options["ValueUnits"];
						float musage = 0;
						if (ValueQuantity.empty())
						{
							ValueQuantity.assign("Count");
						}
						if (ValueUnits.empty())
						{
							ValueUnits.assign("");
						}
						float divider = m_sql.GetCounterDivider(int(metertype), int(dType), float(AddjValue2));

						// get value of today
						time_t now = mytime(nullptr);
						struct tm ltime;
						localtime_r(&now, &ltime);
						char szDate[40];
						sprintf(szDate, "%04d-%02d-%02d", ltime.tm_year + 1900, ltime.tm_mon + 1, ltime.tm_mday);

						std::vector<std::vector<std::string>> result2;
						strcpy(szTmp, "0");
						result2 = m_sql.safe_query("SELECT MIN(Value), MAX(Value) FROM Meter WHERE (DeviceRowID='%q' AND Date>='%q')", sd[0].c_str(), szDate);
						if (!result2.empty())
						{
							std::vector<std::string> sd2 = result2[0];

							unsigned long long total_min = std::strtoull(sd2[0].c_str(), nullptr, 10);
							unsigned long long total_max = std::strtoull(sd2[1].c_str(), nullptr, 10);
							unsigned long long total_real;

							total_real = total_max - total_min;
							sprintf(szTmp, "%lld", total_real);

							musage = 0;
							switch (metertype)
							{
								case MTYPE_ENERGY:
								case MTYPE_ENERGY_GENERATED:
									musage = float(total_real) / divider;
									sprintf(szTmp, "%.3f kWh", musage);
									break;
								case MTYPE_GAS:
									musage = float(total_real) / divider;
									sprintf(szTmp, "%.3f m3", musage);
									break;
								case MTYPE_WATER:
									musage = float(total_real) / divider;
									sprintf(szTmp, "%.3f m3", musage);
									break;
								case MTYPE_COUNTER:
									sprintf(szTmp, "%g %s", float(total_real) / divider, ValueUnits.c_str());
									break;
								default:
									strcpy(szTmp, "0");
									break;
							}
						}
						root["result"][ii]["CounterToday"] = szTmp;

						std::vector<std::string> splitresults;
						StringSplit(sValue, ";", splitresults);
						if (splitresults.size() < 2)
							continue;

						unsigned long long total_actual = std::strtoull(splitresults[0].c_str(), nullptr, 10);
						musage = 0;
						switch (metertype)
						{
							case MTYPE_ENERGY:
							case MTYPE_ENERGY_GENERATED:
								musage = float(total_actual) / divider;
								sprintf(szTmp, "%.03f", musage);
								break;
							case MTYPE_GAS:
							case MTYPE_WATER:
								musage = float(total_actual) / divider;
								sprintf(szTmp, "%.03f", musage);
								break;
							case MTYPE_COUNTER:
								sprintf(szTmp, "%g", float(total_actual) / divider);
								break;
							default:
								strcpy(szTmp, "0");
								break;
						}
						root["result"][ii]["Counter"] = szTmp;

						root["result"][ii]["SwitchTypeVal"] = metertype;

						unsigned long long acounter = std::strtoull(sValue.c_str(), nullptr, 10);
						musage = 0;
						switch (metertype)
						{
							case MTYPE_ENERGY:
							case MTYPE_ENERGY_GENERATED:
								musage = float(acounter) / divider;
								sprintf(szTmp, "%.3f kWh %s Watt", musage, splitresults[1].c_str());
								break;
							case MTYPE_GAS:
								musage = float(acounter) / divider;
								sprintf(szTmp, "%.3f m3", musage);
								break;
							case MTYPE_WATER:
								musage = float(acounter) / divider;
								sprintf(szTmp, "%.3f m3", musage);
								break;
							case MTYPE_COUNTER:
								sprintf(szTmp, "%g %s", float(acounter) / divider, ValueUnits.c_str());
								break;
							default:
								strcpy(szTmp, "0");
								break;
						}
						root["result"][ii]["Data"] = szTmp;
						root["result"][ii]["ValueQuantity"] = "";
						root["result"][ii]["ValueUnits"] = "";
						switch (metertype)
						{
							case MTYPE_ENERGY:
							case MTYPE_ENERGY_GENERATED:
								sprintf(szTmp, "%s Watt", splitresults[1].c_str());
								break;
							case MTYPE_GAS:
								sprintf(szTmp, "%s m3", splitresults[1].c_str());
								break;
							case MTYPE_WATER:
								sprintf(szTmp, "%s m3", splitresults[1].c_str());
								break;
							case MTYPE_COUNTER:
								sprintf(szTmp, "%s", splitresults[1].c_str());
								root["result"][ii]["ValueQuantity"] = ValueQuantity;
								root["result"][ii]["ValueUnits"] = ValueUnits;
								break;
							default:
								strcpy(szTmp, "0");
								break;
						}

						root["result"][ii]["Usage"] = szTmp;
						root["result"][ii]["HaveTimeout"] = bHaveTimeout;
					}
					else if (dType == pTypeP1Power)
					{
						std::vector<std::string> splitresults;
						StringSplit(sValue, ";", splitresults);
						if (splitresults.size() != 6)
						{
							root["result"][ii]["SwitchTypeVal"] = MTYPE_ENERGY;
							root["result"][ii]["Counter"] = "0";
							root["result"][ii]["CounterDeliv"] = "0";
							root["result"][ii]["Usage"] = "Invalid";
							root["result"][ii]["UsageDeliv"] = "Invalid";
							root["result"][ii]["Data"] = "Invalid!: " + sValue;
							root["result"][ii]["HaveTimeout"] = true;
							root["result"][ii]["CounterToday"] = "Invalid";
							root["result"][ii]["CounterDelivToday"] = "Invalid";
						}
						else
						{
							float EnergyDivider = 1000.0F;
							int tValue;
							if (m_sql.GetPreferencesVar("MeterDividerEnergy", tValue))
							{
								EnergyDivider = float(tValue);
							}

							unsigned long long powerusage1 = std::strtoull(splitresults[0].c_str(), nullptr, 10);
							unsigned long long powerusage2 = std::strtoull(splitresults[1].c_str(), nullptr, 10);
							unsigned long long powerdeliv1 = std::strtoull(splitresults[2].c_str(), nullptr, 10);
							unsigned long long powerdeliv2 = std::strtoull(splitresults[3].c_str(), nullptr, 10);
							unsigned long long usagecurrent = std::strtoull(splitresults[4].c_str(), nullptr, 10);
							unsigned long long delivcurrent = std::strtoull(splitresults[5].c_str(), nullptr, 10);

							powerdeliv1 = (powerdeliv1 < 10) ? 0 : powerdeliv1;
							powerdeliv2 = (powerdeliv2 < 10) ? 0 : powerdeliv2;

							unsigned long long powerusage = powerusage1 + powerusage2;
							unsigned long long powerdeliv = powerdeliv1 + powerdeliv2;
							if (powerdeliv < 2)
								powerdeliv = 0;

							double musage = 0;

							root["result"][ii]["SwitchTypeVal"] = MTYPE_ENERGY;
							musage = double(powerusage) / EnergyDivider;
							sprintf(szTmp, "%.03f", musage);
							root["result"][ii]["Counter"] = szTmp;
							musage = double(powerdeliv) / EnergyDivider;
							sprintf(szTmp, "%.03f", musage);
							root["result"][ii]["CounterDeliv"] = szTmp;

							if (bHaveTimeout)
							{
								usagecurrent = 0;
								delivcurrent = 0;
							}
							sprintf(szTmp, "%llu Watt", usagecurrent);
							root["result"][ii]["Usage"] = szTmp;
							sprintf(szTmp, "%llu Watt", delivcurrent);
							root["result"][ii]["UsageDeliv"] = szTmp;
							root["result"][ii]["Data"] = sValue;
							root["result"][ii]["HaveTimeout"] = bHaveTimeout;

							// get value of today
							time_t now = mytime(nullptr);
							struct tm ltime;
							localtime_r(&now, &ltime);
							char szDate[40];
							sprintf(szDate, "%04d-%02d-%02d", ltime.tm_year + 1900, ltime.tm_mon + 1, ltime.tm_mday);

							std::vector<std::vector<std::string>> result2;
							strcpy(szTmp, "0");
							result2 = m_sql.safe_query("SELECT MIN(Value1), MIN(Value2), MIN(Value5), MIN(Value6) FROM MultiMeter WHERE (DeviceRowID='%q' AND Date>='%q')",
										   sd[0].c_str(), szDate);
							if (!result2.empty())
							{
								std::vector<std::string> sd2 = result2[0];

								unsigned long long total_min_usage_1 = std::strtoull(sd2[0].c_str(), nullptr, 10);
								unsigned long long total_min_deliv_1 = std::strtoull(sd2[1].c_str(), nullptr, 10);
								unsigned long long total_min_usage_2 = std::strtoull(sd2[2].c_str(), nullptr, 10);
								unsigned long long total_min_deliv_2 = std::strtoull(sd2[3].c_str(), nullptr, 10);
								unsigned long long total_real_usage, total_real_deliv;

								total_real_usage = powerusage - (total_min_usage_1 + total_min_usage_2);
								total_real_deliv = powerdeliv - (total_min_deliv_1 + total_min_deliv_2);

								musage = double(total_real_usage) / EnergyDivider;
								sprintf(szTmp, "%.3f kWh", musage);
								root["result"][ii]["CounterToday"] = szTmp;
								musage = double(total_real_deliv) / EnergyDivider;
								sprintf(szTmp, "%.3f kWh", musage);
								root["result"][ii]["CounterDelivToday"] = szTmp;
							}
							else
							{
								sprintf(szTmp, "%.3f kWh", 0.0F);
								root["result"][ii]["CounterToday"] = szTmp;
								root["result"][ii]["CounterDelivToday"] = szTmp;
							}
						}
					}
					else if (dType == pTypeP1Gas)
					{
						root["result"][ii]["SwitchTypeVal"] = MTYPE_GAS;

						// get lowest value of today
						time_t now = mytime(nullptr);
						struct tm ltime;
						localtime_r(&now, &ltime);
						char szDate[40];
						sprintf(szDate, "%04d-%02d-%02d", ltime.tm_year + 1900, ltime.tm_mon + 1, ltime.tm_mday);

						std::vector<std::vector<std::string>> result2;

						float divider = m_sql.GetCounterDivider(int(metertype), int(dType), float(AddjValue2));

						strcpy(szTmp, "0");
						result2 = m_sql.safe_query("SELECT MIN(Value) FROM Meter WHERE (DeviceRowID='%q' AND Date>='%q')", sd[0].c_str(), szDate);
						if (!result2.empty())
						{
							std::vector<std::string> sd2 = result2[0];

							uint64_t total_min_gas = std::stoull(sd2[0]);
							uint64_t gasactual;
							try
							{
								gasactual = std::stoull(sValue);
							}
							catch (std::invalid_argument e)
							{
								_log.Log(LOG_ERROR, "Gas - invalid value: '%s'", sValue.c_str());
								continue;
							}
							uint64_t total_real_gas = gasactual - total_min_gas;

							double musage = double(gasactual) / divider;
							sprintf(szTmp, "%.03f", musage);
							root["result"][ii]["Counter"] = szTmp;
							musage = double(total_real_gas) / divider;
							sprintf(szTmp, "%.03f m3", musage);
							root["result"][ii]["CounterToday"] = szTmp;
							root["result"][ii]["HaveTimeout"] = bHaveTimeout;
							sprintf(szTmp, "%.03f", atof(sValue.c_str()) / divider);
							root["result"][ii]["Data"] = szTmp;
						}
						else
						{
							sprintf(szTmp, "%.03f", 0.0F);
							root["result"][ii]["Counter"] = szTmp;
							sprintf(szTmp, "%.03f m3", 0.0F);
							root["result"][ii]["CounterToday"] = szTmp;
							sprintf(szTmp, "%.03f", atof(sValue.c_str()) / divider);
							root["result"][ii]["Data"] = szTmp;
							root["result"][ii]["HaveTimeout"] = bHaveTimeout;
						}
					}
					else if (dType == pTypeCURRENT)
					{
						std::vector<std::string> strarray;
						StringSplit(sValue, ";", strarray);
						if (strarray.size() == 3)
						{
							// CM113
							int displaytype = 0;
							int voltage = 230;
							m_sql.GetPreferencesVar("CM113DisplayType", displaytype);
							m_sql.GetPreferencesVar("ElectricVoltage", voltage);

							double val1 = atof(strarray[0].c_str());
							double val2 = atof(strarray[1].c_str());
							double val3 = atof(strarray[2].c_str());

							if (displaytype == 0)
							{
								if ((val2 == 0) && (val3 == 0))
									sprintf(szData, "%.1f A", val1);
								else
									sprintf(szData, "%.1f A, %.1f A, %.1f A", val1, val2, val3);
							}
							else
							{
								if ((val2 == 0) && (val3 == 0))
									sprintf(szData, "%d Watt", int(val1 * voltage));
								else
									sprintf(szData, "%d Watt, %d Watt, %d Watt", int(val1 * voltage), int(val2 * voltage), int(val3 * voltage));
							}
							root["result"][ii]["Data"] = szData;
							root["result"][ii]["displaytype"] = displaytype;
							root["result"][ii]["HaveTimeout"] = bHaveTimeout;
						}
					}
					else if (dType == pTypeCURRENTENERGY)
					{
						std::vector<std::string> strarray;
						StringSplit(sValue, ";", strarray);
						if (strarray.size() == 4)
						{
							// CM180i
							int displaytype = 0;
							int voltage = 230;
							m_sql.GetPreferencesVar("CM113DisplayType", displaytype);
							m_sql.GetPreferencesVar("ElectricVoltage", voltage);

							double total = atof(strarray[3].c_str());
							if (displaytype == 0)
							{
								sprintf(szData, "%.1f A, %.1f A, %.1f A", atof(strarray[0].c_str()), atof(strarray[1].c_str()), atof(strarray[2].c_str()));
							}
							else
							{
								sprintf(szData, "%d Watt, %d Watt, %d Watt", int(atof(strarray[0].c_str()) * voltage), int(atof(strarray[1].c_str()) * voltage),
									int(atof(strarray[2].c_str()) * voltage));
							}
							if (total > 0)
							{
								sprintf(szTmp, ", Total: %.3f kWh", total / 1000.0F);
								strcat(szData, szTmp);
							}
							root["result"][ii]["Data"] = szData;
							root["result"][ii]["displaytype"] = displaytype;
							root["result"][ii]["HaveTimeout"] = bHaveTimeout;
						}
					}
					else if (((dType == pTypeENERGY) || (dType == pTypePOWER)) || ((dType == pTypeGeneral) && (dSubType == sTypeKwh)))
					{
						std::vector<std::string> strarray;
						StringSplit(sValue, ";", strarray);
						if (strarray.size() == 2)
						{
							double total = atof(strarray[1].c_str()) / 1000;

							time_t now = mytime(nullptr);
							struct tm ltime;
							localtime_r(&now, &ltime);
							char szDate[40];
							sprintf(szDate, "%04d-%02d-%02d", ltime.tm_year + 1900, ltime.tm_mon + 1, ltime.tm_mday);

							std::vector<std::vector<std::string>> result2;
							strcpy(szTmp, "0");
							// get the first value of the day instead of the minimum value, because counter can also decrease
							// result2 = m_sql.safe_query("SELECT MIN(Value) FROM Meter WHERE (DeviceRowID='%q' AND Date>='%q')",
							result2 = m_sql.safe_query("SELECT Value FROM Meter WHERE (DeviceRowID='%q' AND Date>='%q') ORDER BY Date LIMIT 1", sd[0].c_str(), szDate);
							if (!result2.empty())
							{
								float divider = m_sql.GetCounterDivider(int(metertype), int(dType), float(AddjValue2));

								std::vector<std::string> sd2 = result2[0];
								double minimum = atof(sd2[0].c_str()) / divider;

								sprintf(szData, "%.3f kWh", total);
								root["result"][ii]["Data"] = szData;
								if ((dType == pTypeENERGY) || (dType == pTypePOWER))
								{
									sprintf(szData, "%ld Watt", atol(strarray[0].c_str()));
								}
								else
								{
									sprintf(szData, "%g Watt", atof(strarray[0].c_str()));
								}
								root["result"][ii]["Usage"] = szData;
								root["result"][ii]["HaveTimeout"] = bHaveTimeout;
								sprintf(szTmp, "%.3f kWh", total - minimum);
								root["result"][ii]["CounterToday"] = szTmp;
							}
							else
							{
								sprintf(szData, "%.3f kWh", total);
								root["result"][ii]["Data"] = szData;
								if ((dType == pTypeENERGY) || (dType == pTypePOWER))
								{
									sprintf(szData, "%ld Watt", atol(strarray[0].c_str()));
								}
								else
								{
									sprintf(szData, "%g Watt", atof(strarray[0].c_str()));
								}
								root["result"][ii]["Usage"] = szData;
								root["result"][ii]["HaveTimeout"] = bHaveTimeout;
								sprintf(szTmp, "%d kWh", 0);
								root["result"][ii]["CounterToday"] = szTmp;
							}
						}
						root["result"][ii]["TypeImg"] = "current";
						root["result"][ii]["SwitchTypeVal"] = switchtype;		    // MTYPE_ENERGY
						root["result"][ii]["EnergyMeterMode"] = options["EnergyMeterMode"]; // for alternate Energy Reading
					}
					else if (dType == pTypeAirQuality)
					{
						if (bHaveTimeout)
							nValue = 0;
						sprintf(szTmp, "%d ppm", nValue);
						root["result"][ii]["Data"] = szTmp;
						root["result"][ii]["HaveTimeout"] = bHaveTimeout;
						int airquality = nValue;
						if (airquality < 700)
							root["result"][ii]["Quality"] = "Excellent";
						else if (airquality < 900)
							root["result"][ii]["Quality"] = "Good";
						else if (airquality < 1100)
							root["result"][ii]["Quality"] = "Fair";
						else if (airquality < 1600)
							root["result"][ii]["Quality"] = "Mediocre";
						else
							root["result"][ii]["Quality"] = "Bad";
					}
					else if (dType == pTypeThermostat)
					{
						if (dSubType == sTypeThermSetpoint)
						{
							bHasTimers = m_sql.HasTimers(sd[0]);

							double tempCelcius = atof(sValue.c_str());
							double temp = ConvertTemperature(tempCelcius, tempsign);

							sprintf(szTmp, "%.1f", temp);
							root["result"][ii]["Data"] = szTmp;
							root["result"][ii]["SetPoint"] = szTmp;
							root["result"][ii]["HaveTimeout"] = bHaveTimeout;
							root["result"][ii]["TypeImg"] = "override_mini";
						}
					}
					else if (dType == pTypeRadiator1)
					{
						if (dSubType == sTypeSmartwares)
						{
							bHasTimers = m_sql.HasTimers(sd[0]);

							double tempCelcius = atof(sValue.c_str());
							double temp = ConvertTemperature(tempCelcius, tempsign);

							sprintf(szTmp, "%.1f", temp);
							root["result"][ii]["Data"] = szTmp;
							root["result"][ii]["SetPoint"] = szTmp;
							root["result"][ii]["HaveTimeout"] = false; // this device does not provide feedback, so no timeout!
							root["result"][ii]["TypeImg"] = "override_mini";
						}
					}
					else if (dType == pTypeGeneral)
					{
						if (dSubType == sTypeVisibility)
						{
							float vis = static_cast<float>(atof(sValue.c_str()));
							if (metertype == 0)
							{
								// km
								sprintf(szTmp, "%.1f km", vis);
							}
							else
							{
								// miles
								sprintf(szTmp, "%.1f mi", vis * 0.6214F);
							}
							root["result"][ii]["Data"] = szTmp;
							root["result"][ii]["Visibility"] = atof(sValue.c_str());
							root["result"][ii]["HaveTimeout"] = bHaveTimeout;
							root["result"][ii]["TypeImg"] = "visibility";
							root["result"][ii]["SwitchTypeVal"] = metertype;
						}
						else if (dSubType == sTypeDistance)
						{
							float vis = static_cast<float>(atof(sValue.c_str()));
							if (metertype == 0)
							{
								// Metric
								sprintf(szTmp, "%.1f cm", vis);
							}
							else
							{
								// Imperial
								sprintf(szTmp, "%.1f in", vis * 0.3937007874015748F);
							}
							root["result"][ii]["Data"] = szTmp;
							root["result"][ii]["HaveTimeout"] = bHaveTimeout;
							root["result"][ii]["TypeImg"] = "visibility";
							root["result"][ii]["SwitchTypeVal"] = metertype;
						}
						else if (dSubType == sTypeSolarRadiation)
						{
							float radiation = static_cast<float>(atof(sValue.c_str()));
							sprintf(szTmp, "%.1f Watt/m2", radiation);
							root["result"][ii]["Data"] = szTmp;
							root["result"][ii]["Radiation"] = atof(sValue.c_str());
							root["result"][ii]["HaveTimeout"] = bHaveTimeout;
							root["result"][ii]["TypeImg"] = "radiation";
							root["result"][ii]["SwitchTypeVal"] = metertype;
						}
						else if (dSubType == sTypeSoilMoisture)
						{
							sprintf(szTmp, "%d cb", nValue);
							root["result"][ii]["Data"] = szTmp;
							root["result"][ii]["Desc"] = Get_Moisture_Desc(nValue);
							root["result"][ii]["TypeImg"] = "moisture";
							root["result"][ii]["HaveTimeout"] = bHaveTimeout;
							root["result"][ii]["SwitchTypeVal"] = metertype;
						}
						else if (dSubType == sTypeLeafWetness)
						{
							sprintf(szTmp, "%d", nValue);
							root["result"][ii]["Data"] = szTmp;
							root["result"][ii]["TypeImg"] = "leaf";
							root["result"][ii]["HaveTimeout"] = bHaveTimeout;
							root["result"][ii]["SwitchTypeVal"] = metertype;
						}
						else if (dSubType == sTypeSystemTemp)
						{
							double tvalue = ConvertTemperature(atof(sValue.c_str()), tempsign);
							root["result"][ii]["Temp"] = tvalue;
							sprintf(szData, "%.1f %c", tvalue, tempsign);
							root["result"][ii]["Data"] = szData;
							root["result"][ii]["HaveTimeout"] = bHaveTimeout;
							root["result"][ii]["Image"] = "Computer";
							root["result"][ii]["TypeImg"] = "temperature";
							root["result"][ii]["Type"] = "temperature";
							_tTrendCalculator::_eTendencyType tstate = _tTrendCalculator::_eTendencyType::TENDENCY_UNKNOWN;
							uint64_t tID = ((uint64_t)(hardwareID & 0x7FFFFFFF) << 32) | (devIdx & 0x7FFFFFFF);
							if (m_mainworker.m_trend_calculator.find(tID) != m_mainworker.m_trend_calculator.end())
							{
								tstate = m_mainworker.m_trend_calculator[tID].m_state;
							}
							root["result"][ii]["trend"] = (int)tstate;
						}
						else if (dSubType == sTypePercentage)
						{
							sprintf(szData, "%g%%", atof(sValue.c_str()));
							root["result"][ii]["Data"] = szData;
							root["result"][ii]["HaveTimeout"] = bHaveTimeout;
							// root["result"][ii]["Image"] = "Computer";
							root["result"][ii]["TypeImg"] = "hardware";
						}
						else if (dSubType == sTypeWaterflow)
						{
							sprintf(szData, "%g l/min", atof(sValue.c_str()));
							root["result"][ii]["Data"] = szData;
							root["result"][ii]["HaveTimeout"] = bHaveTimeout;
							root["result"][ii]["Image"] = "Moisture";
							root["result"][ii]["TypeImg"] = "moisture";
						}
						else if (dSubType == sTypeCustom)
						{
							std::string szAxesLabel;
							int SensorType = 1;
							std::vector<std::string> sResults;
							StringSplit(sOptions, ";", sResults);

							if (sResults.size() == 2)
							{
								SensorType = atoi(sResults[0].c_str());
								szAxesLabel = sResults[1];
							}
							sprintf(szData, "%g %s", atof(sValue.c_str()), szAxesLabel.c_str());
							root["result"][ii]["Data"] = szData;
							root["result"][ii]["SensorType"] = SensorType;
							root["result"][ii]["SensorUnit"] = szAxesLabel;
							root["result"][ii]["HaveTimeout"] = bHaveTimeout;

							std::string IconFile = "Custom";
							if (CustomImage != 0)
							{
								auto ittIcon = m_custom_light_icons_lookup.find(CustomImage);
								if (ittIcon != m_custom_light_icons_lookup.end())
								{
									IconFile = m_custom_light_icons[ittIcon->second].RootFile;
								}
							}
							root["result"][ii]["Image"] = IconFile;
							root["result"][ii]["TypeImg"] = IconFile;
						}
						else if (dSubType == sTypeFan)
						{
							sprintf(szData, "%d RPM", atoi(sValue.c_str()));
							root["result"][ii]["Data"] = szData;
							root["result"][ii]["HaveTimeout"] = bHaveTimeout;
							root["result"][ii]["Image"] = "Fan";
							root["result"][ii]["TypeImg"] = "Fan";
						}
						else if (dSubType == sTypeSoundLevel)
						{
							sprintf(szData, "%d dB", atoi(sValue.c_str()));
							root["result"][ii]["Data"] = szData;
							root["result"][ii]["TypeImg"] = "Speaker";
							root["result"][ii]["HaveTimeout"] = bHaveTimeout;
						}
						else if (dSubType == sTypeVoltage)
						{
							sprintf(szData, "%g V", atof(sValue.c_str()));
							root["result"][ii]["Data"] = szData;
							root["result"][ii]["TypeImg"] = "current";
							root["result"][ii]["HaveTimeout"] = bHaveTimeout;
							root["result"][ii]["Voltage"] = atof(sValue.c_str());
						}
						else if (dSubType == sTypeCurrent)
						{
							sprintf(szData, "%g A", atof(sValue.c_str()));
							root["result"][ii]["Data"] = szData;
							root["result"][ii]["TypeImg"] = "current";
							root["result"][ii]["HaveTimeout"] = bHaveTimeout;
							root["result"][ii]["Current"] = atof(sValue.c_str());
						}
						else if (dSubType == sTypeTextStatus)
						{
							root["result"][ii]["Data"] = sValue;
							root["result"][ii]["TypeImg"] = "text";
							root["result"][ii]["HaveTimeout"] = false;
							root["result"][ii]["ShowNotifications"] = false;
						}
						else if (dSubType == sTypeAlert)
						{
							if (nValue > 4)
								nValue = 4;
							sprintf(szData, "Level: %d", nValue);
							root["result"][ii]["Data"] = szData;
							if (!sValue.empty())
								root["result"][ii]["Data"] = sValue;
							else
								root["result"][ii]["Data"] = Get_Alert_Desc(nValue);
							root["result"][ii]["TypeImg"] = "Alert";
							root["result"][ii]["Level"] = nValue;
							root["result"][ii]["HaveTimeout"] = false;
						}
						else if (dSubType == sTypePressure)
						{
							sprintf(szData, "%.1f Bar", atof(sValue.c_str()));
							root["result"][ii]["Data"] = szData;
							root["result"][ii]["TypeImg"] = "gauge";
							root["result"][ii]["HaveTimeout"] = bHaveTimeout;
							root["result"][ii]["Pressure"] = atof(sValue.c_str());
						}
						else if (dSubType == sTypeBaro)
						{
							std::vector<std::string> tstrarray;
							StringSplit(sValue, ";", tstrarray);
							if (tstrarray.empty())
								continue;
							sprintf(szData, "%g hPa", atof(tstrarray[0].c_str()));
							root["result"][ii]["Data"] = szData;
							root["result"][ii]["TypeImg"] = "gauge";
							root["result"][ii]["HaveTimeout"] = bHaveTimeout;
							if (tstrarray.size() > 1)
							{
								root["result"][ii]["Barometer"] = atof(tstrarray[0].c_str());
								int forecast = atoi(tstrarray[1].c_str());
								root["result"][ii]["Forecast"] = forecast;
								root["result"][ii]["ForecastStr"] = BMP_Forecast_Desc(forecast);
							}
						}
						else if (dSubType == sTypeZWaveClock)
						{
							std::vector<std::string> tstrarray;
							StringSplit(sValue, ";", tstrarray);
							int day = 0;
							int hour = 0;
							int minute = 0;
							if (tstrarray.size() == 3)
							{
								day = atoi(tstrarray[0].c_str());
								hour = atoi(tstrarray[1].c_str());
								minute = atoi(tstrarray[2].c_str());
							}
							sprintf(szData, "%s %02d:%02d", ZWave_Clock_Days(day), hour, minute);
							root["result"][ii]["DayTime"] = sValue;
							root["result"][ii]["Data"] = szData;
							root["result"][ii]["HaveTimeout"] = bHaveTimeout;
							root["result"][ii]["TypeImg"] = "clock";
						}
						else if (dSubType == sTypeZWaveThermostatMode)
						{
							strcpy(szData, "");
							root["result"][ii]["Mode"] = nValue;
							root["result"][ii]["TypeImg"] = "mode";
							root["result"][ii]["HaveTimeout"] = bHaveTimeout;
							std::string modes;
							// Add supported modes
#ifdef WITH_OPENZWAVE
							if (pHardware)
							{
								if (pHardware->HwdType == HTYPE_OpenZWave)
								{
									COpenZWave *pZWave = reinterpret_cast<COpenZWave *>(pHardware);
									unsigned long ID;
									std::stringstream s_strid;
									s_strid << std::hex << sd[1];
									s_strid >> ID;
									std::vector<std::string> vmodes = pZWave->GetSupportedThermostatModes(ID);
									int smode = 0;
									char szTmp[200];
									for (const auto &mode : vmodes)
									{
										// Value supported
										sprintf(szTmp, "%d;%s;", smode, mode.c_str());
										modes += szTmp;
										smode++;
									}

									if (!vmodes.empty())
									{
										if (nValue < (int)vmodes.size())
										{
											sprintf(szData, "%s", vmodes[nValue].c_str());
										}
									}
								}
							}
#endif
							root["result"][ii]["Data"] = szData;
							root["result"][ii]["Modes"] = modes;
						}
						else if (dSubType == sTypeZWaveThermostatFanMode)
						{
							sprintf(szData, "%s", ZWave_Thermostat_Fan_Modes[nValue]);
							root["result"][ii]["Data"] = szData;
							root["result"][ii]["Mode"] = nValue;
							root["result"][ii]["TypeImg"] = "mode";
							root["result"][ii]["HaveTimeout"] = bHaveTimeout;
							// Add supported modes (add all for now)
							bool bAddedSupportedModes = false;
							std::string modes;
							// Add supported modes
#ifdef WITH_OPENZWAVE
							if (pHardware)
							{
								if (pHardware->HwdType == HTYPE_OpenZWave)
								{
									COpenZWave *pZWave = reinterpret_cast<COpenZWave *>(pHardware);
									unsigned long ID;
									std::stringstream s_strid;
									s_strid << std::hex << sd[1];
									s_strid >> ID;
									modes = pZWave->GetSupportedThermostatFanModes(ID);
									bAddedSupportedModes = !modes.empty();
								}
							}
#endif
							if (!bAddedSupportedModes)
							{
								int smode = 0;
								while (ZWave_Thermostat_Fan_Modes[smode] != nullptr)
								{
									sprintf(szTmp, "%d;%s;", smode, ZWave_Thermostat_Fan_Modes[smode]);
									modes += szTmp;
									smode++;
								}
							}
							root["result"][ii]["Modes"] = modes;
						}
						else if (dSubType == sTypeZWaveThermostatOperatingState)
						{
							strcpy(szData, "");
							root["result"][ii]["State"] = nValue;
							root["result"][ii]["TypeImg"] = "Fan";
							root["result"][ii]["HaveTimeout"] = bHaveTimeout;
							if (nValue == 1)
							{
								sprintf(szData, "%s", "Cooling");
							}
							else if (nValue == 2)
							{
								sprintf(szData, "%s", "Heating");
							}
							else
							{
								sprintf(szData, "%s", "Idle");
							}
							root["result"][ii]["Data"] = szData;
						}
						else if (dSubType == sTypeZWaveAlarm)
						{
							sprintf(szData, "Event: 0x%02X (%d)", nValue, nValue);
							root["result"][ii]["Data"] = szData;
							root["result"][ii]["TypeImg"] = "Alert";
							root["result"][ii]["Level"] = nValue;
							root["result"][ii]["HaveTimeout"] = false;
						}
					}
					else if (dType == pTypeLux)
					{
						sprintf(szTmp, "%.0f Lux", atof(sValue.c_str()));
						root["result"][ii]["Data"] = szTmp;
						root["result"][ii]["HaveTimeout"] = bHaveTimeout;
					}
					else if (dType == pTypeWEIGHT)
					{
						sprintf(szTmp, "%g %s", m_sql.m_weightscale * atof(sValue.c_str()), m_sql.m_weightsign.c_str());
						root["result"][ii]["Data"] = szTmp;
						root["result"][ii]["HaveTimeout"] = false;
						root["result"][ii]["SwitchTypeVal"] = (m_sql.m_weightsign == "kg") ? 0 : 1;
					}
					else if (dType == pTypeUsage)
					{
						if (dSubType == sTypeElectric)
						{
							sprintf(szData, "%g Watt", atof(sValue.c_str()));
							root["result"][ii]["Data"] = szData;
						}
						else
						{
							root["result"][ii]["Data"] = sValue;
						}
						root["result"][ii]["HaveTimeout"] = bHaveTimeout;
					}
					else if (dType == pTypeRFXSensor)
					{
						switch (dSubType)
						{
							case sTypeRFXSensorAD:
								sprintf(szData, "%d mV", atoi(sValue.c_str()));
								root["result"][ii]["TypeImg"] = "current";
								break;
							case sTypeRFXSensorVolt:
								sprintf(szData, "%d mV", atoi(sValue.c_str()));
								root["result"][ii]["TypeImg"] = "current";
								break;
						}
						root["result"][ii]["Data"] = szData;
						root["result"][ii]["HaveTimeout"] = bHaveTimeout;
					}
					else if (dType == pTypeRego6XXValue)
					{
						switch (dSubType)
						{
							case sTypeRego6XXStatus: {
								std::string lstatus = "On";

								if (atoi(sValue.c_str()) == 0)
								{
									lstatus = "Off";
								}
								root["result"][ii]["Status"] = lstatus;
								root["result"][ii]["HaveDimmer"] = false;
								root["result"][ii]["MaxDimLevel"] = 0;
								root["result"][ii]["HaveGroupCmd"] = false;
								root["result"][ii]["TypeImg"] = "utility";
								root["result"][ii]["SwitchTypeVal"] = STYPE_OnOff;
								root["result"][ii]["SwitchType"] = Switch_Type_Desc(STYPE_OnOff);
								sprintf(szData, "%d", atoi(sValue.c_str()));
								root["result"][ii]["Data"] = szData;
								root["result"][ii]["HaveTimeout"] = bHaveTimeout;
								root["result"][ii]["StrParam1"] = strParam1;
								root["result"][ii]["StrParam2"] = strParam2;
								root["result"][ii]["Protected"] = (iProtected != 0);

								if (CustomImage < static_cast<int>(m_custom_light_icons.size()))
									root["result"][ii]["Image"] = m_custom_light_icons[CustomImage].RootFile;
								else
									root["result"][ii]["Image"] = "Light";

								uint64_t camIDX = m_mainworker.m_cameras.IsDevSceneInCamera(0, sd[0]);
								root["result"][ii]["UsedByCamera"] = (camIDX != 0) ? true : false;
								if (camIDX != 0)
								{
									std::stringstream scidx;
									scidx << camIDX;
									root["result"][ii]["CameraIdx"] = scidx.str();
								}

								root["result"][ii]["Level"] = 0;
								root["result"][ii]["LevelInt"] = atoi(sValue.c_str());
							}
							break;
							case sTypeRego6XXCounter: {
								// get value of today
								time_t now = mytime(nullptr);
								struct tm ltime;
								localtime_r(&now, &ltime);
								char szDate[40];
								sprintf(szDate, "%04d-%02d-%02d", ltime.tm_year + 1900, ltime.tm_mon + 1, ltime.tm_mday);

								std::vector<std::vector<std::string>> result2;
								strcpy(szTmp, "0");
								result2 = m_sql.safe_query("SELECT MIN(Value), MAX(Value) FROM Meter WHERE (DeviceRowID='%q' AND Date>='%q')", sd[0].c_str(), szDate);
								if (!result2.empty())
								{
									std::vector<std::string> sd2 = result2[0];

									unsigned long long total_min = std::strtoull(sd2[0].c_str(), nullptr, 10);
									unsigned long long total_max = std::strtoull(sd2[1].c_str(), nullptr, 10);
									unsigned long long total_real;

									total_real = total_max - total_min;
									sprintf(szTmp, "%lld", total_real);
								}
								root["result"][ii]["SwitchTypeVal"] = MTYPE_COUNTER;
								root["result"][ii]["Counter"] = sValue;
								root["result"][ii]["CounterToday"] = szTmp;
								root["result"][ii]["Data"] = sValue;
								root["result"][ii]["HaveTimeout"] = bHaveTimeout;
							}
							break;
						}
					}
					if (CustomImage != 0 && !root["result"][ii].isMember("Image"))
					{
						auto ittIcon = m_custom_light_icons_lookup.find(CustomImage);
						if (ittIcon != m_custom_light_icons_lookup.end())
						{
							root["result"][ii]["Image"] = m_custom_light_icons[ittIcon->second].RootFile;
						}
					}
#ifdef ENABLE_PYTHON
					if (pHardware != nullptr)
					{
						if (pHardware->HwdType == HTYPE_PythonPlugin)
						{
							Plugins::CPlugin *pPlugin = (Plugins::CPlugin *)pHardware;
							bHaveTimeout = pPlugin->HasNodeFailed(sd[1].c_str(), atoi(sd[2].c_str()));
							root["result"][ii]["HaveTimeout"] = bHaveTimeout;
						}
					}
#endif
					root["result"][ii]["Timers"] = (bHasTimers == true) ? "true" : "false";
					ii++;
				}
			}
		}

		void CWebServer::UploadFloorplanImage(WebEmSession &session, const request &req, std::string &redirect_uri)
		{
			Json::Value root;
			root["title"] = "UploadFloorplanImage";
			root["status"] = "ERR";

			if (session.rights != 2)
			{
				session.reply_status = reply::forbidden;
				return; // Only admin user allowed
			}

			std::string planname = request::findValue(&req, "planname");
			std::string scalefactor = request::findValue(&req, "scalefactor");
			std::string imagefile = request::findValue(&req, "imagefile");

			std::vector<std::vector<std::string>> result;
			m_sql.safe_query("INSERT INTO Floorplans ([Name],[ScaleFactor]) VALUES('%s','%s')", planname.c_str(), scalefactor.c_str());
			result = m_sql.safe_query("SELECT MAX(ID) FROM Floorplans");
			if (!result.empty())
			{
				if (!m_sql.safe_UpdateBlobInTableWithID("Floorplans", "Image", result[0][0], imagefile))
				{
					_log.Log(LOG_ERROR, "SQL: Problem inserting floorplan image into database! ");
				}
				else
					root["status"] = "OK";
			}
			redirect_uri = root.toStyledString();
		}

		void CWebServer::GetFloorplanImage(WebEmSession &session, const request &req, reply &rep)
		{
			std::string idx = request::findValue(&req, "idx");
			if (idx.empty())
			{
				return;
			}
			std::vector<std::vector<std::string>> result;
			result = m_sql.safe_queryBlob("SELECT Image FROM Floorplans WHERE ID=%d", atol(idx.c_str()));
			if (result.empty())
				return;
			reply::set_content(&rep, result[0][0].begin(), result[0][0].end());
			std::string oname = "floorplan";
			if (result[0][0].size() > 10)
			{
				if (result[0][0][0] == 'P')
					oname += ".png";
				else if (result[0][0][0] == -1)
					oname += ".jpg";
				else if (result[0][0][0] == 'B')
					oname += ".bmp";
				else if (result[0][0][0] == 'G')
					oname += ".gif";
				else if ((result[0][0][0] == '<') && (result[0][0][1] == 's') && (result[0][0][2] == 'v') && (result[0][0][3] == 'g'))
					oname += ".svg";
				else if (result[0][0].find("<svg") != std::string::npos) // some SVG's start with <xml
					oname += ".svg";
			}
			reply::add_header_attachment(&rep, oname);
		}

		void CWebServer::GetDatabaseBackup(WebEmSession &session, const request &req, reply &rep)
		{
			if (session.rights != 2)
			{
				session.reply_status = reply::forbidden;
				return; // Only admin user allowed
			}
			time_t now = mytime(nullptr);
			Json::Value backupInfo;

			backupInfo["type"] = "Web";
#ifdef WIN32
			backupInfo["location"] = szUserDataFolder + "backup.db";
#else
			backupInfo["location"] = "/tmp/backup.db";
#endif
			if (m_sql.BackupDatabase(backupInfo["location"].asString()))
			{
				std::string szAttachmentName = "domoticz.db";
				std::string szVar;
				if (m_sql.GetPreferencesVar("Title", szVar))
				{
					stdreplace(szVar, " ", "_");
					stdreplace(szVar, "/", "_");
					stdreplace(szVar, "\\", "_");
					if (!szVar.empty())
					{
						szAttachmentName = szVar + ".db";
					}
				}
				reply::set_download_file(&rep, backupInfo["location"].asString(), szAttachmentName);
				backupInfo["duration"] = difftime(mytime(nullptr), now);
				m_mainworker.m_notificationsystem.Notify(Notification::DZ_BACKUP_DONE, Notification::STATUS_INFO, JSonToRawString(backupInfo));
			}
		}

		void CWebServer::RType_DeleteDevice(WebEmSession &session, const request &req, Json::Value &root)
		{
			if (session.rights != 2)
			{
				session.reply_status = reply::forbidden;
				return; // Only admin user allowed
			}

			std::string idx = request::findValue(&req, "idx");
			if (idx.empty())
				return;

			root["status"] = "OK";
			root["title"] = "DeleteDevice";
			m_sql.DeleteDevices(idx);
			m_mainworker.m_scheduler.ReloadSchedules();
		}

		void CWebServer::RType_AddScene(WebEmSession &session, const request &req, Json::Value &root)
		{
			if (session.rights != 2)
			{
				session.reply_status = reply::forbidden;
				return; // Only admin user allowed
			}

			std::string name = HTMLSanitizer::Sanitize(request::findValue(&req, "name"));
			name = HTMLSanitizer::Sanitize(name);
			if (name.empty())
			{
				root["status"] = "ERR";
				root["message"] = "No Scene Name specified!";
				return;
			}
			std::string stype = request::findValue(&req, "scenetype");
			if (stype.empty())
			{
				root["status"] = "ERR";
				root["message"] = "No Scene Type specified!";
				return;
			}
			if (m_sql.DoesSceneByNameExits(name) == true)
			{
				root["status"] = "ERR";
				root["message"] = "A Scene with this Name already Exits!";
				return;
			}
			root["status"] = "OK";
			root["title"] = "AddScene";
			m_sql.safe_query("INSERT INTO Scenes (Name,SceneType) VALUES ('%q',%d)", name.c_str(), atoi(stype.c_str()));
			if (m_sql.m_bEnableEventSystem)
			{
				m_mainworker.m_eventsystem.GetCurrentScenesGroups();
			}
		}

		void CWebServer::RType_DeleteScene(WebEmSession &session, const request &req, Json::Value &root)
		{
			if (session.rights != 2)
			{
				session.reply_status = reply::forbidden;
				return; // Only admin user allowed
			}

			std::string idx = request::findValue(&req, "idx");
			if (idx.empty())
				return;
			root["status"] = "OK";
			root["title"] = "DeleteScene";
			m_sql.DeleteScenes(idx);
		}

		void CWebServer::RType_UpdateScene(WebEmSession &session, const request &req, Json::Value &root)
		{
			if (session.rights != 2)
			{
				session.reply_status = reply::forbidden;
				return; // Only admin user allowed
			}

			std::string idx = request::findValue(&req, "idx");
			std::string name = HTMLSanitizer::Sanitize(request::findValue(&req, "name"));
			std::string description = HTMLSanitizer::Sanitize(request::findValue(&req, "description"));

			name = HTMLSanitizer::Sanitize(name);
			description = HTMLSanitizer::Sanitize(description);

			if ((idx.empty()) || (name.empty()))
				return;
			std::string stype = request::findValue(&req, "scenetype");
			if (stype.empty())
			{
				root["status"] = "ERR";
				root["message"] = "No Scene Type specified!";
				return;
			}
			std::string tmpstr = request::findValue(&req, "protected");
			int iProtected = (tmpstr == "true") ? 1 : 0;

			std::string onaction = base64_decode(request::findValue(&req, "onaction"));
			std::string offaction = base64_decode(request::findValue(&req, "offaction"));

			root["status"] = "OK";
			root["title"] = "UpdateScene";
			m_sql.safe_query("UPDATE Scenes SET Name='%q', Description='%q', SceneType=%d, Protected=%d, OnAction='%q', OffAction='%q' WHERE (ID == '%q')", name.c_str(),
					 description.c_str(), atoi(stype.c_str()), iProtected, onaction.c_str(), offaction.c_str(), idx.c_str());
			uint64_t ullidx = std::strtoull(idx.c_str(), nullptr, 10);
			m_mainworker.m_eventsystem.WWWUpdateSingleState(ullidx, name, m_mainworker.m_eventsystem.REASON_SCENEGROUP);
		}

		bool compareIconsByName(const http::server::CWebServer::_tCustomIcon &a, const http::server::CWebServer::_tCustomIcon &b)
		{
			return a.Title < b.Title;
		}

		void CWebServer::RType_CustomLightIcons(WebEmSession &session, const request &req, Json::Value &root)
		{
			int ii = 0;

			std::vector<_tCustomIcon> temp_custom_light_icons = m_custom_light_icons;
			// Sort by name
			std::sort(temp_custom_light_icons.begin(), temp_custom_light_icons.end(), compareIconsByName);

			for (const auto &icon : temp_custom_light_icons)
			{
				root["result"][ii]["idx"] = icon.idx;
				root["result"][ii]["imageSrc"] = icon.RootFile;
				root["result"][ii]["text"] = icon.Title;
				root["result"][ii]["description"] = icon.Description;
				ii++;
			}
			root["status"] = "OK";
		}

		void CWebServer::RType_Plans(WebEmSession &session, const request &req, Json::Value &root)
		{
			root["status"] = "OK";
			root["title"] = "Plans";

			std::string sDisplayHidden = request::findValue(&req, "displayhidden");
			bool bDisplayHidden = (sDisplayHidden == "1");

			std::vector<std::vector<std::string>> result, result2;
			result = m_sql.safe_query("SELECT ID, Name, [Order] FROM Plans ORDER BY [Order]");
			if (!result.empty())
			{
				int ii = 0;
				for (const auto &sd : result)
				{
					std::string Name = sd[1];
					bool bIsHidden = (Name[0] == '$');

					if ((bDisplayHidden) || (!bIsHidden))
					{
						root["result"][ii]["idx"] = sd[0];
						root["result"][ii]["Name"] = Name;
						root["result"][ii]["Order"] = sd[2];

						unsigned int totDevices = 0;

						result2 = m_sql.safe_query("SELECT COUNT(*) FROM DeviceToPlansMap WHERE (PlanID=='%q')", sd[0].c_str());
						if (!result2.empty())
						{
							totDevices = (unsigned int)atoi(result2[0][0].c_str());
						}
						root["result"][ii]["Devices"] = totDevices;

						ii++;
					}
				}
			}
		}

		void CWebServer::RType_FloorPlans(WebEmSession &session, const request &req, Json::Value &root)
		{
			root["status"] = "OK";
			root["title"] = "Floorplans";

			std::vector<std::vector<std::string>> result, result2, result3;

			result = m_sql.safe_query("SELECT Key, nValue, sValue FROM Preferences WHERE Key LIKE 'Floorplan%%'");
			if (result.empty())
				return;

			for (const auto &sd : result)
			{
				std::string Key = sd[0];
				int nValue = atoi(sd[1].c_str());
				std::string sValue = sd[2];

				if (Key == "FloorplanPopupDelay")
				{
					root["PopupDelay"] = nValue;
				}
				if (Key == "FloorplanFullscreenMode")
				{
					root["FullscreenMode"] = nValue;
				}
				if (Key == "FloorplanAnimateZoom")
				{
					root["AnimateZoom"] = nValue;
				}
				if (Key == "FloorplanShowSensorValues")
				{
					root["ShowSensorValues"] = nValue;
				}
				if (Key == "FloorplanShowSwitchValues")
				{
					root["ShowSwitchValues"] = nValue;
				}
				if (Key == "FloorplanShowSceneNames")
				{
					root["ShowSceneNames"] = nValue;
				}
				if (Key == "FloorplanRoomColour")
				{
					root["RoomColour"] = sValue;
				}
				if (Key == "FloorplanActiveOpacity")
				{
					root["ActiveRoomOpacity"] = nValue;
				}
				if (Key == "FloorplanInactiveOpacity")
				{
					root["InactiveRoomOpacity"] = nValue;
				}
			}

			result2 = m_sql.safe_query("SELECT ID, Name, ScaleFactor, [Order] FROM Floorplans ORDER BY [Order]");
			if (!result2.empty())
			{
				int ii = 0;
				for (const auto &sd : result2)
				{
					root["result"][ii]["idx"] = sd[0];
					root["result"][ii]["Name"] = sd[1];
					std::string ImageURL = "images/floorplans/plan?idx=" + sd[0];
					root["result"][ii]["Image"] = ImageURL;
					root["result"][ii]["ScaleFactor"] = sd[2];
					root["result"][ii]["Order"] = sd[3];

					unsigned int totPlans = 0;

					result3 = m_sql.safe_query("SELECT COUNT(*) FROM Plans WHERE (FloorplanID=='%q')", sd[0].c_str());
					if (!result3.empty())
					{
						totPlans = (unsigned int)atoi(result3[0][0].c_str());
					}
					root["result"][ii]["Plans"] = totPlans;

					ii++;
				}
			}
		}

		void CWebServer::RType_Scenes(WebEmSession &session, const request &req, Json::Value &root)
		{
			root["status"] = "OK";
			root["title"] = "Scenes";
			root["AllowWidgetOrdering"] = m_sql.m_bAllowWidgetOrdering;

			std::string sDisplayHidden = request::findValue(&req, "displayhidden");
			bool bDisplayHidden = (sDisplayHidden == "1");

			std::string sLastUpdate = request::findValue(&req, "lastupdate");

			std::string rid = request::findValue(&req, "rid");

			time_t LastUpdate = 0;
			if (!sLastUpdate.empty())
			{
				std::stringstream sstr;
				sstr << sLastUpdate;
				sstr >> LastUpdate;
			}

			time_t now = mytime(nullptr);
			struct tm tm1;
			localtime_r(&now, &tm1);
			struct tm tLastUpdate;
			localtime_r(&now, &tLastUpdate);

			root["ActTime"] = static_cast<int>(now);

			std::vector<std::vector<std::string>> result, result2;
			std::string szQuery = "SELECT ID, Name, Activators, Favorite, nValue, SceneType, LastUpdate, Protected, OnAction, OffAction, Description FROM Scenes";
			if (!rid.empty())
				szQuery += " WHERE (ID == " + rid + ")";
			szQuery += " ORDER BY [Order]";
			result = m_sql.safe_query(szQuery.c_str());
			if (!result.empty())
			{
				int ii = 0;
				for (const auto &sd : result)
				{
					std::string sName = sd[1];
					if ((bDisplayHidden == false) && (sName[0] == '$'))
						continue;

					std::string sLastUpdate = sd[6];
					if (LastUpdate != 0)
					{
						time_t cLastUpdate;
						ParseSQLdatetime(cLastUpdate, tLastUpdate, sLastUpdate, tm1.tm_isdst);
						if (cLastUpdate <= LastUpdate)
							continue;
					}

					unsigned char nValue = atoi(sd[4].c_str());
					unsigned char scenetype = atoi(sd[5].c_str());
					int iProtected = atoi(sd[7].c_str());

					std::string onaction = base64_encode(sd[8]);
					std::string offaction = base64_encode(sd[9]);

					root["result"][ii]["idx"] = sd[0];
					root["result"][ii]["Name"] = sName;
					root["result"][ii]["Description"] = sd[10];
					root["result"][ii]["Favorite"] = atoi(sd[3].c_str());
					root["result"][ii]["Protected"] = (iProtected != 0);
					root["result"][ii]["OnAction"] = onaction;
					root["result"][ii]["OffAction"] = offaction;

					if (scenetype == 0)
					{
						root["result"][ii]["Type"] = "Scene";
					}
					else
					{
						root["result"][ii]["Type"] = "Group";
					}

					root["result"][ii]["LastUpdate"] = sLastUpdate;

					if (nValue == 0)
						root["result"][ii]["Status"] = "Off";
					else if (nValue == 1)
						root["result"][ii]["Status"] = "On";
					else
						root["result"][ii]["Status"] = "Mixed";
					root["result"][ii]["Timers"] = (m_sql.HasSceneTimers(sd[0]) == true) ? "true" : "false";
					uint64_t camIDX = m_mainworker.m_cameras.IsDevSceneInCamera(1, sd[0]);
					root["result"][ii]["UsedByCamera"] = (camIDX != 0) ? true : false;
					if (camIDX != 0)
					{
						std::stringstream scidx;
						scidx << camIDX;
						root["result"][ii]["CameraIdx"] = scidx.str();
					}
					ii++;
				}
			}
			if (!m_mainworker.m_LastSunriseSet.empty())
			{
				std::vector<std::string> strarray;
				StringSplit(m_mainworker.m_LastSunriseSet, ";", strarray);
				if (strarray.size() == 10)
				{
					char szTmp[100];
					// strftime(szTmp, 80, "%b %d %Y %X", &tm1);
					strftime(szTmp, 80, "%Y-%m-%d %X", &tm1);
					root["ServerTime"] = szTmp;
					root["Sunrise"] = strarray[0];
					root["Sunset"] = strarray[1];
					root["SunAtSouth"] = strarray[2];
					root["CivTwilightStart"] = strarray[3];
					root["CivTwilightEnd"] = strarray[4];
					root["NautTwilightStart"] = strarray[5];
					root["NautTwilightEnd"] = strarray[6];
					root["AstrTwilightStart"] = strarray[7];
					root["AstrTwilightEnd"] = strarray[8];
					root["DayLength"] = strarray[9];
				}
			}
		}

		void CWebServer::RType_Hardware(WebEmSession &session, const request &req, Json::Value &root)
		{
			root["status"] = "OK";
			root["title"] = "Hardware";

#ifdef WITH_OPENZWAVE
			m_ZW_Hwidx = -1;
#endif

			std::vector<std::vector<std::string>> result;
			result = m_sql.safe_query("SELECT ID, Name, Enabled, Type, Address, Port, SerialPort, Username, Password, Extra, Mode1, Mode2, Mode3, Mode4, Mode5, Mode6, DataTimeout, "
						  "LogLevel FROM Hardware ORDER BY ID ASC");
			if (!result.empty())
			{
				int ii = 0;
				for (const auto &sd : result)
				{
					_eHardwareTypes hType = (_eHardwareTypes)atoi(sd[3].c_str());
					if (hType == HTYPE_DomoticzInternal)
						continue;
					root["result"][ii]["idx"] = sd[0];
					root["result"][ii]["Name"] = sd[1];
					root["result"][ii]["Enabled"] = (sd[2] == "1") ? "true" : "false";
					root["result"][ii]["Type"] = hType;
					root["result"][ii]["Address"] = sd[4];
					root["result"][ii]["Port"] = atoi(sd[5].c_str());
					root["result"][ii]["SerialPort"] = sd[6];
					root["result"][ii]["Username"] = sd[7];
					root["result"][ii]["Password"] = sd[8];
					root["result"][ii]["Extra"] = sd[9];

					if (hType == HTYPE_PythonPlugin)
					{
						root["result"][ii]["Mode1"] = sd[10]; // Plugins can have non-numeric values in the Mode fields
						root["result"][ii]["Mode2"] = sd[11];
						root["result"][ii]["Mode3"] = sd[12];
						root["result"][ii]["Mode4"] = sd[13];
						root["result"][ii]["Mode5"] = sd[14];
						root["result"][ii]["Mode6"] = sd[15];
					}
					else
					{
						root["result"][ii]["Mode1"] = atoi(sd[10].c_str());
						root["result"][ii]["Mode2"] = atoi(sd[11].c_str());
						root["result"][ii]["Mode3"] = atoi(sd[12].c_str());
						root["result"][ii]["Mode4"] = atoi(sd[13].c_str());
						root["result"][ii]["Mode5"] = atoi(sd[14].c_str());
						root["result"][ii]["Mode6"] = atoi(sd[15].c_str());
					}
					root["result"][ii]["DataTimeout"] = atoi(sd[16].c_str());
					root["result"][ii]["LogLevel"] = atoi(sd[17].c_str());

					// Special case for openzwave (status for nodes queried)
					CDomoticzHardwareBase *pHardware = m_mainworker.GetHardware(atoi(sd[0].c_str()));
					if (pHardware != nullptr)
					{
						if ((pHardware->HwdType == HTYPE_RFXtrx315) || (pHardware->HwdType == HTYPE_RFXtrx433) || (pHardware->HwdType == HTYPE_RFXtrx868) ||
						    (pHardware->HwdType == HTYPE_RFXLAN))
						{
							CRFXBase *pMyHardware = reinterpret_cast<CRFXBase *>(pHardware);
							if (!pMyHardware->m_Version.empty())
								root["result"][ii]["version"] = pMyHardware->m_Version;
							else
								root["result"][ii]["version"] = sd[11];
							root["result"][ii]["noiselvl"] = pMyHardware->m_NoiseLevel;
						}
						else if ((pHardware->HwdType == HTYPE_MySensorsUSB) || (pHardware->HwdType == HTYPE_MySensorsTCP) || (pHardware->HwdType == HTYPE_MySensorsMQTT))
						{
							MySensorsBase *pMyHardware = reinterpret_cast<MySensorsBase *>(pHardware);
							root["result"][ii]["version"] = pMyHardware->GetGatewayVersion();
						}
						else if ((pHardware->HwdType == HTYPE_OpenThermGateway) || (pHardware->HwdType == HTYPE_OpenThermGatewayTCP))
						{
							OTGWBase *pMyHardware = reinterpret_cast<OTGWBase *>(pHardware);
							root["result"][ii]["version"] = pMyHardware->m_Version;
						}
						else if ((pHardware->HwdType == HTYPE_RFLINKUSB) || (pHardware->HwdType == HTYPE_RFLINKTCP))
						{
							CRFLinkBase *pMyHardware = reinterpret_cast<CRFLinkBase *>(pHardware);
							root["result"][ii]["version"] = pMyHardware->m_Version;
						}
						else
						{
#ifdef WITH_OPENZWAVE
							if (pHardware->HwdType == HTYPE_OpenZWave)
							{
								COpenZWave *pOZWHardware = reinterpret_cast<COpenZWave *>(pHardware);
								root["result"][ii]["version"] = pOZWHardware->GetVersionLong();
								root["result"][ii]["NodesQueried"] = (pOZWHardware->m_awakeNodesQueried || pOZWHardware->m_allNodesQueried);
							}
#endif
						}
					}
					ii++;
				}
			}
		}

		void CWebServer::RType_Devices(WebEmSession &session, const request &req, Json::Value &root)
		{
			std::string rfilter = request::findValue(&req, "filter");
			std::string order = request::findValue(&req, "order");
			std::string rused = request::findValue(&req, "used");
			std::string rid = request::findValue(&req, "rid");
			std::string planid = request::findValue(&req, "plan");
			std::string floorid = request::findValue(&req, "floor");
			std::string sDisplayHidden = request::findValue(&req, "displayhidden");
			std::string sFetchFavorites = request::findValue(&req, "favorite");
			std::string sDisplayDisabled = request::findValue(&req, "displaydisabled");
			bool bDisplayHidden = (sDisplayHidden == "1");
			bool bFetchFavorites = (sFetchFavorites == "1");

			int HideDisabledHardwareSensors = 0;
			m_sql.GetPreferencesVar("HideDisabledHardwareSensors", HideDisabledHardwareSensors);
			bool bDisabledDisabled = (HideDisabledHardwareSensors == 0);
			if (sDisplayDisabled == "1")
				bDisabledDisabled = true;

			std::string sLastUpdate = request::findValue(&req, "lastupdate");
			std::string hwidx = request::findValue(&req, "hwidx"); // OTO

			time_t LastUpdate = 0;
			if (!sLastUpdate.empty())
			{
				std::stringstream sstr;
				sstr << sLastUpdate;
				sstr >> LastUpdate;
			}

			root["status"] = "OK";
			root["title"] = "Devices";
			root["app_version"] = szAppVersion;
			GetJSonDevices(root, rused, rfilter, order, rid, planid, floorid, bDisplayHidden, bDisabledDisabled, bFetchFavorites, LastUpdate, session.username, hwidx);
		}

		void CWebServer::RType_Users(WebEmSession &session, const request &req, Json::Value &root)
		{
			bool bHaveUser = (!session.username.empty());
			int urights = 3;
			if (bHaveUser)
			{
				int iUser = FindUser(session.username.c_str());
				if (iUser != -1)
					urights = static_cast<int>(m_users[iUser].userrights);
			}
			if (urights < 2)
				return;

			root["status"] = "OK";
			root["title"] = "Users";

			std::vector<std::vector<std::string>> result;
			result = m_sql.safe_query("SELECT ID, Active, Username, Password, Rights, RemoteSharing, TabsEnabled FROM USERS ORDER BY ID ASC");
			if (!result.empty())
			{
				int ii = 0;
				for (const auto &sd : result)
				{
					root["result"][ii]["idx"] = sd[0];
					root["result"][ii]["Enabled"] = (sd[1] == "1") ? "true" : "false";
					root["result"][ii]["Username"] = base64_decode(sd[2]);
					root["result"][ii]["Password"] = sd[3];
					root["result"][ii]["Rights"] = atoi(sd[4].c_str());
					root["result"][ii]["RemoteSharing"] = atoi(sd[5].c_str());
					root["result"][ii]["TabsEnabled"] = atoi(sd[6].c_str());
					ii++;
				}
			}
		}

		void CWebServer::RType_Mobiles(WebEmSession &session, const request &req, Json::Value &root)
		{
			bool bHaveUser = (!session.username.empty());
			int urights = 3;
			if (bHaveUser)
			{
				int iUser = FindUser(session.username.c_str());
				if (iUser != -1)
					urights = static_cast<int>(m_users[iUser].userrights);
			}
			if (urights < 2)
				return;

			root["status"] = "OK";
			root["title"] = "Mobiles";

			std::vector<std::vector<std::string>> result;
			result = m_sql.safe_query("SELECT ID, Active, Name, UUID, LastUpdate, DeviceType FROM MobileDevices ORDER BY Name ASC");
			if (!result.empty())
			{
				int ii = 0;
				for (const auto &sd : result)
				{
					root["result"][ii]["idx"] = sd[0];
					root["result"][ii]["Enabled"] = (sd[1] == "1") ? "true" : "false";
					root["result"][ii]["Name"] = sd[2];
					root["result"][ii]["UUID"] = sd[3];
					root["result"][ii]["LastUpdate"] = sd[4];
					root["result"][ii]["DeviceType"] = sd[5];
					ii++;
				}
			}
		}

		void CWebServer::Cmd_SetSetpoint(WebEmSession &session, const request &req, Json::Value &root)
		{
			bool bHaveUser = (!session.username.empty());
			int iUser = -1;
			int urights = 3;
			if (bHaveUser)
			{
				iUser = FindUser(session.username.c_str());
				if (iUser != -1)
				{
					urights = static_cast<int>(m_users[iUser].userrights);
				}
			}
			if (urights < 1)
				return;

			std::string idx = request::findValue(&req, "idx");
			std::string setpoint = request::findValue(&req, "setpoint");
			if ((idx.empty()) || (setpoint.empty()))
				return;
			root["status"] = "OK";
			root["title"] = "SetSetpoint";
			if (iUser != -1)
			{
				_log.Log(LOG_STATUS, "User: %s initiated a SetPoint command", m_users[iUser].Username.c_str());
			}
			m_mainworker.SetSetPoint(idx, static_cast<float>(atof(setpoint.c_str())));
		}

		void CWebServer::Cmd_GetSceneActivations(WebEmSession &session, const request &req, Json::Value &root)
		{
			if (session.rights != 2)
			{
				session.reply_status = reply::forbidden;
				return; // Only admin user allowed
			}

			std::string idx = request::findValue(&req, "idx");
			if (idx.empty())
				return;

			root["status"] = "OK";
			root["title"] = "GetSceneActivations";

			std::vector<std::vector<std::string>> result, result2;
			result = m_sql.safe_query("SELECT Activators, SceneType FROM Scenes WHERE (ID==%q)", idx.c_str());
			if (result.empty())
				return;
			int ii = 0;
			std::string Activators = result[0][0];
			int SceneType = atoi(result[0][1].c_str());
			if (!Activators.empty())
			{
				// Get Activator device names
				std::vector<std::string> arrayActivators;
				StringSplit(Activators, ";", arrayActivators);
				for (const auto &sCodeCmd : arrayActivators)
				{
					std::vector<std::string> arrayCode;
					StringSplit(sCodeCmd, ":", arrayCode);

					std::string sID = arrayCode[0];
					int sCode = 0;
					if (arrayCode.size() == 2)
					{
						sCode = atoi(arrayCode[1].c_str());
					}

					result2 = m_sql.safe_query("SELECT Name, [Type], SubType, SwitchType FROM DeviceStatus WHERE (ID==%q)", sID.c_str());
					if (!result2.empty())
					{
						std::vector<std::string> sd = result2[0];
						std::string lstatus = "-";
						if ((SceneType == 0) && (arrayCode.size() == 2))
						{
							unsigned char devType = (unsigned char)atoi(sd[1].c_str());
							unsigned char subType = (unsigned char)atoi(sd[2].c_str());
							_eSwitchType switchtype = (_eSwitchType)atoi(sd[3].c_str());
							int nValue = sCode;
							std::string sValue;
							int llevel = 0;
							bool bHaveDimmer = false;
							bool bHaveGroupCmd = false;
							int maxDimLevel = 0;
							GetLightStatus(devType, subType, switchtype, nValue, sValue, lstatus, llevel, bHaveDimmer, maxDimLevel, bHaveGroupCmd);
						}
						uint64_t dID = std::strtoull(sID.c_str(), nullptr, 10);
						root["result"][ii]["idx"] = Json::Value::UInt64(dID);
						root["result"][ii]["name"] = sd[0];
						root["result"][ii]["code"] = sCode;
						root["result"][ii]["codestr"] = lstatus;
						ii++;
					}
				}
			}
		}

		void CWebServer::Cmd_AddSceneCode(WebEmSession &session, const request &req, Json::Value &root)
		{
			if (session.rights != 2)
			{
				session.reply_status = reply::forbidden;
				return; // Only admin user allowed
			}

			std::string sceneidx = request::findValue(&req, "sceneidx");
			std::string idx = request::findValue(&req, "idx");
			std::string cmnd = request::findValue(&req, "cmnd");
			if ((sceneidx.empty()) || (idx.empty()) || (cmnd.empty()))
				return;
			root["status"] = "OK";
			root["title"] = "AddSceneCode";

			// First check if we do not already have this device as activation code
			std::vector<std::vector<std::string>> result;
			result = m_sql.safe_query("SELECT Activators, SceneType FROM Scenes WHERE (ID==%q)", sceneidx.c_str());
			if (result.empty())
				return;
			std::string Activators = result[0][0];
			unsigned char scenetype = atoi(result[0][1].c_str());

			if (!Activators.empty())
			{
				// Get Activator device names
				std::vector<std::string> arrayActivators;
				StringSplit(Activators, ";", arrayActivators);
				for (const auto &sCodeCmd : arrayActivators)
				{
					std::vector<std::string> arrayCode;
					StringSplit(sCodeCmd, ":", arrayCode);

					std::string sID = arrayCode[0];
					std::string sCode;
					if (arrayCode.size() == 2)
					{
						sCode = arrayCode[1];
					}

					if (sID == idx)
					{
						if (scenetype == 1)
							return; // Group does not work with separate codes, so already there
						if (sCode == cmnd)
							return; // same code, already there!
					}
				}
			}
			if (!Activators.empty())
				Activators += ";";
			Activators += idx;
			if (scenetype == 0)
			{
				Activators += ":" + cmnd;
			}
			m_sql.safe_query("UPDATE Scenes SET Activators='%q' WHERE (ID==%q)", Activators.c_str(), sceneidx.c_str());
		}

		void CWebServer::Cmd_RemoveSceneCode(WebEmSession &session, const request &req, Json::Value &root)
		{
			if (session.rights != 2)
			{
				session.reply_status = reply::forbidden;
				return; // Only admin user allowed
			}

			std::string sceneidx = request::findValue(&req, "sceneidx");
			std::string idx = request::findValue(&req, "idx");
			std::string code = request::findValue(&req, "code");
			if ((idx.empty()) || (sceneidx.empty()) || (code.empty()))
				return;
			root["status"] = "OK";
			root["title"] = "RemoveSceneCode";

			std::vector<std::vector<std::string>> result;
			result = m_sql.safe_query("SELECT Activators, SceneType FROM Scenes WHERE (ID==%q)", sceneidx.c_str());
			if (result.empty())
				return;
			std::string Activators = result[0][0];
			int SceneType = atoi(result[0][1].c_str());
			if (!Activators.empty())
			{
				// Get Activator device names
				std::vector<std::string> arrayActivators;
				StringSplit(Activators, ";", arrayActivators);
				std::string newActivation;
				for (const auto &sCodeCmd : arrayActivators)
				{
					std::vector<std::string> arrayCode;
					StringSplit(sCodeCmd, ":", arrayCode);

					std::string sID = arrayCode[0];
					std::string sCode;
					if (arrayCode.size() == 2)
					{
						sCode = arrayCode[1];
					}
					bool bFound = false;
					if (sID == idx)
					{
						if ((SceneType == 1) || (sCode.empty()))
						{
							bFound = true;
						}
						else
						{
							// Also check the code
							bFound = (sCode == code);
						}
					}
					if (!bFound)
					{
						if (!newActivation.empty())
							newActivation += ";";
						newActivation += sID;
						if ((SceneType == 0) && (!sCode.empty()))
						{
							newActivation += ":" + sCode;
						}
					}
				}
				if (Activators != newActivation)
				{
					m_sql.safe_query("UPDATE Scenes SET Activators='%q' WHERE (ID==%q)", newActivation.c_str(), sceneidx.c_str());
				}
			}
		}

		void CWebServer::Cmd_ClearSceneCodes(WebEmSession &session, const request &req, Json::Value &root)
		{
			if (session.rights != 2)
			{
				session.reply_status = reply::forbidden;
				return; // Only admin user allowed
			}

			std::string sceneidx = request::findValue(&req, "sceneidx");
			if (sceneidx.empty())
				return;
			root["status"] = "OK";
			root["title"] = "ClearSceneCode";

			m_sql.safe_query("UPDATE Scenes SET Activators='' WHERE (ID==%q)", sceneidx.c_str());
		}

		void CWebServer::Cmd_GetSerialDevices(WebEmSession &session, const request &req, Json::Value &root)
		{
			root["status"] = "OK";
			root["title"] = "GetSerialDevices";

			bool bUseDirectPath = false;
			std::vector<std::string> serialports = GetSerialPorts(bUseDirectPath);
			int ii = 0;
			for (const auto &port : serialports)
			{
				root["result"][ii]["name"] = port;
				root["result"][ii]["value"] = ii;
				ii++;
			}
		}

		void CWebServer::Cmd_GetDevicesList(WebEmSession &session, const request &req, Json::Value &root)
		{
			root["status"] = "OK";
			root["title"] = "GetDevicesList";
			int ii = 0;
			std::vector<std::vector<std::string>> result;
			result = m_sql.safe_query("SELECT ID, Name FROM DeviceStatus WHERE (Used == 1) ORDER BY Name");
			if (!result.empty())
			{
				for (const auto &sd : result)
				{
					root["result"][ii]["name"] = sd[1];
					root["result"][ii]["value"] = sd[0];
					ii++;
				}
			}
		}

		void CWebServer::Post_UploadCustomIcon(WebEmSession &session, const request &req, reply &rep)
		{
			Json::Value root;
			root["title"] = "UploadCustomIcon";
			root["status"] = "ERROR";
			root["error"] = "Invalid";
			// Only admin user allowed
			if (session.rights != 2)
			{
				session.reply_status = reply::forbidden;
				return; // Only admin user allowed
			}
			std::string zipfile = request::findValue(&req, "file");
			if (!zipfile.empty())
			{
				std::string ErrorMessage;
				bool bOK = m_sql.InsertCustomIconFromZip(zipfile, ErrorMessage);
				if (bOK)
				{
					root["status"] = "OK";
				}
				else
				{
					root["status"] = "ERROR";
					root["error"] = ErrorMessage;
				}
			}
			std::string jcallback = request::findValue(&req, "jsoncallback");
			if (jcallback.empty())
			{
				reply::set_content(&rep, root.toStyledString());
				return;
			}
			reply::set_content(&rep, "var data=" + root.toStyledString() + '\n' + jcallback + "(data);");
		}

		void CWebServer::Cmd_GetCustomIconSet(WebEmSession &session, const request &req, Json::Value &root)
		{
			root["status"] = "OK";
			root["title"] = "GetCustomIconSet";
			int ii = 0;
			for (const auto &icon : m_custom_light_icons)
			{
				if (icon.idx >= 100)
				{
					std::string IconFile16 = "images/" + icon.RootFile + ".png";
					std::string IconFile48On = "images/" + icon.RootFile + "48_On.png";
					std::string IconFile48Off = "images/" + icon.RootFile + "48_Off.png";

					root["result"][ii]["idx"] = icon.idx - 100;
					root["result"][ii]["Title"] = icon.Title;
					root["result"][ii]["Description"] = icon.Description;
					root["result"][ii]["IconFile16"] = IconFile16;
					root["result"][ii]["IconFile48On"] = IconFile48On;
					root["result"][ii]["IconFile48Off"] = IconFile48Off;
					ii++;
				}
			}
		}

		void CWebServer::Cmd_DeleteCustomIcon(WebEmSession &session, const request &req, Json::Value &root)
		{
			if (session.rights != 2)
			{
				session.reply_status = reply::forbidden;
				return; // Only admin user allowed
			}

			std::string sidx = request::findValue(&req, "idx");
			if (sidx.empty())
				return;
			int idx = atoi(sidx.c_str());
			root["status"] = "OK";
			root["title"] = "DeleteCustomIcon";

			m_sql.safe_query("DELETE FROM CustomImages WHERE (ID == %d)", idx);

			// Delete icons file from disk
			for (const auto &icon : m_custom_light_icons)
			{
				if (icon.idx == idx + 100)
				{
					std::string IconFile16 = szWWWFolder + "/images/" + icon.RootFile + ".png";
					std::string IconFile48On = szWWWFolder + "/images/" + icon.RootFile + "48_On.png";
					std::string IconFile48Off = szWWWFolder + "/images/" + icon.RootFile + "48_Off.png";
					std::remove(IconFile16.c_str());
					std::remove(IconFile48On.c_str());
					std::remove(IconFile48Off.c_str());
					break;
				}
			}
			ReloadCustomSwitchIcons();
		}

		void CWebServer::Cmd_UpdateCustomIcon(WebEmSession &session, const request &req, Json::Value &root)
		{
			if (session.rights != 2)
			{
				session.reply_status = reply::forbidden;
				return; // Only admin user allowed
			}

			std::string sidx = request::findValue(&req, "idx");
			std::string sname = HTMLSanitizer::Sanitize(request::findValue(&req, "name"));
			std::string sdescription = HTMLSanitizer::Sanitize(request::findValue(&req, "description"));
			if ((sidx.empty()) || (sname.empty()) || (sdescription.empty()))
				return;

			int idx = atoi(sidx.c_str());
			root["status"] = "OK";
			root["title"] = "UpdateCustomIcon";

			m_sql.safe_query("UPDATE CustomImages SET Name='%q', Description='%q' WHERE (ID == %d)", sname.c_str(), sdescription.c_str(), idx);
			ReloadCustomSwitchIcons();
		}

		void CWebServer::Cmd_RenameDevice(WebEmSession &session, const request &req, Json::Value &root)
		{
			if (session.rights != 2)
			{
				session.reply_status = reply::forbidden;
				return; // Only admin user allowed
			}

			std::string sidx = request::findValue(&req, "idx");
			std::string sname = HTMLSanitizer::Sanitize(request::findValue(&req, "name"));
			if ((sidx.empty()) || (sname.empty()))
				return;
			int idx = atoi(sidx.c_str());
			root["status"] = "OK";
			root["title"] = "RenameDevice";

			m_sql.safe_query("UPDATE DeviceStatus SET Name='%q' WHERE (ID == %d)", sname.c_str(), idx);
			uint64_t ullidx = std::strtoull(sidx.c_str(), nullptr, 10);
			m_mainworker.m_eventsystem.WWWUpdateSingleState(ullidx, sname, m_mainworker.m_eventsystem.REASON_DEVICE);

#ifdef ENABLE_PYTHON
			// Notify plugin framework about the change
			m_mainworker.m_pluginsystem.DeviceModified(idx);
#endif
		}

		void CWebServer::Cmd_RenameScene(WebEmSession &session, const request &req, Json::Value &root)
		{
			if (session.rights != 2)
			{
				session.reply_status = reply::forbidden;
				return; // Only admin user allowed
			}

			std::string sidx = request::findValue(&req, "idx");
			std::string sname = HTMLSanitizer::Sanitize(request::findValue(&req, "name"));
			if ((sidx.empty()) || (sname.empty()))
				return;
			int idx = atoi(sidx.c_str());
			root["status"] = "OK";
			root["title"] = "RenameScene";

			m_sql.safe_query("UPDATE Scenes SET Name='%q' WHERE (ID == %d)", sname.c_str(), idx);
			uint64_t ullidx = std::strtoull(sidx.c_str(), nullptr, 10);
			m_mainworker.m_eventsystem.WWWUpdateSingleState(ullidx, sname, m_mainworker.m_eventsystem.REASON_SCENEGROUP);
		}

		void CWebServer::Cmd_SetDeviceUsed(WebEmSession &session, const request &req, Json::Value &root)
		{
			if (session.rights != 2)
			{
				session.reply_status = reply::forbidden;
				return; // Only admin user allowed
			}

			std::string sIdx = request::findValue(&req, "idx");
			std::string sUsed = request::findValue(&req, "used");
			std::string sName = request::findValue(&req, "name");
			std::string sMainDeviceIdx = request::findValue(&req, "maindeviceidx");
			if (sIdx.empty() || sUsed.empty())
				return;
			const int idx = atoi(sIdx.c_str());
			bool bIsUsed = (sUsed == "true");

			if (!sName.empty())
				m_sql.safe_query("UPDATE DeviceStatus SET Used=%d, Name='%q' WHERE (ID == %d)", bIsUsed ? 1 : 0, sName.c_str(), idx);
			else
				m_sql.safe_query("UPDATE DeviceStatus SET Used=%d WHERE (ID == %d)", bIsUsed ? 1 : 0, idx);

			root["status"] = "OK";
			root["title"] = "SetDeviceUsed";

			if ((!sMainDeviceIdx.empty()) && (sMainDeviceIdx != sIdx))
			{
				// this is a sub device for another light/switch
				// first check if it is not already a sub device
				auto result = m_sql.safe_query("SELECT ID FROM LightSubDevices WHERE (DeviceRowID=='%q') AND (ParentID =='%q')", sIdx.c_str(), sMainDeviceIdx.c_str());
				if (result.empty())
				{
					// no it is not, add it
					m_sql.safe_query("INSERT INTO LightSubDevices (DeviceRowID, ParentID) VALUES ('%q','%q')", sIdx.c_str(), sMainDeviceIdx.c_str());
				}
			}

			if (m_sql.m_bEnableEventSystem)
			{
				if (!bIsUsed)
					m_mainworker.m_eventsystem.RemoveSingleState(idx, m_mainworker.m_eventsystem.REASON_DEVICE);
				else
					m_mainworker.m_eventsystem.WWWUpdateSingleState(idx, sName, m_mainworker.m_eventsystem.REASON_DEVICE);
			}
#ifdef ENABLE_PYTHON
			// Notify plugin framework about the change
			m_mainworker.m_pluginsystem.DeviceModified(idx);
#endif
		}

		void CWebServer::Cmd_AddLogMessage(WebEmSession &session, const request &req, Json::Value &root)
		{
			std::string smessage = request::findValue(&req, "message");
			if (smessage.empty())
				return;
			root["title"] = "AddLogMessage";

			_eLogLevel logLevel = LOG_STATUS;
			std::string slevel = request::findValue(&req, "level");
			if (!slevel.empty())
			{
				if ((slevel == "1") || (slevel == "normal"))
					logLevel = LOG_NORM;
				else if ((slevel == "2") || (slevel == "status"))
					logLevel = LOG_STATUS;
				else if ((slevel == "4") || (slevel == "error"))
					logLevel = LOG_ERROR;
				else
				{
					root["status"] = "ERR";
					return;
				}
			}
			root["status"] = "OK";

			_log.Log(logLevel, "%s", smessage.c_str());
		}

		void CWebServer::Cmd_ClearShortLog(WebEmSession &session, const request &req, Json::Value &root)
		{
			if (session.rights != 2)
			{
				session.reply_status = reply::forbidden;
				return; // Only admin user allowed
			}
			root["status"] = "OK";
			root["title"] = "ClearShortLog";

			_log.Log(LOG_STATUS, "Clearing Short Log...");

			m_sql.ClearShortLog();

			_log.Log(LOG_STATUS, "Short Log Cleared!");
		}

		void CWebServer::Cmd_VacuumDatabase(WebEmSession &session, const request &req, Json::Value &root)
		{
			if (session.rights != 2)
			{
				session.reply_status = reply::forbidden;
				return; // Only admin user allowed
			}
			root["status"] = "OK";
			root["title"] = "VacuumDatabase";

			m_sql.VacuumDatabase();
		}

		void CWebServer::Cmd_AddMobileDevice(WebEmSession &session, const request &req, Json::Value &root)
		{
			std::string suuid = HTMLSanitizer::Sanitize(request::findValue(&req, "uuid"));
			std::string ssenderid = HTMLSanitizer::Sanitize(request::findValue(&req, "senderid"));
			std::string sname = HTMLSanitizer::Sanitize(request::findValue(&req, "name"));
			std::string sdevtype = HTMLSanitizer::Sanitize(request::findValue(&req, "devicetype"));
			std::string sactive = request::findValue(&req, "active");
			if ((suuid.empty()) || (ssenderid.empty()))
				return;
			root["status"] = "OK";
			root["title"] = "AddMobileDevice";

			if (sactive.empty())
				sactive = "1";
			int iActive = (sactive == "1") ? 1 : 0;

			std::vector<std::vector<std::string>> result;
			result = m_sql.safe_query("SELECT ID, Name, DeviceType FROM MobileDevices WHERE (UUID=='%q')", suuid.c_str());
			if (result.empty())
			{
				// New
				m_sql.safe_query("INSERT INTO MobileDevices (Active,UUID,SenderID,Name,DeviceType) VALUES (%d,'%q','%q','%q','%q')", iActive, suuid.c_str(), ssenderid.c_str(),
						 sname.c_str(), sdevtype.c_str());
			}
			else
			{
				// Update
				time_t now = mytime(nullptr);
				struct tm ltime;
				localtime_r(&now, &ltime);
				m_sql.safe_query("UPDATE MobileDevices SET Active=%d, SenderID='%q', LastUpdate='%04d-%02d-%02d %02d:%02d:%02d' WHERE (UUID == '%q')", iActive, ssenderid.c_str(),
						 ltime.tm_year + 1900, ltime.tm_mon + 1, ltime.tm_mday, ltime.tm_hour, ltime.tm_min, ltime.tm_sec, suuid.c_str());

				std::string dname = result[0][1];
				std::string ddevtype = result[0][2];
				if (dname.empty() || ddevtype.empty())
				{
					m_sql.safe_query("UPDATE MobileDevices SET Name='%q', DeviceType='%q' WHERE (UUID == '%q')", sname.c_str(), sdevtype.c_str(), suuid.c_str());
				}
			}
		}

		void CWebServer::Cmd_UpdateMobileDevice(WebEmSession &session, const request &req, Json::Value &root)
		{
			if (session.rights != 2)
			{
				session.reply_status = reply::forbidden;
				return; // Only admin user allowed
			}
			std::string sidx = request::findValue(&req, "idx");
			std::string enabled = request::findValue(&req, "enabled");
			std::string name = HTMLSanitizer::Sanitize(request::findValue(&req, "name"));

			if ((sidx.empty()) || (enabled.empty()) || (name.empty()))
				return;
			uint64_t idx = std::strtoull(sidx.c_str(), nullptr, 10);

			m_sql.safe_query("UPDATE MobileDevices SET Name='%q', Active=%d WHERE (ID==%" PRIu64 ")", name.c_str(), (enabled == "true") ? 1 : 0, idx);

			root["status"] = "OK";
			root["title"] = "UpdateMobile";
		}

		void CWebServer::Cmd_DeleteMobileDevice(WebEmSession &session, const request &req, Json::Value &root)
		{
			if (session.rights != 2)
			{
				session.reply_status = reply::forbidden;
				return; // Only admin user allowed
			}
			std::string suuid = request::findValue(&req, "uuid");
			if (suuid.empty())
				return;
			std::vector<std::vector<std::string>> result;
			result = m_sql.safe_query("SELECT ID FROM MobileDevices WHERE (UUID=='%q')", suuid.c_str());
			if (result.empty())
				return;
			m_sql.safe_query("DELETE FROM MobileDevices WHERE (UUID == '%q')", suuid.c_str());
			root["status"] = "OK";
			root["title"] = "DeleteMobileDevice";
		}

		void CWebServer::RType_GetTransfers(WebEmSession &session, const request &req, Json::Value &root)
		{
			root["status"] = "OK";
			root["title"] = "GetTransfers";

			uint64_t idx = 0;
			if (!request::findValue(&req, "idx").empty())
			{
				idx = std::strtoull(request::findValue(&req, "idx").c_str(), nullptr, 10);
			}

			std::vector<std::vector<std::string>> result;
			result = m_sql.safe_query("SELECT Type, SubType FROM DeviceStatus WHERE (ID==%" PRIu64 ")", idx);
			if (!result.empty())
			{
				int dType = atoi(result[0][0].c_str());
				if ((dType == pTypeTEMP) || (dType == pTypeTEMP_HUM) || (dType == pTypeTEMP_HUM_BARO))
				{
					result = m_sql.safe_query("SELECT ID, Name FROM DeviceStatus WHERE (Type=='%q') AND (ID!=%" PRIu64 ")", result[0][0].c_str(), idx);
				}
				else
				{
					result = m_sql.safe_query("SELECT ID, Name FROM DeviceStatus WHERE (Type=='%q') AND (SubType=='%q') AND (ID!=%" PRIu64 ")", result[0][0].c_str(),
								  result[0][1].c_str(), idx);
				}

				std::sort(std::begin(result), std::end(result), [](std::vector<std::string> a, std::vector<std::string> b) { return a[1] < b[1]; });

				int ii = 0;
				for (const auto &sd : result)
				{
					root["result"][ii]["idx"] = sd[0];
					root["result"][ii]["Name"] = sd[1];
					ii++;
				}
			}
		}

		// Will transfer Newest sensor log to OLD sensor,
		// then set the HardwareID/DeviceID/Unit/Name/Type/Subtype/Unit for the OLD sensor to the NEW sensor ID/Type/Subtype/Unit
		// then delete the NEW sensor
		void CWebServer::RType_TransferDevice(WebEmSession &session, const request &req, Json::Value &root)
		{
			std::string sidx = request::findValue(&req, "idx");
			if (sidx.empty())
				return;

			std::string newidx = request::findValue(&req, "newidx");
			if (newidx.empty())
				return;

			std::vector<std::vector<std::string>> result;

			// Check which device is newer

			time_t now = mytime(nullptr);
			struct tm tm1;
			localtime_r(&now, &tm1);
			struct tm LastUpdateTime_A;
			struct tm LastUpdateTime_B;

			result = m_sql.safe_query("SELECT A.LastUpdate, B.LastUpdate FROM DeviceStatus as A, DeviceStatus as B WHERE (A.ID == '%q') AND (B.ID == '%q')", sidx.c_str(), newidx.c_str());
			if (result.empty())
				return;

			std::string sLastUpdate_A = result[0][0];
			std::string sLastUpdate_B = result[0][1];

			time_t timeA, timeB;
			ParseSQLdatetime(timeA, LastUpdateTime_A, sLastUpdate_A, tm1.tm_isdst);
			ParseSQLdatetime(timeB, LastUpdateTime_B, sLastUpdate_B, tm1.tm_isdst);

			if (timeA < timeB)
			{
				// Swap idx with newidx
				sidx.swap(newidx);
			}

			result = m_sql.safe_query("SELECT HardwareID, DeviceID, Unit, Name, Type, SubType, SignalLevel, BatteryLevel, nValue, sValue FROM DeviceStatus WHERE (ID == '%q')",
						  newidx.c_str());
			if (result.empty())
				return;

			root["status"] = "OK";
			root["title"] = "TransferDevice";

			// transfer device logs (new to old)
			m_sql.TransferDevice(newidx, sidx);

			// now delete the NEW device
			m_sql.DeleteDevices(newidx);

			m_mainworker.m_scheduler.ReloadSchedules();
		}

		void CWebServer::RType_Notifications(WebEmSession &session, const request &req, Json::Value &root)
		{
			root["status"] = "OK";
			root["title"] = "Notifications";

			int ii = 0;

			// Add known notification systems
			for (const auto &notifier : m_notifications.m_notifiers)
			{
				root["notifiers"][ii]["name"] = notifier.first;
				root["notifiers"][ii]["description"] = notifier.first;
				ii++;
			}

			uint64_t idx = 0;
			if (!request::findValue(&req, "idx").empty())
			{
				idx = std::strtoull(request::findValue(&req, "idx").c_str(), nullptr, 10);
			}
			std::vector<_tNotification> notifications = m_notifications.GetNotifications(idx);
			if (!notifications.empty())
			{
				ii = 0;
				for (const auto &n : notifications)
				{
					root["result"][ii]["idx"] = Json::Value::UInt64(n.ID);
					std::string sParams = n.Params;
					if (sParams.empty())
					{
						sParams = "S";
					}
					root["result"][ii]["Params"] = sParams;
					root["result"][ii]["Priority"] = n.Priority;
					root["result"][ii]["SendAlways"] = n.SendAlways;
					root["result"][ii]["CustomMessage"] = n.CustomMessage;
					root["result"][ii]["ActiveSystems"] = n.ActiveSystems;
					ii++;
				}
			}
		}

		void CWebServer::RType_GetSharedUserDevices(WebEmSession &session, const request &req, Json::Value &root)
		{
			std::string idx = request::findValue(&req, "idx");
			if (idx.empty())
				return;
			root["status"] = "OK";
			root["title"] = "GetSharedUserDevices";

			std::vector<std::vector<std::string>> result;
			result = m_sql.safe_query("SELECT DeviceRowID FROM SharedDevices WHERE (SharedUserID == '%q')", idx.c_str());
			if (!result.empty())
			{
				int ii = 0;
				for (const auto &sd : result)
				{
					root["result"][ii]["DeviceRowIdx"] = sd[0];
					ii++;
				}
			}
		}

		void CWebServer::RType_SetSharedUserDevices(WebEmSession &session, const request &req, Json::Value &root)
		{
			std::string idx = request::findValue(&req, "idx");
			std::string userdevices = request::findValue(&req, "devices");
			if (idx.empty())
				return;
			root["status"] = "OK";
			root["title"] = "SetSharedUserDevices";
			std::vector<std::string> strarray;
			StringSplit(userdevices, ";", strarray);

			// First make a backup of the favorite devices before deleting the devices for this user, then add the (new) onces and restore favorites
			m_sql.safe_query("UPDATE SharedDevices SET SharedUserID = 0 WHERE SharedUserID == '%q' and Favorite == 1", idx.c_str());
			m_sql.safe_query("DELETE FROM SharedDevices WHERE SharedUserID == '%q'", idx.c_str());

			int nDevices = static_cast<int>(strarray.size());
			for (int ii = 0; ii < nDevices; ii++)
			{
				m_sql.safe_query("INSERT INTO SharedDevices (SharedUserID,DeviceRowID) VALUES ('%q','%q')", idx.c_str(), strarray[ii].c_str());
				m_sql.safe_query("UPDATE SharedDevices SET Favorite = 1 WHERE SharedUserid == '%q' AND DeviceRowID IN (SELECT DeviceRowID FROM SharedDevices WHERE SharedUserID == 0)",
						 idx.c_str());
			}
			m_sql.safe_query("DELETE FROM SharedDevices WHERE SharedUserID == 0");
			LoadUsers();
		}

		void CWebServer::RType_SetUsed(WebEmSession &session, const request &req, Json::Value &root)
		{
			if (session.rights != 2)
			{
				session.reply_status = reply::forbidden;
				return; // Only admin user allowed
			}

			std::string idx = request::findValue(&req, "idx");
			std::string sused = request::findValue(&req, "used");
			if ((idx.empty()) || (sused.empty()))
				return;
			std::vector<std::vector<std::string>> result;
			result = m_sql.safe_query("SELECT Type,SubType,HardwareID,CustomImage FROM DeviceStatus WHERE (ID == '%q')", idx.c_str());
			if (result.empty())
				return;

			std::string deviceid = request::findValue(&req, "deviceid");
			std::string name = HTMLSanitizer::Sanitize(request::findValue(&req, "name"));
			std::string description = HTMLSanitizer::Sanitize(request::findValue(&req, "description"));
			std::string sswitchtype = request::findValue(&req, "switchtype");
			std::string maindeviceidx = request::findValue(&req, "maindeviceidx");
			std::string addjvalue = request::findValue(&req, "addjvalue");
			std::string addjmulti = request::findValue(&req, "addjmulti");
			std::string addjvalue2 = request::findValue(&req, "addjvalue2");
			std::string addjmulti2 = request::findValue(&req, "addjmulti2");
			std::string setPoint = request::findValue(&req, "setpoint");
			std::string state = request::findValue(&req, "state");
			std::string mode = request::findValue(&req, "mode");
			std::string until = request::findValue(&req, "until");
			std::string clock = request::findValue(&req, "clock");
			std::string tmode = request::findValue(&req, "tmode");
			std::string fmode = request::findValue(&req, "fmode");
			std::string sCustomImage = request::findValue(&req, "customimage");

			std::string strunit = request::findValue(&req, "unit");
			std::string strParam1 = HTMLSanitizer::Sanitize(base64_decode(request::findValue(&req, "strparam1")));
			std::string strParam2 = HTMLSanitizer::Sanitize(base64_decode(request::findValue(&req, "strparam2")));
			std::string tmpstr = request::findValue(&req, "protected");
			bool bHasstrParam1 = request::hasValue(&req, "strparam1");
			int iProtected = (tmpstr == "true") ? 1 : 0;

			std::string sOptions = HTMLSanitizer::Sanitize(base64_decode(request::findValue(&req, "options")));
			std::string devoptions = HTMLSanitizer::Sanitize(CURLEncode::URLDecode(request::findValue(&req, "devoptions")));
			std::string EnergyMeterMode = CURLEncode::URLDecode(request::findValue(&req, "EnergyMeterMode"));

			char szTmp[200];

			bool bHaveUser = (!session.username.empty());
			// int iUser = -1;
			if (bHaveUser)
			{
				// iUser = FindUser(session.username.c_str());
			}

			int switchtype = -1;
			if (!sswitchtype.empty())
				switchtype = atoi(sswitchtype.c_str());

			int used = (sused == "true") ? 1 : 0;
			if (!maindeviceidx.empty())
				used = 0;

			std::vector<std::string> sd = result[0];
			unsigned char dType = atoi(sd[0].c_str());
			unsigned char dSubType = atoi(sd[1].c_str());
			int HwdID = atoi(sd[2].c_str());
			std::string sHwdID = sd[2];
			int OldCustomImage = atoi(sd[3].c_str());

			int CustomImage = (!sCustomImage.empty()) ? std::stoi(sCustomImage) : OldCustomImage;

			// Strip trailing spaces in 'name'
			name = stdstring_trim(name);

			// Strip trailing spaces in 'description'
			description = stdstring_trim(description);

			if (!setPoint.empty() || !state.empty())
			{
				double tempcelcius = atof(setPoint.c_str());
				if (m_sql.m_tempunit == TEMPUNIT_F)
				{
					// Convert back to Celsius
					tempcelcius = ConvertToCelsius(tempcelcius);
				}
				sprintf(szTmp, "%.2f", tempcelcius);

				if (dType != pTypeEvohomeZone && dType != pTypeEvohomeWater) // sql update now done in setsetpoint for evohome devices
				{
					m_sql.safe_query("UPDATE DeviceStatus SET Used=%d, sValue='%q' WHERE (ID == '%q')", used, szTmp, idx.c_str());
				}
			}
			if (name.empty())
			{
				m_sql.safe_query("UPDATE DeviceStatus SET Used=%d WHERE (ID == '%q')", used, idx.c_str());
			}
			else
			{
				if (switchtype == -1)
				{
					m_sql.safe_query("UPDATE DeviceStatus SET Used=%d, Name='%q', Description='%q', CustomImage=%d WHERE (ID == '%q')", used, name.c_str(), description.c_str(),
							 CustomImage, idx.c_str());
				}
				else
				{
					m_sql.safe_query("UPDATE DeviceStatus SET Used=%d, Name='%q', Description='%q', SwitchType=%d, CustomImage=%d WHERE (ID == '%q')", used, name.c_str(),
							 description.c_str(), switchtype, CustomImage, idx.c_str());
				}
			}

			if (bHasstrParam1)
			{
				m_sql.safe_query("UPDATE DeviceStatus SET StrParam1='%q', StrParam2='%q' WHERE (ID == '%q')", strParam1.c_str(), strParam2.c_str(), idx.c_str());
			}

			m_sql.safe_query("UPDATE DeviceStatus SET Protected=%d WHERE (ID == '%q')", iProtected, idx.c_str());

			if (!setPoint.empty() || !state.empty())
			{
				int urights = 3;
				if (bHaveUser)
				{
					int iUser = FindUser(session.username.c_str());
					if (iUser != -1)
					{
						urights = static_cast<int>(m_users[iUser].userrights);
						_log.Log(LOG_STATUS, "User: %s initiated a SetPoint command", m_users[iUser].Username.c_str());
					}
				}
				if (urights < 1)
					return;
				if (dType == pTypeEvohomeWater)
					m_mainworker.SetSetPoint(idx, (state == "On") ? 1.0F : 0.0F, mode, until); // FIXME float not guaranteed precise?
				else if (dType == pTypeEvohomeZone)
					m_mainworker.SetSetPoint(idx, static_cast<float>(atof(setPoint.c_str())), mode, until);
				else
					m_mainworker.SetSetPoint(idx, static_cast<float>(atof(setPoint.c_str())));
			}
			else if (!clock.empty())
			{
				int urights = 3;
				if (bHaveUser)
				{
					int iUser = FindUser(session.username.c_str());
					if (iUser != -1)
					{
						urights = static_cast<int>(m_users[iUser].userrights);
						_log.Log(LOG_STATUS, "User: %s initiated a SetClock command", m_users[iUser].Username.c_str());
					}
				}
				if (urights < 1)
					return;
				m_mainworker.SetClock(idx, clock);
			}
			else if (!tmode.empty())
			{
				int urights = 3;
				if (bHaveUser)
				{
					int iUser = FindUser(session.username.c_str());
					if (iUser != -1)
					{
						urights = static_cast<int>(m_users[iUser].userrights);
						_log.Log(LOG_STATUS, "User: %s initiated a Thermostat Mode command", m_users[iUser].Username.c_str());
					}
				}
				if (urights < 1)
					return;
				m_mainworker.SetZWaveThermostatMode(idx, atoi(tmode.c_str()));
			}
			else if (!fmode.empty())
			{
				int urights = 3;
				if (bHaveUser)
				{
					int iUser = FindUser(session.username.c_str());
					if (iUser != -1)
					{
						urights = static_cast<int>(m_users[iUser].userrights);
						_log.Log(LOG_STATUS, "User: %s initiated a Thermostat Fan Mode command", m_users[iUser].Username.c_str());
					}
				}
				if (urights < 1)
					return;
				m_mainworker.SetZWaveThermostatFanMode(idx, atoi(fmode.c_str()));
			}

			if (!strunit.empty())
			{
				bool bUpdateUnit = true;
#ifdef ENABLE_PYTHON
				// check if HW is plugin
				std::vector<std::vector<std::string>> result;
				result = m_sql.safe_query("SELECT Type FROM Hardware WHERE (ID == %d)", HwdID);
				if (!result.empty())
				{
					std::vector<std::string> sd = result[0];
					_eHardwareTypes Type = (_eHardwareTypes)dType;
					if (Type == HTYPE_PythonPlugin)
					{
						bUpdateUnit = false;
						_log.Log(LOG_ERROR, "CWebServer::RType_SetUsed: Not allowed to change unit of device owned by plugin %u!", HwdID);
					}
				}
#endif
				if (bUpdateUnit)
				{
					m_sql.safe_query("UPDATE DeviceStatus SET Unit='%q' WHERE (ID == '%q')", strunit.c_str(), idx.c_str());
				}
			}
			// FIXME evohome ...we need the zone id to update the correct zone...but this should be ok as a generic call?
			if (!deviceid.empty())
			{
				m_sql.safe_query("UPDATE DeviceStatus SET DeviceID='%q' WHERE (ID == '%q')", deviceid.c_str(), idx.c_str());
			}
			if (!addjvalue.empty())
			{
				double faddjvalue = atof(addjvalue.c_str());
				m_sql.safe_query("UPDATE DeviceStatus SET AddjValue=%f WHERE (ID == '%q')", faddjvalue, idx.c_str());
			}
			if (!addjmulti.empty())
			{
				double faddjmulti = atof(addjmulti.c_str());
				if (faddjmulti == 0)
					faddjmulti = 1;
				m_sql.safe_query("UPDATE DeviceStatus SET AddjMulti=%f WHERE (ID == '%q')", faddjmulti, idx.c_str());
			}
			if (!addjvalue2.empty())
			{
				double faddjvalue2 = atof(addjvalue2.c_str());
				m_sql.safe_query("UPDATE DeviceStatus SET AddjValue2=%f WHERE (ID == '%q')", faddjvalue2, idx.c_str());
			}
			if (!addjmulti2.empty())
			{
				double faddjmulti2 = atof(addjmulti2.c_str());
				if (faddjmulti2 == 0)
					faddjmulti2 = 1;
				m_sql.safe_query("UPDATE DeviceStatus SET AddjMulti2=%f WHERE (ID == '%q')", faddjmulti2, idx.c_str());
			}
			if (!EnergyMeterMode.empty())
			{
				auto options = m_sql.GetDeviceOptions(idx);
				options["EnergyMeterMode"] = EnergyMeterMode;
				uint64_t ullidx = std::stoull(idx);
				m_sql.SetDeviceOptions(ullidx, options);
			}

			if (!devoptions.empty())
			{
				m_sql.safe_query("UPDATE DeviceStatus SET Options='%q' WHERE (ID == '%q')", devoptions.c_str(), idx.c_str());
			}

			if (used == 0)
			{
				bool bRemoveSubDevices = (request::findValue(&req, "RemoveSubDevices") == "true");

				if (bRemoveSubDevices)
				{
					// if this device was a slave device, remove it
					m_sql.safe_query("DELETE FROM LightSubDevices WHERE (DeviceRowID == '%q')", idx.c_str());
				}
				m_sql.safe_query("DELETE FROM LightSubDevices WHERE (ParentID == '%q')", idx.c_str());

				m_sql.safe_query("DELETE FROM Timers WHERE (DeviceRowID == '%q')", idx.c_str());
			}

			// Save device options
			if (!sOptions.empty())
			{
				uint64_t ullidx = std::strtoull(idx.c_str(), nullptr, 10);
				m_sql.SetDeviceOptions(ullidx, m_sql.BuildDeviceOptions(sOptions, false));
			}

			if (!maindeviceidx.empty())
			{
				if (maindeviceidx != idx)
				{
					// this is a sub device for another light/switch
					// first check if it is not already a sub device
					result = m_sql.safe_query("SELECT ID FROM LightSubDevices WHERE (DeviceRowID=='%q') AND (ParentID =='%q')", idx.c_str(), maindeviceidx.c_str());
					if (result.empty())
					{
						// no it is not, add it
						m_sql.safe_query("INSERT INTO LightSubDevices (DeviceRowID, ParentID) VALUES ('%q','%q')", idx.c_str(), maindeviceidx.c_str());
					}
				}
			}
			if ((used == 0) && (maindeviceidx.empty()))
			{
				// really remove it, including log etc
				m_sql.DeleteDevices(idx);
			}
			else
			{
#ifdef ENABLE_PYTHON
				// Notify plugin framework about the change
				m_mainworker.m_pluginsystem.DeviceModified(atoi(idx.c_str()));
#endif
			}
			if (!result.empty())
			{
				root["status"] = "OK";
				root["title"] = "SetUsed";
			}
			if (m_sql.m_bEnableEventSystem)
				m_mainworker.m_eventsystem.GetCurrentStates();
		}

		void CWebServer::RType_Settings(WebEmSession &session, const request &req, Json::Value &root)
		{
			std::vector<std::vector<std::string>> result;
			char szTmp[100];

			result = m_sql.safe_query("SELECT Key, nValue, sValue FROM Preferences");
			if (result.empty())
				return;
			root["status"] = "OK";
			root["title"] = "settings";
#ifndef NOCLOUD
			root["cloudenabled"] = true;
#else
			root["cloudenabled"] = false;
#endif

			for (const auto &sd : result)
			{
				std::string Key = sd[0];
				int nValue = atoi(sd[1].c_str());
				std::string sValue = sd[2];

				if (Key == "Location")
				{
					std::vector<std::string> strarray;
					StringSplit(sValue, ";", strarray);

					if (strarray.size() == 2)
					{
						root["Location"]["Latitude"] = strarray[0];
						root["Location"]["Longitude"] = strarray[1];
					}
				}
				/* RK: notification settings */
				if (m_notifications.IsInConfig(Key))
				{
					if (sValue.empty() && nValue > 0)
					{
						root[Key] = nValue;
					}
					else
					{
						root[Key] = sValue;
					}
				}
				else if (Key == "DashboardType")
				{
					root["DashboardType"] = nValue;
				}
				else if (Key == "MobileType")
				{
					root["MobileType"] = nValue;
				}
				else if (Key == "LightHistoryDays")
				{
					root["LightHistoryDays"] = nValue;
				}
				else if (Key == "5MinuteHistoryDays")
				{
					root["ShortLogDays"] = nValue;
				}
				else if (Key == "ShortLogInterval")
				{
					root["ShortLogInterval"] = nValue;
				}
				else if (Key == "WebUserName")
				{
					root["WebUserName"] = base64_decode(sValue);
				}
				// else if (Key == "WebPassword")
				//{
				//	root["WebPassword"] = sValue;
				//}
				else if (Key == "SecPassword")
				{
					root["SecPassword"] = sValue;
				}
				else if (Key == "ProtectionPassword")
				{
					root["ProtectionPassword"] = sValue;
				}
				else if (Key == "WebLocalNetworks")
				{
					root["WebLocalNetworks"] = sValue;
				}
				else if (Key == "WebRemoteProxyIPs")
				{
					root["WebRemoteProxyIPs"] = sValue;
				}
				else if (Key == "RandomTimerFrame")
				{
					root["RandomTimerFrame"] = nValue;
				}
				else if (Key == "MeterDividerEnergy")
				{
					root["EnergyDivider"] = nValue;
				}
				else if (Key == "MeterDividerGas")
				{
					root["GasDivider"] = nValue;
				}
				else if (Key == "MeterDividerWater")
				{
					root["WaterDivider"] = nValue;
				}
				else if (Key == "ElectricVoltage")
				{
					root["ElectricVoltage"] = nValue;
				}
				else if (Key == "MaxElectricPower")
				{
					root["MaxElectricPower"] = nValue;
				}
				else if (Key == "CM113DisplayType")
				{
					root["CM113DisplayType"] = nValue;
				}
				else if (Key == "UseAutoUpdate")
				{
					root["UseAutoUpdate"] = nValue;
				}
				else if (Key == "UseAutoBackup")
				{
					root["UseAutoBackup"] = nValue;
				}
				else if (Key == "Rego6XXType")
				{
					root["Rego6XXType"] = nValue;
				}
				else if (Key == "CostEnergy")
				{
					sprintf(szTmp, "%.4f", (float)(nValue) / 10000.0F);
					root["CostEnergy"] = szTmp;
				}
				else if (Key == "CostEnergyT2")
				{
					sprintf(szTmp, "%.4f", (float)(nValue) / 10000.0F);
					root["CostEnergyT2"] = szTmp;
				}
				else if (Key == "CostEnergyR1")
				{
					sprintf(szTmp, "%.4f", (float)(nValue) / 10000.0F);
					root["CostEnergyR1"] = szTmp;
				}
				else if (Key == "CostEnergyR2")
				{
					sprintf(szTmp, "%.4f", (float)(nValue) / 10000.0F);
					root["CostEnergyR2"] = szTmp;
				}
				else if (Key == "CostGas")
				{
					sprintf(szTmp, "%.4f", (float)(nValue) / 10000.0F);
					root["CostGas"] = szTmp;
				}
				else if (Key == "CostWater")
				{
					sprintf(szTmp, "%.4f", (float)(nValue) / 10000.0F);
					root["CostWater"] = szTmp;
				}
				else if (Key == "ActiveTimerPlan")
				{
					root["ActiveTimerPlan"] = nValue;
				}
				else if (Key == "DoorbellCommand")
				{
					root["DoorbellCommand"] = nValue;
				}
				else if (Key == "SmartMeterType")
				{
					root["SmartMeterType"] = nValue;
				}
				else if (Key == "EnableTabFloorplans")
				{
					root["EnableTabFloorplans"] = nValue;
				}
				else if (Key == "EnableTabLights")
				{
					root["EnableTabLights"] = nValue;
				}
				else if (Key == "EnableTabTemp")
				{
					root["EnableTabTemp"] = nValue;
				}
				else if (Key == "EnableTabWeather")
				{
					root["EnableTabWeather"] = nValue;
				}
				else if (Key == "EnableTabUtility")
				{
					root["EnableTabUtility"] = nValue;
				}
				else if (Key == "EnableTabScenes")
				{
					root["EnableTabScenes"] = nValue;
				}
				else if (Key == "EnableTabCustom")
				{
					root["EnableTabCustom"] = nValue;
				}
				else if (Key == "NotificationSensorInterval")
				{
					root["NotificationSensorInterval"] = nValue;
				}
				else if (Key == "NotificationSwitchInterval")
				{
					root["NotificationSwitchInterval"] = nValue;
				}
				else if (Key == "RemoteSharedPort")
				{
					root["RemoteSharedPort"] = nValue;
				}
				else if (Key == "Language")
				{
					root["Language"] = sValue;
				}
				else if (Key == "Title")
				{
					root["Title"] = sValue;
				}
				else if (Key == "WindUnit")
				{
					root["WindUnit"] = nValue;
				}
				else if (Key == "TempUnit")
				{
					root["TempUnit"] = nValue;
				}
				else if (Key == "WeightUnit")
				{
					root["WeightUnit"] = nValue;
				}
				else if (Key == "AuthenticationMethod")
				{
					root["AuthenticationMethod"] = nValue;
				}
				else if (Key == "ReleaseChannel")
				{
					root["ReleaseChannel"] = nValue;
				}
				else if (Key == "RaspCamParams")
				{
					root["RaspCamParams"] = sValue;
				}
				else if (Key == "UVCParams")
				{
					root["UVCParams"] = sValue;
				}
				else if (Key == "AcceptNewHardware")
				{
					root["AcceptNewHardware"] = nValue;
				}
				else if (Key == "HideDisabledHardwareSensors")
				{
					root["HideDisabledHardwareSensors"] = nValue;
				}
				else if (Key == "ShowUpdateEffect")
				{
					root["ShowUpdateEffect"] = nValue;
				}
				else if (Key == "DegreeDaysBaseTemperature")
				{
					root["DegreeDaysBaseTemperature"] = sValue;
				}
				else if (Key == "EnableEventScriptSystem")
				{
					root["EnableEventScriptSystem"] = nValue;
				}
				else if (Key == "EventSystemLogFullURL")
				{
					root["EventSystemLogFullURL"] = nValue;
				}
				else if (Key == "DisableDzVentsSystem")
				{
					root["DisableDzVentsSystem"] = nValue;
				}
				else if (Key == "DzVentsLogLevel")
				{
					root["DzVentsLogLevel"] = nValue;
				}
				else if (Key == "LogEventScriptTrigger")
				{
					root["LogEventScriptTrigger"] = nValue;
				}
				else if (Key == "(1WireSensorPollPeriod")
				{
					root["1WireSensorPollPeriod"] = nValue;
				}
				else if (Key == "(1WireSwitchPollPeriod")
				{
					root["1WireSwitchPollPeriod"] = nValue;
				}
				else if (Key == "SecOnDelay")
				{
					root["SecOnDelay"] = nValue;
				}
				else if (Key == "AllowWidgetOrdering")
				{
					root["AllowWidgetOrdering"] = nValue;
				}
				else if (Key == "FloorplanPopupDelay")
				{
					root["FloorplanPopupDelay"] = nValue;
				}
				else if (Key == "FloorplanFullscreenMode")
				{
					root["FloorplanFullscreenMode"] = nValue;
				}
				else if (Key == "FloorplanAnimateZoom")
				{
					root["FloorplanAnimateZoom"] = nValue;
				}
				else if (Key == "FloorplanShowSensorValues")
				{
					root["FloorplanShowSensorValues"] = nValue;
				}
				else if (Key == "FloorplanShowSwitchValues")
				{
					root["FloorplanShowSwitchValues"] = nValue;
				}
				else if (Key == "FloorplanShowSceneNames")
				{
					root["FloorplanShowSceneNames"] = nValue;
				}
				else if (Key == "FloorplanRoomColour")
				{
					root["FloorplanRoomColour"] = sValue;
				}
				else if (Key == "FloorplanActiveOpacity")
				{
					root["FloorplanActiveOpacity"] = nValue;
				}
				else if (Key == "FloorplanInactiveOpacity")
				{
					root["FloorplanInactiveOpacity"] = nValue;
				}
				else if (Key == "SensorTimeout")
				{
					root["SensorTimeout"] = nValue;
				}
				else if (Key == "BatteryLowNotification")
				{
					root["BatterLowLevel"] = nValue;
				}
				else if (Key == "WebTheme")
				{
					root["WebTheme"] = sValue;
				}
#ifndef NOCLOUD
				else if (Key == "MyDomoticzInstanceId")
				{
					root["MyDomoticzInstanceId"] = sValue;
				}
				else if (Key == "MyDomoticzUserId")
				{
					root["MyDomoticzUserId"] = sValue;
				}
				else if (Key == "MyDomoticzPassword")
				{
					root["MyDomoticzPassword"] = sValue;
				}
				else if (Key == "MyDomoticzSubsystems")
				{
					root["MyDomoticzSubsystems"] = nValue;
				}
#endif
				else if (Key == "MyDomoticzSubsystems")
				{
					root["MyDomoticzSubsystems"] = nValue;
				}
				else if (Key == "SendErrorsAsNotification")
				{
					root["SendErrorsAsNotification"] = nValue;
				}
				else if (Key == "DeltaTemperatureLog")
				{
					root[Key] = sValue;
				}
				else if (Key == "IFTTTEnabled")
				{
					root["IFTTTEnabled"] = nValue;
				}
				else if (Key == "IFTTTAPI")
				{
					root["IFTTTAPI"] = sValue;
				}
			}
		}

		void CWebServer::RType_LightLog(WebEmSession &session, const request &req, Json::Value &root)
		{
			uint64_t idx = 0;
			if (!request::findValue(&req, "idx").empty())
			{
				idx = std::strtoull(request::findValue(&req, "idx").c_str(), nullptr, 10);
			}
			std::vector<std::vector<std::string>> result;
			// First get Device Type/SubType
			result = m_sql.safe_query("SELECT Type, SubType, SwitchType, Options FROM DeviceStatus WHERE (ID == %" PRIu64 ")", idx);
			if (result.empty())
				return;

			unsigned char dType = atoi(result[0][0].c_str());
			unsigned char dSubType = atoi(result[0][1].c_str());
			_eSwitchType switchtype = (_eSwitchType)atoi(result[0][2].c_str());
			std::map<std::string, std::string> options = m_sql.BuildDeviceOptions(result[0][3]);

			if ((dType != pTypeLighting1) && (dType != pTypeLighting2) && (dType != pTypeLighting3) && (dType != pTypeLighting4) && (dType != pTypeLighting5) &&
			    (dType != pTypeLighting6) && (dType != pTypeFan) && (dType != pTypeColorSwitch) && (dType != pTypeSecurity1) && (dType != pTypeSecurity2) && (dType != pTypeEvohome) &&
			    (dType != pTypeEvohomeRelay) && (dType != pTypeCurtain) && (dType != pTypeBlinds) && (dType != pTypeRFY) && (dType != pTypeRego6XXValue) && (dType != pTypeChime) &&
			    (dType != pTypeThermostat2) && (dType != pTypeThermostat3) && (dType != pTypeThermostat4) && (dType != pTypeRemote) && (dType != pTypeGeneralSwitch) &&
			    (dType != pTypeHomeConfort) && (dType != pTypeFS20) && (!((dType == pTypeRadiator1) && (dSubType == sTypeSmartwaresSwitchRadiator))) && (dType != pTypeHunter))
				return; // no light device! we should not be here!

			root["status"] = "OK";
			root["title"] = "LightLog";

			result = m_sql.safe_query("SELECT ROWID, nValue, sValue, User, Date FROM LightingLog WHERE (DeviceRowID==%" PRIu64 ") ORDER BY Date DESC", idx);
			if (!result.empty())
			{
				std::map<std::string, std::string> selectorStatuses;
				if (switchtype == STYPE_Selector)
				{
					GetSelectorSwitchStatuses(options, selectorStatuses);
				}

				int ii = 0;
				for (const auto &sd : result)
				{
					std::string lidx = sd.at(0);
					int nValue = atoi(sd.at(1).c_str());
					std::string sValue = sd.at(2);
					std::string sUser = sd.at(3);
					std::string ldate = sd.at(4);

					// add light details
					std::string lstatus;
					std::string ldata;
					int llevel = 0;
					bool bHaveDimmer = false;
					bool bHaveSelector = false;
					bool bHaveGroupCmd = false;
					int maxDimLevel = 0;

					if (switchtype == STYPE_Media)
					{
						if (sValue == "0")
							continue; // skip 0-values in log for MediaPlayers
						lstatus = sValue;
						ldata = lstatus;
					}
					else if (switchtype == STYPE_Selector)
					{
						if (ii == 0)
						{
							bHaveSelector = true;
							maxDimLevel = selectorStatuses.size();
						}
						if (!selectorStatuses.empty())
						{

							std::string sLevel = selectorStatuses[sValue];
							ldata = sLevel;
							lstatus = "Set Level: " + sLevel;
							llevel = atoi(sValue.c_str());
						}
					}
					else
					{
						GetLightStatus(dType, dSubType, switchtype, nValue, sValue, lstatus, llevel, bHaveDimmer, maxDimLevel, bHaveGroupCmd);
						ldata = lstatus;
					}

					if (ii == 0)
					{
						// Log these parameters once
						root["HaveDimmer"] = bHaveDimmer;
						root["result"][ii]["MaxDimLevel"] = maxDimLevel;
						root["HaveGroupCmd"] = bHaveGroupCmd;
						root["HaveSelector"] = bHaveSelector;
					}

					// Corrent names for certain switch types
					switch (switchtype)
					{
						case STYPE_Contact:
							ldata = (ldata == "On") ? "Open" : "Closed";
							break;
						case STYPE_DoorContact:
							ldata = (ldata == "On") ? "Open" : "Closed";
							break;
						case STYPE_DoorLock:
							ldata = (ldata == "On") ? "Locked" : "Unlocked";
							break;
						case STYPE_DoorLockInverted:
							ldata = (ldata == "On") ? "Unlocked" : "Locked";
							break;
						case STYPE_Blinds:
						case STYPE_VenetianBlindsEU:
						case STYPE_VenetianBlindsUS:
							ldata = (ldata == "On") ? "Closed" : "Open";
							break;
						case STYPE_BlindsInverted:
							ldata = (ldata == "On") ? "Open" : "Closed";
							break;
						case STYPE_BlindsPercentage:
						case STYPE_BlindsPercentageWithStop:
							if ((ldata == "On") || (ldata == "Off"))
							{
								ldata = (ldata == "On") ? "Closed" : "Open";
							}
							break;
						case STYPE_BlindsPercentageInverted:
						case STYPE_BlindsPercentageInvertedWithStop:
							if ((ldata == "On") || (ldata == "Off"))
							{
								ldata = (ldata == "On") ? "Open" : "Closed";
							}
							break;
					}

					root["result"][ii]["idx"] = lidx;
					root["result"][ii]["Date"] = ldate;
					root["result"][ii]["Data"] = ldata;
					root["result"][ii]["Status"] = lstatus;
					root["result"][ii]["Level"] = llevel;
					root["result"][ii]["User"] = sUser;
					ii++;
				}
			}
		}

		void CWebServer::RType_TextLog(WebEmSession &session, const request &req, Json::Value &root)
		{
			uint64_t idx = 0;
			if (!request::findValue(&req, "idx").empty())
			{
				idx = std::strtoull(request::findValue(&req, "idx").c_str(), nullptr, 10);
			}
			std::vector<std::vector<std::string>> result;

			root["status"] = "OK";
			root["title"] = "TextLog";

			result = m_sql.safe_query("SELECT ROWID, sValue, User, Date FROM LightingLog WHERE (DeviceRowID==%" PRIu64 ") ORDER BY Date DESC", idx);
			if (!result.empty())
			{
				int ii = 0;
				for (const auto &sd : result)
				{
					root["result"][ii]["idx"] = sd[0];
					root["result"][ii]["Data"] = sd[1];
					root["result"][ii]["User"] = sd[2];
					root["result"][ii]["Date"] = sd[3];
					ii++;
				}
			}
		}

		void CWebServer::RType_SceneLog(WebEmSession &session, const request &req, Json::Value &root)
		{
			uint64_t idx = 0;
			if (!request::findValue(&req, "idx").empty())
			{
				idx = std::strtoull(request::findValue(&req, "idx").c_str(), nullptr, 10);
			}
			std::vector<std::vector<std::string>> result;

			root["status"] = "OK";
			root["title"] = "SceneLog";

			result = m_sql.safe_query("SELECT ROWID, nValue, User, Date FROM SceneLog WHERE (SceneRowID==%" PRIu64 ") ORDER BY Date DESC", idx);
			if (!result.empty())
			{
				int ii = 0;
				for (const auto &sd : result)
				{
					root["result"][ii]["idx"] = sd[0];
					int nValue = atoi(sd[1].c_str());
					root["result"][ii]["Data"] = (nValue == 0) ? "Off" : "On";
					root["result"][ii]["User"] = sd[2];
					root["result"][ii]["Date"] = sd[3];
					ii++;
				}
			}
		}

		void CWebServer::RType_HandleGraph(WebEmSession &session, const request &req, Json::Value &root)
		{
			uint64_t idx = 0;
			if (!request::findValue(&req, "idx").empty())
			{
				idx = std::strtoull(request::findValue(&req, "idx").c_str(), nullptr, 10);
			}

			std::vector<std::vector<std::string>> result;
			char szTmp[300];

			std::string sensor = request::findValue(&req, "sensor");
			if (sensor.empty())
				return;
			std::string sensorarea = request::findValue(&req, "sensorarea");
			std::string srange = request::findValue(&req, "range");
			std::string sgroupby = request::findValue(&req, "groupby");
			if (srange.empty() && sgroupby.empty())
				return;

			time_t now = mytime(nullptr);
			struct tm tm1;
			localtime_r(&now, &tm1);

			result = m_sql.safe_query("SELECT Type, SubType, SwitchType, AddjValue, AddjMulti, AddjValue2, Options FROM DeviceStatus WHERE (ID == %" PRIu64 ")", idx);
			if (result.empty())
				return;

			unsigned char dType = atoi(result[0][0].c_str());
			unsigned char dSubType = atoi(result[0][1].c_str());
			_eMeterType metertype = (_eMeterType)atoi(result[0][2].c_str());
			_log.Debug(DEBUG_WEBSERVER, "dType:%02X  dSubType:%02X  metertype:%d", dType, dSubType, int(metertype));
			if ((dType == pTypeP1Power) || (dType == pTypeENERGY) || (dType == pTypePOWER) || (dType == pTypeCURRENTENERGY) || ((dType == pTypeGeneral) && (dSubType == sTypeKwh)))
			{
				metertype = MTYPE_ENERGY;
			}
			else if (dType == pTypeP1Gas)
				metertype = MTYPE_GAS;
			else if ((dType == pTypeRego6XXValue) && (dSubType == sTypeRego6XXCounter))
				metertype = MTYPE_COUNTER;

			// Special case of managed counter: Usage instead of Value in Meter table, and we don't want to calculate last value
			bool bIsManagedCounter = (dType == pTypeGeneral) && (dSubType == sTypeManagedCounter);

			double AddjValue = atof(result[0][3].c_str());
			double AddjMulti = atof(result[0][4].c_str());
			double AddjValue2 = atof(result[0][5].c_str());
			std::string sOptions = result[0][6];
			std::map<std::string, std::string> options = m_sql.BuildDeviceOptions(sOptions);

			float divider = m_sql.GetCounterDivider(int(metertype), int(dType), float(AddjValue2));

			std::string dbasetable;
			if (srange == "day")
			{
				if (sensor == "temp")
					dbasetable = "Temperature";
				else if (sensor == "rain")
					dbasetable = "Rain";
				else if (sensor == "Percentage")
					dbasetable = "Percentage";
				else if (sensor == "fan")
					dbasetable = "Fan";
				else if (sensor == "counter")
				{
					if ((dType == pTypeP1Power) || (dType == pTypeCURRENT) || (dType == pTypeCURRENTENERGY))
					{
						dbasetable = "MultiMeter";
					}
					else
					{
						dbasetable = "Meter";
					}
				}
				else if ((sensor == "wind") || (sensor == "winddir"))
					dbasetable = "Wind";
				else if (sensor == "uv")
					dbasetable = "UV";
				else
					return;
			}
			else
			{
				// week,year,month
				if (sensor == "temp")
					dbasetable = "Temperature_Calendar";
				else if (sensor == "rain")
					dbasetable = "Rain_Calendar";
				else if (sensor == "Percentage")
					dbasetable = "Percentage_Calendar";
				else if (sensor == "fan")
					dbasetable = "Fan_Calendar";
				else if (sensor == "counter")
				{
                    if (dType == pTypeP1Power
                        || dType == pTypeCURRENT
                        || dType == pTypeCURRENTENERGY
                        || dType == pTypeAirQuality
                        || dType == pTypeLux
                        || dType == pTypeWEIGHT
                        || dType == pTypeUsage
                        || dType == pTypeGeneral && dSubType == sTypeVisibility
                        || dType == pTypeGeneral && dSubType == sTypeDistance
                        || dType == pTypeGeneral && dSubType == sTypeSolarRadiation
                        || dType == pTypeGeneral && dSubType == sTypeSoilMoisture
                        || dType == pTypeGeneral && dSubType == sTypeLeafWetness
                        || dType == pTypeGeneral && dSubType == sTypeVoltage
                        || dType == pTypeGeneral && dSubType == sTypeCurrent
                        || dType == pTypeGeneral && dSubType == sTypePressure
                        || dType == pTypeGeneral && dSubType == sTypeSoundLevel
                        || dType == pTypeRFXSensor && dSubType == sTypeRFXSensorAD
                        || dType == pTypeRFXSensor && dSubType == sTypeRFXSensorVolt
                    ) {
                        dbasetable = "MultiMeter_Calendar";
                    } else {
                        dbasetable = "Meter_Calendar";
                    }
                }
				else if ((sensor == "wind") || (sensor == "winddir"))
					dbasetable = "Wind_Calendar";
				else if (sensor == "uv")
					dbasetable = "UV_Calendar";
				else
					return;
			}
			unsigned char tempsign = m_sql.m_tempsign[0];
			int iPrev;

			if (srange == "day")
			{
				if (sensor == "temp")
				{
					root["status"] = "OK";
					root["title"] = "Graph " + sensor + " " + srange;

					result = m_sql.safe_query("SELECT Temperature, Chill, Humidity, Barometer, Date, SetPoint FROM %s WHERE (DeviceRowID==%" PRIu64 ") ORDER BY Date ASC",
								  dbasetable.c_str(), idx);
					if (!result.empty())
					{
						int ii = 0;
						for (const auto &sd : result)
						{
							root["result"][ii]["d"] = sd[4].substr(0, 16);
							if (dType == pTypeRego6XXTemp
                                || dType == pTypeTEMP
                                || dType == pTypeTEMP_HUM
                                || dType == pTypeTEMP_HUM_BARO
                                || dType == pTypeTEMP_BARO
                                || dType == pTypeWIND && dSubType == sTypeWIND4
                                || dType == pTypeUV && dSubType == sTypeUV3
                                || dType == pTypeThermostat1
                                || dType == pTypeRadiator1
                                || dType == pTypeRFXSensor && dSubType == sTypeRFXSensorTemp
                                || dType == pTypeGeneral && dSubType == sTypeSystemTemp
                                || dType == pTypeGeneral && dSubType == sTypeBaro
                                || dType == pTypeThermostat && dSubType == sTypeThermSetpoint
                                || dType == pTypeEvohomeZone
                                || dType == pTypeEvohomeWater
                            )
							{
								double tvalue = ConvertTemperature(atof(sd[0].c_str()), tempsign);
								root["result"][ii]["te"] = tvalue;
							}
							if (((dType == pTypeWIND) && (dSubType == sTypeWIND4)) || ((dType == pTypeWIND) && (dSubType == sTypeWINDNoTemp)))
							{
								double tvalue = ConvertTemperature(atof(sd[1].c_str()), tempsign);
								root["result"][ii]["ch"] = tvalue;
							}
							if ((dType == pTypeHUM) || (dType == pTypeTEMP_HUM) || (dType == pTypeTEMP_HUM_BARO))
							{
								root["result"][ii]["hu"] = sd[2];
							}
							if ((dType == pTypeTEMP_HUM_BARO) || (dType == pTypeTEMP_BARO) || ((dType == pTypeGeneral) && (dSubType == sTypeBaro)))
							{
								if (dType == pTypeTEMP_HUM_BARO)
								{
									if (dSubType == sTypeTHBFloat)
									{
										sprintf(szTmp, "%.1f", atof(sd[3].c_str()) / 10.0F);
										root["result"][ii]["ba"] = szTmp;
									}
									else
										root["result"][ii]["ba"] = sd[3];
								}
								else if (dType == pTypeTEMP_BARO)
								{
									sprintf(szTmp, "%.1f", atof(sd[3].c_str()) / 10.0F);
									root["result"][ii]["ba"] = szTmp;
								}
								else if ((dType == pTypeGeneral) && (dSubType == sTypeBaro))
								{
									sprintf(szTmp, "%.1f", atof(sd[3].c_str()) / 10.0F);
									root["result"][ii]["ba"] = szTmp;
								}
							}
							if ((dType == pTypeEvohomeZone) || (dType == pTypeEvohomeWater))
							{
								double se = ConvertTemperature(atof(sd[5].c_str()), tempsign);
								root["result"][ii]["se"] = se;
							}

							ii++;
						}
					}
				}
				else if (sensor == "Percentage")
				{
					root["status"] = "OK";
					root["title"] = "Graph " + sensor + " " + srange;

					result = m_sql.safe_query("SELECT Percentage, Date FROM %s WHERE (DeviceRowID==%" PRIu64 ") ORDER BY Date ASC", dbasetable.c_str(), idx);
					if (!result.empty())
					{
						int ii = 0;
						for (const auto &sd : result)
						{
							root["result"][ii]["d"] = sd[1].substr(0, 16);
							root["result"][ii]["v"] = sd[0];
							ii++;
						}
					}
				}
				else if (sensor == "fan")
				{
					root["status"] = "OK";
					root["title"] = "Graph " + sensor + " " + srange;

					result = m_sql.safe_query("SELECT Speed, Date FROM %s WHERE (DeviceRowID==%" PRIu64 ") ORDER BY Date ASC", dbasetable.c_str(), idx);
					if (!result.empty())
					{
						int ii = 0;
						for (const auto &sd : result)
						{
							root["result"][ii]["d"] = sd[1].substr(0, 16);
							root["result"][ii]["v"] = sd[0];
							ii++;
						}
					}
				}

				else if (sensor == "counter")
				{
					if (dType == pTypeP1Power)
					{
						root["status"] = "OK";
						root["title"] = "Graph " + sensor + " " + srange;

						result = m_sql.safe_query("SELECT Value1, Value2, Value3, Value4, Value5, Value6, Date FROM %s WHERE (DeviceRowID==%" PRIu64 ") ORDER BY Date ASC",
									  dbasetable.c_str(), idx);
						if (!result.empty())
						{
							int ii = 0;
							bool bHaveDeliverd = false;
							bool bHaveFirstValue = false;
							long long lastUsage1, lastUsage2, lastDeliv1, lastDeliv2;
							time_t lastTime = 0;

							long long firstUsage1 = 0;
							long long firstUsage2 = 0;
							long long firstDeliv1 = 0;
							long long firstDeliv2 = 0;

							int nMeterType = 0;
							m_sql.GetPreferencesVar("SmartMeterType", nMeterType);

							int lastDay = 0;

							for (const auto &sd : result)
							{
								if (nMeterType == 0)
								{
									long long actUsage1 = std::strtoll(sd[0].c_str(), nullptr, 10);
									long long actUsage2 = std::strtoll(sd[4].c_str(), nullptr, 10);
									long long actDeliv1 = std::strtoll(sd[1].c_str(), nullptr, 10);
									long long actDeliv2 = std::strtoll(sd[5].c_str(), nullptr, 10);
									actDeliv1 = (actDeliv1 < 10) ? 0 : actDeliv1;
									actDeliv2 = (actDeliv2 < 10) ? 0 : actDeliv2;

									std::string stime = sd[6];
									struct tm ntime;
									time_t atime;
									ParseSQLdatetime(atime, ntime, stime, -1);
									if (lastDay != ntime.tm_mday)
									{
										lastDay = ntime.tm_mday;
										firstUsage1 = actUsage1;
										firstUsage2 = actUsage2;
										firstDeliv1 = actDeliv1;
										firstDeliv2 = actDeliv2;
									}

									if (bHaveFirstValue)
									{
										long curUsage1 = (long)(actUsage1 - lastUsage1);
										long curUsage2 = (long)(actUsage2 - lastUsage2);
										long curDeliv1 = (long)(actDeliv1 - lastDeliv1);
										long curDeliv2 = (long)(actDeliv2 - lastDeliv2);

										if ((curUsage1 < 0) || (curUsage1 > 100000))
											curUsage1 = 0;
										if ((curUsage2 < 0) || (curUsage2 > 100000))
											curUsage2 = 0;
										if ((curDeliv1 < 0) || (curDeliv1 > 100000))
											curDeliv1 = 0;
										if ((curDeliv2 < 0) || (curDeliv2 > 100000))
											curDeliv2 = 0;

										float tdiff = static_cast<float>(difftime(atime, lastTime));
										if (tdiff == 0)
											tdiff = 1;
										float tlaps = 3600.0F / tdiff;
										curUsage1 *= int(tlaps);
										curUsage2 *= int(tlaps);
										curDeliv1 *= int(tlaps);
										curDeliv2 *= int(tlaps);

										root["result"][ii]["d"] = sd[6].substr(0, 16);

										if ((curDeliv1 != 0) || (curDeliv2 != 0))
											bHaveDeliverd = true;

										sprintf(szTmp, "%ld", curUsage1);
										root["result"][ii]["v"] = szTmp;
										sprintf(szTmp, "%ld", curUsage2);
										root["result"][ii]["v2"] = szTmp;
										sprintf(szTmp, "%ld", curDeliv1);
										root["result"][ii]["r1"] = szTmp;
										sprintf(szTmp, "%ld", curDeliv2);
										root["result"][ii]["r2"] = szTmp;

										long pUsage1 = (long)(actUsage1 - firstUsage1);
										long pUsage2 = (long)(actUsage2 - firstUsage2);

										sprintf(szTmp, "%ld", pUsage1 + pUsage2);
										root["result"][ii]["eu"] = szTmp;
										if (bHaveDeliverd)
										{
											long pDeliv1 = (long)(actDeliv1 - firstDeliv1);
											long pDeliv2 = (long)(actDeliv2 - firstDeliv2);
											sprintf(szTmp, "%ld", pDeliv1 + pDeliv2);
											root["result"][ii]["eg"] = szTmp;
										}

										ii++;
									}
									else
									{
										bHaveFirstValue = true;
										if ((ntime.tm_hour != 0) && (ntime.tm_min != 0))
										{
											struct tm ltime;
											localtime_r(&atime, &tm1);
											getNoon(atime, ltime, ntime.tm_year + 1900, ntime.tm_mon + 1,
												ntime.tm_mday - 1); // We're only interested in finding the date
											int year = ltime.tm_year + 1900;
											int mon = ltime.tm_mon + 1;
											int day = ltime.tm_mday;
											sprintf(szTmp, "%04d-%02d-%02d", year, mon, day);
											std::vector<std::vector<std::string>> result2;
											result2 = m_sql.safe_query(
												"SELECT Counter1, Counter2, Counter3, Counter4 FROM Multimeter_Calendar WHERE (DeviceRowID==%" PRIu64
												") AND (Date=='%q')",
												idx, szTmp);
											if (!result2.empty())
											{
												std::vector<std::string> sd = result2[0];
												firstUsage1 = std::strtoll(sd[0].c_str(), nullptr, 10);
												firstDeliv1 = std::strtoll(sd[1].c_str(), nullptr, 10);
												firstUsage2 = std::strtoll(sd[2].c_str(), nullptr, 10);
												firstDeliv2 = std::strtoll(sd[3].c_str(), nullptr, 10);
												lastDay = ntime.tm_mday;
											}
										}
									}
									lastUsage1 = actUsage1;
									lastUsage2 = actUsage2;
									lastDeliv1 = actDeliv1;
									lastDeliv2 = actDeliv2;
									lastTime = atime;
								}
								else
								{
									// this meter has no decimals, so return the use peaks
									root["result"][ii]["d"] = sd[6].substr(0, 16);

									if (sd[3] != "0")
										bHaveDeliverd = true;
									root["result"][ii]["v"] = sd[2];
									root["result"][ii]["r1"] = sd[3];
									ii++;
								}
							}
							if (bHaveDeliverd)
							{
								root["delivered"] = true;
							}
						}
					}
					else if (dType == pTypeAirQuality)
					{ // day
						root["status"] = "OK";
						root["title"] = "Graph " + sensor + " " + srange;

						result = m_sql.safe_query("SELECT Value, Date FROM %s WHERE (DeviceRowID==%" PRIu64 ") ORDER BY Date ASC", dbasetable.c_str(), idx);
						if (!result.empty())
						{
							int ii = 0;
							for (const auto &sd : result)
							{
								root["result"][ii]["d"] = sd[1].substr(0, 16);
								root["result"][ii]["co2"] = sd[0];
								ii++;
							}
						}
					}
					else if ((dType == pTypeGeneral) && ((dSubType == sTypeSoilMoisture) || (dSubType == sTypeLeafWetness)))
					{ // day
						root["status"] = "OK";
						root["title"] = "Graph " + sensor + " " + srange;

						result = m_sql.safe_query("SELECT Value, Date FROM %s WHERE (DeviceRowID==%" PRIu64 ") ORDER BY Date ASC", dbasetable.c_str(), idx);
						if (!result.empty())
						{
							int ii = 0;
							for (const auto &sd : result)
							{
								root["result"][ii]["d"] = sd[1].substr(0, 16);
								root["result"][ii]["v"] = sd[0];
								ii++;
							}
						}
					}
					else if (((dType == pTypeGeneral) && (dSubType == sTypeVisibility)) || ((dType == pTypeGeneral) && (dSubType == sTypeDistance)) ||
						 ((dType == pTypeGeneral) && (dSubType == sTypeSolarRadiation)) || ((dType == pTypeGeneral) && (dSubType == sTypeVoltage)) ||
						 ((dType == pTypeGeneral) && (dSubType == sTypeCurrent)) || ((dType == pTypeGeneral) && (dSubType == sTypePressure)) ||
						 ((dType == pTypeGeneral) && (dSubType == sTypeSoundLevel)))
					{ // day
						root["status"] = "OK";
						root["title"] = "Graph " + sensor + " " + srange;
						float vdiv = 10.0F;
						if (((dType == pTypeGeneral) && (dSubType == sTypeVoltage)) || ((dType == pTypeGeneral) && (dSubType == sTypeCurrent)))
						{
							vdiv = 1000.0F;
						}
						result = m_sql.safe_query("SELECT Value, Date FROM %s WHERE (DeviceRowID==%" PRIu64 ") ORDER BY Date ASC", dbasetable.c_str(), idx);
						if (!result.empty())
						{
							int ii = 0;
							for (const auto &sd : result)
							{
								root["result"][ii]["d"] = sd[1].substr(0, 16);
								float fValue = float(atof(sd[0].c_str())) / vdiv;
								if (metertype == 1)
								{
									if ((dType == pTypeGeneral) && (dSubType == sTypeDistance))
										fValue *= 0.3937007874015748F; // inches
									else
										fValue *= 0.6214F; // miles
								}
								if ((dType == pTypeGeneral) && (dSubType == sTypeVoltage))
									sprintf(szTmp, "%.3f", fValue);
								else if ((dType == pTypeGeneral) && (dSubType == sTypeCurrent))
									sprintf(szTmp, "%.3f", fValue);
								else
									sprintf(szTmp, "%.1f", fValue);
								root["result"][ii]["v"] = szTmp;
								ii++;
							}
						}
					}
					else if ((dType == pTypeRFXSensor) && ((dSubType == sTypeRFXSensorAD) || (dSubType == sTypeRFXSensorVolt)))
					{ // day
						root["status"] = "OK";
						root["title"] = "Graph " + sensor + " " + srange;

						result = m_sql.safe_query("SELECT Value, Date FROM %s WHERE (DeviceRowID==%" PRIu64 ") ORDER BY Date ASC", dbasetable.c_str(), idx);
						if (!result.empty())
						{
							int ii = 0;
							for (const auto &sd : result)
							{
								root["result"][ii]["d"] = sd[1].substr(0, 16);
								root["result"][ii]["v"] = sd[0];
								ii++;
							}
						}
					}
					else if (dType == pTypeLux)
					{ // day
						root["status"] = "OK";
						root["title"] = "Graph " + sensor + " " + srange;

						result = m_sql.safe_query("SELECT Value, Date FROM %s WHERE (DeviceRowID==%" PRIu64 ") ORDER BY Date ASC", dbasetable.c_str(), idx);
						if (!result.empty())
						{
							int ii = 0;
							for (const auto &sd : result)
							{
								root["result"][ii]["d"] = sd[1].substr(0, 16);
								root["result"][ii]["lux"] = sd[0];
								ii++;
							}
						}
					}
					else if (dType == pTypeWEIGHT)
					{ // day
						root["status"] = "OK";
						root["title"] = "Graph " + sensor + " " + srange;

						result = m_sql.safe_query("SELECT Value, Date FROM %s WHERE (DeviceRowID==%" PRIu64 ") ORDER BY Date ASC", dbasetable.c_str(), idx);
						if (!result.empty())
						{
							int ii = 0;
							for (const auto &sd : result)
							{
								root["result"][ii]["d"] = sd[1].substr(0, 16);
								sprintf(szTmp, "%.1f", m_sql.m_weightscale * atof(sd[0].c_str()) / 10.0F);
								root["result"][ii]["v"] = szTmp;
								ii++;
							}
						}
					}
					else if (dType == pTypeUsage)
					{ // day
						root["status"] = "OK";
						root["title"] = "Graph " + sensor + " " + srange;

						result = m_sql.safe_query("SELECT Value, Date FROM %s WHERE (DeviceRowID==%" PRIu64 ") ORDER BY Date ASC", dbasetable.c_str(), idx);
						if (!result.empty())
						{
							int ii = 0;
							for (const auto &sd : result)
							{
								root["result"][ii]["d"] = sd[1].substr(0, 16);
								root["result"][ii]["u"] = atof(sd[0].c_str()) / 10.0F;
								ii++;
							}
						}
					}
					else if (dType == pTypeCURRENT)
					{
						root["status"] = "OK";
						root["title"] = "Graph " + sensor + " " + srange;

						// CM113
						int displaytype = 0;
						int voltage = 230;
						m_sql.GetPreferencesVar("CM113DisplayType", displaytype);
						m_sql.GetPreferencesVar("ElectricVoltage", voltage);

						root["displaytype"] = displaytype;

						result = m_sql.safe_query("SELECT Value1, Value2, Value3, Date FROM %s WHERE (DeviceRowID==%" PRIu64 ") ORDER BY Date ASC", dbasetable.c_str(), idx);
						if (!result.empty())
						{
							int ii = 0;
							bool bHaveL1 = false;
							bool bHaveL2 = false;
							bool bHaveL3 = false;
							for (const auto &sd : result)
							{
								root["result"][ii]["d"] = sd[3].substr(0, 16);

								float fval1 = static_cast<float>(atof(sd[0].c_str()) / 10.0F);
								float fval2 = static_cast<float>(atof(sd[1].c_str()) / 10.0F);
								float fval3 = static_cast<float>(atof(sd[2].c_str()) / 10.0F);

								if (fval1 != 0)
									bHaveL1 = true;
								if (fval2 != 0)
									bHaveL2 = true;
								if (fval3 != 0)
									bHaveL3 = true;

								if (displaytype == 0)
								{
									sprintf(szTmp, "%.1f", fval1);
									root["result"][ii]["v1"] = szTmp;
									sprintf(szTmp, "%.1f", fval2);
									root["result"][ii]["v2"] = szTmp;
									sprintf(szTmp, "%.1f", fval3);
									root["result"][ii]["v3"] = szTmp;
								}
								else
								{
									sprintf(szTmp, "%d", int(fval1 * voltage));
									root["result"][ii]["v1"] = szTmp;
									sprintf(szTmp, "%d", int(fval2 * voltage));
									root["result"][ii]["v2"] = szTmp;
									sprintf(szTmp, "%d", int(fval3 * voltage));
									root["result"][ii]["v3"] = szTmp;
								}
								ii++;
							}
							if ((!bHaveL1) && (!bHaveL2) && (!bHaveL3))
							{
								root["haveL1"] = true; // show at least something
							}
							else
							{
								if (bHaveL1)
									root["haveL1"] = true;
								if (bHaveL2)
									root["haveL2"] = true;
								if (bHaveL3)
									root["haveL3"] = true;
							}
						}
					}
					else if (dType == pTypeCURRENTENERGY)
					{
						root["status"] = "OK";
						root["title"] = "Graph " + sensor + " " + srange;

						// CM113
						int displaytype = 0;
						int voltage = 230;
						m_sql.GetPreferencesVar("CM113DisplayType", displaytype);
						m_sql.GetPreferencesVar("ElectricVoltage", voltage);

						root["displaytype"] = displaytype;

						result = m_sql.safe_query("SELECT Value1, Value2, Value3, Date FROM %s WHERE (DeviceRowID==%" PRIu64 ") ORDER BY Date ASC", dbasetable.c_str(), idx);
						if (!result.empty())
						{
							int ii = 0;
							bool bHaveL1 = false;
							bool bHaveL2 = false;
							bool bHaveL3 = false;
							for (const auto &sd : result)
							{
								root["result"][ii]["d"] = sd[3].substr(0, 16);

								float fval1 = static_cast<float>(atof(sd[0].c_str()) / 10.0F);
								float fval2 = static_cast<float>(atof(sd[1].c_str()) / 10.0F);
								float fval3 = static_cast<float>(atof(sd[2].c_str()) / 10.0F);

								if (fval1 != 0)
									bHaveL1 = true;
								if (fval2 != 0)
									bHaveL2 = true;
								if (fval3 != 0)
									bHaveL3 = true;

								if (displaytype == 0)
								{
									sprintf(szTmp, "%.1f", fval1);
									root["result"][ii]["v1"] = szTmp;
									sprintf(szTmp, "%.1f", fval2);
									root["result"][ii]["v2"] = szTmp;
									sprintf(szTmp, "%.1f", fval3);
									root["result"][ii]["v3"] = szTmp;
								}
								else
								{
									sprintf(szTmp, "%d", int(fval1 * voltage));
									root["result"][ii]["v1"] = szTmp;
									sprintf(szTmp, "%d", int(fval2 * voltage));
									root["result"][ii]["v2"] = szTmp;
									sprintf(szTmp, "%d", int(fval3 * voltage));
									root["result"][ii]["v3"] = szTmp;
								}
								ii++;
							}
							if ((!bHaveL1) && (!bHaveL2) && (!bHaveL3))
							{
								root["haveL1"] = true; // show at least something
							}
							else
							{
								if (bHaveL1)
									root["haveL1"] = true;
								if (bHaveL2)
									root["haveL2"] = true;
								if (bHaveL3)
									root["haveL3"] = true;
							}
						}
					}
					else if ((dType == pTypeENERGY) || (dType == pTypePOWER) || (dType == pTypeYouLess) || ((dType == pTypeGeneral) && (dSubType == sTypeKwh)))
					{
						root["status"] = "OK";
						root["title"] = "Graph " + sensor + " " + srange;
						root["ValueQuantity"] = options["ValueQuantity"];
						root["ValueUnits"] = options["ValueUnits"];

						// First check if we had any usage in the short log, if not, its probably a meter without usage
						bool bHaveUsage = true;
						result = m_sql.safe_query("SELECT MIN([Usage]), MAX([Usage]) FROM %s WHERE (DeviceRowID==%" PRIu64 ")", dbasetable.c_str(), idx);
						if (!result.empty())
						{
							long long minValue = std::strtoll(result[0][0].c_str(), nullptr, 10);
							long long maxValue = std::strtoll(result[0][1].c_str(), nullptr, 10);

							if ((minValue == 0) && (maxValue == 0))
							{
								bHaveUsage = false;
							}
						}

						int ii = 0;
						result = m_sql.safe_query("SELECT Value,[Usage], Date FROM %s WHERE (DeviceRowID==%" PRIu64 ") ORDER BY Date ASC", dbasetable.c_str(), idx);

						int method = 0;
						std::string sMethod = request::findValue(&req, "method");
						if (!sMethod.empty())
							method = atoi(sMethod.c_str());
						if (bHaveUsage == false)
							method = 0;

						if ((dType == pTypeYouLess) && ((metertype == MTYPE_ENERGY) || (metertype == MTYPE_ENERGY_GENERATED)))
							method = 1;

						if (method != 0)
						{
							// realtime graph
							if ((dType == pTypeENERGY) || (dType == pTypePOWER))
								divider /= 100.0F;
						}
						root["method"] = method;
						bool bHaveFirstValue = false;
						bool bHaveFirstRealValue = false;
						long long ulFirstRealValue = 0;
						long long ulFirstValue = 0;
						long long ulLastValue = 0;
						std::string LastDateTime;

						if (!result.empty())
						{
							for (auto itt = result.begin(); itt != result.end(); ++itt)
							{
								std::vector<std::string> sd = *itt;

								// If method == 1, provide BOTH hourly and instant usage for combined graph
								{
									// bars / hour
									std::string actDateTimeHour = sd[2].substr(0, 13);
									long long actValue = std::strtoll(sd[0].c_str(), nullptr, 10); // actual energy value

									// if (actValue >= ulLastValue) ulLastValue = actValue; //Removed because usage energy may be negative if the production power
									// is greater than usage power
									ulLastValue = actValue;

									if (actDateTimeHour != LastDateTime || ((method == 1) && (itt + 1 == result.end())))
									{
										if (bHaveFirstValue)
										{
											// root["result"][ii]["d"] = LastDateTime + (method == 1 ? ":30" : ":00");
											//^^ not necessarily bad, but is currently inconsistent with all other day graphs
											root["result"][ii]["d"] = LastDateTime + ":00";

											long long ulTotalValue = ulLastValue - ulFirstValue;
											if (ulTotalValue == 0)
											{
												// Could be the P1 Gas Meter, only transmits one every 1 a 2 hours
												ulTotalValue = ulLastValue - ulFirstRealValue;
											}
											ulFirstRealValue = ulLastValue;
											float TotalValue = float(ulTotalValue);
											switch (metertype)
											{
												case MTYPE_ENERGY:
												case MTYPE_ENERGY_GENERATED:
													sprintf(szTmp, "%.3f", (TotalValue / divider) * 1000.0F); // from kWh -> Watt
													break;
												case MTYPE_GAS:
													sprintf(szTmp, "%.3f", TotalValue / divider);
													break;
												case MTYPE_WATER:
													sprintf(szTmp, "%.3f", TotalValue / divider);
													break;
												case MTYPE_COUNTER:
													sprintf(szTmp, "%g", TotalValue / divider);
													break;
												default:
													strcpy(szTmp, "0");
													break;
											}
											root["result"][ii][method == 1 ? "eu" : "v"] = szTmp;
											ii++;
										}
										LastDateTime = actDateTimeHour;
										bHaveFirstValue = false;
									}
									if (!bHaveFirstValue)
									{
										ulFirstValue = ulLastValue;
										bHaveFirstValue = true;
									}
									if (!bHaveFirstRealValue)
									{
										bHaveFirstRealValue = true;
										ulFirstRealValue = ulLastValue;
									}
								}

								if (method == 1)
								{
									long long actValue = std::strtoll(sd[1].c_str(), nullptr, 10);

									root["result"][ii]["d"] = sd[2].substr(0, 16);

									float TotalValue = float(actValue);
									if ((dType == pTypeGeneral) && (dSubType == sTypeKwh))
										TotalValue /= 10.0F;
									switch (metertype)
									{
										case MTYPE_ENERGY:
										case MTYPE_ENERGY_GENERATED:
											sprintf(szTmp, "%.3f", (TotalValue / divider) * 1000.0F); // from kWh -> Watt
											break;
										case MTYPE_GAS:
											sprintf(szTmp, "%.2f", TotalValue / divider);
											break;
										case MTYPE_WATER:
											sprintf(szTmp, "%.3f", TotalValue / divider);
											break;
										case MTYPE_COUNTER:
											sprintf(szTmp, "%g", TotalValue / divider);
											break;
										default:
											strcpy(szTmp, "0");
											break;
									}
									root["result"][ii]["v"] = szTmp;
									ii++;
								}
							}
						}
					}
					else
					{
						root["status"] = "OK";
						root["title"] = "Graph " + sensor + " " + srange;
						root["ValueQuantity"] = options["ValueQuantity"];
						root["ValueUnits"] = options["ValueUnits"];

						int ii = 0;

						bool bHaveFirstValue = false;
						bool bHaveFirstRealValue = false;
						unsigned long long ulFirstValue = 0;
						unsigned long long ulLastValue = 0;

						std::string LastDateTime;
						time_t lastTime = 0;

						if (bIsManagedCounter)
						{
							result = m_sql.safe_query("SELECT Usage, Date FROM %s WHERE (DeviceRowID==%" PRIu64 ") ORDER BY Date ASC", dbasetable.c_str(), idx);
							bHaveFirstValue = true;
							bHaveFirstRealValue = true;
						}
						else
						{
							result = m_sql.safe_query("SELECT Value, Date FROM %s WHERE (DeviceRowID==%" PRIu64 ") ORDER BY Date ASC", dbasetable.c_str(), idx);
						}

						int method = 0;
						std::string sMethod = request::findValue(&req, "method");
						if (!sMethod.empty())
							method = atoi(sMethod.c_str());

						if (!result.empty())
						{
							for (const auto &sd : result)
							{
								if (method == 0)
								{
									// bars / hour

									unsigned long long actValue = std::strtoull(sd[0].c_str(), nullptr, 10);

									std::string actDateTimeHour = sd[1].substr(0, 13);
									if (actDateTimeHour != LastDateTime)
									{
										if (bHaveFirstValue)
										{
											struct tm ntime;
											time_t atime;
											if (actDateTimeHour.size() == 10)
												actDateTimeHour += " 00";
											constructTime(atime, ntime, atoi(actDateTimeHour.substr(0, 4).c_str()),
												      atoi(actDateTimeHour.substr(5, 2).c_str()), atoi(actDateTimeHour.substr(8, 2).c_str()),
												      atoi(actDateTimeHour.substr(11, 2).c_str()) - 1, 0, 0, -1);

											char szTime[50];
											sprintf(szTime, "%04d-%02d-%02d %02d:00", ntime.tm_year + 1900, ntime.tm_mon + 1, ntime.tm_mday, ntime.tm_hour);
											root["result"][ii]["d"] = szTime;

											// float TotalValue = float(actValue - ulFirstValue);

											// prevents graph from going crazy if the meter counter resets
											float TotalValue = (actValue >= ulFirstValue) ? float(actValue - ulFirstValue) : actValue;

											// if (TotalValue != 0)
											{
												switch (metertype)
												{
													case MTYPE_ENERGY:
													case MTYPE_ENERGY_GENERATED:
														sprintf(szTmp, "%.3f", (TotalValue / divider) * 1000.0F); // from kWh -> Watt
														break;
													case MTYPE_GAS:
														sprintf(szTmp, "%.3f", TotalValue / divider);
														break;
													case MTYPE_WATER:
														sprintf(szTmp, "%.3f", TotalValue / divider);
														break;
													case MTYPE_COUNTER:
														sprintf(szTmp, "%g", TotalValue / divider);
														break;
													default:
														strcpy(szTmp, "0");
														break;
												}
												root["result"][ii]["v"] = szTmp;
												ii++;
											}
										}
										if (!bIsManagedCounter)
										{
											ulFirstValue = actValue;
										}
										LastDateTime = actDateTimeHour;
									}

									if (!bHaveFirstValue)
									{
										ulFirstValue = actValue;
										bHaveFirstValue = true;
									}
									ulLastValue = actValue;
								}
								else
								{
									// realtime graph
									unsigned long long actValue = std::strtoull(sd[0].c_str(), nullptr, 10);

									std::string stime = sd[1];
									struct tm ntime;
									time_t atime;
									ParseSQLdatetime(atime, ntime, stime, -1);
									if (bHaveFirstRealValue)
									{
										long long curValue = actValue - ulLastValue;

										float tdiff = static_cast<float>(difftime(atime, lastTime));
										if (tdiff == 0)
											tdiff = 1;
										float tlaps = 3600.0F / tdiff;
										curValue *= int(tlaps);

										root["result"][ii]["d"] = sd[1].substr(0, 16);

										float TotalValue = float(curValue);
										// if (TotalValue != 0)
										{
											switch (metertype)
											{
												case MTYPE_ENERGY:
												case MTYPE_ENERGY_GENERATED:
													sprintf(szTmp, "%.3f", (TotalValue / divider) * 1000.0F); // from kWh -> Watt
													break;
												case MTYPE_GAS:
													sprintf(szTmp, "%.2f", TotalValue / divider);
													break;
												case MTYPE_WATER:
													sprintf(szTmp, "%.3f", TotalValue / divider);
													break;
												case MTYPE_COUNTER:
													sprintf(szTmp, "%g", TotalValue / divider);
													break;
												default:
													strcpy(szTmp, "0");
													break;
											}
											root["result"][ii]["v"] = szTmp;
											ii++;
										}
									}
									else
										bHaveFirstRealValue = true;
									if (!bIsManagedCounter)
									{
										ulLastValue = actValue;
									}
									lastTime = atime;
								}
							}
						}
						if ((!bIsManagedCounter) && (bHaveFirstValue) && (method == 0))
						{
							// add last value
							root["result"][ii]["d"] = LastDateTime + ":00";

							unsigned long long ulTotalValue = ulLastValue - ulFirstValue;

							float TotalValue = float(ulTotalValue);

							// if (TotalValue != 0)
							{
								switch (metertype)
								{
									case MTYPE_ENERGY:
									case MTYPE_ENERGY_GENERATED:
										sprintf(szTmp, "%.3f", (TotalValue / divider) * 1000.0F); // from kWh -> Watt
										break;
									case MTYPE_GAS:
										sprintf(szTmp, "%.3f", TotalValue / divider);
										break;
									case MTYPE_WATER:
										sprintf(szTmp, "%.3f", TotalValue / divider);
										break;
									case MTYPE_COUNTER:
										sprintf(szTmp, "%g", TotalValue / divider);
										break;
									default:
										strcpy(szTmp, "0");
										break;
								}
								root["result"][ii]["v"] = szTmp;
								ii++;
							}
						}
					}
				}
				else if (sensor == "uv")
				{
					root["status"] = "OK";
					root["title"] = "Graph " + sensor + " " + srange;

					result = m_sql.safe_query("SELECT Level, Date FROM %s WHERE (DeviceRowID==%" PRIu64 ") ORDER BY Date ASC", dbasetable.c_str(), idx);
					if (!result.empty())
					{
						int ii = 0;
						for (const auto &sd : result)
						{
							root["result"][ii]["d"] = sd[1].substr(0, 16);
							root["result"][ii]["uvi"] = sd[0];
							ii++;
						}
					}
				}
				else if (sensor == "rain")
				{
					root["status"] = "OK";
					root["title"] = "Graph " + sensor + " " + srange;

					int LastHour = -1;
					float LastTotalPreviousHour = -1;

					float LastValue = -1;
					std::string LastDate;

					result = m_sql.safe_query("SELECT Total, Date FROM %s WHERE (DeviceRowID==%" PRIu64 ") ORDER BY Date ASC", dbasetable.c_str(), idx);
					if (!result.empty())
					{
						int ii = 0;
						for (const auto &sd : result)
						{
							float ActTotal = static_cast<float>(atof(sd[0].c_str()));
							int Hour = atoi(sd[1].substr(11, 2).c_str());
							if (Hour != LastHour)
							{
								if (LastHour != -1)
								{
									int NextCalculatedHour = (LastHour + 1) % 24;
									if (Hour != NextCalculatedHour)
									{
										// Looks like we have a GAP somewhere, finish the last hour
										root["result"][ii]["d"] = LastDate;
										double mmval = ActTotal - LastValue;
										mmval *= AddjMulti;
										sprintf(szTmp, "%.1f", mmval);
										root["result"][ii]["mm"] = szTmp;
										ii++;
									}
									else
									{
										root["result"][ii]["d"] = sd[1].substr(0, 16);
										double mmval = ActTotal - LastTotalPreviousHour;
										mmval *= AddjMulti;
										sprintf(szTmp, "%.1f", mmval);
										root["result"][ii]["mm"] = szTmp;
										ii++;
									}
								}
								LastHour = Hour;
								LastTotalPreviousHour = ActTotal;
							}
							LastValue = ActTotal;
							LastDate = sd[1];
						}
					}
				}
				else if (sensor == "wind")
				{
					root["status"] = "OK";
					root["title"] = "Graph " + sensor + " " + srange;

					result = m_sql.safe_query("SELECT Direction, Speed, Gust, Date FROM %s WHERE (DeviceRowID==%" PRIu64 ") ORDER BY Date ASC", dbasetable.c_str(), idx);
					if (!result.empty())
					{
						int ii = 0;
						for (const auto &sd : result)
						{
							root["result"][ii]["d"] = sd[3].substr(0, 16);
							root["result"][ii]["di"] = sd[0];

							int intSpeed = atoi(sd[1].c_str());
							int intGust = atoi(sd[2].c_str());

							if (m_sql.m_windunit != WINDUNIT_Beaufort)
							{
								sprintf(szTmp, "%.1f", float(intSpeed) * m_sql.m_windscale);
								root["result"][ii]["sp"] = szTmp;
								sprintf(szTmp, "%.1f", float(intGust) * m_sql.m_windscale);
								root["result"][ii]["gu"] = szTmp;
							}
							else
							{
								float windspeedms = float(intSpeed) * 0.1F;
								float windgustms = float(intGust) * 0.1F;
								sprintf(szTmp, "%d", MStoBeaufort(windspeedms));
								root["result"][ii]["sp"] = szTmp;
								sprintf(szTmp, "%d", MStoBeaufort(windgustms));
								root["result"][ii]["gu"] = szTmp;
							}
							ii++;
						}
					}
				}
				else if (sensor == "winddir")
				{
					root["status"] = "OK";
					root["title"] = "Graph " + sensor + " " + srange;

					result = m_sql.safe_query("SELECT Direction, Speed, Gust FROM %s WHERE (DeviceRowID==%" PRIu64 ") ORDER BY Date ASC", dbasetable.c_str(), idx);
					if (!result.empty())
					{
						std::map<int, int> _directions;
						std::array<std::array<int, 8>, 17> wdirtabletemp = {};
						std::string szLegendLabels[7];
						int ii = 0;

						int totalvalues = 0;
						// init dir list
						int idir;
						for (idir = 0; idir < 360 + 1; idir++)
							_directions[idir] = 0;

						if (m_sql.m_windunit == WINDUNIT_MS)
						{
							szLegendLabels[0] = "&lt; 0.5 " + m_sql.m_windsign;
							szLegendLabels[1] = "0.5-2 " + m_sql.m_windsign;
							szLegendLabels[2] = "2-4 " + m_sql.m_windsign;
							szLegendLabels[3] = "4-6 " + m_sql.m_windsign;
							szLegendLabels[4] = "6-8 " + m_sql.m_windsign;
							szLegendLabels[5] = "8-10 " + m_sql.m_windsign;
							szLegendLabels[6] = "&gt; 10" + m_sql.m_windsign;
						}
						else if (m_sql.m_windunit == WINDUNIT_KMH)
						{
							szLegendLabels[0] = "&lt; 2 " + m_sql.m_windsign;
							szLegendLabels[1] = "2-4 " + m_sql.m_windsign;
							szLegendLabels[2] = "4-6 " + m_sql.m_windsign;
							szLegendLabels[3] = "6-10 " + m_sql.m_windsign;
							szLegendLabels[4] = "10-20 " + m_sql.m_windsign;
							szLegendLabels[5] = "20-36 " + m_sql.m_windsign;
							szLegendLabels[6] = "&gt; 36" + m_sql.m_windsign;
						}
						else if (m_sql.m_windunit == WINDUNIT_MPH)
						{
							szLegendLabels[0] = "&lt; 3 " + m_sql.m_windsign;
							szLegendLabels[1] = "3-7 " + m_sql.m_windsign;
							szLegendLabels[2] = "7-12 " + m_sql.m_windsign;
							szLegendLabels[3] = "12-18 " + m_sql.m_windsign;
							szLegendLabels[4] = "18-24 " + m_sql.m_windsign;
							szLegendLabels[5] = "24-46 " + m_sql.m_windsign;
							szLegendLabels[6] = "&gt; 46" + m_sql.m_windsign;
						}
						else if (m_sql.m_windunit == WINDUNIT_Knots)
						{
							szLegendLabels[0] = "&lt; 3 " + m_sql.m_windsign;
							szLegendLabels[1] = "3-7 " + m_sql.m_windsign;
							szLegendLabels[2] = "7-17 " + m_sql.m_windsign;
							szLegendLabels[3] = "17-27 " + m_sql.m_windsign;
							szLegendLabels[4] = "27-34 " + m_sql.m_windsign;
							szLegendLabels[5] = "34-41 " + m_sql.m_windsign;
							szLegendLabels[6] = "&gt; 41" + m_sql.m_windsign;
						}
						else if (m_sql.m_windunit == WINDUNIT_Beaufort)
						{
							szLegendLabels[0] = "&lt; 2 " + m_sql.m_windsign;
							szLegendLabels[1] = "2-4 " + m_sql.m_windsign;
							szLegendLabels[2] = "4-6 " + m_sql.m_windsign;
							szLegendLabels[3] = "6-8 " + m_sql.m_windsign;
							szLegendLabels[4] = "8-10 " + m_sql.m_windsign;
							szLegendLabels[5] = "10-12 " + m_sql.m_windsign;
							szLegendLabels[6] = "&gt; 12" + m_sql.m_windsign;
						}
						else
						{
							// Todo !
							szLegendLabels[0] = "&lt; 0.5 " + m_sql.m_windsign;
							szLegendLabels[1] = "0.5-2 " + m_sql.m_windsign;
							szLegendLabels[2] = "2-4 " + m_sql.m_windsign;
							szLegendLabels[3] = "4-6 " + m_sql.m_windsign;
							szLegendLabels[4] = "6-8 " + m_sql.m_windsign;
							szLegendLabels[5] = "8-10 " + m_sql.m_windsign;
							szLegendLabels[6] = "&gt; 10" + m_sql.m_windsign;
						}

						for (const auto &sd : result)
						{
							float fdirection = static_cast<float>(atof(sd[0].c_str()));
							if (fdirection >= 360)
								fdirection = 0;
							int direction = int(fdirection);
							float speedOrg = static_cast<float>(atof(sd[1].c_str()));
							float gustOrg = static_cast<float>(atof(sd[2].c_str()));
							if ((gustOrg == 0) && (speedOrg != 0))
								gustOrg = speedOrg;
							if (gustOrg == 0)
								continue; // no direction if wind is still
							// float speed = speedOrg * m_sql.m_windscale;
							float gust = gustOrg * m_sql.m_windscale;
							int bucket = int(fdirection / 22.5F);

							int speedpos = 0;

							if (m_sql.m_windunit == WINDUNIT_MS)
							{
								if (gust < 0.5F)
									speedpos = 0;
								else if (gust < 2.0F)
									speedpos = 1;
								else if (gust < 4.0F)
									speedpos = 2;
								else if (gust < 6.0F)
									speedpos = 3;
								else if (gust < 8.0F)
									speedpos = 4;
								else if (gust < 10.0F)
									speedpos = 5;
								else
									speedpos = 6;
							}
							else if (m_sql.m_windunit == WINDUNIT_KMH)
							{
								if (gust < 2.0F)
									speedpos = 0;
								else if (gust < 4.0F)
									speedpos = 1;
								else if (gust < 6.0F)
									speedpos = 2;
								else if (gust < 10.0F)
									speedpos = 3;
								else if (gust < 20.0F)
									speedpos = 4;
								else if (gust < 36.0F)
									speedpos = 5;
								else
									speedpos = 6;
							}
							else if (m_sql.m_windunit == WINDUNIT_MPH)
							{
								if (gust < 3.0F)
									speedpos = 0;
								else if (gust < 7.0F)
									speedpos = 1;
								else if (gust < 12.0F)
									speedpos = 2;
								else if (gust < 18.0F)
									speedpos = 3;
								else if (gust < 24.0F)
									speedpos = 4;
								else if (gust < 46.0F)
									speedpos = 5;
								else
									speedpos = 6;
							}
							else if (m_sql.m_windunit == WINDUNIT_Knots)
							{
								if (gust < 3.0F)
									speedpos = 0;
								else if (gust < 7.0F)
									speedpos = 1;
								else if (gust < 17.0F)
									speedpos = 2;
								else if (gust < 27.0F)
									speedpos = 3;
								else if (gust < 34.0F)
									speedpos = 4;
								else if (gust < 41.0F)
									speedpos = 5;
								else
									speedpos = 6;
							}
							else if (m_sql.m_windunit == WINDUNIT_Beaufort)
							{
								float gustms = gustOrg * 0.1F;
								int iBeaufort = MStoBeaufort(gustms);
								if (iBeaufort < 2)
									speedpos = 0;
								else if (iBeaufort < 4)
									speedpos = 1;
								else if (iBeaufort < 6)
									speedpos = 2;
								else if (iBeaufort < 8)
									speedpos = 3;
								else if (iBeaufort < 10)
									speedpos = 4;
								else if (iBeaufort < 12)
									speedpos = 5;
								else
									speedpos = 6;
							}
							else
							{
								// Still todo !
								if (gust < 0.5F)
									speedpos = 0;
								else if (gust < 2.0F)
									speedpos = 1;
								else if (gust < 4.0F)
									speedpos = 2;
								else if (gust < 6.0F)
									speedpos = 3;
								else if (gust < 8.0F)
									speedpos = 4;
								else if (gust < 10.0F)
									speedpos = 5;
								else
									speedpos = 6;
							}
							wdirtabletemp[bucket][speedpos]++;
							_directions[direction]++;
							totalvalues++;
						}

						for (int jj = 0; jj < 7; jj++)
						{
							root["result_speed"][jj]["label"] = szLegendLabels[jj];

							for (ii = 0; ii < 16; ii++)
							{
								float svalue = 0;
								if (totalvalues > 0)
								{
									svalue = (100.0F / totalvalues) * wdirtabletemp[ii][jj];
								}
								sprintf(szTmp, "%.2f", svalue);
								root["result_speed"][jj]["sp"][ii] = szTmp;
							}
						}
						ii = 0;
						for (idir = 0; idir < 360 + 1; idir++)
						{
							if (_directions[idir] != 0)
							{
								root["result"][ii]["dig"] = idir;
								float percentage = 0;
								if (totalvalues > 0)
								{
									percentage = (float(100.0 / float(totalvalues)) * float(_directions[idir]));
								}
								sprintf(szTmp, "%.2f", percentage);
								root["result"][ii]["div"] = szTmp;
								ii++;
							}
						}
					}
				}

			} // day
			else if (srange == "week")
			{
				if (sensor == "rain")
				{
					root["status"] = "OK";
					root["title"] = "Graph " + sensor + " " + srange;

					char szDateStart[40];
					char szDateEnd[40];
					sprintf(szDateEnd, "%04d-%02d-%02d", tm1.tm_year + 1900, tm1.tm_mon + 1, tm1.tm_mday);

					// Subtract one week
					time_t weekbefore;
					struct tm tm2;
					getNoon(weekbefore, tm2, tm1.tm_year + 1900, tm1.tm_mon + 1, tm1.tm_mday - 7); // We only want the date
					sprintf(szDateStart, "%04d-%02d-%02d", tm2.tm_year + 1900, tm2.tm_mon + 1, tm2.tm_mday);

					result = m_sql.safe_query("SELECT Total, Rate, Date FROM %s WHERE (DeviceRowID==%" PRIu64 " AND Date>='%q' AND Date<='%q') ORDER BY Date ASC",
								  dbasetable.c_str(), idx, szDateStart, szDateEnd);
					int ii = 0;
					if (!result.empty())
					{
						for (const auto &sd : result)
						{
							root["result"][ii]["d"] = sd[2].substr(0, 16);
							double mmval = atof(sd[0].c_str());
							mmval *= AddjMulti;
							sprintf(szTmp, "%.1f", mmval);
							root["result"][ii]["mm"] = szTmp;
							ii++;
						}
					}
					// add today (have to calculate it)
					if (dSubType == sTypeRAINWU || dSubType == sTypeRAINByRate)
					{
						result = m_sql.safe_query("SELECT Total, Total, Rate FROM Rain WHERE (DeviceRowID=%" PRIu64 " AND Date>='%q') ORDER BY ROWID DESC LIMIT 1", idx,
									  szDateEnd);
					}
					else
					{
						result = m_sql.safe_query("SELECT MIN(Total), MAX(Total), MAX(Rate) FROM Rain WHERE (DeviceRowID=%" PRIu64 " AND Date>='%q')", idx, szDateEnd);
					}
					if (!result.empty())
					{
						std::vector<std::string> sd = result[0];

						float total_min = static_cast<float>(atof(sd[0].c_str()));
						float total_max = static_cast<float>(atof(sd[1].c_str()));
						// int rate = atoi(sd[2].c_str());

						double total_real = 0;
						if (dSubType == sTypeRAINWU || dSubType == sTypeRAINByRate)
						{
							total_real = total_max;
						}
						else
						{
							total_real = total_max - total_min;
						}
						total_real *= AddjMulti;
						sprintf(szTmp, "%.1f", total_real);
						root["result"][ii]["d"] = szDateEnd;
						root["result"][ii]["mm"] = szTmp;
						ii++;
					}
				}
				else if (sensor == "counter")
				{
					root["status"] = "OK";
					root["title"] = "Graph " + sensor + " " + srange;
					root["ValueQuantity"] = options["ValueQuantity"];
					root["ValueUnits"] = options["ValueUnits"];

					char szDateStart[40];
					char szDateEnd[40];
					sprintf(szDateEnd, "%04d-%02d-%02d", tm1.tm_year + 1900, tm1.tm_mon + 1, tm1.tm_mday);

					// Subtract one week
					time_t weekbefore;
					struct tm tm2;
					getNoon(weekbefore, tm2, tm1.tm_year + 1900, tm1.tm_mon + 1, tm1.tm_mday - 7); // We only want the date
					sprintf(szDateStart, "%04d-%02d-%02d", tm2.tm_year + 1900, tm2.tm_mon + 1, tm2.tm_mday);

					int ii = 0;
					if (dType == pTypeP1Power)
					{
						result = m_sql.safe_query("SELECT Value1,Value2,Value5,Value6,Date FROM %s WHERE (DeviceRowID==%" PRIu64
									  " AND Date>='%q' AND Date<='%q') ORDER BY Date ASC",
									  dbasetable.c_str(), idx, szDateStart, szDateEnd);
						if (!result.empty())
						{
							bool bHaveDeliverd = false;
							for (const auto &sd : result)
							{
								root["result"][ii]["d"] = sd[4].substr(0, 16);
								std::string szValueUsage1 = sd[0];
								std::string szValueDeliv1 = sd[1];
								std::string szValueUsage2 = sd[2];
								std::string szValueDeliv2 = sd[3];

								float fUsage1 = (float)(atof(szValueUsage1.c_str()));
								float fUsage2 = (float)(atof(szValueUsage2.c_str()));
								float fDeliv1 = (float)(atof(szValueDeliv1.c_str()));
								float fDeliv2 = (float)(atof(szValueDeliv2.c_str()));

								fDeliv1 = (fDeliv1 < 10) ? 0 : fDeliv1;
								fDeliv2 = (fDeliv2 < 10) ? 0 : fDeliv2;

								if ((fDeliv1 != 0) || (fDeliv2 != 0))
									bHaveDeliverd = true;
								sprintf(szTmp, "%.3f", fUsage1 / divider);
								root["result"][ii]["v"] = szTmp;
								sprintf(szTmp, "%.3f", fUsage2 / divider);
								root["result"][ii]["v2"] = szTmp;
								sprintf(szTmp, "%.3f", fDeliv1 / divider);
								root["result"][ii]["r1"] = szTmp;
								sprintf(szTmp, "%.3f", fDeliv2 / divider);
								root["result"][ii]["r2"] = szTmp;
								ii++;
							}
							if (bHaveDeliverd)
							{
								root["delivered"] = true;
							}
						}
					}
					else
					{
						result = m_sql.safe_query("SELECT Value, Date FROM %s WHERE (DeviceRowID==%" PRIu64 " AND Date>='%q' AND Date<='%q') ORDER BY Date ASC",
									  dbasetable.c_str(), idx, szDateStart, szDateEnd);
						if (!result.empty())
						{
							for (const auto &sd : result)
							{
								root["result"][ii]["d"] = sd[1].substr(0, 16);
								std::string szValue = sd[0];
								switch (metertype)
								{
									case MTYPE_ENERGY:
									case MTYPE_ENERGY_GENERATED:
										sprintf(szTmp, "%.3f", atof(szValue.c_str()) / divider);
										szValue = szTmp;
										break;
									case MTYPE_GAS:
										sprintf(szTmp, "%.3f", atof(szValue.c_str()) / divider);
										szValue = szTmp;
										break;
									case MTYPE_WATER:
										sprintf(szTmp, "%.3f", atof(szValue.c_str()) / divider);
										szValue = szTmp;
										break;
									case MTYPE_COUNTER:
										sprintf(szTmp, "%g", atof(szValue.c_str()) / divider);
										szValue = szTmp;
										break;
									default:
										szValue = "0";
										break;
								}
								root["result"][ii]["v"] = szValue;
								ii++;
							}
						}
					}
					// add today (have to calculate it)
					if (dType == pTypeP1Power)
					{
						result = m_sql.safe_query("SELECT MIN(Value1), MAX(Value1), MIN(Value2), MAX(Value2),MIN(Value5), MAX(Value5), MIN(Value6), MAX(Value6) FROM "
									  "MultiMeter WHERE (DeviceRowID==%" PRIu64 " AND Date>='%q')",
									  idx, szDateEnd);
						if (!result.empty())
						{
							std::vector<std::string> sd = result[0];

							unsigned long long total_min_usage_1 = std::strtoull(sd[0].c_str(), nullptr, 10);
							unsigned long long total_max_usage_1 = std::strtoull(sd[1].c_str(), nullptr, 10);
							unsigned long long total_min_usage_2 = std::strtoull(sd[4].c_str(), nullptr, 10);
							unsigned long long total_max_usage_2 = std::strtoull(sd[5].c_str(), nullptr, 10);
							unsigned long long total_real_usage_1, total_real_usage_2;
							unsigned long long total_min_deliv_1 = std::strtoull(sd[2].c_str(), nullptr, 10);
							unsigned long long total_max_deliv_1 = std::strtoull(sd[3].c_str(), nullptr, 10);
							unsigned long long total_min_deliv_2 = std::strtoull(sd[6].c_str(), nullptr, 10);
							unsigned long long total_max_deliv_2 = std::strtoull(sd[7].c_str(), nullptr, 10);
							unsigned long long total_real_deliv_1, total_real_deliv_2;

							bool bHaveDeliverd = false;

							total_real_usage_1 = total_max_usage_1 - total_min_usage_1;
							total_real_usage_2 = total_max_usage_2 - total_min_usage_2;

							total_real_deliv_1 = total_max_deliv_1 - total_min_deliv_1;
							total_real_deliv_2 = total_max_deliv_2 - total_min_deliv_2;
							if ((total_real_deliv_1 != 0) || (total_real_deliv_2 != 0))
								bHaveDeliverd = true;

							root["result"][ii]["d"] = szDateEnd;

							sprintf(szTmp, "%llu", total_real_usage_1);
							std::string szValue = szTmp;
							sprintf(szTmp, "%.3f", atof(szValue.c_str()) / divider);
							root["result"][ii]["v"] = szTmp;
							sprintf(szTmp, "%llu", total_real_usage_2);
							szValue = szTmp;
							sprintf(szTmp, "%.3f", atof(szValue.c_str()) / divider);
							root["result"][ii]["v2"] = szTmp;

							sprintf(szTmp, "%llu", total_real_deliv_1);
							szValue = szTmp;
							sprintf(szTmp, "%.3f", atof(szValue.c_str()) / divider);
							root["result"][ii]["r1"] = szTmp;
							sprintf(szTmp, "%llu", total_real_deliv_2);
							szValue = szTmp;
							sprintf(szTmp, "%.3f", atof(szValue.c_str()) / divider);
							root["result"][ii]["r2"] = szTmp;

							ii++;
							if (bHaveDeliverd)
							{
								root["delivered"] = true;
							}
						}
					}
					else if (!bIsManagedCounter)
					{
						// get the first value of the day
						result = m_sql.safe_query("SELECT Value FROM Meter WHERE (DeviceRowID==%" PRIu64 " AND Date>='%q') ORDER BY Date ASC LIMIT 1", idx, szDateEnd);
						if (!result.empty())
						{
							std::vector<std::string> sd = result[0];

							unsigned long long total_min = std::strtoull(sd[0].c_str(), nullptr, 10);
							unsigned long long total_max = total_min;
							unsigned long long total_real;

							// get the last value of the day
							result = m_sql.safe_query("SELECT Value FROM Meter WHERE (DeviceRowID==%" PRIu64 " AND Date>='%q') ORDER BY Date DESC LIMIT 1", idx, szDateEnd);
							if (!result.empty())
							{
								std::vector<std::string> sd = result[0];
								total_max = std::strtoull(sd[0].c_str(), nullptr, 10);
							}

							total_real = total_max - total_min;
							sprintf(szTmp, "%lld", total_real);
							std::string szValue = szTmp;
							switch (metertype)
							{
								case MTYPE_ENERGY:
								case MTYPE_ENERGY_GENERATED:
									sprintf(szTmp, "%.3f", atof(szValue.c_str()) / divider);
									szValue = szTmp;
									break;
								case MTYPE_GAS:
									sprintf(szTmp, "%.3f", atof(szValue.c_str()) / divider);
									szValue = szTmp;
									break;
								case MTYPE_WATER:
									sprintf(szTmp, "%.3f", atof(szValue.c_str()) / divider);
									szValue = szTmp;
									break;
								case MTYPE_COUNTER:
									sprintf(szTmp, "%g", atof(szValue.c_str()) / divider);
									szValue = szTmp;
									break;
								default:
									szValue = "0";
									break;
							}

							root["result"][ii]["d"] = szDateEnd;
							root["result"][ii]["v"] = szValue;
							ii++;
						}
					}
				}
			} // week
			else if (srange == "month" || srange == "year" || !sgroupby.empty())
			{
				char szDateStart[40];
				char szDateEnd[40];
				char szDateStartPrev[40];
				char szDateEndPrev[40];

				std::string sactmonth = request::findValue(&req, "actmonth");
				std::string sactyear = request::findValue(&req, "actyear");

				int actMonth = atoi(sactmonth.c_str());
				int actYear = atoi(sactyear.c_str());

				if ((!sactmonth.empty()) && (!sactyear.empty()))
				{
					sprintf(szDateStart, "%04d-%02d-%02d", actYear, actMonth, 1);
					sprintf(szDateStartPrev, "%04d-%02d-%02d", actYear - 1, actMonth, 1);
					actMonth++;
					if (actMonth == 13)
					{
						actMonth = 1;
						actYear++;
					}
					sprintf(szDateEnd, "%04d-%02d-%02d", actYear, actMonth, 1);
					sprintf(szDateEndPrev, "%04d-%02d-%02d", actYear - 1, actMonth, 1);
				}
				else if (!sactyear.empty())
				{
					sprintf(szDateStart, "%04d-%02d-%02d", actYear, 1, 1);
					sprintf(szDateStartPrev, "%04d-%02d-%02d", actYear - 1, 1, 1);
					actYear++;
					sprintf(szDateEnd, "%04d-%02d-%02d", actYear, 1, 1);
					sprintf(szDateEndPrev, "%04d-%02d-%02d", actYear - 1, 1, 1);
				}
				else
				{
					sprintf(szDateEnd, "%04d-%02d-%02d", tm1.tm_year + 1900, tm1.tm_mon + 1, tm1.tm_mday);
					sprintf(szDateEndPrev, "%04d-%02d-%02d", tm1.tm_year + 1900 - 1, tm1.tm_mon + 1, tm1.tm_mday);

					struct tm tm2;
					if (srange == "month")
					{
						// Subtract one month
						time_t monthbefore;
						getNoon(monthbefore, tm2, tm1.tm_year + 1900, tm1.tm_mon, tm1.tm_mday);
					}
					else
					{
						// Subtract one year
						time_t yearbefore;
						getNoon(yearbefore, tm2, tm1.tm_year + 1900 - 1, tm1.tm_mon + 1, tm1.tm_mday);
					}

					sprintf(szDateStart, "%04d-%02d-%02d", tm2.tm_year + 1900, tm2.tm_mon + 1, tm2.tm_mday);
					sprintf(szDateStartPrev, "%04d-%02d-%02d", tm2.tm_year + 1900 - 1, tm2.tm_mon + 1, tm2.tm_mday);
				}

				if (sensor == "temp")
				{
					root["status"] = "OK";
					root["title"] = "Graph " + sensor + " " + srange;

					// Actual Year
					result = m_sql.safe_query("SELECT Temp_Min, Temp_Max, Chill_Min, Chill_Max,"
								  " Humidity, Barometer, Temp_Avg, Date, SetPoint_Min,"
								  " SetPoint_Max, SetPoint_Avg "
								  "FROM %s WHERE (DeviceRowID==%" PRIu64 " AND Date>='%q'"
								  " AND Date<='%q') ORDER BY Date ASC",
								  dbasetable.c_str(), idx, szDateStart, szDateEnd);
					int ii = 0;
					if (!result.empty())
					{
						for (const auto &sd : result)
						{
							root["result"][ii]["d"] = sd[7].substr(0, 16);

							if ((dType == pTypeRego6XXTemp) || (dType == pTypeTEMP) || (dType == pTypeTEMP_HUM) || (dType == pTypeTEMP_HUM_BARO) ||
							    (dType == pTypeTEMP_BARO) || (dType == pTypeWIND) || (dType == pTypeThermostat1) || (dType == pTypeRadiator1) ||
							    ((dType == pTypeRFXSensor) && (dSubType == sTypeRFXSensorTemp)) || ((dType == pTypeUV) && (dSubType == sTypeUV3)) ||
							    ((dType == pTypeGeneral) && (dSubType == sTypeSystemTemp)) || ((dType == pTypeThermostat) && (dSubType == sTypeThermSetpoint)) ||
							    (dType == pTypeEvohomeZone) || (dType == pTypeEvohomeWater) || ((dType == pTypeGeneral) && (dSubType == sTypeBaro)))
							{
								bool bOK = true;
								if (dType == pTypeWIND)
								{
									bOK = ((dSubType != sTypeWINDNoTemp) && (dSubType != sTypeWINDNoTempNoChill));
								}
								if (bOK)
								{
									double te = ConvertTemperature(atof(sd[1].c_str()), tempsign);
									double tm = ConvertTemperature(atof(sd[0].c_str()), tempsign);
									double ta = ConvertTemperature(atof(sd[6].c_str()), tempsign);
									root["result"][ii]["te"] = te;
									root["result"][ii]["tm"] = tm;
									root["result"][ii]["ta"] = ta;
								}
							}
							if (((dType == pTypeWIND) && (dSubType == sTypeWIND4)) || ((dType == pTypeWIND) && (dSubType == sTypeWINDNoTemp)))
							{
								double ch = ConvertTemperature(atof(sd[3].c_str()), tempsign);
								double cm = ConvertTemperature(atof(sd[2].c_str()), tempsign);
								root["result"][ii]["ch"] = ch;
								root["result"][ii]["cm"] = cm;
							}
							if ((dType == pTypeHUM) || (dType == pTypeTEMP_HUM) || (dType == pTypeTEMP_HUM_BARO))
							{
								root["result"][ii]["hu"] = sd[4];
							}
							if ((dType == pTypeTEMP_HUM_BARO) || (dType == pTypeTEMP_BARO) || ((dType == pTypeGeneral) && (dSubType == sTypeBaro)))
							{
								if (dType == pTypeTEMP_HUM_BARO)
								{
									if (dSubType == sTypeTHBFloat)
									{
										sprintf(szTmp, "%.1f", atof(sd[5].c_str()) / 10.0F);
										root["result"][ii]["ba"] = szTmp;
									}
									else
										root["result"][ii]["ba"] = sd[5];
								}
								else if (dType == pTypeTEMP_BARO)
								{
									sprintf(szTmp, "%.1f", atof(sd[5].c_str()) / 10.0F);
									root["result"][ii]["ba"] = szTmp;
								}
								else if ((dType == pTypeGeneral) && (dSubType == sTypeBaro))
								{
									sprintf(szTmp, "%.1f", atof(sd[5].c_str()) / 10.0F);
									root["result"][ii]["ba"] = szTmp;
								}
							}
							if ((dType == pTypeEvohomeZone) || (dType == pTypeEvohomeWater))
							{
								double sm = ConvertTemperature(atof(sd[8].c_str()), tempsign);
								double sx = ConvertTemperature(atof(sd[9].c_str()), tempsign);
								double se = ConvertTemperature(atof(sd[10].c_str()), tempsign);
								root["result"][ii]["sm"] = sm;
								root["result"][ii]["se"] = se;
								root["result"][ii]["sx"] = sx;
							}
							ii++;
						}
					}
					// add today (have to calculate it)
					result = m_sql.safe_query("SELECT MIN(Temperature), MAX(Temperature),"
								  " MIN(Chill), MAX(Chill), AVG(Humidity),"
								  " AVG(Barometer), AVG(Temperature), MIN(SetPoint),"
								  " MAX(SetPoint), AVG(SetPoint) "
								  "FROM Temperature WHERE (DeviceRowID==%" PRIu64 ""
								  " AND Date>='%q')",
								  idx, szDateEnd);
					if (!result.empty())
					{
						std::vector<std::string> sd = result[0];

						root["result"][ii]["d"] = szDateEnd;
						if (((dType == pTypeRego6XXTemp) || (dType == pTypeTEMP) || (dType == pTypeTEMP_HUM) || (dType == pTypeTEMP_HUM_BARO) || (dType == pTypeTEMP_BARO) ||
						     (dType == pTypeWIND) || (dType == pTypeThermostat1) || (dType == pTypeRadiator1)) ||
						    ((dType == pTypeUV) && (dSubType == sTypeUV3)) || ((dType == pTypeWIND) && (dSubType == sTypeWIND4)) || (dType == pTypeEvohomeZone) ||
						    (dType == pTypeEvohomeWater))
						{
							double te = ConvertTemperature(atof(sd[1].c_str()), tempsign);
							double tm = ConvertTemperature(atof(sd[0].c_str()), tempsign);
							double ta = ConvertTemperature(atof(sd[6].c_str()), tempsign);

							root["result"][ii]["te"] = te;
							root["result"][ii]["tm"] = tm;
							root["result"][ii]["ta"] = ta;
						}
						if (((dType == pTypeWIND) && (dSubType == sTypeWIND4)) || ((dType == pTypeWIND) && (dSubType == sTypeWINDNoTemp)))
						{
							double ch = ConvertTemperature(atof(sd[3].c_str()), tempsign);
							double cm = ConvertTemperature(atof(sd[2].c_str()), tempsign);
							root["result"][ii]["ch"] = ch;
							root["result"][ii]["cm"] = cm;
						}
						if ((dType == pTypeHUM) || (dType == pTypeTEMP_HUM) || (dType == pTypeTEMP_HUM_BARO))
						{
							root["result"][ii]["hu"] = sd[4];
						}
						if ((dType == pTypeTEMP_HUM_BARO) || (dType == pTypeTEMP_BARO) || ((dType == pTypeGeneral) && (dSubType == sTypeBaro)))
						{
							if (dType == pTypeTEMP_HUM_BARO)
							{
								if (dSubType == sTypeTHBFloat)
								{
									sprintf(szTmp, "%.1f", atof(sd[5].c_str()) / 10.0F);
									root["result"][ii]["ba"] = szTmp;
								}
								else
									root["result"][ii]["ba"] = sd[5];
							}
							else if (dType == pTypeTEMP_BARO)
							{
								sprintf(szTmp, "%.1f", atof(sd[5].c_str()) / 10.0F);
								root["result"][ii]["ba"] = szTmp;
							}
							else if ((dType == pTypeGeneral) && (dSubType == sTypeBaro))
							{
								sprintf(szTmp, "%.1f", atof(sd[5].c_str()) / 10.0F);
								root["result"][ii]["ba"] = szTmp;
							}
						}
						if ((dType == pTypeEvohomeZone) || (dType == pTypeEvohomeWater))
						{
							double sx = ConvertTemperature(atof(sd[8].c_str()), tempsign);
							double sm = ConvertTemperature(atof(sd[7].c_str()), tempsign);
							double se = ConvertTemperature(atof(sd[9].c_str()), tempsign);
							root["result"][ii]["se"] = se;
							root["result"][ii]["sm"] = sm;
							root["result"][ii]["sx"] = sx;
						}
						ii++;
					}
					// Previous Year
					result = m_sql.safe_query("SELECT Temp_Min, Temp_Max, Chill_Min, Chill_Max,"
								  " Humidity, Barometer, Temp_Avg, Date, SetPoint_Min,"
								  " SetPoint_Max, SetPoint_Avg "
								  "FROM %s WHERE (DeviceRowID==%" PRIu64 " AND Date>='%q'"
								  " AND Date<='%q') ORDER BY Date ASC",
								  dbasetable.c_str(), idx, szDateStartPrev, szDateEndPrev);
					if (!result.empty())
					{
						iPrev = 0;
						for (const auto &sd : result)
						{
							root["resultprev"][iPrev]["d"] = sd[7].substr(0, 16);

							if ((dType == pTypeRego6XXTemp) || (dType == pTypeTEMP) || (dType == pTypeTEMP_HUM) || (dType == pTypeTEMP_HUM_BARO) ||
							    (dType == pTypeTEMP_BARO) || (dType == pTypeWIND) || (dType == pTypeThermostat1) || (dType == pTypeRadiator1) ||
							    ((dType == pTypeRFXSensor) && (dSubType == sTypeRFXSensorTemp)) || ((dType == pTypeUV) && (dSubType == sTypeUV3)) ||
							    ((dType == pTypeGeneral) && (dSubType == sTypeSystemTemp)) || ((dType == pTypeThermostat) && (dSubType == sTypeThermSetpoint)) ||
							    (dType == pTypeEvohomeZone) || (dType == pTypeEvohomeWater))
							{
								bool bOK = true;
								if (dType == pTypeWIND)
								{
									bOK = ((dSubType == sTypeWIND4) || (dSubType == sTypeWINDNoTemp));
								}
								if (bOK)
								{
									double te = ConvertTemperature(atof(sd[1].c_str()), tempsign);
									double tm = ConvertTemperature(atof(sd[0].c_str()), tempsign);
									double ta = ConvertTemperature(atof(sd[6].c_str()), tempsign);
									root["resultprev"][iPrev]["te"] = te;
									root["resultprev"][iPrev]["tm"] = tm;
									root["resultprev"][iPrev]["ta"] = ta;
								}
							}
							if (((dType == pTypeWIND) && (dSubType == sTypeWIND4)) || ((dType == pTypeWIND) && (dSubType == sTypeWINDNoTemp)))
							{
								double ch = ConvertTemperature(atof(sd[3].c_str()), tempsign);
								double cm = ConvertTemperature(atof(sd[2].c_str()), tempsign);
								root["resultprev"][iPrev]["ch"] = ch;
								root["resultprev"][iPrev]["cm"] = cm;
							}
							if ((dType == pTypeHUM) || (dType == pTypeTEMP_HUM) || (dType == pTypeTEMP_HUM_BARO))
							{
								root["resultprev"][iPrev]["hu"] = sd[4];
							}
							if ((dType == pTypeTEMP_HUM_BARO) || (dType == pTypeTEMP_BARO) || ((dType == pTypeGeneral) && (dSubType == sTypeBaro)))
							{
								if (dType == pTypeTEMP_HUM_BARO)
								{
									if (dSubType == sTypeTHBFloat)
									{
										sprintf(szTmp, "%.1f", atof(sd[5].c_str()) / 10.0F);
										root["resultprev"][iPrev]["ba"] = szTmp;
									}
									else
										root["resultprev"][iPrev]["ba"] = sd[5];
								}
								else if (dType == pTypeTEMP_BARO)
								{
									sprintf(szTmp, "%.1f", atof(sd[5].c_str()) / 10.0F);
									root["resultprev"][iPrev]["ba"] = szTmp;
								}
								else if ((dType == pTypeGeneral) && (dSubType == sTypeBaro))
								{
									sprintf(szTmp, "%.1f", atof(sd[5].c_str()) / 10.0F);
									root["resultprev"][iPrev]["ba"] = szTmp;
								}
							}
							if ((dType == pTypeEvohomeZone) || (dType == pTypeEvohomeWater))
							{
								double sx = ConvertTemperature(atof(sd[8].c_str()), tempsign);
								double sm = ConvertTemperature(atof(sd[7].c_str()), tempsign);
								double se = ConvertTemperature(atof(sd[9].c_str()), tempsign);
								root["resultprev"][iPrev]["se"] = se;
								root["resultprev"][iPrev]["sm"] = sm;
								root["resultprev"][iPrev]["sx"] = sx;
							}
							iPrev++;
						}
					}
				}
				else if (sensor == "Percentage")
				{
					root["status"] = "OK";
					root["title"] = "Graph " + sensor + " " + srange;

					result = m_sql.safe_query("SELECT Percentage_Min, Percentage_Max, Percentage_Avg, Date FROM %s WHERE (DeviceRowID==%" PRIu64
								  " AND Date>='%q' AND Date<='%q') ORDER BY Date ASC",
								  dbasetable.c_str(), idx, szDateStart, szDateEnd);
					int ii = 0;
					if (!result.empty())
					{
						for (const auto &sd : result)
						{
							root["result"][ii]["d"] = sd[3].substr(0, 16);
							root["result"][ii]["v_min"] = sd[0];
							root["result"][ii]["v_max"] = sd[1];
							root["result"][ii]["v_avg"] = sd[2];
							ii++;
						}
					}
					// add today (have to calculate it)
					result = m_sql.safe_query("SELECT MIN(Percentage), MAX(Percentage), AVG(Percentage) FROM Percentage WHERE (DeviceRowID=%" PRIu64 " AND Date>='%q')", idx,
								  szDateEnd);
					if (!result.empty())
					{
						std::vector<std::string> sd = result[0];
						root["result"][ii]["d"] = szDateEnd;
						root["result"][ii]["v_min"] = sd[0];
						root["result"][ii]["v_max"] = sd[1];
						root["result"][ii]["v_avg"] = sd[2];
						ii++;
					}
				}
				else if (sensor == "fan")
				{
					root["status"] = "OK";
					root["title"] = "Graph " + sensor + " " + srange;

					result = m_sql.safe_query("SELECT Speed_Min, Speed_Max, Date FROM %s WHERE (DeviceRowID==%" PRIu64 " AND Date>='%q' AND Date<='%q') ORDER BY Date ASC",
								  dbasetable.c_str(), idx, szDateStart, szDateEnd);
					int ii = 0;
					if (!result.empty())
					{
						for (const auto &sd : result)
						{
							root["result"][ii]["d"] = sd[2].substr(0, 16);
							root["result"][ii]["v_max"] = sd[1];
							root["result"][ii]["v_min"] = sd[0];
							ii++;
						}
					}
					// add today (have to calculate it)
					result = m_sql.safe_query("SELECT MIN(Speed), MAX(Speed) FROM Fan WHERE (DeviceRowID=%" PRIu64 " AND Date>='%q')", idx, szDateEnd);
					if (!result.empty())
					{
						std::vector<std::string> sd = result[0];
						root["result"][ii]["d"] = szDateEnd;
						root["result"][ii]["v_max"] = sd[1];
						root["result"][ii]["v_min"] = sd[0];
						ii++;
					}
				}
				else if (sensor == "uv")
				{
					root["status"] = "OK";
					root["title"] = "Graph " + sensor + " " + srange;

					result = m_sql.safe_query("SELECT Level, Date FROM %s WHERE (DeviceRowID==%" PRIu64 " AND Date>='%q' AND Date<='%q') ORDER BY Date ASC", dbasetable.c_str(),
								  idx, szDateStart, szDateEnd);
					int ii = 0;
					if (!result.empty())
					{
						for (const auto &sd : result)
						{
							root["result"][ii]["d"] = sd[1].substr(0, 16);
							root["result"][ii]["uvi"] = sd[0];
							ii++;
						}
					}
					// add today (have to calculate it)
					result = m_sql.safe_query("SELECT MAX(Level) FROM UV WHERE (DeviceRowID=%" PRIu64 " AND Date>='%q')", idx, szDateEnd);
					if (!result.empty())
					{
						std::vector<std::string> sd = result[0];

						root["result"][ii]["d"] = szDateEnd;
						root["result"][ii]["uvi"] = sd[0];
						ii++;
					}
					// Previous Year
					result = m_sql.safe_query("SELECT Level, Date FROM %s WHERE (DeviceRowID==%" PRIu64 " AND Date>='%q' AND Date<='%q') ORDER BY Date ASC", dbasetable.c_str(),
								  idx, szDateStartPrev, szDateEndPrev);
					if (!result.empty())
					{
						iPrev = 0;
						for (const auto &sd : result)
						{
							root["resultprev"][iPrev]["d"] = sd[1].substr(0, 16);
							root["resultprev"][iPrev]["uvi"] = sd[0];
							iPrev++;
						}
					}
				}
				else if (sensor == "rain")
				{
					root["status"] = "OK";
					root["title"] = "Graph " + sensor + " " + srange;

					result = m_sql.safe_query("SELECT Total, Rate, Date FROM %s WHERE (DeviceRowID==%" PRIu64 " AND Date>='%q' AND Date<='%q') ORDER BY Date ASC",
								  dbasetable.c_str(), idx, szDateStart, szDateEnd);
					int ii = 0;
					if (!result.empty())
					{
						for (const auto &sd : result)
						{
							root["result"][ii]["d"] = sd[2].substr(0, 16);
							double mmval = atof(sd[0].c_str());
							mmval *= AddjMulti;
							sprintf(szTmp, "%.1f", mmval);
							root["result"][ii]["mm"] = szTmp;
							ii++;
						}
					}
					// add today (have to calculate it)
					if (dSubType == sTypeRAINWU || dSubType == sTypeRAINByRate)
					{
						result = m_sql.safe_query("SELECT Total, Total, Rate FROM Rain WHERE (DeviceRowID=%" PRIu64 " AND Date>='%q') ORDER BY ROWID DESC LIMIT 1", idx,
									  szDateEnd);
					}
					else
					{
						result = m_sql.safe_query("SELECT MIN(Total), MAX(Total), MAX(Rate) FROM Rain WHERE (DeviceRowID=%" PRIu64 " AND Date>='%q')", idx, szDateEnd);
					}
					if (!result.empty())
					{
						std::vector<std::string> sd = result[0];

						float total_min = static_cast<float>(atof(sd[0].c_str()));
						float total_max = static_cast<float>(atof(sd[1].c_str()));
						// int rate = atoi(sd[2].c_str());

						double total_real = 0;
						if (dSubType == sTypeRAINWU || dSubType == sTypeRAINByRate)
						{
							total_real = total_max;
						}
						else
						{
							total_real = total_max - total_min;
						}
						total_real *= AddjMulti;
						sprintf(szTmp, "%.1f", total_real);
						root["result"][ii]["d"] = szDateEnd;
						root["result"][ii]["mm"] = szTmp;
						ii++;
					}
					// Previous Year
					result = m_sql.safe_query("SELECT Total, Rate, Date FROM %s WHERE (DeviceRowID==%" PRIu64 " AND Date>='%q' AND Date<='%q') ORDER BY Date ASC",
								  dbasetable.c_str(), idx, szDateStartPrev, szDateEndPrev);
					if (!result.empty())
					{
						iPrev = 0;
						for (const auto &sd : result)
						{
							root["resultprev"][iPrev]["d"] = sd[2].substr(0, 16);
							double mmval = atof(sd[0].c_str());
							mmval *= AddjMulti;
							sprintf(szTmp, "%.1f", mmval);
							root["resultprev"][iPrev]["mm"] = szTmp;
							iPrev++;
						}
					}
				}
				else if (sensor == "counter")
				{
					root["status"] = "OK";
					root["title"] = sgroupby.empty() ? "Graph " + sensor + " " + srange : "Comparing " + sensor;
					root["ValueQuantity"] = options["ValueQuantity"];
					root["ValueUnits"] = options["ValueUnits"];

					// int nValue = 0;
					std::string sValue;

					result = m_sql.safe_query("SELECT nValue, sValue FROM DeviceStatus WHERE (ID==%" PRIu64 ")", idx);
					if (!result.empty())
					{
						std::vector<std::string> sd = result[0];
						// nValue = atoi(sd[0].c_str());
						sValue = sd[1];
					}

					std::function<std::string(std::string, std::string)> tableColumn = [](std::string table, std::string expr) {
						return (table.empty() ? "" : table + ".") + expr;
					};

					int ii = 0;
					iPrev = 0;
					if (dType == pTypeP1Power)
					{
                        if (!sgroupby.empty()) {
                            if (sensorarea.empty())
                                {
								_log.Log(LOG_ERROR, "Parameter sensorarea missing with groupby '%s'", sgroupby.c_str());
                                return;
                            }
							std::function<std::string(const char *, char *, char *, char *, char *)> sensorareaExpr =
								[sensorarea, this](const char *expr, char *usageLow, char *usageNormal, char *deliveryLow, char *deliveryNormal) {
									if (sensorarea == "usage")
									{
										return std_format(expr, usageLow, usageNormal);
									}
									if (sensorarea == "delivery")
									{
										return std_format(expr, deliveryLow, deliveryNormal);
									}
									return std::string(expr);
								};
							std::function<std::string(std::string)> counterExpr = [sensorareaExpr](std::string expr) {
								return sensorareaExpr(expr.c_str(), "1", "3", "2", "4");
							};
							std::function<std::string(std::string)> valueExpr = [sensorareaExpr](std::string expr) {
								return sensorareaExpr(expr.c_str(), "1", "5", "2", "6");
							};
							GroupBy(
								root, dbasetable, idx, sgroupby,
								[counterExpr, tableColumn](std::string table) {
									return counterExpr(tableColumn(table, "Counter%s") + "+" + tableColumn(table, "Counter%s"));
								},
								[valueExpr, tableColumn](std::string table) { return valueExpr(tableColumn(table, "Value%s") + "+" + tableColumn(table, "Value%s")); },
								[divider, this](double sum) {
									if (sum == 0)
									{
										return std::string("0");
									}
									return std_format("%.3f", sum / divider);
								});
							ii = root["result"].size();
						}
						else
						{
							// Actual Year
							result = m_sql.safe_query("SELECT Value1,Value2,Value5,Value6, Date,"
										  " Counter1, Counter2, Counter3, Counter4 "
										  "FROM %s WHERE (DeviceRowID==%" PRIu64 " AND Date>='%q'"
										  " AND Date<='%q') ORDER BY Date ASC",
										  dbasetable.c_str(), idx, szDateStart, szDateEnd);
							if (!result.empty())
							{
								bool bHaveDeliverd = false;
								for (const auto &sd : result)
								{
									root["result"][ii]["d"] = sd[4].substr(0, 16);

									double counter_1 = std::stod(sd[5]);
									double counter_2 = std::stod(sd[6]);
									double counter_3 = std::stod(sd[7]);
									double counter_4 = std::stod(sd[8]);

									float fUsage_1 = std::stof(sd[0]);
									float fUsage_2 = std::stof(sd[2]);
									float fDeliv_1 = std::stof(sd[1]);
									float fDeliv_2 = std::stof(sd[3]);

									fDeliv_1 = (fDeliv_1 < 10) ? 0 : fDeliv_1;
									fDeliv_2 = (fDeliv_2 < 10) ? 0 : fDeliv_2;

									if ((fDeliv_1 != 0) || (fDeliv_2 != 0))
									{
										bHaveDeliverd = true;
									}
									sprintf(szTmp, "%.3f", fUsage_1 / divider);
									root["result"][ii]["v"] = szTmp;
									sprintf(szTmp, "%.3f", fUsage_2 / divider);
									root["result"][ii]["v2"] = szTmp;
									sprintf(szTmp, "%.3f", fDeliv_1 / divider);
									root["result"][ii]["r1"] = szTmp;
									sprintf(szTmp, "%.3f", fDeliv_2 / divider);
									root["result"][ii]["r2"] = szTmp;

									if (counter_1 != 0)
									{
										sprintf(szTmp, "%.3f", (counter_1 - fUsage_1) / divider);
									}
									else
									{
										strcpy(szTmp, "0");
									}
									root["result"][ii]["c1"] = szTmp;

									if (counter_2 != 0)
									{
										sprintf(szTmp, "%.3f", (counter_2 - fDeliv_1) / divider);
									}
									else
									{
										strcpy(szTmp, "0");
									}
									root["result"][ii]["c2"] = szTmp;

									if (counter_3 != 0)
									{
										sprintf(szTmp, "%.3f", (counter_3 - fUsage_2) / divider);
									}
									else
									{
										strcpy(szTmp, "0");
									}
									root["result"][ii]["c3"] = szTmp;

									if (counter_4 != 0)
									{
										sprintf(szTmp, "%.3f", (counter_4 - fDeliv_2) / divider);
									}
									else
									{
										strcpy(szTmp, "0");
									}
									root["result"][ii]["c4"] = szTmp;

									ii++;
								}
								if (bHaveDeliverd)
								{
									root["delivered"] = true;
								}
							}
							// Previous Year
							result = m_sql.safe_query("SELECT Value1,Value2,Value5,Value6, Date "
										  "FROM %s WHERE (DeviceRowID==%" PRIu64 " AND Date>='%q' AND Date<='%q') ORDER BY Date ASC",
										  dbasetable.c_str(), idx, szDateStartPrev, szDateEndPrev);
							if (!result.empty())
							{
								bool bHaveDeliverd = false;
								iPrev = 0;
								for (const auto &sd : result)
								{
									root["resultprev"][iPrev]["d"] = sd[4].substr(0, 16);

									float fUsage_1 = std::stof(sd[0]);
									float fUsage_2 = std::stof(sd[2]);
									float fDeliv_1 = std::stof(sd[1]);
									float fDeliv_2 = std::stof(sd[3]);

									if ((fDeliv_1 != 0) || (fDeliv_2 != 0))
									{
										bHaveDeliverd = true;
									}
									sprintf(szTmp, "%.3f", fUsage_1 / divider);
									root["resultprev"][iPrev]["v"] = szTmp;
									sprintf(szTmp, "%.3f", fUsage_2 / divider);
									root["resultprev"][iPrev]["v2"] = szTmp;
									sprintf(szTmp, "%.3f", fDeliv_1 / divider);
									root["resultprev"][iPrev]["r1"] = szTmp;
									sprintf(szTmp, "%.3f", fDeliv_2 / divider);
									root["resultprev"][iPrev]["r2"] = szTmp;
									iPrev++;
								}
								if (bHaveDeliverd)
								{
									root["delivered"] = true;
								}
							}
						}
					}
					else if (dType == pTypeAirQuality)
					{ // month/year
						root["status"] = "OK";
						root["title"] = "Graph " + sensor + " " + srange;

						result = m_sql.safe_query("SELECT Value1,Value2,Value3,Date FROM %s WHERE (DeviceRowID==%" PRIu64 " AND Date>='%q' AND Date<='%q') ORDER BY Date ASC",
									  dbasetable.c_str(), idx, szDateStart, szDateEnd);
						if (!result.empty())
						{
							for (const auto &sd : result)
							{
								root["result"][ii]["d"] = sd[3].substr(0, 16);
								root["result"][ii]["co2_min"] = sd[0];
								root["result"][ii]["co2_max"] = sd[1];
								root["result"][ii]["co2_avg"] = sd[2];
								ii++;
							}
						}
						result = m_sql.safe_query("SELECT Value2,Date FROM %s WHERE (DeviceRowID==%" PRIu64 " AND Date>='%q' AND Date<='%q') ORDER BY Date ASC",
									  dbasetable.c_str(), idx, szDateStartPrev, szDateEndPrev);
						if (!result.empty())
						{
							iPrev = 0;
							for (const auto &sd : result)
							{
								root["resultprev"][iPrev]["d"] = sd[1].substr(0, 16);
								root["resultprev"][iPrev]["co2_max"] = sd[0];
								iPrev++;
							}
						}
					}
					else if (((dType == pTypeGeneral) && ((dSubType == sTypeSoilMoisture) || (dSubType == sTypeLeafWetness))) ||
						 ((dType == pTypeRFXSensor) && ((dSubType == sTypeRFXSensorAD) || (dSubType == sTypeRFXSensorVolt))))
					{ // month/year
						root["status"] = "OK";
						root["title"] = "Graph " + sensor + " " + srange;

						result = m_sql.safe_query("SELECT Value1,Value2, Date FROM %s WHERE (DeviceRowID==%" PRIu64 " AND Date>='%q' AND Date<='%q') ORDER BY Date ASC",
									  dbasetable.c_str(), idx, szDateStart, szDateEnd);
						if (!result.empty())
						{
							for (const auto &sd : result)
							{
								root["result"][ii]["d"] = sd[2].substr(0, 16);
								root["result"][ii]["v_min"] = sd[0];
								root["result"][ii]["v_max"] = sd[1];
								ii++;
							}
						}
					}
					else if (((dType == pTypeGeneral) && (dSubType == sTypeVisibility)) || ((dType == pTypeGeneral) && (dSubType == sTypeDistance)) ||
						 ((dType == pTypeGeneral) && (dSubType == sTypeSolarRadiation)) || ((dType == pTypeGeneral) && (dSubType == sTypeVoltage)) ||
						 ((dType == pTypeGeneral) && (dSubType == sTypeCurrent)) || ((dType == pTypeGeneral) && (dSubType == sTypePressure)) ||
						 ((dType == pTypeGeneral) && (dSubType == sTypeSoundLevel)))
					{ // month/year
						root["status"] = "OK";
						root["title"] = "Graph " + sensor + " " + srange;

						float vdiv = 10.0F;
						if (((dType == pTypeGeneral) && (dSubType == sTypeVoltage)) || ((dType == pTypeGeneral) && (dSubType == sTypeCurrent)))
						{
							vdiv = 1000.0F;
						}

						result = m_sql.safe_query("SELECT Value1,Value2,Value3,Date FROM %s WHERE (DeviceRowID==%" PRIu64 " AND Date>='%q' AND Date<='%q') ORDER BY Date ASC",
									  dbasetable.c_str(), idx, szDateStart, szDateEnd);
						if (!result.empty())
						{
							for (const auto &sd : result)
							{
								float fValue1 = float(atof(sd[0].c_str())) / vdiv;
								float fValue2 = float(atof(sd[1].c_str())) / vdiv;
								float fValue3 = float(atof(sd[2].c_str())) / vdiv;
								root["result"][ii]["d"] = sd[3].substr(0, 16);

								if (metertype == 1)
								{
									if ((dType == pTypeGeneral) && (dSubType == sTypeDistance))
									{
										// Inches
										fValue1 *= 0.3937007874015748F;
										fValue2 *= 0.3937007874015748F;
									}
									else
									{
										// Miles
										fValue1 *= 0.6214F;
										fValue2 *= 0.6214F;
									}
								}
								if (((dType == pTypeGeneral) && (dSubType == sTypeVoltage)) || ((dType == pTypeGeneral) && (dSubType == sTypeCurrent)))
								{
									sprintf(szTmp, "%.3f", fValue1);
									root["result"][ii]["v_min"] = szTmp;
									sprintf(szTmp, "%.3f", fValue2);
									root["result"][ii]["v_max"] = szTmp;
									if (fValue3 != 0)
									{
										sprintf(szTmp, "%.3f", fValue3);
										root["result"][ii]["v_avg"] = szTmp;
									}
								}
								else
								{
									sprintf(szTmp, "%.1f", fValue1);
									root["result"][ii]["v_min"] = szTmp;
									sprintf(szTmp, "%.1f", fValue2);
									root["result"][ii]["v_max"] = szTmp;
									if (fValue3 != 0)
									{
										sprintf(szTmp, "%.1f", fValue3);
										root["result"][ii]["v_avg"] = szTmp;
									}
								}
								ii++;
							}
						}
					}
					else if (dType == pTypeLux)
					{ // month/year
						root["status"] = "OK";
						root["title"] = "Graph " + sensor + " " + srange;

						result = m_sql.safe_query("SELECT Value1,Value2,Value3, Date FROM %s WHERE (DeviceRowID==%" PRIu64 " AND Date>='%q' AND Date<='%q') ORDER BY Date ASC",
									  dbasetable.c_str(), idx, szDateStart, szDateEnd);
						if (!result.empty())
						{
							for (const auto &sd : result)
							{
								root["result"][ii]["d"] = sd[3].substr(0, 16);
								root["result"][ii]["lux_min"] = sd[0];
								root["result"][ii]["lux_max"] = sd[1];
								root["result"][ii]["lux_avg"] = sd[2];
								ii++;
							}
						}
					}
					else if (dType == pTypeWEIGHT)
					{ // month/year
						root["status"] = "OK";
						root["title"] = "Graph " + sensor + " " + srange;

						result = m_sql.safe_query("SELECT Value1,Value2, Date FROM %s WHERE (DeviceRowID==%" PRIu64 " AND Date>='%q' AND Date<='%q') ORDER BY Date ASC",
									  dbasetable.c_str(), idx, szDateStart, szDateEnd);
						if (!result.empty())
						{
							for (const auto &sd : result)
							{
								root["result"][ii]["d"] = sd[2].substr(0, 16);
								sprintf(szTmp, "%.1f", m_sql.m_weightscale * atof(sd[0].c_str()) / 10.0F);
								root["result"][ii]["v_min"] = szTmp;
								sprintf(szTmp, "%.1f", m_sql.m_weightscale * atof(sd[1].c_str()) / 10.0F);
								root["result"][ii]["v_max"] = szTmp;
								ii++;
							}
						}
					}
					else if (dType == pTypeUsage)
					{ // month/year
						root["status"] = "OK";
						root["title"] = "Graph " + sensor + " " + srange;

						result = m_sql.safe_query("SELECT Value1,Value2, Date FROM %s WHERE (DeviceRowID==%" PRIu64 " AND Date>='%q' AND Date<='%q') ORDER BY Date ASC",
									  dbasetable.c_str(), idx, szDateStart, szDateEnd);
						if (!result.empty())
						{
							for (const auto &sd : result)
							{
								root["result"][ii]["d"] = sd[2].substr(0, 16);
								root["result"][ii]["u_min"] = atof(sd[0].c_str()) / 10.0F;
								root["result"][ii]["u_max"] = atof(sd[1].c_str()) / 10.0F;
								ii++;
							}
						}
					}
					else if (dType == pTypeCURRENT)
					{
						result = m_sql.safe_query("SELECT Value1,Value2,Value3,Value4,Value5,Value6, Date FROM %s WHERE (DeviceRowID==%" PRIu64
									  " AND Date>='%q' AND Date<='%q') ORDER BY Date ASC",
									  dbasetable.c_str(), idx, szDateStart, szDateEnd);
						if (!result.empty())
						{
							// CM113
							int displaytype = 0;
							int voltage = 230;
							m_sql.GetPreferencesVar("CM113DisplayType", displaytype);
							m_sql.GetPreferencesVar("ElectricVoltage", voltage);

							root["displaytype"] = displaytype;

							bool bHaveL1 = false;
							bool bHaveL2 = false;
							bool bHaveL3 = false;
							for (const auto &sd : result)
							{
								root["result"][ii]["d"] = sd[6].substr(0, 16);

								float fval1 = static_cast<float>(atof(sd[0].c_str()) / 10.0F);
								float fval2 = static_cast<float>(atof(sd[1].c_str()) / 10.0F);
								float fval3 = static_cast<float>(atof(sd[2].c_str()) / 10.0F);
								float fval4 = static_cast<float>(atof(sd[3].c_str()) / 10.0F);
								float fval5 = static_cast<float>(atof(sd[4].c_str()) / 10.0F);
								float fval6 = static_cast<float>(atof(sd[5].c_str()) / 10.0F);

								if ((fval1 != 0) || (fval2 != 0))
									bHaveL1 = true;
								if ((fval3 != 0) || (fval4 != 0))
									bHaveL2 = true;
								if ((fval5 != 0) || (fval6 != 0))
									bHaveL3 = true;

								if (displaytype == 0)
								{
									sprintf(szTmp, "%.1f", fval1);
									root["result"][ii]["v1"] = szTmp;
									sprintf(szTmp, "%.1f", fval2);
									root["result"][ii]["v2"] = szTmp;
									sprintf(szTmp, "%.1f", fval3);
									root["result"][ii]["v3"] = szTmp;
									sprintf(szTmp, "%.1f", fval4);
									root["result"][ii]["v4"] = szTmp;
									sprintf(szTmp, "%.1f", fval5);
									root["result"][ii]["v5"] = szTmp;
									sprintf(szTmp, "%.1f", fval6);
									root["result"][ii]["v6"] = szTmp;
								}
								else
								{
									sprintf(szTmp, "%d", int(fval1 * voltage));
									root["result"][ii]["v1"] = szTmp;
									sprintf(szTmp, "%d", int(fval2 * voltage));
									root["result"][ii]["v2"] = szTmp;
									sprintf(szTmp, "%d", int(fval3 * voltage));
									root["result"][ii]["v3"] = szTmp;
									sprintf(szTmp, "%d", int(fval4 * voltage));
									root["result"][ii]["v4"] = szTmp;
									sprintf(szTmp, "%d", int(fval5 * voltage));
									root["result"][ii]["v5"] = szTmp;
									sprintf(szTmp, "%d", int(fval6 * voltage));
									root["result"][ii]["v6"] = szTmp;
								}

								ii++;
							}
							if ((!bHaveL1) && (!bHaveL2) && (!bHaveL3))
							{
								root["haveL1"] = true; // show at least something
							}
							else
							{
								if (bHaveL1)
									root["haveL1"] = true;
								if (bHaveL2)
									root["haveL2"] = true;
								if (bHaveL3)
									root["haveL3"] = true;
							}
						}
					}
					else if (dType == pTypeCURRENTENERGY)
					{
						result = m_sql.safe_query("SELECT Value1,Value2,Value3,Value4,Value5,Value6, Date FROM %s WHERE (DeviceRowID==%" PRIu64
									  " AND Date>='%q' AND Date<='%q') ORDER BY Date ASC",
									  dbasetable.c_str(), idx, szDateStart, szDateEnd);
						if (!result.empty())
						{
							// CM180i
							int displaytype = 0;
							int voltage = 230;
							m_sql.GetPreferencesVar("CM113DisplayType", displaytype);
							m_sql.GetPreferencesVar("ElectricVoltage", voltage);

							root["displaytype"] = displaytype;

							bool bHaveL1 = false;
							bool bHaveL2 = false;
							bool bHaveL3 = false;
							for (const auto &sd : result)
							{
								root["result"][ii]["d"] = sd[6].substr(0, 16);

								float fval1 = static_cast<float>(atof(sd[0].c_str()) / 10.0F);
								float fval2 = static_cast<float>(atof(sd[1].c_str()) / 10.0F);
								float fval3 = static_cast<float>(atof(sd[2].c_str()) / 10.0F);
								float fval4 = static_cast<float>(atof(sd[3].c_str()) / 10.0F);
								float fval5 = static_cast<float>(atof(sd[4].c_str()) / 10.0F);
								float fval6 = static_cast<float>(atof(sd[5].c_str()) / 10.0F);

								if ((fval1 != 0) || (fval2 != 0))
									bHaveL1 = true;
								if ((fval3 != 0) || (fval4 != 0))
									bHaveL2 = true;
								if ((fval5 != 0) || (fval6 != 0))
									bHaveL3 = true;

								if (displaytype == 0)
								{
									sprintf(szTmp, "%.1f", fval1);
									root["result"][ii]["v1"] = szTmp;
									sprintf(szTmp, "%.1f", fval2);
									root["result"][ii]["v2"] = szTmp;
									sprintf(szTmp, "%.1f", fval3);
									root["result"][ii]["v3"] = szTmp;
									sprintf(szTmp, "%.1f", fval4);
									root["result"][ii]["v4"] = szTmp;
									sprintf(szTmp, "%.1f", fval5);
									root["result"][ii]["v5"] = szTmp;
									sprintf(szTmp, "%.1f", fval6);
									root["result"][ii]["v6"] = szTmp;
								}
								else
								{
									sprintf(szTmp, "%d", int(fval1 * voltage));
									root["result"][ii]["v1"] = szTmp;
									sprintf(szTmp, "%d", int(fval2 * voltage));
									root["result"][ii]["v2"] = szTmp;
									sprintf(szTmp, "%d", int(fval3 * voltage));
									root["result"][ii]["v3"] = szTmp;
									sprintf(szTmp, "%d", int(fval4 * voltage));
									root["result"][ii]["v4"] = szTmp;
									sprintf(szTmp, "%d", int(fval5 * voltage));
									root["result"][ii]["v5"] = szTmp;
									sprintf(szTmp, "%d", int(fval6 * voltage));
									root["result"][ii]["v6"] = szTmp;
								}

								ii++;
							}
							if ((!bHaveL1) && (!bHaveL2) && (!bHaveL3))
							{
								root["haveL1"] = true; // show at least something
							}
							else
							{
								if (bHaveL1)
									root["haveL1"] = true;
								if (bHaveL2)
									root["haveL2"] = true;
								if (bHaveL3)
									root["haveL3"] = true;
							}
						}
					}
					else
					{
						if (dType == pTypeP1Gas)
						{
							// Add last counter value
							sprintf(szTmp, "%.3f", atof(sValue.c_str()) / 1000.0);
							root["counter"] = szTmp;
						}
						else if (dType == pTypeENERGY)
						{
							size_t spos = sValue.find(';');
							if (spos != std::string::npos)
							{
								float fvalue = static_cast<float>(atof(sValue.substr(spos + 1).c_str()));
								sprintf(szTmp, "%.3f", fvalue / (divider / 100.0F));
								root["counter"] = szTmp;
							}
						}
						else if ((dType == pTypeGeneral) && (dSubType == sTypeKwh))
						{
							size_t spos = sValue.find(';');
							if (spos != std::string::npos)
							{
								float fvalue = static_cast<float>(atof(sValue.substr(spos + 1).c_str()));
								sprintf(szTmp, "%.3f", fvalue / divider);
								root["counter"] = szTmp;
							}
						}
						else if (dType == pTypeRFXMeter)
						{
							// Add last counter value
							float fvalue = static_cast<float>(atof(sValue.c_str()));
							switch (metertype)
							{
								case MTYPE_ENERGY:
								case MTYPE_ENERGY_GENERATED:
									sprintf(szTmp, "%.3f", AddjValue + (fvalue / divider));
									break;
								case MTYPE_GAS:
									sprintf(szTmp, "%.2f", AddjValue + (fvalue / divider));
									break;
								case MTYPE_WATER:
									sprintf(szTmp, "%.3f", AddjValue + (fvalue / divider));
									break;
								default:
									strcpy(szTmp, "");
									break;
							}
							root["counter"] = szTmp;
						}
						else if (dType == pTypeYouLess)
						{
							std::vector<std::string> results;
							StringSplit(sValue, ";", results);
							if (results.size() == 2)
							{
								// Add last counter value
								float fvalue = static_cast<float>(atof(results[0].c_str()));
								switch (metertype)
								{
									case MTYPE_ENERGY:
									case MTYPE_ENERGY_GENERATED:
										sprintf(szTmp, "%.3f", fvalue / divider);
										break;
									case MTYPE_GAS:
										sprintf(szTmp, "%.2f", fvalue / divider);
										break;
									case MTYPE_WATER:
										sprintf(szTmp, "%.3f", fvalue / divider);
										break;
									default:
										strcpy(szTmp, "");
										break;
								}
								root["counter"] = szTmp;
							}
						}
						else if (!bIsManagedCounter)
						{
							// Add last counter value
							sprintf(szTmp, "%d", atoi(sValue.c_str()));
							root["counter"] = szTmp;
						}
						else
						{
							root["counter"] = "0";
						}
						if (!sgroupby.empty())
						{
							GroupBy(
								root, dbasetable, idx, sgroupby, [tableColumn](std::string table) { return tableColumn(table, "Counter"); },
								[tableColumn](std::string table) { return tableColumn(table, "Value"); },
								[metertype, AddjValue, divider, this](double sum) {
									if (sum == 0)
									{
										return std::string("0");
									}
									switch (metertype)
									{
										case MTYPE_ENERGY:
										case MTYPE_ENERGY_GENERATED:
											return std_format("%.3f", sum / divider);
										case MTYPE_GAS:
											return std_format("%.2f", sum / divider);
										case MTYPE_WATER:
											return std_format("%.3f", sum / divider);
										case MTYPE_COUNTER:
											return std_format("%g", sum / divider);
									}
									return std::string("");
								});
							ii = root["result"].size();
						}
						else
						{
							// Actual Year
							result =
								m_sql.safe_query("SELECT Value, Date, Counter FROM %s WHERE (DeviceRowID==%" PRIu64 " AND Date>='%q' AND Date<='%q') ORDER BY Date ASC",
										 dbasetable.c_str(), idx, szDateStart, szDateEnd);
							if (!result.empty())
							{
								for (const auto &sd : result)
								{
									root["result"][ii]["d"] = sd[1].substr(0, 16);

									std::string szValue = sd[0];

									double fcounter = atof(sd[2].c_str());

									switch (metertype)
									{
										case MTYPE_ENERGY:
										case MTYPE_ENERGY_GENERATED:
											sprintf(szTmp, "%.3f", atof(szValue.c_str()) / divider);
											root["result"][ii]["v"] = szTmp;
											if (fcounter != 0)
												sprintf(szTmp, "%.3f", AddjValue + ((fcounter - atof(szValue.c_str())) / divider));
											else
												strcpy(szTmp, "0");
											root["result"][ii]["c"] = szTmp;
											break;
										case MTYPE_GAS:
											sprintf(szTmp, "%.2f", atof(szValue.c_str()) / divider);
											root["result"][ii]["v"] = szTmp;
											if (fcounter != 0)
												sprintf(szTmp, "%.2f", AddjValue + ((fcounter - atof(szValue.c_str())) / divider));
											else
												strcpy(szTmp, "0");
											root["result"][ii]["c"] = szTmp;
											break;
										case MTYPE_WATER:
											sprintf(szTmp, "%.3f", atof(szValue.c_str()) / divider);
											root["result"][ii]["v"] = szTmp;
											if (fcounter != 0)
												sprintf(szTmp, "%.3f", AddjValue + ((fcounter - atof(szValue.c_str())) / divider));
											else
												strcpy(szTmp, "0");
											root["result"][ii]["c"] = szTmp;
											break;
										case MTYPE_COUNTER:
											sprintf(szTmp, "%g", atof(szValue.c_str()) / divider);
											root["result"][ii]["v"] = szTmp;
											if (fcounter != 0)
												sprintf(szTmp, "%g", AddjValue + ((fcounter - atof(szValue.c_str())) / divider));
											else
												strcpy(szTmp, "0");
											root["result"][ii]["c"] = szTmp;
											break;
									}
									ii++;
								}
							}
							// Past Year
							result =
								m_sql.safe_query("SELECT Value, Date, Counter FROM %s WHERE (DeviceRowID==%" PRIu64 " AND Date>='%q' AND Date<='%q') ORDER BY Date ASC",
										 dbasetable.c_str(), idx, szDateStartPrev, szDateEndPrev);
							if (!result.empty())
							{
								iPrev = 0;
								for (const auto &sd : result)
								{
									root["resultprev"][iPrev]["d"] = sd[1].substr(0, 16);

									std::string szValue = sd[0];
									switch (metertype)
									{
										case MTYPE_ENERGY:
										case MTYPE_ENERGY_GENERATED:
											sprintf(szTmp, "%.3f", atof(szValue.c_str()) / divider);
											root["resultprev"][iPrev]["v"] = szTmp;
											break;
										case MTYPE_GAS:
											sprintf(szTmp, "%.2f", atof(szValue.c_str()) / divider);
											root["resultprev"][iPrev]["v"] = szTmp;
											break;
										case MTYPE_WATER:
											sprintf(szTmp, "%.3f", atof(szValue.c_str()) / divider);
											root["resultprev"][iPrev]["v"] = szTmp;
											break;
										case MTYPE_COUNTER:
											sprintf(szTmp, "%g", atof(szValue.c_str()) / divider);
											root["resultprev"][iPrev]["v"] = szTmp;
											break;
									}
									iPrev++;
								}
							}
						}
					}
					// add today (have to calculate it)

					if ((!sactmonth.empty()) || (!sactyear.empty()))
					{
						struct tm loctime;
						time_t now = mytime(nullptr);
						localtime_r(&now, &loctime);
						if ((!sactmonth.empty()) && (!sactyear.empty()))
						{
							bool bIsThisMonth = (atoi(sactyear.c_str()) == loctime.tm_year + 1900) && (atoi(sactmonth.c_str()) == loctime.tm_mon + 1);
							if (bIsThisMonth)
							{
								sprintf(szDateEnd, "%04d-%02d-%02d", loctime.tm_year + 1900, loctime.tm_mon + 1, loctime.tm_mday);
							}
						}
						else if (!sactyear.empty())
						{
							bool bIsThisYear = (atoi(sactyear.c_str()) == loctime.tm_year + 1900);
							if (bIsThisYear)
							{
								sprintf(szDateEnd, "%04d-%02d-%02d", loctime.tm_year + 1900, loctime.tm_mon + 1, loctime.tm_mday);
							}
						}
					}

					if (dType == pTypeP1Power)
					{
						result = m_sql.safe_query("SELECT "
									  " MIN(Value1) as levering_laag_min,"
									  " MAX(Value1) as levering_laag_max,"
									  " MIN(Value2) as teruglevering_laag_min,"
									  " MAX(Value2) as teruglevering_laag_max,"
									  " MIN(Value5) as levering_normaal_min,"
									  " MAX(Value5) as levering_normaal_max,"
									  " MIN(Value6) as teruglevering_normaal_min,"
									  " MAX(Value6) as teruglevering_normaal_max"
									  " FROM MultiMeter WHERE (DeviceRowID=%" PRIu64 ""
									  " AND Date>='%q')",
									  idx, szDateEnd);
						bool bHaveDeliverd = false;
						if (!result.empty())
						{
							std::vector<std::string> sd = result[0];
							unsigned long long total_min_usage_1 = std::strtoull(sd[0].c_str(), nullptr, 10);
							unsigned long long total_max_usage_1 = std::strtoull(sd[1].c_str(), nullptr, 10);
							unsigned long long total_min_usage_2 = std::strtoull(sd[4].c_str(), nullptr, 10);
							unsigned long long total_max_usage_2 = std::strtoull(sd[5].c_str(), nullptr, 10);
							unsigned long long total_real_usage_1, total_real_usage_2;
							unsigned long long total_min_deliv_1 = std::strtoull(sd[2].c_str(), nullptr, 10);
							unsigned long long total_max_deliv_1 = std::strtoull(sd[3].c_str(), nullptr, 10);
							unsigned long long total_min_deliv_2 = std::strtoull(sd[6].c_str(), nullptr, 10);
							unsigned long long total_max_deliv_2 = std::strtoull(sd[7].c_str(), nullptr, 10);
							unsigned long long total_real_deliv_1, total_real_deliv_2;

							total_real_usage_1 = total_max_usage_1 - total_min_usage_1;
							total_real_usage_2 = total_max_usage_2 - total_min_usage_2;

							total_real_deliv_1 = total_max_deliv_1 - total_min_deliv_1;
							total_real_deliv_2 = total_max_deliv_2 - total_min_deliv_2;

							if (total_max_deliv_1 != 0 || total_max_deliv_2 != 0)
								bHaveDeliverd = true;

							if (!sgroupby.empty())
							{
								const float todayValue = (sensorarea == "usage"	     ? (total_real_usage_1 + total_real_usage_2)
											  : sensorarea == "delivery" ? (total_real_deliv_1 + total_real_deliv_2)
														     : 0) /
											 divider;
								AddTodayValueToResult(root, sgroupby, std::string(szDateEnd), todayValue, "%.3f");
							}
							else
							{
								root["result"][ii]["d"] = szDateEnd;

								sprintf(szTmp, "%.3f", (float)(total_real_usage_1 / divider));
								root["result"][ii]["v"] = szTmp;
								sprintf(szTmp, "%.3f", (float)(total_real_usage_2 / divider));
								root["result"][ii]["v2"] = szTmp;

								sprintf(szTmp, "%.3f", (float)(total_real_deliv_1 / divider));
								root["result"][ii]["r1"] = szTmp;
								sprintf(szTmp, "%.3f", (float)(total_real_deliv_2 / divider));
								root["result"][ii]["r2"] = szTmp;

								sprintf(szTmp, "%.3f", (float)(total_min_usage_1 / divider));
								root["result"][ii]["c1"] = szTmp;
								sprintf(szTmp, "%.3f", (float)(total_min_usage_2 / divider));
								root["result"][ii]["c3"] = szTmp;

								if (total_max_deliv_2 != 0)
								{
									sprintf(szTmp, "%.3f", (float)(total_min_deliv_1 / divider));
									root["result"][ii]["c2"] = szTmp;
									sprintf(szTmp, "%.3f", (float)(total_min_deliv_2 / divider));
									root["result"][ii]["c4"] = szTmp;
								}
								else
								{
									strcpy(szTmp, "0");
									root["result"][ii]["c2"] = szTmp;
									root["result"][ii]["c4"] = szTmp;
								}

								ii++;
							}
						}
						if (bHaveDeliverd)
						{
							root["delivered"] = true;
						}
					}
					else if (dType == pTypeAirQuality)
					{
						result = m_sql.safe_query("SELECT MIN(Value), MAX(Value), AVG(Value) FROM Meter WHERE (DeviceRowID==%" PRIu64 " AND Date>='%q')", idx, szDateEnd);
						if (!result.empty())
						{
							root["result"][ii]["d"] = szDateEnd;
							root["result"][ii]["co2_min"] = result[0][0];
							root["result"][ii]["co2_max"] = result[0][1];
							root["result"][ii]["co2_avg"] = result[0][2];
							ii++;
						}
					}
					else if (((dType == pTypeGeneral) && ((dSubType == sTypeSoilMoisture) || (dSubType == sTypeLeafWetness))) ||
						 ((dType == pTypeRFXSensor) && ((dSubType == sTypeRFXSensorAD) || (dSubType == sTypeRFXSensorVolt))))
					{
						result = m_sql.safe_query("SELECT MIN(Value), MAX(Value) FROM Meter WHERE (DeviceRowID==%" PRIu64 " AND Date>='%q')", idx, szDateEnd);
						if (!result.empty())
						{
							root["result"][ii]["d"] = szDateEnd;
							root["result"][ii]["v_min"] = result[0][0];
							root["result"][ii]["v_max"] = result[0][1];
							ii++;
						}
					}
					else if (((dType == pTypeGeneral) && (dSubType == sTypeVisibility)) || ((dType == pTypeGeneral) && (dSubType == sTypeDistance)) ||
						 ((dType == pTypeGeneral) && (dSubType == sTypeSolarRadiation)) || ((dType == pTypeGeneral) && (dSubType == sTypeVoltage)) ||
						 ((dType == pTypeGeneral) && (dSubType == sTypeCurrent)) || ((dType == pTypeGeneral) && (dSubType == sTypePressure)) ||
						 ((dType == pTypeGeneral) && (dSubType == sTypeSoundLevel)))
					{
						float vdiv = 10.0F;
						if (((dType == pTypeGeneral) && (dSubType == sTypeVoltage)) || ((dType == pTypeGeneral) && (dSubType == sTypeCurrent)))
						{
							vdiv = 1000.0F;
						}

						result = m_sql.safe_query("SELECT MIN(Value), MAX(Value) FROM Meter WHERE (DeviceRowID==%" PRIu64 " AND Date>='%q')", idx, szDateEnd);
						if (!result.empty())
						{
							root["result"][ii]["d"] = szDateEnd;
							float fValue1 = float(atof(result[0][0].c_str())) / vdiv;
							float fValue2 = float(atof(result[0][1].c_str())) / vdiv;
							if (metertype == 1)
							{
								if ((dType == pTypeGeneral) && (dSubType == sTypeDistance))
								{
									// Inches
									fValue1 *= 0.3937007874015748F;
									fValue2 *= 0.3937007874015748F;
								}
								else
								{
									// Miles
									fValue1 *= 0.6214F;
									fValue2 *= 0.6214F;
								}
							}

							if ((dType == pTypeGeneral) && (dSubType == sTypeVoltage))
								sprintf(szTmp, "%.3f", fValue1);
							else if ((dType == pTypeGeneral) && (dSubType == sTypeCurrent))
								sprintf(szTmp, "%.3f", fValue1);
							else
								sprintf(szTmp, "%.1f", fValue1);
							root["result"][ii]["v_min"] = szTmp;
							if ((dType == pTypeGeneral) && (dSubType == sTypeVoltage))
								sprintf(szTmp, "%.3f", fValue2);
							else if ((dType == pTypeGeneral) && (dSubType == sTypeCurrent))
								sprintf(szTmp, "%.3f", fValue2);
							else
								sprintf(szTmp, "%.1f", fValue2);
							root["result"][ii]["v_max"] = szTmp;
							ii++;
						}
					}
					else if (dType == pTypeLux)
					{
						result = m_sql.safe_query("SELECT MIN(Value), MAX(Value), AVG(Value) FROM Meter WHERE (DeviceRowID==%" PRIu64 " AND Date>='%q')", idx, szDateEnd);
						if (!result.empty())
						{
							root["result"][ii]["d"] = szDateEnd;
							root["result"][ii]["lux_min"] = result[0][0];
							root["result"][ii]["lux_max"] = result[0][1];
							root["result"][ii]["lux_avg"] = result[0][2];
							ii++;
						}
					}
					else if (dType == pTypeWEIGHT)
					{
						result = m_sql.safe_query("SELECT MIN(Value), MAX(Value) FROM Meter WHERE (DeviceRowID==%" PRIu64 " AND Date>='%q')", idx, szDateEnd);
						if (!result.empty())
						{
							root["result"][ii]["d"] = szDateEnd;
							sprintf(szTmp, "%.1f", m_sql.m_weightscale * atof(result[0][0].c_str()) / 10.0F);
							root["result"][ii]["v_min"] = szTmp;
							sprintf(szTmp, "%.1f", m_sql.m_weightscale * atof(result[0][1].c_str()) / 10.0F);
							root["result"][ii]["v_max"] = szTmp;
							ii++;
						}
					}
					else if (dType == pTypeUsage)
					{
						result = m_sql.safe_query("SELECT MIN(Value), MAX(Value) FROM Meter WHERE (DeviceRowID=%" PRIu64 " AND Date>='%q')", idx, szDateEnd);
						if (!result.empty())
						{
							root["result"][ii]["d"] = szDateEnd;
							root["result"][ii]["u_min"] = atof(result[0][0].c_str()) / 10.0F;
							root["result"][ii]["u_max"] = atof(result[0][1].c_str()) / 10.0F;
							ii++;
						}
					}
					else if (!bIsManagedCounter)
					{
						/*if (sgroupby == "year") {

						} else*/
						{
							// get the first value
							result = m_sql.safe_query(
								//"SELECT MIN(Value), MAX(Value) FROM Meter WHERE (DeviceRowID==%" PRIu64 " AND Date>='%q')",
								"SELECT Value FROM Meter WHERE (DeviceRowID==%" PRIu64 " AND Date>='%q') ORDER BY Date ASC LIMIT 1", idx, szDateEnd);
							if (!result.empty())
							{
								std::vector<std::string> sd = result[0];
								unsigned long long total_min = std::strtoull(sd[0].c_str(), nullptr, 10);
								unsigned long long total_max = total_min;
								unsigned long long total_real;

								// Get the last value
								result = m_sql.safe_query("SELECT Value FROM Meter WHERE (DeviceRowID==%" PRIu64 " AND Date>='%q') ORDER BY Date DESC LIMIT 1", idx,
											  szDateEnd);
								if (!result.empty())
								{
									std::vector<std::string> sd = result[0];
									total_max = std::strtoull(sd[0].c_str(), nullptr, 10);
								}

								total_real = total_max - total_min;
								sprintf(szTmp, "%lld", total_real);
								std::string szValue = szTmp;

								if (!sgroupby.empty())
								{
									float todayValue = total_real / divider;
									std::string formatString;
									switch (metertype)
									{
										case MTYPE_ENERGY:
										case MTYPE_ENERGY_GENERATED:
											formatString = "%.3f";
											break;
										case MTYPE_GAS:
											formatString = "%.2f";
											break;
										case MTYPE_WATER:
											formatString = "%.3f";
											break;
										case MTYPE_COUNTER:
											formatString = "%g";
											break;
									}
									AddTodayValueToResult(root, sgroupby, std::string(szDateEnd), todayValue, formatString);
								}
								else
								{
									root["result"][ii]["d"] = szDateEnd;
									switch (metertype)
									{
										case MTYPE_ENERGY:
										case MTYPE_ENERGY_GENERATED: {
											sprintf(szTmp, "%.3f", atof(szValue.c_str()) / divider);
											root["result"][ii]["v"] = szTmp;

											std::vector<std::string> mresults;
											StringSplit(sValue, ";", mresults);
											if (mresults.size() == 2)
											{
												sValue = mresults[1];
											}
											if (dType == pTypeENERGY)
												sprintf(szTmp, "%.3f",
													AddjValue + (((atof(sValue.c_str()) * 100.0F) - atof(szValue.c_str())) / divider));
											else
												sprintf(szTmp, "%.3f", AddjValue + ((atof(sValue.c_str()) - atof(szValue.c_str())) / divider));
											root["result"][ii]["c"] = szTmp;
										}
										break;
										case MTYPE_GAS:
											sprintf(szTmp, "%.2f", atof(szValue.c_str()) / divider);
											root["result"][ii]["v"] = szTmp;
											sprintf(szTmp, "%.2f", AddjValue + ((atof(sValue.c_str()) - atof(szValue.c_str())) / divider));
											root["result"][ii]["c"] = szTmp;
											break;
										case MTYPE_WATER:
											sprintf(szTmp, "%.3f", atof(szValue.c_str()) / divider);
											root["result"][ii]["v"] = szTmp;
											sprintf(szTmp, "%.3f", AddjValue + ((atof(sValue.c_str()) - atof(szValue.c_str())) / divider));
											root["result"][ii]["c"] = szTmp;
											break;
										case MTYPE_COUNTER:
											sprintf(szTmp, "%g", atof(szValue.c_str()) / divider);
											root["result"][ii]["v"] = szTmp;
											sprintf(szTmp, "%g", AddjValue + ((atof(sValue.c_str()) - atof(szValue.c_str())) / divider));
											root["result"][ii]["c"] = szTmp;
											break;
									}
									ii++;
								}
							}
						}
					}
				}
				else if (sensor == "wind")
				{
					root["status"] = "OK";
					root["title"] = "Graph " + sensor + " " + srange;

					int ii = 0;

					result = m_sql.safe_query("SELECT Direction, Speed_Min, Speed_Max, Gust_Min,"
								  " Gust_Max, Date "
								  "FROM %s WHERE (DeviceRowID==%" PRIu64 " AND Date>='%q'"
								  " AND Date<='%q') ORDER BY Date ASC",
								  dbasetable.c_str(), idx, szDateStart, szDateEnd);
					if (!result.empty())
					{
						for (const auto &sd : result)
						{
							root["result"][ii]["d"] = sd[5].substr(0, 16);
							root["result"][ii]["di"] = sd[0];

							int intSpeed = atoi(sd[2].c_str());
							int intGust = atoi(sd[4].c_str());
							if (m_sql.m_windunit != WINDUNIT_Beaufort)
							{
								sprintf(szTmp, "%.1f", float(intSpeed) * m_sql.m_windscale);
								root["result"][ii]["sp"] = szTmp;
								sprintf(szTmp, "%.1f", float(intGust) * m_sql.m_windscale);
								root["result"][ii]["gu"] = szTmp;
							}
							else
							{
								float windspeedms = float(intSpeed) * 0.1F;
								float windgustms = float(intGust) * 0.1F;
								sprintf(szTmp, "%d", MStoBeaufort(windspeedms));
								root["result"][ii]["sp"] = szTmp;
								sprintf(szTmp, "%d", MStoBeaufort(windgustms));
								root["result"][ii]["gu"] = szTmp;
							}
							ii++;
						}
					}
					// add today (have to calculate it)
					result = m_sql.safe_query("SELECT AVG(Direction), MIN(Speed), MAX(Speed),"
								  " MIN(Gust), MAX(Gust) "
								  "FROM Wind WHERE (DeviceRowID==%" PRIu64 " AND Date>='%q') ORDER BY Date ASC",
								  idx, szDateEnd);
					if (!result.empty())
					{
						std::vector<std::string> sd = result[0];

						root["result"][ii]["d"] = szDateEnd;
						root["result"][ii]["di"] = sd[0];

						int intSpeed = atoi(sd[2].c_str());
						int intGust = atoi(sd[4].c_str());
						if (m_sql.m_windunit != WINDUNIT_Beaufort)
						{
							sprintf(szTmp, "%.1f", float(intSpeed) * m_sql.m_windscale);
							root["result"][ii]["sp"] = szTmp;
							sprintf(szTmp, "%.1f", float(intGust) * m_sql.m_windscale);
							root["result"][ii]["gu"] = szTmp;
						}
						else
						{
							float windspeedms = float(intSpeed) * 0.1F;
							float windgustms = float(intGust) * 0.1F;
							sprintf(szTmp, "%d", MStoBeaufort(windspeedms));
							root["result"][ii]["sp"] = szTmp;
							sprintf(szTmp, "%d", MStoBeaufort(windgustms));
							root["result"][ii]["gu"] = szTmp;
						}
						ii++;
					}
					// Previous Year
					result = m_sql.safe_query("SELECT Direction, Speed_Min, Speed_Max, Gust_Min,"
								  " Gust_Max, Date "
								  "FROM %s WHERE (DeviceRowID==%" PRIu64 " AND Date>='%q'"
								  " AND Date<='%q') ORDER BY Date ASC",
								  dbasetable.c_str(), idx, szDateStartPrev, szDateEndPrev);
					if (!result.empty())
					{
						iPrev = 0;
						for (const auto &sd : result)
						{
							root["resultprev"][iPrev]["d"] = sd[5].substr(0, 16);
							root["resultprev"][iPrev]["di"] = sd[0];

							int intSpeed = atoi(sd[2].c_str());
							int intGust = atoi(sd[4].c_str());
							if (m_sql.m_windunit != WINDUNIT_Beaufort)
							{
								sprintf(szTmp, "%.1f", float(intSpeed) * m_sql.m_windscale);
								root["resultprev"][iPrev]["sp"] = szTmp;
								sprintf(szTmp, "%.1f", float(intGust) * m_sql.m_windscale);
								root["resultprev"][iPrev]["gu"] = szTmp;
							}
							else
							{
								float windspeedms = float(intSpeed) * 0.1F;
								float windgustms = float(intGust) * 0.1F;
								sprintf(szTmp, "%d", MStoBeaufort(windspeedms));
								root["resultprev"][iPrev]["sp"] = szTmp;
								sprintf(szTmp, "%d", MStoBeaufort(windgustms));
								root["resultprev"][iPrev]["gu"] = szTmp;
							}
							iPrev++;
						}
					}
				}
			}													 // month or year
			else if ((srange.substr(0, 1) == "2") && (srange.substr(10, 1) == "T") && (srange.substr(11, 1) == "2")) // custom range 2013-01-01T2013-12-31
			{
				std::string szDateStart = srange.substr(0, 10);
				std::string szDateEnd = srange.substr(11, 10);
				std::string sgraphtype = request::findValue(&req, "graphtype");
				std::string sgraphTemp = request::findValue(&req, "graphTemp");
				std::string sgraphChill = request::findValue(&req, "graphChill");
				std::string sgraphHum = request::findValue(&req, "graphHum");
				std::string sgraphBaro = request::findValue(&req, "graphBaro");
				std::string sgraphDew = request::findValue(&req, "graphDew");
				std::string sgraphSet = request::findValue(&req, "graphSet");

				if (sensor == "temp")
				{
					root["status"] = "OK";
					root["title"] = "Graph " + sensor + " " + srange;

					bool sendTemp = false;
					bool sendChill = false;
					bool sendHum = false;
					bool sendBaro = false;
					bool sendDew = false;
					bool sendSet = false;

					if ((sgraphTemp == "true") &&
					    ((dType == pTypeRego6XXTemp) || (dType == pTypeTEMP) || (dType == pTypeTEMP_HUM) || (dType == pTypeTEMP_HUM_BARO) || (dType == pTypeTEMP_BARO) ||
					     (dType == pTypeWIND) || (dType == pTypeThermostat1) || (dType == pTypeRadiator1) || ((dType == pTypeUV) && (dSubType == sTypeUV3)) ||
					     ((dType == pTypeWIND) && (dSubType == sTypeWIND4)) || ((dType == pTypeRFXSensor) && (dSubType == sTypeRFXSensorTemp)) ||
					     ((dType == pTypeThermostat) && (dSubType == sTypeThermSetpoint)) || (dType == pTypeEvohomeZone) || (dType == pTypeEvohomeWater)))
					{
						sendTemp = true;
					}
					if ((sgraphSet == "true") && ((dType == pTypeEvohomeZone) || (dType == pTypeEvohomeWater))) // FIXME cheat for water setpoint is just on or off
					{
						sendSet = true;
					}
					if ((sgraphChill == "true") && (((dType == pTypeWIND) && (dSubType == sTypeWIND4)) || ((dType == pTypeWIND) && (dSubType == sTypeWINDNoTemp))))
					{
						sendChill = true;
					}
					if ((sgraphHum == "true") && ((dType == pTypeHUM) || (dType == pTypeTEMP_HUM) || (dType == pTypeTEMP_HUM_BARO)))
					{
						sendHum = true;
					}
					if ((sgraphBaro == "true") && ((dType == pTypeTEMP_HUM_BARO) || (dType == pTypeTEMP_BARO) || ((dType == pTypeGeneral) && (dSubType == sTypeBaro))))
					{
						sendBaro = true;
					}
					if ((sgraphDew == "true") && ((dType == pTypeTEMP_HUM) || (dType == pTypeTEMP_HUM_BARO)))
					{
						sendDew = true;
					}

					if (sgraphtype == "1")
					{
						// Need to get all values of the end date so 23:59:59 is appended to the date string
						result = m_sql.safe_query("SELECT Temperature, Chill, Humidity, Barometer,"
									  " Date, DewPoint, SetPoint "
									  "FROM Temperature WHERE (DeviceRowID==%" PRIu64 ""
									  " AND Date>='%q' AND Date<='%q 23:59:59') ORDER BY Date ASC",
									  idx, szDateStart.c_str(), szDateEnd.c_str());
						int ii = 0;
						if (!result.empty())
						{
							for (const auto &sd : result)
							{
								root["result"][ii]["d"] = sd[4]; //.substr(0,16);
								if (sendTemp)
								{
									double te = ConvertTemperature(atof(sd[0].c_str()), tempsign);
									double tm = ConvertTemperature(atof(sd[0].c_str()), tempsign);
									root["result"][ii]["te"] = te;
									root["result"][ii]["tm"] = tm;
								}
								if (sendChill)
								{
									double ch = ConvertTemperature(atof(sd[1].c_str()), tempsign);
									double cm = ConvertTemperature(atof(sd[1].c_str()), tempsign);
									root["result"][ii]["ch"] = ch;
									root["result"][ii]["cm"] = cm;
								}
								if (sendHum)
								{
									root["result"][ii]["hu"] = sd[2];
								}
								if (sendBaro)
								{
									if (dType == pTypeTEMP_HUM_BARO)
									{
										if (dSubType == sTypeTHBFloat)
										{
											sprintf(szTmp, "%.1f", atof(sd[3].c_str()) / 10.0F);
											root["result"][ii]["ba"] = szTmp;
										}
										else
											root["result"][ii]["ba"] = sd[3];
									}
									else if (dType == pTypeTEMP_BARO)
									{
										sprintf(szTmp, "%.1f", atof(sd[3].c_str()) / 10.0F);
										root["result"][ii]["ba"] = szTmp;
									}
									else if ((dType == pTypeGeneral) && (dSubType == sTypeBaro))
									{
										sprintf(szTmp, "%.1f", atof(sd[3].c_str()) / 10.0F);
										root["result"][ii]["ba"] = szTmp;
									}
								}
								if (sendDew)
								{
									double dp = ConvertTemperature(atof(sd[5].c_str()), tempsign);
									root["result"][ii]["dp"] = dp;
								}
								if (sendSet)
								{
									double se = ConvertTemperature(atof(sd[6].c_str()), tempsign);
									root["result"][ii]["se"] = se;
								}
								ii++;
							}
						}
					}
					else
					{
						result = m_sql.safe_query("SELECT Temp_Min, Temp_Max, Chill_Min, Chill_Max,"
									  " Humidity, Barometer, Date, DewPoint, Temp_Avg,"
									  " SetPoint_Min, SetPoint_Max, SetPoint_Avg "
									  "FROM Temperature_Calendar "
									  "WHERE (DeviceRowID==%" PRIu64 " AND Date>='%q'"
									  " AND Date<='%q') ORDER BY Date ASC",
									  idx, szDateStart.c_str(), szDateEnd.c_str());
						int ii = 0;
						if (!result.empty())
						{
							for (const auto &sd : result)
							{
								root["result"][ii]["d"] = sd[6].substr(0, 16);
								if (sendTemp)
								{
									double te = ConvertTemperature(atof(sd[1].c_str()), tempsign);
									double tm = ConvertTemperature(atof(sd[0].c_str()), tempsign);
									double ta = ConvertTemperature(atof(sd[8].c_str()), tempsign);

									root["result"][ii]["te"] = te;
									root["result"][ii]["tm"] = tm;
									root["result"][ii]["ta"] = ta;
								}
								if (sendChill)
								{
									double ch = ConvertTemperature(atof(sd[3].c_str()), tempsign);
									double cm = ConvertTemperature(atof(sd[2].c_str()), tempsign);

									root["result"][ii]["ch"] = ch;
									root["result"][ii]["cm"] = cm;
								}
								if (sendHum)
								{
									root["result"][ii]["hu"] = sd[4];
								}
								if (sendBaro)
								{
									if (dType == pTypeTEMP_HUM_BARO)
									{
										if (dSubType == sTypeTHBFloat)
										{
											sprintf(szTmp, "%.1f", atof(sd[5].c_str()) / 10.0F);
											root["result"][ii]["ba"] = szTmp;
										}
										else
											root["result"][ii]["ba"] = sd[5];
									}
									else if (dType == pTypeTEMP_BARO)
									{
										sprintf(szTmp, "%.1f", atof(sd[5].c_str()) / 10.0F);
										root["result"][ii]["ba"] = szTmp;
									}
									else if ((dType == pTypeGeneral) && (dSubType == sTypeBaro))
									{
										sprintf(szTmp, "%.1f", atof(sd[5].c_str()) / 10.0F);
										root["result"][ii]["ba"] = szTmp;
									}
								}
								if (sendDew)
								{
									double dp = ConvertTemperature(atof(sd[7].c_str()), tempsign);
									root["result"][ii]["dp"] = dp;
								}
								if (sendSet)
								{
									double sm = ConvertTemperature(atof(sd[9].c_str()), tempsign);
									double sx = ConvertTemperature(atof(sd[10].c_str()), tempsign);
									double se = ConvertTemperature(atof(sd[11].c_str()), tempsign);
									root["result"][ii]["sm"] = sm;
									root["result"][ii]["se"] = se;
									root["result"][ii]["sx"] = sx;
									char szTmp[1024];
									sprintf(szTmp, "%.1f %.1f %.1f", sm, se, sx);
									_log.Log(LOG_STATUS, "%s", szTmp);
								}
								ii++;
							}
						}

						// add today (have to calculate it)
						result = m_sql.safe_query("SELECT MIN(Temperature), MAX(Temperature),"
									  " MIN(Chill), MAX(Chill), AVG(Humidity),"
									  " AVG(Barometer), MIN(DewPoint), AVG(Temperature),"
									  " MIN(SetPoint), MAX(SetPoint), AVG(SetPoint) "
									  "FROM Temperature WHERE (DeviceRowID==%" PRIu64 " AND Date>='%q')",
									  idx, szDateEnd.c_str());
						if (!result.empty())
						{
							std::vector<std::string> sd = result[0];

							root["result"][ii]["d"] = szDateEnd;
							if (sendTemp)
							{
								double te = ConvertTemperature(atof(sd[1].c_str()), tempsign);
								double tm = ConvertTemperature(atof(sd[0].c_str()), tempsign);
								double ta = ConvertTemperature(atof(sd[7].c_str()), tempsign);

								root["result"][ii]["te"] = te;
								root["result"][ii]["tm"] = tm;
								root["result"][ii]["ta"] = ta;
							}
							if (sendChill)
							{
								double ch = ConvertTemperature(atof(sd[3].c_str()), tempsign);
								double cm = ConvertTemperature(atof(sd[2].c_str()), tempsign);
								root["result"][ii]["ch"] = ch;
								root["result"][ii]["cm"] = cm;
							}
							if (sendHum)
							{
								root["result"][ii]["hu"] = sd[4];
							}
							if (sendBaro)
							{
								if (dType == pTypeTEMP_HUM_BARO)
								{
									if (dSubType == sTypeTHBFloat)
									{
										sprintf(szTmp, "%.1f", atof(sd[5].c_str()) / 10.0F);
										root["result"][ii]["ba"] = szTmp;
									}
									else
										root["result"][ii]["ba"] = sd[5];
								}
								else if (dType == pTypeTEMP_BARO)
								{
									sprintf(szTmp, "%.1f", atof(sd[5].c_str()) / 10.0F);
									root["result"][ii]["ba"] = szTmp;
								}
								else if ((dType == pTypeGeneral) && (dSubType == sTypeBaro))
								{
									sprintf(szTmp, "%.1f", atof(sd[5].c_str()) / 10.0F);
									root["result"][ii]["ba"] = szTmp;
								}
							}
							if (sendDew)
							{
								double dp = ConvertTemperature(atof(sd[6].c_str()), tempsign);
								root["result"][ii]["dp"] = dp;
							}
							if (sendSet)
							{
								double sm = ConvertTemperature(atof(sd[8].c_str()), tempsign);
								double sx = ConvertTemperature(atof(sd[9].c_str()), tempsign);
								double se = ConvertTemperature(atof(sd[10].c_str()), tempsign);

								root["result"][ii]["sm"] = sm;
								root["result"][ii]["se"] = se;
								root["result"][ii]["sx"] = sx;
							}
							ii++;
						}
					}
				}
				else if (sensor == "uv")
				{
					root["status"] = "OK";
					root["title"] = "Graph " + sensor + " " + srange;

					result = m_sql.safe_query("SELECT Level, Date FROM %s WHERE (DeviceRowID==%" PRIu64 ""
								  " AND Date>='%q' AND Date<='%q') ORDER BY Date ASC",
								  dbasetable.c_str(), idx, szDateStart.c_str(), szDateEnd.c_str());
					int ii = 0;
					if (!result.empty())
					{
						for (const auto &sd : result)
						{
							root["result"][ii]["d"] = sd[1].substr(0, 16);
							root["result"][ii]["uvi"] = sd[0];
							ii++;
						}
					}
					// add today (have to calculate it)
					result = m_sql.safe_query("SELECT MAX(Level) FROM UV WHERE (DeviceRowID==%" PRIu64 " AND Date>='%q')", idx, szDateEnd.c_str());
					if (!result.empty())
					{
						std::vector<std::string> sd = result[0];

						root["result"][ii]["d"] = szDateEnd;
						root["result"][ii]["uvi"] = sd[0];
						ii++;
					}
				}
				else if (sensor == "rain")
				{
					root["status"] = "OK";
					root["title"] = "Graph " + sensor + " " + srange;

					result = m_sql.safe_query("SELECT Total, Rate, Date FROM %s "
								  "WHERE (DeviceRowID==%" PRIu64 " AND Date>='%q' AND Date<='%q') ORDER BY Date ASC",
								  dbasetable.c_str(), idx, szDateStart.c_str(), szDateEnd.c_str());
					int ii = 0;
					if (!result.empty())
					{
						for (const auto &sd : result)
						{
							root["result"][ii]["d"] = sd[2].substr(0, 16);
							root["result"][ii]["mm"] = sd[0];
							ii++;
						}
					}
					// add today (have to calculate it)
					if (dSubType == sTypeRAINWU || dSubType == sTypeRAINByRate)
					{
						result = m_sql.safe_query("SELECT Total, Total, Rate FROM Rain WHERE (DeviceRowID==%" PRIu64 " AND Date>='%q') ORDER BY ROWID DESC LIMIT 1", idx,
									  szDateEnd.c_str());
					}
					else
					{
						result = m_sql.safe_query("SELECT MIN(Total), MAX(Total), MAX(Rate) FROM Rain WHERE (DeviceRowID==%" PRIu64 " AND Date>='%q')", idx, szDateEnd.c_str());
					}
					if (!result.empty())
					{
						std::vector<std::string> sd = result[0];

						float total_min = static_cast<float>(atof(sd[0].c_str()));
						float total_max = static_cast<float>(atof(sd[1].c_str()));
						// int rate = atoi(sd[2].c_str());

						float total_real = 0;
						if (dSubType == sTypeRAINWU || dSubType == sTypeRAINByRate)
						{
							total_real = total_max;
						}
						else
						{
							total_real = total_max - total_min;
						}
						sprintf(szTmp, "%.1f", total_real);
						root["result"][ii]["d"] = szDateEnd;
						root["result"][ii]["mm"] = szTmp;
						ii++;
					}
				}
				else if (sensor == "counter")
				{
					root["status"] = "OK";
					root["title"] = "Graph " + sensor + " " + srange;
					root["ValueQuantity"] = options["ValueQuantity"];
					root["ValueUnits"] = options["ValueUnits"];

					int ii = 0;
					if (dType == pTypeP1Power)
					{
						result = m_sql.safe_query("SELECT Value1,Value2,Value5,Value6, Date "
									  "FROM %s WHERE (DeviceRowID==%" PRIu64 " AND Date>='%q'"
									  " AND Date<='%q') ORDER BY Date ASC",
									  dbasetable.c_str(), idx, szDateStart.c_str(), szDateEnd.c_str());
						if (!result.empty())
						{
							bool bHaveDeliverd = false;
							for (const auto &sd : result)
							{
								root["result"][ii]["d"] = sd[4].substr(0, 16);

								std::string szUsage1 = sd[0];
								std::string szDeliv1 = sd[1];
								std::string szUsage2 = sd[2];
								std::string szDeliv2 = sd[3];

								float fUsage = (float)(atof(szUsage1.c_str()) + atof(szUsage2.c_str()));
								float fDeliv = (float)(atof(szDeliv1.c_str()) + atof(szDeliv2.c_str()));

								if (fDeliv != 0)
									bHaveDeliverd = true;
								sprintf(szTmp, "%.3f", fUsage / divider);
								root["result"][ii]["v"] = szTmp;
								sprintf(szTmp, "%.3f", fDeliv / divider);
								root["result"][ii]["v2"] = szTmp;
								ii++;
							}
							if (bHaveDeliverd)
							{
								root["delivered"] = true;
							}
						}
					}
					else
					{
						result = m_sql.safe_query("SELECT Value, Date FROM %s WHERE (DeviceRowID==%" PRIu64 " AND Date>='%q' AND Date<='%q') ORDER BY Date ASC",
									  dbasetable.c_str(), idx, szDateStart.c_str(), szDateEnd.c_str());
						if (!result.empty())
						{
							for (const auto &sd : result)
							{
								std::string szValue = sd[0];
								switch (metertype)
								{
									case MTYPE_ENERGY:
									case MTYPE_ENERGY_GENERATED:
										sprintf(szTmp, "%.3f", atof(szValue.c_str()) / divider);
										szValue = szTmp;
										break;
									case MTYPE_GAS:
										sprintf(szTmp, "%.2f", atof(szValue.c_str()) / divider);
										szValue = szTmp;
										break;
									case MTYPE_WATER:
										sprintf(szTmp, "%.3f", atof(szValue.c_str()) / divider);
										szValue = szTmp;
										break;
								}
								root["result"][ii]["d"] = sd[1].substr(0, 16);
								root["result"][ii]["v"] = szValue;
								ii++;
							}
						}
					}
					// add today (have to calculate it)
					if (dType == pTypeP1Power)
					{
						result = m_sql.safe_query("SELECT MIN(Value1), MAX(Value1), MIN(Value2),"
									  " MAX(Value2),MIN(Value5), MAX(Value5),"
									  " MIN(Value6), MAX(Value6) "
									  "FROM MultiMeter WHERE (DeviceRowID==%" PRIu64 " AND Date>='%q')",
									  idx, szDateEnd.c_str());
						bool bHaveDeliverd = false;
						if (!result.empty())
						{
							std::vector<std::string> sd = result[0];

							unsigned long long total_min_usage_1 = std::strtoull(sd[0].c_str(), nullptr, 10);
							unsigned long long total_max_usage_1 = std::strtoull(sd[1].c_str(), nullptr, 10);
							unsigned long long total_min_usage_2 = std::strtoull(sd[4].c_str(), nullptr, 10);
							unsigned long long total_max_usage_2 = std::strtoull(sd[5].c_str(), nullptr, 10);
							unsigned long long total_real_usage;

							unsigned long long total_min_deliv_1 = std::strtoull(sd[2].c_str(), nullptr, 10);
							unsigned long long total_max_deliv_1 = std::strtoull(sd[3].c_str(), nullptr, 10);
							unsigned long long total_min_deliv_2 = std::strtoull(sd[6].c_str(), nullptr, 10);
							unsigned long long total_max_deliv_2 = std::strtoull(sd[7].c_str(), nullptr, 10);
							unsigned long long total_real_deliv;

							total_real_usage = (total_max_usage_1 + total_max_usage_2) - (total_min_usage_1 + total_min_usage_2);
							total_real_deliv = (total_max_deliv_1 + total_max_deliv_2) - (total_min_deliv_1 + total_min_deliv_2);

							if (total_real_deliv != 0)
								bHaveDeliverd = true;

							root["result"][ii]["d"] = szDateEnd;

							sprintf(szTmp, "%lld", total_real_usage);
							std::string szValue = szTmp;
							sprintf(szTmp, "%.3f", atof(szValue.c_str()) / divider);
							root["result"][ii]["v"] = szTmp;
							sprintf(szTmp, "%llu", total_real_deliv);
							szValue = szTmp;
							sprintf(szTmp, "%.3f", atof(szValue.c_str()) / divider);
							root["result"][ii]["v2"] = szTmp;
							ii++;
							if (bHaveDeliverd)
							{
								root["delivered"] = true;
							}
						}
					}
					else if (!bIsManagedCounter)
					{ // get the first value of the day
						result = m_sql.safe_query(
							//"SELECT MIN(Value), MAX(Value) FROM Meter WHERE (DeviceRowID==%" PRIu64 " AND Date>='%q')",
							"SELECT Value FROM Meter WHERE (DeviceRowID==%" PRIu64 " AND Date>='%q') ORDER BY Date ASC LIMIT 1", idx, szDateEnd.c_str());
						if (!result.empty())
						{
							std::vector<std::string> sd = result[0];
							unsigned long long total_min = std::strtoull(sd[0].c_str(), nullptr, 10);
							unsigned long long total_max = total_min;
							unsigned long long total_real;

							// get the last value of the day
							result = m_sql.safe_query("SELECT Value FROM Meter WHERE (DeviceRowID==%" PRIu64 " AND Date>='%q') ORDER BY Date DESC LIMIT 1", idx,
										  szDateEnd.c_str());
							if (!result.empty())
							{
								std::vector<std::string> sd = result[0];
								total_max = std::strtoull(sd[0].c_str(), nullptr, 10);
							}

							total_real = total_max - total_min;
							sprintf(szTmp, "%lld", total_real);

							std::string szValue = szTmp;
							switch (metertype)
							{
								case MTYPE_ENERGY:
								case MTYPE_ENERGY_GENERATED:
									sprintf(szTmp, "%.3f", atof(szValue.c_str()) / divider);
									szValue = szTmp;
									break;
								case MTYPE_GAS:
									sprintf(szTmp, "%.2f", atof(szValue.c_str()) / divider);
									szValue = szTmp;
									break;
								case MTYPE_WATER:
									sprintf(szTmp, "%.3f", atof(szValue.c_str()) / divider);
									szValue = szTmp;
									break;
							}

							root["result"][ii]["d"] = szDateEnd;
							root["result"][ii]["v"] = szValue;
							ii++;
						}
					}
				}
				else if (sensor == "wind")
				{
					root["status"] = "OK";
					root["title"] = "Graph " + sensor + " " + srange;

					int ii = 0;

					result = m_sql.safe_query("SELECT Direction, Speed_Min, Speed_Max, Gust_Min,"
								  " Gust_Max, Date "
								  "FROM %s WHERE (DeviceRowID==%" PRIu64 " AND Date>='%q'"
								  " AND Date<='%q') ORDER BY Date ASC",
								  dbasetable.c_str(), idx, szDateStart.c_str(), szDateEnd.c_str());
					if (!result.empty())
					{
						for (const auto &sd : result)
						{
							root["result"][ii]["d"] = sd[5].substr(0, 16);
							root["result"][ii]["di"] = sd[0];

							int intSpeed = atoi(sd[2].c_str());
							int intGust = atoi(sd[4].c_str());
							if (m_sql.m_windunit != WINDUNIT_Beaufort)
							{
								sprintf(szTmp, "%.1f", float(intSpeed) * m_sql.m_windscale);
								root["result"][ii]["sp"] = szTmp;
								sprintf(szTmp, "%.1f", float(intGust) * m_sql.m_windscale);
								root["result"][ii]["gu"] = szTmp;
							}
							else
							{
								float windspeedms = float(intSpeed) * 0.1F;
								float windgustms = float(intGust) * 0.1F;
								sprintf(szTmp, "%d", MStoBeaufort(windspeedms));
								root["result"][ii]["sp"] = szTmp;
								sprintf(szTmp, "%d", MStoBeaufort(windgustms));
								root["result"][ii]["gu"] = szTmp;
							}
							ii++;
						}
					}
					// add today (have to calculate it)
					result = m_sql.safe_query("SELECT AVG(Direction), MIN(Speed), MAX(Speed), MIN(Gust), MAX(Gust) FROM Wind WHERE (DeviceRowID==%" PRIu64
								  " AND Date>='%q') ORDER BY Date ASC",
								  idx, szDateEnd.c_str());
					if (!result.empty())
					{
						std::vector<std::string> sd = result[0];

						root["result"][ii]["d"] = szDateEnd;
						root["result"][ii]["di"] = sd[0];

						int intSpeed = atoi(sd[2].c_str());
						int intGust = atoi(sd[4].c_str());
						if (m_sql.m_windunit != WINDUNIT_Beaufort)
						{
							sprintf(szTmp, "%.1f", float(intSpeed) * m_sql.m_windscale);
							root["result"][ii]["sp"] = szTmp;
							sprintf(szTmp, "%.1f", float(intGust) * m_sql.m_windscale);
							root["result"][ii]["gu"] = szTmp;
						}
						else
						{
							float windspeedms = float(intSpeed) * 0.1F;
							float windgustms = float(intGust) * 0.1F;
							sprintf(szTmp, "%d", MStoBeaufort(windspeedms));
							root["result"][ii]["sp"] = szTmp;
							sprintf(szTmp, "%d", MStoBeaufort(windgustms));
							root["result"][ii]["gu"] = szTmp;
						}
						ii++;
					}
				}
			} // custom range
		}

		/**
		 * Retrieve user session from store, without remote host.
		 */
		WebEmStoredSession CWebServer::GetSession(const std::string &sessionId)
		{
			//_log.Log(LOG_STATUS, "SessionStore : get...");
			WebEmStoredSession session;

			if (sessionId.empty())
			{
				_log.Log(LOG_ERROR, "SessionStore : cannot get session without id.");
			}
			else
			{
				std::vector<std::vector<std::string>> result;
				result = m_sql.safe_query("SELECT SessionID, Username, AuthToken, ExpirationDate FROM UserSessions WHERE SessionID = '%q'", sessionId.c_str());
				if (!result.empty())
				{
					session.id = result[0][0];
					session.username = base64_decode(result[0][1]);
					session.auth_token = result[0][2];

					std::string sExpirationDate = result[0][3];
					// time_t now = mytime(NULL);
					struct tm tExpirationDate;
					ParseSQLdatetime(session.expires, tExpirationDate, sExpirationDate);
					// RemoteHost is not used to restore the session
					// LastUpdate is not used to restore the session
				}
			}

			return session;
		}

		/*
		 * Takes root["result"] and groups all items according to sgroupby, summing all values for each category, then creating new items in root["result"]
		 * for each combination year/category.
		 */
		void CWebServer::GroupBy(Json::Value &root, std::string dbasetable, uint64_t idx, std::string sgroupby, std::function<std::string(std::string)> counter,
					 std::function<std::string(std::string)> value, std::function<std::string(double)> sumToResult)
		{
			/*
			 * This query selects all records (in mc0) that belong to DeviceRowID, each with the record before it (in mc1), and calculates for each record
			 * the "usage" by subtracting the previous counter from its counter.
			 * - It does not take into account records that have a 0-valued counter, to prevent one falling between two categories, which would cause the
             *   value for one category to be extremely low and the value for the other extremely high.
			 * - When the previous counter is greater than its counter, assumed is that a meter change has taken place; the previous counter is ignored
			 *   and the value of the record is taken as the "usage" (hoping for the best as the value is not always reliable.)
			 * - The reason why not simply the record values are summed, but instead the differences between all the individual counters are summed, is that
			 *   records for some days are not recorded or sometimes disappear, hence values would be missing and that would result in an incomplete total.
			 *   Plus it seems that the value is not always the same as the difference between the counters. Counters are more often reliable.
			 */
			std::string queryString;
            queryString.append(" select");
            queryString.append("  strftime('%%Y',Date) as Year,");
            queryString.append("  sum(Difference) as Sum");
            if (sgroupby == "quarter")
            {
                queryString.append(",case");
                queryString.append("   when cast(strftime('%%m',Date) as integer) between 1 and 3 then 'Q1'");
                queryString.append("   when cast(strftime('%%m',Date) as integer) between 4 and 6 then 'Q2'");
                queryString.append("   when cast(strftime('%%m',Date) as integer) between 7 and 9 then 'Q3'");
                queryString.append("                                                              else 'Q4'");
                queryString.append("   end as Quarter");
            }
            else if (sgroupby == "month")
            {
                queryString.append(",strftime('%%m',Date) as Month");
            }
            queryString.append(" from (");
            queryString.append(" 	select");
            queryString.append("         mc0.DeviceRowID,");
            queryString.append("         date(mc0.Date) as Date,");
            queryString.append("         case");
            queryString.append("            when (" + counter("mc1") + ") <= (" + counter("mc0") + ")");
            queryString.append("            then (" + counter("mc0") + ") - (" + counter("mc1") + ")");
            queryString.append("            else (" + value("mc0") + ")");
            queryString.append("         end as Difference");
            queryString.append(" 	from " + dbasetable + " mc0");
            queryString.append(" 	inner join " + dbasetable + " mc1 on mc1.DeviceRowID = mc0.DeviceRowID");
            queryString.append("         and mc1.Date = (");
            queryString.append("             select max(mcm.Date)");
            queryString.append("             from " + dbasetable + " mcm");
            queryString.append("             where mcm.DeviceRowID = mc0.DeviceRowID and mcm.Date < mc0.Date and (" + counter("mcm") + ") <> 0");
            queryString.append("         )");
            queryString.append(" 	where");
            queryString.append("         mc0.DeviceRowID = %" PRIu64 "");
            queryString.append("         and ("+counter("mc0")+") <> 0");
            queryString.append("         and (select min(Date) from " + dbasetable + " where DeviceRowID = %" PRIu64 " and (" + counter("") + ") <> 0) <= mc1.Date");
            queryString.append("         and mc0.Date <= (select max(Date) from " + dbasetable + " where DeviceRowID = %" PRIu64 " and (" + counter("") + ") <> 0)");
            queryString.append("    union all");
            queryString.append("    select");
            queryString.append("         DeviceRowID,");
            queryString.append("         date(Date) as Date,");
            queryString.append("         " + value(""));
            queryString.append(" 	from " + dbasetable);
            queryString.append(" 	where");
            queryString.append("         DeviceRowID = %" PRIu64 "");
            queryString.append("         and (select min(Date) from " + dbasetable + " where DeviceRowID = %" PRIu64 " and (" + counter("") + ") <> 0) = Date");
            queryString.append(" )");
            queryString.append(" group by strftime('%%Y',Date)");
            if (sgroupby == "quarter")
            {
                queryString.append(",case");
                queryString.append("   when cast(strftime('%%m',Date) as integer) between 1 and 3 then 'Q1'");
                queryString.append("   when cast(strftime('%%m',Date) as integer) between 4 and 6 then 'Q2'");
                queryString.append("   when cast(strftime('%%m',Date) as integer) between 7 and 9 then 'Q3'");
                queryString.append("                                                              else 'Q4'");
                queryString.append("   end");
            }
            else if (sgroupby == "month")
            {
                queryString.append(",strftime('%%m',Date)");
            }
			std::vector<std::vector<std::string>> result = m_sql.safe_query(queryString.c_str(), idx, idx, idx, idx, idx);
			if (!result.empty())
			{
				int firstYearCounting = 0;
				double fsumPrevious;
				for (const auto &sd : result)
				{
					const int year = atoi(sd[0].c_str());
					const double fsum = atof(sd[1].c_str());
					const char *trend = firstYearCounting == 0 ? "" : fsumPrevious < fsum ? "up" : fsumPrevious > fsum ? "down" : "equal";
					const int ii = root["result"].size();
					if (firstYearCounting == 0 || year < firstYearCounting)
					{
						firstYearCounting = year;
					}
					root["result"][ii]["y"] = sd[0];
					root["result"][ii]["c"] = sgroupby == "year" ? sd[0] : sd[2];
					root["result"][ii]["s"] = sumToResult(fsum);
					root["result"][ii]["t"] = trend;
					fsumPrevious = fsum;
				}
				root["firstYear"] = firstYearCounting;
			}
		}

		/*
		 * Adds todayValue to root["result"], either by adding it to the value of the item with the corresponding category or by adding a new item with the
		 * respective category with todayValue. If root["firstYear"] is missing, the today's year is set in it's place.
		 */
		void CWebServer::AddTodayValueToResult(Json::Value &root, std::string sgroupby, std::string today, float todayValue, std::string formatString)
		{
			std::string todayYear = today.substr(0, 4);
			std::string todayCategory;
			if (sgroupby == "quarter")
			{
				int todayMonth = atoi(today.substr(5, 2).c_str());
				if (todayMonth < 4)
					todayCategory = "Q1";
				else if (todayMonth < 7)
					todayCategory = "Q2";
				else if (todayMonth < 10)
					todayCategory = "Q3";
				else
					todayCategory = "Q4";
			}
			else if (sgroupby == "month")
			{
				todayCategory = today.substr(5, 2);
			}
			else
			{
				todayCategory = todayYear;
			}
			int todayResultIndex = -1;
			for (int resultIndex = 0; resultIndex < static_cast<int>(root["result"].size()) && todayResultIndex == -1; resultIndex++)
			{
				std::string resultYear = root["result"][resultIndex]["y"].asString();
				std::string resultCategory = root["result"][resultIndex]["c"].asString();
				if (resultYear == todayYear && todayCategory == resultCategory)
				{
					todayResultIndex = resultIndex;
				}
			}
			float resultPlusTodayValue{};
			if (todayResultIndex == -1)
			{
				todayResultIndex = root["result"].size();
				resultPlusTodayValue = todayValue;
				root["result"][todayResultIndex]["y"] = todayYear.c_str();
				root["result"][todayResultIndex]["c"] = todayCategory.c_str();
			}
			else
			{
				resultPlusTodayValue = static_cast<float>(atof(root["result"][todayResultIndex]["s"].asString().c_str())) + todayValue;
			}
			char szTmp[30];
			sprintf(szTmp, formatString.c_str(), resultPlusTodayValue);
			root["result"][todayResultIndex]["s"] = szTmp;

            if (!root.isMember("firstYear")) {
                root["firstYear"] = todayYear.c_str();
            }
        }

		/**
		 * Save user session.
		 */
		void CWebServer::StoreSession(const WebEmStoredSession &session)
		{
			//_log.Log(LOG_STATUS, "SessionStore : store...");
			if (session.id.empty())
			{
				_log.Log(LOG_ERROR, "SessionStore : cannot store session without id.");
				return;
			}

			char szExpires[30];
			struct tm ltime;
			localtime_r(&session.expires, &ltime);
			strftime(szExpires, sizeof(szExpires), "%Y-%m-%d %H:%M:%S", &ltime);

			std::string remote_host = (session.remote_host.size() <= 50) ? // IPv4 : 15, IPv6 : (39|45)
							  session.remote_host
										     : session.remote_host.substr(0, 50);

			WebEmStoredSession storedSession = GetSession(session.id);
			if (storedSession.id.empty())
			{
				m_sql.safe_query("INSERT INTO UserSessions (SessionID, Username, AuthToken, ExpirationDate, RemoteHost) VALUES ('%q', '%q', '%q', '%q', '%q')", session.id.c_str(),
						 base64_encode(session.username).c_str(), session.auth_token.c_str(), szExpires, remote_host.c_str());
			}
			else
			{
				m_sql.safe_query("UPDATE UserSessions set AuthToken = '%q', ExpirationDate = '%q', RemoteHost = '%q', LastUpdate = datetime('now', 'localtime') WHERE SessionID = '%q'",
						 session.auth_token.c_str(), szExpires, remote_host.c_str(), session.id.c_str());
			}
		}

		/**
		 * Remove user session and expired sessions.
		 */
		void CWebServer::RemoveSession(const std::string &sessionId)
		{
			//_log.Log(LOG_STATUS, "SessionStore : remove...");
			if (sessionId.empty())
			{
				return;
			}
			m_sql.safe_query("DELETE FROM UserSessions WHERE SessionID = '%q'", sessionId.c_str());
		}

		/**
		 * Remove all expired user sessions.
		 */
		void CWebServer::CleanSessions()
		{
			//_log.Log(LOG_STATUS, "SessionStore : clean...");
			m_sql.safe_query("DELETE FROM UserSessions WHERE ExpirationDate < datetime('now', 'localtime')");
		}

		/**
		 * Delete all user's session, except the session used to modify the username or password.
		 * username must have been hashed
		 *
		 * Note : on the WebUserName modification, this method will not delete the session, but the session will be deleted anyway
		 * because the username will be unknown (see cWebemRequestHandler::checkAuthToken).
		 */
		void CWebServer::RemoveUsersSessions(const std::string &username, const WebEmSession &exceptSession)
		{
			m_sql.safe_query("DELETE FROM UserSessions WHERE (Username=='%q') and (SessionID!='%q')", username.c_str(), exceptSession.id.c_str());
		}

	} // namespace server
} // namespace http
