diff options
author | Christian Völlinger <zerov83@gmail.com> | 2013-12-02 19:57:54 +0100 |
---|---|---|
committer | Christian Völlinger <zerov83@gmail.com> | 2013-12-02 19:57:54 +0100 |
commit | 47e367a8524a09dbdce8016296a611674e536225 (patch) | |
tree | c435f431a4964e1f8e8d4d54032c31c3f8ca2120 /lib/boblight_client.cpp | |
parent | 620ea3a800f2235abf45a57ca898bf96473c576b (diff) | |
download | vdr-plugin-boblight-47e367a8524a09dbdce8016296a611674e536225.tar.gz vdr-plugin-boblight-47e367a8524a09dbdce8016296a611674e536225.tar.bz2 |
Initial commit0.0.1
Diffstat (limited to 'lib/boblight_client.cpp')
-rw-r--r-- | lib/boblight_client.cpp | 704 |
1 files changed, 704 insertions, 0 deletions
diff --git a/lib/boblight_client.cpp b/lib/boblight_client.cpp new file mode 100644 index 0000000..dfab262 --- /dev/null +++ b/lib/boblight_client.cpp @@ -0,0 +1,704 @@ +/* + * boblight + * Copyright (C) Bob 2009 + * + * boblight is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * boblight is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "util/inclstdint.h" + +#include <string> +#include <iostream> //debug +#include <sstream> + +#include "boblight_client.h" +#include "util/misc.h" +#include "util/timeutils.h" +#include "protocolversion.h" + +using namespace std; +using namespace boblight; + +#define GAMMASIZE (sizeof(m_gammacurve) / sizeof(m_gammacurve[0])) + +CLight::CLight() +{ + #define BOBLIGHT_OPTION(name, type, min, max, default, variable, postprocess) variable = default; + #include "options.h" + #undef BOBLIGHT_OPTION + + m_singlechange = 0.0; + + m_width = -1; + m_height = -1; + + memset(m_rgb, 0, sizeof(m_rgb)); + m_rgbcount = 0; + memset(m_prevrgb, 0, sizeof(m_prevrgb)); + memset(m_hscanscaled, 0, sizeof(m_hscanscaled)); + memset(m_vscanscaled, 0, sizeof(m_vscanscaled)); + + for (int i = 0; i < GAMMASIZE; i++) + m_gammacurve[i] = i; +} + +string CLight::SetOption(const char* option, bool& send) +{ + string stroption = option; + string strname; + + send = false; + + if (!GetWord(stroption, strname)) + return "emtpy option"; //string with only whitespace + + #define BOBLIGHT_OPTION(name, type, min, max, default, variable, postprocess) \ + if (strname == #name) \ + { \ + type value; \ + if (#type == "bool")\ + {\ + if (!StrToBool(stroption, *(bool*)(&value)))\ + return "invalid value " + stroption + " for option " + strname + " with type " + #type; \ + }\ + else\ + {\ + stringstream stream; \ + stream << stroption; \ + stream >> value; \ + if (stream.fail()) return "invalid value " + stroption + " for option " + strname + " with type " + #type; \ + \ + }\ + variable = value; \ + postprocess\ + \ + return ""; \ + } + #include "options.h" + #undef BOBLIGHT_OPTION + + return "unknown option " + strname; +} + +std::string CLight::GetOption(const char* option, std::string& output) +{ + string stroption = option; + string strname; + + if (!GetWord(stroption, strname)) + return "emtpy option"; //string with only whitespace + + #define BOBLIGHT_OPTION(name, type, min, max, default, variable, postprocess) \ + if (#name == strname)\ + {\ + output = ToString(variable);\ + return "";\ + } + #include "options.h" + #undef BOBLIGHT_OPTION + + return "unknown option"; +} + +void CLight::AddPixel(int* rgb) +{ + if (rgb[0] >= m_threshold || rgb[1] >= m_threshold || rgb[2] >= m_threshold) + { + if (m_gamma == 1.0) + { + m_rgb[0] += Clamp(rgb[0], 0, 255); + m_rgb[1] += Clamp(rgb[1], 0, 255); + m_rgb[2] += Clamp(rgb[2], 0, 255); + } + else + { + m_rgb[0] += m_gammacurve[Clamp(rgb[0], 0, GAMMASIZE - 1)]; + m_rgb[1] += m_gammacurve[Clamp(rgb[1], 0, GAMMASIZE - 1)]; + m_rgb[2] += m_gammacurve[Clamp(rgb[2], 0, GAMMASIZE - 1)]; + } + } + m_rgbcount++; +} + +void CLight::GetRGB(float* rgb) +{ + //if no pixels are set, the denominator is 0, so just return black + if (m_rgbcount == 0) + { + for (int i = 0; i < 3; i++) + { + rgb[i] = 0.0f; + m_rgb[i] = 0.0f; + } + + return; + } + + //convert from numerator/denominator to float + for (int i = 0; i < 3; i++) + { + rgb[i] = Clamp(m_rgb[i] / (float)m_rgbcount / 255.0f, 0.0f, 1.0f); + m_rgb[i] = 0.0f; + } + m_rgbcount = 0; + + //this tries to set the speed based on how fast the input is changing + //it needs sync mode to work properly + if (m_autospeed > 0.0) + { + float change = Abs(rgb[0] - m_prevrgb[0]) + Abs(rgb[1] - m_prevrgb[1]) + Abs(rgb[2] - m_prevrgb[2]); + change /= 3.0; + + //only apply singlechange if it's large enough, otherwise we risk sending it continously + if (change > 0.001) + m_singlechange = Clamp(change * m_autospeed / 10.0, 0.0, 1.0); + else + m_singlechange = 0.0; + } + + memcpy(m_prevrgb, rgb, sizeof(m_prevrgb)); + + //we need some hsv adjustments + if (m_value != 1.0 || m_valuerange[0] != 0.0 || m_valuerange[1] != 1.0 || + m_saturation != 1.0 || m_satrange[0] != 0.0 || m_satrange[1] != 1.0) + { + //rgb - hsv conversion, thanks wikipedia! + float hsv[3]; + float max = Max(rgb[0], rgb[1], rgb[2]); + float min = Min(rgb[0], rgb[1], rgb[2]); + + if (min == max) //grayscale + { + hsv[0] = -1.0f; //undefined + hsv[1] = 0.0; //no saturation + hsv[2] = min; //value + } + else + { + if (max == rgb[0]) //red zone + { + hsv[0] = (60.0f * ((rgb[1] - rgb[2]) / (max - min)) + 360.0f); + while (hsv[0] >= 360.0f) + hsv[0] -= 360.0f; + } + else if (max == rgb[1]) //green zone + { + hsv[0] = 60.0f * ((rgb[2] - rgb[0]) / (max - min)) + 120.0f; + } + else if (max == rgb[2]) //blue zone + { + hsv[0] = 60.0f * ((rgb[0] - rgb[1]) / (max - min)) + 240.0f; + } + + hsv[1] = (max - min) / max; //saturation + hsv[2] = max; //value + } + + //saturation and value adjustment + hsv[1] = Clamp(hsv[1] * m_saturation, m_satrange[0], m_satrange[1]); + hsv[2] = Clamp(hsv[2] * m_value, m_valuerange[0], m_valuerange[1]); + + if (hsv[0] == -1.0f) //grayscale + { + for (int i = 0; i < 3; i++) + rgb[i] = hsv[2]; + } + else + { + int hi = (int)(hsv[0] / 60.0f) % 6; + float f = (hsv[0] / 60.0f) - (float)(int)(hsv[0] / 60.0f); + + float s = hsv[1]; + float v = hsv[2]; + float p = v * (1.0f - s); + float q = v * (1.0f - f * s); + float t = v * (1.0f - (1.0f - f) * s); + + if (hi == 0) + { rgb[0] = v; rgb[1] = t; rgb[2] = p; } + else if (hi == 1) + { rgb[0] = q; rgb[1] = v; rgb[2] = p; } + else if (hi == 2) + { rgb[0] = p; rgb[1] = v; rgb[2] = t; } + else if (hi == 3) + { rgb[0] = p; rgb[1] = q; rgb[2] = v; } + else if (hi == 4) + { rgb[0] = t; rgb[1] = p; rgb[2] = v; } + else if (hi == 5) + { rgb[0] = v; rgb[1] = p; rgb[2] = q; } + } + + for (int i = 0; i < 3; i++) + rgb[i] = Clamp(rgb[i], 0.0f, 1.0f); + } +} + +//scale the light's scanrange to the dimensions set with boblight_setscanrange() +void CLight::SetScanRange(int width, int height) +{ + m_width = width; + m_height = height; + + m_hscanscaled[0] = Round32(m_hscan[0] / 100.0 * ((float)width - 1)); + m_hscanscaled[1] = Round32(m_hscan[1] / 100.0 * ((float)width - 1)); + m_vscanscaled[0] = Round32(m_vscan[0] / 100.0 * ((float)height - 1)); + m_vscanscaled[1] = Round32(m_vscan[1] / 100.0 * ((float)height - 1)); +} + +int CBoblight::Connect(const char* address, int port, int usectimeout) +{ + CMessage message; + CTcpData data; + int64_t now; + int64_t target; + string word; + + //set address + m_usectimeout = usectimeout; + if (address) + m_address = address; + else + m_address = "127.0.0.1"; + + //set port + if (port >= 0) + m_port = port; + else + m_port = 19333; + + //try to open a tcp connection + if (m_socket.Open(m_address, m_port, m_usectimeout) != SUCCESS) + { + m_error = m_socket.GetError(); + return 0; + } + + //write hello to the server, we should get hello back + if (!WriteDataToSocket("hello\n")) + return 0; + + if (!ReadDataToQueue()) + return 0; + + message = m_messagequeue.GetMessage(); + if (!ParseWord(message, "hello")) + { + m_error = m_address + ":" + ToString(m_port) + " sent gibberish"; + return 0; + } + + //get the protocol version from the server + if (!WriteDataToSocket("get version\n")) + return 0; + + if (!ReadDataToQueue()) + return 0; + + message = m_messagequeue.GetMessage(); + + if (!ParseWord(message, "version") || !GetWord(message.message, word)) + { + m_error = m_address + ":" + ToString(m_port) + " sent gibberish"; + return 0; + } + + //if we don't get the same protocol version back as we have, we can't work together + if (word != PROTOCOLVERSION) + { + m_error = "version mismatch, " + m_address + ":" + ToString(m_port) + " has version \"" + word + + "\", libboblight has version \"" + PROTOCOLVERSION + "\""; + return 0; + } + + //get lights info, like number, names and area + if (!WriteDataToSocket("get lights\n")) + return 0; + + if (!ReadDataToQueue()) + return 0; + + message = m_messagequeue.GetMessage(); + if (!ParseLights(message)) + { + m_error = m_address + ":" + ToString(m_port) + " sent gibberish"; + return 0; + } + + return 1; +} + +CBoblight::CBoblight() +{ + int padsize = 1; + //get option name pad size so it looks pretty + #define BOBLIGHT_OPTION(name, type, min, max, default, variable, postprocess) \ + if (strlen(#name) + 1 > padsize)\ + padsize = strlen(#name) + 1; + #include "options.h" + #undef BOBLIGHT_OPTION + + //stick in a line that describes the options + string option = "name"; + option.append(Max(padsize - option.length(), 1), ' '); + option += "type min max default"; + m_options.push_back(option); + + //fill vector with option strings + #define BOBLIGHT_OPTION(name, type, min, max, default, variable, postprocess) \ + {\ + string option = #name;\ + option.append(padsize - strlen(#name), ' ');\ + \ + option += #type;\ + option.append(Max(8 - strlen(#type), 1), ' ');\ + \ + option += #min;\ + option.append(Max(8 - strlen(#min), 1), ' ');\ + \ + option += #max;\ + option.append(Max(8 - strlen(#max), 1), ' ');\ + \ + if (strcmp(#default, "-1.0") == 0)\ + option += "set by boblightd";\ + else\ + option += #default;\ + \ + m_options.push_back(option);\ + } + #include "options.h" + #undef BOBLIGHT_OPTION +} + +//reads from socket until timeout or one message has arrived +bool CBoblight::ReadDataToQueue() +{ + CTcpData data; + int64_t now = GetTimeUs(); + int64_t target = now + m_usectimeout; + int nrmessages = m_messagequeue.GetNrMessages(); + + while (now < target && m_messagequeue.GetNrMessages() == nrmessages) + { + if (m_socket.Read(data) != SUCCESS) + { + m_error = m_socket.GetError(); + return false; + } + + m_messagequeue.AddData(data.GetData()); + + if (m_messagequeue.GetRemainingDataSize() > MAXDATA) + { + m_error = m_address + ":" + ToString(m_port) + " sent too much data"; + return false; + } + + now = GetTimeUs(); + } + + if (nrmessages == m_messagequeue.GetNrMessages()) + { + m_error = m_address + ":" + ToString(m_port) + " read timed out"; + return false; + } + return true; +} + +bool CBoblight::WriteDataToSocket(std::string strdata) +{ + CTcpData data; + data.SetData(strdata); + + if (m_socket.Write(data) != SUCCESS) + { + m_error = m_socket.GetError(); + return false; + } + + return true; +} + +//removes one word from the string in the messages, and compares it to wordtocmp +bool CBoblight::ParseWord(CMessage& message, std::string wordtocmp) +{ + string readword; + if (!GetWord(message.message, readword) || readword != wordtocmp) + return false; + + return true; +} + +bool CBoblight::ParseLights(CMessage& message) +{ + string word; + int nrlights; + + //first word in the message is "lights", second word is the number of lights + if (!ParseWord(message, "lights") || !GetWord(message.message, word) || !StrToInt(word, nrlights) || nrlights < 1) + return false; + + for (int i = 0; i < nrlights; i++) + { + CLight light; + + //read some data to the message queue if we have no messages + if (m_messagequeue.GetNrMessages() == 0) + { + if (!ReadDataToQueue()) + return false; + } + + message = m_messagequeue.GetMessage(); + + //first word sent is "light, second one is the name + if (!ParseWord(message, "light") || !GetWord(message.message, light.m_name)) + { + return false; + } + + //third one is "scan" + if (!ParseWord(message, "scan")) + return false; + + //now we read the scanrange + string scanarea; + for (int i = 0; i < 4; i++) + { + if (!GetWord(message.message, word)) + return false; + + scanarea += word + " "; + } + + ConvertFloatLocale(scanarea); //workaround for locale mismatch (, and .) + + if (sscanf(scanarea.c_str(), "%f %f %f %f", light.m_vscan, light.m_vscan + 1, light.m_hscan, light.m_hscan + 1) != 4) + return false; + + m_lights.push_back(light); + } + return true; +} + +const char* CBoblight::GetLightName(int lightnr) +{ + if (lightnr < 0) //negative lights don't exist, so we set it to an invalid number to get the error message + lightnr = m_lights.size(); + + if (CheckLightExists(lightnr)) + return m_lights[lightnr].m_name.c_str(); + + return NULL; +} + +int CBoblight::SetPriority(int priority) +{ + string data = "set priority " + ToString(priority) + "\n"; + + return WriteDataToSocket(data); +} + +bool CBoblight::CheckLightExists(int lightnr, bool printerror /*= true*/) +{ + if (lightnr >= (int)m_lights.size()) + { + if (printerror) + { + m_error = "light " + ToString(lightnr) + " doesn't exist (have " + ToString(m_lights.size()) + " lights)"; + } + return false; + } + return true; +} + +void CBoblight::SetScanRange(int width, int height) +{ + for (int i = 0; i < m_lights.size(); i++) + { + m_lights[i].SetScanRange(width, height); + } +} + +int CBoblight::AddPixel(int lightnr, int* rgb) +{ + if (!CheckLightExists(lightnr)) + return 0; + + if (lightnr < 0) + { + for (int i = 0; i < m_lights.size(); i++) + m_lights[i].AddPixel(rgb); + } + else + { + m_lights[lightnr].AddPixel(rgb); + } + + return 1; +} + +void CBoblight::AddPixel(int* rgb, int x, int y) +{ + for (int i = 0; i < m_lights.size(); i++) + { + if (x >= m_lights[i].m_hscanscaled[0] && x <= m_lights[i].m_hscanscaled[1] && + y >= m_lights[i].m_vscanscaled[0] && y <= m_lights[i].m_vscanscaled[1]) + { + m_lights[i].AddPixel(rgb); + } + } +} + +int CBoblight::SendRGB(int sync, int* outputused) +{ + string data; + + for (int i = 0; i < m_lights.size(); i++) + { + float rgb[3]; + m_lights[i].GetRGB(rgb); + data += "set light " + m_lights[i].m_name + " rgb " + ToString(rgb[0]) + " " + ToString(rgb[1]) + " " + ToString(rgb[2]) + "\n"; + if (m_lights[i].m_autospeed > 0.0 && m_lights[i].m_singlechange > 0.0) + data += "set light " + m_lights[i].m_name + " singlechange " + ToString(m_lights[i].m_singlechange) + "\n"; + } + + //send a message that we want devices to sync to our input + if (sync) + data += "sync\n"; + + //if we want to check if our output is used, send a ping message + if (outputused) + data += "ping\n"; + + if (!WriteDataToSocket(data)) + return 0; + + if (outputused) + return Ping(outputused, false); + else + return 1; +} + +int CBoblight::Ping(int* outputused, bool send) +{ + string word; + + if (send) + { + if (!WriteDataToSocket("ping\n")) + return 0; + } + + if (!ReadDataToQueue()) + return 0; + + CMessage message = m_messagequeue.GetMessage(); + + if (!GetWord(message.message, word) || word != "ping") + { + m_error = m_address + ":" + ToString(m_port) + " sent gibberish"; + return 0; + } + + //client can set outputused to NULL + if (outputused) + { + if (!GetWord(message.message, word) || !StrToInt(word, *outputused)) + { + m_error = m_address + ":" + ToString(m_port) + " sent gibberish"; + return 0; + } + } + + return 1; +} + +int CBoblight::GetNrOptions() +{ + return m_options.size(); +} + +const char* CBoblight::GetOptionDescription(int option) +{ + if (option < 0 || option >= m_options.size()) + return NULL; + + return m_options[option].c_str(); +} + +int CBoblight::SetOption(int lightnr, const char* option) +{ + string error; + string data; + bool send; + + if (!CheckLightExists(lightnr)) + return 0; + + if (lightnr < 0) + { + for (int i = 0; i < m_lights.size(); i++) + { + error = m_lights[i].SetOption(option, send); + if (!error.empty()) + { + m_error = error; + return 0; + } + if (send) + { + data += "set light " + m_lights[i].m_name + " " + option + "\n"; + } + } + } + else + { + error = m_lights[lightnr].SetOption(option, send); + if (!error.empty()) + { + m_error = error; + return 0; + } + if (send) + { + data += "set light " + m_lights[lightnr].m_name + " " + option + "\n"; + } + } + + if (!WriteDataToSocket(data)) + return 0; + + return 1; +} + +int CBoblight::GetOption(int lightnr, const char* option, const char** output) +{ + if (lightnr < 0) //negative lights don't exist, so we set it to an invalid number to get the error message + lightnr = m_lights.size(); + + if (!CheckLightExists(lightnr)) + return 0; + + string error = m_lights[lightnr].GetOption(option, m_lastoption); + if (!error.empty()) + { + m_error = error; + return 0; + } + + *output = m_lastoption.c_str(); + + return 1; +} |