|
solarpowerlog trunk
|
00001 /* ---------------------------------------------------------------------------- 00002 solarpowerlog 00003 Copyright (C) 2009 Tobias Frost 00004 00005 This file is part of solarpowerlog. 00006 00007 Solarpowerlog is free software; However, it is dual-licenced 00008 as described in the file "COPYING". 00009 00010 For this file (CInverterSputnikSSeries.cpp), the license terms are: 00011 00012 You can redistribute it and/or modify it under the terms of the GNU 00013 General Public License as published by the Free Software Foundation; either 00014 version 3 of the License, or (at your option) any later version. 00015 00016 This program is distributed in the hope that it will be useful, but 00017 WITHOUT ANY WARRANTY; without even the implied warranty of 00018 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00019 Lesser General Public License for more details. 00020 00021 You should have received a copy of the GNU Library General Public 00022 License along with this proramm; if not, see 00023 <http://www.gnu.org/licenses/>. 00024 ---------------------------------------------------------------------------- 00025 */ 00026 00027 // HOW TO HANLDE MULTI-PHASE UNITS 00028 // not implemented, but some ideas: 00029 // -- for all parameters, which are dependend on the phase, 00030 // create extra parameters which the appendix _L[1-3] 00031 // -- create a filter, a "multi-phase-data-splitter", and connect it 00032 // to the inverter. 00033 // -- it will search all capabilities for the phase variants and, if found, 00034 // it will locally "rename" the capability for the following filters and displays. 00035 // --> The phases will be transformed to new data source, like a virtual inverter. 00036 00043 #ifdef HAVE_CONFIG_H 00044 #include "config.h" 00045 #endif 00046 00047 #ifdef HAVE_INV_SPUTNIK 00048 00049 #include "configuration/Registry.h" 00050 #include "configuration/CConfigHelper.h" 00051 00052 #include "CInverterSputnikSSeries.h" 00053 00054 #include <libconfig.h++> 00055 #include <iostream> 00056 #include <sstream> 00057 #include <cstring> 00058 00059 #include "patterns/ICommand.h" 00060 00061 #include "interfaces/CWorkScheduler.h" 00062 00063 #include "Inverters/Capabilites.h" 00064 #include "patterns/CValue.h" 00065 00066 #include "configuration/ILogger.h" 00067 00068 #include <cstring> 00069 00070 using namespace libconfig; 00071 00072 static struct 00073 { 00074 unsigned int typ; 00075 const char *description; 00076 } 00077 model_lookup[] = { 00078 { 2001, "SolarMax 2000 E" }, 00079 { 3001, "SolarMax 3000 E" }, 00080 { 4000, "SolarMax 4000 E" }, 00081 { 6000, "SolarMax 6000 E" }, 00082 { 2010, "SolarMax 2000 C" }, 00083 { 3010, "SolarMax 3000 C" }, 00084 { 4010, "SolarMax 4000 C" }, 00085 { 4200, "SolarMax 4200 C" }, 00086 { 6010, "SolarMax 6000 C" }, 00087 { 20010, "SolarMax 2000 S" }, 00088 { 20020, "SolarMax 3000 S" }, 00089 { 20030, "SolarMax 4200 S" }, 00090 { 20040, "SolarMax 6000 S" }, 00091 { 20, "SolarMax 20 C" }, 00092 { 25, "SolarMax 25 C" }, 00093 { 30, "SolarMax 30 C" }, 00094 { 35, "SolarMax 35 C" }, 00095 { 50, "SolarMax 50 C" }, 00096 { 80, "SolarMax 80 C" }, 00097 { 100, "SolarMax 100 C" }, 00098 { 300, "SolarMax 300 C" }, 00099 { 00100 -1, 00101 "UKNOWN MODEL. PLEASE FILE A BUG WITH THE REPORTED ID, ALONG WITH ALL INFOS YOU HAVE" } }; 00102 00103 static struct 00104 { 00105 unsigned int code; 00106 enum InverterStatusCodes status; 00107 const char *description; 00108 } 00109 statuscodes[] = { 00110 { 20002, NOT_FEEDING_OK, "Solar radiation too low" }, 00111 { 20003, NOT_FEEDING_OK, "Inverter Starting up" }, 00112 { 20004, FEEDING_MPP, "Feeding on MPP" }, 00113 00114 { 20006, FEEDING_MAXPOWER, "Feeding. Inverter at power limit" }, 00115 { 20008, FEEDING, "Feeding" }, 00116 00117 { 20115, NOT_FEEDING_EXTEVENT, "Off-grid" }, 00118 { 20116, NOT_FEEDING_EXTEVENT, "Grid Frequency too high" }, 00119 { 20117, NOT_FEEDING_EXTEVENT, "Grid Frequency too low" }, 00120 00121 { 00122 -1, 00123 STATUS_UNAVAILABLE, 00124 "Unknown Statuscode -- PLEASE FILE A BUG WITH AS MUCH INFOS AS YOU CAN FIND OUT -- BEST, READ THE DISPLAY OF THE INVERTER." }, }; 00125 00126 using namespace std; 00127 00128 CInverterSputnikSSeries::CInverterSputnikSSeries(const string &name, 00129 const string & configurationpath) : 00130 IInverterBase::IInverterBase(name, configurationpath, "inverter") 00131 { 00132 00133 swversion = swbuild = 0; 00134 ownadr = 0xfb; 00135 commadr = 0x01; 00136 laststatuscode = (unsigned int) -1; 00137 errcnt_ = 0; 00138 00139 // Add the capabilites that this inverter has 00140 // Note: The "must-have" ones CAPA_CAPAS_REMOVEALL and CAPA_CAPAS_UPDATED are already instanciated by the base class constructor. 00141 // Note2: You also can add capabilites as soon you know them (runtime detection) 00142 00143 string s; 00144 IValue *v; 00145 CCapability *c; 00146 s = CAPA_INVERTER_MANUFACTOR_NAME; 00147 v = IValue::Factory(CAPA_INVERTER_MANUFACTOR_TYPE); 00148 ((CValue<string>*) v)->Set("Sputnik Engineering"); 00149 c = new CCapability(s, v, this); 00150 AddCapability(s, c); 00151 00152 // Add the request to initialize as soon as the system is up. 00153 ICommand *cmd = new ICommand(CMD_INIT, this); 00154 Registry::GetMainScheduler()->ScheduleWork(cmd); 00155 00156 CConfigHelper cfghlp(configurationpath); 00157 float interval; 00158 cfghlp.GetConfig("queryinterval", interval, 5.0f); 00159 00160 // Query settings needed and default all optional settings. 00161 cfghlp.GetConfig("ownadr", ownadr, 0xFBu); 00162 cfghlp.GetConfig("commadr", commadr, 0x01u); 00163 00164 s = CAPA_INVERTER_QUERYINTERVAL; 00165 v = IValue::Factory(CAPA_INVERTER_QUERYINTERVAL_TYPE); 00166 ((CValue<float>*) v)->Set(interval); 00167 c = new CCapability(s, v, this); 00168 AddCapability(s, c); 00169 00170 s = CAPA_INVERTER_CONFIGNAME; 00171 v = IValue::Factory(CAPA_INVERTER_CONFIGNAME_TYPE); 00172 ((CValue<std::string>*) v)->Set(name); 00173 c = new CCapability(s, v, this); 00174 AddCapability(s, c); 00175 00176 LOGDEBUG(logger,"Inverter configuration:"); 00177 LOGDEBUG(logger,"class CInverterSputnikSSeries "); 00178 LOGDEBUG(logger,"Query Interval: "<< interval); 00179 LOGDEBUG(logger,"Ownadr: " << ownadr << " Commadr: " << commadr); 00180 cfghlp.GetConfig("comms", s, (string) "unset"); 00181 LOGDEBUG(logger,"Communication: " << s); 00182 00183 } 00184 00185 CInverterSputnikSSeries::~CInverterSputnikSSeries() 00186 { 00187 // TODO Auto-generated destructor stub 00188 } 00189 00190 bool CInverterSputnikSSeries::CheckConfig() 00191 { 00192 string setting; 00193 string str; 00194 00195 bool fail = false; 00196 00197 CConfigHelper hlp(configurationpath); 00198 fail |= (true != hlp.CheckConfig("comms", Setting::TypeString)); 00199 // Note: Queryinterval is optional. But CConfigHelper handle also opt. 00200 // parameters and checks for type. 00201 fail 00202 |= (true != hlp.CheckConfig("queryinterval", Setting::TypeFloat, 00203 true)); 00204 fail |= (true != hlp.CheckConfig("commadr", Setting::TypeInt)); 00205 00206 // Check config of the component, if already instanciated. 00207 if (connection) { 00208 fail |= (true != connection->CheckConfig()); 00209 } 00210 00211 LOGTRACE(logger, "Check Configuration result: " << !fail); 00212 return !fail; 00213 } 00214 00225 unsigned int CInverterSputnikSSeries::CalcChecksum(const char *str, int len) 00226 { 00227 unsigned int chksum = 0; 00228 str++; 00229 do { 00230 chksum += *str++; 00231 } while (--len); 00232 00233 return chksum; 00234 } 00235 00236 void CInverterSputnikSSeries::ExecuteCommand(const ICommand *Command) 00237 { 00238 bool dbg = true; 00239 00240 string commstring = ""; 00241 string reccomm = ""; 00242 ICommand *cmd; 00243 timespec ts; 00244 00245 switch ((Commands) Command->getCmd()) 00246 { 00247 00248 case CMD_DISCONNECTED: 00249 { 00250 // DISCONNECTED: Error detected, the link to the com partner is down. 00251 // Action: Schedule connection retry in xxx seconds 00252 // Next-State: INIT (Try to connect) 00253 LOGTRACE(logger, "new state: CMD_DISCONNECTED"); 00254 00255 // Timeout on reception 00256 // we assume that we are now disconnected. 00257 // so lets schedule a reconnection. 00258 // TODO this time should be configurable. 00259 connection->Disconnect(); 00260 00261 // Tell everyone that all data is now invalid. 00262 CCapability *c = GetConcreteCapability(CAPA_INVERTER_DATASTATE); 00263 CValue<bool> *v = (CValue<bool> *) c->getValue(); 00264 v->Set(false); 00265 c->Notify(); 00266 00267 cmd = new ICommand(CMD_INIT, this); 00268 timespec ts; 00269 ts.tv_sec = 15; 00270 ts.tv_nsec = 0; 00271 Registry::GetMainScheduler()->ScheduleWork(cmd, ts); 00272 break; 00273 } 00274 00275 case CMD_INIT: 00276 { 00277 // INIT: Try to connect to the comm partner 00278 // Action Connection Attempt 00279 // Next-State: Wait4Connection 00280 LOGTRACE(logger, "new state: CMD_INIT"); 00281 00282 cmd = new ICommand(CMD_WAIT4CONNECTION, this); 00283 connection->Connect(cmd); 00284 break; 00285 00286 // storage of objects in boost::any 00287 //cmd->addData("TEST", cmd); 00288 //cmd = boost::any_cast<ICommand*>(cmd->findData("TEST")); 00289 } 00290 00291 case CMD_WAIT4CONNECTION: 00292 { 00293 LOGTRACE(logger, "new state: CMD_WAIT4CONNECTION"); 00294 00295 int err = -1; 00296 // WAIT4CONNECTION: Wait until connection is up of failed to set up 00297 // by the communication object. 00298 // Action: Check success/error flag 00299 // Next-State: Depending on success: 00300 // success IDENTIFY_COMM 00301 // error DISCONNECTED 00302 try { 00303 err = boost::any_cast<int>(Command->findData(ICMD_ERRNO)); 00304 } catch (...) { 00305 LOGDEBUG(logger,"CMD_WAIT4CONNECTION: unexpected exception"); 00306 err = -1; 00307 } 00308 00309 if (err < 0) { 00310 try { 00311 LOGERROR(logger, "Error while connecting: " << 00312 boost::any_cast<string>(Command->findData(ICMD_ERRNO_STR))); 00313 } catch (...) { 00314 LOGERROR(logger, "Unknown error while connecting."); 00315 } 00316 00317 cmd = new ICommand(CMD_DISCONNECTED, this); 00318 Registry::GetMainScheduler()->ScheduleWork(cmd); 00319 } else { 00320 cmd = new ICommand(CMD_QUERY_IDENTIFY, this); 00321 Registry::GetMainScheduler()->ScheduleWork(cmd); 00322 } 00323 } 00324 break; 00325 00326 case CMD_QUERY_IDENTIFY: 00327 LOGTRACE(logger, "new state: CMD_QUERY_IDENTIFY "); 00328 00329 pushinverterquery(TYP); 00330 pushinverterquery(SWV); 00331 pushinverterquery(BUILDVER); 00332 00333 case CMD_QUERY_POLL: 00334 LOGTRACE(logger, "new state: CMD_QUERY_POLL "); 00335 00336 pushinverterquery(PAC); 00337 pushinverterquery(KHR); 00338 pushinverterquery(PIN); 00339 pushinverterquery(KT0); 00340 pushinverterquery(DDY); 00341 pushinverterquery(KYR); 00342 pushinverterquery(KMT); 00343 pushinverterquery(KDY); 00344 pushinverterquery(KT0); 00345 pushinverterquery(PRL); 00346 00347 pushinverterquery(UDC); 00348 pushinverterquery(IDC); 00349 pushinverterquery(IL1); 00350 pushinverterquery(UL1); 00351 pushinverterquery(TKK); 00352 pushinverterquery(TMI); 00353 pushinverterquery(THR); 00354 pushinverterquery(TNF); 00355 pushinverterquery(SYS); 00356 00357 case CMD_SEND_QUERIES: 00358 { 00359 LOGTRACE(logger, "new state: CMD_SEND_QUERIES "); 00360 00361 commstring = assemblequerystring(); 00362 LOGTRACE(logger, "Sending: " << commstring << " Len: "<< commstring.size()); 00363 00364 cmd = new ICommand(CMD_WAIT_SENT, this); 00365 connection->Send(commstring, cmd); 00366 } 00367 break; 00368 00369 case CMD_WAIT_SENT: 00370 { 00371 LOGTRACE(logger, "new state: CMD_WAIT_SENT"); 00372 int err; 00373 try { 00374 err = boost::any_cast<int>(Command->findData(ICMD_ERRNO)); 00375 } catch (...) { 00376 LOGDEBUG(logger, "BUG: Unexpected exception."); 00377 err = -1; 00378 } 00379 00380 if (err < 0) { 00381 cmd = new ICommand(CMD_DISCONNECTED, this); 00382 Registry::GetMainScheduler()->ScheduleWork(cmd); 00383 break; 00384 } 00385 } 00386 // Fall through ok. 00387 00388 case CMD_WAIT_RECEIVE: 00389 { 00390 LOGTRACE(logger, "new state: CMD_WAIT_RECEIVE"); 00391 00392 cmd = new ICommand(CMD_EVALUATE_RECEIVE, this); 00393 connection->Receive(cmd); 00394 } 00395 break; 00396 00397 case CMD_EVALUATE_RECEIVE: 00398 { 00399 LOGTRACE(logger, "new state: CMD_EVALUATE_RECEIVE"); 00400 00401 int err; 00402 std::string s; 00403 try { 00404 err = boost::any_cast<int>(Command->findData(ICMD_ERRNO)); 00405 } catch (...) { 00406 LOGDEBUG(logger, "BUG: Unexpected exception."); 00407 err = -1; 00408 } 00409 00410 if (err < 0) { 00411 // we do not differenziate the error here, a error is a error.... 00412 cmd = new ICommand(CMD_DISCONNECTED, this); 00413 Registry::GetMainScheduler()->ScheduleWork(cmd); 00414 try { 00415 s = boost::any_cast<std::string>(Command->findData( 00416 ICMD_ERRNO_STR)); 00417 LOGERROR(logger, "Receive Error: " << s); 00418 } catch (...) { 00419 LOGERROR(logger, "Receive Error: " << strerror(-err)); 00420 } 00421 break; 00422 } 00423 00424 try { 00425 s = boost::any_cast<std::string>(Command->findData( 00426 ICONN_TOKEN_RECEIVE_STRING)); 00427 } catch (...) { 00428 LOGDEBUG(logger, "Unexpected Exception"); 00429 break; 00430 } 00431 00432 LOGTRACE(logger, "Received :" << s << "len: " << s.size()); 00433 if (logger.IsEnabled(ILogger::LL_TRACE)) { 00434 string st; 00435 char buf[32]; 00436 for (unsigned int i = 0; i < s.size(); i++) { 00437 sprintf(buf, "%02x", (unsigned char) s[i]); 00438 st = st + buf; 00439 if (i && i % 16 == 0) 00440 st = st + "\n"; 00441 else 00442 st = st + ' '; 00443 } 00444 LOGTRACE(logger, "Received in hex: "<< st ); 00445 } 00446 00447 int parseresult = parsereceivedstring(s); 00448 if (0 > parseresult ) { 00449 // Reconnect on parse errors. 00450 LOGERROR(logger, "Parse error while receiving."); 00451 LOGDEBUG(logger, "Received: " << s); 00452 cmd = new ICommand(CMD_DISCONNECTED, this); 00453 Registry::GetMainScheduler()->ScheduleWork(cmd); 00454 break; 00455 } else if ( parseresult == 0) { 00456 // The received data seems not to be for our inverter. 00457 // Can happen on a shared connection. 00458 // So lets wait again. 00459 00460 ICommand *cmd = new ICommand(CMD_EVALUATE_RECEIVE, this); 00461 connection->Receive(cmd); 00462 break; 00463 } 00464 00465 // check if there are queries left in this cycle. 00466 if (!cmdqueue.empty()) { 00467 cmd = new ICommand(CMD_SEND_QUERIES, this); 00468 // there are more. finish them before setting the data valid. 00469 Registry::GetMainScheduler()->ScheduleWork(cmd); 00470 break; 00471 } 00472 00473 // TODO differenciate between identify query and "normal" runtime queries 00474 cmd = new ICommand(CMD_QUERY_POLL, this); 00475 00476 CCapability *c = GetConcreteCapability(CAPA_INVERTER_DATASTATE); 00477 CValue<bool> *vb = (CValue<bool> *) c->getValue(); 00478 vb->Set(true); 00479 c->Notify(); 00480 00481 c = GetConcreteCapability(CAPA_INVERTER_QUERYINTERVAL); 00482 CValue<float> *v = (CValue<float> *) c->getValue(); 00483 ts.tv_sec = v->Get(); 00484 ts.tv_nsec = ((v->Get() - ts.tv_sec) * 1e9); 00485 Registry::GetMainScheduler()->ScheduleWork(cmd, ts); 00486 } 00487 break; 00488 00489 default: 00490 LOGFATAL(logger, "Unknown CMD received"); 00491 abort(); 00492 00493 } 00494 00495 } 00496 00497 void CInverterSputnikSSeries::pushinverterquery(enum query q) 00498 { 00499 cmdqueue.push(q); 00500 } 00501 00502 string CInverterSputnikSSeries::assemblequerystring() 00503 { 00504 if (cmdqueue.empty()) { 00505 LOGTRACE(logger, "assemblequerystring: empty task lisk."); 00506 return ""; 00507 } 00508 00509 int len = 0; 00510 int expectedanswerlen = 0; 00511 int currentport = 0; 00512 string nextcmd; 00513 string querystring, tmp; 00514 bool cont = true; 00515 char formatbuffer[256]; 00516 00517 do { 00518 switch (cmdqueue.front()) 00519 { 00520 00521 case TYP: 00522 { 00523 if (currentport && currentport != QUERY) { 00524 // Changing ports are not supported. 00525 // Different ports means a different message. 00526 cont = false; 00527 break; 00528 } 00529 currentport = QUERY; 00530 nextcmd = "TYP"; 00531 expectedanswerlen += 9; 00532 break; 00533 } 00534 00535 case SWV: 00536 { 00537 if (currentport && currentport != QUERY) { 00538 cont = false; 00539 break; 00540 } 00541 currentport = QUERY; 00542 nextcmd = "SWV"; 00543 expectedanswerlen += 7; 00544 break; 00545 } 00546 00547 case BUILDVER: 00548 { 00549 if (currentport && currentport != QUERY) { 00550 cont = false; 00551 break; 00552 } 00553 currentport = QUERY; 00554 nextcmd = "BDN"; 00555 expectedanswerlen += 9; 00556 break; 00557 } 00558 00559 case EC00: 00560 { 00561 if (currentport && currentport != QUERY) { 00562 cont = false; 00563 break; 00564 } 00565 currentport = QUERY; 00566 nextcmd = "EC00"; 00567 expectedanswerlen += 27; 00568 break; 00569 } 00570 00571 case EC01: 00572 { 00573 if (currentport && currentport != QUERY) { 00574 cont = false; 00575 break; 00576 } 00577 currentport = QUERY; 00578 nextcmd = "EC01"; 00579 expectedanswerlen += 27; 00580 break; 00581 } 00582 00583 case EC02: 00584 { 00585 if (currentport && currentport != QUERY) { 00586 cont = false; 00587 break; 00588 } 00589 currentport = QUERY; 00590 nextcmd = "EC02"; 00591 expectedanswerlen += 27; 00592 break; 00593 } 00594 00595 case EC03: 00596 { 00597 00598 if (currentport && currentport != QUERY) { 00599 cont = false; 00600 break; 00601 } 00602 currentport = QUERY; 00603 nextcmd = "EC03"; 00604 expectedanswerlen += 27; 00605 break; 00606 } 00607 00608 case EC04: 00609 { 00610 if (currentport && currentport != QUERY) { 00611 cont = false; 00612 break; 00613 } 00614 currentport = QUERY; 00615 nextcmd = "EC04"; 00616 expectedanswerlen += 27; 00617 break; 00618 } 00619 00620 case EC05: 00621 { 00622 if (currentport && currentport != QUERY) { 00623 cont = false; 00624 break; 00625 } 00626 currentport = QUERY; 00627 nextcmd = "EC05"; 00628 expectedanswerlen += 27; 00629 break; 00630 } 00631 00632 case EC06: 00633 { 00634 if (currentport && currentport != QUERY) { 00635 cont = false; 00636 break; 00637 } 00638 currentport = QUERY; 00639 nextcmd = "EC06"; 00640 expectedanswerlen += 27; 00641 break; 00642 } 00643 00644 case EC07: 00645 { 00646 if (currentport && currentport != QUERY) { 00647 cont = false; 00648 break; 00649 } 00650 currentport = QUERY; 00651 nextcmd = "EC07"; 00652 expectedanswerlen += 27; 00653 break; 00654 } 00655 00656 case EC08: 00657 { 00658 if (currentport && currentport != QUERY) { 00659 cont = false; 00660 break; 00661 } 00662 currentport = QUERY; 00663 nextcmd = "EC08"; 00664 expectedanswerlen += 27; 00665 break; 00666 } 00667 00668 case PAC: 00669 { 00670 if (currentport && currentport != QUERY) { 00671 cont = false; 00672 break; 00673 } 00674 currentport = QUERY; 00675 nextcmd = "PAC"; 00676 expectedanswerlen += 9; 00677 break; 00678 } 00679 00680 case KHR: 00681 { 00682 if (currentport && currentport != QUERY) { 00683 cont = false; 00684 break; 00685 } 00686 currentport = QUERY; 00687 nextcmd = "KHR"; 00688 expectedanswerlen += 9; 00689 break; 00690 } 00691 00692 case DYR: 00693 { 00694 if (currentport && currentport != QUERY) { 00695 cont = false; 00696 break; 00697 } 00698 currentport = QUERY; 00699 nextcmd = "DYR"; 00700 expectedanswerlen += 7; 00701 break; 00702 } 00703 00704 case DMT: 00705 { 00706 if (currentport && currentport != QUERY) { 00707 cont = false; 00708 break; 00709 } 00710 currentport = QUERY; 00711 nextcmd = "DMT"; 00712 // TODO check answer len 00713 expectedanswerlen += 7; 00714 break; 00715 } 00716 00717 case DDY: 00718 { 00719 if (currentport && currentport != QUERY) { 00720 cont = false; 00721 break; 00722 } 00723 currentport = QUERY; 00724 nextcmd = "DDY"; 00725 expectedanswerlen += 7; 00726 break; 00727 } 00728 00729 case KYR: 00730 { 00731 if (currentport && currentport != QUERY) { 00732 cont = false; 00733 break; 00734 } 00735 currentport = QUERY; 00736 nextcmd = "KYR"; 00737 expectedanswerlen += 9; 00738 break; 00739 } 00740 00741 case KMT: 00742 { 00743 if (currentport && currentport != QUERY) { 00744 cont = false; 00745 break; 00746 } 00747 currentport = QUERY; 00748 nextcmd = "KMT"; 00749 expectedanswerlen += 7; 00750 break; 00751 } 00752 00753 case KDY: 00754 { 00755 if (currentport && currentport != QUERY) { 00756 cont = false; 00757 break; 00758 } 00759 currentport = QUERY; 00760 nextcmd = "KDY"; 00761 expectedanswerlen += 10; 00762 break; 00763 } 00764 00765 case KT0: 00766 { 00767 if (currentport && currentport != QUERY) { 00768 cont = false; 00769 break; 00770 } 00771 currentport = QUERY; 00772 nextcmd = "KT0"; 00773 expectedanswerlen += 10; 00774 break; 00775 } 00776 00777 case PIN: 00778 { 00779 if (currentport && currentport != QUERY) { 00780 cont = false; 00781 break; 00782 } 00783 currentport = QUERY; 00784 nextcmd = "PIN"; 00785 expectedanswerlen += 9; 00786 break; 00787 } 00788 00789 case TNF: 00790 { 00791 if (currentport && currentport != QUERY) { 00792 cont = false; 00793 break; 00794 } 00795 currentport = QUERY; 00796 nextcmd = "TNF"; 00797 // TODO check answer len 00798 expectedanswerlen += 10; 00799 break; 00800 } 00801 00802 case PRL: 00803 { 00804 if (currentport && currentport != QUERY) { 00805 cont = false; 00806 break; 00807 } 00808 currentport = QUERY; 00809 nextcmd = "RPL"; 00810 // TODO check answer len 00811 expectedanswerlen += 10; 00812 break; 00813 } 00814 00815 case UDC: 00816 { 00817 if (currentport && currentport != QUERY) { 00818 cont = false; 00819 break; 00820 } 00821 currentport = QUERY; 00822 nextcmd = "UDC"; 00823 // TODO check answer len 00824 expectedanswerlen += 10; 00825 break; 00826 } 00827 00828 case UL1: 00829 { 00830 if (currentport && currentport != QUERY) { 00831 cont = false; 00832 break; 00833 } 00834 currentport = QUERY; 00835 nextcmd = "UL1"; 00836 // TODO check answer len 00837 expectedanswerlen += 10; 00838 break; 00839 } 00840 00841 case UL2: 00842 { 00843 if (currentport && currentport != QUERY) { 00844 cont = false; 00845 break; 00846 } 00847 currentport = QUERY; 00848 nextcmd = "UL2"; 00849 // TODO check answer len 00850 expectedanswerlen += 10; 00851 break; 00852 } 00853 00854 case UL3: 00855 { 00856 if (currentport && currentport != QUERY) { 00857 cont = false; 00858 break; 00859 } 00860 currentport = QUERY; 00861 nextcmd = "UL3"; 00862 // TODO check answer len 00863 expectedanswerlen += 10; 00864 break; 00865 } 00866 00867 case IDC: 00868 { 00869 if (currentport && currentport != QUERY) { 00870 cont = false; 00871 break; 00872 } 00873 currentport = QUERY; 00874 nextcmd = "IDC"; 00875 // TODO check answer len 00876 expectedanswerlen += 10; 00877 break; 00878 } 00879 00880 case IL1: 00881 { 00882 if (currentport && currentport != QUERY) { 00883 cont = false; 00884 break; 00885 } 00886 currentport = QUERY; 00887 nextcmd = "IL1"; 00888 // TODO check answer len 00889 expectedanswerlen += 10; 00890 break; 00891 } 00892 00893 case IL2: 00894 { 00895 if (currentport && currentport != QUERY) { 00896 cont = false; 00897 break; 00898 } 00899 currentport = QUERY; 00900 nextcmd = "IL2"; 00901 // TODO check answer len 00902 expectedanswerlen += 10; 00903 break; 00904 } 00905 00906 case IL3: 00907 { 00908 if (currentport && currentport != QUERY) { 00909 cont = false; 00910 break; 00911 } 00912 currentport = QUERY; 00913 nextcmd = "IL3"; 00914 // TODO check answer len 00915 expectedanswerlen += 10; 00916 break; 00917 } 00918 00919 case TKK: 00920 { 00921 if (currentport && currentport != QUERY) { 00922 cont = false; 00923 break; 00924 } 00925 currentport = QUERY; 00926 nextcmd = "TKK"; 00927 // TODO check answer len 00928 expectedanswerlen += 10; 00929 break; 00930 } 00931 00932 case TK2: 00933 { 00934 if (currentport && currentport != QUERY) { 00935 cont = false; 00936 break; 00937 } 00938 currentport = QUERY; 00939 nextcmd = "TK2"; 00940 // TODO check answer len 00941 expectedanswerlen += 10; 00942 break; 00943 } 00944 00945 case TK3: 00946 { 00947 if (currentport && currentport != QUERY) { 00948 cont = false; 00949 break; 00950 } 00951 currentport = QUERY; 00952 nextcmd = "TK3"; 00953 // TODO check answer len 00954 expectedanswerlen += 10; 00955 break; 00956 } 00957 00958 case TMI: 00959 { 00960 if (currentport && currentport != QUERY) { 00961 cont = false; 00962 break; 00963 } 00964 currentport = QUERY; 00965 nextcmd = "TMI"; 00966 // TODO check answer len 00967 expectedanswerlen += 10; 00968 break; 00969 } 00970 00971 case THR: 00972 { 00973 if (currentport && currentport != QUERY) { 00974 cont = false; 00975 break; 00976 } 00977 currentport = QUERY; 00978 nextcmd = "THR"; 00979 // TODO check answer len 00980 expectedanswerlen += 10; 00981 break; 00982 } 00983 00984 case SYS: 00985 { 00986 if (currentport && currentport != QUERY) { 00987 cont = false; 00988 break; 00989 } 00990 currentport = QUERY; 00991 nextcmd = "SYS"; 00992 // TODO check answer len 00993 expectedanswerlen += 10; 00994 break; 00995 } 00996 00997 } 00998 00999 if (cont) { 01000 // check if command will fit into max. telegram len 01001 // note: 255 = max len, 15 = header "{xx;yy;zz|pppp:", 6 = tail "|CHKS}" 01002 // (plus one byte for the seperator.) 01003 // note: this is not squeezed to its end, as we will usually not 01004 // fill up to 255 bytes because lacking commands... 01005 if (nextcmd.length() + len <= (255 - 15 - 6) - 1 && 01006 // use our assumption to limit the expected receive telegram 01007 // add a safety margin of 10. 01008 expectedanswerlen <= (255 - 15 - 6 - 1 - 10)) { 01009 // Check if we need to insert a seperator. 01010 if (len) { 01011 querystring += ";"; 01012 len++; 01013 } 01014 // Add the prepared command and remove it from the queue. 01015 querystring += nextcmd; 01016 len += nextcmd.length(); 01017 cmdqueue.pop(); 01018 } else { 01019 cont = false; 01020 } 01021 } 01022 } while (cont && !cmdqueue.empty()); 01023 01024 sprintf(formatbuffer, "%X:", currentport); 01025 len = strlen(formatbuffer) + querystring.length() + 10 + 6; 01026 01027 // finally prepare Header "{<from>;<to>;<len>|<port>:" 01028 sprintf(formatbuffer, "{%02X;%02X;%02X|%X:", ownadr, commadr, len, 01029 currentport); 01030 01031 tmp = formatbuffer + querystring + '|'; 01032 querystring = tmp; 01033 01034 sprintf(formatbuffer, "%04X}", CalcChecksum(querystring.c_str(), 01035 querystring.length())); 01036 01037 querystring += formatbuffer; 01038 return querystring; 01039 } 01040 01041 int CInverterSputnikSSeries::parsereceivedstring(const string & s) 01042 { 01043 01044 unsigned int i; 01045 01046 // check for basic constraints... 01047 if (s[0] != '{' || s[s.length() - 1] != '}') 01048 return -1; 01049 01050 // tokenizer (taken from 01051 // http://oopweb.com/CPP/Documents/CPPHOWTO/Volume/C++Programming-HOWTO-7.html 01052 // but modified. 01053 01054 // we take the received string and "split" it into smaller pieces. 01055 // the pieces ("tokens") then assemble one single information to be 01056 // for this, we split by: 01057 // ";" "|" ":" (and "{}") 01058 01059 vector<string> tokens; 01060 char delimiters[] = "{;|:}"; 01061 tokenizer(delimiters, s, tokens); 01062 01063 // Debug: Print all received tokens 01064 #if defined DEBUG_TOKENIZER 01065 if (logger.IsEnabled(ILogger::LL_TRACE)) { 01066 std::stringstream ss; 01067 vector<string>::iterator it; 01068 for (i = 0, it = tokens.begin(); it != tokens.end() - 1; it++) { 01069 ss << i++ << ": " << (*it) << "\tlen: " 01070 << (*it).length() << endl; 01071 } 01072 LOGTRACE(logger,ss); 01073 } 01074 #endif 01075 01076 unsigned int tmp; 01077 if (1 != sscanf(tokens.back().c_str(), "%x", &tmp)) { 01078 LOGDEBUG(logger, "could not parse checksum. Token was:" ); 01079 return -1; 01080 } 01081 01082 if (tmp != CalcChecksum(s.c_str(), s.length() - 6)) { 01083 LOGDEBUG(logger, "Checksum error on received telegram"); 01084 return -1; 01085 } 01086 01087 if (1 != sscanf(tokens[0].c_str(), "%x", &tmp)) { 01088 LOGDEBUG(logger, " could not parse from address"); 01089 return -1; 01090 } 01091 01092 if (tmp != commadr) { 01093 LOGDEBUG(logger, "Received string is not for us: Wrong Sender"); 01094 return 0; 01095 } 01096 01097 if (1 != sscanf(tokens[1].c_str(), "%x", &tmp)) { 01098 LOGDEBUG(logger, "could not parse to-address"); 01099 return -1; 01100 } 01101 01102 if (tmp != ownadr) { 01103 LOGDEBUG(logger, "Received string is not for us: Wrong receiver"); 01104 return 0; 01105 } 01106 01107 if (1 != sscanf(tokens[2].c_str(), "%x", &tmp)) { 01108 LOGDEBUG(logger, "could not parse telegram length"); 01109 return -1; 01110 } 01111 01112 if (tmp != s.length()) { 01113 LOGDEBUG(logger, "wrong telegram length "); 01114 return -1; 01115 } 01116 01117 // TODO FIXME: Currently the data port is simply "ignored" 01118 // parsetoken should get a second argument to get the port infos. 01119 01120 int ret = 1; 01121 for (i = 4; i < tokens.size() - 1; i++) { 01122 if (!parsetoken(tokens[i])) { 01123 LOGDEBUG(logger, 01124 "BUG: Parse Error at token " << tokens[i] 01125 << ". Received: " << s << "If the token is unknown or you subject a bug, please report it giving the token ans received string" 01126 ); 01127 ret = -1; 01128 } 01129 } 01130 return ret; 01131 } 01132 01133 bool CInverterSputnikSSeries::parsetoken(string token) 01134 { 01135 01136 vector<string> subtokens; 01137 const char delimiters[] = "=,"; 01138 tokenizer(delimiters, token, subtokens); 01139 01140 // TODO rewrite this section: Lookup the strings and functions in a table, call 01141 // by function pointer. 01142 01143 if (subtokens[0] == "TYP") { 01144 return token_TYP(subtokens); 01145 } 01146 01147 if (subtokens[0] == "SWV") { 01148 return token_SWVER(subtokens); 01149 } 01150 01151 if (subtokens[0] == "BDN") { 01152 return token_BUILDVER(subtokens); 01153 } 01154 01155 if (subtokens[0].substr(0, 2) == "EC") { 01156 return token_ECxx(subtokens); 01157 } 01158 01159 if (subtokens[0] == "PAC") { 01160 return token_PAC(subtokens); 01161 } 01162 01163 if (subtokens[0] == "KHR") { 01164 return token_KHR(subtokens); 01165 } 01166 01167 if (subtokens[0] == "DYR") { 01168 return token_DYR(subtokens); 01169 } 01170 01171 if (subtokens[0] == "DMT") { 01172 return token_DMT(subtokens); 01173 } 01174 01175 if (subtokens[0] == "DDY") { 01176 return token_DDY(subtokens); 01177 } 01178 01179 if (subtokens[0] == "KYR") { 01180 return token_KYR(subtokens); 01181 } 01182 01183 if (subtokens[0] == "KMT") { 01184 return token_KMT(subtokens); 01185 } 01186 01187 if (subtokens[0] == "KDY") { 01188 return token_KDY(subtokens); 01189 } 01190 01191 if (subtokens[0] == "KT0") { 01192 return token_KT0(subtokens); 01193 } 01194 01195 if (subtokens[0] == "PIN") { 01196 return token_PIN(subtokens); 01197 } 01198 01199 if (subtokens[0] == "TNF") { 01200 return token_TNF(subtokens); 01201 } 01202 if (subtokens[0] == "PRL") { 01203 return token_PRL(subtokens); 01204 } 01205 if (subtokens[0] == "UDC") { 01206 return token_UDC(subtokens); 01207 } 01208 01209 if (subtokens[0] == "UL1") { 01210 return token_UL1(subtokens); 01211 } 01212 01213 if (subtokens[0] == "UL2") { 01214 return token_UL2(subtokens); 01215 } 01216 01217 if (subtokens[0] == "UL3") { 01218 return token_UL3(subtokens); 01219 } 01220 01221 if (subtokens[0] == "IDC") { 01222 return token_IDC(subtokens); 01223 } 01224 if (subtokens[0] == "IL1") { 01225 return token_IL1(subtokens); 01226 } 01227 01228 if (subtokens[0] == "IL2") { 01229 return token_IL2(subtokens); 01230 } 01231 01232 if (subtokens[0] == "IL3") { 01233 return token_IL3(subtokens); 01234 } 01235 01236 if (subtokens[0] == "TKK") { 01237 return token_TKK(subtokens); 01238 } 01239 01240 if (subtokens[0] == "TK2") { 01241 return token_TK2(subtokens); 01242 } 01243 01244 if (subtokens[0] == "TK3") { 01245 return token_TK3(subtokens); 01246 } 01247 01248 if (subtokens[0] == "TMI") { 01249 return token_TMI(subtokens); 01250 } 01251 01252 if (subtokens[0] == "THR") { 01253 return token_THR(subtokens); 01254 } 01255 01256 if (subtokens[0] == "SYS") { 01257 return token_SYS(subtokens); 01258 } 01259 01260 return true; 01261 } 01262 01263 void CInverterSputnikSSeries::tokenizer(const char *delimiters, 01264 const string& s, vector<string> &tokens) 01265 { 01266 unsigned int i; 01267 01268 string::size_type lastPos = 0; 01269 string::size_type pos = 0; 01270 01271 i = 0; 01272 // Skip tokens at the start of the string 01273 do { 01274 if (s[lastPos] == delimiters[i]) { 01275 lastPos++; 01276 i = 0; 01277 } 01278 } while (++i < strlen(delimiters)); 01279 01280 pos = lastPos; 01281 01282 // get the first substring by finding the "second" delimiter 01283 i = lastPos; 01284 01285 do { 01286 unsigned int tmp; 01287 tmp = s.find_first_of(delimiters[i], lastPos); 01288 if (tmp < pos) 01289 pos = tmp; 01290 } while (++i < strlen(delimiters)); 01291 01292 while (s.length() > pos && s.length() > lastPos) { 01293 unsigned int tmp, tmp2; 01294 01295 if (pos - lastPos) { 01296 tokens.push_back(s.substr(lastPos, pos - lastPos)); 01297 } 01298 lastPos = pos; 01299 01300 // Skip delimiters. 01301 i = 0; 01302 do { 01303 if (s[lastPos] == delimiters[i]) { 01304 lastPos++; 01305 i = 0; 01306 } 01307 01308 } while (++i < strlen(delimiters)); 01309 01310 // Find next "delimiter" 01311 i = 0; 01312 tmp2 = -1; 01313 do { 01314 tmp = s.find_first_of(delimiters[i], lastPos); 01315 if (tmp < tmp2) 01316 tmp2 = tmp; 01317 } while (++i < strlen(delimiters)); 01318 pos = tmp2; 01319 } 01320 01321 // Check if we have an "end-token" (not seperated) 01322 if (lastPos != s.length()) { 01323 tokens.push_back(s.substr(lastPos, s.length() - lastPos)); 01324 } 01325 } 01326 01327 bool CInverterSputnikSSeries::token_TYP(const vector<string> & tokens) 01328 { 01329 01330 string strmodel; 01331 unsigned int i = 0; 01332 01333 // Check syntax 01334 if (tokens.size() != 2) 01335 return false; 01336 unsigned int model; 01337 sscanf(tokens[1].c_str(), "%x", &model); 01338 01339 do { 01340 if (model_lookup[i].typ == model) 01341 break; 01342 } while (model_lookup[++i].typ != (unsigned int) -1); 01343 01344 if (model_lookup[i].typ == (unsigned int) -1) { 01345 LOGWARN(logger, "Identified a " << model_lookup[i].description); 01346 LOGWARN(logger, "Received TYP was " << tokens[0] << "=" << tokens[1]); 01347 } 01348 01349 // lookup if we already know that information. 01350 CCapability *cap = GetConcreteCapability(CAPA_INVERTER_MODEL); 01351 01352 if (!cap) { 01353 string s; 01354 IValue *v; 01355 CCapability *c; 01356 s = CAPA_INVERTER_MODEL; 01357 v = IValue::Factory(CAPA_INVERTER_MODEL_TYPE); 01358 ((CValue<string>*) v)->Set(model_lookup[i].description); 01359 c = new CCapability(s, v, this); 01360 AddCapability(s, c); 01361 01362 // TODO: Check if we schould derefer (using a scheduled work) this. 01363 cap = GetConcreteCapability(CAPA_CAPAS_UPDATED); 01364 cap->Notify(); 01365 } 01366 // Capa already in the list. Check if we need to update it. 01367 else if (cap->getValue()->GetType() == CAPA_INVERTER_MODEL_TYPE) { 01368 CValue<string> *val = (CValue<string>*) cap->getValue(); 01369 if (model_lookup[i].description != val->Get()) { 01370 01371 LOGDEBUG(logger, "WEIRD: Updating inverter type from " 01372 << val->Get() << " to " 01373 << model_lookup[i].description); 01374 01375 val->Set(model_lookup[i].description); 01376 cap->Notify(); 01377 } 01378 } else { 01379 LOGDEBUG(logger, "BUG: " << CAPA_INVERTER_MODEL << " not a string "); 01380 } 01381 01382 return true; 01383 } 01384 01385 bool CInverterSputnikSSeries::token_SWVER(const vector<string> & tokens) 01386 { 01387 int tmp; 01388 string ver; 01389 01390 // Check syntax 01391 if (tokens.size() != 2) 01392 return false; 01393 01394 sscanf(tokens[1].c_str(), "%x", &tmp); 01395 01396 // Been there, seen that. 01397 if (swversion == tmp) 01398 return true; 01399 swversion = tmp; 01400 01401 create_versioncapa(); 01402 01403 return true; 01404 } 01405 01406 bool CInverterSputnikSSeries::token_BUILDVER(const vector<string> & tokens) 01407 { 01408 int tmp; 01409 string ver; 01410 01411 // Check syntax 01412 if (tokens.size() != 2) 01413 return false; 01414 01415 sscanf(tokens[1].c_str(), "%x", &tmp); 01416 01417 // Been there, seen that. 01418 if (swbuild == tmp) 01419 return true; 01420 01421 swbuild = tmp; 01422 create_versioncapa(); 01423 01424 return true; 01425 } 01426 01427 bool CInverterSputnikSSeries::token_ECxx(const vector<string> & tokens) 01428 { 01429 // FIXME TODO Implement me! 01430 // Will be implemented later, as currently not-so-important 01431 // (and just unsure, how to handle the different entries. Probably as an ring- 01432 // buffer, with just updating the last. 01433 return true; 01434 } 01435 01436 bool CInverterSputnikSSeries::token_PAC(const vector<string> & tokens) 01437 { 01438 if (tokens.size() != 2) 01439 return false; 01440 01441 unsigned int pac; 01442 float fpac; 01443 sscanf(tokens[1].c_str(), "%x", &pac); 01444 01445 fpac = pac / 2.0; 01446 01447 // lookup if we already know that information. 01448 CCapability *cap = GetConcreteCapability(CAPA_INVERTER_ACPOWER_TOTAL); 01449 01450 if (!cap) { 01451 string s; 01452 IValue *v; 01453 CCapability *c; 01454 s = CAPA_INVERTER_ACPOWER_TOTAL; 01455 v = IValue::Factory(CAPA_INVERTER_ACPOWER_TOTAL_TYPE); 01456 ((CValue<float>*) v)->Set(fpac); 01457 c = new CCapability(s, v, this); 01458 AddCapability(s, c); 01459 01460 // TODO: Check if we schould derefer (using a scheduled work) this. 01461 cap = GetConcreteCapability(CAPA_CAPAS_UPDATED); 01462 cap->Notify(); 01463 } 01464 // Capa already in the list. Check if we need to update it. 01465 else if (cap->getValue()->GetType() == CAPA_INVERTER_ACPOWER_TOTAL_TYPE) { 01466 CValue<float> *val = (CValue<float>*) cap->getValue(); 01467 01468 if (val -> Get() != fpac) { 01469 val->Set(fpac); 01470 cap->Notify(); 01471 } 01472 } else { 01473 LOGDEBUG(logger, "BUG: " << CAPA_INVERTER_ACPOWER_TOTAL 01474 << " not a float "); 01475 } 01476 01477 return true; 01478 } 01479 01480 bool CInverterSputnikSSeries::token_KHR(const vector<string> & tokens) 01481 { 01482 // Power-On-Hours 01483 if (tokens.size() != 2) 01484 return false; 01485 01486 unsigned int tmp; 01487 float f; 01488 sscanf(tokens[1].c_str(), "%x", &tmp); 01489 01490 f = tmp; 01491 01492 // lookup if we already know that information. 01493 CCapability *cap = GetConcreteCapability(CAPA_INVERTER_PON_HOURS); 01494 01495 if (!cap) { 01496 string s; 01497 IValue *v; 01498 CCapability *c; 01499 s = CAPA_INVERTER_PON_HOURS; 01500 v = IValue::Factory(CAPA_INVERTER_PON_HOURS_TYPE); 01501 ((CValue<float>*) v)->Set(f); 01502 c = new CCapability(s, v, this); 01503 AddCapability(s, c); 01504 01505 // TODO: Check if we schould derefer (using a scheduled work) this. 01506 cap = GetConcreteCapability(CAPA_CAPAS_UPDATED); 01507 cap->Notify(); 01508 } 01509 // Capa already in the list. Check if we need to update it. 01510 else if (cap->getValue()->GetType() == CAPA_INVERTER_PON_HOURS_TYPE) { 01511 CValue<float> *val = (CValue<float>*) cap->getValue(); 01512 if (val -> Get() != f) { 01513 val->Set(f); 01514 cap->Notify(); 01515 } 01516 } else { 01517 LOGDEBUG(logger, "BUG: " << CAPA_INVERTER_PON_HOURS << " not a float "); 01518 } 01519 01520 return true; 01521 } 01522 01523 bool CInverterSputnikSSeries::token_DYR(const vector<string> & tokens) 01524 { 01525 // FIXME TODO Implement me! 01526 return true; 01527 } 01528 01529 bool CInverterSputnikSSeries::token_DMT(const vector<string> & tokens) 01530 { 01531 // FIXME TODO Implement me! 01532 return true; 01533 } 01534 01535 bool CInverterSputnikSSeries::token_DDY(const vector<string> & tokens) 01536 { 01537 // FIXME TODO Implement me! 01538 return true; 01539 } 01540 01541 bool CInverterSputnikSSeries::token_KYR(const vector<string> & tokens) 01542 { 01543 // Unit kwH 01544 if (tokens.size() != 2) 01545 return false; 01546 01547 unsigned int raw; 01548 float kwh; 01549 sscanf(tokens[1].c_str(), "%x", &raw); 01550 01551 kwh = raw; 01552 01553 // lookup if we already know that information. 01554 CCapability *cap = GetConcreteCapability(CAPA_INVERTER_KWH_Y2D); 01555 01556 if (!cap) { 01557 string s; 01558 IValue *v; 01559 CCapability *c; 01560 s = CAPA_INVERTER_KWH_Y2D; 01561 v = IValue::Factory(CAPA_INVERTER_KWH_Y2D_TYPE); 01562 ((CValue<float>*) v)->Set(kwh); 01563 c = new CCapability(s, v, this); 01564 AddCapability(s, c); 01565 01566 // TODO: Check if we schould derefer (using a scheduled work) this. 01567 cap = GetConcreteCapability(CAPA_CAPAS_UPDATED); 01568 cap->Notify(); 01569 } 01570 // Capa already in the list. Check if we need to update it. 01571 else if (cap->getValue()->GetType() == CAPA_INVERTER_KWH_Y2D_TYPE) { 01572 CValue<float> *val = (CValue<float>*) cap->getValue(); 01573 01574 if (val -> Get() != kwh) { 01575 val->Set(kwh); 01576 cap->Notify(); 01577 } 01578 } else { 01579 LOGDEBUG(logger, "BUG: " << CAPA_INVERTER_KWH_Y2D << " not a float "); 01580 } 01581 01582 return true; 01583 } 01584 01585 bool CInverterSputnikSSeries::token_KMT(const vector<string> & tokens) 01586 { 01587 // Unit kwH 01588 if (tokens.size() != 2) 01589 return false; 01590 01591 unsigned int raw; 01592 float kwh; 01593 sscanf(tokens[1].c_str(), "%x", &raw); 01594 01595 kwh = raw; 01596 01597 // lookup if we already know that information. 01598 CCapability *cap = GetConcreteCapability(CAPA_INVERTER_KWH_M2D); 01599 01600 if (!cap) { 01601 string s; 01602 IValue *v; 01603 CCapability *c; 01604 s = CAPA_INVERTER_KWH_M2D; 01605 v = IValue::Factory(CAPA_INVERTER_KWH_M2D_TYPE); 01606 ((CValue<float>*) v)->Set(kwh); 01607 c = new CCapability(s, v, this); 01608 AddCapability(s, c); 01609 01610 // TODO: Check if we schould derefer (using a scheduled work) this. 01611 cap = GetConcreteCapability(CAPA_CAPAS_UPDATED); 01612 cap->Notify(); 01613 } 01614 // Capa already in the list. Check if we need to update it. 01615 else if (cap->getValue()->GetType() == CAPA_INVERTER_KWH_M2D_TYPE) { 01616 CValue<float> *val = (CValue<float>*) cap->getValue(); 01617 01618 if (val -> Get() != kwh) { 01619 val->Set(kwh); 01620 cap->Notify(); 01621 } 01622 } else { 01623 LOGDEBUG(logger, "BUG: " << CAPA_INVERTER_KWH_M2D << " not a float "); 01624 } 01625 01626 return true; 01627 } 01628 01629 bool CInverterSputnikSSeries::token_KDY(const vector<string> & tokens) 01630 { 01631 // Unit 0.1 kwH 01632 if (tokens.size() != 2) 01633 return false; 01634 01635 unsigned int raw; 01636 float kwh; 01637 sscanf(tokens[1].c_str(), "%x", &raw); 01638 01639 kwh = raw / 10.0; 01640 01641 // lookup if we already know that information. 01642 CCapability *cap = GetConcreteCapability(CAPA_INVERTER_KWH_2D); 01643 if (!cap) { 01644 string s; 01645 IValue *v; 01646 CCapability *c; 01647 s = CAPA_INVERTER_KWH_2D; 01648 v = IValue::Factory(CAPA_INVERTER_KWH_2D_TYPE); 01649 ((CValue<float>*) v)->Set(kwh); 01650 c = new CCapability(s, v, this); 01651 AddCapability(s, c); 01652 01653 // TODO: Check if we schould derefer (using a scheduled work) this. 01654 cap = GetConcreteCapability(CAPA_CAPAS_UPDATED); 01655 cap->Notify(); 01656 } 01657 // Capa already in the list. Check if we need to update it. 01658 else if (cap->getValue()->GetType() == CAPA_INVERTER_KWH_2D_TYPE) { 01659 CValue<float> *val = (CValue<float>*) cap->getValue(); 01660 01661 if (val -> Get() != kwh) { 01662 val->Set(kwh); 01663 cap->Notify(); 01664 } 01665 } else { 01666 LOGDEBUG(logger, "BUG: " << CAPA_INVERTER_KWH_2D << " not a float "); 01667 } 01668 01669 return true; 01670 } 01671 01672 bool CInverterSputnikSSeries::token_KT0(const vector<string> & tokens) 01673 { 01674 // FIXME TODO Implement me! 01675 // Unit kwH 01676 if (tokens.size() != 2) 01677 return false; 01678 01679 unsigned int raw; 01680 float kwh; 01681 sscanf(tokens[1].c_str(), "%x", &raw); 01682 01683 kwh = raw; 01684 01685 // lookup if we already know that information. 01686 CCapability *cap = GetConcreteCapability(CAPA_INVERTER_KWH_TOTAL_NAME); 01687 01688 if (!cap) { 01689 string s; 01690 IValue *v; 01691 CCapability *c; 01692 s = CAPA_INVERTER_KWH_TOTAL_NAME; 01693 v = IValue::Factory(CAPA_INVERTER_KWH_TOTAL_TYPE); 01694 ((CValue<float>*) v)->Set(kwh); 01695 c = new CCapability(s, v, this); 01696 AddCapability(s, c); 01697 01698 // TODO: Check if we schould derefer (using a scheduled work) this. 01699 cap = GetConcreteCapability(CAPA_CAPAS_UPDATED); 01700 cap->Notify(); 01701 } 01702 // Capa already in the list. Check if we need to update it. 01703 else if (cap->getValue()->GetType() == CAPA_INVERTER_KWH_TOTAL_TYPE) { 01704 CValue<float> *val = (CValue<float>*) cap->getValue(); 01705 01706 if (val -> Get() != kwh) { 01707 val->Set(kwh); 01708 cap->Notify(); 01709 } 01710 } else { 01711 LOGDEBUG(logger, "BUG: " << CAPA_INVERTER_KWH_TOTAL_NAME 01712 << " not a float "); 01713 } 01714 01715 return true; 01716 } 01717 01718 bool CInverterSputnikSSeries::token_PIN(const vector<string> & tokens) 01719 { 01720 // Unit 0.5 Watts 01721 if (tokens.size() != 2) 01722 return false; 01723 01724 unsigned int raw; 01725 float f; 01726 sscanf(tokens[1].c_str(), "%x", &raw); 01727 01728 f = raw * 0.5; 01729 // lookup if we already know that information. 01730 CCapability *cap = GetConcreteCapability(CAPA_INVERTER_INSTALLEDPOWER_NAME); 01731 01732 if (!cap) { 01733 string s; 01734 IValue *v; 01735 CCapability *c; 01736 s = CAPA_INVERTER_INSTALLEDPOWER_NAME; 01737 v = IValue::Factory(CAPA_INVERTER_INSTALLEDPOWER_TYPE); 01738 ((CValue<float>*) v)->Set(f); 01739 c = new CCapability(s, v, this); 01740 AddCapability(s, c); 01741 01742 // TODO: Check if we schould derefer (using a scheduled work) this. 01743 cap = GetConcreteCapability(CAPA_CAPAS_UPDATED); 01744 cap->Notify(); 01745 } 01746 // Capa already in the list. Check if we need to update it. 01747 else if (cap->getValue()->GetType() == CAPA_INVERTER_INSTALLEDPOWER_TYPE) { 01748 CValue<float> *val = (CValue<float>*) cap->getValue(); 01749 01750 if (val -> Get() != f) { 01751 val->Set(f); 01752 cap->Notify(); 01753 } 01754 } else { 01755 LOGDEBUG(logger, "BUG: " << CAPA_INVERTER_INSTALLEDPOWER_NAME 01756 << " not a float "); 01757 } 01758 01759 return true; 01760 } 01761 01762 bool CInverterSputnikSSeries::token_TNF(const vector<string> & tokens) 01763 { 01764 // FIXME TODO Implement me! 01765 // Unit us 01766 // f = 1 / T , T = x / 1E6 01767 if (tokens.size() != 2) 01768 return false; 01769 01770 unsigned int raw; 01771 float f; 01772 sscanf(tokens[1].c_str(), "%x", &raw); 01773 01774 f = raw / 100.0; 01775 // lookup if we already know that information. 01776 CCapability *cap = GetConcreteCapability(CAPA_INVERTER_NET_FREQUENCY_NAME); 01777 01778 if (!cap) { 01779 string s; 01780 IValue *v; 01781 CCapability *c; 01782 s = CAPA_INVERTER_NET_FREQUENCY_NAME; 01783 v = IValue::Factory(CAPA_INVERTER_NET_FREQUENCY_TYPE); 01784 ((CValue<float>*) v)->Set(f); 01785 c = new CCapability(s, v, this); 01786 AddCapability(s, c); 01787 01788 // TODO: Check if we schould derefer (using a scheduled work) this. 01789 cap = GetConcreteCapability(CAPA_CAPAS_UPDATED); 01790 cap->Notify(); 01791 } 01792 // Capa already in the list. Check if we need to update it. 01793 else if (cap->getValue()->GetType() == CAPA_INVERTER_NET_FREQUENCY_TYPE) { 01794 CValue<float> *val = (CValue<float>*) cap->getValue(); 01795 01796 if (val -> Get() != f) { 01797 val->Set(f); 01798 cap->Notify(); 01799 } 01800 } else { 01801 LOGDEBUG(logger, "BUG: " << CAPA_INVERTER_NET_FREQUENCY_NAME 01802 << " not a float "); 01803 } 01804 01805 return true; 01806 } 01807 01808 bool CInverterSputnikSSeries::token_PRL(const vector<string> & tokens) 01809 { 01810 // Unit 1 % 01811 if (tokens.size() != 2) 01812 return false; 01813 01814 unsigned int raw; 01815 float f; 01816 sscanf(tokens[1].c_str(), "%x", &raw); 01817 01818 f = raw; 01819 // lookup if we already know that information. 01820 CCapability *cap = GetConcreteCapability(CAPA_INVERTER_RELPOWER_NAME); 01821 01822 if (!cap) { 01823 string s; 01824 IValue *v; 01825 CCapability *c; 01826 s = CAPA_INVERTER_RELPOWER_NAME; 01827 v = IValue::Factory(CAPA_INVERTER_RELPOWER_TYPE); 01828 ((CValue<float>*) v)->Set(f); 01829 c = new CCapability(s, v, this); 01830 AddCapability(s, c); 01831 01832 // TODO: Check if we schould derefer (using a scheduled work) this. 01833 cap = GetConcreteCapability(CAPA_CAPAS_UPDATED); 01834 cap->Notify(); 01835 } 01836 // Capa already in the list. Check if we need to update it. 01837 else if (cap->getValue()->GetType() == CAPA_INVERTER_RELPOWER_TYPE) { 01838 CValue<float> *val = (CValue<float>*) cap->getValue(); 01839 01840 if (val -> Get() != f) { 01841 val->Set(f); 01842 cap->Notify(); 01843 } 01844 } else { 01845 LOGDEBUG(logger, "BUG: " << CAPA_INVERTER_RELPOWER_NAME 01846 << " not a float "); 01847 } 01848 01849 return true; 01850 01851 // FIXME TODO Implement me! 01852 01853 return true; 01854 } 01855 01856 bool CInverterSputnikSSeries::token_UDC(const vector<string> & tokens) 01857 { 01858 // Unit 0.1 Volts 01859 if (tokens.size() != 2) 01860 return false; 01861 01862 unsigned int raw; 01863 float f; 01864 sscanf(tokens[1].c_str(), "%x", &raw); 01865 01866 f = raw * 0.1; 01867 // lookup if we already know that information. 01868 CCapability *cap = GetConcreteCapability( 01869 CAPA_INVERTER_INPUT_DC_VOLTAGE_NAME); 01870 01871 if (!cap) { 01872 string s; 01873 IValue *v; 01874 CCapability *c; 01875 s = CAPA_INVERTER_INPUT_DC_VOLTAGE_NAME; 01876 v = IValue::Factory(CAPA_INVERTER_INPUT_DC_VOLTAGE_TYPE); 01877 ((CValue<float>*) v)->Set(f); 01878 c = new CCapability(s, v, this); 01879 AddCapability(s, c); 01880 01881 // TODO: Check if we schould derefer (using a scheduled work) this. 01882 cap = GetConcreteCapability(CAPA_CAPAS_UPDATED); 01883 cap->Notify(); 01884 } 01885 // Capa already in the list. Check if we need to update it. 01886 else if (cap->getValue()->GetType() == CAPA_INVERTER_INPUT_DC_VOLTAGE_TYPE) { 01887 CValue<float> *val = (CValue<float>*) cap->getValue(); 01888 01889 if (val -> Get() != f) { 01890 val->Set(f); 01891 cap->Notify(); 01892 } 01893 } else { 01894 LOGDEBUG(logger, "BUG: " << CAPA_INVERTER_INPUT_DC_VOLTAGE_NAME 01895 << " not a float "); 01896 } 01897 01898 return true; 01899 } 01900 01901 bool CInverterSputnikSSeries::token_UL1(const vector<string> & tokens) 01902 { // Unit 0.1 Volts 01903 if (tokens.size() != 2) 01904 return false; 01905 01906 unsigned int raw; 01907 float f; 01908 sscanf(tokens[1].c_str(), "%x", &raw); 01909 01910 f = raw * 0.1; 01911 // lookup if we already know that information. 01912 CCapability *cap = 01913 GetConcreteCapability(CAPA_INVERTER_GRID_AC_VOLTAGE_NAME); 01914 01915 if (!cap) { 01916 string s; 01917 IValue *v; 01918 CCapability *c; 01919 s = CAPA_INVERTER_GRID_AC_VOLTAGE_NAME; 01920 v = IValue::Factory(CAPA_INVERTER_GRID_AC_VOLTAGE_TYPE); 01921 ((CValue<float>*) v)->Set(f); 01922 c = new CCapability(s, v, this); 01923 AddCapability(s, c); 01924 01925 // TODO: Check if we schould derefer (using a scheduled work) this. 01926 cap = GetConcreteCapability(CAPA_CAPAS_UPDATED); 01927 cap->Notify(); 01928 } 01929 // Capa already in the list. Check if we need to update it. 01930 else if (cap->getValue()->GetType() == CAPA_INVERTER_GRID_AC_VOLTAGE_TYPE) { 01931 CValue<float> *val = (CValue<float>*) cap->getValue(); 01932 01933 if (val -> Get() != f) { 01934 val->Set(f); 01935 cap->Notify(); 01936 } 01937 } else { 01938 LOGDEBUG(logger, "BUG: " << CAPA_INVERTER_GRID_AC_VOLTAGE_NAME 01939 << " not a float"); 01940 } 01941 01942 return true; 01943 } 01944 01945 // TODO generate a more fitting concept for multi-phase invertes 01946 // like "pseudo-inverters" 01947 bool CInverterSputnikSSeries::token_UL2(const vector<string> & tokens) 01948 { 01949 // FIXME TODO Implement me! 01950 return true; 01951 } 01952 01953 bool CInverterSputnikSSeries::token_UL3(const vector<string> & tokens) 01954 { 01955 // FIXME TODO Implement me! 01956 return true; 01957 } 01958 01959 bool CInverterSputnikSSeries::token_IDC(const vector<string> & tokens) 01960 { // Unit 0.01 Amps 01961 if (tokens.size() != 2) 01962 return false; 01963 01964 unsigned int raw; 01965 float f; 01966 sscanf(tokens[1].c_str(), "%x", &raw); 01967 01968 f = raw * 0.01; 01969 // lookup if we already know that information. 01970 CCapability *cap = GetConcreteCapability( 01971 CAPA_INVERTER_INPUT_DC_CURRENT_NAME); 01972 01973 if (!cap) { 01974 string s; 01975 IValue *v; 01976 CCapability *c; 01977 s = CAPA_INVERTER_INPUT_DC_CURRENT_NAME; 01978 v = IValue::Factory(CAPA_INVERTER_INPUT_DC_CURRENT_TYPE); 01979 ((CValue<float>*) v)->Set(f); 01980 c = new CCapability(s, v, this); 01981 AddCapability(s, c); 01982 01983 // TODO: Check if we schould derefer (using a scheduled work) this. 01984 cap = GetConcreteCapability(CAPA_CAPAS_UPDATED); 01985 cap->Notify(); 01986 } 01987 // Capa already in the list. Check if we need to update it. 01988 else if (cap->getValue()->GetType() == CAPA_INVERTER_INPUT_DC_CURRENT_TYPE) { 01989 CValue<float> *val = (CValue<float>*) cap->getValue(); 01990 01991 if (val -> Get() != f) { 01992 val->Set(f); 01993 cap->Notify(); 01994 } 01995 } else { 01996 LOGDEBUG(logger, "BUG: " << CAPA_INVERTER_INPUT_DC_CURRENT_NAME 01997 << " not a float"); 01998 } 01999 02000 return true; 02001 } 02002 02003 bool CInverterSputnikSSeries::token_IL1(const vector<string> & tokens) 02004 { 02005 // unit: 0.01 Amps 02006 if (tokens.size() != 2) 02007 return false; 02008 02009 unsigned int raw; 02010 float f; 02011 sscanf(tokens[1].c_str(), "%x", &raw); 02012 02013 f = raw * 0.01; 02014 // lookup if we already know that information. 02015 CCapability *cap = 02016 GetConcreteCapability(CAPA_INVERTER_GRID_AC_CURRENT_NAME); 02017 02018 if (!cap) { 02019 string s; 02020 IValue *v; 02021 CCapability *c; 02022 s = CAPA_INVERTER_GRID_AC_CURRENT_NAME; 02023 v = IValue::Factory(CAPA_INVERTER_GRID_AC_CURRENT_TYPE); 02024 ((CValue<float>*) v)->Set(f); 02025 c = new CCapability(s, v, this); 02026 AddCapability(s, c); 02027 02028 // TODO: Check if we schould derefer (using a scheduled work) this. 02029 cap = GetConcreteCapability(CAPA_CAPAS_UPDATED); 02030 cap->Notify(); 02031 } 02032 // Capa already in the list. Check if we need to update it. 02033 else if (cap->getValue()->GetType() == CAPA_INVERTER_GRID_AC_CURRENT_TYPE) { 02034 CValue<float> *val = (CValue<float>*) cap->getValue(); 02035 02036 if (val -> Get() != f) { 02037 val->Set(f); 02038 cap->Notify(); 02039 } 02040 } else { 02041 LOGDEBUG(logger, "BUG: " << CAPA_INVERTER_GRID_AC_CURRENT_NAME 02042 << " not a float"); 02043 } 02044 02045 return true; 02046 } 02047 02048 bool CInverterSputnikSSeries::token_IL2(const vector<string> & tokens) 02049 { 02050 // FIXME TODO Implement me! 02051 return true; 02052 } 02053 02054 bool CInverterSputnikSSeries::token_IL3(const vector<string> & tokens) 02055 { 02056 // FIXME TODO Implement me! 02057 return true; 02058 } 02059 02060 bool CInverterSputnikSSeries::token_TKK(const vector<string> & tokens) 02061 { 02062 // Unit 1 °C 02063 if (tokens.size() != 2) 02064 return false; 02065 02066 unsigned int raw; 02067 float f; 02068 sscanf(tokens[1].c_str(), "%x", &raw); 02069 02070 f = raw; 02071 // lookup if we already know that information. 02072 CCapability *cap = GetConcreteCapability(CAPA_INVERTER_TEMPERATURE_NAME); 02073 02074 if (!cap) { 02075 string s; 02076 IValue *v; 02077 CCapability *c; 02078 s = CAPA_INVERTER_TEMPERATURE_NAME; 02079 v = IValue::Factory(CAPA_INVERTER_TEMPERATURE_TYPE); 02080 ((CValue<float>*) v)->Set(f); 02081 c = new CCapability(s, v, this); 02082 AddCapability(s, c); 02083 02084 // TODO: Check if we schould derefer (using a scheduled work) this. 02085 cap = GetConcreteCapability(CAPA_CAPAS_UPDATED); 02086 cap->Notify(); 02087 } 02088 // Capa already in the list. Check if we need to update it. 02089 else if (cap->getValue()->GetType() == CAPA_INVERTER_TEMPERATURE_TYPE) { 02090 CValue<float> *val = (CValue<float>*) cap->getValue(); 02091 02092 if (val -> Get() != f) { 02093 val->Set(f); 02094 cap->Notify(); 02095 } 02096 } else { 02097 LOGDEBUG(logger, "BUG: " << CAPA_INVERTER_TEMPERATURE_NAME 02098 << " not a float"); 02099 } 02100 02101 return true; 02102 } 02103 02104 bool CInverterSputnikSSeries::token_TK2(const vector<string> & tokens) 02105 { 02106 // FIXME TODO Implement me! 02107 return true; 02108 } 02109 02110 bool CInverterSputnikSSeries::token_TK3(const vector<string> & tokens) 02111 { 02112 // FIXME TODO Implement me! 02113 return true; 02114 } 02115 02116 bool CInverterSputnikSSeries::token_TMI(const vector<string> & tokens) 02117 { 02118 // FIXME TODO Implement me! 02119 return true; 02120 } 02121 02122 bool CInverterSputnikSSeries::token_THR(const vector<string> & tokens) 02123 { 02124 // FIXME TODO Implement me! 02125 return true; 02126 } 02127 02128 bool CInverterSputnikSSeries::token_SYS(const vector<string> &tokens) 02129 { 02130 // gets the system state of the inverter. 02131 // note: Alarms are handled with another command, SAL. 02132 02133 // SYS reponses a code (eg. 20004) and a second parameter, which I 02134 // never saw != 0. 02135 if (tokens.size() != 3) 02136 return false; 02137 02138 if (tokens[2] != "0") { 02139 LOGINFO(logger, "Received an unknown SYS response. Please file a bug" 02140 << " along with the following: " << tokens[0] << "," 02141 << tokens[1] << "," << tokens[2]); 02142 } 02143 02144 unsigned int code; 02145 sscanf(tokens[1].c_str(), "%x", &code); 02146 02147 string description; 02148 02149 int i = 0; 02150 do { 02151 if (statuscodes[i].code == code) 02152 break; 02153 } while (statuscodes[++i].code != (unsigned int) -1); 02154 02155 if (laststatuscode != (unsigned int) -1 && statuscodes[i].code 02156 == (unsigned int) -1) { 02157 LOGINFO(logger, "SYS reported an (too us) unknown status code of " 02158 << tokens[0] << "=" << tokens[1] << "," << tokens[2] 02159 ); 02160 LOGINFO (logger, 02161 " PLEASE file a with all information you have, for example," 02162 << " reading the display of the inverter and of course the infors given above." 02163 ); 02164 } 02165 02166 laststatuscode = statuscodes[i].code; 02167 02168 /* Update the status */ 02169 CCapability *cap = GetConcreteCapability(CAPA_INVERTER_STATUS_NAME); 02170 if (!cap) { 02171 string s; 02172 IValue *v; 02173 CCapability *c; 02174 s = CAPA_INVERTER_STATUS_NAME; 02175 v = IValue::Factory(CAPA_INVERTER_STATUS_TYPE); 02176 ((CValue<int>*) v)->Set(statuscodes[i].status); 02177 c = new CCapability(s, v, this); 02178 AddCapability(s, c); 02179 02180 // TODO: Check if we schould derefer (using a scheduled work) this. 02181 cap = GetConcreteCapability(CAPA_CAPAS_UPDATED); 02182 cap->Notify(); 02183 } else if (cap->getValue()->GetType() == CAPA_INVERTER_STATUS_TYPE) { 02184 CValue<int> * val = (CValue<int> *) cap->getValue(); 02185 if (val->Get() != statuscodes[i].status) { 02186 val->Set(statuscodes[i].status); 02187 cap->Notify(); 02188 } 02189 } else { 02190 LOGDEBUG(logger, "BUG: " << CAPA_INVERTER_STATUS_NAME << " not a int"); 02191 } 02192 02193 // now also do the same with the string. 02194 cap = GetConcreteCapability(CAPA_INVERTER_STATUS_READABLE_NAME); 02195 if (!cap) { 02196 string s; 02197 IValue *v; 02198 CCapability *c; 02199 s = CAPA_INVERTER_STATUS_READABLE_NAME; 02200 v = IValue::Factory(CAPA_INVERTER_STATUS_READABLE_TYPE); 02201 ((CValue<string>*) v)->Set(statuscodes[i].description); 02202 c = new CCapability(s, v, this); 02203 AddCapability(s, c); 02204 02205 // TODO: Check if we schould derefer (using a scheduled work) this. 02206 cap = GetConcreteCapability(CAPA_CAPAS_UPDATED); 02207 cap->Notify(); 02208 } else if (cap->getValue()->GetType() == CAPA_INVERTER_STATUS_READABLE_TYPE) { 02209 CValue<string> * val = (CValue<string> *) cap->getValue(); 02210 if (val->Get() != statuscodes[i].description) { 02211 val->Set(statuscodes[i].description); 02212 cap->Notify(); 02213 } 02214 } else { 02215 LOGDEBUG(logger, "BUG: " << CAPA_INVERTER_STATUS_READABLE_NAME 02216 << " not a string "); 02217 } 02218 02219 return true; 02220 } 02221 02230 void CInverterSputnikSSeries::create_versioncapa(void) 02231 { 02232 string ver; 02233 char buf[128]; 02234 02235 unsigned int major, minor; 02236 02237 // Won't build a version string if the "major" version is unknown. 02238 if (!swversion) 02239 return; 02240 02241 major = swversion / 10; 02242 minor = swversion % 10; 02243 02244 if (swbuild) { 02245 sprintf(buf, "%d.%d Build %d", major, minor, swbuild); 02246 } else { 02247 sprintf(buf, "%d.%d", major, minor); 02248 } 02249 02250 ver = buf; 02251 02252 // lookup if we already know that information. 02253 CCapability *cap = GetConcreteCapability(CAPA_INVERTER_FIRMWARE); 02254 02255 if (!cap) { 02256 string s; 02257 IValue *v; 02258 CCapability *c; 02259 s = CAPA_INVERTER_FIRMWARE; 02260 v = IValue::Factory(CAPA_INVERTER_FIRMWARE_TYPE); 02261 ((CValue<string>*) v)->Set(ver); 02262 c = new CCapability(s, v, this); 02263 AddCapability(s, c); 02264 02265 // TODO: Check if we schould derefer (using a scheduled work) this. 02266 cap = GetConcreteCapability(CAPA_CAPAS_UPDATED); 02267 cap->Notify(); 02268 } else if (cap->getValue()->GetType() == IValue::string_type) { 02269 CValue<string> *val = (CValue<string>*) cap->getValue(); 02270 if (ver != val->Get()) { 02271 val->Set(ver); 02272 cap->Notify(); 02273 } 02274 } 02275 } 02276 02277 #endif