From 7a5f4a25e2dfa3d5386ab75e4a35af78c58ac3db Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Mon, 25 Jul 2016 13:51:47 +0200 Subject: [PATCH] - added functions SV_ASDU_addFLOAT64 and SV_ASDU_setFLOAT64 to SV publisher --- examples/server_example_config_file/model.cfg | 56 ++++++---- examples/sv_publisher/sv_publisher_example.c | 4 +- src/iec61850/inc_private/ied_server_private.h | 5 + src/iec61850/server/impl/ied_server.c | 79 ++++++++++++++ src/iec61850/server/mms_mapping/reporting.c | 7 -- src/sampled_values/sv_publisher.c | 23 ++++ src/sampled_values/sv_publisher.h | 6 ++ src/vs/libiec61850-wo-goose.def | 2 + src/vs/libiec61850.def | 2 + tools/model_generator/gendyncode.jar | Bin 86852 -> 87820 bytes .../tools/DynamicCodeGenerator.java | 99 ++++++++++++++++-- .../tools/StaticModelGenerator.java | 6 +- 12 files changed, 243 insertions(+), 46 deletions(-) diff --git a/examples/server_example_config_file/model.cfg b/examples/server_example_config_file/model.cfg index 02feeb04..ecfea166 100644 --- a/examples/server_example_config_file/model.cfg +++ b/examples/server_example_config_file/model.cfg @@ -2,6 +2,7 @@ MODEL(simpleIO){ LD(GenericIO){ LN(LLN0){ DO(Mod 0){ +DA(stVal 0 3 0 1 0); DA(q 0 23 0 2 0); DA(t 0 22 0 0 0); DA(ctlModel 0 12 4 0 0)=0; @@ -17,9 +18,9 @@ DA(q 0 23 0 2 0); DA(t 0 22 0 0 0); } DO(NamPlt 0){ -DA(vendor 0 20 5 0 0); -DA(swRev 0 20 5 0 0); -DA(d 0 20 5 0 0); +DA(vendor 0 20 5 0 0)="MZ Automation"; +DA(swRev 0 20 5 0 0)="0.7.3"; +DA(d 0 20 5 0 0)="libiec61850 server example"; DA(configRev 0 20 5 0 0); DA(ldNs 0 20 11 0 0); } @@ -29,24 +30,33 @@ DE(GGIO1$ST$SPCSO2$stVal); DE(GGIO1$ST$SPCSO3$stVal); DE(GGIO1$ST$SPCSO4$stVal); } -DS(AnalogValues){ -DE(GGIO1$MX$AnIn1); -DE(GGIO1$MX$AnIn2); -DE(GGIO1$MX$AnIn3); -DE(GGIO1$MX$AnIn4); -} -RC(EventsRCB01 Events 0 Events 1 24 111 50 1000); -RC(AnalogValuesRCB01 AnalogValues 0 AnalogValues 1 24 111 50 1000); -LC(EventLog Events GenericIO/LLN0$EventLog 19 0 0 1) -LC(GeneralLog - - 19 0 0 1) -LOG(GeneralLog) -LOG(EventLog) -GC(gcbEvents events Events 2 0 -1 -1 ){ -PA(4 273 4096 010ccd010001); -} -GC(gcbAnalogValues analog AnalogValues 2 0 -1 -1 ){ -PA(4 273 4096 010ccd010001); -} +DS(Events2){ +DE(GGIO1$ST$SPCSO1); +DE(GGIO1$ST$SPCSO2); +DE(GGIO1$ST$SPCSO3); +DE(GGIO1$ST$SPCSO4); +} +DS(Measurements){ +DE(GGIO1$MX$AnIn1$mag$f); +DE(GGIO1$MX$AnIn1$q); +DE(GGIO1$MX$AnIn2$mag$f); +DE(GGIO1$MX$AnIn2$q); +DE(GGIO1$MX$AnIn3$mag$f); +DE(GGIO1$MX$AnIn3$q); +DE(GGIO1$MX$AnIn4$mag$f); +DE(GGIO1$MX$AnIn4$q); +} +RC(EventsRCB01 Events1 0 Events 4294967295 24 111 50 1000); +RC(EventsIndexed01 Events2 0 Events 1 24 111 50 1000); +RC(EventsIndexed02 Events2 0 Events 1 24 111 50 1000); +RC(EventsIndexed03 Events2 0 Events 1 24 111 50 1000); +RC(Measurements01 Measurements 1 Measurements 1 16 111 50 1000); +RC(Measurements02 Measurements 1 Measurements 1 16 111 50 1000); +RC(Measurements03 Measurements 1 Measurements 1 16 111 50 1000); +LC(EventLog Events GenericIO/LLN0$EventLog 19 0 1 1); +LC(GeneralLog - - 19 0 1 1); +LOG(GeneralLog); +LOG(EventLog); } LN(LPHD1){ DO(PhyNam 0){ @@ -92,11 +102,11 @@ DA(q 0 23 1 2 0); DA(t 0 22 1 0 0); } DO(AnIn2 0){ -DA(mag 0 27 1 1 101){ +DA(mag 0 27 1 1 0){ DA(f 0 10 1 1 0); } DA(q 0 23 1 2 0); -DA(t 0 22 1 0 102); +DA(t 0 22 1 0 0); } DO(AnIn3 0){ DA(mag 0 27 1 1 0){ diff --git a/examples/sv_publisher/sv_publisher_example.c b/examples/sv_publisher/sv_publisher_example.c index 9d42d53a..b048befe 100644 --- a/examples/sv_publisher/sv_publisher_example.c +++ b/examples/sv_publisher/sv_publisher_example.c @@ -20,7 +20,7 @@ void sigint_handler(int signalId) int main(int argc, char** argv) { - SampledValuesPublisher svPublisher = SampledValuesPublisher_create("eth1"); + SampledValuesPublisher svPublisher = SampledValuesPublisher_create("vboxnet0"); SV_ASDU asdu1 = SampledValuesPublisher_addASDU(svPublisher, "svpub1", NULL, 1); @@ -51,7 +51,7 @@ main(int argc, char** argv) SampledValuesPublisher_publish(svPublisher); - Thread_sleep(50); + //Thread_sleep(50); } SampledValuesPublisher_destroy(svPublisher); diff --git a/src/iec61850/inc_private/ied_server_private.h b/src/iec61850/inc_private/ied_server_private.h index c32e43e8..df140828 100644 --- a/src/iec61850/inc_private/ied_server_private.h +++ b/src/iec61850/inc_private/ied_server_private.h @@ -42,6 +42,11 @@ struct sIedServer MmsMapping* mmsMapping; LinkedList clientConnections; uint8_t writeAccessPolicies; + +#if (CONFIG_MMS_THREADLESS_STACK != 1) + Semaphore dataModelLock; +#endif + bool running; }; diff --git a/src/iec61850/server/impl/ied_server.c b/src/iec61850/server/impl/ied_server.c index 32ebf60e..92725320 100644 --- a/src/iec61850/server/impl/ied_server.c +++ b/src/iec61850/server/impl/ied_server.c @@ -406,6 +406,10 @@ IedServer_create(IedModel* iedModel) // self->running = false; /* not required due to CALLOC */ +#if (CONFIG_MMS_THREADLESS_STACK != 1) + self->dataModelLock = Semaphore_create(1); +#endif + self->mmsMapping = MmsMapping_create(iedModel); self->mmsDevice = MmsMapping_getMmsDeviceModel(self->mmsMapping); @@ -477,6 +481,11 @@ IedServer_destroy(IedServer self) MmsMapping_destroy(self->mmsMapping); LinkedList_destroyDeep(self->clientConnections, (LinkedListValueDeleteFunction) private_ClientConnection_destroy); + +#if (CONFIG_MMS_THREADLESS_STACK != 1) + Semaphore_destroy(self->dataModelLock); +#endif + GLOBAL_FREEMEM(self); } @@ -891,8 +900,17 @@ IedServer_updateAttributeValue(IedServer self, DataAttribute* dataAttribute, Mms if (MmsValue_equals(dataAttribute->mmsValue, value)) checkForUpdateTrigger(self, dataAttribute); else { + +#if (CONFIG_MMS_THREADLESS_STACK != 1) + Semaphore_wait(self->dataModelLock); +#endif + MmsValue_update(dataAttribute->mmsValue, value); +#if (CONFIG_MMS_THREADLESS_STACK != 1) + Semaphore_post(self->dataModelLock); +#endif + checkForChangedTriggers(self, dataAttribute); } } @@ -910,7 +928,13 @@ IedServer_updateFloatAttributeValue(IedServer self, DataAttribute* dataAttribute checkForUpdateTrigger(self, dataAttribute); } else { +#if (CONFIG_MMS_THREADLESS_STACK != 1) + Semaphore_wait(self->dataModelLock); +#endif MmsValue_setFloat(dataAttribute->mmsValue, value); +#if (CONFIG_MMS_THREADLESS_STACK != 1) + Semaphore_post(self->dataModelLock); +#endif checkForChangedTriggers(self, dataAttribute); } } @@ -928,7 +952,13 @@ IedServer_updateInt32AttributeValue(IedServer self, DataAttribute* dataAttribute checkForUpdateTrigger(self, dataAttribute); } else { +#if (CONFIG_MMS_THREADLESS_STACK != 1) + Semaphore_wait(self->dataModelLock); +#endif MmsValue_setInt32(dataAttribute->mmsValue, value); +#if (CONFIG_MMS_THREADLESS_STACK != 1) + Semaphore_post(self->dataModelLock); +#endif checkForChangedTriggers(self, dataAttribute); } @@ -947,7 +977,13 @@ IedServer_updateInt64AttributeValue(IedServer self, DataAttribute* dataAttribute checkForUpdateTrigger(self, dataAttribute); } else { +#if (CONFIG_MMS_THREADLESS_STACK != 1) + Semaphore_wait(self->dataModelLock); +#endif MmsValue_setInt64(dataAttribute->mmsValue, value); +#if (CONFIG_MMS_THREADLESS_STACK != 1) + Semaphore_post(self->dataModelLock); +#endif checkForChangedTriggers(self, dataAttribute); } @@ -966,7 +1002,13 @@ IedServer_updateUnsignedAttributeValue(IedServer self, DataAttribute* dataAttrib checkForUpdateTrigger(self, dataAttribute); } else { +#if (CONFIG_MMS_THREADLESS_STACK != 1) + Semaphore_wait(self->dataModelLock); +#endif MmsValue_setUint32(dataAttribute->mmsValue, value); +#if (CONFIG_MMS_THREADLESS_STACK != 1) + Semaphore_post(self->dataModelLock); +#endif checkForChangedTriggers(self, dataAttribute); } @@ -985,7 +1027,13 @@ IedServer_updateBitStringAttributeValue(IedServer self, DataAttribute* dataAttri checkForUpdateTrigger(self, dataAttribute); } else { +#if (CONFIG_MMS_THREADLESS_STACK != 1) + Semaphore_wait(self->dataModelLock); +#endif MmsValue_setBitStringFromInteger(dataAttribute->mmsValue, value); +#if (CONFIG_MMS_THREADLESS_STACK != 1) + Semaphore_post(self->dataModelLock); +#endif checkForChangedTriggers(self, dataAttribute); } @@ -1005,7 +1053,13 @@ IedServer_updateBooleanAttributeValue(IedServer self, DataAttribute* dataAttribu checkForUpdateTrigger(self, dataAttribute); } else { +#if (CONFIG_MMS_THREADLESS_STACK != 1) + Semaphore_wait(self->dataModelLock); +#endif MmsValue_setBoolean(dataAttribute->mmsValue, value); +#if (CONFIG_MMS_THREADLESS_STACK != 1) + Semaphore_post(self->dataModelLock); +#endif checkForChangedTriggers(self, dataAttribute); } @@ -1024,7 +1078,13 @@ IedServer_updateVisibleStringAttributeValue(IedServer self, DataAttribute* dataA checkForUpdateTrigger(self, dataAttribute); } else { +#if (CONFIG_MMS_THREADLESS_STACK != 1) + Semaphore_wait(self->dataModelLock); +#endif MmsValue_setVisibleString(dataAttribute->mmsValue, value); +#if (CONFIG_MMS_THREADLESS_STACK != 1) + Semaphore_post(self->dataModelLock); +#endif checkForChangedTriggers(self, dataAttribute); } @@ -1043,7 +1103,13 @@ IedServer_updateUTCTimeAttributeValue(IedServer self, DataAttribute* dataAttribu checkForUpdateTrigger(self, dataAttribute); } else { +#if (CONFIG_MMS_THREADLESS_STACK != 1) + Semaphore_wait(self->dataModelLock); +#endif MmsValue_setUtcTimeMs(dataAttribute->mmsValue, value); +#if (CONFIG_MMS_THREADLESS_STACK != 1) + Semaphore_post(self->dataModelLock); +#endif checkForChangedTriggers(self, dataAttribute); } @@ -1060,7 +1126,13 @@ IedServer_updateTimestampAttributeValue(IedServer self, DataAttribute* dataAttri checkForUpdateTrigger(self, dataAttribute); } else { +#if (CONFIG_MMS_THREADLESS_STACK != 1) + Semaphore_wait(self->dataModelLock); +#endif MmsValue_setUtcTimeByBuffer(dataAttribute->mmsValue, timestamp->val); +#if (CONFIG_MMS_THREADLESS_STACK != 1) + Semaphore_post(self->dataModelLock); +#endif checkForChangedTriggers(self, dataAttribute); } @@ -1077,8 +1149,15 @@ IedServer_updateQuality(IedServer self, DataAttribute* dataAttribute, Quality qu uint32_t oldQuality = MmsValue_getBitStringAsInteger(dataAttribute->mmsValue); if (oldQuality != (uint32_t) quality) { +#if (CONFIG_MMS_THREADLESS_STACK != 1) + Semaphore_wait(self->dataModelLock); +#endif MmsValue_setBitStringFromInteger(dataAttribute->mmsValue, (uint32_t) quality); +#if (CONFIG_MMS_THREADLESS_STACK != 1) + Semaphore_post(self->dataModelLock); +#endif + #if (CONFIG_INCLUDE_GOOSE_SUPPORT == 1) MmsMapping_triggerGooseObservers(self->mmsMapping, dataAttribute->mmsValue); #endif diff --git a/src/iec61850/server/mms_mapping/reporting.c b/src/iec61850/server/mms_mapping/reporting.c index a94d59da..9bd155ba 100644 --- a/src/iec61850/server/mms_mapping/reporting.c +++ b/src/iec61850/server/mms_mapping/reporting.c @@ -542,10 +542,7 @@ updateReportDataset(MmsMapping* mapping, ReportControl* rc, MmsValue* newDatSet, if (dataSet == NULL) { - /* check if association specific data set is requested */ - - if (dataSetName[0] == '@') { if (rc->buffered == false) { /* for buffered report non-permanent datasets are not allowed */ @@ -1643,8 +1640,6 @@ printReportId(ReportBufferEntry* report) static void removeAllGIReportsFromReportBuffer(ReportBuffer* reportBuffer) { - printf("removeAllGIReportsFromReportBuffer\n"); - ReportBufferEntry* currentReport = reportBuffer->oldestReport; ReportBufferEntry* lastReport = NULL; @@ -2393,8 +2388,6 @@ Reporting_activateBufferedReports(MmsMapping* self) static void processEventsForReport(ReportControl* rc, uint64_t currentTimeInMs) { - - if ((rc->enabled) || (rc->isBuffering)) { if (rc->triggerOps & TRG_OPT_GI) { diff --git a/src/sampled_values/sv_publisher.c b/src/sampled_values/sv_publisher.c index 7960a7c2..5f812fb2 100644 --- a/src/sampled_values/sv_publisher.c +++ b/src/sampled_values/sv_publisher.c @@ -545,6 +545,29 @@ SV_ASDU_setFLOAT(SV_ASDU self, int index, float value) } +int +SV_ASDU_addFLOAT64(SV_ASDU self) +{ + int index = self->dataSize; + self->dataSize += 8; + return index; +} + +void +SV_ASDU_setFLOAT64(SV_ASDU self, int index, double value) +{ + uint8_t* buf = (uint8_t*) &value; +#if (ORDER_LITTLE_ENDIAN == 1) + BerEncoder_revertByteOrder(buf, 8); +#endif + int i; + uint8_t* buffer = self->_dataBuffer + index; + for (i = 0; i < 8; i++) { + buffer[i] = buf[i]; + } +} + + void SV_ASDU_setSmpCnt(SV_ASDU self, uint16_t value) { diff --git a/src/sampled_values/sv_publisher.h b/src/sampled_values/sv_publisher.h index 13466405..5612aa96 100644 --- a/src/sampled_values/sv_publisher.h +++ b/src/sampled_values/sv_publisher.h @@ -80,6 +80,12 @@ SV_ASDU_addFLOAT(SV_ASDU self); void SV_ASDU_setFLOAT(SV_ASDU self, int index, float value); +int +SV_ASDU_addFLOAT64(SV_ASDU self); + +void +SV_ASDU_setFLOAT64(SV_ASDU self, int index, double value); + void SV_ASDU_setSmpCnt(SV_ASDU self, uint16_t value); diff --git a/src/vs/libiec61850-wo-goose.def b/src/vs/libiec61850-wo-goose.def index 1113aa2e..68873dbb 100644 --- a/src/vs/libiec61850-wo-goose.def +++ b/src/vs/libiec61850-wo-goose.def @@ -547,3 +547,5 @@ EXPORTS IedConnection_queryLogByTime IedConnection_queryLogAfter CDC_DPL_create + SV_ASDU_addFLOAT64 + SV_ASDU_setFLOAT64 diff --git a/src/vs/libiec61850.def b/src/vs/libiec61850.def index 7d0c71c2..157f2161 100644 --- a/src/vs/libiec61850.def +++ b/src/vs/libiec61850.def @@ -623,3 +623,5 @@ EXPORTS IedConnection_queryLogByTime IedConnection_queryLogAfter CDC_DPL_create + SV_ASDU_addFLOAT64 + SV_ASDU_setFLOAT64 diff --git a/tools/model_generator/gendyncode.jar b/tools/model_generator/gendyncode.jar index 3e9fff307e11f1166932ead24d793512df985c79..e25b642dda0e2810a50a3710ae2df9e3372cd55f 100644 GIT binary patch delta 19185 zcmZ5{V{9ef7i}@MZQH%IJGF0Z+jghzovCfxcBi&&+qT_#zyJI8Ub3=xva@rNvp$@& zS9aE52~<=K6q1r0xm~{y^fW*+dbR>1PWzKio$JOznbPvIc!t^+PB9 z8o9&pZ$#+U!JmMzdT7@nRGdEpc!Ygx`Fg)7En`hsqeO%P_Yfs<_pQXD%qm83_qD`8 zQ7+0-g-C#5R8u5COeotnDh4884izedXBu^cpkNkNB7~M}kWx&ZYY?tItsoLzU!HT2 z14kZbL7!D&(x`w2-=u^LI6uUQGl{+6?#%YWr;Ag18+43oQiC8XQBHPDwTDo^2lI93oO9piBW2^s7pxFi*6jh!o$A@l0#0Hd^?wamenmi$5UzmiGrMS4@ ztz=n*Eg;>oh-Ddvu_HOBkWF(9@`muFj7d_ zqUAOj9RP#XdS02#%lP{q!0r45aC-Iq|zRuT4}%*2Y}3+a$7Y z`>;}^JH(Y6qq-?ne*K`8DOAvm%QtV%`a_7}4 z!DQVWgPP%9^{?wx-Dv*+gDp`n*<{_^gD6>_bEv|2`<7y#E2urV`~n?_3z9IV)*aN_-Vu6?_#u6?U@#GIufj5P6QGiMT`b&Up^A z2887}R5OCLwe zLkHO}+ypzn=u7j_?N89$sw2+vRj|cFT_AZ-ZVBB z*uVk2Lsp)EJ|$?g_m=TOL|*ySH-V5nfoQZ4fs7JnDK?&vzWNYC^U*oxR?Qe=*5$YZ zMBZ&E&-mq0IOf(wtQ+9}w1 z=tWC~Si;7W)yJ_M?4b97ZduxKi0Dlon2rcEazYQ(Mg&^0X7j)$&)AE0 z;+{hE>_(|V^gtC8=CBqb?oFi*^6KR?CwF9&EX>spDbtnrukc+$!lJT!<;9V5FpD2u z>X5BwJfbGyEe-99hK461r&y|j{nMJs8k)$ocPrkN456__)D?Crf@pFrwq2Z%)T+t3 zr~<_ZZ9d)VThqE%Pru|UJ4?OKC08`nyv|YixX7$ll!Pn)@+sOYWP{`tntEzVY~0ip z)*v!|%IdFmo=p=*$l=^)@ue)SEO}Rasgm2A<4l|0YyD)Zu%R~%SL2i`s!L>{!U18B z+XVdZMZg(B&nv)%NJsvYFk~t@qB8pE{pr7d8q}WgzK*MM<(sUag<8OwPsh~QxoD`f zf5%@oX3mL#v#j}RLiTGr!zM^1SdBGroPXwmj)3p9i~<_@Hd!4hOcbA$noa6nfxeP# ziSgmrxRinFEN8|YJjd~?cmi*Bk9HfRD zzur_{{<5HxR8`uqdzDA29YnpNJf~8Cs_`nbWUdxXLq-|3J0A~g z+aP+ZqNG5?dp!R)u~lP9)Oev8)O1+nzaVopGOLHWj(va0$eA{dFVfF)mkLT@qNT5H zx^EXi>f>~C5{({9Z0*m7)VR$MviLFLmm z3y83i8B(=m-zc46M_t$SS-qF$uNd~h{xu;0wRAZjGi@U>sQfzcF_c)gRU5557+odQ zwS4I2O)VPAX41UFo?k+3*92(15$uh}{^Qm$kWlxuvE}}EqEySD zt-T^rUme%QZ*J)<|NQj+8mQQO;FR%R;B5EJbHG?4B6SOs&%+QC(B#p6*V=MOLb(b* z{18Og?Ad_zhj6FJyEI`ZJmVz(E(DAH(`!AN^<ZexAJ@}d-9(xL`;b%toVd~wSSer{R~HUiGz%_ z{+pJfwz{>xbsEi7BK_SWQ%PEo%ww2xMC)M0w+NbRX;&sZheHn?_BO6T1GSIrg!djW zmL2oIYn5?nVEW6yy@E3Eg;QHv#^p*z2GN|Sz8qI8%{{7IPkw~5=9}rs8#X_d3`W9B zeKOJPx#3^d*U4nzuDpD1#pv|V&xN4}Qov8~uhZyKh_eP*!HWH3LkPjabke*!P$14P zc$zphyjndQJ?qhBH#u7BT!hcG_-rcYq{6Zc^4O92&L{{L>Hxv!D+=g_j1a<%K3ShddoX_ z+$l;O_7|tJ+Cfk(h3*u@igM!>)TzsFtvl8CdKe_b(U=gNL>Vm+0wHv%6IdkziZvWZ zZ!TC+zOK~MfFmfiuuP}IaB%2M=SVu1B%Jhn6YubFlzksn19}$rqoaFxeFN+9O0Rc6 zT3=6Hba%~_*DWm0r==qY$EFT5`TULmo~XDhG+k}-O8UJ| zn@%cs+Px(4q=HLy_{#w#wf|7q4r^-?HM3(%OIj;}F#DZ1_5=o^2kR?}kc`dQy|#-R zOU+$MM}K^0Q^sq#bE40_HH3@!8twU-LeEOMy2%6k8cl{z??*>RdV!lf;c!#q)b(RfnWZd0#9;wmVMf zT6VAaJ_f{XQc_5lKsxRTroUni#T%vmqkmh*`4xPzb19TN3M#Zgn^GN$rAUF9-{MQ! zpBe2{%ZZVB&P?Ijk9qR+mJL-l2JDR=Z6I$xwfIr2p#^vZSB1B*O>r{iN^Jf8QM>_t z>h5qyWq})Q*?jq?j3C{Iyy1ym^rX`W&qizgO23L1LVyV z)Lo2VTPbj)p@{ask0|pV;E?&DiEW}P#GE@zRsTk}tmhZ=6*H6cPiXLVfbn3va{M^` z-r51(a}1ud-pO_pm}9(Il>+fOx*L%u6uJ`|&k#OKl^|geMD41q6|2y8fXWUF^F-Yp zc$4u;@olz0^J<6z+FL|<%?{rIvAtq}$fIEoR7Z%~4xD7ndickhj~X|<5kd%>+>Hi` zgiCy5?1*Dc zf|cQiqw9y+EkAL|6A9JL#<>qOt0EkEGlb%n)$W(>?cZYPc9LHjr%zfzrNDGn%lX4{ z*Ws89KAY*Z@WKe1C<5E@msUQCW}kI4pD6aD54{b#g6AOXLU@FdX~dR)zXk0x>ol3F z_Gs<YLSlBy_8Ti@)OrsI4b)Ig4BKyhOV-f& z`eEI@>2sjY9B5B8*F5q|uP8g8kXJ|bTgtz3;zs^WFjHfNywnERWGN%kw4*A6QC_Jn zJw_JTDE|gY5$=yU=@B`YrE&G3q48@R&pl(M?@QTV9Kn}z-AV`aDNwwmO_@Equf&)w zQ57ec=5sRoerAyCX^>14m58s+-(69%s7Sd$jxe&ksER;;g^7q_RnJge+kh*NVF{f` z;rtAoA&`ti4yx*t1q!3)9{w%oz@3XnpEGeL0Sc$o>enf zR+Jh)8re=0@9wkq>MAQtk`?_`dVbMB(j2)yRxk4kGu0+#`*6i&i#hvRx1xyWniNbk z?rXpu%ICu1PF(B!^60(Z_8HRO@43-SC2}gRWma{1 zc4U||`e=-bx;ih?u_%(Rvd+}<^#{9!bYE!2Hb{^n2NQ7CtRf~VeM~xA%~I7}S>N3~ zt-ReiJX6FIS`hcqY&KHF5!!DQuA+OBHgYBWnu$e!5IVu8EjA^#84X%uh$GBj-pJ|U zM?fh`u{wuOZcokPTKfaNp|S>W=6Q}|WR5Tu?IywWzNlGHK6%P@+Bj~4H1^=Kt93^0 zD;<;_vVj)Vly!tOZoXp%Q-ckzvh#yqv9*uaiFA+xFKvR0RRAcz^l4GJk{3p#vKT|f zG<^nNjS$bDwU7VLYg>j2ySof`}E+8EXs#9B&c8Y_&W0Iq6}~| z-LXK4?7nWTf*?xVH7OKFi0(NXN{-&Ppm+T6OFFaYiT-DcubR4hRDF0z|03BVsMq_O0XBI)#eT~Q?TY1qc$%e$L`?t<-1*bSx>Vbb znS#Vn05OQ%#MHVtbmwQVfVAZ7cYy$c?(V0GA>hsM!Y-{{eMy6Q-5}d|uB=jEDf#;i z+vN`v)yM3LA1(A;kV$N$^?kYWfD3akEeTY_qD1k?&LEo@MLZ_VoDF8lm`MK2@<)jj zj{g3d=c>A!u{HOW6^yQ>lKZA9%5nl-)8C(Sz<0(#2;DiImYDj$O z2qU+i2!f*sfi^^arI$mbprmPIwvV6b#Ifdj`1`sGgL8m-zm9tU#_=q9j+VrS1I-W8 z4xyN-qt?WAHX6{gV~OVWz&rHdzG`Xtpy>LKn93SMp7I(2T&5wz&NGx^@M^mvMx~6uglk#<>#_8_f~tzh49fs zK1YS@m7XX+@gmZ!7fv8yZyMB5^LlJW^^sY_QO1O*n7Z~?+Rar)T;2X|cusMjbdq%v zqn;k?GPOd4?Nzm&2LP|m9jjo-Ye14O=s&9-)fHUom_3V5Vc}0a zebT<;uC;A{Iu4__i#j#uoqP_0zsM%(ujAokt>nC3bXF}K|D?jOJ7T6jsHRQdW!wlp z^4RS!WUOkwKH*ExkmbLX|BF=M!WOxGQRrfu%?;}6rpr+=RA}-OoT(E7iv$?t+II+y zvDPw>_h-%80;S;WV+FS_%IQn58+9>#DX(uWVp=jxbWB<97z@^P=S`evg*=R_sS@J| zv;T6gi`x<>Vr!~tTibq2JrphrP&gI`4^bN_uB13sOff4`5iFXaXlt}hz1#x29EOlP z4?v2@GQHi#y`9M0>wnKH3NMD2gUs2QlNM%)j(2InPN4&06T$CX#^E{x{5AL0HQsGb zyparS1#4Y%Rz`MfhY6}4qk&%vfG69m_sr~fU6XT1_j52c zN2l|fi1MC|@*bL!PlL`G*_~Jvtr-~SF3{8FZxrpBQ0Ky@%|ijmX;*fKd`r)QCM=Tt zPcC|2EDl!i+p){3-)k-yu+RyAO!5HIMtPPe_7D{Os`decm-f3EZFmV>fAE0Rstrg~ z8x8?UPSzbx);6siOnU1=dKtvbQ?RKX{{b=8B1DegK5&}dl}jwuSJR`U8LnLgv}CNd z#H5Pwo4*(|o(1NnIMzrEej2}?_!G>>K45z-eVF<*F*~Of?%l#MXqyfaBF>#+?pVI; z=M>UU;o)J0xa{;gjCBjkc8EB!HfSrcv&kT^LzNgEvzwrzP*RpA5EwM;cdxu3&E0QX z2-dKBq(Q=I^K)=qd2gF(?RO+ZP;VRw` zum|mtKf|AbTZ?=fc-WJw1WAALJ^Ef^nzeh zjo9=;1ovbo11bS1HGRfTL@~ijUILrHshlu!V7U+MTA^@y?OQP&Mk4osD&`uW9d}%< zI5&SjWWq@(CANObzOzo5VhDkQ`zBc=z`#X|3&@1>@(Gm$YKE3C3p<{YoX_HsZY`Ar zhR3R6_!H^NUV1^~Qrgpi1Zl8g=^^DNCC>qjL_op|%2P{dqa<~%jPbNgD`)mV#2-cs z&i822!S;iFSF78;^GT7o8=vqTcA; zeY>ecsZr;NR%MWu7M?CgmQ7?m+J$)BMZu_RLj037Tw!o9prnF9stD5B4|c&+2@Gy_ zzdft5N3@w-F0tR}3&!q(a>@%fHjSI;qArVR3uMrYPb6E)8g($Go(d#5(g(Y&=IkYP z?5TC_*UR(aCc`nx8o+%d{$`0r;%rf(SGf0fL*-FoYlv)gq}Iqh5I z^(Wpx9kW^vnc$;=^mFBtdGncD`s|(wN)Krq0(sflFE?i=K_}8OxuFBQ zP;7lT`P`j|ytnX#;I;jB-H5;gEpj2~Bt}6-t6#5(cP3J! z&%_Wi#b8e$GlTGQ;*GAXMA4Ih6cMYuKuGjozf@M5gp?94zZ*lAo-Z+rW?Fq$L8wY;e<` zU9TF8Ru{OL%O$aetHeaj78Z<_ zXiA*w{cVssZzT%U-|8$d#3*cUn;Vx^U(w+Mu_Hz-T(0llc1~TiRtHZdNLv$yFJem< zHOGj5U`zjXBnpqhmNxqvBR+yH?Pf<54rGcuUF1WTJa-2UT2^Ct;SNpxCK55Gu>wWv z=Lsg86%Zf~5YJ-#62X4{BM{*hYRnZiq(?jOtrQ4p3_uRCy>p6i3GF}d!o2MFeFYCS z6&ApUIRNk=NbQ*rMqBP_b)q)jx@1Y~wnF$q#g4Sp!+P$S353zWa;Jn5W>Hh`yE)V6 z(4OB>I#W%f6F4_}5uSvhIM?umJ%G%}QznE?<1y-0Qt~uY_RC#(nd3Z-lYJp8k7%?C z6P)A6f99V5@#n|5BH#=$y7^yffelx-1FwgY>P3nQv*oV*jw zPYN^M%+ggpB8>)EqAG1BCDhI_=X0><`P-`%G?s;%1Iv3}g-W7+(_Y)ydxlj|U^5C0?aPTxCSxL)&}rp~&PGZQxD0m8Ul(p~!5i zPM+G=TH8znIsSl3OiMpC!g3z(Z|B}w;_KTyx%5utDVdgUFrS}&H4-}*4;j`r{)2dY z7io4%x&B+X=qE`${DC+=W`vs-DPJ!VImUAMv{TC|#7lV1Z641g`rzv_>^|vZGcT}h zOKF}>b7!z6D>0Zj?~ll)K2vCgTk=WgPAesgKGe%2=q+O)I7=E^{Q4o*WLW2ho4nxT zBke($AihiXUdNKq4*7Qfawu8fz-%QlL4@(a`qR}$1HNl4dX_-y zS=oo3$1&9X9&?GW*88g8muPHl)~e;|x9`Q-W!S?${dTig#0b_#_VI?UUAN%s=Ny>8 zF$Xu>6VdF6()b2gs3rd0ZBz>PiOA+iHkyDji)#Gw?xfHVAksb^dM%i}lnc1`JatP6 zs{Hx`GhhQ^Zl&XmU+n(9`0b_gL9X&4Q}moDbEQbylq+f@oV@6dU;gE`%3xuH1Hkwd zshvgz942&zCuTU_Eib^FL4UY)P>w-?Md_xdzqMBm{}rSOt4y_7p!0~)WD=)EDG8s8 z%ksJZ1E4&KD#y5h3vV&Xv7BiA$c5Ysr}axpu))#L)GN_J1P`>fOi>$8(ZSbD!7_e@ zpQO`mW5P$zC=<=DDEQ-_0SpFPQySIzSzcI98~A%LvX(jg5E6f@e<>B|^fN+2Bcrmd zP54ZRMhC8(FLMqRAEaFul?M{Re#T}End>hOVX{y+K{5e}E>6`&(~>eNU_D;0y|$0| z%rxS@EwAFO_c|y=OL62qqf;do78zHeIl=|wBfo?t?rX4$P9zD3D5(_UR%IcXChVyp z@JSZTAX-1m0HOCn?*$r03f#-Ji9M~QH4A5_M{_agUdv>G`tOrf-*9?Y)>f_7kZf&x9<9`K6 zzxTE8>Q|vic$rUiBZ@_`pi6j&_#)VMVV$uF8K(cJ4z|^aR#D>ogPeWF?*@{RzB>88js7B& zJYCwmZKHg-f0o5J6VV^S6lU#?JbwHTZc^M6r=k^Vk`fYReNZov{Za($Hpf}?B^DA=z57)TuBj6kK3)IW!Y}~SsTPrKLD?NyA=(SgKyg|HA zA&r_lyZ=R36giVu`AX;$&O)l`3nezV!m9TPtMc0df8N$H2tR(2>6$%nYKi()C8Q z;4g*mJjs&!C13FA%{u<)By0ulQ9y7d!^k;@y(`#z`f_;$#Vr7>7Mqh7PseZ|(e%=O z56g%^VP3{zC#oVJba#Ji_N#8yoXahR*C}Z-Fx>?#Y5b28RcJ4|H%}IIY#sFqaZ%Tr z5ZIANUJF42(Z46CuWm$O>`YG8nIJKH7gFEZ7l>pexUHCTpt|fSL0b*iLYXUWB2fx0 zGUPuD;Cge^TTy1QNcHKvAXcTCT3fOq6DJ-pX}B8+)@g&>HJto#(f#Lox4#)a*V}UH zW0OSYAHHwJJLX5{XWMX;k}mWvyVP^ey|Yn(psnCMqa{; z8_+bwCz!-f{yf~Ou=}7EN#p){J5Lip<{LlA?_do2c0s?hVw+5$`7H(f(`XK-M_byT z?Sj8aQ>qL41b}(V{9u`Tr5+618iejHZ3sTX%Fr>{ET4)XR5G!$37=Aoj!Q6&Uth)i zCABAEFzHOc_D4$DhCsHMhc1)`w=Xe$H-SbZCkskyJkdBJ3yEezbu{a^SHXq3DzUhT z`@yJ@k%IY?#%-f4uC*7zy+(}FruVAfw4nr}eFzJ5*@je^Pz9#1k#LfsnA9_LV>iBj zptch7to6xtTjHB6<`G(cskgNI`GZ#tr5pF}_E&vhg;17DOgZ{Mqyc_OJsK5sogbxP zOkQ<`ddS&Z4aelDqKT13yN8hzpst2ei$rX25agQ930sIG`DFrMmhIQyc@l=w&`tE{(GBy6T2)3PemK z>KKOy=R9vg^a+Z5p(R)NeMsTc%hCVVM&J~6J*BG)@h{=+GpbjNqhogXJ%QeR!a{+~ z{&*jIT=b`F!lop8DRbF*G=*qBfoQogL&qed)4uTslhFd*;Pdd7%)&^#4@;o8aAQwF zj^9$vr!|hR|F)ilk7~rKq7G_e-%B^2W=mo`Rt-H@1~OE}9H;e9{^@%P3JlMv%!N@q zxJWm;i6K9NF7r%bJlrNO21ZL9v`o1t`H`612I_gav&Oal{U&h0@fi5%81xmU$ff4) zOx(O}JPgUD;r=9V+e^u+?jq}C3Yy-~|2I*N4p!qg<*^n1J!Unbm`27H2bjK|j1 ze&Qk=(eOz4N2uSk>jBMiMp^DO-jIaIdxlUBJzEXGNjL9l;;BH`g-0v}qTkdNM>qTU zZ>6n@*Jv!Ge)~vmhM%M;vc*5C>65;alMa%oC)5M{UF7mU{Wm_j zA?&dqa+D;)$nGG4a5M7oLmu@1M5sDicpthv1q8o_3g&vA-(f6_j zI7Fx(ELCPBXD9fRtKV_XWMR)hcLR*gLrnPmiu-dbe#;s2`0UexQU%#}r@RtFBFJ3% zQ8+2MDGRg!UBP3+sb~bGJ^s;Cub$6vfflKoM9BwO7A6gq*F^^0q^l8`(F+siV-L7B zVWyNX7`46QycMzU!ntF27Eb>mS#)_-t*&de58we)T%DXEgP3%I!CLz>d{#W+0KNpq0v8 zMGO7niR5XoL_D=SBok(yh6s&2S+x$%0HQlxRta;c>mt@{UQNKrqWj@wO{o4k^lGJp zsD3qBs0RXZp~JwP4HL79=PrE{GK-4m9w_oY5EUdJF0(gj_ECFXI@p(gDr!{|q-riN9lEdyeU%hs@K>JEKF)+Hhso;$ z>=1wCQwV;z5sAm^(o^3A0`MH(J5Yujt#3-=`q}K=i|p4M9E&D81-q?t=kXU}K+ow4 z3YosgS-<@z=tP&w-L9v#jz*CqYZV3fTr+2Bzxd%Lua1XYne?`G0x35ei1w!P9k^K# zjJ!GcGq@|0lJcQB@8T350`1qs8cZnGe~v++8Vn(84aS0L)P($iH@lkjWuUl>S{bM1 zcdbYUTuwqTB$`-OR7Gt}GyY{80j;N>FU8$I!b=A=68uzjZn@Mq(Ne2bNnIJ4)mmP$o3gea=o;-rehbF85ebduR7VqL<%Ik$Nj=0IK#Md^)%E z)%psEEi=5cej>P5>p8GJNAXqt3MZ-DYL&a{yIC~&RK6P6vJt*kg$XJ_h8N?EP(u=L z4=Ko`7EdV=uSp@Lwh1Yl)icRYp{$ur7}D3#y^C803KSPdr3{wC4R?(Obyg-=Y&o)-=_TNOO+ zu&zb`*n>_x&Y!G>>aT%d+J?l#w%yZxb6Qyw2p2| zv0`755>;XF4rytnl5jAbL~$Vb{h2lXwK!zPZ~9~Qh8Cg+wljsr1GK^2cvDw1o|{DU zuJ^ir8jlk7ab=6Y472b1`JNyqNt})0j$DNeHS;I+t}2uGp|W7q)c<~F5Zrcuu2i)v z;6r%-H)kv}!w+aAiXEy@9hM~QjZC&gLkDVp3YYP8J%Vq{k&F#Yp!Q+PVGEK@^n}TY z$}_yo^m#lw=Y#p=EGX%;yjHqEa7(anEi?peDf{%RrJbK?QGXL9c;VJf(u$xL1XWN& zcz?O!{D#LbN@i@q5A%X3g||MW@dHPq@<`iRCkLyxn26ZrY3*5|6PtBJ81t|i?ZXrmtQ%sos_2xc1$YM@03_#^^$^l@UQiF z1*y$uW;t?AuLGV3Gh$T6GnBF#$IQylC@%$y#%j%}AO%70?{}RD0c@;I z;4Yj!>!&UO1pi-(J*q3(R}F#?W+sxvroB}If^fX`dLC(lXsUHB0U3hWiNE1q@g9T* z6(H6^K%235YFa(r-E__I1N4 zrvSsqtElhh<0L`S=lg%62_}9BeuLmvk=PzBC9Ba&i1t@APOPtE)6+`TG49L%_Rv2= zELpNzN#>c*xzAb$4Q4{G0)szxqXtbq#4N9|g+7#xO(?G3F-+hfSH&8y9U8a@2jx!B z3XVQZd_*Je{{80`f`P^58uaqy00UF|^Z&Vr5`h(DiB90qsd*ycXpI$WCW!ynj+942 zlgMR^531Hi@Ks&H`TOvFL~{WYmg9mPT#<~7 zgI8Iv+iL|M%{eL-H{p7@>Inl>C8G1btI&yC=0;YmpsxaV3orEI&89$Rk@{z~!i?aF z@6ErLt%vN-_s_YJXYexP1+&FG1`^E!e7OH!T+r|fr5??~`v-bWYCYbD_$Da@i9Wi+ z;Y{Z1EhO9q)h44!Xqc|}CjCIfh8f}zux8%_O*jdF-V**AABBFIfYwLk`)rEVVIC!! z8pdxPC6O9c-C7BjTG#=mxl7~QsyisSS@}!iGfB7wYnN@4X5SwT<<`0-D0hr@AP}8+ zAC&MS`t;WIdS_NUrI>i%wu z;^vifyc=>f`^4@!DC=U{dqMvu-HY{o0>uOgJHL0JZy#W9r{4Yv5|(?74C;}6%|wLp zJQ0H&(%sTuw@HhXiKv2oAVxzdEZT2k;C#j&bA3J}Y1Tk5dLU$?%E1ihw) zf!Ie~xxJczTR4b85}#6osfc1-WC}~1FT=mr_a25PlT&_m_Qlxr)Gn2JTgs>N`nkBT zUer3=zvEQq*I|<*kbg7m$A$e1=aKix42X}@%se@$S(|s8T7l4!UJA^ozb+1N*J9ZZ z3n)CLI5~*&U~yjGz^HJAZVx;3{Fo=C#A*&rH#4SV36bl<=W!F%`ZJ0B{Nr%BFO2lXi>tRDSj zV(XNROL9Zs8^k3s6T+n2j8r9nuy!$pR=~v3#MFzpp|^u&D6%7nAa9>B?D~mRYcuZ_ z!P+Ec-YjYR@Y>IJ!lI%DAgEwvASkKZv->g7s<-?Yovo9$VOJ8~FWGQ_OAl@F_+!J* zU};C1HySSK_D2#5KnJq8G87k#P|)xk5BBlgTuqm&k1YX2Ug zwa5LVza9V9=4e~pX4Lov2ESZ9>$x>aSNG(>2KiSh{yc<_1QV7*(|`z+qvxt%H1kIg zD^&-Nx&S~Jiz|Ytwr)<+J4q2%Q%S~DaBQ7uDe2rT7FX|ZA?UW{4kTHEb3`7Z?eQ+N zVvS;TAqf6a^*Gh~eIGO|{xU{92q&pdNb#jGIFULO^{sn^L?-x z%dw2aB-X&BR!VIh1`nl1x-G1=4<%+8z)6pL3{z2gyhx0i08!P2VgWm9D83;%g(8RO zrNGMreqN1ja`fzV1$0I^V-sDbTG4uNP5LKmUCtQ0Ogg1{9yMR2f56RBnd$X-6QCFYz2Hkl}bq^-a<2h`iE>)tL+?laj5p zpI#eFc(F`Cfv`_G+$yn#DuBY(6uPNzN#25Xl7l~3J-!t90Wu3-a^e{D!iyhLxwr6Y zR=Jk8sBR7PZOpY)qa{pR22ZF!j#Df3Goai((yILSu?h2$K9c&7tz96_ADhH#&qn;) zVw*2fACq2H#d|kSe|ZBf#?Q-U5fpVVK~fk}ihm+rvuPRWT-7ikuFxn)LxEGYz^+ zw`t;a`1v(`xJvw%xGhc)_}*zmIh)dn3P?g)mtDYZ+?3-a-FDr~SgB2Lt=9@EYOkEL zM_A~vPvfd8kA@aRQ)~Yq7-*Nw-j-j%4MEEx=+;N!E=~iPe&S?~>%BKL@4rypi_Ztb4Th##vZIg8x+wb4Bi`7R zcRfbNi!jK{&0h;aTWfWcSehD0c=UM3y#7gW;z-9K4r57=iT^qc=Wt%tEZ>u~4|LdE zf#W1I0x`bB5KAR4?rR}Mh_W-`}N<( z@S3C%qX83A95EN-A$|6;(6(tq6%}_obUEztW&4whr7c zP}~XYikDSa948wN&CM`UKjH*zr0EV(>o}la#T2VB&Lc12xF3o$dT=B1Glh6`Ie<1v z_C3-BmTtqwcuUJf8WSHq(FC2DOra=j)m{{o4(OtFI2%%WZ)|pYBSyUFyCsg&8a?y7 z@BuWKtDwZVYWpb9n>0T59*;L3UG>p|h`YwZy0NG@Vv4=4Xem!63zF*6e)3~jYy7!$ znw<=neHVmNFO%W~j$nELu!Q7k$}EjAEKKRj$$+~HFhuPCYSL(nV3&3#_q4@75 zpNxOpi`${sYm3(d9H=iXflWjKP`XiiX}3gr5a}S(sA2`*|70eeZ7N>@&-QOnCr%+; z5+M4nR8Cr+cDkN}w=bES4!%R~Z}=}jO!uxdmo>}jb}J-)ZuV#$$$|b`^CvB})=A6fH0eeafW1+~<@nW`j+ z{wApe`;V|9XOjC%*X7@7z+<+gsi&c`x#VRD@7OAXO8v@+sprG$2{Iw*qciE{+U)Xy z-845B>jT2nZZruv=aL0On+=O_hr`?Zbnw%;*an?Mb#T&(j^o5B!L1JCNIt zWPdB5yK~^8NuP;WNR@Q<;Cjm9X`+fSS7wY=tv+!ftY;YW*j~Zq6$TpO4n-i&L+$kY zQ>!buR&yTh&ZYADZzF;}S-Hb^7*i7#m#|mdW%;a1?}JlZCfes|^N35E1l1L#eC$)A z0!%U!ppK-m7Aw3&-ddNJAaNEm)MQ@&tIaUdf;2?jaZO28`IMFVyhPkBAnwhs1Nh!n z;m6yTX(O6;T4_bsB#e;=3jGmft}CxnqY8Bs^aI_McfbVPY(U&9J9<(1TB7;~@>yXP z=U8&(UpZp@6HY=Y6u(`|YjZ-Gb?isrFR9lY{_c@-bjGT`VpiTkEwiHeYXl){M1V1f z#qrLK+0>;>NrYwpVbz7m&JJ|96I^bzU+s1155CmU8}!&SY0ktS1Gh82*;RQaYi^iv zN*{}wP=IRf=Kx`AX2?1xmlNuj(CgV}1lr0!I9FSf)v!Z3cb$pavAZ8YFD$gqJrX;x z`9)<;!F#09>|;OZZQ4BVb3IsGaySlM9W!6D8Afg$!#I_`*>9Tr<7^$HIJLY{Y?^)( zzGU?D`t`GJvdH%ZiZT&nAMc{&(S_Or5_5YtXt~4g9D+<4hFbsn* z!{UeovekjeATx?KC6_=!LsEF1Dk%HnDbZq*R>q<aYC@_0_*8ynNS^Cvt~6#7Sn zo!YW$gImq^`oPi&A&bY`-JYR-w6#nrH`n}?YxBTR`~2~t z_RNxe2d(#M&CS=cJeBq7iQz3XYR=>@5iVxrKUmw-7oPDhA<~uIxZ-_Jsy`5G`$tAvVIC zmGxG50rAZnho6T$yoAC$7t5X>d|{q~CPWdn&{9xhfzjj&Enin4qer8No$yo9_hR&| zQ75DnmtmsNxz$p}xWZfGTQTyIA44#^D_hK79Ycl+3wDnd;RAawi5DT?Jb=q(ys=z? z^v`~T1^durEuuZt%^R8(d|{)#Fzw_vkp{_3Ot3p8Lvwmn zO>|TrN1TKyr{Bo2!ld*2#1fv>w~H=!8oV&V+q$79EsrO|gy0)P#b}RcVIxHa{=i<* zCHjvBbP<%zr?IKbPES>dM9IrqKQcEgK+v~2ic-pE9TaRStZ1K$?V{I%i8y?Ho3)^L z@yZTM?Ea97gY1Qc^<6)U!*iSYG7p+IiDY23 zY7)tkLliIR)G3@y#qi>A;%c#A3STs3qf0r4!{(n|;Ai;%Q-l;Nz z;3B5(5umho3IDQ2t^7=h&SqF8Vz>o)_eDyokC8HK8G0s%WzrWBWF!_gEE0kpt1-xS zEu9)koDu1W0i~CE@j>|1LF4^*P z*0c?%%XYsvy5&THn|6~Jh21|41@0N!)XN~b7HstWJHtKPf>b`Gc*ua_YHF`uuC(Gf z3plz0%(5!Civ}~Ji6`0?M1yVhkG;j%@}qVt=#6NpWz7t(qN0`CF4w+hhO+Lo?hzB= zy!is<4zlRg` zqce3)kl20+0NiUq_Phi#1lj9Y@Y(}pPiR)r;1p?u!mVu=TcNDYjAbgp!=zmWl66jN&{{0DtszDAS#2yF~9tcj@XPkie z6u_T4^|{Va5f{22rUBs!1{Z63VO-wt)I>R;unQS+hl`=Gb5NMptgxrvqI*y(8IDWO zRlk3FF0?KK=;phv&R3+u1IP!d{e`^y|KN;%jA}sE?$+qINLb6OU{j7UW8LzjUTI_! zvevcloPQiF{o((oKr`0z09uqr!jX0C_gk5zP@D&xT;}sy6v!M$P+7XPc-_;XIXPdl usFu!7Cm!qxRm6e`AxzMRUJfD-eY-YBqjdg%>CIu3d_^Y8zKek6&ix0RIVAP~ delta 18345 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 DJY-EO diff --git a/tools/model_generator/src/com/libiec61850/tools/DynamicCodeGenerator.java b/tools/model_generator/src/com/libiec61850/tools/DynamicCodeGenerator.java index c9629b73..1071242d 100644 --- a/tools/model_generator/src/com/libiec61850/tools/DynamicCodeGenerator.java +++ b/tools/model_generator/src/com/libiec61850/tools/DynamicCodeGenerator.java @@ -11,10 +11,13 @@ import java.util.LinkedList; import java.util.List; import java.util.Set; +import com.libiec61850.scl.DataAttributeDefinition; import com.libiec61850.scl.DataObjectDefinition; import com.libiec61850.scl.SclParser; import com.libiec61850.scl.SclParserException; +import com.libiec61850.scl.model.AttributeType; import com.libiec61850.scl.model.LogicalNode; +import com.libiec61850.scl.types.DataAttributeType; import com.libiec61850.scl.types.DataObjectType; import com.libiec61850.scl.types.LogicalNodeType; import com.libiec61850.scl.types.SclType; @@ -76,25 +79,48 @@ public class DynamicCodeGenerator { Set doTypeDefs = new HashSet(); Set lnTypeDefs = new HashSet(); + Set daTypeDefs = new HashSet(); List functionPrototypes = new LinkedList(); + - List types = declarations.getTypeDeclarations(); + /* Create type lists */ - for (SclType type : types) { + for (SclType type : declarations.getTypeDeclarations()) { if (type.getClass().equals(LogicalNodeType.class)) lnTypeDefs.add((LogicalNodeType) type); else if (type.getClass().equals(DataObjectType.class)) doTypeDefs.add((DataObjectType) type); - + else if (type.getClass().equals(DataAttributeType.class)) + daTypeDefs.add((DataAttributeType) type); } + /* Create function prototypes */ + for (LogicalNodeType lnType : lnTypeDefs) { String functionPrototype = "LogicalNode*\nLN_" + lnType.getId() + "_createInstance(char* lnName, LogicalDevice* parent);"; functionPrototypes.add(functionPrototype); + } + + for (DataObjectType doType : doTypeDefs) { + String functionPrototype = "DataObject*\nDO_" + doType.getId() + + "_createInstance(char* doName, ModelNode* parent);"; + + functionPrototypes.add(functionPrototype); + } + + for (DataAttributeType daType : daTypeDefs) { + String functionPrototype = "DataAttribute*\nDA_" + daType.getId() + + "_createInstance(char* daName, ModelNode* parent, FunctionalConstraint fc, uint8_t triggerOptions);"; + + functionPrototypes.add(functionPrototype); + } + + + for (LogicalNodeType lnType : lnTypeDefs) { out.println("/**"); out.printf(" * LN: %s ", lnType.getId()); @@ -109,7 +135,7 @@ public class DynamicCodeGenerator { List doDefs = lnType.getDataObjectDefinitions(); for (DataObjectDefinition objDef : doDefs) { - out.printf(" %s_createInstance(\"%s\", (ModelNode*) newLn);\n", objDef.getType(), objDef.getName()); + out.printf(" DO_%s_createInstance(\"%s\", (ModelNode*) newLn);\n", objDef.getType(), objDef.getName()); } out.println("\n return newLn;"); @@ -117,11 +143,6 @@ public class DynamicCodeGenerator { } for (DataObjectType doType : doTypeDefs) { - String functionPrototype = "DataObject*\nDO_" + doType.getId() - + "_createInstance(char* doName, ModelNode* parent);"; - - functionPrototypes.add(functionPrototype); - out.println("/**"); out.printf(" * DO: %s ", doType.getId()); if (doType.getDesc() != null) @@ -131,12 +152,70 @@ public class DynamicCodeGenerator { out.println("DataObject*"); out.printf("DO_%s_createInstance(char* doName, ModelNode* parent)\n", doType.getId()); out.println("{"); - out.println(" LogicalNode* newDo = DataObject_create(doName, parent);\n"); + out.println(" DataObject* newDo = DataObject_create(doName, parent);\n"); + + for (DataAttributeDefinition dad : doType.getDataAttributes()) { + + if (dad.getAttributeType() == AttributeType.CONSTRUCTED) { + out.print(" DA_" + dad.getType() + "_createInstance(\"" + dad.getName() + "\", "); + out.print("(ModelNode*) newDo, IEC61850_FC_" + dad.getFc().toString()); + out.print(", " + dad.getTriggerOptions().getIntValue()); + out.println(");"); + } + else { + out.print(" DataAttribute_create(\"" + dad.getName() + "\", "); + out.print("(ModelNode*) newDo, IEC61850_" + dad.getAttributeType()); + out.print(", IEC61850_FC_" + dad.getFc().toString()); + out.print(", " + dad.getTriggerOptions().getIntValue()); + out.print(", " + dad.getCount()); + out.print(", 0"); + out.println(");"); + } + + } + + + for (DataObjectDefinition dod : doType.getSubDataObjects()) { + out.print(" DO_" + dod.getType() + "_createInstance(\"" + dod.getName() + "\")"); + out.println("(ModelNode*) newDo);"); + } out.println("\n return newDo;"); out.println("}\n\n"); } + for (DataAttributeType daType : daTypeDefs) { + out.println("/**"); + out.printf(" * DA: %s ", daType.getId()); + if (daType.getDesc() != null) + out.printf("(%s)", daType.getDesc()); + out.println(); + out.println(" */"); + out.println("DataAttribute*"); + out.printf("DA_%s_createInstance(char* daName, ModelNode* parent, FunctionalConstraint fc, uint8_t triggerOptions)\n", daType.getId()); + out.println("{"); + out.println(" DataAttribute* newDa = DataAttribute_create(daName, parent, IEC61850_CONSTRUCTED, fc, triggerOptions, 0, 0);\n"); + + for (DataAttributeDefinition dad : daType.getSubDataAttributes()) { + if (dad.getAttributeType() == AttributeType.CONSTRUCTED) { + out.print(" DA_" + dad.getType() + "_createInstance(\"" + dad.getName() + "\", "); + out.println("(ModelNode*) newDo, fc, triggerOptions);"); + } + else { + out.print(" DataAttribute_create(\"" + dad.getName() + "\", "); + out.print("(ModelNode*) newDa, IEC61850_" + dad.getAttributeType()); + out.print(", fc"); + out.print(", triggerOptions"); + out.print(", " + dad.getCount()); + out.print(", 0"); + out.println(");"); + } + } + + out.println("\n return newDo;"); + out.println("}\n\n"); + + } } diff --git a/tools/model_generator/src/com/libiec61850/tools/StaticModelGenerator.java b/tools/model_generator/src/com/libiec61850/tools/StaticModelGenerator.java index 020339f7..b0500a70 100644 --- a/tools/model_generator/src/com/libiec61850/tools/StaticModelGenerator.java +++ b/tools/model_generator/src/com/libiec61850/tools/StaticModelGenerator.java @@ -706,10 +706,8 @@ public class StaticModelGenerator { cOut.println("};\n"); - if (dataAttribute.getSubDataAttributes() != null) - - - printDataAttributeDefinitions(daName, dataAttribute.getSubDataAttributes()); + if (dataAttribute.getSubDataAttributes() != null) + printDataAttributeDefinitions(daName, dataAttribute.getSubDataAttributes()); DataModelValue value = dataAttribute.getValue();