[{"data":1,"prerenderedAt":3746},["ShallowReactive",2],{"docs-nav-docs_cs":3,"docs-page-\u002Fcs\u002Fcustom-extensions\u002Fiss-example":366,"docs-surround-\u002Fcs\u002Fcustom-extensions\u002Fiss-example":3743},[4],{"title":5,"path":6,"stem":7,"children":8},"Cs","\u002Fcs","cs",[9,13,41,58,76,103,157,171,190,211,238,282,302],{"title":10,"path":6,"stem":11,"navTitle":12},"Dokumentace QInsight","cs\u002Findex","Přehled",{"title":14,"navTitle":15,"path":16,"stem":17,"children":18,"page":40},"Začínáme",null,"\u002Fcs\u002Fgetting-started","cs\u002F01.getting-started",[19,23,27,31,36],{"title":20,"path":21,"stem":22,"navTitle":20},"Co je QENEX Suite","\u002Fcs\u002Fgetting-started\u002Fwhat-is-qenex-suite","cs\u002F01.getting-started\u002F1.what-is-qenex-suite",{"title":24,"path":25,"stem":26,"navTitle":24},"Co je QInsight","\u002Fcs\u002Fgetting-started\u002Fwhat-is-qinsight","cs\u002F01.getting-started\u002F2.what-is-qinsight",{"title":28,"path":29,"stem":30,"navTitle":28},"Instalace","\u002Fcs\u002Fgetting-started\u002Finstallation","cs\u002F01.getting-started\u002F3.installation",{"title":32,"path":33,"stem":34,"navTitle":35},"Uživatelské rozhraní QInsight","\u002Fcs\u002Fgetting-started\u002Fthe-qinsight-interface","cs\u002F01.getting-started\u002F4.the-qinsight-interface","Uživatelské rozhraní",{"title":37,"path":38,"stem":39,"navTitle":37},"Licencování","\u002Fcs\u002Fgetting-started\u002Flicensing","cs\u002F01.getting-started\u002F5.licensing",false,{"title":42,"navTitle":15,"path":43,"stem":44,"children":45,"page":40},"První projekt","\u002Fcs\u002Ffirst-project","cs\u002F02.first-project",[46,50,54],{"title":47,"path":48,"stem":49,"navTitle":47},"Založení projektu","\u002Fcs\u002Ffirst-project\u002Fcreate-a-project","cs\u002F02.first-project\u002F1.create-a-project",{"title":51,"path":52,"stem":53,"navTitle":51},"Struktura projektu","\u002Fcs\u002Ffirst-project\u002Fproject-structure","cs\u002F02.first-project\u002F2.project-structure",{"title":55,"path":56,"stem":57,"navTitle":55},"Heslo projektu","\u002Fcs\u002Ffirst-project\u002Fproject-password","cs\u002F02.first-project\u002F3.project-password",{"title":59,"navTitle":15,"path":60,"stem":61,"children":62,"page":40},"Připojení zařízení","\u002Fcs\u002Fconnecting-a-device","cs\u002F03.connecting-a-device",[63,67,71],{"title":64,"path":65,"stem":66,"navTitle":64},"Přidání driveru","\u002Fcs\u002Fconnecting-a-device\u002Fadd-a-driver","cs\u002F03.connecting-a-device\u002F1.add-a-driver",{"title":68,"path":69,"stem":70,"navTitle":68},"Přidání protokolu","\u002Fcs\u002Fconnecting-a-device\u002Fadd-a-protocol","cs\u002F03.connecting-a-device\u002F2.add-a-protocol",{"title":72,"path":73,"stem":74,"navTitle":75},"Propojení driveru a protokolu","\u002Fcs\u002Fconnecting-a-device\u002Flink-driver-and-protocol","cs\u002F03.connecting-a-device\u002F3.link-driver-and-protocol","Propojení",{"title":77,"navTitle":15,"path":78,"stem":79,"children":80,"page":40},"Proměnné","\u002Fcs\u002Fvariables","cs\u002F04.variables",[81,85,89,93,98],{"title":82,"path":83,"stem":84,"navTitle":82},"Co je proměnná","\u002Fcs\u002Fvariables\u002Fwhat-is-a-variable","cs\u002F04.variables\u002F1.what-is-a-variable",{"title":86,"path":87,"stem":88,"navTitle":86},"Konverze a prezentace","\u002Fcs\u002Fvariables\u002Fconversion-and-presentation","cs\u002F04.variables\u002F2.conversion-and-presentation",{"title":90,"path":91,"stem":92,"navTitle":90},"Přidání proměnné","\u002Fcs\u002Fvariables\u002Fadd-a-variable","cs\u002F04.variables\u002F3.add-a-variable",{"title":94,"path":95,"stem":96,"navTitle":97},"Proměnná Matrix (pole, křivka, mapa)","\u002Fcs\u002Fvariables\u002Fmatrix-variable","cs\u002F04.variables\u002F4.matrix-variable","Proměnná Matrix",{"title":99,"path":100,"stem":101,"navTitle":102},"Události (Events)","\u002Fcs\u002Fvariables\u002Fevents","cs\u002F04.variables\u002F5.events","Události",{"title":104,"navTitle":15,"path":105,"stem":106,"children":107,"page":40},"Vizualizace","\u002Fcs\u002Fvisualization","cs\u002F05.visualization",[108,113,118,122,127,132,137,142,147,152],{"title":109,"path":110,"stem":111,"navTitle":112},"Vytvoření pracovní plochy","\u002Fcs\u002Fvisualization\u002Fcreate-a-workspace","cs\u002F05.visualization\u002F1.create-a-workspace","Pracovní plocha",{"title":114,"path":115,"stem":116,"navTitle":117},"Zápis hodnot do zařízení","\u002Fcs\u002Fvisualization\u002Fwrite-to-device","cs\u002F05.visualization\u002F10.write-to-device","Zápis do zařízení",{"title":119,"path":120,"stem":121,"navTitle":119},"Přidání controlu","\u002Fcs\u002Fvisualization\u002Fadd-a-control","cs\u002F05.visualization\u002F2.add-a-control",{"title":123,"path":124,"stem":125,"navTitle":126},"Navázání controlu na proměnnou","\u002Fcs\u002Fvisualization\u002Fbind-a-control","cs\u002F05.visualization\u002F3.bind-a-control","Navázání controlu",{"title":128,"path":129,"stem":130,"navTitle":131},"Graph — časový průběh signálů","\u002Fcs\u002Fvisualization\u002Fgraph-control","cs\u002F05.visualization\u002F4.graph-control","Graph",{"title":133,"path":134,"stem":135,"navTitle":136},"XY Graph — signál proti signálu","\u002Fcs\u002Fvisualization\u002Fxy-graph-control","cs\u002F05.visualization\u002F5.xy-graph-control","XY Graph",{"title":138,"path":139,"stem":140,"navTitle":141},"Gauge — ukazatel okamžité hodnoty","\u002Fcs\u002Fvisualization\u002Fgauge-control","cs\u002F05.visualization\u002F6.gauge-control","Gauge",{"title":143,"path":144,"stem":145,"navTitle":146},"Single-Signal — hodnota s popiskem","\u002Fcs\u002Fvisualization\u002Fsignal-control","cs\u002F05.visualization\u002F7.signal-control","Single-Signal",{"title":148,"path":149,"stem":150,"navTitle":151},"Watch Table — tabulka proměnných","\u002Fcs\u002Fvisualization\u002Fwatch-table-control","cs\u002F05.visualization\u002F8.watch-table-control","Watch Table",{"title":153,"path":154,"stem":155,"navTitle":156},"Matrix control — mapy a kalibrace","\u002Fcs\u002Fvisualization\u002Fmatrix-control","cs\u002F05.visualization\u002F9.matrix-control","Matrix control",{"title":158,"navTitle":15,"path":159,"stem":160,"children":161,"page":40},"Běh a sledování","\u002Fcs\u002Frunning","cs\u002F06.running",[162,166],{"title":163,"path":164,"stem":165,"navTitle":163},"Spuštění a zastavení","\u002Fcs\u002Frunning\u002Fstart-and-stop","cs\u002F06.running\u002F1.start-and-stop",{"title":167,"path":168,"stem":169,"navTitle":170},"Sledování živých dat","\u002Fcs\u002Frunning\u002Fwatching-live-data","cs\u002F06.running\u002F2.watching-live-data","Živá data",{"title":172,"navTitle":15,"path":173,"stem":174,"children":175,"page":40},"Logování a přehrávání","\u002Fcs\u002Flogging-and-replay","cs\u002F07.logging-and-replay",[176,180,185],{"title":177,"path":178,"stem":179,"navTitle":177},"Logování dat","\u002Fcs\u002Flogging-and-replay\u002Flogging-data","cs\u002F07.logging-and-replay\u002F1.logging-data",{"title":181,"path":182,"stem":183,"navTitle":184},"Přehrávání logu","\u002Fcs\u002Flogging-and-replay\u002Freplaying-a-log","cs\u002F07.logging-and-replay\u002F2.replaying-a-log","Přehrávání",{"title":186,"path":187,"stem":188,"navTitle":189},"Export dat","\u002Fcs\u002Flogging-and-replay\u002Fexporting-data","cs\u002F07.logging-and-replay\u002F3.exporting-data","Export",{"title":191,"navTitle":15,"path":192,"stem":193,"children":194,"page":40},"Python skripty","\u002Fcs\u002Fpython-scripts","cs\u002F08.python-scripts",[195,199,203,207],{"title":196,"path":197,"stem":198,"navTitle":12},"Přehled skriptů","\u002Fcs\u002Fpython-scripts\u002Fscripts-overview","cs\u002F08.python-scripts\u002F1.scripts-overview",{"title":200,"path":201,"stem":202,"navTitle":200},"Přístup k proměnným","\u002Fcs\u002Fpython-scripts\u002Faccessing-variables","cs\u002F08.python-scripts\u002F2.accessing-variables",{"title":204,"path":205,"stem":206,"navTitle":204},"Příklad skriptu","\u002Fcs\u002Fpython-scripts\u002Fexample-script","cs\u002F08.python-scripts\u002F3.example-script",{"title":208,"path":209,"stem":210,"navTitle":208},"Post-processing CSV","\u002Fcs\u002Fpython-scripts\u002Fcsv-post-processing","cs\u002F08.python-scripts\u002F4.csv-post-processing",{"title":212,"navTitle":15,"path":213,"stem":214,"children":215,"page":40},"Vlastní rozšíření","\u002Fcs\u002Fcustom-extensions","cs\u002F09.custom-extensions",[216,220,224,228,233],{"title":217,"path":218,"stem":219,"navTitle":217},"Vlastní driver","\u002Fcs\u002Fcustom-extensions\u002Fcustom-driver","cs\u002F09.custom-extensions\u002F1.custom-driver",{"title":221,"path":222,"stem":223,"navTitle":221},"Vlastní protokol","\u002Fcs\u002Fcustom-extensions\u002Fcustom-protocol","cs\u002F09.custom-extensions\u002F2.custom-protocol",{"title":225,"path":226,"stem":227,"navTitle":225},"Vlastní Control","\u002Fcs\u002Fcustom-extensions\u002Fcustom-control","cs\u002F09.custom-extensions\u002F3.custom-control",{"title":229,"path":230,"stem":231,"navTitle":232},"Kompletní ukázka — poloha ISS","\u002Fcs\u002Fcustom-extensions\u002Fiss-example","cs\u002F09.custom-extensions\u002F4.iss-example","Ukázka ISS",{"title":234,"path":235,"stem":236,"navTitle":237},"Kompletní ukázka — teploměr","\u002Fcs\u002Fcustom-extensions\u002Ftemp-sensor-example","cs\u002F09.custom-extensions\u002F5.temp-sensor-example","Ukázka teploměr",{"title":239,"navTitle":15,"path":240,"stem":241,"children":242,"page":40},"QFW SDK","\u002Fcs\u002Fdevice-sdk","cs\u002F10.device-sdk",[243,248,253,258,263,268,272,277],{"title":244,"path":245,"stem":246,"navTitle":247},"QFW SDK — úvod","\u002Fcs\u002Fdevice-sdk\u002Fintroduction","cs\u002F10.device-sdk\u002F1.introduction","Úvod",{"title":249,"path":250,"stem":251,"navTitle":252},"XCP v QFW SDK","\u002Fcs\u002Fdevice-sdk\u002Fxcp","cs\u002F10.device-sdk\u002F2.xcp","XCP",{"title":254,"path":255,"stem":256,"navTitle":257},"Integrace krok za krokem: XCP on TCP s FreeRTOS + lwIP","\u002Fcs\u002Fdevice-sdk\u002Fintegration-tcp","cs\u002F10.device-sdk\u002F3.integration-tcp","Integrace: XCP on TCP",{"title":259,"path":260,"stem":261,"navTitle":262},"XCP on classic CAN — co se mění","\u002Fcs\u002Fdevice-sdk\u002Fcan","cs\u002F10.device-sdk\u002F4.can","XCP on classic CAN",{"title":264,"path":265,"stem":266,"navTitle":267},"Integrace bez RTOS: XCP on TCP z hlavní smyčky","\u002Fcs\u002Fdevice-sdk\u002Fbaremetal","cs\u002F10.device-sdk\u002F5.baremetal","Integrace bez RTOS",{"title":269,"path":270,"stem":271,"navTitle":269},"Kontrakt enginu","\u002Fcs\u002Fdevice-sdk\u002Fengine-contract","cs\u002F10.device-sdk\u002F6.engine-contract",{"title":273,"path":274,"stem":275,"navTitle":276},"Adresy proměnných pro QInsight","\u002Fcs\u002Fdevice-sdk\u002Fvariable-addresses","cs\u002F10.device-sdk\u002F7.variable-addresses","Adresy proměnných",{"title":278,"path":279,"stem":280,"navTitle":281},"Řešení potíží při integraci SDK","\u002Fcs\u002Fdevice-sdk\u002Ftroubleshooting","cs\u002F10.device-sdk\u002F8.troubleshooting","Řešení potíží",{"title":283,"navTitle":15,"path":284,"stem":285,"children":286,"page":40},"Reference","\u002Fcs\u002Freference","cs\u002F11.reference",[287,291,295,299],{"title":288,"path":289,"stem":290,"navTitle":288},"Drivery","\u002Fcs\u002Freference\u002Fdrivers","cs\u002F11.reference\u002F1.drivers",{"title":292,"path":293,"stem":294,"navTitle":292},"Protokoly","\u002Fcs\u002Freference\u002Fprotocols","cs\u002F11.reference\u002F2.protocols",{"title":296,"path":297,"stem":298,"navTitle":296},"Controls","\u002Fcs\u002Freference\u002Fcontrols","cs\u002F11.reference\u002F3.controls",{"title":281,"path":300,"stem":301,"navTitle":281},"\u002Fcs\u002Freference\u002Ftroubleshooting","cs\u002F11.reference\u002F4.troubleshooting",{"title":303,"navTitle":15,"path":304,"stem":305,"children":306,"page":40},"Příklady","\u002Fcs\u002Fexamples","cs\u002F12.examples",[307,311,316,321,326,330,334,338,342,346,351,356,361],{"title":308,"path":309,"stem":310,"navTitle":308},"Simulace: všechny signály","\u002Fcs\u002Fexamples\u002Fsimulation-all-signals","cs\u002F12.examples\u002F01.simulation-all-signals",{"title":312,"path":313,"stem":314,"navTitle":315},"Modbus RTU master po sériové lince","\u002Fcs\u002Fexamples\u002Fserial-modbus-master","cs\u002F12.examples\u002F02.serial-modbus-master","Modbus RTU master",{"title":317,"path":318,"stem":319,"navTitle":320},"Modbus RTU slave se simulovanými senzory","\u002Fcs\u002Fexamples\u002Fserial-modbus-slave","cs\u002F12.examples\u002F03.serial-modbus-slave","Modbus RTU slave",{"title":322,"path":323,"stem":324,"navTitle":325},"Modbus TCP master přes TCP klienta","\u002Fcs\u002Fexamples\u002Ftcp-client-modbus-master","cs\u002F12.examples\u002F04.tcp-client-modbus-master","Modbus TCP master",{"title":327,"path":328,"stem":329,"navTitle":327},"Modbus TCP slave na TCP serveru","\u002Fcs\u002Fexamples\u002Ftcp-server-modbus-slave","cs\u002F12.examples\u002F05.tcp-server-modbus-slave",{"title":331,"path":332,"stem":333,"navTitle":331},"Bitové pole a skript","\u002Fcs\u002Fexamples\u002Fbit-field-script","cs\u002F12.examples\u002F08.bit-field-script",{"title":335,"path":336,"stem":337,"navTitle":335},"Dvě ECU najednou","\u002Fcs\u002Fexamples\u002Ftwo-ecus","cs\u002F12.examples\u002F09.two-ecus",{"title":339,"path":340,"stem":341,"navTitle":339},"Poloha ISS naživo","\u002Fcs\u002Fexamples\u002Fiss-live-tracking","cs\u002F12.examples\u002F10.iss-live-tracking",{"title":343,"path":344,"stem":345,"navTitle":343},"Simulovaný teploměr","\u002Fcs\u002Fexamples\u002Fsimul-temp-measure","cs\u002F12.examples\u002F11.simul-temp-measure",{"title":347,"path":348,"stem":349,"navTitle":350},"JSON signály z Python generátoru","\u002Fcs\u002Fexamples\u002Fjson-signal-python","cs\u002F12.examples\u002F12.json-signal-python","JSON signály z Pythonu",{"title":352,"path":353,"stem":354,"navTitle":355},"Raw CAN rámce z Raspberry","\u002Fcs\u002Fexamples\u002Fraw-can-python","cs\u002F12.examples\u002F13.raw-can-python","Raw CAN z Raspberry",{"title":357,"path":358,"stem":359,"navTitle":360},"XCP on TCP na NUCLEO-H723ZG: měření, stimulace a kalibrační mapy","\u002Fcs\u002Fexamples\u002Ftcp-client-xcp-nucleo-maps","cs\u002F12.examples\u002F14.tcp-client-xcp-nucleo-maps","XCP on TCP na NUCLEO-H723ZG",{"title":362,"path":363,"stem":364,"navTitle":365},"XCP on CAN na NUCLEO-H723ZG: měření, stimulace a kalibrační mapy","\u002Fcs\u002Fexamples\u002Fcan-xcp-nucleo-maps","cs\u002F12.examples\u002F15.can-xcp-nucleo-maps","XCP on CAN na NUCLEO-H723ZG",{"id":367,"title":229,"body":368,"description":3738,"extension":3739,"meta":3740,"navTitle":232,"navigation":971,"path":230,"seo":3741,"stem":231,"__hash__":3742},"docs_cs\u002Fcs\u002F09.custom-extensions\u002F4.iss-example.md",{"type":369,"value":370,"toc":3727},"minimark",[371,376,385,388,417,431,434,439,450,460,463,575,582,586,596,685,689,707,783,850,854,869,930,2370,2374,2392,2407,3385,3389,3400,3562,3566,3669,3673,3680,3704,3709,3713,3723],[372,373,375],"h1",{"id":374},"kompletní-ukázka-poloha-iss-driver-protokol","Kompletní ukázka: poloha ISS (driver + protokol)",[377,378,379,380,384],"p",{},"Předchozí kapitoly ukázaly kostry — zde je ",[381,382,383],"strong",{},"nejmenší skutečně funkční\ndvojice driver + protokol",", jakou lze napsat. Přestože se celá vejde\nna několik obrazovek, pracuje s reálnými živými daty: jednou za sekundu se\ndotáže veřejného REST API na aktuální polohu Mezinárodní vesmírné stanice\na plní čtyři proměnné — zeměpisnou šířku, délku, výšku a rychlost objektu,\nkterý právě obíhá Zemi rychlostí přibližně 27 600 km\u002Fh.",[377,386,387],{},"Dělba práce je přesně ta, kterou používají všechny drivery a protokoly\nv QInsight:",[389,390,391,407],"ul",{},[392,393,394,397,398,402,403,406],"li",{},[381,395,396],{},"IssDriver"," má jediný úkol: v zadané periodě stáhne přes ",[399,400,401],"code",{},"HttpClient"," jednu\nJSON odpověď a předá ji protokolům ",[381,404,405],{},"tak, jak přišla",". O jejím obsahu\nnic neví — z jeho pohledu jde pouze o text.",[392,408,409,412,413,416],{},[381,410,411],{},"IssJsonProtocol"," dostane text, rozparsuje ho jako JSON a rozdělí hodnoty\nproměnným. Každá proměnná určuje commParamem ",[399,414,415],{},"path=\"latitude\""," své\npole, takže jedna odpověď naplní všechny proměnné najednou.",[377,418,419,426],{},[420,421],"img",{"alt":422,"className":423,"src":425},"Projekt ISS Live za běhu — graf šířky a délky, ukazatel výšky, aktuální hodnoty",[424],"img-light","\u002Fimg\u002FQInsight_tut_9_4_iss_light.png",[420,427],{"alt":422,"className":428,"src":430},[429],"img-dark","\u002Fimg\u002FQInsight_tut_9_4_iss_dark.png",[377,432,433],{},"Data mají navíc užitečnou vlastnost: nejsou náhodná. Zeměpisná šířka kreslí\nsinusoidu s periodou jednoho oběhu (~92 minut). Zeměpisná délka zase pilový\nprůběh: plynule roste, při 180° přejde na −180° a roste znovu — stanice\nletí stále „rovně“, ale Země se pod ní otáčí, takže každý oběh začíná o něco\nzápadněji. Necháte-li graf běžet několik minut, tyto tvary se stanou zřetelnými.",[435,436,438],"h2",{"id":437},"odkud-data-pocházejí","Odkud data pocházejí",[377,440,441,442,449],{},"Ukázka používá API ",[443,444,448],"a",{"href":445,"rel":446},"https:\u002F\u002Fwheretheiss.at",[447],"nofollow","wheretheiss.at"," — je zdarma,\nbez registrace a klíče. Jeden GET požadavek:",[451,452,457],"pre",{"className":453,"code":455,"language":456},[454],"language-text","https:\u002F\u002Fapi.wheretheiss.at\u002Fv1\u002Fsatellites\u002F25544\n","text",[399,458,455],{"__ignoreMap":459},"",[377,461,462],{},"vrátí jeden plochý JSON objekt (25544 je katalogové číslo ISS):",[451,464,468],{"className":465,"code":466,"language":467,"meta":459,"style":459},"language-json shiki shiki-themes github-light github-dark-default","{\n  \"name\": \"iss\",\n  \"latitude\": -29.06,\n  \"longitude\": 20.04,\n  \"altitude\": 423.31,\n  \"velocity\": 27575.13,\n  \"visibility\": \"daylight\",\n  ...\n}\n","json",[399,469,470,479,496,510,523,536,549,562,569],{"__ignoreMap":459},[471,472,475],"span",{"class":473,"line":474},"line",1,[471,476,478],{"class":477},"szmH1","{\n",[471,480,482,486,489,493],{"class":473,"line":481},2,[471,483,485],{"class":484},"swgif","  \"name\"",[471,487,488],{"class":477},": ",[471,490,492],{"class":491},"sNbzA","\"iss\"",[471,494,495],{"class":477},",\n",[471,497,499,502,504,508],{"class":473,"line":498},3,[471,500,501],{"class":484},"  \"latitude\"",[471,503,488],{"class":477},[471,505,507],{"class":506},"sPgVT","-29.06",[471,509,495],{"class":477},[471,511,513,516,518,521],{"class":473,"line":512},4,[471,514,515],{"class":484},"  \"longitude\"",[471,517,488],{"class":477},[471,519,520],{"class":506},"20.04",[471,522,495],{"class":477},[471,524,526,529,531,534],{"class":473,"line":525},5,[471,527,528],{"class":484},"  \"altitude\"",[471,530,488],{"class":477},[471,532,533],{"class":506},"423.31",[471,535,495],{"class":477},[471,537,539,542,544,547],{"class":473,"line":538},6,[471,540,541],{"class":484},"  \"velocity\"",[471,543,488],{"class":477},[471,545,546],{"class":506},"27575.13",[471,548,495],{"class":477},[471,550,552,555,557,560],{"class":473,"line":551},7,[471,553,554],{"class":484},"  \"visibility\"",[471,556,488],{"class":477},[471,558,559],{"class":491},"\"daylight\"",[471,561,495],{"class":477},[471,563,565],{"class":473,"line":564},8,[471,566,568],{"class":567},"sAjPK","  ...\n",[471,570,572],{"class":473,"line":571},9,[471,573,574],{"class":477},"}\n",[377,576,577,578,581],{},"Provozovatel žádá o nejvýše ~1 požadavek za sekundu — driver proto\nrychlejší periodu odmítne (viz ",[399,579,580],{},"SetConfiguration"," níže). K běhu ukázky je\npotřeba připojení k internetu.",[435,583,585],{"id":584},"jak-si-ukázku-vyzkoušet","Jak si ukázku vyzkoušet",[377,587,588,589,592,593,595],{},"Hotový projekt s touto dvojicí pluginů je v instalátoru jako\n",[399,590,591],{},"Qenex_IssLiveTracking.qproj",", popsaný v příkladu\n",[443,594,339],{"href":340},". Postup níže jej sestavuje od začátku.",[597,598,599,620,643,679,682],"ol",{},[392,600,601,602,605,606,495,611,605,614,619],{},"Oba pluginy už dodává instalátor\n(",[399,603,604],{},"Qenex.QSuite.Examples.IssDriver.dll"," ve složce ",[381,607,608],{},[399,609,610],{},"Drivers",[399,612,613],{},"Qenex.QSuite.Examples.IssJsonProtocol.dll",[381,615,616],{},[399,617,618],{},"Protocols","\nvedle QInsight.exe). Přeložíte-li si vlastní upravenou kopii (projekty\nníže), nahraďte jí tyto DLL.",[392,621,622,623,626,627,630,631,634,635,638,639,642],{},"V ",[381,624,625],{},"Project Configuration"," přidejte driver ",[381,628,629],{},"ISS Position (example)","\n— předvyplní se ",[399,632,633],{},"periodMs=1000;reconnectDelayMs=5000;reconnectAttempts=-1","\n— a k němu protokol ",[381,636,637],{},"ISS JSON Protocol\n(example)",". Protokol svůj driver jmenuje v ",[399,640,641],{},"CompatibleDrivers"," (viz\nníže), takže pod jiným driverem je zašedlý — a naopak.",[392,644,645,646],{},"Vytvořte čtyři proměnné typu double a přiřaďte je protokolu s commParams:\n",[389,647,648,655,663,671],{},[392,649,650,488,653],{},[399,651,652],{},"Latitude",[399,654,415],{},[392,656,657,488,660],{},[399,658,659],{},"Longitude",[399,661,662],{},"path=\"longitude\"",[392,664,665,488,668],{},[399,666,667],{},"Altitude",[399,669,670],{},"path=\"altitude\"",[392,672,673,488,676],{},[399,674,675],{},"Velocity",[399,677,678],{},"path=\"velocity\"",[392,680,681],{},"Aby grafy a ukazatele měly smysluplné rozsahy, doporučujeme prezentace:\nšířka −90…90 °, délka −180…180 °, výška 380…460 km, rychlost\n27 400…27 700 km\u002Fh.",[392,683,684],{},"Spusťte runtime — hodnoty se začnou aktualizovat ihned po první odpovědi API.",[435,686,688],{"id":687},"projekty","Projekty",[377,690,691,692,695,696,699,700,703,704,706],{},"Dva běžné class-library projekty; referencují jen základní projekty QSuite\n(",[399,693,694],{},"Driver",", resp. ",[399,697,698],{},"Protocol"," + ",[399,701,702],{},"VariableEvents","). ",[399,705,401],{}," je součást\n.NET, žádný NuGet balíček není potřeba.",[451,708,712],{"className":709,"code":710,"language":711,"meta":459,"style":459},"language-xml shiki shiki-themes github-light github-dark-default","\u003C!-- IssDriver.csproj -->\n\u003CProject Sdk=\"Microsoft.NET.Sdk\">\n    \u003CPropertyGroup>\n        \u003CTargetFramework>net10.0\u003C\u002FTargetFramework>\n        \u003CImplicitUsings>enable\u003C\u002FImplicitUsings>\n        \u003CNullable>enable\u003C\u002FNullable>\n        \u003CAssemblyName>Qenex.QSuite.Examples.IssDriver\u003C\u002FAssemblyName>\n        \u003CRootNamespace>Qenex.QSuite.Examples.IssDriver\u003C\u002FRootNamespace>\n    \u003C\u002FPropertyGroup>\n    \u003CItemGroup>\n      \u003CProjectReference Include=\"..\\..\\Drivers\\Driver\\Driver.csproj\" \u002F>\n    \u003C\u002FItemGroup>\n\u003C\u002FProject>\n","xml",[399,713,714,719,724,729,734,739,744,749,754,759,765,771,777],{"__ignoreMap":459},[471,715,716],{"class":473,"line":474},[471,717,718],{},"\u003C!-- IssDriver.csproj -->\n",[471,720,721],{"class":473,"line":481},[471,722,723],{},"\u003CProject Sdk=\"Microsoft.NET.Sdk\">\n",[471,725,726],{"class":473,"line":498},[471,727,728],{},"    \u003CPropertyGroup>\n",[471,730,731],{"class":473,"line":512},[471,732,733],{},"        \u003CTargetFramework>net10.0\u003C\u002FTargetFramework>\n",[471,735,736],{"class":473,"line":525},[471,737,738],{},"        \u003CImplicitUsings>enable\u003C\u002FImplicitUsings>\n",[471,740,741],{"class":473,"line":538},[471,742,743],{},"        \u003CNullable>enable\u003C\u002FNullable>\n",[471,745,746],{"class":473,"line":551},[471,747,748],{},"        \u003CAssemblyName>Qenex.QSuite.Examples.IssDriver\u003C\u002FAssemblyName>\n",[471,750,751],{"class":473,"line":564},[471,752,753],{},"        \u003CRootNamespace>Qenex.QSuite.Examples.IssDriver\u003C\u002FRootNamespace>\n",[471,755,756],{"class":473,"line":571},[471,757,758],{},"    \u003C\u002FPropertyGroup>\n",[471,760,762],{"class":473,"line":761},10,[471,763,764],{},"    \u003CItemGroup>\n",[471,766,768],{"class":473,"line":767},11,[471,769,770],{},"      \u003CProjectReference Include=\"..\\..\\Drivers\\Driver\\Driver.csproj\" \u002F>\n",[471,772,774],{"class":473,"line":773},12,[471,775,776],{},"    \u003C\u002FItemGroup>\n",[471,778,780],{"class":473,"line":779},13,[471,781,782],{},"\u003C\u002FProject>\n",[451,784,786],{"className":709,"code":785,"language":711,"meta":459,"style":459},"\u003C!-- IssJsonProtocol.csproj -->\n\u003CProject Sdk=\"Microsoft.NET.Sdk\">\n    \u003CPropertyGroup>\n        \u003CTargetFramework>net10.0\u003C\u002FTargetFramework>\n        \u003CImplicitUsings>enable\u003C\u002FImplicitUsings>\n        \u003CNullable>enable\u003C\u002FNullable>\n        \u003CAssemblyName>Qenex.QSuite.Examples.IssJsonProtocol\u003C\u002FAssemblyName>\n        \u003CRootNamespace>Qenex.QSuite.Examples.IssJsonProtocol\u003C\u002FRootNamespace>\n    \u003C\u002FPropertyGroup>\n    \u003CItemGroup>\n      \u003CProjectReference Include=\"..\\..\\Protocols\\Protocol\\Protocol.csproj\" \u002F>\n      \u003CProjectReference Include=\"..\\..\\Variables\\VariableEvents\\VariableEvents.csproj\" \u002F>\n    \u003C\u002FItemGroup>\n\u003C\u002FProject>\n",[399,787,788,793,797,801,805,809,813,818,823,827,831,836,841,845],{"__ignoreMap":459},[471,789,790],{"class":473,"line":474},[471,791,792],{},"\u003C!-- IssJsonProtocol.csproj -->\n",[471,794,795],{"class":473,"line":481},[471,796,723],{},[471,798,799],{"class":473,"line":498},[471,800,728],{},[471,802,803],{"class":473,"line":512},[471,804,733],{},[471,806,807],{"class":473,"line":525},[471,808,738],{},[471,810,811],{"class":473,"line":538},[471,812,743],{},[471,814,815],{"class":473,"line":551},[471,816,817],{},"        \u003CAssemblyName>Qenex.QSuite.Examples.IssJsonProtocol\u003C\u002FAssemblyName>\n",[471,819,820],{"class":473,"line":564},[471,821,822],{},"        \u003CRootNamespace>Qenex.QSuite.Examples.IssJsonProtocol\u003C\u002FRootNamespace>\n",[471,824,825],{"class":473,"line":571},[471,826,758],{},[471,828,829],{"class":473,"line":761},[471,830,764],{},[471,832,833],{"class":473,"line":767},[471,834,835],{},"      \u003CProjectReference Include=\"..\\..\\Protocols\\Protocol\\Protocol.csproj\" \u002F>\n",[471,837,838],{"class":473,"line":773},[471,839,840],{},"      \u003CProjectReference Include=\"..\\..\\Variables\\VariableEvents\\VariableEvents.csproj\" \u002F>\n",[471,842,843],{"class":473,"line":779},[471,844,776],{},[471,846,848],{"class":473,"line":847},14,[471,849,782],{},[435,851,853],{"id":852},"driver-issdrivercs","Driver: IssDriver.cs",[377,855,856,857,860,861,864,865,868],{},"Celý životní cyklus driveru na jednom místě: ",[399,858,859],{},"DefaultRawSettings"," jako\nšablona nastavení, stavový automat ve ",[399,862,863],{},"StartAsync","\u002F",[399,866,867],{},"StopAsync"," a jedna\nsmyčka, která v periodě volá API. Za povšimnutí stojí tři místa:",[389,870,871,882,915],{},[392,872,873,877,878,881],{},[381,874,875],{},[399,876,580],{}," čte nastavení společným ",[399,879,880],{},"SettingsParser"," se\nseznamem známých klíčů, takže překlep v Project Configuration skončí\nvarováním v logu, ne tichým použitím výchozí hodnoty. Periodu zároveň\nomezuje na minimálně 1000 ms — jde o bezplatnou veřejnou službu a ohleduplnost\nk ní je součástí návrhu, nikoli poznámka pod čarou.",[392,883,884,890,891,894,895,898,899,902,903,906,907,910,911,914],{},[381,885,886,887],{},"Chybu sítě řeší ",[399,888,889],{},"ReconnectPolicy"," stejně jako dodávané drivery:\nneúspěšný požadavek přepne driver do ",[399,892,893],{},"Faulted",", po ",[399,896,897],{},"reconnectDelayMs","\nse pokus opakuje a po vyčerpání ",[399,900,901],{},"reconnectAttempts"," se driver zastaví\ns důvodem ve stavu (",[399,904,905],{},"-1"," = zkoušet donekonečna, výchozí). Zastavený\ndriver znovu spustí tlačítko ",[381,908,909],{},"Start"," v jeho Properties, ostatní\ndrivery projektu běží dál. První úspěšná odpověď vrátí ",[399,912,913],{},"Running",".",[392,916,917,926,927,914],{},[381,918,919,864,922,925],{},[399,920,921],{},"Send",[399,923,924],{},"SendAsync"," jsou prázdné."," API je jen ke čtení, takže driver\nnemá co odesílat. Jak vypadá plná zápisová cesta, ukazuje\n",[443,928,929],{"href":235},"ukázka teploměru",[451,931,935],{"className":932,"code":933,"language":934,"meta":459,"style":459},"language-csharp shiki shiki-themes github-light github-dark-default","using System.Reflection;\nusing Qenex.QSuite.Common.CoreComm;\nusing Qenex.QSuite.Drivers.Driver;\nusing Qenex.QSuite.LogSystems.LogSystem;\nusing Qenex.QSuite.Protocols.Protocol;\nusing Qenex.QSuite.Specifications.Specification;\n\nnamespace Qenex.QSuite.Examples.IssDriver;\n\n\u002F\u002F\u002F \u003Csummary>\n\u002F\u002F\u002F Example driver: reads live data from a public REST API — the current position of the\n\u002F\u002F\u002F International Space Station. Every period it performs one HTTP GET and hands the raw JSON\n\u002F\u002F\u002F response to its protocols; turning the JSON into variable values is the protocol's job\n\u002F\u002F\u002F (see the IssJsonProtocol example).\n\u002F\u002F\u002F This is about the smallest possible real-data driver: read-only, one fixed URL, one setting\n\u002F\u002F\u002F of its own — plus the shared settings parser and reconnect policy every QENEX driver uses,\n\u002F\u002F\u002F so the driver behaves in Project Configuration and at runtime like the built-in ones.\n\u002F\u002F\u002F \u003C\u002Fsummary>\npublic class IssDriver : DriverBase, ITransportSource\u003Cstring>\n{\n    \u002F\u002F Free, key-less API returning one flat JSON object with the current ISS position.\n    \u002F\u002F Be polite to the public service: do not poll faster than ~1 request per second.\n    private const string Url = \"https:\u002F\u002Fapi.wheretheiss.at\u002Fv1\u002Fsatellites\u002F25544\";\n    private const int MinPeriodMs = 1000;\n\n    \u002F\u002F The internet is a flaky transport: wait a few seconds before the next attempt.\n    private const int DefaultReconnectDelayMs = 5000;\n\n    private int periodMs = MinPeriodMs;\n    private ReconnectPolicy reconnect = new(DefaultReconnectDelayMs);\n\n    private HttpClient? httpClient;\n    private CancellationTokenSource? runCts;\n    private Task? runTask;\n\n    public IssDriver()\n    {\n        Specification = new SpecificationBase\n        {\n            Name = \"IssDriver\",\n            Label = \"ISS Position (example)\",\n            Description = \"Example driver polling the ISS position from a public REST API.\",\n            CreatedOn = new DateTime(2026, 7, 27),\n            Version = Assembly.GetExecutingAssembly().GetName().Version ?? new Version(1, 0, 0),\n            Author = \"Qenex\",\n            Company = \"QENEX Ltd.\"\n        };\n    }\n\n    #region Configuration\n\n    \u002F\u002F Shown pre-filled when the driver is added in Project Configuration. The reconnect part\n    \u002F\u002F (reconnectDelayMs \u002F reconnectAttempts) comes from the shared template, so it reads the\n    \u002F\u002F same as in every built-in driver: -1 = retry forever, 0 = stop at the first failure,\n    \u002F\u002F N = give up after N consecutive failures.\n    public override string DefaultRawSettings =>\n        $\"periodMs={MinPeriodMs};\" + ReconnectPolicy.SettingsTemplate(DefaultReconnectDelayMs);\n\n    private static readonly string[] KnownSettings =\n    [\n        \"periodMs\", ReconnectPolicy.ReconnectDelayKey, ReconnectPolicy.ReconnectAttemptsKey\n    ];\n\n    public override void SetConfiguration()\n    {\n        \u002F\u002F The shared parser splits \"key=value;...\" and warns about every key it does not know —\n        \u002F\u002F a typo in Project Configuration is reported instead of being ignored silently.\n        var settings = SettingsParser.Parse(RawSettings, KnownSettings, Logger, \"ISS position driver\");\n\n        if (settings.TryGetValue(\"periodMs\", out var periodText))\n        {\n            if (int.TryParse(periodText, out var parsedPeriod))\n            {\n                \u002F\u002F The API asks for at most ~1 request per second, so slower is allowed, faster is not.\n                periodMs = Math.Max(parsedPeriod, MinPeriodMs);\n            }\n            else\n            {\n                Logger?.Log(LogLevel.Warn, $\"ISS position driver: invalid periodMs '{periodText}', using {periodMs} ms.\");\n            }\n        }\n\n        reconnect = ReconnectPolicy.Parse(settings, DefaultReconnectDelayMs, Logger, \"ISS position driver\");\n    }\n\n    #endregion\n\n    #region Driver control\n\n    public override async Task StartAsync(CancellationToken ct = default)\n    {\n        if (!IsEnabled)\n        {\n            SetState(CommunicationState.Disabled);\n            return;\n        }\n\n        if (State == CommunicationState.Running || runTask is { IsCompleted: false })\n        {\n            return;\n        }\n\n        SetState(CommunicationState.Starting);\n\n        \u002F\u002F Short timeout so a slow or unreachable server cannot block the loop for long.\n        httpClient = new HttpClient { Timeout = TimeSpan.FromSeconds(5) };\n\n        foreach (var protocol in Protocols)\n        {\n            await protocol.StartAsync(ct);\n        }\n\n        runCts = CancellationTokenSource.CreateLinkedTokenSource(ct);\n        \u002F\u002F Task.Run: the loop must not inherit the caller's (UI) SynchronizationContext —\n        \u002F\u002F a blocked dispatcher (window drag, busy UI) would stall the whole session.\n        \u002F\u002F Capture the token now: a racing StopAsync may null runCts before the loop starts.\n        var runToken = runCts.Token;\n        runTask = Task.Run(() => RunLoopAsync(runToken));\n        SetState(CommunicationState.Running);\n    }\n\n    public override async Task StopAsync(CancellationToken ct = default)\n    {\n        SetState(CommunicationState.Stopping);\n\n        if (runCts != null)\n        {\n            await runCts.CancelAsync();\n        }\n\n        if (runTask != null)\n        {\n            try\n            {\n                await runTask.WaitAsync(TimeSpan.FromSeconds(5), ct);\n            }\n            catch (OperationCanceledException)\n            {\n            }\n            catch (TimeoutException)\n            {\n                Logger?.Log(LogLevel.Warn, \"ISS position: the polling loop did not stop before timeout.\");\n            }\n        }\n\n        foreach (var protocol in Protocols)\n        {\n            await protocol.StopAsync(ct);\n        }\n\n        httpClient?.Dispose();\n        httpClient = null;\n        runCts?.Dispose();\n        runCts = null;\n        runTask = null;\n        SetState(CommunicationState.Stopped);\n    }\n\n    public override void Dispose()\n    {\n        httpClient?.Dispose();\n        runCts?.Dispose();\n    }\n\n    #endregion\n\n    #region Communication\n\n    \u002F\u002F The API is read-only, so there is nothing to send. A writable driver would push the\n    \u002F\u002F encoded data to the device here (see the TempSensorDriver example).\n    public override void Send\u003CT>(T data)\n    {\n    }\n\n    public override Task SendAsync\u003CT>(T data, CancellationToken ct = default)\n    {\n        return Task.CompletedTask;\n    }\n\n    #endregion\n\n    #region Polling loop\n\n    private async Task RunLoopAsync(CancellationToken ct)\n    {\n        string? stopMessage = null;\n        try\n        {\n            while (!ct.IsCancellationRequested)\n            {\n                try\n                {\n                    \u002F\u002F One GET returns one JSON object carrying several signals at once\n                    \u002F\u002F (latitude, longitude, altitude, velocity, ...).\n                    var json = await httpClient!.GetStringAsync(Url, ct);\n\n                    \u002F\u002F Back online after a failure: clear the failure counter and the Faulted state.\n                    reconnect.ResetAfterSuccess();\n                    if (State != CommunicationState.Running)\n                    {\n                        SetState(CommunicationState.Running);\n                    }\n\n                    foreach (var protocol in Protocols)\n                    {\n                        if (protocol is ProtocolBase\u003Cstring> textProtocol)\n                        {\n                            await textProtocol.AddReceivedDataToQueueAsync([json], ct);\n                        }\n                    }\n\n                    await Task.Delay(periodMs, ct);\n                }\n                catch (Exception e) when (e is HttpRequestException or TaskCanceledException && !ct.IsCancellationRequested)\n                {\n                    \u002F\u002F Network hiccups are normal with a public API. The reconnect policy decides\n                    \u002F\u002F whether to keep trying (Faulted, retry after reconnectDelayMs) or to give up\n                    \u002F\u002F (Stopped with the reason; the Start button in the driver's Properties restarts it).\n                    if (!reconnect.RegisterFailure())\n                    {\n                        stopMessage = reconnect.GiveUpMessage(e.Message);\n                        Logger?.Log(LogLevel.Error, $\"ISS position driver '{Label}' {stopMessage}\");\n                        break;\n                    }\n\n                    Logger?.Log(reconnect.FailureLogLevel(),\n                        $\"ISS position driver '{Label}' request failed ({reconnect.AttemptText}): {e.Message} \"\n                        + $\"Retrying in {reconnect.ReconnectDelayMs} ms.\");\n                    SetState(CommunicationState.Faulted, e.Message);\n                    await Task.Delay(reconnect.ReconnectDelayMs, ct);\n                }\n            }\n        }\n        catch (OperationCanceledException) when (ct.IsCancellationRequested)\n        {\n            \u002F\u002F Normal stop.\n        }\n        catch (Exception e)\n        {\n            Logger?.Log(LogLevel.Error, $\"ISS position loop failed: {e.Message}\");\n            SetState(CommunicationState.Faulted, e.Message);\n            return;\n        }\n\n        if (stopMessage != null)\n        {\n            \u002F\u002F Gave up: stop the protocols and keep the reason visible in the driver state.\n            foreach (var protocol in Protocols)\n            {\n                await protocol.StopAsync(CancellationToken.None);\n            }\n\n            SetState(CommunicationState.Stopped, stopMessage);\n        }\n    }\n\n    #endregion\n}\n","csharp",[399,936,937,942,947,952,957,962,967,973,978,982,987,992,997,1002,1007,1013,1019,1025,1031,1037,1042,1048,1054,1060,1066,1071,1077,1083,1088,1094,1100,1105,1111,1117,1123,1128,1134,1140,1146,1152,1158,1164,1170,1176,1182,1188,1194,1200,1206,1211,1217,1222,1228,1234,1240,1246,1252,1258,1263,1269,1275,1281,1287,1292,1298,1303,1309,1315,1321,1326,1332,1337,1343,1349,1355,1361,1367,1373,1378,1384,1389,1395,1400,1406,1411,1416,1422,1427,1433,1438,1444,1449,1455,1460,1466,1472,1477,1482,1488,1493,1498,1503,1508,1514,1519,1525,1531,1536,1542,1547,1553,1558,1563,1569,1575,1581,1587,1593,1599,1605,1610,1615,1621,1626,1632,1637,1643,1648,1654,1659,1664,1670,1675,1681,1686,1692,1697,1703,1708,1713,1719,1724,1730,1735,1740,1745,1750,1755,1761,1766,1771,1777,1783,1789,1795,1801,1807,1812,1817,1823,1828,1833,1838,1843,1848,1853,1858,1864,1869,1875,1881,1887,1892,1897,1902,1908,1913,1919,1924,1929,1934,1939,1945,1950,1956,1961,1967,1973,1978,1984,1989,1995,2001,2007,2013,2019,2024,2030,2036,2042,2048,2054,2060,2065,2071,2076,2082,2088,2094,2100,2105,2110,2116,2122,2128,2133,2139,2145,2151,2157,2162,2168,2174,2180,2185,2190,2196,2202,2208,2214,2220,2225,2230,2235,2241,2246,2252,2257,2263,2268,2274,2280,2285,2290,2295,2301,2306,2312,2318,2323,2329,2334,2339,2345,2350,2355,2360,2365],{"__ignoreMap":459},[471,938,939],{"class":473,"line":474},[471,940,941],{},"using System.Reflection;\n",[471,943,944],{"class":473,"line":481},[471,945,946],{},"using Qenex.QSuite.Common.CoreComm;\n",[471,948,949],{"class":473,"line":498},[471,950,951],{},"using Qenex.QSuite.Drivers.Driver;\n",[471,953,954],{"class":473,"line":512},[471,955,956],{},"using Qenex.QSuite.LogSystems.LogSystem;\n",[471,958,959],{"class":473,"line":525},[471,960,961],{},"using Qenex.QSuite.Protocols.Protocol;\n",[471,963,964],{"class":473,"line":538},[471,965,966],{},"using Qenex.QSuite.Specifications.Specification;\n",[471,968,969],{"class":473,"line":551},[471,970,972],{"emptyLinePlaceholder":971},true,"\n",[471,974,975],{"class":473,"line":564},[471,976,977],{},"namespace Qenex.QSuite.Examples.IssDriver;\n",[471,979,980],{"class":473,"line":571},[471,981,972],{"emptyLinePlaceholder":971},[471,983,984],{"class":473,"line":761},[471,985,986],{},"\u002F\u002F\u002F \u003Csummary>\n",[471,988,989],{"class":473,"line":767},[471,990,991],{},"\u002F\u002F\u002F Example driver: reads live data from a public REST API — the current position of the\n",[471,993,994],{"class":473,"line":773},[471,995,996],{},"\u002F\u002F\u002F International Space Station. Every period it performs one HTTP GET and hands the raw JSON\n",[471,998,999],{"class":473,"line":779},[471,1000,1001],{},"\u002F\u002F\u002F response to its protocols; turning the JSON into variable values is the protocol's job\n",[471,1003,1004],{"class":473,"line":847},[471,1005,1006],{},"\u002F\u002F\u002F (see the IssJsonProtocol example).\n",[471,1008,1010],{"class":473,"line":1009},15,[471,1011,1012],{},"\u002F\u002F\u002F This is about the smallest possible real-data driver: read-only, one fixed URL, one setting\n",[471,1014,1016],{"class":473,"line":1015},16,[471,1017,1018],{},"\u002F\u002F\u002F of its own — plus the shared settings parser and reconnect policy every QENEX driver uses,\n",[471,1020,1022],{"class":473,"line":1021},17,[471,1023,1024],{},"\u002F\u002F\u002F so the driver behaves in Project Configuration and at runtime like the built-in ones.\n",[471,1026,1028],{"class":473,"line":1027},18,[471,1029,1030],{},"\u002F\u002F\u002F \u003C\u002Fsummary>\n",[471,1032,1034],{"class":473,"line":1033},19,[471,1035,1036],{},"public class IssDriver : DriverBase, ITransportSource\u003Cstring>\n",[471,1038,1040],{"class":473,"line":1039},20,[471,1041,478],{},[471,1043,1045],{"class":473,"line":1044},21,[471,1046,1047],{},"    \u002F\u002F Free, key-less API returning one flat JSON object with the current ISS position.\n",[471,1049,1051],{"class":473,"line":1050},22,[471,1052,1053],{},"    \u002F\u002F Be polite to the public service: do not poll faster than ~1 request per second.\n",[471,1055,1057],{"class":473,"line":1056},23,[471,1058,1059],{},"    private const string Url = \"https:\u002F\u002Fapi.wheretheiss.at\u002Fv1\u002Fsatellites\u002F25544\";\n",[471,1061,1063],{"class":473,"line":1062},24,[471,1064,1065],{},"    private const int MinPeriodMs = 1000;\n",[471,1067,1069],{"class":473,"line":1068},25,[471,1070,972],{"emptyLinePlaceholder":971},[471,1072,1074],{"class":473,"line":1073},26,[471,1075,1076],{},"    \u002F\u002F The internet is a flaky transport: wait a few seconds before the next attempt.\n",[471,1078,1080],{"class":473,"line":1079},27,[471,1081,1082],{},"    private const int DefaultReconnectDelayMs = 5000;\n",[471,1084,1086],{"class":473,"line":1085},28,[471,1087,972],{"emptyLinePlaceholder":971},[471,1089,1091],{"class":473,"line":1090},29,[471,1092,1093],{},"    private int periodMs = MinPeriodMs;\n",[471,1095,1097],{"class":473,"line":1096},30,[471,1098,1099],{},"    private ReconnectPolicy reconnect = new(DefaultReconnectDelayMs);\n",[471,1101,1103],{"class":473,"line":1102},31,[471,1104,972],{"emptyLinePlaceholder":971},[471,1106,1108],{"class":473,"line":1107},32,[471,1109,1110],{},"    private HttpClient? httpClient;\n",[471,1112,1114],{"class":473,"line":1113},33,[471,1115,1116],{},"    private CancellationTokenSource? runCts;\n",[471,1118,1120],{"class":473,"line":1119},34,[471,1121,1122],{},"    private Task? runTask;\n",[471,1124,1126],{"class":473,"line":1125},35,[471,1127,972],{"emptyLinePlaceholder":971},[471,1129,1131],{"class":473,"line":1130},36,[471,1132,1133],{},"    public IssDriver()\n",[471,1135,1137],{"class":473,"line":1136},37,[471,1138,1139],{},"    {\n",[471,1141,1143],{"class":473,"line":1142},38,[471,1144,1145],{},"        Specification = new SpecificationBase\n",[471,1147,1149],{"class":473,"line":1148},39,[471,1150,1151],{},"        {\n",[471,1153,1155],{"class":473,"line":1154},40,[471,1156,1157],{},"            Name = \"IssDriver\",\n",[471,1159,1161],{"class":473,"line":1160},41,[471,1162,1163],{},"            Label = \"ISS Position (example)\",\n",[471,1165,1167],{"class":473,"line":1166},42,[471,1168,1169],{},"            Description = \"Example driver polling the ISS position from a public REST API.\",\n",[471,1171,1173],{"class":473,"line":1172},43,[471,1174,1175],{},"            CreatedOn = new DateTime(2026, 7, 27),\n",[471,1177,1179],{"class":473,"line":1178},44,[471,1180,1181],{},"            Version = Assembly.GetExecutingAssembly().GetName().Version ?? new Version(1, 0, 0),\n",[471,1183,1185],{"class":473,"line":1184},45,[471,1186,1187],{},"            Author = \"Qenex\",\n",[471,1189,1191],{"class":473,"line":1190},46,[471,1192,1193],{},"            Company = \"QENEX Ltd.\"\n",[471,1195,1197],{"class":473,"line":1196},47,[471,1198,1199],{},"        };\n",[471,1201,1203],{"class":473,"line":1202},48,[471,1204,1205],{},"    }\n",[471,1207,1209],{"class":473,"line":1208},49,[471,1210,972],{"emptyLinePlaceholder":971},[471,1212,1214],{"class":473,"line":1213},50,[471,1215,1216],{},"    #region Configuration\n",[471,1218,1220],{"class":473,"line":1219},51,[471,1221,972],{"emptyLinePlaceholder":971},[471,1223,1225],{"class":473,"line":1224},52,[471,1226,1227],{},"    \u002F\u002F Shown pre-filled when the driver is added in Project Configuration. The reconnect part\n",[471,1229,1231],{"class":473,"line":1230},53,[471,1232,1233],{},"    \u002F\u002F (reconnectDelayMs \u002F reconnectAttempts) comes from the shared template, so it reads the\n",[471,1235,1237],{"class":473,"line":1236},54,[471,1238,1239],{},"    \u002F\u002F same as in every built-in driver: -1 = retry forever, 0 = stop at the first failure,\n",[471,1241,1243],{"class":473,"line":1242},55,[471,1244,1245],{},"    \u002F\u002F N = give up after N consecutive failures.\n",[471,1247,1249],{"class":473,"line":1248},56,[471,1250,1251],{},"    public override string DefaultRawSettings =>\n",[471,1253,1255],{"class":473,"line":1254},57,[471,1256,1257],{},"        $\"periodMs={MinPeriodMs};\" + ReconnectPolicy.SettingsTemplate(DefaultReconnectDelayMs);\n",[471,1259,1261],{"class":473,"line":1260},58,[471,1262,972],{"emptyLinePlaceholder":971},[471,1264,1266],{"class":473,"line":1265},59,[471,1267,1268],{},"    private static readonly string[] KnownSettings =\n",[471,1270,1272],{"class":473,"line":1271},60,[471,1273,1274],{},"    [\n",[471,1276,1278],{"class":473,"line":1277},61,[471,1279,1280],{},"        \"periodMs\", ReconnectPolicy.ReconnectDelayKey, ReconnectPolicy.ReconnectAttemptsKey\n",[471,1282,1284],{"class":473,"line":1283},62,[471,1285,1286],{},"    ];\n",[471,1288,1290],{"class":473,"line":1289},63,[471,1291,972],{"emptyLinePlaceholder":971},[471,1293,1295],{"class":473,"line":1294},64,[471,1296,1297],{},"    public override void SetConfiguration()\n",[471,1299,1301],{"class":473,"line":1300},65,[471,1302,1139],{},[471,1304,1306],{"class":473,"line":1305},66,[471,1307,1308],{},"        \u002F\u002F The shared parser splits \"key=value;...\" and warns about every key it does not know —\n",[471,1310,1312],{"class":473,"line":1311},67,[471,1313,1314],{},"        \u002F\u002F a typo in Project Configuration is reported instead of being ignored silently.\n",[471,1316,1318],{"class":473,"line":1317},68,[471,1319,1320],{},"        var settings = SettingsParser.Parse(RawSettings, KnownSettings, Logger, \"ISS position driver\");\n",[471,1322,1324],{"class":473,"line":1323},69,[471,1325,972],{"emptyLinePlaceholder":971},[471,1327,1329],{"class":473,"line":1328},70,[471,1330,1331],{},"        if (settings.TryGetValue(\"periodMs\", out var periodText))\n",[471,1333,1335],{"class":473,"line":1334},71,[471,1336,1151],{},[471,1338,1340],{"class":473,"line":1339},72,[471,1341,1342],{},"            if (int.TryParse(periodText, out var parsedPeriod))\n",[471,1344,1346],{"class":473,"line":1345},73,[471,1347,1348],{},"            {\n",[471,1350,1352],{"class":473,"line":1351},74,[471,1353,1354],{},"                \u002F\u002F The API asks for at most ~1 request per second, so slower is allowed, faster is not.\n",[471,1356,1358],{"class":473,"line":1357},75,[471,1359,1360],{},"                periodMs = Math.Max(parsedPeriod, MinPeriodMs);\n",[471,1362,1364],{"class":473,"line":1363},76,[471,1365,1366],{},"            }\n",[471,1368,1370],{"class":473,"line":1369},77,[471,1371,1372],{},"            else\n",[471,1374,1376],{"class":473,"line":1375},78,[471,1377,1348],{},[471,1379,1381],{"class":473,"line":1380},79,[471,1382,1383],{},"                Logger?.Log(LogLevel.Warn, $\"ISS position driver: invalid periodMs '{periodText}', using {periodMs} ms.\");\n",[471,1385,1387],{"class":473,"line":1386},80,[471,1388,1366],{},[471,1390,1392],{"class":473,"line":1391},81,[471,1393,1394],{},"        }\n",[471,1396,1398],{"class":473,"line":1397},82,[471,1399,972],{"emptyLinePlaceholder":971},[471,1401,1403],{"class":473,"line":1402},83,[471,1404,1405],{},"        reconnect = ReconnectPolicy.Parse(settings, DefaultReconnectDelayMs, Logger, \"ISS position driver\");\n",[471,1407,1409],{"class":473,"line":1408},84,[471,1410,1205],{},[471,1412,1414],{"class":473,"line":1413},85,[471,1415,972],{"emptyLinePlaceholder":971},[471,1417,1419],{"class":473,"line":1418},86,[471,1420,1421],{},"    #endregion\n",[471,1423,1425],{"class":473,"line":1424},87,[471,1426,972],{"emptyLinePlaceholder":971},[471,1428,1430],{"class":473,"line":1429},88,[471,1431,1432],{},"    #region Driver control\n",[471,1434,1436],{"class":473,"line":1435},89,[471,1437,972],{"emptyLinePlaceholder":971},[471,1439,1441],{"class":473,"line":1440},90,[471,1442,1443],{},"    public override async Task StartAsync(CancellationToken ct = default)\n",[471,1445,1447],{"class":473,"line":1446},91,[471,1448,1139],{},[471,1450,1452],{"class":473,"line":1451},92,[471,1453,1454],{},"        if (!IsEnabled)\n",[471,1456,1458],{"class":473,"line":1457},93,[471,1459,1151],{},[471,1461,1463],{"class":473,"line":1462},94,[471,1464,1465],{},"            SetState(CommunicationState.Disabled);\n",[471,1467,1469],{"class":473,"line":1468},95,[471,1470,1471],{},"            return;\n",[471,1473,1475],{"class":473,"line":1474},96,[471,1476,1394],{},[471,1478,1480],{"class":473,"line":1479},97,[471,1481,972],{"emptyLinePlaceholder":971},[471,1483,1485],{"class":473,"line":1484},98,[471,1486,1487],{},"        if (State == CommunicationState.Running || runTask is { IsCompleted: false })\n",[471,1489,1491],{"class":473,"line":1490},99,[471,1492,1151],{},[471,1494,1496],{"class":473,"line":1495},100,[471,1497,1471],{},[471,1499,1501],{"class":473,"line":1500},101,[471,1502,1394],{},[471,1504,1506],{"class":473,"line":1505},102,[471,1507,972],{"emptyLinePlaceholder":971},[471,1509,1511],{"class":473,"line":1510},103,[471,1512,1513],{},"        SetState(CommunicationState.Starting);\n",[471,1515,1517],{"class":473,"line":1516},104,[471,1518,972],{"emptyLinePlaceholder":971},[471,1520,1522],{"class":473,"line":1521},105,[471,1523,1524],{},"        \u002F\u002F Short timeout so a slow or unreachable server cannot block the loop for long.\n",[471,1526,1528],{"class":473,"line":1527},106,[471,1529,1530],{},"        httpClient = new HttpClient { Timeout = TimeSpan.FromSeconds(5) };\n",[471,1532,1534],{"class":473,"line":1533},107,[471,1535,972],{"emptyLinePlaceholder":971},[471,1537,1539],{"class":473,"line":1538},108,[471,1540,1541],{},"        foreach (var protocol in Protocols)\n",[471,1543,1545],{"class":473,"line":1544},109,[471,1546,1151],{},[471,1548,1550],{"class":473,"line":1549},110,[471,1551,1552],{},"            await protocol.StartAsync(ct);\n",[471,1554,1556],{"class":473,"line":1555},111,[471,1557,1394],{},[471,1559,1561],{"class":473,"line":1560},112,[471,1562,972],{"emptyLinePlaceholder":971},[471,1564,1566],{"class":473,"line":1565},113,[471,1567,1568],{},"        runCts = CancellationTokenSource.CreateLinkedTokenSource(ct);\n",[471,1570,1572],{"class":473,"line":1571},114,[471,1573,1574],{},"        \u002F\u002F Task.Run: the loop must not inherit the caller's (UI) SynchronizationContext —\n",[471,1576,1578],{"class":473,"line":1577},115,[471,1579,1580],{},"        \u002F\u002F a blocked dispatcher (window drag, busy UI) would stall the whole session.\n",[471,1582,1584],{"class":473,"line":1583},116,[471,1585,1586],{},"        \u002F\u002F Capture the token now: a racing StopAsync may null runCts before the loop starts.\n",[471,1588,1590],{"class":473,"line":1589},117,[471,1591,1592],{},"        var runToken = runCts.Token;\n",[471,1594,1596],{"class":473,"line":1595},118,[471,1597,1598],{},"        runTask = Task.Run(() => RunLoopAsync(runToken));\n",[471,1600,1602],{"class":473,"line":1601},119,[471,1603,1604],{},"        SetState(CommunicationState.Running);\n",[471,1606,1608],{"class":473,"line":1607},120,[471,1609,1205],{},[471,1611,1613],{"class":473,"line":1612},121,[471,1614,972],{"emptyLinePlaceholder":971},[471,1616,1618],{"class":473,"line":1617},122,[471,1619,1620],{},"    public override async Task StopAsync(CancellationToken ct = default)\n",[471,1622,1624],{"class":473,"line":1623},123,[471,1625,1139],{},[471,1627,1629],{"class":473,"line":1628},124,[471,1630,1631],{},"        SetState(CommunicationState.Stopping);\n",[471,1633,1635],{"class":473,"line":1634},125,[471,1636,972],{"emptyLinePlaceholder":971},[471,1638,1640],{"class":473,"line":1639},126,[471,1641,1642],{},"        if (runCts != null)\n",[471,1644,1646],{"class":473,"line":1645},127,[471,1647,1151],{},[471,1649,1651],{"class":473,"line":1650},128,[471,1652,1653],{},"            await runCts.CancelAsync();\n",[471,1655,1657],{"class":473,"line":1656},129,[471,1658,1394],{},[471,1660,1662],{"class":473,"line":1661},130,[471,1663,972],{"emptyLinePlaceholder":971},[471,1665,1667],{"class":473,"line":1666},131,[471,1668,1669],{},"        if (runTask != null)\n",[471,1671,1673],{"class":473,"line":1672},132,[471,1674,1151],{},[471,1676,1678],{"class":473,"line":1677},133,[471,1679,1680],{},"            try\n",[471,1682,1684],{"class":473,"line":1683},134,[471,1685,1348],{},[471,1687,1689],{"class":473,"line":1688},135,[471,1690,1691],{},"                await runTask.WaitAsync(TimeSpan.FromSeconds(5), ct);\n",[471,1693,1695],{"class":473,"line":1694},136,[471,1696,1366],{},[471,1698,1700],{"class":473,"line":1699},137,[471,1701,1702],{},"            catch (OperationCanceledException)\n",[471,1704,1706],{"class":473,"line":1705},138,[471,1707,1348],{},[471,1709,1711],{"class":473,"line":1710},139,[471,1712,1366],{},[471,1714,1716],{"class":473,"line":1715},140,[471,1717,1718],{},"            catch (TimeoutException)\n",[471,1720,1722],{"class":473,"line":1721},141,[471,1723,1348],{},[471,1725,1727],{"class":473,"line":1726},142,[471,1728,1729],{},"                Logger?.Log(LogLevel.Warn, \"ISS position: the polling loop did not stop before timeout.\");\n",[471,1731,1733],{"class":473,"line":1732},143,[471,1734,1366],{},[471,1736,1738],{"class":473,"line":1737},144,[471,1739,1394],{},[471,1741,1743],{"class":473,"line":1742},145,[471,1744,972],{"emptyLinePlaceholder":971},[471,1746,1748],{"class":473,"line":1747},146,[471,1749,1541],{},[471,1751,1753],{"class":473,"line":1752},147,[471,1754,1151],{},[471,1756,1758],{"class":473,"line":1757},148,[471,1759,1760],{},"            await protocol.StopAsync(ct);\n",[471,1762,1764],{"class":473,"line":1763},149,[471,1765,1394],{},[471,1767,1769],{"class":473,"line":1768},150,[471,1770,972],{"emptyLinePlaceholder":971},[471,1772,1774],{"class":473,"line":1773},151,[471,1775,1776],{},"        httpClient?.Dispose();\n",[471,1778,1780],{"class":473,"line":1779},152,[471,1781,1782],{},"        httpClient = null;\n",[471,1784,1786],{"class":473,"line":1785},153,[471,1787,1788],{},"        runCts?.Dispose();\n",[471,1790,1792],{"class":473,"line":1791},154,[471,1793,1794],{},"        runCts = null;\n",[471,1796,1798],{"class":473,"line":1797},155,[471,1799,1800],{},"        runTask = null;\n",[471,1802,1804],{"class":473,"line":1803},156,[471,1805,1806],{},"        SetState(CommunicationState.Stopped);\n",[471,1808,1810],{"class":473,"line":1809},157,[471,1811,1205],{},[471,1813,1815],{"class":473,"line":1814},158,[471,1816,972],{"emptyLinePlaceholder":971},[471,1818,1820],{"class":473,"line":1819},159,[471,1821,1822],{},"    public override void Dispose()\n",[471,1824,1826],{"class":473,"line":1825},160,[471,1827,1139],{},[471,1829,1831],{"class":473,"line":1830},161,[471,1832,1776],{},[471,1834,1836],{"class":473,"line":1835},162,[471,1837,1788],{},[471,1839,1841],{"class":473,"line":1840},163,[471,1842,1205],{},[471,1844,1846],{"class":473,"line":1845},164,[471,1847,972],{"emptyLinePlaceholder":971},[471,1849,1851],{"class":473,"line":1850},165,[471,1852,1421],{},[471,1854,1856],{"class":473,"line":1855},166,[471,1857,972],{"emptyLinePlaceholder":971},[471,1859,1861],{"class":473,"line":1860},167,[471,1862,1863],{},"    #region Communication\n",[471,1865,1867],{"class":473,"line":1866},168,[471,1868,972],{"emptyLinePlaceholder":971},[471,1870,1872],{"class":473,"line":1871},169,[471,1873,1874],{},"    \u002F\u002F The API is read-only, so there is nothing to send. A writable driver would push the\n",[471,1876,1878],{"class":473,"line":1877},170,[471,1879,1880],{},"    \u002F\u002F encoded data to the device here (see the TempSensorDriver example).\n",[471,1882,1884],{"class":473,"line":1883},171,[471,1885,1886],{},"    public override void Send\u003CT>(T data)\n",[471,1888,1890],{"class":473,"line":1889},172,[471,1891,1139],{},[471,1893,1895],{"class":473,"line":1894},173,[471,1896,1205],{},[471,1898,1900],{"class":473,"line":1899},174,[471,1901,972],{"emptyLinePlaceholder":971},[471,1903,1905],{"class":473,"line":1904},175,[471,1906,1907],{},"    public override Task SendAsync\u003CT>(T data, CancellationToken ct = default)\n",[471,1909,1911],{"class":473,"line":1910},176,[471,1912,1139],{},[471,1914,1916],{"class":473,"line":1915},177,[471,1917,1918],{},"        return Task.CompletedTask;\n",[471,1920,1922],{"class":473,"line":1921},178,[471,1923,1205],{},[471,1925,1927],{"class":473,"line":1926},179,[471,1928,972],{"emptyLinePlaceholder":971},[471,1930,1932],{"class":473,"line":1931},180,[471,1933,1421],{},[471,1935,1937],{"class":473,"line":1936},181,[471,1938,972],{"emptyLinePlaceholder":971},[471,1940,1942],{"class":473,"line":1941},182,[471,1943,1944],{},"    #region Polling loop\n",[471,1946,1948],{"class":473,"line":1947},183,[471,1949,972],{"emptyLinePlaceholder":971},[471,1951,1953],{"class":473,"line":1952},184,[471,1954,1955],{},"    private async Task RunLoopAsync(CancellationToken ct)\n",[471,1957,1959],{"class":473,"line":1958},185,[471,1960,1139],{},[471,1962,1964],{"class":473,"line":1963},186,[471,1965,1966],{},"        string? stopMessage = null;\n",[471,1968,1970],{"class":473,"line":1969},187,[471,1971,1972],{},"        try\n",[471,1974,1976],{"class":473,"line":1975},188,[471,1977,1151],{},[471,1979,1981],{"class":473,"line":1980},189,[471,1982,1983],{},"            while (!ct.IsCancellationRequested)\n",[471,1985,1987],{"class":473,"line":1986},190,[471,1988,1348],{},[471,1990,1992],{"class":473,"line":1991},191,[471,1993,1994],{},"                try\n",[471,1996,1998],{"class":473,"line":1997},192,[471,1999,2000],{},"                {\n",[471,2002,2004],{"class":473,"line":2003},193,[471,2005,2006],{},"                    \u002F\u002F One GET returns one JSON object carrying several signals at once\n",[471,2008,2010],{"class":473,"line":2009},194,[471,2011,2012],{},"                    \u002F\u002F (latitude, longitude, altitude, velocity, ...).\n",[471,2014,2016],{"class":473,"line":2015},195,[471,2017,2018],{},"                    var json = await httpClient!.GetStringAsync(Url, ct);\n",[471,2020,2022],{"class":473,"line":2021},196,[471,2023,972],{"emptyLinePlaceholder":971},[471,2025,2027],{"class":473,"line":2026},197,[471,2028,2029],{},"                    \u002F\u002F Back online after a failure: clear the failure counter and the Faulted state.\n",[471,2031,2033],{"class":473,"line":2032},198,[471,2034,2035],{},"                    reconnect.ResetAfterSuccess();\n",[471,2037,2039],{"class":473,"line":2038},199,[471,2040,2041],{},"                    if (State != CommunicationState.Running)\n",[471,2043,2045],{"class":473,"line":2044},200,[471,2046,2047],{},"                    {\n",[471,2049,2051],{"class":473,"line":2050},201,[471,2052,2053],{},"                        SetState(CommunicationState.Running);\n",[471,2055,2057],{"class":473,"line":2056},202,[471,2058,2059],{},"                    }\n",[471,2061,2063],{"class":473,"line":2062},203,[471,2064,972],{"emptyLinePlaceholder":971},[471,2066,2068],{"class":473,"line":2067},204,[471,2069,2070],{},"                    foreach (var protocol in Protocols)\n",[471,2072,2074],{"class":473,"line":2073},205,[471,2075,2047],{},[471,2077,2079],{"class":473,"line":2078},206,[471,2080,2081],{},"                        if (protocol is ProtocolBase\u003Cstring> textProtocol)\n",[471,2083,2085],{"class":473,"line":2084},207,[471,2086,2087],{},"                        {\n",[471,2089,2091],{"class":473,"line":2090},208,[471,2092,2093],{},"                            await textProtocol.AddReceivedDataToQueueAsync([json], ct);\n",[471,2095,2097],{"class":473,"line":2096},209,[471,2098,2099],{},"                        }\n",[471,2101,2103],{"class":473,"line":2102},210,[471,2104,2059],{},[471,2106,2108],{"class":473,"line":2107},211,[471,2109,972],{"emptyLinePlaceholder":971},[471,2111,2113],{"class":473,"line":2112},212,[471,2114,2115],{},"                    await Task.Delay(periodMs, ct);\n",[471,2117,2119],{"class":473,"line":2118},213,[471,2120,2121],{},"                }\n",[471,2123,2125],{"class":473,"line":2124},214,[471,2126,2127],{},"                catch (Exception e) when (e is HttpRequestException or TaskCanceledException && !ct.IsCancellationRequested)\n",[471,2129,2131],{"class":473,"line":2130},215,[471,2132,2000],{},[471,2134,2136],{"class":473,"line":2135},216,[471,2137,2138],{},"                    \u002F\u002F Network hiccups are normal with a public API. The reconnect policy decides\n",[471,2140,2142],{"class":473,"line":2141},217,[471,2143,2144],{},"                    \u002F\u002F whether to keep trying (Faulted, retry after reconnectDelayMs) or to give up\n",[471,2146,2148],{"class":473,"line":2147},218,[471,2149,2150],{},"                    \u002F\u002F (Stopped with the reason; the Start button in the driver's Properties restarts it).\n",[471,2152,2154],{"class":473,"line":2153},219,[471,2155,2156],{},"                    if (!reconnect.RegisterFailure())\n",[471,2158,2160],{"class":473,"line":2159},220,[471,2161,2047],{},[471,2163,2165],{"class":473,"line":2164},221,[471,2166,2167],{},"                        stopMessage = reconnect.GiveUpMessage(e.Message);\n",[471,2169,2171],{"class":473,"line":2170},222,[471,2172,2173],{},"                        Logger?.Log(LogLevel.Error, $\"ISS position driver '{Label}' {stopMessage}\");\n",[471,2175,2177],{"class":473,"line":2176},223,[471,2178,2179],{},"                        break;\n",[471,2181,2183],{"class":473,"line":2182},224,[471,2184,2059],{},[471,2186,2188],{"class":473,"line":2187},225,[471,2189,972],{"emptyLinePlaceholder":971},[471,2191,2193],{"class":473,"line":2192},226,[471,2194,2195],{},"                    Logger?.Log(reconnect.FailureLogLevel(),\n",[471,2197,2199],{"class":473,"line":2198},227,[471,2200,2201],{},"                        $\"ISS position driver '{Label}' request failed ({reconnect.AttemptText}): {e.Message} \"\n",[471,2203,2205],{"class":473,"line":2204},228,[471,2206,2207],{},"                        + $\"Retrying in {reconnect.ReconnectDelayMs} ms.\");\n",[471,2209,2211],{"class":473,"line":2210},229,[471,2212,2213],{},"                    SetState(CommunicationState.Faulted, e.Message);\n",[471,2215,2217],{"class":473,"line":2216},230,[471,2218,2219],{},"                    await Task.Delay(reconnect.ReconnectDelayMs, ct);\n",[471,2221,2223],{"class":473,"line":2222},231,[471,2224,2121],{},[471,2226,2228],{"class":473,"line":2227},232,[471,2229,1366],{},[471,2231,2233],{"class":473,"line":2232},233,[471,2234,1394],{},[471,2236,2238],{"class":473,"line":2237},234,[471,2239,2240],{},"        catch (OperationCanceledException) when (ct.IsCancellationRequested)\n",[471,2242,2244],{"class":473,"line":2243},235,[471,2245,1151],{},[471,2247,2249],{"class":473,"line":2248},236,[471,2250,2251],{},"            \u002F\u002F Normal stop.\n",[471,2253,2255],{"class":473,"line":2254},237,[471,2256,1394],{},[471,2258,2260],{"class":473,"line":2259},238,[471,2261,2262],{},"        catch (Exception e)\n",[471,2264,2266],{"class":473,"line":2265},239,[471,2267,1151],{},[471,2269,2271],{"class":473,"line":2270},240,[471,2272,2273],{},"            Logger?.Log(LogLevel.Error, $\"ISS position loop failed: {e.Message}\");\n",[471,2275,2277],{"class":473,"line":2276},241,[471,2278,2279],{},"            SetState(CommunicationState.Faulted, e.Message);\n",[471,2281,2283],{"class":473,"line":2282},242,[471,2284,1471],{},[471,2286,2288],{"class":473,"line":2287},243,[471,2289,1394],{},[471,2291,2293],{"class":473,"line":2292},244,[471,2294,972],{"emptyLinePlaceholder":971},[471,2296,2298],{"class":473,"line":2297},245,[471,2299,2300],{},"        if (stopMessage != null)\n",[471,2302,2304],{"class":473,"line":2303},246,[471,2305,1151],{},[471,2307,2309],{"class":473,"line":2308},247,[471,2310,2311],{},"            \u002F\u002F Gave up: stop the protocols and keep the reason visible in the driver state.\n",[471,2313,2315],{"class":473,"line":2314},248,[471,2316,2317],{},"            foreach (var protocol in Protocols)\n",[471,2319,2321],{"class":473,"line":2320},249,[471,2322,1348],{},[471,2324,2326],{"class":473,"line":2325},250,[471,2327,2328],{},"                await protocol.StopAsync(CancellationToken.None);\n",[471,2330,2332],{"class":473,"line":2331},251,[471,2333,1366],{},[471,2335,2337],{"class":473,"line":2336},252,[471,2338,972],{"emptyLinePlaceholder":971},[471,2340,2342],{"class":473,"line":2341},253,[471,2343,2344],{},"            SetState(CommunicationState.Stopped, stopMessage);\n",[471,2346,2348],{"class":473,"line":2347},254,[471,2349,1394],{},[471,2351,2353],{"class":473,"line":2352},255,[471,2354,1205],{},[471,2356,2358],{"class":473,"line":2357},256,[471,2359,972],{"emptyLinePlaceholder":971},[471,2361,2363],{"class":473,"line":2362},257,[471,2364,1421],{},[471,2366,2368],{"class":473,"line":2367},258,[471,2369,574],{},[435,2371,2373],{"id":2372},"protokol-issjsonprotocolcs","Protokol: IssJsonProtocol.cs",[377,2375,2376,2377,2380,2381,2384,2385,864,2387,2389,2390,914],{},"Protokol je ještě kratší než driver, a to hlavně díky jednomu rozhodnutí:\n",[381,2378,2379],{},"nemá vlastní frontu ani worker vlákno",". Data přicházejí jednou za sekundu,\ntakže je dekóduje přímo v ",[399,2382,2383],{},"AddReceivedDataToQueueAsync"," — na vlákně\nsmyčky driveru. ",[399,2386,863],{},[399,2388,867],{}," pak jen přepínají stav. U rychlých\nsběrnic by takový přístup nebyl vhodný (driver nesmí čekat na zpracování);\njak vypadá plnohodnotná fronta s vlastním konzumentem, ukazuje\n",[443,2391,929],{"href":235},[377,2393,2394,2395,2398,2399,2402,2403,2406],{},"Dekódování v ",[399,2396,2397],{},"Decode"," je přímočaré: rozparsovat dokument, a každé proměnné,\njejíž ",[399,2400,2401],{},"path"," ukazuje na číselné pole, nastavit hodnotu. ",[399,2404,2405],{},"Encode"," vrací\nprázdnou sekvenci — REST API je pouze ke čtení, takže není co kódovat.",[451,2408,2410],{"className":932,"code":2409,"language":934,"meta":459,"style":459},"using System.Reflection;\nusing System.Text.Json;\nusing Qenex.QSuite.Common.CoreComm;\nusing Qenex.QSuite.LogSystems.LogSystem;\nusing Qenex.QSuite.Protocols.Protocol;\nusing Qenex.QSuite.Specifications.Specification;\nusing Qenex.QSuite.Variables.QVariables;\nusing Qenex.QSuite.Variables.QVariables.Values;\nusing Qenex.QSuite.Variables.VariableEvents;\n\nnamespace Qenex.QSuite.Examples.IssJsonProtocol;\n\n\u002F\u002F\u002F \u003Csummary>\n\u002F\u002F\u002F Example protocol: picks numeric fields out of the received ISS position JSON by name. Each\n\u002F\u002F\u002F variable states which field it wants via the \"path\" commParam (path=\"latitude\"), so one JSON\n\u002F\u002F\u002F document can feed many variables at once. Nothing here is ISS-specific except the name — the\n\u002F\u002F\u002F same code would decode any flat JSON object — but as an example it is paired with the IssDriver.\n\u002F\u002F\u002F \u003C\u002Fsummary>\npublic class IssJsonProtocol : ProtocolBase\u003Cstring>\n{\n    public IssJsonProtocol()\n    {\n        Specification = new SpecificationBase\n        {\n            Name = \"IssJsonProtocol\",\n            Label = \"ISS JSON Protocol (example)\",\n            Description = \"Example protocol decoding numeric fields of the ISS position JSON into variables.\",\n            CreatedOn = new DateTime(2026, 7, 27),\n            Version = Assembly.GetExecutingAssembly().GetName().Version ?? new Version(1, 0, 0),\n            Author = \"Qenex\",\n            Company = \"QENEX Ltd.\"\n        };\n    }\n\n    \u002F\u002F The transport type (string) alone cannot tell text-line drivers apart, and this\n    \u002F\u002F protocol only understands the ISS position JSON, so it narrows itself to its driver.\n    public override IReadOnlyList\u003Cstring> CompatibleDrivers => [\"IssDriver\"];\n\n    #region Configuration\n\n    \u002F\u002F The protocol has no protocol-level settings; everything is per-variable commParams.\n    \u002F\u002F The shared parser still runs so that a key typed into the protocol settings is reported\n    \u002F\u002F as unknown instead of being ignored silently.\n    public override void SetConfiguration()\n    {\n        SettingsParser.Parse(RawSettings, [], Logger, \"ISS JSON protocol\");\n    }\n\n    public override string CreateDefaultCommParam(IVariableBase variable, IEnumerable\u003CIVarEvent> variableEvents)\n    {\n        return \"path=\\\"latitude\\\"\";\n    }\n\n    #endregion\n\n    #region Protocol variables\n\n    public override IProtocolVariable? CreateProtocolVariable(IVariableBase variable, string commParams, bool isCommunicated)\n    {\n        try\n        {\n            return new ProtocolVariable\n            {\n                Variable = variable,\n                IsCommunicated = isCommunicated,\n                ProtocolVariableSpecification = IssJsonVariableSpecification.Create(commParams)\n            };\n        }\n        catch (Exception e)\n        {\n            Logger?.Log(LogLevel.Warn, $\"Protocol variable specification for variable {variable.Name} could not be created ({e.Message}).\");\n            return null;\n        }\n    }\n\n    public override IProtocolVariable? CreateProtocolVariable(IVariableBase variable, IVarEvent variableEvent, string id)\n    {\n        return CreateProtocolVariable(variable, $\"path=\\\"{id}\\\"\", true);\n    }\n\n    public override IProtocolVariable? CreateProtocolVariable(IVariableBase variable, IEnumerable\u003CIVarEvent> variableEvents,\n        string commParams, bool isCommunicated)\n    {\n        \u002F\u002F The data arrive whenever the driver polls them, so variable events are not used.\n        return CreateProtocolVariable(variable, commParams, isCommunicated);\n    }\n\n    #endregion\n\n    #region Protocol control\n\n    \u002F\u002F No worker loop is needed: received documents are decoded directly in\n    \u002F\u002F AddReceivedDataToQueueAsync, so start\u002Fstop only maintain the state machine.\n    \u002F\u002F (For a queued, cancelable consumer loop see the TempSensorProtocol example.)\n    public override Task StartAsync(CancellationToken ct = default)\n    {\n        SetState(IsEnabled ? CommunicationState.Running : CommunicationState.Disabled);\n        return Task.CompletedTask;\n    }\n\n    public override Task StopAsync(CancellationToken ct = default)\n    {\n        SetState(CommunicationState.Stopped);\n        return Task.CompletedTask;\n    }\n\n    public override void Dispose()\n    {\n    }\n\n    #endregion\n\n    #region Received data\n\n    \u002F\u002F At ~1 document per second there is no need for an internal queue — the data are\n    \u002F\u002F decoded right away on the driver's polling task.\n    public override async Task AddReceivedDataToQueueAsync(IEnumerable\u003Cstring> data, CancellationToken ct = default)\n    {\n        if (State != CommunicationState.Running)\n        {\n            return;\n        }\n\n        await ProcessReceivedDataAsync(data, ct);\n    }\n\n    protected override void ProcessReceivedData(IEnumerable\u003Cstring> data)\n    {\n        foreach (var protocolVariable in Decode(data))\n        {\n            protocolVariable.NotifyValueChanged();\n        }\n    }\n\n    protected override async Task ProcessReceivedDataAsync(IEnumerable\u003Cstring> data, CancellationToken ct = default)\n    {\n        var notifyTasks = Decode(data).Select(protocolVariable => protocolVariable.NotifyValueChangedAsync());\n        await Task.WhenAll(notifyTasks);\n    }\n\n    #endregion\n\n    #region Encoding and decoding\n\n    protected override IEnumerable\u003CIProtocolVariable> Decode(IEnumerable\u003Cstring> data)\n    {\n        var updated = new List\u003CIProtocolVariable>();\n\n        foreach (var json in data)\n        {\n            JsonDocument document;\n            try\n            {\n                document = JsonDocument.Parse(json);\n            }\n            catch (JsonException e)\n            {\n                Logger?.Log(LogLevel.Warn, $\"ISS JSON protocol: cannot parse received data ({e.Message}).\");\n                continue;\n            }\n\n            using (document)\n            {\n                \u002F\u002F Every variable whose \"path\" names a numeric field of this document gets its value.\n                foreach (var protocolVariable in Variables)\n                {\n                    if (!protocolVariable.IsCommunicated\n                        || protocolVariable.ProtocolVariableSpecification is not IssJsonVariableSpecification spec\n                        || protocolVariable.Variable is not ScalarVariable scalarVariable\n                        || !document.RootElement.TryGetProperty(spec.Path, out var field)\n                        || field.ValueKind != JsonValueKind.Number)\n                    {\n                        continue;\n                    }\n\n                    if (!TrySetValue(scalarVariable, field.GetDouble()))\n                    {\n                        Logger?.Log(LogLevel.Warn, $\"ISS JSON protocol: variable '{scalarVariable.Name}' has an unsupported value type.\");\n                        continue;\n                    }\n\n                    scalarVariable.Timestamp = DateTime.UtcNow;\n                    updated.Add(protocolVariable);\n                }\n            }\n        }\n\n        return updated;\n    }\n\n    \u002F\u002F The protocol is read-only (a REST API you can only GET), so nothing is ever encoded.\n    \u002F\u002F For the write direction see the TempSensorProtocol example.\n    protected override IEnumerable\u003Cstring> Encode(IEnumerable\u003CIProtocolVariable> protocolVariables)\n    {\n        yield break;\n    }\n\n    \u002F\u002F Values\u003CT>.SetValue does not convert, so the received double is converted to the\n    \u002F\u002F variable's type here; the example supports the common numeric types.\n    private static bool TrySetValue(ScalarVariable scalarVariable, double value)\n    {\n        switch (scalarVariable.Values)\n        {\n            case Values\u003Cdouble> doubleValues:\n                doubleValues.Value = value;\n                return true;\n            case Values\u003Cfloat> floatValues:\n                floatValues.Value = (float)value;\n                return true;\n            case Values\u003Cint> intValues:\n                intValues.Value = (int)Math.Round(value);\n                return true;\n            default:\n                return false;\n        }\n    }\n\n    #endregion\n}\n",[399,2411,2412,2416,2421,2425,2429,2433,2437,2442,2447,2452,2456,2461,2465,2469,2474,2479,2484,2489,2493,2498,2502,2507,2511,2515,2519,2524,2529,2534,2538,2542,2546,2550,2554,2558,2562,2567,2572,2577,2581,2585,2589,2594,2599,2604,2608,2612,2617,2621,2625,2630,2634,2639,2643,2647,2651,2655,2660,2664,2669,2673,2677,2681,2686,2690,2695,2700,2705,2710,2714,2718,2722,2727,2732,2736,2740,2744,2749,2753,2758,2762,2766,2771,2776,2780,2785,2790,2794,2798,2802,2806,2811,2815,2820,2825,2830,2835,2839,2844,2848,2852,2856,2861,2865,2869,2873,2877,2881,2885,2889,2893,2897,2901,2905,2910,2914,2919,2924,2929,2933,2938,2942,2946,2950,2954,2959,2963,2967,2972,2976,2981,2985,2990,2994,2998,3002,3007,3011,3016,3021,3025,3029,3033,3037,3042,3046,3051,3055,3060,3064,3069,3073,3078,3082,3086,3091,3095,3100,3104,3109,3114,3118,3122,3127,3131,3136,3141,3145,3150,3155,3160,3165,3170,3174,3179,3183,3187,3192,3196,3201,3205,3209,3213,3218,3223,3227,3231,3235,3239,3244,3248,3252,3257,3262,3267,3271,3276,3280,3284,3289,3294,3299,3303,3308,3312,3317,3322,3327,3332,3337,3341,3346,3351,3355,3360,3365,3369,3373,3377,3381],{"__ignoreMap":459},[471,2413,2414],{"class":473,"line":474},[471,2415,941],{},[471,2417,2418],{"class":473,"line":481},[471,2419,2420],{},"using System.Text.Json;\n",[471,2422,2423],{"class":473,"line":498},[471,2424,946],{},[471,2426,2427],{"class":473,"line":512},[471,2428,956],{},[471,2430,2431],{"class":473,"line":525},[471,2432,961],{},[471,2434,2435],{"class":473,"line":538},[471,2436,966],{},[471,2438,2439],{"class":473,"line":551},[471,2440,2441],{},"using Qenex.QSuite.Variables.QVariables;\n",[471,2443,2444],{"class":473,"line":564},[471,2445,2446],{},"using Qenex.QSuite.Variables.QVariables.Values;\n",[471,2448,2449],{"class":473,"line":571},[471,2450,2451],{},"using Qenex.QSuite.Variables.VariableEvents;\n",[471,2453,2454],{"class":473,"line":761},[471,2455,972],{"emptyLinePlaceholder":971},[471,2457,2458],{"class":473,"line":767},[471,2459,2460],{},"namespace Qenex.QSuite.Examples.IssJsonProtocol;\n",[471,2462,2463],{"class":473,"line":773},[471,2464,972],{"emptyLinePlaceholder":971},[471,2466,2467],{"class":473,"line":779},[471,2468,986],{},[471,2470,2471],{"class":473,"line":847},[471,2472,2473],{},"\u002F\u002F\u002F Example protocol: picks numeric fields out of the received ISS position JSON by name. Each\n",[471,2475,2476],{"class":473,"line":1009},[471,2477,2478],{},"\u002F\u002F\u002F variable states which field it wants via the \"path\" commParam (path=\"latitude\"), so one JSON\n",[471,2480,2481],{"class":473,"line":1015},[471,2482,2483],{},"\u002F\u002F\u002F document can feed many variables at once. Nothing here is ISS-specific except the name — the\n",[471,2485,2486],{"class":473,"line":1021},[471,2487,2488],{},"\u002F\u002F\u002F same code would decode any flat JSON object — but as an example it is paired with the IssDriver.\n",[471,2490,2491],{"class":473,"line":1027},[471,2492,1030],{},[471,2494,2495],{"class":473,"line":1033},[471,2496,2497],{},"public class IssJsonProtocol : ProtocolBase\u003Cstring>\n",[471,2499,2500],{"class":473,"line":1039},[471,2501,478],{},[471,2503,2504],{"class":473,"line":1044},[471,2505,2506],{},"    public IssJsonProtocol()\n",[471,2508,2509],{"class":473,"line":1050},[471,2510,1139],{},[471,2512,2513],{"class":473,"line":1056},[471,2514,1145],{},[471,2516,2517],{"class":473,"line":1062},[471,2518,1151],{},[471,2520,2521],{"class":473,"line":1068},[471,2522,2523],{},"            Name = \"IssJsonProtocol\",\n",[471,2525,2526],{"class":473,"line":1073},[471,2527,2528],{},"            Label = \"ISS JSON Protocol (example)\",\n",[471,2530,2531],{"class":473,"line":1079},[471,2532,2533],{},"            Description = \"Example protocol decoding numeric fields of the ISS position JSON into variables.\",\n",[471,2535,2536],{"class":473,"line":1085},[471,2537,1175],{},[471,2539,2540],{"class":473,"line":1090},[471,2541,1181],{},[471,2543,2544],{"class":473,"line":1096},[471,2545,1187],{},[471,2547,2548],{"class":473,"line":1102},[471,2549,1193],{},[471,2551,2552],{"class":473,"line":1107},[471,2553,1199],{},[471,2555,2556],{"class":473,"line":1113},[471,2557,1205],{},[471,2559,2560],{"class":473,"line":1119},[471,2561,972],{"emptyLinePlaceholder":971},[471,2563,2564],{"class":473,"line":1125},[471,2565,2566],{},"    \u002F\u002F The transport type (string) alone cannot tell text-line drivers apart, and this\n",[471,2568,2569],{"class":473,"line":1130},[471,2570,2571],{},"    \u002F\u002F protocol only understands the ISS position JSON, so it narrows itself to its driver.\n",[471,2573,2574],{"class":473,"line":1136},[471,2575,2576],{},"    public override IReadOnlyList\u003Cstring> CompatibleDrivers => [\"IssDriver\"];\n",[471,2578,2579],{"class":473,"line":1142},[471,2580,972],{"emptyLinePlaceholder":971},[471,2582,2583],{"class":473,"line":1148},[471,2584,1216],{},[471,2586,2587],{"class":473,"line":1154},[471,2588,972],{"emptyLinePlaceholder":971},[471,2590,2591],{"class":473,"line":1160},[471,2592,2593],{},"    \u002F\u002F The protocol has no protocol-level settings; everything is per-variable commParams.\n",[471,2595,2596],{"class":473,"line":1166},[471,2597,2598],{},"    \u002F\u002F The shared parser still runs so that a key typed into the protocol settings is reported\n",[471,2600,2601],{"class":473,"line":1172},[471,2602,2603],{},"    \u002F\u002F as unknown instead of being ignored silently.\n",[471,2605,2606],{"class":473,"line":1178},[471,2607,1297],{},[471,2609,2610],{"class":473,"line":1184},[471,2611,1139],{},[471,2613,2614],{"class":473,"line":1190},[471,2615,2616],{},"        SettingsParser.Parse(RawSettings, [], Logger, \"ISS JSON protocol\");\n",[471,2618,2619],{"class":473,"line":1196},[471,2620,1205],{},[471,2622,2623],{"class":473,"line":1202},[471,2624,972],{"emptyLinePlaceholder":971},[471,2626,2627],{"class":473,"line":1208},[471,2628,2629],{},"    public override string CreateDefaultCommParam(IVariableBase variable, IEnumerable\u003CIVarEvent> variableEvents)\n",[471,2631,2632],{"class":473,"line":1213},[471,2633,1139],{},[471,2635,2636],{"class":473,"line":1219},[471,2637,2638],{},"        return \"path=\\\"latitude\\\"\";\n",[471,2640,2641],{"class":473,"line":1224},[471,2642,1205],{},[471,2644,2645],{"class":473,"line":1230},[471,2646,972],{"emptyLinePlaceholder":971},[471,2648,2649],{"class":473,"line":1236},[471,2650,1421],{},[471,2652,2653],{"class":473,"line":1242},[471,2654,972],{"emptyLinePlaceholder":971},[471,2656,2657],{"class":473,"line":1248},[471,2658,2659],{},"    #region Protocol variables\n",[471,2661,2662],{"class":473,"line":1254},[471,2663,972],{"emptyLinePlaceholder":971},[471,2665,2666],{"class":473,"line":1260},[471,2667,2668],{},"    public override IProtocolVariable? CreateProtocolVariable(IVariableBase variable, string commParams, bool isCommunicated)\n",[471,2670,2671],{"class":473,"line":1265},[471,2672,1139],{},[471,2674,2675],{"class":473,"line":1271},[471,2676,1972],{},[471,2678,2679],{"class":473,"line":1277},[471,2680,1151],{},[471,2682,2683],{"class":473,"line":1283},[471,2684,2685],{},"            return new ProtocolVariable\n",[471,2687,2688],{"class":473,"line":1289},[471,2689,1348],{},[471,2691,2692],{"class":473,"line":1294},[471,2693,2694],{},"                Variable = variable,\n",[471,2696,2697],{"class":473,"line":1300},[471,2698,2699],{},"                IsCommunicated = isCommunicated,\n",[471,2701,2702],{"class":473,"line":1305},[471,2703,2704],{},"                ProtocolVariableSpecification = IssJsonVariableSpecification.Create(commParams)\n",[471,2706,2707],{"class":473,"line":1311},[471,2708,2709],{},"            };\n",[471,2711,2712],{"class":473,"line":1317},[471,2713,1394],{},[471,2715,2716],{"class":473,"line":1323},[471,2717,2262],{},[471,2719,2720],{"class":473,"line":1328},[471,2721,1151],{},[471,2723,2724],{"class":473,"line":1334},[471,2725,2726],{},"            Logger?.Log(LogLevel.Warn, $\"Protocol variable specification for variable {variable.Name} could not be created ({e.Message}).\");\n",[471,2728,2729],{"class":473,"line":1339},[471,2730,2731],{},"            return null;\n",[471,2733,2734],{"class":473,"line":1345},[471,2735,1394],{},[471,2737,2738],{"class":473,"line":1351},[471,2739,1205],{},[471,2741,2742],{"class":473,"line":1357},[471,2743,972],{"emptyLinePlaceholder":971},[471,2745,2746],{"class":473,"line":1363},[471,2747,2748],{},"    public override IProtocolVariable? CreateProtocolVariable(IVariableBase variable, IVarEvent variableEvent, string id)\n",[471,2750,2751],{"class":473,"line":1369},[471,2752,1139],{},[471,2754,2755],{"class":473,"line":1375},[471,2756,2757],{},"        return CreateProtocolVariable(variable, $\"path=\\\"{id}\\\"\", true);\n",[471,2759,2760],{"class":473,"line":1380},[471,2761,1205],{},[471,2763,2764],{"class":473,"line":1386},[471,2765,972],{"emptyLinePlaceholder":971},[471,2767,2768],{"class":473,"line":1391},[471,2769,2770],{},"    public override IProtocolVariable? CreateProtocolVariable(IVariableBase variable, IEnumerable\u003CIVarEvent> variableEvents,\n",[471,2772,2773],{"class":473,"line":1397},[471,2774,2775],{},"        string commParams, bool isCommunicated)\n",[471,2777,2778],{"class":473,"line":1402},[471,2779,1139],{},[471,2781,2782],{"class":473,"line":1408},[471,2783,2784],{},"        \u002F\u002F The data arrive whenever the driver polls them, so variable events are not used.\n",[471,2786,2787],{"class":473,"line":1413},[471,2788,2789],{},"        return CreateProtocolVariable(variable, commParams, isCommunicated);\n",[471,2791,2792],{"class":473,"line":1418},[471,2793,1205],{},[471,2795,2796],{"class":473,"line":1424},[471,2797,972],{"emptyLinePlaceholder":971},[471,2799,2800],{"class":473,"line":1429},[471,2801,1421],{},[471,2803,2804],{"class":473,"line":1435},[471,2805,972],{"emptyLinePlaceholder":971},[471,2807,2808],{"class":473,"line":1440},[471,2809,2810],{},"    #region Protocol control\n",[471,2812,2813],{"class":473,"line":1446},[471,2814,972],{"emptyLinePlaceholder":971},[471,2816,2817],{"class":473,"line":1451},[471,2818,2819],{},"    \u002F\u002F No worker loop is needed: received documents are decoded directly in\n",[471,2821,2822],{"class":473,"line":1457},[471,2823,2824],{},"    \u002F\u002F AddReceivedDataToQueueAsync, so start\u002Fstop only maintain the state machine.\n",[471,2826,2827],{"class":473,"line":1462},[471,2828,2829],{},"    \u002F\u002F (For a queued, cancelable consumer loop see the TempSensorProtocol example.)\n",[471,2831,2832],{"class":473,"line":1468},[471,2833,2834],{},"    public override Task StartAsync(CancellationToken ct = default)\n",[471,2836,2837],{"class":473,"line":1474},[471,2838,1139],{},[471,2840,2841],{"class":473,"line":1479},[471,2842,2843],{},"        SetState(IsEnabled ? CommunicationState.Running : CommunicationState.Disabled);\n",[471,2845,2846],{"class":473,"line":1484},[471,2847,1918],{},[471,2849,2850],{"class":473,"line":1490},[471,2851,1205],{},[471,2853,2854],{"class":473,"line":1495},[471,2855,972],{"emptyLinePlaceholder":971},[471,2857,2858],{"class":473,"line":1500},[471,2859,2860],{},"    public override Task StopAsync(CancellationToken ct = default)\n",[471,2862,2863],{"class":473,"line":1505},[471,2864,1139],{},[471,2866,2867],{"class":473,"line":1510},[471,2868,1806],{},[471,2870,2871],{"class":473,"line":1516},[471,2872,1918],{},[471,2874,2875],{"class":473,"line":1521},[471,2876,1205],{},[471,2878,2879],{"class":473,"line":1527},[471,2880,972],{"emptyLinePlaceholder":971},[471,2882,2883],{"class":473,"line":1533},[471,2884,1822],{},[471,2886,2887],{"class":473,"line":1538},[471,2888,1139],{},[471,2890,2891],{"class":473,"line":1544},[471,2892,1205],{},[471,2894,2895],{"class":473,"line":1549},[471,2896,972],{"emptyLinePlaceholder":971},[471,2898,2899],{"class":473,"line":1555},[471,2900,1421],{},[471,2902,2903],{"class":473,"line":1560},[471,2904,972],{"emptyLinePlaceholder":971},[471,2906,2907],{"class":473,"line":1565},[471,2908,2909],{},"    #region Received data\n",[471,2911,2912],{"class":473,"line":1571},[471,2913,972],{"emptyLinePlaceholder":971},[471,2915,2916],{"class":473,"line":1577},[471,2917,2918],{},"    \u002F\u002F At ~1 document per second there is no need for an internal queue — the data are\n",[471,2920,2921],{"class":473,"line":1583},[471,2922,2923],{},"    \u002F\u002F decoded right away on the driver's polling task.\n",[471,2925,2926],{"class":473,"line":1589},[471,2927,2928],{},"    public override async Task AddReceivedDataToQueueAsync(IEnumerable\u003Cstring> data, CancellationToken ct = default)\n",[471,2930,2931],{"class":473,"line":1595},[471,2932,1139],{},[471,2934,2935],{"class":473,"line":1601},[471,2936,2937],{},"        if (State != CommunicationState.Running)\n",[471,2939,2940],{"class":473,"line":1607},[471,2941,1151],{},[471,2943,2944],{"class":473,"line":1612},[471,2945,1471],{},[471,2947,2948],{"class":473,"line":1617},[471,2949,1394],{},[471,2951,2952],{"class":473,"line":1623},[471,2953,972],{"emptyLinePlaceholder":971},[471,2955,2956],{"class":473,"line":1628},[471,2957,2958],{},"        await ProcessReceivedDataAsync(data, ct);\n",[471,2960,2961],{"class":473,"line":1634},[471,2962,1205],{},[471,2964,2965],{"class":473,"line":1639},[471,2966,972],{"emptyLinePlaceholder":971},[471,2968,2969],{"class":473,"line":1645},[471,2970,2971],{},"    protected override void ProcessReceivedData(IEnumerable\u003Cstring> data)\n",[471,2973,2974],{"class":473,"line":1650},[471,2975,1139],{},[471,2977,2978],{"class":473,"line":1656},[471,2979,2980],{},"        foreach (var protocolVariable in Decode(data))\n",[471,2982,2983],{"class":473,"line":1661},[471,2984,1151],{},[471,2986,2987],{"class":473,"line":1666},[471,2988,2989],{},"            protocolVariable.NotifyValueChanged();\n",[471,2991,2992],{"class":473,"line":1672},[471,2993,1394],{},[471,2995,2996],{"class":473,"line":1677},[471,2997,1205],{},[471,2999,3000],{"class":473,"line":1683},[471,3001,972],{"emptyLinePlaceholder":971},[471,3003,3004],{"class":473,"line":1688},[471,3005,3006],{},"    protected override async Task ProcessReceivedDataAsync(IEnumerable\u003Cstring> data, CancellationToken ct = default)\n",[471,3008,3009],{"class":473,"line":1694},[471,3010,1139],{},[471,3012,3013],{"class":473,"line":1699},[471,3014,3015],{},"        var notifyTasks = Decode(data).Select(protocolVariable => protocolVariable.NotifyValueChangedAsync());\n",[471,3017,3018],{"class":473,"line":1705},[471,3019,3020],{},"        await Task.WhenAll(notifyTasks);\n",[471,3022,3023],{"class":473,"line":1710},[471,3024,1205],{},[471,3026,3027],{"class":473,"line":1715},[471,3028,972],{"emptyLinePlaceholder":971},[471,3030,3031],{"class":473,"line":1721},[471,3032,1421],{},[471,3034,3035],{"class":473,"line":1726},[471,3036,972],{"emptyLinePlaceholder":971},[471,3038,3039],{"class":473,"line":1732},[471,3040,3041],{},"    #region Encoding and decoding\n",[471,3043,3044],{"class":473,"line":1737},[471,3045,972],{"emptyLinePlaceholder":971},[471,3047,3048],{"class":473,"line":1742},[471,3049,3050],{},"    protected override IEnumerable\u003CIProtocolVariable> Decode(IEnumerable\u003Cstring> data)\n",[471,3052,3053],{"class":473,"line":1747},[471,3054,1139],{},[471,3056,3057],{"class":473,"line":1752},[471,3058,3059],{},"        var updated = new List\u003CIProtocolVariable>();\n",[471,3061,3062],{"class":473,"line":1757},[471,3063,972],{"emptyLinePlaceholder":971},[471,3065,3066],{"class":473,"line":1763},[471,3067,3068],{},"        foreach (var json in data)\n",[471,3070,3071],{"class":473,"line":1768},[471,3072,1151],{},[471,3074,3075],{"class":473,"line":1773},[471,3076,3077],{},"            JsonDocument document;\n",[471,3079,3080],{"class":473,"line":1779},[471,3081,1680],{},[471,3083,3084],{"class":473,"line":1785},[471,3085,1348],{},[471,3087,3088],{"class":473,"line":1791},[471,3089,3090],{},"                document = JsonDocument.Parse(json);\n",[471,3092,3093],{"class":473,"line":1797},[471,3094,1366],{},[471,3096,3097],{"class":473,"line":1803},[471,3098,3099],{},"            catch (JsonException e)\n",[471,3101,3102],{"class":473,"line":1809},[471,3103,1348],{},[471,3105,3106],{"class":473,"line":1814},[471,3107,3108],{},"                Logger?.Log(LogLevel.Warn, $\"ISS JSON protocol: cannot parse received data ({e.Message}).\");\n",[471,3110,3111],{"class":473,"line":1819},[471,3112,3113],{},"                continue;\n",[471,3115,3116],{"class":473,"line":1825},[471,3117,1366],{},[471,3119,3120],{"class":473,"line":1830},[471,3121,972],{"emptyLinePlaceholder":971},[471,3123,3124],{"class":473,"line":1835},[471,3125,3126],{},"            using (document)\n",[471,3128,3129],{"class":473,"line":1840},[471,3130,1348],{},[471,3132,3133],{"class":473,"line":1845},[471,3134,3135],{},"                \u002F\u002F Every variable whose \"path\" names a numeric field of this document gets its value.\n",[471,3137,3138],{"class":473,"line":1850},[471,3139,3140],{},"                foreach (var protocolVariable in Variables)\n",[471,3142,3143],{"class":473,"line":1855},[471,3144,2000],{},[471,3146,3147],{"class":473,"line":1860},[471,3148,3149],{},"                    if (!protocolVariable.IsCommunicated\n",[471,3151,3152],{"class":473,"line":1866},[471,3153,3154],{},"                        || protocolVariable.ProtocolVariableSpecification is not IssJsonVariableSpecification spec\n",[471,3156,3157],{"class":473,"line":1871},[471,3158,3159],{},"                        || protocolVariable.Variable is not ScalarVariable scalarVariable\n",[471,3161,3162],{"class":473,"line":1877},[471,3163,3164],{},"                        || !document.RootElement.TryGetProperty(spec.Path, out var field)\n",[471,3166,3167],{"class":473,"line":1883},[471,3168,3169],{},"                        || field.ValueKind != JsonValueKind.Number)\n",[471,3171,3172],{"class":473,"line":1889},[471,3173,2047],{},[471,3175,3176],{"class":473,"line":1894},[471,3177,3178],{},"                        continue;\n",[471,3180,3181],{"class":473,"line":1899},[471,3182,2059],{},[471,3184,3185],{"class":473,"line":1904},[471,3186,972],{"emptyLinePlaceholder":971},[471,3188,3189],{"class":473,"line":1910},[471,3190,3191],{},"                    if (!TrySetValue(scalarVariable, field.GetDouble()))\n",[471,3193,3194],{"class":473,"line":1915},[471,3195,2047],{},[471,3197,3198],{"class":473,"line":1921},[471,3199,3200],{},"                        Logger?.Log(LogLevel.Warn, $\"ISS JSON protocol: variable '{scalarVariable.Name}' has an unsupported value type.\");\n",[471,3202,3203],{"class":473,"line":1926},[471,3204,3178],{},[471,3206,3207],{"class":473,"line":1931},[471,3208,2059],{},[471,3210,3211],{"class":473,"line":1936},[471,3212,972],{"emptyLinePlaceholder":971},[471,3214,3215],{"class":473,"line":1941},[471,3216,3217],{},"                    scalarVariable.Timestamp = DateTime.UtcNow;\n",[471,3219,3220],{"class":473,"line":1947},[471,3221,3222],{},"                    updated.Add(protocolVariable);\n",[471,3224,3225],{"class":473,"line":1952},[471,3226,2121],{},[471,3228,3229],{"class":473,"line":1958},[471,3230,1366],{},[471,3232,3233],{"class":473,"line":1963},[471,3234,1394],{},[471,3236,3237],{"class":473,"line":1969},[471,3238,972],{"emptyLinePlaceholder":971},[471,3240,3241],{"class":473,"line":1975},[471,3242,3243],{},"        return updated;\n",[471,3245,3246],{"class":473,"line":1980},[471,3247,1205],{},[471,3249,3250],{"class":473,"line":1986},[471,3251,972],{"emptyLinePlaceholder":971},[471,3253,3254],{"class":473,"line":1991},[471,3255,3256],{},"    \u002F\u002F The protocol is read-only (a REST API you can only GET), so nothing is ever encoded.\n",[471,3258,3259],{"class":473,"line":1997},[471,3260,3261],{},"    \u002F\u002F For the write direction see the TempSensorProtocol example.\n",[471,3263,3264],{"class":473,"line":2003},[471,3265,3266],{},"    protected override IEnumerable\u003Cstring> Encode(IEnumerable\u003CIProtocolVariable> protocolVariables)\n",[471,3268,3269],{"class":473,"line":2009},[471,3270,1139],{},[471,3272,3273],{"class":473,"line":2015},[471,3274,3275],{},"        yield break;\n",[471,3277,3278],{"class":473,"line":2021},[471,3279,1205],{},[471,3281,3282],{"class":473,"line":2026},[471,3283,972],{"emptyLinePlaceholder":971},[471,3285,3286],{"class":473,"line":2032},[471,3287,3288],{},"    \u002F\u002F Values\u003CT>.SetValue does not convert, so the received double is converted to the\n",[471,3290,3291],{"class":473,"line":2038},[471,3292,3293],{},"    \u002F\u002F variable's type here; the example supports the common numeric types.\n",[471,3295,3296],{"class":473,"line":2044},[471,3297,3298],{},"    private static bool TrySetValue(ScalarVariable scalarVariable, double value)\n",[471,3300,3301],{"class":473,"line":2050},[471,3302,1139],{},[471,3304,3305],{"class":473,"line":2056},[471,3306,3307],{},"        switch (scalarVariable.Values)\n",[471,3309,3310],{"class":473,"line":2062},[471,3311,1151],{},[471,3313,3314],{"class":473,"line":2067},[471,3315,3316],{},"            case Values\u003Cdouble> doubleValues:\n",[471,3318,3319],{"class":473,"line":2073},[471,3320,3321],{},"                doubleValues.Value = value;\n",[471,3323,3324],{"class":473,"line":2078},[471,3325,3326],{},"                return true;\n",[471,3328,3329],{"class":473,"line":2084},[471,3330,3331],{},"            case Values\u003Cfloat> floatValues:\n",[471,3333,3334],{"class":473,"line":2090},[471,3335,3336],{},"                floatValues.Value = (float)value;\n",[471,3338,3339],{"class":473,"line":2096},[471,3340,3326],{},[471,3342,3343],{"class":473,"line":2102},[471,3344,3345],{},"            case Values\u003Cint> intValues:\n",[471,3347,3348],{"class":473,"line":2107},[471,3349,3350],{},"                intValues.Value = (int)Math.Round(value);\n",[471,3352,3353],{"class":473,"line":2112},[471,3354,3326],{},[471,3356,3357],{"class":473,"line":2118},[471,3358,3359],{},"            default:\n",[471,3361,3362],{"class":473,"line":2124},[471,3363,3364],{},"                return false;\n",[471,3366,3367],{"class":473,"line":2130},[471,3368,1394],{},[471,3370,3371],{"class":473,"line":2135},[471,3372,1205],{},[471,3374,3375],{"class":473,"line":2141},[471,3376,972],{"emptyLinePlaceholder":971},[471,3378,3379],{"class":473,"line":2147},[471,3380,1421],{},[471,3382,3383],{"class":473,"line":2153},[471,3384,574],{},[435,3386,3388],{"id":3387},"specifikace-proměnné-issjsonvariablespecificationcs","Specifikace proměnné: IssJsonVariableSpecification.cs",[377,3390,3391,3392,3395,3396,3399],{},"Adresování je zde jediné: jméno JSON pole. Property ",[399,3393,3394],{},"Path"," odpovídá klíči\ncommParamu 1:1, takže zděděná serializace ",[399,3397,3398],{},"ToCommParam()"," funguje bez\npřepsání — stejný postup jako v ukázce teploměru.",[451,3401,3403],{"className":932,"code":3402,"language":934,"meta":459,"style":459},"using Qenex.QSuite.Protocols.Protocol;\n\nnamespace Qenex.QSuite.Examples.IssJsonProtocol;\n\n\u002F\u002F\u002F \u003Csummary>\n\u002F\u002F\u002F Addressing of one variable: the name of the JSON field whose value the variable receives.\n\u002F\u002F\u002F The property matches the commParam key 1:1 (\"path\"), so the inherited reflection-based\n\u002F\u002F\u002F ToCommParam() round-trips the specification without an override.\n\u002F\u002F\u002F \u003C\u002Fsummary>\npublic class IssJsonVariableSpecification : ProtVariableSpecification\n{\n    public IssJsonVariableSpecification()\n    {\n        Name = \"IssJsonVariableSpecification\";\n    }\n\n    \u002F\u002F\u002F \u003Csummary>Name of the field in the received JSON object, e.g. \"latitude\". Case-sensitive.\u003C\u002Fsummary>\n    public string Path { get; set; } = string.Empty;\n\n    public static IssJsonVariableSpecification Create(string commParams)\n    {\n        var parameters = commParams\n            .Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)\n            .Select(parameter => parameter.Split('=', 2))\n            .Where(parts => parts.Length == 2)\n            .ToDictionary(parts => parts[0].Trim(), parts => parts[1].Trim().Trim('\"'), StringComparer.OrdinalIgnoreCase);\n\n        if (!parameters.TryGetValue(\"path\", out var path) || string.IsNullOrWhiteSpace(path))\n        {\n            throw new ArgumentException(\"Missing mandatory commParam 'path'.\");\n        }\n\n        return new IssJsonVariableSpecification { Path = path };\n    }\n}\n",[399,3404,3405,3409,3413,3417,3421,3425,3430,3435,3440,3444,3449,3453,3458,3462,3467,3471,3475,3480,3485,3489,3494,3498,3503,3508,3513,3518,3523,3527,3532,3536,3541,3545,3549,3554,3558],{"__ignoreMap":459},[471,3406,3407],{"class":473,"line":474},[471,3408,961],{},[471,3410,3411],{"class":473,"line":481},[471,3412,972],{"emptyLinePlaceholder":971},[471,3414,3415],{"class":473,"line":498},[471,3416,2460],{},[471,3418,3419],{"class":473,"line":512},[471,3420,972],{"emptyLinePlaceholder":971},[471,3422,3423],{"class":473,"line":525},[471,3424,986],{},[471,3426,3427],{"class":473,"line":538},[471,3428,3429],{},"\u002F\u002F\u002F Addressing of one variable: the name of the JSON field whose value the variable receives.\n",[471,3431,3432],{"class":473,"line":551},[471,3433,3434],{},"\u002F\u002F\u002F The property matches the commParam key 1:1 (\"path\"), so the inherited reflection-based\n",[471,3436,3437],{"class":473,"line":564},[471,3438,3439],{},"\u002F\u002F\u002F ToCommParam() round-trips the specification without an override.\n",[471,3441,3442],{"class":473,"line":571},[471,3443,1030],{},[471,3445,3446],{"class":473,"line":761},[471,3447,3448],{},"public class IssJsonVariableSpecification : ProtVariableSpecification\n",[471,3450,3451],{"class":473,"line":767},[471,3452,478],{},[471,3454,3455],{"class":473,"line":773},[471,3456,3457],{},"    public IssJsonVariableSpecification()\n",[471,3459,3460],{"class":473,"line":779},[471,3461,1139],{},[471,3463,3464],{"class":473,"line":847},[471,3465,3466],{},"        Name = \"IssJsonVariableSpecification\";\n",[471,3468,3469],{"class":473,"line":1009},[471,3470,1205],{},[471,3472,3473],{"class":473,"line":1015},[471,3474,972],{"emptyLinePlaceholder":971},[471,3476,3477],{"class":473,"line":1021},[471,3478,3479],{},"    \u002F\u002F\u002F \u003Csummary>Name of the field in the received JSON object, e.g. \"latitude\". Case-sensitive.\u003C\u002Fsummary>\n",[471,3481,3482],{"class":473,"line":1027},[471,3483,3484],{},"    public string Path { get; set; } = string.Empty;\n",[471,3486,3487],{"class":473,"line":1033},[471,3488,972],{"emptyLinePlaceholder":971},[471,3490,3491],{"class":473,"line":1039},[471,3492,3493],{},"    public static IssJsonVariableSpecification Create(string commParams)\n",[471,3495,3496],{"class":473,"line":1044},[471,3497,1139],{},[471,3499,3500],{"class":473,"line":1050},[471,3501,3502],{},"        var parameters = commParams\n",[471,3504,3505],{"class":473,"line":1056},[471,3506,3507],{},"            .Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)\n",[471,3509,3510],{"class":473,"line":1062},[471,3511,3512],{},"            .Select(parameter => parameter.Split('=', 2))\n",[471,3514,3515],{"class":473,"line":1068},[471,3516,3517],{},"            .Where(parts => parts.Length == 2)\n",[471,3519,3520],{"class":473,"line":1073},[471,3521,3522],{},"            .ToDictionary(parts => parts[0].Trim(), parts => parts[1].Trim().Trim('\"'), StringComparer.OrdinalIgnoreCase);\n",[471,3524,3525],{"class":473,"line":1079},[471,3526,972],{"emptyLinePlaceholder":971},[471,3528,3529],{"class":473,"line":1085},[471,3530,3531],{},"        if (!parameters.TryGetValue(\"path\", out var path) || string.IsNullOrWhiteSpace(path))\n",[471,3533,3534],{"class":473,"line":1090},[471,3535,1151],{},[471,3537,3538],{"class":473,"line":1096},[471,3539,3540],{},"            throw new ArgumentException(\"Missing mandatory commParam 'path'.\");\n",[471,3542,3543],{"class":473,"line":1102},[471,3544,1394],{},[471,3546,3547],{"class":473,"line":1107},[471,3548,972],{"emptyLinePlaceholder":971},[471,3550,3551],{"class":473,"line":1113},[471,3552,3553],{},"        return new IssJsonVariableSpecification { Path = path };\n",[471,3555,3556],{"class":473,"line":1119},[471,3557,1205],{},[471,3559,3560],{"class":473,"line":1125},[471,3561,574],{},[435,3563,3565],{"id":3564},"jak-spolu-členy-souvisí","Jak spolu členy souvisí",[3567,3568,3569,3585],"table",{},[3570,3571,3572],"thead",{},[3573,3574,3575,3579,3582],"tr",{},[3576,3577,3578],"th",{},"Krok",[3576,3580,3581],{},"Kdo",[3576,3583,3584],{},"Členy",[3586,3587,3588,3605,3623,3636,3654],"tbody",{},[3573,3589,3590,3594,3597],{},[3591,3592,3593],"td",{},"Přidání driveru\u002Fprotokolu v UI",[3591,3595,3596],{},"plugin",[3591,3598,3599,3601,3602],{},[399,3600,859],{},", ",[399,3603,3604],{},"CreateDefaultCommParam",[3573,3606,3607,3610,3613],{},[3591,3608,3609],{},"Přiřazení proměnné",[3591,3611,3612],{},"protokol",[3591,3614,3615,3618,3619,3622],{},[399,3616,3617],{},"CreateProtocolVariable"," → ",[399,3620,3621],{},"Create"," specifikace",[3573,3624,3625,3628,3631],{},[3591,3626,3627],{},"Start runtime",[3591,3629,3630],{},"driver",[3591,3632,3633,3635],{},[399,3634,863],{}," → start protokolů → spuštění smyčky",[3573,3637,3638,3641,3644],{},[3591,3639,3640],{},"Čtení dat",[3591,3642,3643],{},"driver → protokol",[3591,3645,3646,3647,3618,3649,3618,3651],{},"HTTP GET → ",[399,3648,2383],{},[399,3650,2397],{},[399,3652,3653],{},"NotifyValueChangedAsync",[3573,3655,3656,3659,3661],{},[3591,3657,3658],{},"Stop",[3591,3660,3630],{},[3591,3662,3663,3665,3666,3668],{},[399,3664,867],{}," (zrušení smyčky, stop protokolů, úklid ",[399,3667,401],{},")",[435,3670,3672],{"id":3671},"co-ukázka-záměrně-vynechává","Co ukázka záměrně vynechává",[377,3674,3675,3676,3679],{},"Aby zůstala co nejkratší, tři věci v ní záměrně chybí — a všechny tři\nnajdete v ",[443,3677,3678],{"href":235},"ukázce teploměru",":",[389,3681,3682,3688,3694],{},[392,3683,3684,3687],{},[381,3685,3686],{},"zápisová cesta"," (operátor zapíše hodnotu → protokol ji zakóduje →\ndriver odešle),",[392,3689,3690,3693],{},[381,3691,3692],{},"fronta přijatých dat"," s vlastním konzumentem — nezbytná všude, kde data\npřicházejí rychleji, než je lze zpracovat,",[392,3695,3696,3699,3700,3703],{},[381,3697,3698],{},"směr komunikace v commParams"," (",[399,3701,3702],{},"direction=\"read\u002Fwrite\"",") — zde se\npouze čte, takže parametr není potřeba.",[377,3705,3706,3707,914],{},"Poznámka na závěr: kromě jména na protokolu není nic specifického\npro ISS. Stejný kód by dekódoval libovolný plochý JSON objekt — stačí jiná\nURL v driveru a jiné hodnoty ",[399,3708,2401],{},[435,3710,3712],{"id":3711},"dále","Dále",[377,3714,3715,3717,3718,3720,3721,914],{},[443,3716,237],{"href":235}," — plný vzor\nvčetně zápisu a fronty. Kostry jednotlivých částí:\n",[443,3719,217],{"href":218},"\na ",[443,3722,221],{"href":222},[3724,3725,3726],"style",{},"html pre.shiki code .szmH1, html code.shiki .szmH1{--shiki-default:#24292E;--shiki-dark:#E6EDF3}html pre.shiki code .swgif, html code.shiki .swgif{--shiki-default:#005CC5;--shiki-dark:#7EE787}html pre.shiki code .sNbzA, html code.shiki .sNbzA{--shiki-default:#032F62;--shiki-dark:#A5D6FF}html pre.shiki code .sPgVT, html code.shiki .sPgVT{--shiki-default:#005CC5;--shiki-dark:#79C0FF}html pre.shiki code .sAjPK, html code.shiki .sAjPK{--shiki-default:#B31D28;--shiki-default-font-style:italic;--shiki-dark:#FFA198;--shiki-dark-font-style:italic}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":459,"searchDepth":498,"depth":498,"links":3728},[3729,3730,3731,3732,3733,3734,3735,3736,3737],{"id":437,"depth":481,"text":438},{"id":584,"depth":481,"text":585},{"id":687,"depth":481,"text":688},{"id":852,"depth":481,"text":853},{"id":2372,"depth":481,"text":2373},{"id":3387,"depth":481,"text":3388},{"id":3564,"depth":481,"text":3565},{"id":3671,"depth":481,"text":3672},{"id":3711,"depth":481,"text":3712},"Nejmenší funkční dvojice vlastní driver + protokol — živá poloha Mezinárodní vesmírné stanice z veřejného REST API.","md",{},{"title":229,"description":3738},"aQZkLTg_iDMnoYTKaDG0CB_1i5tuvt7L0RzJjdrqk6Y",[3744,3745],{"title":225,"path":226,"stem":227,"navTitle":225,"children":-1},{"title":234,"path":235,"stem":236,"navTitle":237,"children":-1},1791554569740]