[{"data":1,"prerenderedAt":4404},["ShallowReactive",2],{"docs-nav-docs_cs":3,"docs-page-\u002Fcs\u002Fcustom-extensions\u002Ftemp-sensor-example":366,"docs-surround-\u002Fcs\u002Fcustom-extensions\u002Ftemp-sensor-example":4401},[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":234,"body":368,"description":4396,"extension":4397,"meta":4398,"navTitle":237,"navigation":830,"path":235,"seo":4399,"stem":236,"__hash__":4400},"docs_cs\u002Fcs\u002F09.custom-extensions\u002F5.temp-sensor-example.md",{"type":369,"value":370,"toc":4387},"minimark",[371,376,392,435,440,553,557,572,661,728,737,741,784,2565,2569,2588,2594,3997,4001,4016,4232,4236,4371,4375,4383],[372,373,375],"h1",{"id":374},"kompletní-ukázka-teploměr-driver-protokol","Kompletní ukázka: teploměr (driver + protokol)",[377,378,379,382,383,387,388,391],"p",{},[380,381,232],"a",{"href":230}," předvedla nejmenší možnou\ndvojici driver + protokol — zde je její protipól: ",[384,385,386],"strong",{},"kompletní, přeložitelná\na funkční dvojice",", driver simulující vícekanálový teploměr a protokol,\nkterý jeho data dekóduje do proměnných. Ukázka záměrně využívá ",[384,389,390],{},"všechny\nčleny kontraktu"," včetně zápisové cesty, a přitom nepotřebuje žádný\nhardware (ani internet):",[393,394,395,407,417],"ul",{},[396,397,398,401,402,406],"li",{},[384,399,400],{},"TempSensorDriver"," zastupuje zařízení: v každé periodě vytvoří pro každý\nkanál textový řádek ",[403,404,405],"code",{},"ch1=23.47"," (teplota pomalu kolísá) a předá ho\nprotokolům.",[396,408,409,412,413,416],{},[384,410,411],{},"TempSensorProtocol"," řádky dekóduje: proměnná adresuje kanál commParamem\n",[403,414,415],{},"channel=\"ch1\"",", hodnota se převede na typ proměnné a notifikují se\nodběratelé (graf, logger, skripty).",[396,418,419,422,423,426,427,430,431,434],{},[384,420,421],{},"Zápis:"," proměnná s ",[403,424,425],{},"direction=\"write\""," projde cestou\nUI → ",[403,428,429],{},"Encode"," → transmitter vložený driverem (",[403,432,433],{},"ApplyWriteAsync","), který\nkanál „přeladí“ na zapsanou hodnotu — v grafu je s dalším vzorkem patrná\nskoková změna a další kolísání kolem nové hodnoty.",[436,437,439],"h2",{"id":438},"jak-si-ukázku-vyzkoušet","Jak si ukázku vyzkoušet",[441,442,443,466,489,528],"ol",{},[396,444,445,446,449,450,455,456,459,460,465],{},"Oba pluginy už dodává instalátor\n(",[403,447,448],{},"Qenex.QSuite.Examples.TempSensorDriver.dll"," ve složce ",[384,451,452],{},[403,453,454],{},"Drivers",",\n",[403,457,458],{},"Qenex.QSuite.Examples.TempSensorProtocol.dll"," ve složce\n",[384,461,462],{},[403,463,464],{},"Protocols"," vedle QInsight.exe). Přeložíte-li si vlastní upravenou\nkopii (projekty níže), nahraďte jí tyto DLL.",[396,467,468,469,472,473,476,477,480,481,484,485,488],{},"V ",[384,470,471],{},"Project Configuration"," přidejte driver ",[384,474,475],{},"Temperature Sensor\n(example)"," — předvyplní se ",[403,478,479],{},"periodMs=500;channels=2"," — a k němu\nprotokol ",[384,482,483],{},"Temperature Sensor Protocol (example)",". Protokol svůj\ndriver jmenuje v ",[403,486,487],{},"CompatibleDrivers"," (viz níže), takže dvojice se\nnabízí jen spolu.",[396,490,491,492,495,496,499,500,503,504],{},"Vytvořte proměnné, např. ",[403,493,494],{},"Temp1"," (double), ",[403,497,498],{},"Temp2"," (double) a ",[403,501,502],{},"TempSet","\n(double), a přiřaďte je protokolu s commParams:\n",[393,505,506,514,521],{},[396,507,508,510,511],{},[403,509,494],{},": ",[403,512,513],{},"direction=\"read\";channel=\"ch1\"",[396,515,516,510,518],{},[403,517,498],{},[403,519,520],{},"direction=\"read\";channel=\"ch2\"",[396,522,523,510,525],{},[403,524,502],{},[403,526,527],{},"direction=\"write\";channel=\"ch1\"",[396,529,530,531,533,534,536,537,539,540,542,543,546,547,549,550,552],{},"Spusťte runtime: hodnoty ",[403,532,494],{},"\u002F",[403,535,498],{}," přicházejí do grafu. Zapište přes control\nve Write Mode (Single-Signal nebo Watch Table, viz\n",[380,538,117],{"href":115},") do ",[403,541,502],{},"\nnapříklad ",[403,544,545],{},"50"," — teplota kanálu ch1 se změní na 50 °C. Control ukazuje\njen hodnoty ze zařízení, takže novou teplotu uvidíte v ",[403,548,494],{},", ne jako\nozvěnu v ",[403,551,502],{},".",[436,554,556],{"id":555},"projekty","Projekty",[377,558,559,560,563,564,567,568,571],{},"Dva běžné class-library projekty; referencují jen základní projekty QSuite\n(",[403,561,562],{},"Driver",", resp. ",[403,565,566],{},"Protocol"," + ",[403,569,570],{},"VariableEvents",").",[573,574,579],"pre",{"className":575,"code":576,"language":577,"meta":578,"style":578},"language-xml shiki shiki-themes github-light github-dark-default","\u003C!-- TempSensorDriver.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.TempSensorDriver\u003C\u002FAssemblyName>\n        \u003CRootNamespace>Qenex.QSuite.Examples.TempSensorDriver\u003C\u002FRootNamespace>\n    \u003C\u002FPropertyGroup>\n    \u003CItemGroup>\n      \u003CProjectReference Include=\"..\\..\\Drivers\\Driver\\Driver.csproj\" \u002F>\n    \u003C\u002FItemGroup>\n\u003C\u002FProject>\n","xml","",[403,580,581,589,595,601,607,613,619,625,631,637,643,649,655],{"__ignoreMap":578},[582,583,586],"span",{"class":584,"line":585},"line",1,[582,587,588],{},"\u003C!-- TempSensorDriver.csproj -->\n",[582,590,592],{"class":584,"line":591},2,[582,593,594],{},"\u003CProject Sdk=\"Microsoft.NET.Sdk\">\n",[582,596,598],{"class":584,"line":597},3,[582,599,600],{},"    \u003CPropertyGroup>\n",[582,602,604],{"class":584,"line":603},4,[582,605,606],{},"        \u003CTargetFramework>net10.0\u003C\u002FTargetFramework>\n",[582,608,610],{"class":584,"line":609},5,[582,611,612],{},"        \u003CImplicitUsings>enable\u003C\u002FImplicitUsings>\n",[582,614,616],{"class":584,"line":615},6,[582,617,618],{},"        \u003CNullable>enable\u003C\u002FNullable>\n",[582,620,622],{"class":584,"line":621},7,[582,623,624],{},"        \u003CAssemblyName>Qenex.QSuite.Examples.TempSensorDriver\u003C\u002FAssemblyName>\n",[582,626,628],{"class":584,"line":627},8,[582,629,630],{},"        \u003CRootNamespace>Qenex.QSuite.Examples.TempSensorDriver\u003C\u002FRootNamespace>\n",[582,632,634],{"class":584,"line":633},9,[582,635,636],{},"    \u003C\u002FPropertyGroup>\n",[582,638,640],{"class":584,"line":639},10,[582,641,642],{},"    \u003CItemGroup>\n",[582,644,646],{"class":584,"line":645},11,[582,647,648],{},"      \u003CProjectReference Include=\"..\\..\\Drivers\\Driver\\Driver.csproj\" \u002F>\n",[582,650,652],{"class":584,"line":651},12,[582,653,654],{},"    \u003C\u002FItemGroup>\n",[582,656,658],{"class":584,"line":657},13,[582,659,660],{},"\u003C\u002FProject>\n",[573,662,664],{"className":575,"code":663,"language":577,"meta":578,"style":578},"\u003C!-- TempSensorProtocol.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.TempSensorProtocol\u003C\u002FAssemblyName>\n        \u003CRootNamespace>Qenex.QSuite.Examples.TempSensorProtocol\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",[403,665,666,671,675,679,683,687,691,696,701,705,709,714,719,723],{"__ignoreMap":578},[582,667,668],{"class":584,"line":585},[582,669,670],{},"\u003C!-- TempSensorProtocol.csproj -->\n",[582,672,673],{"class":584,"line":591},[582,674,594],{},[582,676,677],{"class":584,"line":597},[582,678,600],{},[582,680,681],{"class":584,"line":603},[582,682,606],{},[582,684,685],{"class":584,"line":609},[582,686,612],{},[582,688,689],{"class":584,"line":615},[582,690,618],{},[582,692,693],{"class":584,"line":621},[582,694,695],{},"        \u003CAssemblyName>Qenex.QSuite.Examples.TempSensorProtocol\u003C\u002FAssemblyName>\n",[582,697,698],{"class":584,"line":627},[582,699,700],{},"        \u003CRootNamespace>Qenex.QSuite.Examples.TempSensorProtocol\u003C\u002FRootNamespace>\n",[582,702,703],{"class":584,"line":633},[582,704,636],{},[582,706,707],{"class":584,"line":639},[582,708,642],{},[582,710,711],{"class":584,"line":645},[582,712,713],{},"      \u003CProjectReference Include=\"..\\..\\Protocols\\Protocol\\Protocol.csproj\" \u002F>\n",[582,715,716],{"class":584,"line":651},[582,717,718],{},"      \u003CProjectReference Include=\"..\\..\\Variables\\VariableEvents\\VariableEvents.csproj\" \u002F>\n",[582,720,721],{"class":584,"line":657},[582,722,654],{},[582,724,726],{"class":584,"line":725},14,[582,727,660],{},[377,729,730,731,734,735,552],{},"Hotový projekt s touto dvojicí pluginů je v instalátoru jako\n",[403,732,733],{},"Qenex_SimulTempMeasure.qproj",", popsaný v příkladu\n",[380,736,343],{"href":344},[436,738,740],{"id":739},"driver-tempsensordrivercs","Driver: TempSensorDriver.cs",[377,742,743,744,747,748,751,752,755,756,759,760,763,764,767,768,771,772,775,776,779,780,783],{},"Za povšimnutí: ",[403,745,746],{},"ITransportSource\u003Cstring>"," (driver předává protokolům\ntextové řádky), ",[403,749,750],{},"DefaultRawSettings"," (šablona nastavení) a ",[403,753,754],{},"SettingsParser","\nv ",[403,757,758],{},"SetConfiguration"," (neznámý klíč se hlásí varováním), životní cyklus se\nstavy, ",[403,761,762],{},"SetTransmitters"," (injektáž zápisové cesty do protokolů) a delegace\npřes ",[403,765,766],{},"IProtocolVariableCommandDriver"," — zápis operátora míří na\n",[403,769,770],{},"IProtocolVariableWriteProtocol"," protokolu, čtení On Request na\n",[403,773,774],{},"IProtocolVariableReadProtocol",". To tento protokol neimplementuje\n(zařízení hodnoty jen posílá), takže ",[403,777,778],{},"CanRequestRead"," vrací ",[403,781,782],{},"false","\na tlačítko Read zůstane pro jeho proměnné neaktivní.",[573,785,789],{"className":786,"code":787,"language":788,"meta":578,"style":578},"language-csharp shiki shiki-themes github-light github-dark-default","using System.Globalization;\nusing 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.TempSensorDriver;\n\n\u002F\u002F\u002F \u003Csummary>\n\u002F\u002F\u002F Example driver: simulates a temperature sensor device with a few channels. Every period it\n\u002F\u002F\u002F produces one text line per channel (\"ch1=23.47\") and hands the lines to its protocols.\n\u002F\u002F\u002F An operator write coming back through Send re-bases the channel's temperature, so the whole\n\u002F\u002F\u002F read\u002Fwrite chain can be tried without any hardware.\n\u002F\u002F\u002F \u003C\u002Fsummary>\npublic class TempSensorDriver : DriverBase, IProtocolVariableCommandDriver, ITransportSource\u003Cstring>\n{\n    private int periodMs = 500;\n    private int channelCount = 2;\n\n    \u002F\u002F The simulated device state: current temperature of every channel.\n    private readonly object channelLock = new();\n    private double[] channelTemps = [];\n\n    private readonly Random random = new();\n    private volatile bool exitRequested;\n    private CancellationTokenSource? runCts;\n    private Task? runTask;\n\n    public TempSensorDriver()\n    {\n        Specification = new SpecificationBase\n        {\n            Name = \"TempSensorDriver\",\n            Label = \"Temperature Sensor (example)\",\n            Description = \"Example driver simulating a multi-channel temperature sensor.\",\n            CreatedOn = new DateTime(2026, 7, 26),\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    public override string DefaultRawSettings => \"periodMs=500;channels=2\";\n\n    private static readonly string[] KnownSettings = [\"periodMs\", \"channels\"];\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 the same way the built-in drivers do. A missing key keeps the default; an invalid\n        \u002F\u002F value is reported and the default stays.\n        var settings = SettingsParser.Parse(RawSettings, KnownSettings, Logger, \"Temperature sensor driver\");\n\n        if (settings.TryGetValue(\"periodMs\", out var periodText))\n        {\n            if (int.TryParse(periodText, out var parsedPeriod) && parsedPeriod > 0)\n            {\n                periodMs = parsedPeriod;\n            }\n            else\n            {\n                Logger?.Log(LogLevel.Warn, $\"Temperature sensor driver: invalid periodMs '{periodText}', using {periodMs} ms.\");\n            }\n        }\n\n        if (settings.TryGetValue(\"channels\", out var channelsText))\n        {\n            if (int.TryParse(channelsText, out var parsedChannels))\n            {\n                channelCount = Math.Clamp(parsedChannels, 1, 16);\n            }\n            else\n            {\n                Logger?.Log(LogLevel.Warn, $\"Temperature sensor driver: invalid channels '{channelsText}', using {channelCount}.\");\n            }\n        }\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        exitRequested = false;\n\n        \u002F\u002F Every channel starts at a slightly different temperature.\n        lock (channelLock)\n        {\n            channelTemps = Enumerable.Range(0, channelCount).Select(i => 20.0 + 2.0 * i).ToArray();\n        }\n\n        \u002F\u002F Give write-capable protocols a TX path into this driver before they start.\n        SetTransmitters(ApplyWriteAsync);\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        exitRequested = true;\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, \"Temp sensor: the sampling loop did not stop before timeout.\");\n            }\n        }\n\n        foreach (var protocol in Protocols)\n        {\n            await protocol.StopAsync(ct);\n        }\n\n        SetTransmitters(null);\n        runCts?.Dispose();\n        runCts = null;\n        runTask = null;\n        SetState(CommunicationState.Stopped);\n    }\n\n    public override void Dispose()\n    {\n        runCts?.Dispose();\n    }\n\n    #endregion\n\n    #region Communication\n\n    \u002F\u002F The protocol encoded an operator write into a \"ch1=50\" line — apply it to the device.\n    public override void Send\u003CT>(T data)\n    {\n        if (data is string line)\n        {\n            ApplyWrite(line);\n        }\n    }\n\n    public override Task SendAsync\u003CT>(T data, CancellationToken ct = default)\n    {\n        Send(data);\n        return Task.CompletedTask;\n    }\n\n    \u002F\u002F Operator writes: delegated to the owning protocol (same pattern as the CAN and serial drivers).\n    public bool CanSendCommand(IProtocolVariable protocolVariable)\n    {\n        return Protocols\n            .OfType\u003CIProtocolVariableWriteProtocol>()\n            .Any(protocol => protocol.CanWriteVariable(protocolVariable));\n    }\n\n    public async Task OnProtocolVariableCommandAsync(IProtocolVariable protocolVariable, CancellationToken ct = default)\n    {\n        foreach (var protocol in Protocols.OfType\u003CIProtocolVariableWriteProtocol>())\n        {\n            if (!protocol.CanWriteVariable(protocolVariable))\n            {\n                continue;\n            }\n\n            await protocol.WriteVariableAsync(protocolVariable, ct);\n            return;\n        }\n    }\n\n    \u002F\u002F On-request reads: the module wires read-requested notifications to this driver; the driver\n    \u002F\u002F delegates to the owning protocol (IProtocolVariableReadProtocol), which reads the variable\n    \u002F\u002F once over this driver's transport. Failures propagate so the requesting control sees them.\n    public bool CanRequestRead(IProtocolVariable protocolVariable)\n    {\n        return Protocols\n            .OfType\u003CIProtocolVariableReadProtocol>()\n            .Any(protocol => protocol.CanReadVariable(protocolVariable));\n    }\n\n    public async Task OnProtocolVariableReadRequestAsync(IProtocolVariable protocolVariable, CancellationToken ct = default)\n    {\n        foreach (var protocol in Protocols.OfType\u003CIProtocolVariableReadProtocol>())\n        {\n            if (!protocol.CanReadVariable(protocolVariable))\n            {\n                continue;\n            }\n\n            await protocol.ReadVariableAsync(protocolVariable, ct);\n            return;\n        }\n\n        throw new InvalidOperationException(\n            $\"No protocol of this driver can read variable '{protocolVariable.Variable?.Name}' on request.\");\n    }\n\n    #endregion\n\n    #region Simulated device\n\n    private async Task RunLoopAsync(CancellationToken ct)\n    {\n        try\n        {\n            while (!ct.IsCancellationRequested && !exitRequested)\n            {\n                var lines = new List\u003Cstring>(channelCount);\n                lock (channelLock)\n                {\n                    for (var i = 0; i \u003C channelTemps.Length; i++)\n                    {\n                        \u002F\u002F The temperature drifts slowly around its current base.\n                        channelTemps[i] += 0.1 * (random.NextDouble() * 2.0 - 1.0);\n                        lines.Add($\"ch{i + 1}={channelTemps[i].ToString(\"F2\", CultureInfo.InvariantCulture)}\");\n                    }\n                }\n\n                foreach (var protocol in Protocols)\n                {\n                    if (protocol is ProtocolBase\u003Cstring> textProtocol)\n                    {\n                        await textProtocol.AddReceivedDataToQueueAsync(lines, ct);\n                    }\n                }\n\n                await Task.Delay(periodMs, ct);\n            }\n        }\n        catch (OperationCanceledException) when (ct.IsCancellationRequested || exitRequested)\n        {\n            \u002F\u002F Normal stop.\n        }\n        catch (Exception e)\n        {\n            Logger?.Log(LogLevel.Error, $\"Temp sensor loop failed: {e.Message}\");\n            SetState(CommunicationState.Faulted, e.Message);\n        }\n    }\n\n    private Task ApplyWriteAsync(string line, CancellationToken ct)\n    {\n        ApplyWrite(line);\n        return Task.CompletedTask;\n    }\n\n    private void ApplyWrite(string line)\n    {\n        var pair = line.Split('=', 2, StringSplitOptions.TrimEntries);\n        if (pair.Length != 2\n            || !pair[0].StartsWith(\"ch\", StringComparison.OrdinalIgnoreCase)\n            || !int.TryParse(pair[0][2..], out var channelNumber)\n            || !double.TryParse(pair[1], NumberStyles.Float, CultureInfo.InvariantCulture, out var value))\n        {\n            Logger?.Log(LogLevel.Warn, $\"Temp sensor: cannot apply write '{line}'.\");\n            return;\n        }\n\n        lock (channelLock)\n        {\n            var index = channelNumber - 1;\n            if (index \u003C 0 || index >= channelTemps.Length)\n            {\n                Logger?.Log(LogLevel.Warn, $\"Temp sensor: channel '{pair[0]}' does not exist.\");\n                return;\n            }\n\n            channelTemps[index] = value;\n        }\n\n        Logger?.Log(LogLevel.Info, $\"Temp sensor: channel {pair[0]} re-based to {pair[1]}.\");\n    }\n\n    private void SetTransmitters(Func\u003Cstring, CancellationToken, Task>? transmitter)\n    {\n        foreach (var protocol in Protocols)\n        {\n            if (protocol is ITransportProtocol\u003Cstring> transportProtocol)\n            {\n                transportProtocol.SetTransmitter(transmitter);\n            }\n        }\n    }\n\n    #endregion\n}\n","csharp",[403,790,791,796,801,806,811,816,821,826,832,837,841,846,851,856,861,867,873,879,885,891,897,902,908,914,920,925,931,937,943,949,954,960,966,972,978,984,990,996,1002,1008,1014,1020,1026,1032,1037,1043,1048,1054,1059,1065,1070,1076,1081,1087,1093,1099,1105,1110,1116,1121,1127,1133,1139,1145,1151,1156,1162,1167,1173,1178,1184,1189,1195,1200,1206,1211,1216,1221,1227,1232,1237,1242,1247,1253,1258,1264,1269,1275,1280,1286,1291,1297,1303,1308,1313,1319,1324,1329,1334,1339,1345,1351,1356,1362,1368,1373,1379,1384,1389,1395,1401,1407,1412,1418,1423,1428,1434,1440,1446,1452,1458,1464,1470,1475,1480,1486,1491,1497,1503,1508,1514,1519,1525,1530,1535,1541,1546,1552,1557,1563,1568,1574,1579,1584,1590,1595,1601,1606,1611,1616,1621,1626,1632,1637,1642,1648,1654,1660,1666,1672,1677,1682,1688,1693,1698,1703,1708,1713,1718,1724,1729,1735,1741,1746,1752,1757,1763,1768,1773,1778,1784,1789,1795,1801,1806,1811,1817,1823,1828,1834,1840,1846,1851,1856,1862,1867,1873,1878,1884,1889,1895,1900,1905,1911,1916,1921,1926,1931,1937,1943,1949,1955,1960,1965,1971,1977,1982,1987,1993,1998,2004,2009,2015,2020,2025,2030,2035,2041,2046,2051,2056,2062,2068,2073,2078,2083,2088,2094,2099,2105,2110,2116,2121,2127,2132,2138,2144,2150,2156,2162,2168,2174,2180,2186,2192,2197,2203,2208,2214,2219,2225,2230,2235,2240,2246,2251,2256,2262,2267,2273,2278,2284,2289,2295,2301,2306,2311,2316,2322,2327,2333,2338,2343,2348,2354,2359,2365,2371,2377,2383,2389,2394,2400,2405,2410,2415,2420,2425,2431,2437,2442,2448,2454,2459,2464,2470,2475,2480,2486,2491,2496,2502,2507,2512,2517,2523,2528,2534,2539,2544,2549,2554,2559],{"__ignoreMap":578},[582,792,793],{"class":584,"line":585},[582,794,795],{},"using System.Globalization;\n",[582,797,798],{"class":584,"line":591},[582,799,800],{},"using System.Reflection;\n",[582,802,803],{"class":584,"line":597},[582,804,805],{},"using Qenex.QSuite.Common.CoreComm;\n",[582,807,808],{"class":584,"line":603},[582,809,810],{},"using Qenex.QSuite.Drivers.Driver;\n",[582,812,813],{"class":584,"line":609},[582,814,815],{},"using Qenex.QSuite.LogSystems.LogSystem;\n",[582,817,818],{"class":584,"line":615},[582,819,820],{},"using Qenex.QSuite.Protocols.Protocol;\n",[582,822,823],{"class":584,"line":621},[582,824,825],{},"using Qenex.QSuite.Specifications.Specification;\n",[582,827,828],{"class":584,"line":627},[582,829,831],{"emptyLinePlaceholder":830},true,"\n",[582,833,834],{"class":584,"line":633},[582,835,836],{},"namespace Qenex.QSuite.Examples.TempSensorDriver;\n",[582,838,839],{"class":584,"line":639},[582,840,831],{"emptyLinePlaceholder":830},[582,842,843],{"class":584,"line":645},[582,844,845],{},"\u002F\u002F\u002F \u003Csummary>\n",[582,847,848],{"class":584,"line":651},[582,849,850],{},"\u002F\u002F\u002F Example driver: simulates a temperature sensor device with a few channels. Every period it\n",[582,852,853],{"class":584,"line":657},[582,854,855],{},"\u002F\u002F\u002F produces one text line per channel (\"ch1=23.47\") and hands the lines to its protocols.\n",[582,857,858],{"class":584,"line":725},[582,859,860],{},"\u002F\u002F\u002F An operator write coming back through Send re-bases the channel's temperature, so the whole\n",[582,862,864],{"class":584,"line":863},15,[582,865,866],{},"\u002F\u002F\u002F read\u002Fwrite chain can be tried without any hardware.\n",[582,868,870],{"class":584,"line":869},16,[582,871,872],{},"\u002F\u002F\u002F \u003C\u002Fsummary>\n",[582,874,876],{"class":584,"line":875},17,[582,877,878],{},"public class TempSensorDriver : DriverBase, IProtocolVariableCommandDriver, ITransportSource\u003Cstring>\n",[582,880,882],{"class":584,"line":881},18,[582,883,884],{},"{\n",[582,886,888],{"class":584,"line":887},19,[582,889,890],{},"    private int periodMs = 500;\n",[582,892,894],{"class":584,"line":893},20,[582,895,896],{},"    private int channelCount = 2;\n",[582,898,900],{"class":584,"line":899},21,[582,901,831],{"emptyLinePlaceholder":830},[582,903,905],{"class":584,"line":904},22,[582,906,907],{},"    \u002F\u002F The simulated device state: current temperature of every channel.\n",[582,909,911],{"class":584,"line":910},23,[582,912,913],{},"    private readonly object channelLock = new();\n",[582,915,917],{"class":584,"line":916},24,[582,918,919],{},"    private double[] channelTemps = [];\n",[582,921,923],{"class":584,"line":922},25,[582,924,831],{"emptyLinePlaceholder":830},[582,926,928],{"class":584,"line":927},26,[582,929,930],{},"    private readonly Random random = new();\n",[582,932,934],{"class":584,"line":933},27,[582,935,936],{},"    private volatile bool exitRequested;\n",[582,938,940],{"class":584,"line":939},28,[582,941,942],{},"    private CancellationTokenSource? runCts;\n",[582,944,946],{"class":584,"line":945},29,[582,947,948],{},"    private Task? runTask;\n",[582,950,952],{"class":584,"line":951},30,[582,953,831],{"emptyLinePlaceholder":830},[582,955,957],{"class":584,"line":956},31,[582,958,959],{},"    public TempSensorDriver()\n",[582,961,963],{"class":584,"line":962},32,[582,964,965],{},"    {\n",[582,967,969],{"class":584,"line":968},33,[582,970,971],{},"        Specification = new SpecificationBase\n",[582,973,975],{"class":584,"line":974},34,[582,976,977],{},"        {\n",[582,979,981],{"class":584,"line":980},35,[582,982,983],{},"            Name = \"TempSensorDriver\",\n",[582,985,987],{"class":584,"line":986},36,[582,988,989],{},"            Label = \"Temperature Sensor (example)\",\n",[582,991,993],{"class":584,"line":992},37,[582,994,995],{},"            Description = \"Example driver simulating a multi-channel temperature sensor.\",\n",[582,997,999],{"class":584,"line":998},38,[582,1000,1001],{},"            CreatedOn = new DateTime(2026, 7, 26),\n",[582,1003,1005],{"class":584,"line":1004},39,[582,1006,1007],{},"            Version = Assembly.GetExecutingAssembly().GetName().Version ?? new Version(1, 0, 0),\n",[582,1009,1011],{"class":584,"line":1010},40,[582,1012,1013],{},"            Author = \"Qenex\",\n",[582,1015,1017],{"class":584,"line":1016},41,[582,1018,1019],{},"            Company = \"QENEX Ltd.\"\n",[582,1021,1023],{"class":584,"line":1022},42,[582,1024,1025],{},"        };\n",[582,1027,1029],{"class":584,"line":1028},43,[582,1030,1031],{},"    }\n",[582,1033,1035],{"class":584,"line":1034},44,[582,1036,831],{"emptyLinePlaceholder":830},[582,1038,1040],{"class":584,"line":1039},45,[582,1041,1042],{},"    #region Configuration\n",[582,1044,1046],{"class":584,"line":1045},46,[582,1047,831],{"emptyLinePlaceholder":830},[582,1049,1051],{"class":584,"line":1050},47,[582,1052,1053],{},"    public override string DefaultRawSettings => \"periodMs=500;channels=2\";\n",[582,1055,1057],{"class":584,"line":1056},48,[582,1058,831],{"emptyLinePlaceholder":830},[582,1060,1062],{"class":584,"line":1061},49,[582,1063,1064],{},"    private static readonly string[] KnownSettings = [\"periodMs\", \"channels\"];\n",[582,1066,1068],{"class":584,"line":1067},50,[582,1069,831],{"emptyLinePlaceholder":830},[582,1071,1073],{"class":584,"line":1072},51,[582,1074,1075],{},"    public override void SetConfiguration()\n",[582,1077,1079],{"class":584,"line":1078},52,[582,1080,965],{},[582,1082,1084],{"class":584,"line":1083},53,[582,1085,1086],{},"        \u002F\u002F The shared parser splits \"key=value;...\" and warns about every key it does not know,\n",[582,1088,1090],{"class":584,"line":1089},54,[582,1091,1092],{},"        \u002F\u002F the same way the built-in drivers do. A missing key keeps the default; an invalid\n",[582,1094,1096],{"class":584,"line":1095},55,[582,1097,1098],{},"        \u002F\u002F value is reported and the default stays.\n",[582,1100,1102],{"class":584,"line":1101},56,[582,1103,1104],{},"        var settings = SettingsParser.Parse(RawSettings, KnownSettings, Logger, \"Temperature sensor driver\");\n",[582,1106,1108],{"class":584,"line":1107},57,[582,1109,831],{"emptyLinePlaceholder":830},[582,1111,1113],{"class":584,"line":1112},58,[582,1114,1115],{},"        if (settings.TryGetValue(\"periodMs\", out var periodText))\n",[582,1117,1119],{"class":584,"line":1118},59,[582,1120,977],{},[582,1122,1124],{"class":584,"line":1123},60,[582,1125,1126],{},"            if (int.TryParse(periodText, out var parsedPeriod) && parsedPeriod > 0)\n",[582,1128,1130],{"class":584,"line":1129},61,[582,1131,1132],{},"            {\n",[582,1134,1136],{"class":584,"line":1135},62,[582,1137,1138],{},"                periodMs = parsedPeriod;\n",[582,1140,1142],{"class":584,"line":1141},63,[582,1143,1144],{},"            }\n",[582,1146,1148],{"class":584,"line":1147},64,[582,1149,1150],{},"            else\n",[582,1152,1154],{"class":584,"line":1153},65,[582,1155,1132],{},[582,1157,1159],{"class":584,"line":1158},66,[582,1160,1161],{},"                Logger?.Log(LogLevel.Warn, $\"Temperature sensor driver: invalid periodMs '{periodText}', using {periodMs} ms.\");\n",[582,1163,1165],{"class":584,"line":1164},67,[582,1166,1144],{},[582,1168,1170],{"class":584,"line":1169},68,[582,1171,1172],{},"        }\n",[582,1174,1176],{"class":584,"line":1175},69,[582,1177,831],{"emptyLinePlaceholder":830},[582,1179,1181],{"class":584,"line":1180},70,[582,1182,1183],{},"        if (settings.TryGetValue(\"channels\", out var channelsText))\n",[582,1185,1187],{"class":584,"line":1186},71,[582,1188,977],{},[582,1190,1192],{"class":584,"line":1191},72,[582,1193,1194],{},"            if (int.TryParse(channelsText, out var parsedChannels))\n",[582,1196,1198],{"class":584,"line":1197},73,[582,1199,1132],{},[582,1201,1203],{"class":584,"line":1202},74,[582,1204,1205],{},"                channelCount = Math.Clamp(parsedChannels, 1, 16);\n",[582,1207,1209],{"class":584,"line":1208},75,[582,1210,1144],{},[582,1212,1214],{"class":584,"line":1213},76,[582,1215,1150],{},[582,1217,1219],{"class":584,"line":1218},77,[582,1220,1132],{},[582,1222,1224],{"class":584,"line":1223},78,[582,1225,1226],{},"                Logger?.Log(LogLevel.Warn, $\"Temperature sensor driver: invalid channels '{channelsText}', using {channelCount}.\");\n",[582,1228,1230],{"class":584,"line":1229},79,[582,1231,1144],{},[582,1233,1235],{"class":584,"line":1234},80,[582,1236,1172],{},[582,1238,1240],{"class":584,"line":1239},81,[582,1241,1031],{},[582,1243,1245],{"class":584,"line":1244},82,[582,1246,831],{"emptyLinePlaceholder":830},[582,1248,1250],{"class":584,"line":1249},83,[582,1251,1252],{},"    #endregion\n",[582,1254,1256],{"class":584,"line":1255},84,[582,1257,831],{"emptyLinePlaceholder":830},[582,1259,1261],{"class":584,"line":1260},85,[582,1262,1263],{},"    #region Driver control\n",[582,1265,1267],{"class":584,"line":1266},86,[582,1268,831],{"emptyLinePlaceholder":830},[582,1270,1272],{"class":584,"line":1271},87,[582,1273,1274],{},"    public override async Task StartAsync(CancellationToken ct = default)\n",[582,1276,1278],{"class":584,"line":1277},88,[582,1279,965],{},[582,1281,1283],{"class":584,"line":1282},89,[582,1284,1285],{},"        if (!IsEnabled)\n",[582,1287,1289],{"class":584,"line":1288},90,[582,1290,977],{},[582,1292,1294],{"class":584,"line":1293},91,[582,1295,1296],{},"            SetState(CommunicationState.Disabled);\n",[582,1298,1300],{"class":584,"line":1299},92,[582,1301,1302],{},"            return;\n",[582,1304,1306],{"class":584,"line":1305},93,[582,1307,1172],{},[582,1309,1311],{"class":584,"line":1310},94,[582,1312,831],{"emptyLinePlaceholder":830},[582,1314,1316],{"class":584,"line":1315},95,[582,1317,1318],{},"        if (State == CommunicationState.Running || runTask is { IsCompleted: false })\n",[582,1320,1322],{"class":584,"line":1321},96,[582,1323,977],{},[582,1325,1327],{"class":584,"line":1326},97,[582,1328,1302],{},[582,1330,1332],{"class":584,"line":1331},98,[582,1333,1172],{},[582,1335,1337],{"class":584,"line":1336},99,[582,1338,831],{"emptyLinePlaceholder":830},[582,1340,1342],{"class":584,"line":1341},100,[582,1343,1344],{},"        SetState(CommunicationState.Starting);\n",[582,1346,1348],{"class":584,"line":1347},101,[582,1349,1350],{},"        exitRequested = false;\n",[582,1352,1354],{"class":584,"line":1353},102,[582,1355,831],{"emptyLinePlaceholder":830},[582,1357,1359],{"class":584,"line":1358},103,[582,1360,1361],{},"        \u002F\u002F Every channel starts at a slightly different temperature.\n",[582,1363,1365],{"class":584,"line":1364},104,[582,1366,1367],{},"        lock (channelLock)\n",[582,1369,1371],{"class":584,"line":1370},105,[582,1372,977],{},[582,1374,1376],{"class":584,"line":1375},106,[582,1377,1378],{},"            channelTemps = Enumerable.Range(0, channelCount).Select(i => 20.0 + 2.0 * i).ToArray();\n",[582,1380,1382],{"class":584,"line":1381},107,[582,1383,1172],{},[582,1385,1387],{"class":584,"line":1386},108,[582,1388,831],{"emptyLinePlaceholder":830},[582,1390,1392],{"class":584,"line":1391},109,[582,1393,1394],{},"        \u002F\u002F Give write-capable protocols a TX path into this driver before they start.\n",[582,1396,1398],{"class":584,"line":1397},110,[582,1399,1400],{},"        SetTransmitters(ApplyWriteAsync);\n",[582,1402,1404],{"class":584,"line":1403},111,[582,1405,1406],{},"        foreach (var protocol in Protocols)\n",[582,1408,1410],{"class":584,"line":1409},112,[582,1411,977],{},[582,1413,1415],{"class":584,"line":1414},113,[582,1416,1417],{},"            await protocol.StartAsync(ct);\n",[582,1419,1421],{"class":584,"line":1420},114,[582,1422,1172],{},[582,1424,1426],{"class":584,"line":1425},115,[582,1427,831],{"emptyLinePlaceholder":830},[582,1429,1431],{"class":584,"line":1430},116,[582,1432,1433],{},"        runCts = CancellationTokenSource.CreateLinkedTokenSource(ct);\n",[582,1435,1437],{"class":584,"line":1436},117,[582,1438,1439],{},"        \u002F\u002F Task.Run: the loop must not inherit the caller's (UI) SynchronizationContext —\n",[582,1441,1443],{"class":584,"line":1442},118,[582,1444,1445],{},"        \u002F\u002F a blocked dispatcher (window drag, busy UI) would stall the whole session.\n",[582,1447,1449],{"class":584,"line":1448},119,[582,1450,1451],{},"        \u002F\u002F Capture the token now: a racing StopAsync may null runCts before the loop starts.\n",[582,1453,1455],{"class":584,"line":1454},120,[582,1456,1457],{},"        var runToken = runCts.Token;\n",[582,1459,1461],{"class":584,"line":1460},121,[582,1462,1463],{},"        runTask = Task.Run(() => RunLoopAsync(runToken));\n",[582,1465,1467],{"class":584,"line":1466},122,[582,1468,1469],{},"        SetState(CommunicationState.Running);\n",[582,1471,1473],{"class":584,"line":1472},123,[582,1474,1031],{},[582,1476,1478],{"class":584,"line":1477},124,[582,1479,831],{"emptyLinePlaceholder":830},[582,1481,1483],{"class":584,"line":1482},125,[582,1484,1485],{},"    public override async Task StopAsync(CancellationToken ct = default)\n",[582,1487,1489],{"class":584,"line":1488},126,[582,1490,965],{},[582,1492,1494],{"class":584,"line":1493},127,[582,1495,1496],{},"        SetState(CommunicationState.Stopping);\n",[582,1498,1500],{"class":584,"line":1499},128,[582,1501,1502],{},"        exitRequested = true;\n",[582,1504,1506],{"class":584,"line":1505},129,[582,1507,831],{"emptyLinePlaceholder":830},[582,1509,1511],{"class":584,"line":1510},130,[582,1512,1513],{},"        if (runCts != null)\n",[582,1515,1517],{"class":584,"line":1516},131,[582,1518,977],{},[582,1520,1522],{"class":584,"line":1521},132,[582,1523,1524],{},"            await runCts.CancelAsync();\n",[582,1526,1528],{"class":584,"line":1527},133,[582,1529,1172],{},[582,1531,1533],{"class":584,"line":1532},134,[582,1534,831],{"emptyLinePlaceholder":830},[582,1536,1538],{"class":584,"line":1537},135,[582,1539,1540],{},"        if (runTask != null)\n",[582,1542,1544],{"class":584,"line":1543},136,[582,1545,977],{},[582,1547,1549],{"class":584,"line":1548},137,[582,1550,1551],{},"            try\n",[582,1553,1555],{"class":584,"line":1554},138,[582,1556,1132],{},[582,1558,1560],{"class":584,"line":1559},139,[582,1561,1562],{},"                await runTask.WaitAsync(TimeSpan.FromSeconds(5), ct);\n",[582,1564,1566],{"class":584,"line":1565},140,[582,1567,1144],{},[582,1569,1571],{"class":584,"line":1570},141,[582,1572,1573],{},"            catch (OperationCanceledException)\n",[582,1575,1577],{"class":584,"line":1576},142,[582,1578,1132],{},[582,1580,1582],{"class":584,"line":1581},143,[582,1583,1144],{},[582,1585,1587],{"class":584,"line":1586},144,[582,1588,1589],{},"            catch (TimeoutException)\n",[582,1591,1593],{"class":584,"line":1592},145,[582,1594,1132],{},[582,1596,1598],{"class":584,"line":1597},146,[582,1599,1600],{},"                Logger?.Log(LogLevel.Warn, \"Temp sensor: the sampling loop did not stop before timeout.\");\n",[582,1602,1604],{"class":584,"line":1603},147,[582,1605,1144],{},[582,1607,1609],{"class":584,"line":1608},148,[582,1610,1172],{},[582,1612,1614],{"class":584,"line":1613},149,[582,1615,831],{"emptyLinePlaceholder":830},[582,1617,1619],{"class":584,"line":1618},150,[582,1620,1406],{},[582,1622,1624],{"class":584,"line":1623},151,[582,1625,977],{},[582,1627,1629],{"class":584,"line":1628},152,[582,1630,1631],{},"            await protocol.StopAsync(ct);\n",[582,1633,1635],{"class":584,"line":1634},153,[582,1636,1172],{},[582,1638,1640],{"class":584,"line":1639},154,[582,1641,831],{"emptyLinePlaceholder":830},[582,1643,1645],{"class":584,"line":1644},155,[582,1646,1647],{},"        SetTransmitters(null);\n",[582,1649,1651],{"class":584,"line":1650},156,[582,1652,1653],{},"        runCts?.Dispose();\n",[582,1655,1657],{"class":584,"line":1656},157,[582,1658,1659],{},"        runCts = null;\n",[582,1661,1663],{"class":584,"line":1662},158,[582,1664,1665],{},"        runTask = null;\n",[582,1667,1669],{"class":584,"line":1668},159,[582,1670,1671],{},"        SetState(CommunicationState.Stopped);\n",[582,1673,1675],{"class":584,"line":1674},160,[582,1676,1031],{},[582,1678,1680],{"class":584,"line":1679},161,[582,1681,831],{"emptyLinePlaceholder":830},[582,1683,1685],{"class":584,"line":1684},162,[582,1686,1687],{},"    public override void Dispose()\n",[582,1689,1691],{"class":584,"line":1690},163,[582,1692,965],{},[582,1694,1696],{"class":584,"line":1695},164,[582,1697,1653],{},[582,1699,1701],{"class":584,"line":1700},165,[582,1702,1031],{},[582,1704,1706],{"class":584,"line":1705},166,[582,1707,831],{"emptyLinePlaceholder":830},[582,1709,1711],{"class":584,"line":1710},167,[582,1712,1252],{},[582,1714,1716],{"class":584,"line":1715},168,[582,1717,831],{"emptyLinePlaceholder":830},[582,1719,1721],{"class":584,"line":1720},169,[582,1722,1723],{},"    #region Communication\n",[582,1725,1727],{"class":584,"line":1726},170,[582,1728,831],{"emptyLinePlaceholder":830},[582,1730,1732],{"class":584,"line":1731},171,[582,1733,1734],{},"    \u002F\u002F The protocol encoded an operator write into a \"ch1=50\" line — apply it to the device.\n",[582,1736,1738],{"class":584,"line":1737},172,[582,1739,1740],{},"    public override void Send\u003CT>(T data)\n",[582,1742,1744],{"class":584,"line":1743},173,[582,1745,965],{},[582,1747,1749],{"class":584,"line":1748},174,[582,1750,1751],{},"        if (data is string line)\n",[582,1753,1755],{"class":584,"line":1754},175,[582,1756,977],{},[582,1758,1760],{"class":584,"line":1759},176,[582,1761,1762],{},"            ApplyWrite(line);\n",[582,1764,1766],{"class":584,"line":1765},177,[582,1767,1172],{},[582,1769,1771],{"class":584,"line":1770},178,[582,1772,1031],{},[582,1774,1776],{"class":584,"line":1775},179,[582,1777,831],{"emptyLinePlaceholder":830},[582,1779,1781],{"class":584,"line":1780},180,[582,1782,1783],{},"    public override Task SendAsync\u003CT>(T data, CancellationToken ct = default)\n",[582,1785,1787],{"class":584,"line":1786},181,[582,1788,965],{},[582,1790,1792],{"class":584,"line":1791},182,[582,1793,1794],{},"        Send(data);\n",[582,1796,1798],{"class":584,"line":1797},183,[582,1799,1800],{},"        return Task.CompletedTask;\n",[582,1802,1804],{"class":584,"line":1803},184,[582,1805,1031],{},[582,1807,1809],{"class":584,"line":1808},185,[582,1810,831],{"emptyLinePlaceholder":830},[582,1812,1814],{"class":584,"line":1813},186,[582,1815,1816],{},"    \u002F\u002F Operator writes: delegated to the owning protocol (same pattern as the CAN and serial drivers).\n",[582,1818,1820],{"class":584,"line":1819},187,[582,1821,1822],{},"    public bool CanSendCommand(IProtocolVariable protocolVariable)\n",[582,1824,1826],{"class":584,"line":1825},188,[582,1827,965],{},[582,1829,1831],{"class":584,"line":1830},189,[582,1832,1833],{},"        return Protocols\n",[582,1835,1837],{"class":584,"line":1836},190,[582,1838,1839],{},"            .OfType\u003CIProtocolVariableWriteProtocol>()\n",[582,1841,1843],{"class":584,"line":1842},191,[582,1844,1845],{},"            .Any(protocol => protocol.CanWriteVariable(protocolVariable));\n",[582,1847,1849],{"class":584,"line":1848},192,[582,1850,1031],{},[582,1852,1854],{"class":584,"line":1853},193,[582,1855,831],{"emptyLinePlaceholder":830},[582,1857,1859],{"class":584,"line":1858},194,[582,1860,1861],{},"    public async Task OnProtocolVariableCommandAsync(IProtocolVariable protocolVariable, CancellationToken ct = default)\n",[582,1863,1865],{"class":584,"line":1864},195,[582,1866,965],{},[582,1868,1870],{"class":584,"line":1869},196,[582,1871,1872],{},"        foreach (var protocol in Protocols.OfType\u003CIProtocolVariableWriteProtocol>())\n",[582,1874,1876],{"class":584,"line":1875},197,[582,1877,977],{},[582,1879,1881],{"class":584,"line":1880},198,[582,1882,1883],{},"            if (!protocol.CanWriteVariable(protocolVariable))\n",[582,1885,1887],{"class":584,"line":1886},199,[582,1888,1132],{},[582,1890,1892],{"class":584,"line":1891},200,[582,1893,1894],{},"                continue;\n",[582,1896,1898],{"class":584,"line":1897},201,[582,1899,1144],{},[582,1901,1903],{"class":584,"line":1902},202,[582,1904,831],{"emptyLinePlaceholder":830},[582,1906,1908],{"class":584,"line":1907},203,[582,1909,1910],{},"            await protocol.WriteVariableAsync(protocolVariable, ct);\n",[582,1912,1914],{"class":584,"line":1913},204,[582,1915,1302],{},[582,1917,1919],{"class":584,"line":1918},205,[582,1920,1172],{},[582,1922,1924],{"class":584,"line":1923},206,[582,1925,1031],{},[582,1927,1929],{"class":584,"line":1928},207,[582,1930,831],{"emptyLinePlaceholder":830},[582,1932,1934],{"class":584,"line":1933},208,[582,1935,1936],{},"    \u002F\u002F On-request reads: the module wires read-requested notifications to this driver; the driver\n",[582,1938,1940],{"class":584,"line":1939},209,[582,1941,1942],{},"    \u002F\u002F delegates to the owning protocol (IProtocolVariableReadProtocol), which reads the variable\n",[582,1944,1946],{"class":584,"line":1945},210,[582,1947,1948],{},"    \u002F\u002F once over this driver's transport. Failures propagate so the requesting control sees them.\n",[582,1950,1952],{"class":584,"line":1951},211,[582,1953,1954],{},"    public bool CanRequestRead(IProtocolVariable protocolVariable)\n",[582,1956,1958],{"class":584,"line":1957},212,[582,1959,965],{},[582,1961,1963],{"class":584,"line":1962},213,[582,1964,1833],{},[582,1966,1968],{"class":584,"line":1967},214,[582,1969,1970],{},"            .OfType\u003CIProtocolVariableReadProtocol>()\n",[582,1972,1974],{"class":584,"line":1973},215,[582,1975,1976],{},"            .Any(protocol => protocol.CanReadVariable(protocolVariable));\n",[582,1978,1980],{"class":584,"line":1979},216,[582,1981,1031],{},[582,1983,1985],{"class":584,"line":1984},217,[582,1986,831],{"emptyLinePlaceholder":830},[582,1988,1990],{"class":584,"line":1989},218,[582,1991,1992],{},"    public async Task OnProtocolVariableReadRequestAsync(IProtocolVariable protocolVariable, CancellationToken ct = default)\n",[582,1994,1996],{"class":584,"line":1995},219,[582,1997,965],{},[582,1999,2001],{"class":584,"line":2000},220,[582,2002,2003],{},"        foreach (var protocol in Protocols.OfType\u003CIProtocolVariableReadProtocol>())\n",[582,2005,2007],{"class":584,"line":2006},221,[582,2008,977],{},[582,2010,2012],{"class":584,"line":2011},222,[582,2013,2014],{},"            if (!protocol.CanReadVariable(protocolVariable))\n",[582,2016,2018],{"class":584,"line":2017},223,[582,2019,1132],{},[582,2021,2023],{"class":584,"line":2022},224,[582,2024,1894],{},[582,2026,2028],{"class":584,"line":2027},225,[582,2029,1144],{},[582,2031,2033],{"class":584,"line":2032},226,[582,2034,831],{"emptyLinePlaceholder":830},[582,2036,2038],{"class":584,"line":2037},227,[582,2039,2040],{},"            await protocol.ReadVariableAsync(protocolVariable, ct);\n",[582,2042,2044],{"class":584,"line":2043},228,[582,2045,1302],{},[582,2047,2049],{"class":584,"line":2048},229,[582,2050,1172],{},[582,2052,2054],{"class":584,"line":2053},230,[582,2055,831],{"emptyLinePlaceholder":830},[582,2057,2059],{"class":584,"line":2058},231,[582,2060,2061],{},"        throw new InvalidOperationException(\n",[582,2063,2065],{"class":584,"line":2064},232,[582,2066,2067],{},"            $\"No protocol of this driver can read variable '{protocolVariable.Variable?.Name}' on request.\");\n",[582,2069,2071],{"class":584,"line":2070},233,[582,2072,1031],{},[582,2074,2076],{"class":584,"line":2075},234,[582,2077,831],{"emptyLinePlaceholder":830},[582,2079,2081],{"class":584,"line":2080},235,[582,2082,1252],{},[582,2084,2086],{"class":584,"line":2085},236,[582,2087,831],{"emptyLinePlaceholder":830},[582,2089,2091],{"class":584,"line":2090},237,[582,2092,2093],{},"    #region Simulated device\n",[582,2095,2097],{"class":584,"line":2096},238,[582,2098,831],{"emptyLinePlaceholder":830},[582,2100,2102],{"class":584,"line":2101},239,[582,2103,2104],{},"    private async Task RunLoopAsync(CancellationToken ct)\n",[582,2106,2108],{"class":584,"line":2107},240,[582,2109,965],{},[582,2111,2113],{"class":584,"line":2112},241,[582,2114,2115],{},"        try\n",[582,2117,2119],{"class":584,"line":2118},242,[582,2120,977],{},[582,2122,2124],{"class":584,"line":2123},243,[582,2125,2126],{},"            while (!ct.IsCancellationRequested && !exitRequested)\n",[582,2128,2130],{"class":584,"line":2129},244,[582,2131,1132],{},[582,2133,2135],{"class":584,"line":2134},245,[582,2136,2137],{},"                var lines = new List\u003Cstring>(channelCount);\n",[582,2139,2141],{"class":584,"line":2140},246,[582,2142,2143],{},"                lock (channelLock)\n",[582,2145,2147],{"class":584,"line":2146},247,[582,2148,2149],{},"                {\n",[582,2151,2153],{"class":584,"line":2152},248,[582,2154,2155],{},"                    for (var i = 0; i \u003C channelTemps.Length; i++)\n",[582,2157,2159],{"class":584,"line":2158},249,[582,2160,2161],{},"                    {\n",[582,2163,2165],{"class":584,"line":2164},250,[582,2166,2167],{},"                        \u002F\u002F The temperature drifts slowly around its current base.\n",[582,2169,2171],{"class":584,"line":2170},251,[582,2172,2173],{},"                        channelTemps[i] += 0.1 * (random.NextDouble() * 2.0 - 1.0);\n",[582,2175,2177],{"class":584,"line":2176},252,[582,2178,2179],{},"                        lines.Add($\"ch{i + 1}={channelTemps[i].ToString(\"F2\", CultureInfo.InvariantCulture)}\");\n",[582,2181,2183],{"class":584,"line":2182},253,[582,2184,2185],{},"                    }\n",[582,2187,2189],{"class":584,"line":2188},254,[582,2190,2191],{},"                }\n",[582,2193,2195],{"class":584,"line":2194},255,[582,2196,831],{"emptyLinePlaceholder":830},[582,2198,2200],{"class":584,"line":2199},256,[582,2201,2202],{},"                foreach (var protocol in Protocols)\n",[582,2204,2206],{"class":584,"line":2205},257,[582,2207,2149],{},[582,2209,2211],{"class":584,"line":2210},258,[582,2212,2213],{},"                    if (protocol is ProtocolBase\u003Cstring> textProtocol)\n",[582,2215,2217],{"class":584,"line":2216},259,[582,2218,2161],{},[582,2220,2222],{"class":584,"line":2221},260,[582,2223,2224],{},"                        await textProtocol.AddReceivedDataToQueueAsync(lines, ct);\n",[582,2226,2228],{"class":584,"line":2227},261,[582,2229,2185],{},[582,2231,2233],{"class":584,"line":2232},262,[582,2234,2191],{},[582,2236,2238],{"class":584,"line":2237},263,[582,2239,831],{"emptyLinePlaceholder":830},[582,2241,2243],{"class":584,"line":2242},264,[582,2244,2245],{},"                await Task.Delay(periodMs, ct);\n",[582,2247,2249],{"class":584,"line":2248},265,[582,2250,1144],{},[582,2252,2254],{"class":584,"line":2253},266,[582,2255,1172],{},[582,2257,2259],{"class":584,"line":2258},267,[582,2260,2261],{},"        catch (OperationCanceledException) when (ct.IsCancellationRequested || exitRequested)\n",[582,2263,2265],{"class":584,"line":2264},268,[582,2266,977],{},[582,2268,2270],{"class":584,"line":2269},269,[582,2271,2272],{},"            \u002F\u002F Normal stop.\n",[582,2274,2276],{"class":584,"line":2275},270,[582,2277,1172],{},[582,2279,2281],{"class":584,"line":2280},271,[582,2282,2283],{},"        catch (Exception e)\n",[582,2285,2287],{"class":584,"line":2286},272,[582,2288,977],{},[582,2290,2292],{"class":584,"line":2291},273,[582,2293,2294],{},"            Logger?.Log(LogLevel.Error, $\"Temp sensor loop failed: {e.Message}\");\n",[582,2296,2298],{"class":584,"line":2297},274,[582,2299,2300],{},"            SetState(CommunicationState.Faulted, e.Message);\n",[582,2302,2304],{"class":584,"line":2303},275,[582,2305,1172],{},[582,2307,2309],{"class":584,"line":2308},276,[582,2310,1031],{},[582,2312,2314],{"class":584,"line":2313},277,[582,2315,831],{"emptyLinePlaceholder":830},[582,2317,2319],{"class":584,"line":2318},278,[582,2320,2321],{},"    private Task ApplyWriteAsync(string line, CancellationToken ct)\n",[582,2323,2325],{"class":584,"line":2324},279,[582,2326,965],{},[582,2328,2330],{"class":584,"line":2329},280,[582,2331,2332],{},"        ApplyWrite(line);\n",[582,2334,2336],{"class":584,"line":2335},281,[582,2337,1800],{},[582,2339,2341],{"class":584,"line":2340},282,[582,2342,1031],{},[582,2344,2346],{"class":584,"line":2345},283,[582,2347,831],{"emptyLinePlaceholder":830},[582,2349,2351],{"class":584,"line":2350},284,[582,2352,2353],{},"    private void ApplyWrite(string line)\n",[582,2355,2357],{"class":584,"line":2356},285,[582,2358,965],{},[582,2360,2362],{"class":584,"line":2361},286,[582,2363,2364],{},"        var pair = line.Split('=', 2, StringSplitOptions.TrimEntries);\n",[582,2366,2368],{"class":584,"line":2367},287,[582,2369,2370],{},"        if (pair.Length != 2\n",[582,2372,2374],{"class":584,"line":2373},288,[582,2375,2376],{},"            || !pair[0].StartsWith(\"ch\", StringComparison.OrdinalIgnoreCase)\n",[582,2378,2380],{"class":584,"line":2379},289,[582,2381,2382],{},"            || !int.TryParse(pair[0][2..], out var channelNumber)\n",[582,2384,2386],{"class":584,"line":2385},290,[582,2387,2388],{},"            || !double.TryParse(pair[1], NumberStyles.Float, CultureInfo.InvariantCulture, out var value))\n",[582,2390,2392],{"class":584,"line":2391},291,[582,2393,977],{},[582,2395,2397],{"class":584,"line":2396},292,[582,2398,2399],{},"            Logger?.Log(LogLevel.Warn, $\"Temp sensor: cannot apply write '{line}'.\");\n",[582,2401,2403],{"class":584,"line":2402},293,[582,2404,1302],{},[582,2406,2408],{"class":584,"line":2407},294,[582,2409,1172],{},[582,2411,2413],{"class":584,"line":2412},295,[582,2414,831],{"emptyLinePlaceholder":830},[582,2416,2418],{"class":584,"line":2417},296,[582,2419,1367],{},[582,2421,2423],{"class":584,"line":2422},297,[582,2424,977],{},[582,2426,2428],{"class":584,"line":2427},298,[582,2429,2430],{},"            var index = channelNumber - 1;\n",[582,2432,2434],{"class":584,"line":2433},299,[582,2435,2436],{},"            if (index \u003C 0 || index >= channelTemps.Length)\n",[582,2438,2440],{"class":584,"line":2439},300,[582,2441,1132],{},[582,2443,2445],{"class":584,"line":2444},301,[582,2446,2447],{},"                Logger?.Log(LogLevel.Warn, $\"Temp sensor: channel '{pair[0]}' does not exist.\");\n",[582,2449,2451],{"class":584,"line":2450},302,[582,2452,2453],{},"                return;\n",[582,2455,2457],{"class":584,"line":2456},303,[582,2458,1144],{},[582,2460,2462],{"class":584,"line":2461},304,[582,2463,831],{"emptyLinePlaceholder":830},[582,2465,2467],{"class":584,"line":2466},305,[582,2468,2469],{},"            channelTemps[index] = value;\n",[582,2471,2473],{"class":584,"line":2472},306,[582,2474,1172],{},[582,2476,2478],{"class":584,"line":2477},307,[582,2479,831],{"emptyLinePlaceholder":830},[582,2481,2483],{"class":584,"line":2482},308,[582,2484,2485],{},"        Logger?.Log(LogLevel.Info, $\"Temp sensor: channel {pair[0]} re-based to {pair[1]}.\");\n",[582,2487,2489],{"class":584,"line":2488},309,[582,2490,1031],{},[582,2492,2494],{"class":584,"line":2493},310,[582,2495,831],{"emptyLinePlaceholder":830},[582,2497,2499],{"class":584,"line":2498},311,[582,2500,2501],{},"    private void SetTransmitters(Func\u003Cstring, CancellationToken, Task>? transmitter)\n",[582,2503,2505],{"class":584,"line":2504},312,[582,2506,965],{},[582,2508,2510],{"class":584,"line":2509},313,[582,2511,1406],{},[582,2513,2515],{"class":584,"line":2514},314,[582,2516,977],{},[582,2518,2520],{"class":584,"line":2519},315,[582,2521,2522],{},"            if (protocol is ITransportProtocol\u003Cstring> transportProtocol)\n",[582,2524,2526],{"class":584,"line":2525},316,[582,2527,1132],{},[582,2529,2531],{"class":584,"line":2530},317,[582,2532,2533],{},"                transportProtocol.SetTransmitter(transmitter);\n",[582,2535,2537],{"class":584,"line":2536},318,[582,2538,1144],{},[582,2540,2542],{"class":584,"line":2541},319,[582,2543,1172],{},[582,2545,2547],{"class":584,"line":2546},320,[582,2548,1031],{},[582,2550,2552],{"class":584,"line":2551},321,[582,2553,831],{"emptyLinePlaceholder":830},[582,2555,2557],{"class":584,"line":2556},322,[582,2558,1252],{},[582,2560,2562],{"class":584,"line":2561},323,[582,2563,2564],{},"}\n",[436,2566,2568],{"id":2567},"protokol-tempsensorprotocolcs","Protokol: TempSensorProtocol.cs",[377,2570,743,2571,2574,2575,2578,2579,533,2582,2584,2585],{},[403,2572,2573],{},"CreateDefaultCommParam"," (šablona commParams z pluginu),\nfronta přijatých dat (",[403,2576,2577],{},"System.Threading.Channels","), ",[403,2580,2581],{},"Decode",[403,2583,429],{},"\na zápisová rozhraní ",[403,2586,2587],{},"ITransportProtocol\u003Cstring>",[393,2589,2590],{},[396,2591,2592,552],{},[403,2593,770],{},[573,2595,2597],{"className":786,"code":2596,"language":788,"meta":578,"style":578},"using System.Globalization;\nusing System.Reflection;\nusing System.Threading.Channels;\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.TempSensorProtocol;\n\n\u002F\u002F\u002F \u003Csummary>\n\u002F\u002F\u002F Example protocol: decodes \"ch1=23.47\" text lines from the TempSensorDriver into variables\n\u002F\u002F\u002F addressed by the \"channel\" commParam, and encodes operator writes back into the same lines.\n\u002F\u002F\u002F The generic parameter (string) is what pairs the protocol with a text-line driver.\n\u002F\u002F\u002F \u003C\u002Fsummary>\npublic class TempSensorProtocol : ProtocolBase\u003Cstring>, ITransportProtocol\u003Cstring>, IProtocolVariableWriteProtocol\n{\n    \u002F\u002F Received lines waiting for processing; the channel keeps the consumer loop cancelable.\n    private Channel\u003Cstring>? receivedLines;\n    private volatile Func\u003Cstring, CancellationToken, Task>? transmitter;\n\n    private volatile bool exitRequested;\n    private CancellationTokenSource? runCts;\n    private Task? runTask;\n\n    public TempSensorProtocol()\n    {\n        Specification = new SpecificationBase\n        {\n            Name = \"TempSensorProtocol\",\n            Label = \"Temperature Sensor Protocol (example)\",\n            Description = \"Example protocol decoding text lines of the simulated temperature sensor.\",\n            CreatedOn = new DateTime(2026, 7, 26),\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 \"chN=value\" sensor lines, so it narrows itself to its driver.\n    public override IReadOnlyList\u003Cstring> CompatibleDrivers => [\"TempSensorDriver\"];\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, \"Temperature sensor protocol\");\n    }\n\n    public override string CreateDefaultCommParam(IVariableBase variable, IEnumerable\u003CIVarEvent> variableEvents)\n    {\n        return \"direction=\\\"read\\\";channel=\\\"ch1\\\"\";\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 = TempSensorVariableSpecification.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, $\"direction=\\\"read\\\";channel=\\\"{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 device sends them, so variable events are not used.\n        return CreateProtocolVariable(variable, commParams, isCommunicated);\n    }\n\n    #endregion\n\n    #region Protocol control\n\n    public override Task StartAsync(CancellationToken ct = default)\n    {\n        if (!IsEnabled)\n        {\n            SetState(CommunicationState.Disabled);\n            return Task.CompletedTask;\n        }\n\n        if (State == CommunicationState.Running || runTask is { IsCompleted: false })\n        {\n            return Task.CompletedTask;\n        }\n\n        SetState(CommunicationState.Starting);\n        exitRequested = false;\n        receivedLines = Channel.CreateUnbounded\u003Cstring>();\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        return Task.CompletedTask;\n    }\n\n    public override async Task StopAsync(CancellationToken ct = default)\n    {\n        SetState(CommunicationState.Stopping);\n        exitRequested = true;\n        receivedLines?.Writer.TryComplete();\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, \"Temp sensor protocol did not stop before timeout.\");\n            }\n        }\n\n        runCts?.Dispose();\n        runCts = null;\n        runTask = null;\n        SetState(CommunicationState.Stopped);\n    }\n\n    public override void Dispose()\n    {\n        runCts?.Dispose();\n    }\n\n    #endregion\n\n    #region Received data\n\n    public override Task AddReceivedDataToQueueAsync(IEnumerable\u003Cstring> data, CancellationToken ct = default)\n    {\n        foreach (var line in data)\n        {\n            receivedLines?.Writer.TryWrite(line);\n        }\n\n        return Task.CompletedTask;\n    }\n\n    private async Task RunLoopAsync(CancellationToken ct)\n    {\n        try\n        {\n            await foreach (var line in receivedLines!.Reader.ReadAllAsync(ct))\n            {\n                if (exitRequested)\n                {\n                    break;\n                }\n\n                await ProcessReceivedDataAsync([line], ct);\n            }\n        }\n        catch (OperationCanceledException) when (ct.IsCancellationRequested || exitRequested)\n        {\n            \u002F\u002F Normal stop.\n        }\n        catch (Exception e)\n        {\n            Logger?.Log(LogLevel.Error, $\"Temp sensor protocol loop failed: {e.Message}\");\n            SetState(CommunicationState.Faulted, e.Message);\n        }\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 line in data)\n        {\n            var pair = line.Split('=', 2, StringSplitOptions.TrimEntries);\n            if (pair.Length != 2\n                || !double.TryParse(pair[1], NumberStyles.Float, CultureInfo.InvariantCulture, out var value))\n            {\n                Logger?.Log(LogLevel.Warn, $\"Temp sensor protocol: cannot decode line '{line}'.\");\n                continue;\n            }\n\n            foreach (var protocolVariable in Variables)\n            {\n                if (!protocolVariable.IsCommunicated\n                    || protocolVariable.ProtocolVariableSpecification is not TempSensorVariableSpecification spec\n                    || spec.Direction != CommDirection.Read\n                    || !spec.Channel.Equals(pair[0], StringComparison.OrdinalIgnoreCase)\n                    || protocolVariable.Variable is not ScalarVariable scalarVariable)\n                {\n                    continue;\n                }\n\n                if (!TrySetValue(scalarVariable, value))\n                {\n                    Logger?.Log(LogLevel.Warn, $\"Temp sensor 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        return updated;\n    }\n\n    protected override IEnumerable\u003Cstring> Encode(IEnumerable\u003CIProtocolVariable> protocolVariables)\n    {\n        foreach (var protocolVariable in protocolVariables)\n        {\n            if (protocolVariable.ProtocolVariableSpecification is not TempSensorVariableSpecification spec\n                || protocolVariable.Variable is not ScalarVariable scalarVariable)\n            {\n                continue;\n            }\n\n            var value = Convert.ToDouble(scalarVariable.Values.GetValue(), CultureInfo.InvariantCulture);\n            yield return $\"{spec.Channel}={value.ToString(\"F2\", CultureInfo.InvariantCulture)}\";\n        }\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    #region Operator writes\n\n    public void SetTransmitter(Func\u003Cstring, CancellationToken, Task>? byteTransmitter)\n    {\n        transmitter = byteTransmitter;\n    }\n\n    public bool CanWriteVariable(IProtocolVariable protocolVariable)\n    {\n        return Variables.Contains(protocolVariable)\n               && protocolVariable.ProtocolVariableSpecification is TempSensorVariableSpecification { Direction: CommDirection.Write };\n    }\n\n    public async Task WriteVariableAsync(IProtocolVariable protocolVariable, CancellationToken ct = default)\n    {\n        var currentTransmitter = transmitter\n            ?? throw new InvalidOperationException(\"Transport is not available (no transmitter injected).\");\n\n        foreach (var line in Encode([protocolVariable]))\n        {\n            await currentTransmitter(line, ct);\n        }\n    }\n\n    #endregion\n}\n",[403,2598,2599,2603,2607,2612,2616,2620,2624,2628,2633,2638,2643,2647,2652,2656,2660,2665,2670,2675,2679,2684,2688,2693,2698,2703,2707,2711,2715,2719,2723,2728,2732,2736,2740,2745,2750,2755,2759,2763,2767,2771,2775,2779,2783,2788,2793,2798,2802,2806,2810,2815,2820,2825,2829,2833,2838,2842,2846,2851,2855,2860,2864,2868,2872,2876,2881,2885,2890,2894,2898,2902,2907,2911,2916,2921,2926,2931,2935,2939,2943,2948,2953,2957,2961,2965,2970,2974,2979,2983,2987,2992,2997,3001,3006,3011,3015,3019,3023,3027,3032,3036,3041,3045,3049,3053,3057,3062,3066,3070,3074,3078,3082,3086,3090,3094,3098,3103,3107,3111,3115,3119,3123,3127,3131,3135,3139,3143,3147,3151,3155,3159,3163,3168,3172,3176,3180,3184,3188,3192,3196,3200,3204,3208,3212,3216,3220,3224,3228,3232,3236,3241,3245,3249,3253,3257,3261,3265,3269,3273,3277,3281,3285,3289,3293,3297,3301,3305,3310,3314,3319,3323,3328,3332,3337,3341,3345,3349,3353,3357,3361,3365,3369,3373,3378,3382,3387,3391,3396,3400,3404,3409,3413,3417,3421,3425,3429,3433,3437,3441,3446,3450,3454,3458,3462,3467,3471,3476,3480,3485,3489,3493,3497,3502,3506,3511,3516,3520,3524,3528,3532,3537,3541,3546,3550,3555,3559,3563,3567,3572,3577,3582,3586,3591,3595,3599,3603,3608,3612,3617,3622,3627,3632,3637,3641,3646,3650,3654,3659,3663,3668,3672,3676,3680,3685,3690,3694,3698,3702,3707,3711,3715,3720,3724,3729,3733,3738,3743,3747,3751,3755,3759,3764,3769,3773,3777,3781,3786,3791,3796,3800,3805,3809,3814,3819,3824,3829,3834,3838,3843,3848,3852,3857,3862,3866,3870,3874,3878,3882,3887,3891,3896,3900,3905,3909,3913,3918,3922,3927,3932,3936,3940,3945,3949,3954,3959,3963,3968,3972,3977,3981,3985,3989,3993],{"__ignoreMap":578},[582,2600,2601],{"class":584,"line":585},[582,2602,795],{},[582,2604,2605],{"class":584,"line":591},[582,2606,800],{},[582,2608,2609],{"class":584,"line":597},[582,2610,2611],{},"using System.Threading.Channels;\n",[582,2613,2614],{"class":584,"line":603},[582,2615,805],{},[582,2617,2618],{"class":584,"line":609},[582,2619,815],{},[582,2621,2622],{"class":584,"line":615},[582,2623,820],{},[582,2625,2626],{"class":584,"line":621},[582,2627,825],{},[582,2629,2630],{"class":584,"line":627},[582,2631,2632],{},"using Qenex.QSuite.Variables.QVariables;\n",[582,2634,2635],{"class":584,"line":633},[582,2636,2637],{},"using Qenex.QSuite.Variables.QVariables.Values;\n",[582,2639,2640],{"class":584,"line":639},[582,2641,2642],{},"using Qenex.QSuite.Variables.VariableEvents;\n",[582,2644,2645],{"class":584,"line":645},[582,2646,831],{"emptyLinePlaceholder":830},[582,2648,2649],{"class":584,"line":651},[582,2650,2651],{},"namespace Qenex.QSuite.Examples.TempSensorProtocol;\n",[582,2653,2654],{"class":584,"line":657},[582,2655,831],{"emptyLinePlaceholder":830},[582,2657,2658],{"class":584,"line":725},[582,2659,845],{},[582,2661,2662],{"class":584,"line":863},[582,2663,2664],{},"\u002F\u002F\u002F Example protocol: decodes \"ch1=23.47\" text lines from the TempSensorDriver into variables\n",[582,2666,2667],{"class":584,"line":869},[582,2668,2669],{},"\u002F\u002F\u002F addressed by the \"channel\" commParam, and encodes operator writes back into the same lines.\n",[582,2671,2672],{"class":584,"line":875},[582,2673,2674],{},"\u002F\u002F\u002F The generic parameter (string) is what pairs the protocol with a text-line driver.\n",[582,2676,2677],{"class":584,"line":881},[582,2678,872],{},[582,2680,2681],{"class":584,"line":887},[582,2682,2683],{},"public class TempSensorProtocol : ProtocolBase\u003Cstring>, ITransportProtocol\u003Cstring>, IProtocolVariableWriteProtocol\n",[582,2685,2686],{"class":584,"line":893},[582,2687,884],{},[582,2689,2690],{"class":584,"line":899},[582,2691,2692],{},"    \u002F\u002F Received lines waiting for processing; the channel keeps the consumer loop cancelable.\n",[582,2694,2695],{"class":584,"line":904},[582,2696,2697],{},"    private Channel\u003Cstring>? receivedLines;\n",[582,2699,2700],{"class":584,"line":910},[582,2701,2702],{},"    private volatile Func\u003Cstring, CancellationToken, Task>? transmitter;\n",[582,2704,2705],{"class":584,"line":916},[582,2706,831],{"emptyLinePlaceholder":830},[582,2708,2709],{"class":584,"line":922},[582,2710,936],{},[582,2712,2713],{"class":584,"line":927},[582,2714,942],{},[582,2716,2717],{"class":584,"line":933},[582,2718,948],{},[582,2720,2721],{"class":584,"line":939},[582,2722,831],{"emptyLinePlaceholder":830},[582,2724,2725],{"class":584,"line":945},[582,2726,2727],{},"    public TempSensorProtocol()\n",[582,2729,2730],{"class":584,"line":951},[582,2731,965],{},[582,2733,2734],{"class":584,"line":956},[582,2735,971],{},[582,2737,2738],{"class":584,"line":962},[582,2739,977],{},[582,2741,2742],{"class":584,"line":968},[582,2743,2744],{},"            Name = \"TempSensorProtocol\",\n",[582,2746,2747],{"class":584,"line":974},[582,2748,2749],{},"            Label = \"Temperature Sensor Protocol (example)\",\n",[582,2751,2752],{"class":584,"line":980},[582,2753,2754],{},"            Description = \"Example protocol decoding text lines of the simulated temperature sensor.\",\n",[582,2756,2757],{"class":584,"line":986},[582,2758,1001],{},[582,2760,2761],{"class":584,"line":992},[582,2762,1007],{},[582,2764,2765],{"class":584,"line":998},[582,2766,1013],{},[582,2768,2769],{"class":584,"line":1004},[582,2770,1019],{},[582,2772,2773],{"class":584,"line":1010},[582,2774,1025],{},[582,2776,2777],{"class":584,"line":1016},[582,2778,1031],{},[582,2780,2781],{"class":584,"line":1022},[582,2782,831],{"emptyLinePlaceholder":830},[582,2784,2785],{"class":584,"line":1028},[582,2786,2787],{},"    \u002F\u002F The transport type (string) alone cannot tell text-line drivers apart, and this\n",[582,2789,2790],{"class":584,"line":1034},[582,2791,2792],{},"    \u002F\u002F protocol only understands the \"chN=value\" sensor lines, so it narrows itself to its driver.\n",[582,2794,2795],{"class":584,"line":1039},[582,2796,2797],{},"    public override IReadOnlyList\u003Cstring> CompatibleDrivers => [\"TempSensorDriver\"];\n",[582,2799,2800],{"class":584,"line":1045},[582,2801,831],{"emptyLinePlaceholder":830},[582,2803,2804],{"class":584,"line":1050},[582,2805,1042],{},[582,2807,2808],{"class":584,"line":1056},[582,2809,831],{"emptyLinePlaceholder":830},[582,2811,2812],{"class":584,"line":1061},[582,2813,2814],{},"    \u002F\u002F The protocol has no protocol-level settings; everything is per-variable commParams.\n",[582,2816,2817],{"class":584,"line":1067},[582,2818,2819],{},"    \u002F\u002F The shared parser still runs so that a key typed into the protocol settings is reported\n",[582,2821,2822],{"class":584,"line":1072},[582,2823,2824],{},"    \u002F\u002F as unknown instead of being ignored silently.\n",[582,2826,2827],{"class":584,"line":1078},[582,2828,1075],{},[582,2830,2831],{"class":584,"line":1083},[582,2832,965],{},[582,2834,2835],{"class":584,"line":1089},[582,2836,2837],{},"        SettingsParser.Parse(RawSettings, [], Logger, \"Temperature sensor protocol\");\n",[582,2839,2840],{"class":584,"line":1095},[582,2841,1031],{},[582,2843,2844],{"class":584,"line":1101},[582,2845,831],{"emptyLinePlaceholder":830},[582,2847,2848],{"class":584,"line":1107},[582,2849,2850],{},"    public override string CreateDefaultCommParam(IVariableBase variable, IEnumerable\u003CIVarEvent> variableEvents)\n",[582,2852,2853],{"class":584,"line":1112},[582,2854,965],{},[582,2856,2857],{"class":584,"line":1118},[582,2858,2859],{},"        return \"direction=\\\"read\\\";channel=\\\"ch1\\\"\";\n",[582,2861,2862],{"class":584,"line":1123},[582,2863,1031],{},[582,2865,2866],{"class":584,"line":1129},[582,2867,831],{"emptyLinePlaceholder":830},[582,2869,2870],{"class":584,"line":1135},[582,2871,1252],{},[582,2873,2874],{"class":584,"line":1141},[582,2875,831],{"emptyLinePlaceholder":830},[582,2877,2878],{"class":584,"line":1147},[582,2879,2880],{},"    #region Protocol variables\n",[582,2882,2883],{"class":584,"line":1153},[582,2884,831],{"emptyLinePlaceholder":830},[582,2886,2887],{"class":584,"line":1158},[582,2888,2889],{},"    public override IProtocolVariable? CreateProtocolVariable(IVariableBase variable, string commParams, bool isCommunicated)\n",[582,2891,2892],{"class":584,"line":1164},[582,2893,965],{},[582,2895,2896],{"class":584,"line":1169},[582,2897,2115],{},[582,2899,2900],{"class":584,"line":1175},[582,2901,977],{},[582,2903,2904],{"class":584,"line":1180},[582,2905,2906],{},"            return new ProtocolVariable\n",[582,2908,2909],{"class":584,"line":1186},[582,2910,1132],{},[582,2912,2913],{"class":584,"line":1191},[582,2914,2915],{},"                Variable = variable,\n",[582,2917,2918],{"class":584,"line":1197},[582,2919,2920],{},"                IsCommunicated = isCommunicated,\n",[582,2922,2923],{"class":584,"line":1202},[582,2924,2925],{},"                ProtocolVariableSpecification = TempSensorVariableSpecification.Create(commParams)\n",[582,2927,2928],{"class":584,"line":1208},[582,2929,2930],{},"            };\n",[582,2932,2933],{"class":584,"line":1213},[582,2934,1172],{},[582,2936,2937],{"class":584,"line":1218},[582,2938,2283],{},[582,2940,2941],{"class":584,"line":1223},[582,2942,977],{},[582,2944,2945],{"class":584,"line":1229},[582,2946,2947],{},"            Logger?.Log(LogLevel.Warn, $\"Protocol variable specification for variable {variable.Name} could not be created ({e.Message}).\");\n",[582,2949,2950],{"class":584,"line":1234},[582,2951,2952],{},"            return null;\n",[582,2954,2955],{"class":584,"line":1239},[582,2956,1172],{},[582,2958,2959],{"class":584,"line":1244},[582,2960,1031],{},[582,2962,2963],{"class":584,"line":1249},[582,2964,831],{"emptyLinePlaceholder":830},[582,2966,2967],{"class":584,"line":1255},[582,2968,2969],{},"    public override IProtocolVariable? CreateProtocolVariable(IVariableBase variable, IVarEvent variableEvent, string id)\n",[582,2971,2972],{"class":584,"line":1260},[582,2973,965],{},[582,2975,2976],{"class":584,"line":1266},[582,2977,2978],{},"        return CreateProtocolVariable(variable, $\"direction=\\\"read\\\";channel=\\\"{id}\\\"\", true);\n",[582,2980,2981],{"class":584,"line":1271},[582,2982,1031],{},[582,2984,2985],{"class":584,"line":1277},[582,2986,831],{"emptyLinePlaceholder":830},[582,2988,2989],{"class":584,"line":1282},[582,2990,2991],{},"    public override IProtocolVariable? CreateProtocolVariable(IVariableBase variable, IEnumerable\u003CIVarEvent> variableEvents,\n",[582,2993,2994],{"class":584,"line":1288},[582,2995,2996],{},"        string commParams, bool isCommunicated)\n",[582,2998,2999],{"class":584,"line":1293},[582,3000,965],{},[582,3002,3003],{"class":584,"line":1299},[582,3004,3005],{},"        \u002F\u002F The data arrive whenever the device sends them, so variable events are not used.\n",[582,3007,3008],{"class":584,"line":1305},[582,3009,3010],{},"        return CreateProtocolVariable(variable, commParams, isCommunicated);\n",[582,3012,3013],{"class":584,"line":1310},[582,3014,1031],{},[582,3016,3017],{"class":584,"line":1315},[582,3018,831],{"emptyLinePlaceholder":830},[582,3020,3021],{"class":584,"line":1321},[582,3022,1252],{},[582,3024,3025],{"class":584,"line":1326},[582,3026,831],{"emptyLinePlaceholder":830},[582,3028,3029],{"class":584,"line":1331},[582,3030,3031],{},"    #region Protocol control\n",[582,3033,3034],{"class":584,"line":1336},[582,3035,831],{"emptyLinePlaceholder":830},[582,3037,3038],{"class":584,"line":1341},[582,3039,3040],{},"    public override Task StartAsync(CancellationToken ct = default)\n",[582,3042,3043],{"class":584,"line":1347},[582,3044,965],{},[582,3046,3047],{"class":584,"line":1353},[582,3048,1285],{},[582,3050,3051],{"class":584,"line":1358},[582,3052,977],{},[582,3054,3055],{"class":584,"line":1364},[582,3056,1296],{},[582,3058,3059],{"class":584,"line":1370},[582,3060,3061],{},"            return Task.CompletedTask;\n",[582,3063,3064],{"class":584,"line":1375},[582,3065,1172],{},[582,3067,3068],{"class":584,"line":1381},[582,3069,831],{"emptyLinePlaceholder":830},[582,3071,3072],{"class":584,"line":1386},[582,3073,1318],{},[582,3075,3076],{"class":584,"line":1391},[582,3077,977],{},[582,3079,3080],{"class":584,"line":1397},[582,3081,3061],{},[582,3083,3084],{"class":584,"line":1403},[582,3085,1172],{},[582,3087,3088],{"class":584,"line":1409},[582,3089,831],{"emptyLinePlaceholder":830},[582,3091,3092],{"class":584,"line":1414},[582,3093,1344],{},[582,3095,3096],{"class":584,"line":1420},[582,3097,1350],{},[582,3099,3100],{"class":584,"line":1425},[582,3101,3102],{},"        receivedLines = Channel.CreateUnbounded\u003Cstring>();\n",[582,3104,3105],{"class":584,"line":1430},[582,3106,831],{"emptyLinePlaceholder":830},[582,3108,3109],{"class":584,"line":1436},[582,3110,1433],{},[582,3112,3113],{"class":584,"line":1442},[582,3114,1439],{},[582,3116,3117],{"class":584,"line":1448},[582,3118,1445],{},[582,3120,3121],{"class":584,"line":1454},[582,3122,1451],{},[582,3124,3125],{"class":584,"line":1460},[582,3126,1457],{},[582,3128,3129],{"class":584,"line":1466},[582,3130,1463],{},[582,3132,3133],{"class":584,"line":1472},[582,3134,1469],{},[582,3136,3137],{"class":584,"line":1477},[582,3138,1800],{},[582,3140,3141],{"class":584,"line":1482},[582,3142,1031],{},[582,3144,3145],{"class":584,"line":1488},[582,3146,831],{"emptyLinePlaceholder":830},[582,3148,3149],{"class":584,"line":1493},[582,3150,1485],{},[582,3152,3153],{"class":584,"line":1499},[582,3154,965],{},[582,3156,3157],{"class":584,"line":1505},[582,3158,1496],{},[582,3160,3161],{"class":584,"line":1510},[582,3162,1502],{},[582,3164,3165],{"class":584,"line":1516},[582,3166,3167],{},"        receivedLines?.Writer.TryComplete();\n",[582,3169,3170],{"class":584,"line":1521},[582,3171,831],{"emptyLinePlaceholder":830},[582,3173,3174],{"class":584,"line":1527},[582,3175,1513],{},[582,3177,3178],{"class":584,"line":1532},[582,3179,977],{},[582,3181,3182],{"class":584,"line":1537},[582,3183,1524],{},[582,3185,3186],{"class":584,"line":1543},[582,3187,1172],{},[582,3189,3190],{"class":584,"line":1548},[582,3191,831],{"emptyLinePlaceholder":830},[582,3193,3194],{"class":584,"line":1554},[582,3195,1540],{},[582,3197,3198],{"class":584,"line":1559},[582,3199,977],{},[582,3201,3202],{"class":584,"line":1565},[582,3203,1551],{},[582,3205,3206],{"class":584,"line":1570},[582,3207,1132],{},[582,3209,3210],{"class":584,"line":1576},[582,3211,1562],{},[582,3213,3214],{"class":584,"line":1581},[582,3215,1144],{},[582,3217,3218],{"class":584,"line":1586},[582,3219,1573],{},[582,3221,3222],{"class":584,"line":1592},[582,3223,1132],{},[582,3225,3226],{"class":584,"line":1597},[582,3227,1144],{},[582,3229,3230],{"class":584,"line":1603},[582,3231,1589],{},[582,3233,3234],{"class":584,"line":1608},[582,3235,1132],{},[582,3237,3238],{"class":584,"line":1613},[582,3239,3240],{},"                Logger?.Log(LogLevel.Warn, \"Temp sensor protocol did not stop before timeout.\");\n",[582,3242,3243],{"class":584,"line":1618},[582,3244,1144],{},[582,3246,3247],{"class":584,"line":1623},[582,3248,1172],{},[582,3250,3251],{"class":584,"line":1628},[582,3252,831],{"emptyLinePlaceholder":830},[582,3254,3255],{"class":584,"line":1634},[582,3256,1653],{},[582,3258,3259],{"class":584,"line":1639},[582,3260,1659],{},[582,3262,3263],{"class":584,"line":1644},[582,3264,1665],{},[582,3266,3267],{"class":584,"line":1650},[582,3268,1671],{},[582,3270,3271],{"class":584,"line":1656},[582,3272,1031],{},[582,3274,3275],{"class":584,"line":1662},[582,3276,831],{"emptyLinePlaceholder":830},[582,3278,3279],{"class":584,"line":1668},[582,3280,1687],{},[582,3282,3283],{"class":584,"line":1674},[582,3284,965],{},[582,3286,3287],{"class":584,"line":1679},[582,3288,1653],{},[582,3290,3291],{"class":584,"line":1684},[582,3292,1031],{},[582,3294,3295],{"class":584,"line":1690},[582,3296,831],{"emptyLinePlaceholder":830},[582,3298,3299],{"class":584,"line":1695},[582,3300,1252],{},[582,3302,3303],{"class":584,"line":1700},[582,3304,831],{"emptyLinePlaceholder":830},[582,3306,3307],{"class":584,"line":1705},[582,3308,3309],{},"    #region Received data\n",[582,3311,3312],{"class":584,"line":1710},[582,3313,831],{"emptyLinePlaceholder":830},[582,3315,3316],{"class":584,"line":1715},[582,3317,3318],{},"    public override Task AddReceivedDataToQueueAsync(IEnumerable\u003Cstring> data, CancellationToken ct = default)\n",[582,3320,3321],{"class":584,"line":1720},[582,3322,965],{},[582,3324,3325],{"class":584,"line":1726},[582,3326,3327],{},"        foreach (var line in data)\n",[582,3329,3330],{"class":584,"line":1731},[582,3331,977],{},[582,3333,3334],{"class":584,"line":1737},[582,3335,3336],{},"            receivedLines?.Writer.TryWrite(line);\n",[582,3338,3339],{"class":584,"line":1743},[582,3340,1172],{},[582,3342,3343],{"class":584,"line":1748},[582,3344,831],{"emptyLinePlaceholder":830},[582,3346,3347],{"class":584,"line":1754},[582,3348,1800],{},[582,3350,3351],{"class":584,"line":1759},[582,3352,1031],{},[582,3354,3355],{"class":584,"line":1765},[582,3356,831],{"emptyLinePlaceholder":830},[582,3358,3359],{"class":584,"line":1770},[582,3360,2104],{},[582,3362,3363],{"class":584,"line":1775},[582,3364,965],{},[582,3366,3367],{"class":584,"line":1780},[582,3368,2115],{},[582,3370,3371],{"class":584,"line":1786},[582,3372,977],{},[582,3374,3375],{"class":584,"line":1791},[582,3376,3377],{},"            await foreach (var line in receivedLines!.Reader.ReadAllAsync(ct))\n",[582,3379,3380],{"class":584,"line":1797},[582,3381,1132],{},[582,3383,3384],{"class":584,"line":1803},[582,3385,3386],{},"                if (exitRequested)\n",[582,3388,3389],{"class":584,"line":1808},[582,3390,2149],{},[582,3392,3393],{"class":584,"line":1813},[582,3394,3395],{},"                    break;\n",[582,3397,3398],{"class":584,"line":1819},[582,3399,2191],{},[582,3401,3402],{"class":584,"line":1825},[582,3403,831],{"emptyLinePlaceholder":830},[582,3405,3406],{"class":584,"line":1830},[582,3407,3408],{},"                await ProcessReceivedDataAsync([line], ct);\n",[582,3410,3411],{"class":584,"line":1836},[582,3412,1144],{},[582,3414,3415],{"class":584,"line":1842},[582,3416,1172],{},[582,3418,3419],{"class":584,"line":1848},[582,3420,2261],{},[582,3422,3423],{"class":584,"line":1853},[582,3424,977],{},[582,3426,3427],{"class":584,"line":1858},[582,3428,2272],{},[582,3430,3431],{"class":584,"line":1864},[582,3432,1172],{},[582,3434,3435],{"class":584,"line":1869},[582,3436,2283],{},[582,3438,3439],{"class":584,"line":1875},[582,3440,977],{},[582,3442,3443],{"class":584,"line":1880},[582,3444,3445],{},"            Logger?.Log(LogLevel.Error, $\"Temp sensor protocol loop failed: {e.Message}\");\n",[582,3447,3448],{"class":584,"line":1886},[582,3449,2300],{},[582,3451,3452],{"class":584,"line":1891},[582,3453,1172],{},[582,3455,3456],{"class":584,"line":1897},[582,3457,1031],{},[582,3459,3460],{"class":584,"line":1902},[582,3461,831],{"emptyLinePlaceholder":830},[582,3463,3464],{"class":584,"line":1907},[582,3465,3466],{},"    protected override void ProcessReceivedData(IEnumerable\u003Cstring> data)\n",[582,3468,3469],{"class":584,"line":1913},[582,3470,965],{},[582,3472,3473],{"class":584,"line":1918},[582,3474,3475],{},"        foreach (var protocolVariable in Decode(data))\n",[582,3477,3478],{"class":584,"line":1923},[582,3479,977],{},[582,3481,3482],{"class":584,"line":1928},[582,3483,3484],{},"            protocolVariable.NotifyValueChanged();\n",[582,3486,3487],{"class":584,"line":1933},[582,3488,1172],{},[582,3490,3491],{"class":584,"line":1939},[582,3492,1031],{},[582,3494,3495],{"class":584,"line":1945},[582,3496,831],{"emptyLinePlaceholder":830},[582,3498,3499],{"class":584,"line":1951},[582,3500,3501],{},"    protected override async Task ProcessReceivedDataAsync(IEnumerable\u003Cstring> data, CancellationToken ct = default)\n",[582,3503,3504],{"class":584,"line":1957},[582,3505,965],{},[582,3507,3508],{"class":584,"line":1962},[582,3509,3510],{},"        var notifyTasks = Decode(data).Select(protocolVariable => protocolVariable.NotifyValueChangedAsync());\n",[582,3512,3513],{"class":584,"line":1967},[582,3514,3515],{},"        await Task.WhenAll(notifyTasks);\n",[582,3517,3518],{"class":584,"line":1973},[582,3519,1031],{},[582,3521,3522],{"class":584,"line":1979},[582,3523,831],{"emptyLinePlaceholder":830},[582,3525,3526],{"class":584,"line":1984},[582,3527,1252],{},[582,3529,3530],{"class":584,"line":1989},[582,3531,831],{"emptyLinePlaceholder":830},[582,3533,3534],{"class":584,"line":1995},[582,3535,3536],{},"    #region Encoding and decoding\n",[582,3538,3539],{"class":584,"line":2000},[582,3540,831],{"emptyLinePlaceholder":830},[582,3542,3543],{"class":584,"line":2006},[582,3544,3545],{},"    protected override IEnumerable\u003CIProtocolVariable> Decode(IEnumerable\u003Cstring> data)\n",[582,3547,3548],{"class":584,"line":2011},[582,3549,965],{},[582,3551,3552],{"class":584,"line":2017},[582,3553,3554],{},"        var updated = new List\u003CIProtocolVariable>();\n",[582,3556,3557],{"class":584,"line":2022},[582,3558,831],{"emptyLinePlaceholder":830},[582,3560,3561],{"class":584,"line":2027},[582,3562,3327],{},[582,3564,3565],{"class":584,"line":2032},[582,3566,977],{},[582,3568,3569],{"class":584,"line":2037},[582,3570,3571],{},"            var pair = line.Split('=', 2, StringSplitOptions.TrimEntries);\n",[582,3573,3574],{"class":584,"line":2043},[582,3575,3576],{},"            if (pair.Length != 2\n",[582,3578,3579],{"class":584,"line":2048},[582,3580,3581],{},"                || !double.TryParse(pair[1], NumberStyles.Float, CultureInfo.InvariantCulture, out var value))\n",[582,3583,3584],{"class":584,"line":2053},[582,3585,1132],{},[582,3587,3588],{"class":584,"line":2058},[582,3589,3590],{},"                Logger?.Log(LogLevel.Warn, $\"Temp sensor protocol: cannot decode line '{line}'.\");\n",[582,3592,3593],{"class":584,"line":2064},[582,3594,1894],{},[582,3596,3597],{"class":584,"line":2070},[582,3598,1144],{},[582,3600,3601],{"class":584,"line":2075},[582,3602,831],{"emptyLinePlaceholder":830},[582,3604,3605],{"class":584,"line":2080},[582,3606,3607],{},"            foreach (var protocolVariable in Variables)\n",[582,3609,3610],{"class":584,"line":2085},[582,3611,1132],{},[582,3613,3614],{"class":584,"line":2090},[582,3615,3616],{},"                if (!protocolVariable.IsCommunicated\n",[582,3618,3619],{"class":584,"line":2096},[582,3620,3621],{},"                    || protocolVariable.ProtocolVariableSpecification is not TempSensorVariableSpecification spec\n",[582,3623,3624],{"class":584,"line":2101},[582,3625,3626],{},"                    || spec.Direction != CommDirection.Read\n",[582,3628,3629],{"class":584,"line":2107},[582,3630,3631],{},"                    || !spec.Channel.Equals(pair[0], StringComparison.OrdinalIgnoreCase)\n",[582,3633,3634],{"class":584,"line":2112},[582,3635,3636],{},"                    || protocolVariable.Variable is not ScalarVariable scalarVariable)\n",[582,3638,3639],{"class":584,"line":2118},[582,3640,2149],{},[582,3642,3643],{"class":584,"line":2123},[582,3644,3645],{},"                    continue;\n",[582,3647,3648],{"class":584,"line":2129},[582,3649,2191],{},[582,3651,3652],{"class":584,"line":2134},[582,3653,831],{"emptyLinePlaceholder":830},[582,3655,3656],{"class":584,"line":2140},[582,3657,3658],{},"                if (!TrySetValue(scalarVariable, value))\n",[582,3660,3661],{"class":584,"line":2146},[582,3662,2149],{},[582,3664,3665],{"class":584,"line":2152},[582,3666,3667],{},"                    Logger?.Log(LogLevel.Warn, $\"Temp sensor protocol: variable '{scalarVariable.Name}' has an unsupported value type.\");\n",[582,3669,3670],{"class":584,"line":2158},[582,3671,3645],{},[582,3673,3674],{"class":584,"line":2164},[582,3675,2191],{},[582,3677,3678],{"class":584,"line":2170},[582,3679,831],{"emptyLinePlaceholder":830},[582,3681,3682],{"class":584,"line":2176},[582,3683,3684],{},"                scalarVariable.Timestamp = DateTime.UtcNow;\n",[582,3686,3687],{"class":584,"line":2182},[582,3688,3689],{},"                updated.Add(protocolVariable);\n",[582,3691,3692],{"class":584,"line":2188},[582,3693,1144],{},[582,3695,3696],{"class":584,"line":2194},[582,3697,1172],{},[582,3699,3700],{"class":584,"line":2199},[582,3701,831],{"emptyLinePlaceholder":830},[582,3703,3704],{"class":584,"line":2205},[582,3705,3706],{},"        return updated;\n",[582,3708,3709],{"class":584,"line":2210},[582,3710,1031],{},[582,3712,3713],{"class":584,"line":2216},[582,3714,831],{"emptyLinePlaceholder":830},[582,3716,3717],{"class":584,"line":2221},[582,3718,3719],{},"    protected override IEnumerable\u003Cstring> Encode(IEnumerable\u003CIProtocolVariable> protocolVariables)\n",[582,3721,3722],{"class":584,"line":2227},[582,3723,965],{},[582,3725,3726],{"class":584,"line":2232},[582,3727,3728],{},"        foreach (var protocolVariable in protocolVariables)\n",[582,3730,3731],{"class":584,"line":2237},[582,3732,977],{},[582,3734,3735],{"class":584,"line":2242},[582,3736,3737],{},"            if (protocolVariable.ProtocolVariableSpecification is not TempSensorVariableSpecification spec\n",[582,3739,3740],{"class":584,"line":2248},[582,3741,3742],{},"                || protocolVariable.Variable is not ScalarVariable scalarVariable)\n",[582,3744,3745],{"class":584,"line":2253},[582,3746,1132],{},[582,3748,3749],{"class":584,"line":2258},[582,3750,1894],{},[582,3752,3753],{"class":584,"line":2264},[582,3754,1144],{},[582,3756,3757],{"class":584,"line":2269},[582,3758,831],{"emptyLinePlaceholder":830},[582,3760,3761],{"class":584,"line":2275},[582,3762,3763],{},"            var value = Convert.ToDouble(scalarVariable.Values.GetValue(), CultureInfo.InvariantCulture);\n",[582,3765,3766],{"class":584,"line":2280},[582,3767,3768],{},"            yield return $\"{spec.Channel}={value.ToString(\"F2\", CultureInfo.InvariantCulture)}\";\n",[582,3770,3771],{"class":584,"line":2286},[582,3772,1172],{},[582,3774,3775],{"class":584,"line":2291},[582,3776,1031],{},[582,3778,3779],{"class":584,"line":2297},[582,3780,831],{"emptyLinePlaceholder":830},[582,3782,3783],{"class":584,"line":2303},[582,3784,3785],{},"    \u002F\u002F Values\u003CT>.SetValue does not convert, so the received double is converted to the\n",[582,3787,3788],{"class":584,"line":2308},[582,3789,3790],{},"    \u002F\u002F variable's type here; the example supports the common numeric types.\n",[582,3792,3793],{"class":584,"line":2313},[582,3794,3795],{},"    private static bool TrySetValue(ScalarVariable scalarVariable, double value)\n",[582,3797,3798],{"class":584,"line":2318},[582,3799,965],{},[582,3801,3802],{"class":584,"line":2324},[582,3803,3804],{},"        switch (scalarVariable.Values)\n",[582,3806,3807],{"class":584,"line":2329},[582,3808,977],{},[582,3810,3811],{"class":584,"line":2335},[582,3812,3813],{},"            case Values\u003Cdouble> doubleValues:\n",[582,3815,3816],{"class":584,"line":2340},[582,3817,3818],{},"                doubleValues.Value = value;\n",[582,3820,3821],{"class":584,"line":2345},[582,3822,3823],{},"                return true;\n",[582,3825,3826],{"class":584,"line":2350},[582,3827,3828],{},"            case Values\u003Cfloat> floatValues:\n",[582,3830,3831],{"class":584,"line":2356},[582,3832,3833],{},"                floatValues.Value = (float)value;\n",[582,3835,3836],{"class":584,"line":2361},[582,3837,3823],{},[582,3839,3840],{"class":584,"line":2367},[582,3841,3842],{},"            case Values\u003Cint> intValues:\n",[582,3844,3845],{"class":584,"line":2373},[582,3846,3847],{},"                intValues.Value = (int)Math.Round(value);\n",[582,3849,3850],{"class":584,"line":2379},[582,3851,3823],{},[582,3853,3854],{"class":584,"line":2385},[582,3855,3856],{},"            default:\n",[582,3858,3859],{"class":584,"line":2391},[582,3860,3861],{},"                return false;\n",[582,3863,3864],{"class":584,"line":2396},[582,3865,1172],{},[582,3867,3868],{"class":584,"line":2402},[582,3869,1031],{},[582,3871,3872],{"class":584,"line":2407},[582,3873,831],{"emptyLinePlaceholder":830},[582,3875,3876],{"class":584,"line":2412},[582,3877,1252],{},[582,3879,3880],{"class":584,"line":2417},[582,3881,831],{"emptyLinePlaceholder":830},[582,3883,3884],{"class":584,"line":2422},[582,3885,3886],{},"    #region Operator writes\n",[582,3888,3889],{"class":584,"line":2427},[582,3890,831],{"emptyLinePlaceholder":830},[582,3892,3893],{"class":584,"line":2433},[582,3894,3895],{},"    public void SetTransmitter(Func\u003Cstring, CancellationToken, Task>? byteTransmitter)\n",[582,3897,3898],{"class":584,"line":2439},[582,3899,965],{},[582,3901,3902],{"class":584,"line":2444},[582,3903,3904],{},"        transmitter = byteTransmitter;\n",[582,3906,3907],{"class":584,"line":2450},[582,3908,1031],{},[582,3910,3911],{"class":584,"line":2456},[582,3912,831],{"emptyLinePlaceholder":830},[582,3914,3915],{"class":584,"line":2461},[582,3916,3917],{},"    public bool CanWriteVariable(IProtocolVariable protocolVariable)\n",[582,3919,3920],{"class":584,"line":2466},[582,3921,965],{},[582,3923,3924],{"class":584,"line":2472},[582,3925,3926],{},"        return Variables.Contains(protocolVariable)\n",[582,3928,3929],{"class":584,"line":2477},[582,3930,3931],{},"               && protocolVariable.ProtocolVariableSpecification is TempSensorVariableSpecification { Direction: CommDirection.Write };\n",[582,3933,3934],{"class":584,"line":2482},[582,3935,1031],{},[582,3937,3938],{"class":584,"line":2488},[582,3939,831],{"emptyLinePlaceholder":830},[582,3941,3942],{"class":584,"line":2493},[582,3943,3944],{},"    public async Task WriteVariableAsync(IProtocolVariable protocolVariable, CancellationToken ct = default)\n",[582,3946,3947],{"class":584,"line":2498},[582,3948,965],{},[582,3950,3951],{"class":584,"line":2504},[582,3952,3953],{},"        var currentTransmitter = transmitter\n",[582,3955,3956],{"class":584,"line":2509},[582,3957,3958],{},"            ?? throw new InvalidOperationException(\"Transport is not available (no transmitter injected).\");\n",[582,3960,3961],{"class":584,"line":2514},[582,3962,831],{"emptyLinePlaceholder":830},[582,3964,3965],{"class":584,"line":2519},[582,3966,3967],{},"        foreach (var line in Encode([protocolVariable]))\n",[582,3969,3970],{"class":584,"line":2525},[582,3971,977],{},[582,3973,3974],{"class":584,"line":2530},[582,3975,3976],{},"            await currentTransmitter(line, ct);\n",[582,3978,3979],{"class":584,"line":2536},[582,3980,1172],{},[582,3982,3983],{"class":584,"line":2541},[582,3984,1031],{},[582,3986,3987],{"class":584,"line":2546},[582,3988,831],{"emptyLinePlaceholder":830},[582,3990,3991],{"class":584,"line":2551},[582,3992,1252],{},[582,3994,3995],{"class":584,"line":2556},[582,3996,2564],{},[436,3998,4000],{"id":3999},"specifikace-proměnné-tempsensorvariablespecificationcs","Specifikace proměnné: TempSensorVariableSpecification.cs",[377,4002,4003,4004,4007,4008,4011,4012,4015],{},"Properties odpovídají klíčům commParams 1:1 (",[403,4005,4006],{},"channel",", ",[403,4009,4010],{},"direction","), takže\nzděděná serializace ",[403,4013,4014],{},"ToCommParam()"," funguje bez přepsání.",[573,4017,4019],{"className":786,"code":4018,"language":788,"meta":578,"style":578},"using Qenex.QSuite.Protocols.Protocol;\n\nnamespace Qenex.QSuite.Examples.TempSensorProtocol;\n\n\u002F\u002F\u002F \u003Csummary>\n\u002F\u002F\u002F Addressing of one variable: which device channel it reads (or writes) and in which direction.\n\u002F\u002F\u002F The properties match the commParam keys 1:1 (\"channel\", \"direction\"), so the inherited\n\u002F\u002F\u002F reflection-based ToCommParam() round-trips the specification without an override.\n\u002F\u002F\u002F \u003C\u002Fsummary>\npublic class TempSensorVariableSpecification : ProtVariableSpecification\n{\n    public TempSensorVariableSpecification()\n    {\n        Name = \"TempSensorVariableSpecification\";\n    }\n\n    \u002F\u002F\u002F \u003Csummary>Channel key as sent by the device, e.g. \"ch1\".\u003C\u002Fsummary>\n    public string Channel { get; set; } = string.Empty;\n\n    public CommDirection Direction { get; set; } = CommDirection.Read;\n\n    public static TempSensorVariableSpecification 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(\"channel\", out var channel) || string.IsNullOrWhiteSpace(channel))\n        {\n            throw new ArgumentException(\"Missing mandatory commParam 'channel'.\");\n        }\n\n        var direction = CommDirection.Read;\n        if (parameters.TryGetValue(\"direction\", out var directionText) && !string.IsNullOrWhiteSpace(directionText))\n        {\n            direction = Enum.Parse\u003CCommDirection>(directionText, ignoreCase: true);\n        }\n\n        return new TempSensorVariableSpecification\n        {\n            Channel = channel,\n            Direction = direction\n        };\n    }\n}\n",[403,4020,4021,4025,4029,4033,4037,4041,4046,4051,4056,4060,4065,4069,4074,4078,4083,4087,4091,4096,4101,4105,4110,4114,4119,4123,4128,4133,4138,4143,4148,4152,4157,4161,4166,4170,4174,4179,4184,4188,4193,4197,4201,4206,4210,4215,4220,4224,4228],{"__ignoreMap":578},[582,4022,4023],{"class":584,"line":585},[582,4024,820],{},[582,4026,4027],{"class":584,"line":591},[582,4028,831],{"emptyLinePlaceholder":830},[582,4030,4031],{"class":584,"line":597},[582,4032,2651],{},[582,4034,4035],{"class":584,"line":603},[582,4036,831],{"emptyLinePlaceholder":830},[582,4038,4039],{"class":584,"line":609},[582,4040,845],{},[582,4042,4043],{"class":584,"line":615},[582,4044,4045],{},"\u002F\u002F\u002F Addressing of one variable: which device channel it reads (or writes) and in which direction.\n",[582,4047,4048],{"class":584,"line":621},[582,4049,4050],{},"\u002F\u002F\u002F The properties match the commParam keys 1:1 (\"channel\", \"direction\"), so the inherited\n",[582,4052,4053],{"class":584,"line":627},[582,4054,4055],{},"\u002F\u002F\u002F reflection-based ToCommParam() round-trips the specification without an override.\n",[582,4057,4058],{"class":584,"line":633},[582,4059,872],{},[582,4061,4062],{"class":584,"line":639},[582,4063,4064],{},"public class TempSensorVariableSpecification : ProtVariableSpecification\n",[582,4066,4067],{"class":584,"line":645},[582,4068,884],{},[582,4070,4071],{"class":584,"line":651},[582,4072,4073],{},"    public TempSensorVariableSpecification()\n",[582,4075,4076],{"class":584,"line":657},[582,4077,965],{},[582,4079,4080],{"class":584,"line":725},[582,4081,4082],{},"        Name = \"TempSensorVariableSpecification\";\n",[582,4084,4085],{"class":584,"line":863},[582,4086,1031],{},[582,4088,4089],{"class":584,"line":869},[582,4090,831],{"emptyLinePlaceholder":830},[582,4092,4093],{"class":584,"line":875},[582,4094,4095],{},"    \u002F\u002F\u002F \u003Csummary>Channel key as sent by the device, e.g. \"ch1\".\u003C\u002Fsummary>\n",[582,4097,4098],{"class":584,"line":881},[582,4099,4100],{},"    public string Channel { get; set; } = string.Empty;\n",[582,4102,4103],{"class":584,"line":887},[582,4104,831],{"emptyLinePlaceholder":830},[582,4106,4107],{"class":584,"line":893},[582,4108,4109],{},"    public CommDirection Direction { get; set; } = CommDirection.Read;\n",[582,4111,4112],{"class":584,"line":899},[582,4113,831],{"emptyLinePlaceholder":830},[582,4115,4116],{"class":584,"line":904},[582,4117,4118],{},"    public static TempSensorVariableSpecification Create(string commParams)\n",[582,4120,4121],{"class":584,"line":910},[582,4122,965],{},[582,4124,4125],{"class":584,"line":916},[582,4126,4127],{},"        var parameters = commParams\n",[582,4129,4130],{"class":584,"line":922},[582,4131,4132],{},"            .Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)\n",[582,4134,4135],{"class":584,"line":927},[582,4136,4137],{},"            .Select(parameter => parameter.Split('=', 2))\n",[582,4139,4140],{"class":584,"line":933},[582,4141,4142],{},"            .Where(parts => parts.Length == 2)\n",[582,4144,4145],{"class":584,"line":939},[582,4146,4147],{},"            .ToDictionary(parts => parts[0].Trim(), parts => parts[1].Trim().Trim('\"'), StringComparer.OrdinalIgnoreCase);\n",[582,4149,4150],{"class":584,"line":945},[582,4151,831],{"emptyLinePlaceholder":830},[582,4153,4154],{"class":584,"line":951},[582,4155,4156],{},"        if (!parameters.TryGetValue(\"channel\", out var channel) || string.IsNullOrWhiteSpace(channel))\n",[582,4158,4159],{"class":584,"line":956},[582,4160,977],{},[582,4162,4163],{"class":584,"line":962},[582,4164,4165],{},"            throw new ArgumentException(\"Missing mandatory commParam 'channel'.\");\n",[582,4167,4168],{"class":584,"line":968},[582,4169,1172],{},[582,4171,4172],{"class":584,"line":974},[582,4173,831],{"emptyLinePlaceholder":830},[582,4175,4176],{"class":584,"line":980},[582,4177,4178],{},"        var direction = CommDirection.Read;\n",[582,4180,4181],{"class":584,"line":986},[582,4182,4183],{},"        if (parameters.TryGetValue(\"direction\", out var directionText) && !string.IsNullOrWhiteSpace(directionText))\n",[582,4185,4186],{"class":584,"line":992},[582,4187,977],{},[582,4189,4190],{"class":584,"line":998},[582,4191,4192],{},"            direction = Enum.Parse\u003CCommDirection>(directionText, ignoreCase: true);\n",[582,4194,4195],{"class":584,"line":1004},[582,4196,1172],{},[582,4198,4199],{"class":584,"line":1010},[582,4200,831],{"emptyLinePlaceholder":830},[582,4202,4203],{"class":584,"line":1016},[582,4204,4205],{},"        return new TempSensorVariableSpecification\n",[582,4207,4208],{"class":584,"line":1022},[582,4209,977],{},[582,4211,4212],{"class":584,"line":1028},[582,4213,4214],{},"            Channel = channel,\n",[582,4216,4217],{"class":584,"line":1034},[582,4218,4219],{},"            Direction = direction\n",[582,4221,4222],{"class":584,"line":1039},[582,4223,1025],{},[582,4225,4226],{"class":584,"line":1045},[582,4227,1031],{},[582,4229,4230],{"class":584,"line":1050},[582,4231,2564],{},[436,4233,4235],{"id":4234},"jak-spolu-členy-souvisí","Jak spolu členy souvisí",[4237,4238,4239,4255],"table",{},[4240,4241,4242],"thead",{},[4243,4244,4245,4249,4252],"tr",{},[4246,4247,4248],"th",{},"Krok",[4246,4250,4251],{},"Kdo",[4246,4253,4254],{},"Členy",[4256,4257,4258,4273,4291,4307,4329,4358],"tbody",{},[4243,4259,4260,4264,4267],{},[4261,4262,4263],"td",{},"Přidání driveru\u002Fprotokolu v UI",[4261,4265,4266],{},"plugin",[4261,4268,4269,4007,4271],{},[403,4270,750],{},[403,4272,2573],{},[4243,4274,4275,4278,4281],{},[4261,4276,4277],{},"Přiřazení proměnné",[4261,4279,4280],{},"protokol",[4261,4282,4283,4286,4287,4290],{},[403,4284,4285],{},"CreateProtocolVariable"," → ",[403,4288,4289],{},"Create"," specifikace",[4243,4292,4293,4296,4299],{},[4261,4294,4295],{},"Start runtime",[4261,4297,4298],{},"driver",[4261,4300,4301,4286,4304,4306],{},[403,4302,4303],{},"StartAsync",[403,4305,762],{}," → start protokolů",[4243,4308,4309,4312,4315],{},[4261,4310,4311],{},"Čtení dat",[4261,4313,4314],{},"driver → protokol",[4261,4316,4317,4318,4286,4321,4286,4324,4286,4326],{},"smyčka driveru → ",[403,4319,4320],{},"AddReceivedDataToQueueAsync",[403,4322,4323],{},"ProcessReceivedDataAsync",[403,4325,2581],{},[403,4327,4328],{},"NotifyValueChangedAsync",[4243,4330,4331,4334,4337],{},[4261,4332,4333],{},"Zápis z UI",[4261,4335,4336],{},"QInsight → driver → protokol → driver",[4261,4338,4339,4342,4343,4286,4346,4286,4349,4286,4352,4354,4355,4357],{},[403,4340,4341],{},"CanWriteVariable"," (nabídka Write Mode) → ",[403,4344,4345],{},"CanSendCommand",[403,4347,4348],{},"OnProtocolVariableCommandAsync",[403,4350,4351],{},"WriteVariableAsync",[403,4353,429],{}," → transmitter (",[403,4356,433],{},")",[4243,4359,4360,4363,4365],{},[4261,4361,4362],{},"Stop",[4261,4364,4298],{},[4261,4366,4367,4370],{},[403,4368,4369],{},"StopAsync"," (zrušení smyčky, stop protokolů, odpojení transmitteru)",[436,4372,4374],{"id":4373},"dále","Dále",[377,4376,4377,4378,4380,4381,552],{},"Zpět na ",[380,4379,217],{"href":218},"\na ",[380,4382,221],{"href":222},[4384,4385,4386],"style",{},"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":578,"searchDepth":597,"depth":597,"links":4388},[4389,4390,4391,4392,4393,4394,4395],{"id":438,"depth":591,"text":439},{"id":555,"depth":591,"text":556},{"id":739,"depth":591,"text":740},{"id":2567,"depth":591,"text":2568},{"id":3999,"depth":591,"text":4000},{"id":4234,"depth":591,"text":4235},{"id":4373,"depth":591,"text":4374},"Funkční dvojice vlastní driver + protokol — simulovaný vícekanálový teploměr se čtením i zápisem, bez hardwaru.","md",{},{"title":234,"description":4396},"rmUOoSZyE5288Atw6_oqWkbxGVbEaad_GA7l-OzWOG8",[4402,4403],{"title":229,"path":230,"stem":231,"navTitle":232,"children":-1},{"title":244,"path":245,"stem":246,"navTitle":247,"children":-1},1791554569742]