[{"data":1,"prerenderedAt":3122},["ShallowReactive",2],{"docs-nav-docs_en":3,"docs-page-\u002Fen\u002Fcustom-extensions\u002Fiss-example":291,"docs-surround-\u002Fen\u002Fcustom-extensions\u002Fiss-example":3119},[4],{"title":5,"path":6,"stem":7,"children":8},"En","\u002Fen","en",[9,13,41,58,76,103,157,171,190,211,238,253,274],{"title":10,"path":6,"stem":11,"navTitle":12},"QInsight Documentation","en\u002Findex","Overview",{"title":14,"navTitle":15,"path":16,"stem":17,"children":18,"page":40},"Getting Started",null,"\u002Fen\u002Fgetting-started","en\u002F01.getting-started",[19,23,27,31,36],{"title":20,"path":21,"stem":22,"navTitle":20},"What is QENEX Suite","\u002Fen\u002Fgetting-started\u002Fwhat-is-qenex-suite","en\u002F01.getting-started\u002F1.what-is-qenex-suite",{"title":24,"path":25,"stem":26,"navTitle":24},"What is QInsight","\u002Fen\u002Fgetting-started\u002Fwhat-is-qinsight","en\u002F01.getting-started\u002F2.what-is-qinsight",{"title":28,"path":29,"stem":30,"navTitle":28},"Installation","\u002Fen\u002Fgetting-started\u002Finstallation","en\u002F01.getting-started\u002F3.installation",{"title":32,"path":33,"stem":34,"navTitle":35},"The QInsight user interface","\u002Fen\u002Fgetting-started\u002Fthe-qinsight-interface","en\u002F01.getting-started\u002F4.the-qinsight-interface","User interface",{"title":37,"path":38,"stem":39,"navTitle":37},"Licensing","\u002Fen\u002Fgetting-started\u002Flicensing","en\u002F01.getting-started\u002F5.licensing",false,{"title":42,"navTitle":15,"path":43,"stem":44,"children":45,"page":40},"First Project","\u002Fen\u002Ffirst-project","en\u002F02.first-project",[46,50,54],{"title":47,"path":48,"stem":49,"navTitle":47},"Create a project","\u002Fen\u002Ffirst-project\u002Fcreate-a-project","en\u002F02.first-project\u002F1.create-a-project",{"title":51,"path":52,"stem":53,"navTitle":51},"Project structure","\u002Fen\u002Ffirst-project\u002Fproject-structure","en\u002F02.first-project\u002F2.project-structure",{"title":55,"path":56,"stem":57,"navTitle":55},"Project password","\u002Fen\u002Ffirst-project\u002Fproject-password","en\u002F02.first-project\u002F3.project-password",{"title":59,"navTitle":15,"path":60,"stem":61,"children":62,"page":40},"Connecting a Device","\u002Fen\u002Fconnecting-a-device","en\u002F03.connecting-a-device",[63,67,71],{"title":64,"path":65,"stem":66,"navTitle":64},"Add a driver","\u002Fen\u002Fconnecting-a-device\u002Fadd-a-driver","en\u002F03.connecting-a-device\u002F1.add-a-driver",{"title":68,"path":69,"stem":70,"navTitle":68},"Add a protocol","\u002Fen\u002Fconnecting-a-device\u002Fadd-a-protocol","en\u002F03.connecting-a-device\u002F2.add-a-protocol",{"title":72,"path":73,"stem":74,"navTitle":75},"Link a driver and a protocol","\u002Fen\u002Fconnecting-a-device\u002Flink-driver-and-protocol","en\u002F03.connecting-a-device\u002F3.link-driver-and-protocol","Linking",{"title":77,"navTitle":15,"path":78,"stem":79,"children":80,"page":40},"Variables","\u002Fen\u002Fvariables","en\u002F04.variables",[81,85,90,94,99],{"title":82,"path":83,"stem":84,"navTitle":82},"What is a variable","\u002Fen\u002Fvariables\u002Fwhat-is-a-variable","en\u002F04.variables\u002F1.what-is-a-variable",{"title":86,"path":87,"stem":88,"navTitle":89},"Conversion and presentation","\u002Fen\u002Fvariables\u002Fconversion-and-presentation","en\u002F04.variables\u002F2.conversion-and-presentation","Conversion & presentation",{"title":91,"path":92,"stem":93,"navTitle":91},"Add a variable","\u002Fen\u002Fvariables\u002Fadd-a-variable","en\u002F04.variables\u002F3.add-a-variable",{"title":95,"path":96,"stem":97,"navTitle":98},"Matrix variable (array, curve, map)","\u002Fen\u002Fvariables\u002Fmatrix-variable","en\u002F04.variables\u002F4.matrix-variable","Matrix variable",{"title":100,"path":101,"stem":102,"navTitle":100},"Events","\u002Fen\u002Fvariables\u002Fevents","en\u002F04.variables\u002F5.events",{"title":104,"navTitle":15,"path":105,"stem":106,"children":107,"page":40},"Visualization","\u002Fen\u002Fvisualization","en\u002F05.visualization",[108,113,118,122,127,132,137,142,147,152],{"title":109,"path":110,"stem":111,"navTitle":112},"Create a workspace","\u002Fen\u002Fvisualization\u002Fcreate-a-workspace","en\u002F05.visualization\u002F1.create-a-workspace","Workspace",{"title":114,"path":115,"stem":116,"navTitle":117},"Writing values to the device","\u002Fen\u002Fvisualization\u002Fwrite-to-device","en\u002F05.visualization\u002F10.write-to-device","Writing to the device",{"title":119,"path":120,"stem":121,"navTitle":119},"Add a control","\u002Fen\u002Fvisualization\u002Fadd-a-control","en\u002F05.visualization\u002F2.add-a-control",{"title":123,"path":124,"stem":125,"navTitle":126},"Bind a control to a variable","\u002Fen\u002Fvisualization\u002Fbind-a-control","en\u002F05.visualization\u002F3.bind-a-control","Bind a control",{"title":128,"path":129,"stem":130,"navTitle":131},"Graph — signal time course","\u002Fen\u002Fvisualization\u002Fgraph-control","en\u002F05.visualization\u002F4.graph-control","Graph",{"title":133,"path":134,"stem":135,"navTitle":136},"XY Graph — signal versus signal","\u002Fen\u002Fvisualization\u002Fxy-graph-control","en\u002F05.visualization\u002F5.xy-graph-control","XY Graph",{"title":138,"path":139,"stem":140,"navTitle":141},"Gauge — instantaneous value indicator","\u002Fen\u002Fvisualization\u002Fgauge-control","en\u002F05.visualization\u002F6.gauge-control","Gauge",{"title":143,"path":144,"stem":145,"navTitle":146},"Single-Signal — a value with a caption","\u002Fen\u002Fvisualization\u002Fsignal-control","en\u002F05.visualization\u002F7.signal-control","Single-Signal",{"title":148,"path":149,"stem":150,"navTitle":151},"Watch Table — a table of variables","\u002Fen\u002Fvisualization\u002Fwatch-table-control","en\u002F05.visualization\u002F8.watch-table-control","Watch Table",{"title":153,"path":154,"stem":155,"navTitle":156},"Matrix control — maps and calibration","\u002Fen\u002Fvisualization\u002Fmatrix-control","en\u002F05.visualization\u002F9.matrix-control","Matrix control",{"title":158,"navTitle":15,"path":159,"stem":160,"children":161,"page":40},"Running and Monitoring","\u002Fen\u002Frunning","en\u002F06.running",[162,166],{"title":163,"path":164,"stem":165,"navTitle":163},"Start and stop","\u002Fen\u002Frunning\u002Fstart-and-stop","en\u002F06.running\u002F1.start-and-stop",{"title":167,"path":168,"stem":169,"navTitle":170},"Watching live data","\u002Fen\u002Frunning\u002Fwatching-live-data","en\u002F06.running\u002F2.watching-live-data","Live data",{"title":172,"navTitle":15,"path":173,"stem":174,"children":175,"page":40},"Logging and Replay","\u002Fen\u002Flogging-and-replay","en\u002F07.logging-and-replay",[176,180,185],{"title":177,"path":178,"stem":179,"navTitle":177},"Logging data","\u002Fen\u002Flogging-and-replay\u002Flogging-data","en\u002F07.logging-and-replay\u002F1.logging-data",{"title":181,"path":182,"stem":183,"navTitle":184},"Replaying a log","\u002Fen\u002Flogging-and-replay\u002Freplaying-a-log","en\u002F07.logging-and-replay\u002F2.replaying-a-log","Replay",{"title":186,"path":187,"stem":188,"navTitle":189},"Exporting data","\u002Fen\u002Flogging-and-replay\u002Fexporting-data","en\u002F07.logging-and-replay\u002F3.exporting-data","Export",{"title":191,"navTitle":15,"path":192,"stem":193,"children":194,"page":40},"Python Scripts","\u002Fen\u002Fpython-scripts","en\u002F08.python-scripts",[195,199,203,207],{"title":196,"path":197,"stem":198,"navTitle":12},"Scripts overview","\u002Fen\u002Fpython-scripts\u002Fscripts-overview","en\u002F08.python-scripts\u002F1.scripts-overview",{"title":200,"path":201,"stem":202,"navTitle":200},"Accessing variables","\u002Fen\u002Fpython-scripts\u002Faccessing-variables","en\u002F08.python-scripts\u002F2.accessing-variables",{"title":204,"path":205,"stem":206,"navTitle":204},"Example script","\u002Fen\u002Fpython-scripts\u002Fexample-script","en\u002F08.python-scripts\u002F3.example-script",{"title":208,"path":209,"stem":210,"navTitle":208},"CSV post-processing","\u002Fen\u002Fpython-scripts\u002Fcsv-post-processing","en\u002F08.python-scripts\u002F4.csv-post-processing",{"title":212,"navTitle":15,"path":213,"stem":214,"children":215,"page":40},"Custom extensions","\u002Fen\u002Fcustom-extensions","en\u002F09.custom-extensions",[216,220,224,228,233],{"title":217,"path":218,"stem":219,"navTitle":217},"Custom driver","\u002Fen\u002Fcustom-extensions\u002Fcustom-driver","en\u002F09.custom-extensions\u002F1.custom-driver",{"title":221,"path":222,"stem":223,"navTitle":221},"Custom protocol","\u002Fen\u002Fcustom-extensions\u002Fcustom-protocol","en\u002F09.custom-extensions\u002F2.custom-protocol",{"title":225,"path":226,"stem":227,"navTitle":225},"Custom Control","\u002Fen\u002Fcustom-extensions\u002Fcustom-control","en\u002F09.custom-extensions\u002F3.custom-control",{"title":229,"path":230,"stem":231,"navTitle":232},"Complete example — ISS position","\u002Fen\u002Fcustom-extensions\u002Fiss-example","en\u002F09.custom-extensions\u002F4.iss-example","Example ISS",{"title":234,"path":235,"stem":236,"navTitle":237},"Complete example — temperature sensor","\u002Fen\u002Fcustom-extensions\u002Ftemp-sensor-example","en\u002F09.custom-extensions\u002F5.temp-sensor-example","Example temp sensor",{"title":239,"navTitle":15,"path":240,"stem":241,"children":242,"page":40},"Firmware SDK","\u002Fen\u002Fdevice-sdk","en\u002F10.device-sdk",[243,248],{"title":244,"path":245,"stem":246,"navTitle":247},"Firmware SDK — introduction","\u002Fen\u002Fdevice-sdk\u002Fintroduction","en\u002F10.device-sdk\u002F1.introduction","Introduction",{"title":249,"path":250,"stem":251,"navTitle":252},"XCP SDK","\u002Fen\u002Fdevice-sdk\u002Fxcp","en\u002F10.device-sdk\u002F2.xcp","XCP",{"title":254,"navTitle":15,"path":255,"stem":256,"children":257,"page":40},"Reference","\u002Fen\u002Freference","en\u002F11.reference",[258,262,266,270],{"title":259,"path":260,"stem":261,"navTitle":259},"Drivers","\u002Fen\u002Freference\u002Fdrivers","en\u002F11.reference\u002F1.drivers",{"title":263,"path":264,"stem":265,"navTitle":263},"Protocols","\u002Fen\u002Freference\u002Fprotocols","en\u002F11.reference\u002F2.protocols",{"title":267,"path":268,"stem":269,"navTitle":267},"Controls","\u002Fen\u002Freference\u002Fcontrols","en\u002F11.reference\u002F3.controls",{"title":271,"path":272,"stem":273,"navTitle":271},"Troubleshooting","\u002Fen\u002Freference\u002Ftroubleshooting","en\u002F11.reference\u002F4.troubleshooting",{"title":275,"navTitle":15,"path":276,"stem":277,"children":278,"page":40},"Examples","\u002Fen\u002Fexamples","en\u002F12.examples",[279,283,287],{"title":280,"path":281,"stem":282,"navTitle":280},"Filtering a signal with a script","\u002Fen\u002Fexamples\u002Ffilter-script","en\u002F12.examples\u002F1.filter-script",{"title":284,"path":285,"stem":286,"navTitle":284},"Bit field and a script","\u002Fen\u002Fexamples\u002Fbit-field-script","en\u002F12.examples\u002F2.bit-field-script",{"title":288,"path":289,"stem":290,"navTitle":288},"Two ECUs at once","\u002Fen\u002Fexamples\u002Ftwo-ecus","en\u002F12.examples\u002F3.two-ecus",{"id":292,"title":229,"body":293,"description":3114,"extension":3115,"meta":3116,"navTitle":232,"navigation":851,"path":230,"seo":3117,"stem":231,"__hash__":3118},"docs_en\u002Fen\u002F09.custom-extensions\u002F4.iss-example.md",{"type":294,"value":295,"toc":3103},"minimark",[296,301,310,313,342,356,359,364,375,385,388,499,506,510,591,595,613,689,756,760,775,810,1830,1834,1853,1868,2760,2764,2775,2937,2941,3044,3048,3054,3078,3084,3088,3099],[297,298,300],"h1",{"id":299},"complete-example-iss-position-driver-protocol","Complete example: ISS position (driver + protocol)",[302,303,304,305,309],"p",{},"The previous chapters showed skeletons — here is the ",[306,307,308],"strong",{},"smallest genuinely\nworking driver + protocol pair"," you can write. And although the whole\nthing fits on a couple of screens, it works with real live data: once\na second it asks a public REST API for the current position of the\nInternational Space Station and feeds four variables — the latitude,\nlongitude, altitude and velocity of an object flying over your head at\nroughly 27,600 km\u002Fh.",[302,311,312],{},"The division of labour is exactly the one every QInsight driver and\nprotocol uses:",[314,315,316,332],"ul",{},[317,318,319,322,323,327,328,331],"li",{},[306,320,321],{},"IssDriver"," does one thing only: every period it downloads one JSON\nresponse with ",[324,325,326],"code",{},"HttpClient"," and hands it to its protocols ",[306,329,330],{},"as-is",".\nIt knows nothing about the content — from its point of view it is just\ntext.",[317,333,334,337,338,341],{},[306,335,336],{},"IssJsonProtocol"," takes the text, parses it as JSON and distributes\nthe values to variables. Each variable claims its field with the\n",[324,339,340],{},"path=\"latitude\""," commParam, so one response fills all the variables\nat once.",[302,343,344,351],{},[345,346],"img",{"alt":347,"className":348,"src":350},"The ISS Live project running — latitude\u002Flongitude graph, altitude gauge, current values",[349],"img-light","\u002Fimg\u002FQInsight_tut_9_4_iss_light.png",[345,352],{"alt":347,"className":353,"src":355},[354],"img-dark","\u002Fimg\u002FQInsight_tut_9_4_iss_dark.png",[302,357,358],{},"The data has a pleasant property on top: it is not random. The latitude\ndraws a sine wave with the period of one orbit (~92 minutes), the\nlongitude a sawtooth — the station keeps flying \"straight\" while the\nEarth rotates underneath, so every orbit starts a bit further west. Let\nthe graph run for a few minutes and the shapes start to show.",[360,361,363],"h2",{"id":362},"where-the-data-comes-from","Where the data comes from",[302,365,366,367,374],{},"The example uses the ",[368,369,373],"a",{"href":370,"rel":371},"https:\u002F\u002Fwheretheiss.at",[372],"nofollow","wheretheiss.at"," API — free,\nno registration, no key. A single GET request:",[376,377,382],"pre",{"className":378,"code":380,"language":381},[379],"language-text","https:\u002F\u002Fapi.wheretheiss.at\u002Fv1\u002Fsatellites\u002F25544\n","text",[324,383,380],{"__ignoreMap":384},"",[302,386,387],{},"returns one flat JSON object (25544 is the ISS catalog number):",[376,389,393],{"className":390,"code":391,"language":392,"meta":384,"style":384},"language-json shiki shiki-themes github-light github-dark","{\n  \"name\": \"iss\",\n  \"latitude\": -29.06,\n  \"longitude\": 20.04,\n  \"altitude\": 423.31,\n  \"velocity\": 27575.13,\n  \"visibility\": \"daylight\",\n  ...\n}\n","json",[324,394,395,404,421,434,447,460,473,486,493],{"__ignoreMap":384},[396,397,400],"span",{"class":398,"line":399},"line",1,[396,401,403],{"class":402},"sVt8B","{\n",[396,405,407,411,414,418],{"class":398,"line":406},2,[396,408,410],{"class":409},"sj4cs","  \"name\"",[396,412,413],{"class":402},": ",[396,415,417],{"class":416},"sZZnC","\"iss\"",[396,419,420],{"class":402},",\n",[396,422,424,427,429,432],{"class":398,"line":423},3,[396,425,426],{"class":409},"  \"latitude\"",[396,428,413],{"class":402},[396,430,431],{"class":409},"-29.06",[396,433,420],{"class":402},[396,435,437,440,442,445],{"class":398,"line":436},4,[396,438,439],{"class":409},"  \"longitude\"",[396,441,413],{"class":402},[396,443,444],{"class":409},"20.04",[396,446,420],{"class":402},[396,448,450,453,455,458],{"class":398,"line":449},5,[396,451,452],{"class":409},"  \"altitude\"",[396,454,413],{"class":402},[396,456,457],{"class":409},"423.31",[396,459,420],{"class":402},[396,461,463,466,468,471],{"class":398,"line":462},6,[396,464,465],{"class":409},"  \"velocity\"",[396,467,413],{"class":402},[396,469,470],{"class":409},"27575.13",[396,472,420],{"class":402},[396,474,476,479,481,484],{"class":398,"line":475},7,[396,477,478],{"class":409},"  \"visibility\"",[396,480,413],{"class":402},[396,482,483],{"class":416},"\"daylight\"",[396,485,420],{"class":402},[396,487,489],{"class":398,"line":488},8,[396,490,492],{"class":491},"s7hpK","  ...\n",[396,494,496],{"class":398,"line":495},9,[396,497,498],{"class":402},"}\n",[302,500,501,502,505],{},"The operator asks for at most ~1 request per second — which is why the\ndriver refuses a faster period (see ",[324,503,504],{},"SetConfiguration"," below). Running\nthe example naturally requires an internet connection.",[360,507,509],{"id":508},"trying-the-example","Trying the example",[511,512,513,526,549,585,588],"ol",{},[317,514,515,516,520,521,525],{},"Build both projects (below) and copy the DLLs into the ",[306,517,518],{},[324,519,259],{},"\nand ",[306,522,523],{},[324,524,263],{}," folders next to QInsight.exe.",[317,527,528,529,532,533,536,537,540,541,544,545,548],{},"In ",[306,530,531],{},"Project Configuration"," add the ",[306,534,535],{},"ISS Position (example)"," driver\n— ",[324,538,539],{},"periodMs=1000"," is pre-filled — and the ",[306,542,543],{},"ISS JSON Protocol\n(example)"," under it. The protocol names the driver in\n",[324,546,547],{},"CompatibleDrivers"," (see below), so under any other driver it is\ngreyed out — and vice versa.",[317,550,551,552],{},"Create four variables of type double and assign them to the protocol\nwith commParams:\n",[314,553,554,561,569,577],{},[317,555,556,413,559],{},[324,557,558],{},"Latitude",[324,560,340],{},[317,562,563,413,566],{},[324,564,565],{},"Longitude",[324,567,568],{},"path=\"longitude\"",[317,570,571,413,574],{},[324,572,573],{},"Altitude",[324,575,576],{},"path=\"altitude\"",[317,578,579,413,582],{},[324,580,581],{},"Velocity",[324,583,584],{},"path=\"velocity\"",[317,586,587],{},"For sensible graph and gauge ranges, use presentations: latitude\n−90…90°, longitude −180…180°, altitude 380…460 km, velocity\n27,400…27,700 km\u002Fh.",[317,589,590],{},"Start the runtime — the values come alive right after the first API\nresponse.",[360,592,594],{"id":593},"projects","Projects",[302,596,597,598,601,602,605,606,609,610,612],{},"Two ordinary class-library projects; they reference only the QSuite base\nprojects (",[324,599,600],{},"Driver",", resp. ",[324,603,604],{},"Protocol"," + ",[324,607,608],{},"VariableEvents","). ",[324,611,326],{},"\nis part of .NET, no NuGet package is needed.",[376,614,618],{"className":615,"code":616,"language":617,"meta":384,"style":384},"language-xml shiki shiki-themes github-light github-dark","\u003C!-- IssDriver.csproj -->\n\u003CProject Sdk=\"Microsoft.NET.Sdk\">\n    \u003CPropertyGroup>\n        \u003CTargetFramework>net10.0\u003C\u002FTargetFramework>\n        \u003CImplicitUsings>enable\u003C\u002FImplicitUsings>\n        \u003CNullable>enable\u003C\u002FNullable>\n        \u003CAssemblyName>Qenex.QSuite.Examples.IssDriver\u003C\u002FAssemblyName>\n        \u003CRootNamespace>Qenex.QSuite.Examples.IssDriver\u003C\u002FRootNamespace>\n    \u003C\u002FPropertyGroup>\n    \u003CItemGroup>\n      \u003CProjectReference Include=\"..\\..\\Drivers\\Driver\\Driver.csproj\" \u002F>\n    \u003C\u002FItemGroup>\n\u003C\u002FProject>\n","xml",[324,619,620,625,630,635,640,645,650,655,660,665,671,677,683],{"__ignoreMap":384},[396,621,622],{"class":398,"line":399},[396,623,624],{},"\u003C!-- IssDriver.csproj -->\n",[396,626,627],{"class":398,"line":406},[396,628,629],{},"\u003CProject Sdk=\"Microsoft.NET.Sdk\">\n",[396,631,632],{"class":398,"line":423},[396,633,634],{},"    \u003CPropertyGroup>\n",[396,636,637],{"class":398,"line":436},[396,638,639],{},"        \u003CTargetFramework>net10.0\u003C\u002FTargetFramework>\n",[396,641,642],{"class":398,"line":449},[396,643,644],{},"        \u003CImplicitUsings>enable\u003C\u002FImplicitUsings>\n",[396,646,647],{"class":398,"line":462},[396,648,649],{},"        \u003CNullable>enable\u003C\u002FNullable>\n",[396,651,652],{"class":398,"line":475},[396,653,654],{},"        \u003CAssemblyName>Qenex.QSuite.Examples.IssDriver\u003C\u002FAssemblyName>\n",[396,656,657],{"class":398,"line":488},[396,658,659],{},"        \u003CRootNamespace>Qenex.QSuite.Examples.IssDriver\u003C\u002FRootNamespace>\n",[396,661,662],{"class":398,"line":495},[396,663,664],{},"    \u003C\u002FPropertyGroup>\n",[396,666,668],{"class":398,"line":667},10,[396,669,670],{},"    \u003CItemGroup>\n",[396,672,674],{"class":398,"line":673},11,[396,675,676],{},"      \u003CProjectReference Include=\"..\\..\\Drivers\\Driver\\Driver.csproj\" \u002F>\n",[396,678,680],{"class":398,"line":679},12,[396,681,682],{},"    \u003C\u002FItemGroup>\n",[396,684,686],{"class":398,"line":685},13,[396,687,688],{},"\u003C\u002FProject>\n",[376,690,692],{"className":615,"code":691,"language":617,"meta":384,"style":384},"\u003C!-- IssJsonProtocol.csproj -->\n\u003CProject Sdk=\"Microsoft.NET.Sdk\">\n    \u003CPropertyGroup>\n        \u003CTargetFramework>net10.0\u003C\u002FTargetFramework>\n        \u003CImplicitUsings>enable\u003C\u002FImplicitUsings>\n        \u003CNullable>enable\u003C\u002FNullable>\n        \u003CAssemblyName>Qenex.QSuite.Examples.IssJsonProtocol\u003C\u002FAssemblyName>\n        \u003CRootNamespace>Qenex.QSuite.Examples.IssJsonProtocol\u003C\u002FRootNamespace>\n    \u003C\u002FPropertyGroup>\n    \u003CItemGroup>\n      \u003CProjectReference Include=\"..\\..\\Protocols\\Protocol\\Protocol.csproj\" \u002F>\n      \u003CProjectReference Include=\"..\\..\\Variables\\VariableEvents\\VariableEvents.csproj\" \u002F>\n    \u003C\u002FItemGroup>\n\u003C\u002FProject>\n",[324,693,694,699,703,707,711,715,719,724,729,733,737,742,747,751],{"__ignoreMap":384},[396,695,696],{"class":398,"line":399},[396,697,698],{},"\u003C!-- IssJsonProtocol.csproj -->\n",[396,700,701],{"class":398,"line":406},[396,702,629],{},[396,704,705],{"class":398,"line":423},[396,706,634],{},[396,708,709],{"class":398,"line":436},[396,710,639],{},[396,712,713],{"class":398,"line":449},[396,714,644],{},[396,716,717],{"class":398,"line":462},[396,718,649],{},[396,720,721],{"class":398,"line":475},[396,722,723],{},"        \u003CAssemblyName>Qenex.QSuite.Examples.IssJsonProtocol\u003C\u002FAssemblyName>\n",[396,725,726],{"class":398,"line":488},[396,727,728],{},"        \u003CRootNamespace>Qenex.QSuite.Examples.IssJsonProtocol\u003C\u002FRootNamespace>\n",[396,730,731],{"class":398,"line":495},[396,732,664],{},[396,734,735],{"class":398,"line":667},[396,736,670],{},[396,738,739],{"class":398,"line":673},[396,740,741],{},"      \u003CProjectReference Include=\"..\\..\\Protocols\\Protocol\\Protocol.csproj\" \u002F>\n",[396,743,744],{"class":398,"line":679},[396,745,746],{},"      \u003CProjectReference Include=\"..\\..\\Variables\\VariableEvents\\VariableEvents.csproj\" \u002F>\n",[396,748,749],{"class":398,"line":685},[396,750,682],{},[396,752,754],{"class":398,"line":753},14,[396,755,688],{},[360,757,759],{"id":758},"driver-issdrivercs","Driver: IssDriver.cs",[302,761,762,763,766,767,770,771,774],{},"The whole driver life cycle in one place: ",[324,764,765],{},"DefaultRawSettings"," as the\nsettings template, the state machine in ",[324,768,769],{},"StartAsync","\u002F",[324,772,773],{},"StopAsync"," and one\nloop calling the API every period. Three spots worth noticing:",[314,776,777,784,794],{},[317,778,779,783],{},[306,780,781],{},[324,782,504],{}," enforces a minimum period of 1000 ms — this is\na free public service and politeness is part of the design, not\na footnote.",[317,785,786,789,790,793],{},[306,787,788],{},"A network error does not kill the loop."," A dropped connection or\na slow response is just logged as a warning and the loop carries on —\nfor a device connected over the internet this is a normal operating\nstate, not a reason to go ",[324,791,792],{},"Faulted",".",[317,795,796,805,806,793],{},[306,797,798,770,801,804],{},[324,799,800],{},"Send",[324,802,803],{},"SendAsync"," are empty."," The API is read-only, so the driver\nhas nothing to transmit. The full write path is shown in the\n",[368,807,809],{"href":808},"\u002Fcustom-extensions\u002Ftemp-sensor-example","temperature sensor example",[376,811,815],{"className":812,"code":813,"language":814,"meta":384,"style":384},"language-csharp shiki shiki-themes github-light github-dark","using System.Reflection;\nusing Qenex.QSuite.Common.CoreComm;\nusing Qenex.QSuite.Drivers.Driver;\nusing Qenex.QSuite.LogSystems.LogSystem;\nusing Qenex.QSuite.Protocols.Protocol;\nusing Qenex.QSuite.Specifications.Specification;\n\nnamespace Qenex.QSuite.Examples.IssDriver;\n\n\u002F\u002F\u002F \u003Csummary>\n\u002F\u002F\u002F Example driver: reads live data from a public REST API — the current position of the\n\u002F\u002F\u002F International Space Station. Every period it performs one HTTP GET and hands the raw JSON\n\u002F\u002F\u002F response to its protocols; turning the JSON into variable values is the protocol's job\n\u002F\u002F\u002F (see the IssJsonProtocol example).\n\u002F\u002F\u002F This is about the smallest possible real-data driver: read-only, one fixed URL, one setting.\n\u002F\u002F\u002F \u003C\u002Fsummary>\npublic class IssDriver : DriverBase, ITransportSource\u003Cstring>\n{\n    \u002F\u002F Free, key-less API returning one flat JSON object with the current ISS position.\n    \u002F\u002F Be polite to the public service: do not poll faster than ~1 request per second.\n    private const string Url = \"https:\u002F\u002Fapi.wheretheiss.at\u002Fv1\u002Fsatellites\u002F25544\";\n\n    private int periodMs = 1000;\n\n    private HttpClient? httpClient;\n    private CancellationTokenSource? runCts;\n    private Task? runTask;\n\n    public IssDriver()\n    {\n        Specification = new SpecificationBase\n        {\n            Name = \"IssDriver\",\n            Label = \"ISS Position (example)\",\n            Description = \"Example driver polling the ISS position from a public REST API.\",\n            CreatedOn = new DateTime(2026, 7, 27),\n            Version = Assembly.GetExecutingAssembly().GetName().Version ?? new Version(1, 0, 0, 0),\n            Author = \"Qenex\",\n            Company = \"QENEX Ltd.\"\n        };\n    }\n\n    \u002F\u002F Shown pre-filled when the driver is added in Project Configuration.\n    public override string DefaultRawSettings => \"periodMs=1000\";\n\n    public override void SetConfiguration()\n    {\n        \u002F\u002F \"key=value;...\" — a missing or invalid key silently keeps the default.\n        foreach (var part in RawSettings.Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries))\n        {\n            var pair = part.Split('=', 2, StringSplitOptions.TrimEntries);\n            if (pair.Length == 2\n                && pair[0].Equals(\"periodMs\", StringComparison.OrdinalIgnoreCase)\n                && int.TryParse(pair[1], out var parsedPeriod))\n            {\n                \u002F\u002F The API asks for at most ~1 request per second, so slower is allowed, faster is not.\n                periodMs = Math.Max(parsedPeriod, 1000);\n            }\n        }\n    }\n\n    public override async Task StartAsync(CancellationToken ct = default)\n    {\n        if (!IsEnabled)\n        {\n            SetState(CommunicationState.Disabled);\n            return;\n        }\n\n        if (State == CommunicationState.Running || runTask is { IsCompleted: false })\n        {\n            return;\n        }\n\n        SetState(CommunicationState.Starting);\n\n        \u002F\u002F Short timeout so a slow or unreachable server cannot block the loop for long.\n        httpClient = new HttpClient { Timeout = TimeSpan.FromSeconds(5) };\n\n        foreach (var protocol in Protocols)\n        {\n            await protocol.StartAsync(ct);\n        }\n\n        runCts = CancellationTokenSource.CreateLinkedTokenSource(ct);\n        runTask = RunLoopAsync(runCts.Token);\n        SetState(CommunicationState.Running);\n    }\n\n    public override async Task StopAsync(CancellationToken ct = default)\n    {\n        SetState(CommunicationState.Stopping);\n\n        if (runCts != null)\n        {\n            await runCts.CancelAsync();\n        }\n\n        if (runTask != null)\n        {\n            try\n            {\n                await runTask.WaitAsync(TimeSpan.FromSeconds(5), ct);\n            }\n            catch (OperationCanceledException)\n            {\n            }\n            catch (TimeoutException)\n            {\n                Logger?.Log(LogLevel.Warn, \"ISS position: the polling loop did not stop before timeout.\");\n            }\n        }\n\n        foreach (var protocol in Protocols)\n        {\n            await protocol.StopAsync(ct);\n        }\n\n        httpClient?.Dispose();\n        httpClient = null;\n        runCts?.Dispose();\n        runCts = null;\n        runTask = null;\n        SetState(CommunicationState.Stopped);\n    }\n\n    public override void Dispose()\n    {\n        httpClient?.Dispose();\n        runCts?.Dispose();\n    }\n\n    \u002F\u002F The API is read-only, so there is nothing to send. A writable driver would push the\n    \u002F\u002F encoded data to the device here (see the TempSensorDriver example).\n    public override void Send\u003CT>(T data)\n    {\n    }\n\n    public override Task SendAsync\u003CT>(T data, CancellationToken ct = default)\n    {\n        return Task.CompletedTask;\n    }\n\n    private async Task RunLoopAsync(CancellationToken ct)\n    {\n        try\n        {\n            while (!ct.IsCancellationRequested)\n            {\n                try\n                {\n                    \u002F\u002F One GET returns one JSON object carrying several signals at once\n                    \u002F\u002F (latitude, longitude, altitude, velocity, ...).\n                    var json = await httpClient!.GetStringAsync(Url, ct);\n\n                    foreach (var protocol in Protocols)\n                    {\n                        if (protocol is ProtocolBase\u003Cstring> textProtocol)\n                        {\n                            await textProtocol.AddReceivedDataToQueueAsync([json], ct);\n                        }\n                    }\n                }\n                catch (Exception e) when (e is HttpRequestException or TaskCanceledException && !ct.IsCancellationRequested)\n                {\n                    \u002F\u002F Network hiccups are normal with a public API: log it and try again next period.\n                    Logger?.Log(LogLevel.Warn, $\"ISS position: request failed ({e.Message}).\");\n                }\n\n                await Task.Delay(periodMs, ct);\n            }\n        }\n        catch (OperationCanceledException) when (ct.IsCancellationRequested)\n        {\n            \u002F\u002F Normal stop.\n        }\n        catch (Exception e)\n        {\n            Logger?.Log(LogLevel.Error, $\"ISS position loop failed: {e.Message}\");\n            SetState(CommunicationState.Faulted, e.Message);\n        }\n    }\n}\n","csharp",[324,816,817,822,827,832,837,842,847,853,858,862,867,872,877,882,887,893,899,905,910,916,922,928,933,939,944,950,956,962,967,973,979,985,991,997,1003,1009,1015,1021,1027,1033,1039,1045,1050,1056,1062,1067,1073,1078,1084,1090,1095,1101,1107,1113,1119,1125,1131,1137,1143,1149,1154,1159,1165,1170,1176,1181,1187,1193,1198,1203,1209,1214,1219,1224,1229,1235,1240,1246,1252,1257,1263,1268,1274,1279,1284,1290,1296,1302,1307,1312,1318,1323,1329,1334,1340,1345,1351,1356,1361,1367,1372,1378,1383,1389,1394,1400,1405,1410,1416,1421,1427,1432,1437,1442,1447,1452,1458,1463,1468,1474,1480,1486,1492,1498,1504,1509,1514,1520,1525,1530,1535,1540,1545,1551,1557,1563,1568,1573,1578,1584,1589,1595,1600,1605,1611,1616,1622,1627,1633,1638,1644,1650,1656,1662,1668,1673,1679,1685,1691,1697,1703,1709,1715,1721,1727,1732,1738,1744,1749,1754,1760,1765,1770,1776,1781,1787,1792,1798,1803,1809,1815,1820,1825],{"__ignoreMap":384},[396,818,819],{"class":398,"line":399},[396,820,821],{},"using System.Reflection;\n",[396,823,824],{"class":398,"line":406},[396,825,826],{},"using Qenex.QSuite.Common.CoreComm;\n",[396,828,829],{"class":398,"line":423},[396,830,831],{},"using Qenex.QSuite.Drivers.Driver;\n",[396,833,834],{"class":398,"line":436},[396,835,836],{},"using Qenex.QSuite.LogSystems.LogSystem;\n",[396,838,839],{"class":398,"line":449},[396,840,841],{},"using Qenex.QSuite.Protocols.Protocol;\n",[396,843,844],{"class":398,"line":462},[396,845,846],{},"using Qenex.QSuite.Specifications.Specification;\n",[396,848,849],{"class":398,"line":475},[396,850,852],{"emptyLinePlaceholder":851},true,"\n",[396,854,855],{"class":398,"line":488},[396,856,857],{},"namespace Qenex.QSuite.Examples.IssDriver;\n",[396,859,860],{"class":398,"line":495},[396,861,852],{"emptyLinePlaceholder":851},[396,863,864],{"class":398,"line":667},[396,865,866],{},"\u002F\u002F\u002F \u003Csummary>\n",[396,868,869],{"class":398,"line":673},[396,870,871],{},"\u002F\u002F\u002F Example driver: reads live data from a public REST API — the current position of the\n",[396,873,874],{"class":398,"line":679},[396,875,876],{},"\u002F\u002F\u002F International Space Station. Every period it performs one HTTP GET and hands the raw JSON\n",[396,878,879],{"class":398,"line":685},[396,880,881],{},"\u002F\u002F\u002F response to its protocols; turning the JSON into variable values is the protocol's job\n",[396,883,884],{"class":398,"line":753},[396,885,886],{},"\u002F\u002F\u002F (see the IssJsonProtocol example).\n",[396,888,890],{"class":398,"line":889},15,[396,891,892],{},"\u002F\u002F\u002F This is about the smallest possible real-data driver: read-only, one fixed URL, one setting.\n",[396,894,896],{"class":398,"line":895},16,[396,897,898],{},"\u002F\u002F\u002F \u003C\u002Fsummary>\n",[396,900,902],{"class":398,"line":901},17,[396,903,904],{},"public class IssDriver : DriverBase, ITransportSource\u003Cstring>\n",[396,906,908],{"class":398,"line":907},18,[396,909,403],{},[396,911,913],{"class":398,"line":912},19,[396,914,915],{},"    \u002F\u002F Free, key-less API returning one flat JSON object with the current ISS position.\n",[396,917,919],{"class":398,"line":918},20,[396,920,921],{},"    \u002F\u002F Be polite to the public service: do not poll faster than ~1 request per second.\n",[396,923,925],{"class":398,"line":924},21,[396,926,927],{},"    private const string Url = \"https:\u002F\u002Fapi.wheretheiss.at\u002Fv1\u002Fsatellites\u002F25544\";\n",[396,929,931],{"class":398,"line":930},22,[396,932,852],{"emptyLinePlaceholder":851},[396,934,936],{"class":398,"line":935},23,[396,937,938],{},"    private int periodMs = 1000;\n",[396,940,942],{"class":398,"line":941},24,[396,943,852],{"emptyLinePlaceholder":851},[396,945,947],{"class":398,"line":946},25,[396,948,949],{},"    private HttpClient? httpClient;\n",[396,951,953],{"class":398,"line":952},26,[396,954,955],{},"    private CancellationTokenSource? runCts;\n",[396,957,959],{"class":398,"line":958},27,[396,960,961],{},"    private Task? runTask;\n",[396,963,965],{"class":398,"line":964},28,[396,966,852],{"emptyLinePlaceholder":851},[396,968,970],{"class":398,"line":969},29,[396,971,972],{},"    public IssDriver()\n",[396,974,976],{"class":398,"line":975},30,[396,977,978],{},"    {\n",[396,980,982],{"class":398,"line":981},31,[396,983,984],{},"        Specification = new SpecificationBase\n",[396,986,988],{"class":398,"line":987},32,[396,989,990],{},"        {\n",[396,992,994],{"class":398,"line":993},33,[396,995,996],{},"            Name = \"IssDriver\",\n",[396,998,1000],{"class":398,"line":999},34,[396,1001,1002],{},"            Label = \"ISS Position (example)\",\n",[396,1004,1006],{"class":398,"line":1005},35,[396,1007,1008],{},"            Description = \"Example driver polling the ISS position from a public REST API.\",\n",[396,1010,1012],{"class":398,"line":1011},36,[396,1013,1014],{},"            CreatedOn = new DateTime(2026, 7, 27),\n",[396,1016,1018],{"class":398,"line":1017},37,[396,1019,1020],{},"            Version = Assembly.GetExecutingAssembly().GetName().Version ?? new Version(1, 0, 0, 0),\n",[396,1022,1024],{"class":398,"line":1023},38,[396,1025,1026],{},"            Author = \"Qenex\",\n",[396,1028,1030],{"class":398,"line":1029},39,[396,1031,1032],{},"            Company = \"QENEX Ltd.\"\n",[396,1034,1036],{"class":398,"line":1035},40,[396,1037,1038],{},"        };\n",[396,1040,1042],{"class":398,"line":1041},41,[396,1043,1044],{},"    }\n",[396,1046,1048],{"class":398,"line":1047},42,[396,1049,852],{"emptyLinePlaceholder":851},[396,1051,1053],{"class":398,"line":1052},43,[396,1054,1055],{},"    \u002F\u002F Shown pre-filled when the driver is added in Project Configuration.\n",[396,1057,1059],{"class":398,"line":1058},44,[396,1060,1061],{},"    public override string DefaultRawSettings => \"periodMs=1000\";\n",[396,1063,1065],{"class":398,"line":1064},45,[396,1066,852],{"emptyLinePlaceholder":851},[396,1068,1070],{"class":398,"line":1069},46,[396,1071,1072],{},"    public override void SetConfiguration()\n",[396,1074,1076],{"class":398,"line":1075},47,[396,1077,978],{},[396,1079,1081],{"class":398,"line":1080},48,[396,1082,1083],{},"        \u002F\u002F \"key=value;...\" — a missing or invalid key silently keeps the default.\n",[396,1085,1087],{"class":398,"line":1086},49,[396,1088,1089],{},"        foreach (var part in RawSettings.Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries))\n",[396,1091,1093],{"class":398,"line":1092},50,[396,1094,990],{},[396,1096,1098],{"class":398,"line":1097},51,[396,1099,1100],{},"            var pair = part.Split('=', 2, StringSplitOptions.TrimEntries);\n",[396,1102,1104],{"class":398,"line":1103},52,[396,1105,1106],{},"            if (pair.Length == 2\n",[396,1108,1110],{"class":398,"line":1109},53,[396,1111,1112],{},"                && pair[0].Equals(\"periodMs\", StringComparison.OrdinalIgnoreCase)\n",[396,1114,1116],{"class":398,"line":1115},54,[396,1117,1118],{},"                && int.TryParse(pair[1], out var parsedPeriod))\n",[396,1120,1122],{"class":398,"line":1121},55,[396,1123,1124],{},"            {\n",[396,1126,1128],{"class":398,"line":1127},56,[396,1129,1130],{},"                \u002F\u002F The API asks for at most ~1 request per second, so slower is allowed, faster is not.\n",[396,1132,1134],{"class":398,"line":1133},57,[396,1135,1136],{},"                periodMs = Math.Max(parsedPeriod, 1000);\n",[396,1138,1140],{"class":398,"line":1139},58,[396,1141,1142],{},"            }\n",[396,1144,1146],{"class":398,"line":1145},59,[396,1147,1148],{},"        }\n",[396,1150,1152],{"class":398,"line":1151},60,[396,1153,1044],{},[396,1155,1157],{"class":398,"line":1156},61,[396,1158,852],{"emptyLinePlaceholder":851},[396,1160,1162],{"class":398,"line":1161},62,[396,1163,1164],{},"    public override async Task StartAsync(CancellationToken ct = default)\n",[396,1166,1168],{"class":398,"line":1167},63,[396,1169,978],{},[396,1171,1173],{"class":398,"line":1172},64,[396,1174,1175],{},"        if (!IsEnabled)\n",[396,1177,1179],{"class":398,"line":1178},65,[396,1180,990],{},[396,1182,1184],{"class":398,"line":1183},66,[396,1185,1186],{},"            SetState(CommunicationState.Disabled);\n",[396,1188,1190],{"class":398,"line":1189},67,[396,1191,1192],{},"            return;\n",[396,1194,1196],{"class":398,"line":1195},68,[396,1197,1148],{},[396,1199,1201],{"class":398,"line":1200},69,[396,1202,852],{"emptyLinePlaceholder":851},[396,1204,1206],{"class":398,"line":1205},70,[396,1207,1208],{},"        if (State == CommunicationState.Running || runTask is { IsCompleted: false })\n",[396,1210,1212],{"class":398,"line":1211},71,[396,1213,990],{},[396,1215,1217],{"class":398,"line":1216},72,[396,1218,1192],{},[396,1220,1222],{"class":398,"line":1221},73,[396,1223,1148],{},[396,1225,1227],{"class":398,"line":1226},74,[396,1228,852],{"emptyLinePlaceholder":851},[396,1230,1232],{"class":398,"line":1231},75,[396,1233,1234],{},"        SetState(CommunicationState.Starting);\n",[396,1236,1238],{"class":398,"line":1237},76,[396,1239,852],{"emptyLinePlaceholder":851},[396,1241,1243],{"class":398,"line":1242},77,[396,1244,1245],{},"        \u002F\u002F Short timeout so a slow or unreachable server cannot block the loop for long.\n",[396,1247,1249],{"class":398,"line":1248},78,[396,1250,1251],{},"        httpClient = new HttpClient { Timeout = TimeSpan.FromSeconds(5) };\n",[396,1253,1255],{"class":398,"line":1254},79,[396,1256,852],{"emptyLinePlaceholder":851},[396,1258,1260],{"class":398,"line":1259},80,[396,1261,1262],{},"        foreach (var protocol in Protocols)\n",[396,1264,1266],{"class":398,"line":1265},81,[396,1267,990],{},[396,1269,1271],{"class":398,"line":1270},82,[396,1272,1273],{},"            await protocol.StartAsync(ct);\n",[396,1275,1277],{"class":398,"line":1276},83,[396,1278,1148],{},[396,1280,1282],{"class":398,"line":1281},84,[396,1283,852],{"emptyLinePlaceholder":851},[396,1285,1287],{"class":398,"line":1286},85,[396,1288,1289],{},"        runCts = CancellationTokenSource.CreateLinkedTokenSource(ct);\n",[396,1291,1293],{"class":398,"line":1292},86,[396,1294,1295],{},"        runTask = RunLoopAsync(runCts.Token);\n",[396,1297,1299],{"class":398,"line":1298},87,[396,1300,1301],{},"        SetState(CommunicationState.Running);\n",[396,1303,1305],{"class":398,"line":1304},88,[396,1306,1044],{},[396,1308,1310],{"class":398,"line":1309},89,[396,1311,852],{"emptyLinePlaceholder":851},[396,1313,1315],{"class":398,"line":1314},90,[396,1316,1317],{},"    public override async Task StopAsync(CancellationToken ct = default)\n",[396,1319,1321],{"class":398,"line":1320},91,[396,1322,978],{},[396,1324,1326],{"class":398,"line":1325},92,[396,1327,1328],{},"        SetState(CommunicationState.Stopping);\n",[396,1330,1332],{"class":398,"line":1331},93,[396,1333,852],{"emptyLinePlaceholder":851},[396,1335,1337],{"class":398,"line":1336},94,[396,1338,1339],{},"        if (runCts != null)\n",[396,1341,1343],{"class":398,"line":1342},95,[396,1344,990],{},[396,1346,1348],{"class":398,"line":1347},96,[396,1349,1350],{},"            await runCts.CancelAsync();\n",[396,1352,1354],{"class":398,"line":1353},97,[396,1355,1148],{},[396,1357,1359],{"class":398,"line":1358},98,[396,1360,852],{"emptyLinePlaceholder":851},[396,1362,1364],{"class":398,"line":1363},99,[396,1365,1366],{},"        if (runTask != null)\n",[396,1368,1370],{"class":398,"line":1369},100,[396,1371,990],{},[396,1373,1375],{"class":398,"line":1374},101,[396,1376,1377],{},"            try\n",[396,1379,1381],{"class":398,"line":1380},102,[396,1382,1124],{},[396,1384,1386],{"class":398,"line":1385},103,[396,1387,1388],{},"                await runTask.WaitAsync(TimeSpan.FromSeconds(5), ct);\n",[396,1390,1392],{"class":398,"line":1391},104,[396,1393,1142],{},[396,1395,1397],{"class":398,"line":1396},105,[396,1398,1399],{},"            catch (OperationCanceledException)\n",[396,1401,1403],{"class":398,"line":1402},106,[396,1404,1124],{},[396,1406,1408],{"class":398,"line":1407},107,[396,1409,1142],{},[396,1411,1413],{"class":398,"line":1412},108,[396,1414,1415],{},"            catch (TimeoutException)\n",[396,1417,1419],{"class":398,"line":1418},109,[396,1420,1124],{},[396,1422,1424],{"class":398,"line":1423},110,[396,1425,1426],{},"                Logger?.Log(LogLevel.Warn, \"ISS position: the polling loop did not stop before timeout.\");\n",[396,1428,1430],{"class":398,"line":1429},111,[396,1431,1142],{},[396,1433,1435],{"class":398,"line":1434},112,[396,1436,1148],{},[396,1438,1440],{"class":398,"line":1439},113,[396,1441,852],{"emptyLinePlaceholder":851},[396,1443,1445],{"class":398,"line":1444},114,[396,1446,1262],{},[396,1448,1450],{"class":398,"line":1449},115,[396,1451,990],{},[396,1453,1455],{"class":398,"line":1454},116,[396,1456,1457],{},"            await protocol.StopAsync(ct);\n",[396,1459,1461],{"class":398,"line":1460},117,[396,1462,1148],{},[396,1464,1466],{"class":398,"line":1465},118,[396,1467,852],{"emptyLinePlaceholder":851},[396,1469,1471],{"class":398,"line":1470},119,[396,1472,1473],{},"        httpClient?.Dispose();\n",[396,1475,1477],{"class":398,"line":1476},120,[396,1478,1479],{},"        httpClient = null;\n",[396,1481,1483],{"class":398,"line":1482},121,[396,1484,1485],{},"        runCts?.Dispose();\n",[396,1487,1489],{"class":398,"line":1488},122,[396,1490,1491],{},"        runCts = null;\n",[396,1493,1495],{"class":398,"line":1494},123,[396,1496,1497],{},"        runTask = null;\n",[396,1499,1501],{"class":398,"line":1500},124,[396,1502,1503],{},"        SetState(CommunicationState.Stopped);\n",[396,1505,1507],{"class":398,"line":1506},125,[396,1508,1044],{},[396,1510,1512],{"class":398,"line":1511},126,[396,1513,852],{"emptyLinePlaceholder":851},[396,1515,1517],{"class":398,"line":1516},127,[396,1518,1519],{},"    public override void Dispose()\n",[396,1521,1523],{"class":398,"line":1522},128,[396,1524,978],{},[396,1526,1528],{"class":398,"line":1527},129,[396,1529,1473],{},[396,1531,1533],{"class":398,"line":1532},130,[396,1534,1485],{},[396,1536,1538],{"class":398,"line":1537},131,[396,1539,1044],{},[396,1541,1543],{"class":398,"line":1542},132,[396,1544,852],{"emptyLinePlaceholder":851},[396,1546,1548],{"class":398,"line":1547},133,[396,1549,1550],{},"    \u002F\u002F The API is read-only, so there is nothing to send. A writable driver would push the\n",[396,1552,1554],{"class":398,"line":1553},134,[396,1555,1556],{},"    \u002F\u002F encoded data to the device here (see the TempSensorDriver example).\n",[396,1558,1560],{"class":398,"line":1559},135,[396,1561,1562],{},"    public override void Send\u003CT>(T data)\n",[396,1564,1566],{"class":398,"line":1565},136,[396,1567,978],{},[396,1569,1571],{"class":398,"line":1570},137,[396,1572,1044],{},[396,1574,1576],{"class":398,"line":1575},138,[396,1577,852],{"emptyLinePlaceholder":851},[396,1579,1581],{"class":398,"line":1580},139,[396,1582,1583],{},"    public override Task SendAsync\u003CT>(T data, CancellationToken ct = default)\n",[396,1585,1587],{"class":398,"line":1586},140,[396,1588,978],{},[396,1590,1592],{"class":398,"line":1591},141,[396,1593,1594],{},"        return Task.CompletedTask;\n",[396,1596,1598],{"class":398,"line":1597},142,[396,1599,1044],{},[396,1601,1603],{"class":398,"line":1602},143,[396,1604,852],{"emptyLinePlaceholder":851},[396,1606,1608],{"class":398,"line":1607},144,[396,1609,1610],{},"    private async Task RunLoopAsync(CancellationToken ct)\n",[396,1612,1614],{"class":398,"line":1613},145,[396,1615,978],{},[396,1617,1619],{"class":398,"line":1618},146,[396,1620,1621],{},"        try\n",[396,1623,1625],{"class":398,"line":1624},147,[396,1626,990],{},[396,1628,1630],{"class":398,"line":1629},148,[396,1631,1632],{},"            while (!ct.IsCancellationRequested)\n",[396,1634,1636],{"class":398,"line":1635},149,[396,1637,1124],{},[396,1639,1641],{"class":398,"line":1640},150,[396,1642,1643],{},"                try\n",[396,1645,1647],{"class":398,"line":1646},151,[396,1648,1649],{},"                {\n",[396,1651,1653],{"class":398,"line":1652},152,[396,1654,1655],{},"                    \u002F\u002F One GET returns one JSON object carrying several signals at once\n",[396,1657,1659],{"class":398,"line":1658},153,[396,1660,1661],{},"                    \u002F\u002F (latitude, longitude, altitude, velocity, ...).\n",[396,1663,1665],{"class":398,"line":1664},154,[396,1666,1667],{},"                    var json = await httpClient!.GetStringAsync(Url, ct);\n",[396,1669,1671],{"class":398,"line":1670},155,[396,1672,852],{"emptyLinePlaceholder":851},[396,1674,1676],{"class":398,"line":1675},156,[396,1677,1678],{},"                    foreach (var protocol in Protocols)\n",[396,1680,1682],{"class":398,"line":1681},157,[396,1683,1684],{},"                    {\n",[396,1686,1688],{"class":398,"line":1687},158,[396,1689,1690],{},"                        if (protocol is ProtocolBase\u003Cstring> textProtocol)\n",[396,1692,1694],{"class":398,"line":1693},159,[396,1695,1696],{},"                        {\n",[396,1698,1700],{"class":398,"line":1699},160,[396,1701,1702],{},"                            await textProtocol.AddReceivedDataToQueueAsync([json], ct);\n",[396,1704,1706],{"class":398,"line":1705},161,[396,1707,1708],{},"                        }\n",[396,1710,1712],{"class":398,"line":1711},162,[396,1713,1714],{},"                    }\n",[396,1716,1718],{"class":398,"line":1717},163,[396,1719,1720],{},"                }\n",[396,1722,1724],{"class":398,"line":1723},164,[396,1725,1726],{},"                catch (Exception e) when (e is HttpRequestException or TaskCanceledException && !ct.IsCancellationRequested)\n",[396,1728,1730],{"class":398,"line":1729},165,[396,1731,1649],{},[396,1733,1735],{"class":398,"line":1734},166,[396,1736,1737],{},"                    \u002F\u002F Network hiccups are normal with a public API: log it and try again next period.\n",[396,1739,1741],{"class":398,"line":1740},167,[396,1742,1743],{},"                    Logger?.Log(LogLevel.Warn, $\"ISS position: request failed ({e.Message}).\");\n",[396,1745,1747],{"class":398,"line":1746},168,[396,1748,1720],{},[396,1750,1752],{"class":398,"line":1751},169,[396,1753,852],{"emptyLinePlaceholder":851},[396,1755,1757],{"class":398,"line":1756},170,[396,1758,1759],{},"                await Task.Delay(periodMs, ct);\n",[396,1761,1763],{"class":398,"line":1762},171,[396,1764,1142],{},[396,1766,1768],{"class":398,"line":1767},172,[396,1769,1148],{},[396,1771,1773],{"class":398,"line":1772},173,[396,1774,1775],{},"        catch (OperationCanceledException) when (ct.IsCancellationRequested)\n",[396,1777,1779],{"class":398,"line":1778},174,[396,1780,990],{},[396,1782,1784],{"class":398,"line":1783},175,[396,1785,1786],{},"            \u002F\u002F Normal stop.\n",[396,1788,1790],{"class":398,"line":1789},176,[396,1791,1148],{},[396,1793,1795],{"class":398,"line":1794},177,[396,1796,1797],{},"        catch (Exception e)\n",[396,1799,1801],{"class":398,"line":1800},178,[396,1802,990],{},[396,1804,1806],{"class":398,"line":1805},179,[396,1807,1808],{},"            Logger?.Log(LogLevel.Error, $\"ISS position loop failed: {e.Message}\");\n",[396,1810,1812],{"class":398,"line":1811},180,[396,1813,1814],{},"            SetState(CommunicationState.Faulted, e.Message);\n",[396,1816,1818],{"class":398,"line":1817},181,[396,1819,1148],{},[396,1821,1823],{"class":398,"line":1822},182,[396,1824,1044],{},[396,1826,1828],{"class":398,"line":1827},183,[396,1829,498],{},[360,1831,1833],{"id":1832},"protocol-issjsonprotocolcs","Protocol: IssJsonProtocol.cs",[302,1835,1836,1837,1840,1841,1844,1845,770,1847,1849,1850,1852],{},"The protocol is even shorter than the driver, mostly thanks to one\ndecision: ",[306,1838,1839],{},"it has no queue and no worker thread of its own",". The data\narrives once a second, so it is decoded straight in\n",[324,1842,1843],{},"AddReceivedDataToQueueAsync"," — on the driver's polling task.\n",[324,1846,769],{},[324,1848,773],{}," then only flip the state. On fast buses this\nwould be a bad idea (a driver must never wait for processing); the\n",[368,1851,809],{"href":808},"\nshows a proper queue with its own consumer.",[302,1854,1855,1856,1859,1860,1863,1864,1867],{},"The decoding in ",[324,1857,1858],{},"Decode"," is straightforward: parse the document and give\na value to every variable whose ",[324,1861,1862],{},"path"," points to a numeric field.\n",[324,1865,1866],{},"Encode"," returns nothing — a REST API you can only read from leaves\nnothing to encode.",[376,1869,1871],{"className":812,"code":1870,"language":814,"meta":384,"style":384},"using System.Reflection;\nusing System.Text.Json;\nusing Qenex.QSuite.Common.CoreComm;\nusing Qenex.QSuite.LogSystems.LogSystem;\nusing Qenex.QSuite.Protocols.Protocol;\nusing Qenex.QSuite.Specifications.Specification;\nusing Qenex.QSuite.Variables.QVariables;\nusing Qenex.QSuite.Variables.QVariables.Values;\nusing Qenex.QSuite.Variables.VariableEvents;\n\nnamespace Qenex.QSuite.Examples.IssJsonProtocol;\n\n\u002F\u002F\u002F \u003Csummary>\n\u002F\u002F\u002F Example protocol: picks numeric fields out of the received ISS position JSON by name. Each\n\u002F\u002F\u002F variable states which field it wants via the \"path\" commParam (path=\"latitude\"), so one JSON\n\u002F\u002F\u002F document can feed many variables at once. Nothing here is ISS-specific except the name — the\n\u002F\u002F\u002F same code would decode any flat JSON object — but as an example it is paired with the IssDriver.\n\u002F\u002F\u002F \u003C\u002Fsummary>\npublic class IssJsonProtocol : ProtocolBase\u003Cstring>\n{\n    public IssJsonProtocol()\n    {\n        Specification = new SpecificationBase\n        {\n            Name = \"IssJsonProtocol\",\n            Label = \"ISS JSON Protocol (example)\",\n            Description = \"Example protocol decoding numeric fields of the ISS position JSON into variables.\",\n            CreatedOn = new DateTime(2026, 7, 27),\n            Version = Assembly.GetExecutingAssembly().GetName().Version ?? new Version(1, 0, 0, 0),\n            Author = \"Qenex\",\n            Company = \"QENEX Ltd.\"\n        };\n    }\n\n    \u002F\u002F The transport type (string) alone cannot tell text-line drivers apart, and this\n    \u002F\u002F protocol only understands the ISS position JSON, so it narrows itself to its driver.\n    public override IReadOnlyList\u003Cstring> CompatibleDrivers => [\"IssDriver\"];\n\n    \u002F\u002F The protocol has no protocol-level settings; everything is per-variable commParams.\n    public override void SetConfiguration()\n    {\n    }\n\n    public override string CreateDefaultCommParam(IVariableBase variable, IEnumerable\u003CIVarEvent> variableEvents)\n    {\n        return \"path=\\\"latitude\\\"\";\n    }\n\n    public override IProtocolVariable? CreateProtocolVariable(IVariableBase variable, string commParams, bool isCommunicated)\n    {\n        try\n        {\n            return new ProtocolVariable\n            {\n                Variable = variable,\n                IsCommunicated = isCommunicated,\n                ProtocolVariableSpecification = IssJsonVariableSpecification.Create(commParams)\n            };\n        }\n        catch (Exception e)\n        {\n            Logger?.Log(LogLevel.Warn, $\"Protocol variable specification for variable {variable.Name} could not be created ({e.Message}).\");\n            return null;\n        }\n    }\n\n    public override IProtocolVariable? CreateProtocolVariable(IVariableBase variable, IVarEvent variableEvent, string id)\n    {\n        return CreateProtocolVariable(variable, $\"path=\\\"{id}\\\"\", true);\n    }\n\n    public override IProtocolVariable? CreateProtocolVariable(IVariableBase variable, IEnumerable\u003CIVarEvent> variableEvents,\n        string commParams, bool isCommunicated)\n    {\n        \u002F\u002F The data arrive whenever the driver polls them, so variable events are not used.\n        return CreateProtocolVariable(variable, commParams, isCommunicated);\n    }\n\n    \u002F\u002F No worker loop is needed: received documents are decoded directly in\n    \u002F\u002F AddReceivedDataToQueueAsync, so start\u002Fstop only maintain the state machine.\n    \u002F\u002F (For a queued, cancelable consumer loop see the TempSensorProtocol example.)\n    public override Task StartAsync(CancellationToken ct = default)\n    {\n        SetState(IsEnabled ? CommunicationState.Running : CommunicationState.Disabled);\n        return Task.CompletedTask;\n    }\n\n    public override Task StopAsync(CancellationToken ct = default)\n    {\n        SetState(CommunicationState.Stopped);\n        return Task.CompletedTask;\n    }\n\n    public override void Dispose()\n    {\n    }\n\n    \u002F\u002F At ~1 document per second there is no need for an internal queue — the data are\n    \u002F\u002F decoded right away on the driver's polling task.\n    public override async Task AddReceivedDataToQueueAsync(IEnumerable\u003Cstring> data, CancellationToken ct = default)\n    {\n        if (State != CommunicationState.Running)\n        {\n            return;\n        }\n\n        await ProcessReceivedDataAsync(data, ct);\n    }\n\n    protected override void ProcessReceivedData(IEnumerable\u003Cstring> data)\n    {\n        foreach (var protocolVariable in Decode(data))\n        {\n            protocolVariable.NotifyValueChanged();\n        }\n    }\n\n    protected override async Task ProcessReceivedDataAsync(IEnumerable\u003Cstring> data, CancellationToken ct = default)\n    {\n        var notifyTasks = Decode(data).Select(protocolVariable => protocolVariable.NotifyValueChangedAsync());\n        await Task.WhenAll(notifyTasks);\n    }\n\n    protected override IEnumerable\u003CIProtocolVariable> Decode(IEnumerable\u003Cstring> data)\n    {\n        var updated = new List\u003CIProtocolVariable>();\n\n        foreach (var json in data)\n        {\n            JsonDocument document;\n            try\n            {\n                document = JsonDocument.Parse(json);\n            }\n            catch (JsonException e)\n            {\n                Logger?.Log(LogLevel.Warn, $\"ISS JSON protocol: cannot parse received data ({e.Message}).\");\n                continue;\n            }\n\n            using (document)\n            {\n                \u002F\u002F Every variable whose \"path\" names a numeric field of this document gets its value.\n                foreach (var protocolVariable in Variables)\n                {\n                    if (!protocolVariable.IsCommunicated\n                        || protocolVariable.ProtocolVariableSpecification is not IssJsonVariableSpecification spec\n                        || protocolVariable.Variable is not ScalarVariable scalarVariable\n                        || !document.RootElement.TryGetProperty(spec.Path, out var field)\n                        || field.ValueKind != JsonValueKind.Number)\n                    {\n                        continue;\n                    }\n\n                    if (!TrySetValue(scalarVariable, field.GetDouble()))\n                    {\n                        Logger?.Log(LogLevel.Warn, $\"ISS JSON protocol: variable '{scalarVariable.Name}' has an unsupported value type.\");\n                        continue;\n                    }\n\n                    scalarVariable.Timestamp = DateTime.UtcNow;\n                    updated.Add(protocolVariable);\n                }\n            }\n        }\n\n        return updated;\n    }\n\n    \u002F\u002F The protocol is read-only (a REST API you can only GET), so nothing is ever encoded.\n    \u002F\u002F For the write direction see the TempSensorProtocol example.\n    protected override IEnumerable\u003Cstring> Encode(IEnumerable\u003CIProtocolVariable> protocolVariables)\n    {\n        yield break;\n    }\n\n    \u002F\u002F Values\u003CT>.SetValue does not convert, so the received double is converted to the\n    \u002F\u002F variable's type here; the example supports the common numeric types.\n    private static bool TrySetValue(ScalarVariable scalarVariable, double value)\n    {\n        switch (scalarVariable.Values)\n        {\n            case Values\u003Cdouble> doubleValues:\n                doubleValues.Value = value;\n                return true;\n            case Values\u003Cfloat> floatValues:\n                floatValues.Value = (float)value;\n                return true;\n            case Values\u003Cint> intValues:\n                intValues.Value = (int)Math.Round(value);\n                return true;\n            default:\n                return false;\n        }\n    }\n}\n",[324,1872,1873,1877,1882,1886,1890,1894,1898,1903,1908,1913,1917,1922,1926,1930,1935,1940,1945,1950,1954,1959,1963,1968,1972,1976,1980,1985,1990,1995,1999,2003,2007,2011,2015,2019,2023,2028,2033,2038,2042,2047,2051,2055,2059,2063,2068,2072,2077,2081,2085,2090,2094,2098,2102,2107,2111,2116,2121,2126,2131,2135,2139,2143,2148,2153,2157,2161,2165,2170,2174,2179,2183,2187,2192,2197,2201,2206,2211,2215,2219,2224,2229,2234,2239,2243,2248,2252,2256,2260,2265,2269,2273,2277,2281,2285,2289,2293,2297,2301,2306,2311,2316,2320,2325,2329,2333,2337,2341,2346,2350,2354,2359,2363,2368,2372,2377,2381,2385,2389,2394,2398,2403,2408,2412,2416,2421,2425,2430,2434,2439,2443,2448,2452,2456,2461,2465,2470,2474,2479,2484,2488,2492,2497,2501,2506,2511,2515,2520,2525,2530,2535,2540,2544,2549,2553,2557,2562,2566,2571,2575,2579,2583,2588,2593,2597,2601,2605,2609,2614,2618,2622,2627,2632,2637,2641,2646,2650,2654,2659,2664,2669,2673,2678,2682,2687,2693,2699,2705,2711,2716,2722,2728,2733,2739,2745,2750,2755],{"__ignoreMap":384},[396,1874,1875],{"class":398,"line":399},[396,1876,821],{},[396,1878,1879],{"class":398,"line":406},[396,1880,1881],{},"using System.Text.Json;\n",[396,1883,1884],{"class":398,"line":423},[396,1885,826],{},[396,1887,1888],{"class":398,"line":436},[396,1889,836],{},[396,1891,1892],{"class":398,"line":449},[396,1893,841],{},[396,1895,1896],{"class":398,"line":462},[396,1897,846],{},[396,1899,1900],{"class":398,"line":475},[396,1901,1902],{},"using Qenex.QSuite.Variables.QVariables;\n",[396,1904,1905],{"class":398,"line":488},[396,1906,1907],{},"using Qenex.QSuite.Variables.QVariables.Values;\n",[396,1909,1910],{"class":398,"line":495},[396,1911,1912],{},"using Qenex.QSuite.Variables.VariableEvents;\n",[396,1914,1915],{"class":398,"line":667},[396,1916,852],{"emptyLinePlaceholder":851},[396,1918,1919],{"class":398,"line":673},[396,1920,1921],{},"namespace Qenex.QSuite.Examples.IssJsonProtocol;\n",[396,1923,1924],{"class":398,"line":679},[396,1925,852],{"emptyLinePlaceholder":851},[396,1927,1928],{"class":398,"line":685},[396,1929,866],{},[396,1931,1932],{"class":398,"line":753},[396,1933,1934],{},"\u002F\u002F\u002F Example protocol: picks numeric fields out of the received ISS position JSON by name. Each\n",[396,1936,1937],{"class":398,"line":889},[396,1938,1939],{},"\u002F\u002F\u002F variable states which field it wants via the \"path\" commParam (path=\"latitude\"), so one JSON\n",[396,1941,1942],{"class":398,"line":895},[396,1943,1944],{},"\u002F\u002F\u002F document can feed many variables at once. Nothing here is ISS-specific except the name — the\n",[396,1946,1947],{"class":398,"line":901},[396,1948,1949],{},"\u002F\u002F\u002F same code would decode any flat JSON object — but as an example it is paired with the IssDriver.\n",[396,1951,1952],{"class":398,"line":907},[396,1953,898],{},[396,1955,1956],{"class":398,"line":912},[396,1957,1958],{},"public class IssJsonProtocol : ProtocolBase\u003Cstring>\n",[396,1960,1961],{"class":398,"line":918},[396,1962,403],{},[396,1964,1965],{"class":398,"line":924},[396,1966,1967],{},"    public IssJsonProtocol()\n",[396,1969,1970],{"class":398,"line":930},[396,1971,978],{},[396,1973,1974],{"class":398,"line":935},[396,1975,984],{},[396,1977,1978],{"class":398,"line":941},[396,1979,990],{},[396,1981,1982],{"class":398,"line":946},[396,1983,1984],{},"            Name = \"IssJsonProtocol\",\n",[396,1986,1987],{"class":398,"line":952},[396,1988,1989],{},"            Label = \"ISS JSON Protocol (example)\",\n",[396,1991,1992],{"class":398,"line":958},[396,1993,1994],{},"            Description = \"Example protocol decoding numeric fields of the ISS position JSON into variables.\",\n",[396,1996,1997],{"class":398,"line":964},[396,1998,1014],{},[396,2000,2001],{"class":398,"line":969},[396,2002,1020],{},[396,2004,2005],{"class":398,"line":975},[396,2006,1026],{},[396,2008,2009],{"class":398,"line":981},[396,2010,1032],{},[396,2012,2013],{"class":398,"line":987},[396,2014,1038],{},[396,2016,2017],{"class":398,"line":993},[396,2018,1044],{},[396,2020,2021],{"class":398,"line":999},[396,2022,852],{"emptyLinePlaceholder":851},[396,2024,2025],{"class":398,"line":1005},[396,2026,2027],{},"    \u002F\u002F The transport type (string) alone cannot tell text-line drivers apart, and this\n",[396,2029,2030],{"class":398,"line":1011},[396,2031,2032],{},"    \u002F\u002F protocol only understands the ISS position JSON, so it narrows itself to its driver.\n",[396,2034,2035],{"class":398,"line":1017},[396,2036,2037],{},"    public override IReadOnlyList\u003Cstring> CompatibleDrivers => [\"IssDriver\"];\n",[396,2039,2040],{"class":398,"line":1023},[396,2041,852],{"emptyLinePlaceholder":851},[396,2043,2044],{"class":398,"line":1029},[396,2045,2046],{},"    \u002F\u002F The protocol has no protocol-level settings; everything is per-variable commParams.\n",[396,2048,2049],{"class":398,"line":1035},[396,2050,1072],{},[396,2052,2053],{"class":398,"line":1041},[396,2054,978],{},[396,2056,2057],{"class":398,"line":1047},[396,2058,1044],{},[396,2060,2061],{"class":398,"line":1052},[396,2062,852],{"emptyLinePlaceholder":851},[396,2064,2065],{"class":398,"line":1058},[396,2066,2067],{},"    public override string CreateDefaultCommParam(IVariableBase variable, IEnumerable\u003CIVarEvent> variableEvents)\n",[396,2069,2070],{"class":398,"line":1064},[396,2071,978],{},[396,2073,2074],{"class":398,"line":1069},[396,2075,2076],{},"        return \"path=\\\"latitude\\\"\";\n",[396,2078,2079],{"class":398,"line":1075},[396,2080,1044],{},[396,2082,2083],{"class":398,"line":1080},[396,2084,852],{"emptyLinePlaceholder":851},[396,2086,2087],{"class":398,"line":1086},[396,2088,2089],{},"    public override IProtocolVariable? CreateProtocolVariable(IVariableBase variable, string commParams, bool isCommunicated)\n",[396,2091,2092],{"class":398,"line":1092},[396,2093,978],{},[396,2095,2096],{"class":398,"line":1097},[396,2097,1621],{},[396,2099,2100],{"class":398,"line":1103},[396,2101,990],{},[396,2103,2104],{"class":398,"line":1109},[396,2105,2106],{},"            return new ProtocolVariable\n",[396,2108,2109],{"class":398,"line":1115},[396,2110,1124],{},[396,2112,2113],{"class":398,"line":1121},[396,2114,2115],{},"                Variable = variable,\n",[396,2117,2118],{"class":398,"line":1127},[396,2119,2120],{},"                IsCommunicated = isCommunicated,\n",[396,2122,2123],{"class":398,"line":1133},[396,2124,2125],{},"                ProtocolVariableSpecification = IssJsonVariableSpecification.Create(commParams)\n",[396,2127,2128],{"class":398,"line":1139},[396,2129,2130],{},"            };\n",[396,2132,2133],{"class":398,"line":1145},[396,2134,1148],{},[396,2136,2137],{"class":398,"line":1151},[396,2138,1797],{},[396,2140,2141],{"class":398,"line":1156},[396,2142,990],{},[396,2144,2145],{"class":398,"line":1161},[396,2146,2147],{},"            Logger?.Log(LogLevel.Warn, $\"Protocol variable specification for variable {variable.Name} could not be created ({e.Message}).\");\n",[396,2149,2150],{"class":398,"line":1167},[396,2151,2152],{},"            return null;\n",[396,2154,2155],{"class":398,"line":1172},[396,2156,1148],{},[396,2158,2159],{"class":398,"line":1178},[396,2160,1044],{},[396,2162,2163],{"class":398,"line":1183},[396,2164,852],{"emptyLinePlaceholder":851},[396,2166,2167],{"class":398,"line":1189},[396,2168,2169],{},"    public override IProtocolVariable? CreateProtocolVariable(IVariableBase variable, IVarEvent variableEvent, string id)\n",[396,2171,2172],{"class":398,"line":1195},[396,2173,978],{},[396,2175,2176],{"class":398,"line":1200},[396,2177,2178],{},"        return CreateProtocolVariable(variable, $\"path=\\\"{id}\\\"\", true);\n",[396,2180,2181],{"class":398,"line":1205},[396,2182,1044],{},[396,2184,2185],{"class":398,"line":1211},[396,2186,852],{"emptyLinePlaceholder":851},[396,2188,2189],{"class":398,"line":1216},[396,2190,2191],{},"    public override IProtocolVariable? CreateProtocolVariable(IVariableBase variable, IEnumerable\u003CIVarEvent> variableEvents,\n",[396,2193,2194],{"class":398,"line":1221},[396,2195,2196],{},"        string commParams, bool isCommunicated)\n",[396,2198,2199],{"class":398,"line":1226},[396,2200,978],{},[396,2202,2203],{"class":398,"line":1231},[396,2204,2205],{},"        \u002F\u002F The data arrive whenever the driver polls them, so variable events are not used.\n",[396,2207,2208],{"class":398,"line":1237},[396,2209,2210],{},"        return CreateProtocolVariable(variable, commParams, isCommunicated);\n",[396,2212,2213],{"class":398,"line":1242},[396,2214,1044],{},[396,2216,2217],{"class":398,"line":1248},[396,2218,852],{"emptyLinePlaceholder":851},[396,2220,2221],{"class":398,"line":1254},[396,2222,2223],{},"    \u002F\u002F No worker loop is needed: received documents are decoded directly in\n",[396,2225,2226],{"class":398,"line":1259},[396,2227,2228],{},"    \u002F\u002F AddReceivedDataToQueueAsync, so start\u002Fstop only maintain the state machine.\n",[396,2230,2231],{"class":398,"line":1265},[396,2232,2233],{},"    \u002F\u002F (For a queued, cancelable consumer loop see the TempSensorProtocol example.)\n",[396,2235,2236],{"class":398,"line":1270},[396,2237,2238],{},"    public override Task StartAsync(CancellationToken ct = default)\n",[396,2240,2241],{"class":398,"line":1276},[396,2242,978],{},[396,2244,2245],{"class":398,"line":1281},[396,2246,2247],{},"        SetState(IsEnabled ? CommunicationState.Running : CommunicationState.Disabled);\n",[396,2249,2250],{"class":398,"line":1286},[396,2251,1594],{},[396,2253,2254],{"class":398,"line":1292},[396,2255,1044],{},[396,2257,2258],{"class":398,"line":1298},[396,2259,852],{"emptyLinePlaceholder":851},[396,2261,2262],{"class":398,"line":1304},[396,2263,2264],{},"    public override Task StopAsync(CancellationToken ct = default)\n",[396,2266,2267],{"class":398,"line":1309},[396,2268,978],{},[396,2270,2271],{"class":398,"line":1314},[396,2272,1503],{},[396,2274,2275],{"class":398,"line":1320},[396,2276,1594],{},[396,2278,2279],{"class":398,"line":1325},[396,2280,1044],{},[396,2282,2283],{"class":398,"line":1331},[396,2284,852],{"emptyLinePlaceholder":851},[396,2286,2287],{"class":398,"line":1336},[396,2288,1519],{},[396,2290,2291],{"class":398,"line":1342},[396,2292,978],{},[396,2294,2295],{"class":398,"line":1347},[396,2296,1044],{},[396,2298,2299],{"class":398,"line":1353},[396,2300,852],{"emptyLinePlaceholder":851},[396,2302,2303],{"class":398,"line":1358},[396,2304,2305],{},"    \u002F\u002F At ~1 document per second there is no need for an internal queue — the data are\n",[396,2307,2308],{"class":398,"line":1363},[396,2309,2310],{},"    \u002F\u002F decoded right away on the driver's polling task.\n",[396,2312,2313],{"class":398,"line":1369},[396,2314,2315],{},"    public override async Task AddReceivedDataToQueueAsync(IEnumerable\u003Cstring> data, CancellationToken ct = default)\n",[396,2317,2318],{"class":398,"line":1374},[396,2319,978],{},[396,2321,2322],{"class":398,"line":1380},[396,2323,2324],{},"        if (State != CommunicationState.Running)\n",[396,2326,2327],{"class":398,"line":1385},[396,2328,990],{},[396,2330,2331],{"class":398,"line":1391},[396,2332,1192],{},[396,2334,2335],{"class":398,"line":1396},[396,2336,1148],{},[396,2338,2339],{"class":398,"line":1402},[396,2340,852],{"emptyLinePlaceholder":851},[396,2342,2343],{"class":398,"line":1407},[396,2344,2345],{},"        await ProcessReceivedDataAsync(data, ct);\n",[396,2347,2348],{"class":398,"line":1412},[396,2349,1044],{},[396,2351,2352],{"class":398,"line":1418},[396,2353,852],{"emptyLinePlaceholder":851},[396,2355,2356],{"class":398,"line":1423},[396,2357,2358],{},"    protected override void ProcessReceivedData(IEnumerable\u003Cstring> data)\n",[396,2360,2361],{"class":398,"line":1429},[396,2362,978],{},[396,2364,2365],{"class":398,"line":1434},[396,2366,2367],{},"        foreach (var protocolVariable in Decode(data))\n",[396,2369,2370],{"class":398,"line":1439},[396,2371,990],{},[396,2373,2374],{"class":398,"line":1444},[396,2375,2376],{},"            protocolVariable.NotifyValueChanged();\n",[396,2378,2379],{"class":398,"line":1449},[396,2380,1148],{},[396,2382,2383],{"class":398,"line":1454},[396,2384,1044],{},[396,2386,2387],{"class":398,"line":1460},[396,2388,852],{"emptyLinePlaceholder":851},[396,2390,2391],{"class":398,"line":1465},[396,2392,2393],{},"    protected override async Task ProcessReceivedDataAsync(IEnumerable\u003Cstring> data, CancellationToken ct = default)\n",[396,2395,2396],{"class":398,"line":1470},[396,2397,978],{},[396,2399,2400],{"class":398,"line":1476},[396,2401,2402],{},"        var notifyTasks = Decode(data).Select(protocolVariable => protocolVariable.NotifyValueChangedAsync());\n",[396,2404,2405],{"class":398,"line":1482},[396,2406,2407],{},"        await Task.WhenAll(notifyTasks);\n",[396,2409,2410],{"class":398,"line":1488},[396,2411,1044],{},[396,2413,2414],{"class":398,"line":1494},[396,2415,852],{"emptyLinePlaceholder":851},[396,2417,2418],{"class":398,"line":1500},[396,2419,2420],{},"    protected override IEnumerable\u003CIProtocolVariable> Decode(IEnumerable\u003Cstring> data)\n",[396,2422,2423],{"class":398,"line":1506},[396,2424,978],{},[396,2426,2427],{"class":398,"line":1511},[396,2428,2429],{},"        var updated = new List\u003CIProtocolVariable>();\n",[396,2431,2432],{"class":398,"line":1516},[396,2433,852],{"emptyLinePlaceholder":851},[396,2435,2436],{"class":398,"line":1522},[396,2437,2438],{},"        foreach (var json in data)\n",[396,2440,2441],{"class":398,"line":1527},[396,2442,990],{},[396,2444,2445],{"class":398,"line":1532},[396,2446,2447],{},"            JsonDocument document;\n",[396,2449,2450],{"class":398,"line":1537},[396,2451,1377],{},[396,2453,2454],{"class":398,"line":1542},[396,2455,1124],{},[396,2457,2458],{"class":398,"line":1547},[396,2459,2460],{},"                document = JsonDocument.Parse(json);\n",[396,2462,2463],{"class":398,"line":1553},[396,2464,1142],{},[396,2466,2467],{"class":398,"line":1559},[396,2468,2469],{},"            catch (JsonException e)\n",[396,2471,2472],{"class":398,"line":1565},[396,2473,1124],{},[396,2475,2476],{"class":398,"line":1570},[396,2477,2478],{},"                Logger?.Log(LogLevel.Warn, $\"ISS JSON protocol: cannot parse received data ({e.Message}).\");\n",[396,2480,2481],{"class":398,"line":1575},[396,2482,2483],{},"                continue;\n",[396,2485,2486],{"class":398,"line":1580},[396,2487,1142],{},[396,2489,2490],{"class":398,"line":1586},[396,2491,852],{"emptyLinePlaceholder":851},[396,2493,2494],{"class":398,"line":1591},[396,2495,2496],{},"            using (document)\n",[396,2498,2499],{"class":398,"line":1597},[396,2500,1124],{},[396,2502,2503],{"class":398,"line":1602},[396,2504,2505],{},"                \u002F\u002F Every variable whose \"path\" names a numeric field of this document gets its value.\n",[396,2507,2508],{"class":398,"line":1607},[396,2509,2510],{},"                foreach (var protocolVariable in Variables)\n",[396,2512,2513],{"class":398,"line":1613},[396,2514,1649],{},[396,2516,2517],{"class":398,"line":1618},[396,2518,2519],{},"                    if (!protocolVariable.IsCommunicated\n",[396,2521,2522],{"class":398,"line":1624},[396,2523,2524],{},"                        || protocolVariable.ProtocolVariableSpecification is not IssJsonVariableSpecification spec\n",[396,2526,2527],{"class":398,"line":1629},[396,2528,2529],{},"                        || protocolVariable.Variable is not ScalarVariable scalarVariable\n",[396,2531,2532],{"class":398,"line":1635},[396,2533,2534],{},"                        || !document.RootElement.TryGetProperty(spec.Path, out var field)\n",[396,2536,2537],{"class":398,"line":1640},[396,2538,2539],{},"                        || field.ValueKind != JsonValueKind.Number)\n",[396,2541,2542],{"class":398,"line":1646},[396,2543,1684],{},[396,2545,2546],{"class":398,"line":1652},[396,2547,2548],{},"                        continue;\n",[396,2550,2551],{"class":398,"line":1658},[396,2552,1714],{},[396,2554,2555],{"class":398,"line":1664},[396,2556,852],{"emptyLinePlaceholder":851},[396,2558,2559],{"class":398,"line":1670},[396,2560,2561],{},"                    if (!TrySetValue(scalarVariable, field.GetDouble()))\n",[396,2563,2564],{"class":398,"line":1675},[396,2565,1684],{},[396,2567,2568],{"class":398,"line":1681},[396,2569,2570],{},"                        Logger?.Log(LogLevel.Warn, $\"ISS JSON protocol: variable '{scalarVariable.Name}' has an unsupported value type.\");\n",[396,2572,2573],{"class":398,"line":1687},[396,2574,2548],{},[396,2576,2577],{"class":398,"line":1693},[396,2578,1714],{},[396,2580,2581],{"class":398,"line":1699},[396,2582,852],{"emptyLinePlaceholder":851},[396,2584,2585],{"class":398,"line":1705},[396,2586,2587],{},"                    scalarVariable.Timestamp = DateTime.UtcNow;\n",[396,2589,2590],{"class":398,"line":1711},[396,2591,2592],{},"                    updated.Add(protocolVariable);\n",[396,2594,2595],{"class":398,"line":1717},[396,2596,1720],{},[396,2598,2599],{"class":398,"line":1723},[396,2600,1142],{},[396,2602,2603],{"class":398,"line":1729},[396,2604,1148],{},[396,2606,2607],{"class":398,"line":1734},[396,2608,852],{"emptyLinePlaceholder":851},[396,2610,2611],{"class":398,"line":1740},[396,2612,2613],{},"        return updated;\n",[396,2615,2616],{"class":398,"line":1746},[396,2617,1044],{},[396,2619,2620],{"class":398,"line":1751},[396,2621,852],{"emptyLinePlaceholder":851},[396,2623,2624],{"class":398,"line":1756},[396,2625,2626],{},"    \u002F\u002F The protocol is read-only (a REST API you can only GET), so nothing is ever encoded.\n",[396,2628,2629],{"class":398,"line":1762},[396,2630,2631],{},"    \u002F\u002F For the write direction see the TempSensorProtocol example.\n",[396,2633,2634],{"class":398,"line":1767},[396,2635,2636],{},"    protected override IEnumerable\u003Cstring> Encode(IEnumerable\u003CIProtocolVariable> protocolVariables)\n",[396,2638,2639],{"class":398,"line":1772},[396,2640,978],{},[396,2642,2643],{"class":398,"line":1778},[396,2644,2645],{},"        yield break;\n",[396,2647,2648],{"class":398,"line":1783},[396,2649,1044],{},[396,2651,2652],{"class":398,"line":1789},[396,2653,852],{"emptyLinePlaceholder":851},[396,2655,2656],{"class":398,"line":1794},[396,2657,2658],{},"    \u002F\u002F Values\u003CT>.SetValue does not convert, so the received double is converted to the\n",[396,2660,2661],{"class":398,"line":1800},[396,2662,2663],{},"    \u002F\u002F variable's type here; the example supports the common numeric types.\n",[396,2665,2666],{"class":398,"line":1805},[396,2667,2668],{},"    private static bool TrySetValue(ScalarVariable scalarVariable, double value)\n",[396,2670,2671],{"class":398,"line":1811},[396,2672,978],{},[396,2674,2675],{"class":398,"line":1817},[396,2676,2677],{},"        switch (scalarVariable.Values)\n",[396,2679,2680],{"class":398,"line":1822},[396,2681,990],{},[396,2683,2684],{"class":398,"line":1827},[396,2685,2686],{},"            case Values\u003Cdouble> doubleValues:\n",[396,2688,2690],{"class":398,"line":2689},184,[396,2691,2692],{},"                doubleValues.Value = value;\n",[396,2694,2696],{"class":398,"line":2695},185,[396,2697,2698],{},"                return true;\n",[396,2700,2702],{"class":398,"line":2701},186,[396,2703,2704],{},"            case Values\u003Cfloat> floatValues:\n",[396,2706,2708],{"class":398,"line":2707},187,[396,2709,2710],{},"                floatValues.Value = (float)value;\n",[396,2712,2714],{"class":398,"line":2713},188,[396,2715,2698],{},[396,2717,2719],{"class":398,"line":2718},189,[396,2720,2721],{},"            case Values\u003Cint> intValues:\n",[396,2723,2725],{"class":398,"line":2724},190,[396,2726,2727],{},"                intValues.Value = (int)Math.Round(value);\n",[396,2729,2731],{"class":398,"line":2730},191,[396,2732,2698],{},[396,2734,2736],{"class":398,"line":2735},192,[396,2737,2738],{},"            default:\n",[396,2740,2742],{"class":398,"line":2741},193,[396,2743,2744],{},"                return false;\n",[396,2746,2748],{"class":398,"line":2747},194,[396,2749,1148],{},[396,2751,2753],{"class":398,"line":2752},195,[396,2754,1044],{},[396,2756,2758],{"class":398,"line":2757},196,[396,2759,498],{},[360,2761,2763],{"id":2762},"variable-specification-issjsonvariablespecificationcs","Variable specification: IssJsonVariableSpecification.cs",[302,2765,2766,2767,2770,2771,2774],{},"The addressing is a single item: the JSON field name. The ",[324,2768,2769],{},"Path"," property\nmatches the commParam key 1:1, so the inherited ",[324,2772,2773],{},"ToCommParam()","\nserialization works without an override — the same trick as in the\ntemperature sensor example.",[376,2776,2778],{"className":812,"code":2777,"language":814,"meta":384,"style":384},"using Qenex.QSuite.Protocols.Protocol;\n\nnamespace Qenex.QSuite.Examples.IssJsonProtocol;\n\n\u002F\u002F\u002F \u003Csummary>\n\u002F\u002F\u002F Addressing of one variable: the name of the JSON field whose value the variable receives.\n\u002F\u002F\u002F The property matches the commParam key 1:1 (\"path\"), so the inherited reflection-based\n\u002F\u002F\u002F ToCommParam() round-trips the specification without an override.\n\u002F\u002F\u002F \u003C\u002Fsummary>\npublic class IssJsonVariableSpecification : ProtVariableSpecification\n{\n    public IssJsonVariableSpecification()\n    {\n        Name = \"IssJsonVariableSpecification\";\n    }\n\n    \u002F\u002F\u002F \u003Csummary>Name of the field in the received JSON object, e.g. \"latitude\". Case-sensitive.\u003C\u002Fsummary>\n    public string Path { get; set; } = string.Empty;\n\n    public static IssJsonVariableSpecification Create(string commParams)\n    {\n        var parameters = commParams\n            .Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)\n            .Select(parameter => parameter.Split('=', 2))\n            .Where(parts => parts.Length == 2)\n            .ToDictionary(parts => parts[0].Trim(), parts => parts[1].Trim().Trim('\"'), StringComparer.OrdinalIgnoreCase);\n\n        if (!parameters.TryGetValue(\"path\", out var path) || string.IsNullOrWhiteSpace(path))\n        {\n            throw new ArgumentException(\"Missing mandatory commParam 'path'.\");\n        }\n\n        return new IssJsonVariableSpecification { Path = path };\n    }\n}\n",[324,2779,2780,2784,2788,2792,2796,2800,2805,2810,2815,2819,2824,2828,2833,2837,2842,2846,2850,2855,2860,2864,2869,2873,2878,2883,2888,2893,2898,2902,2907,2911,2916,2920,2924,2929,2933],{"__ignoreMap":384},[396,2781,2782],{"class":398,"line":399},[396,2783,841],{},[396,2785,2786],{"class":398,"line":406},[396,2787,852],{"emptyLinePlaceholder":851},[396,2789,2790],{"class":398,"line":423},[396,2791,1921],{},[396,2793,2794],{"class":398,"line":436},[396,2795,852],{"emptyLinePlaceholder":851},[396,2797,2798],{"class":398,"line":449},[396,2799,866],{},[396,2801,2802],{"class":398,"line":462},[396,2803,2804],{},"\u002F\u002F\u002F Addressing of one variable: the name of the JSON field whose value the variable receives.\n",[396,2806,2807],{"class":398,"line":475},[396,2808,2809],{},"\u002F\u002F\u002F The property matches the commParam key 1:1 (\"path\"), so the inherited reflection-based\n",[396,2811,2812],{"class":398,"line":488},[396,2813,2814],{},"\u002F\u002F\u002F ToCommParam() round-trips the specification without an override.\n",[396,2816,2817],{"class":398,"line":495},[396,2818,898],{},[396,2820,2821],{"class":398,"line":667},[396,2822,2823],{},"public class IssJsonVariableSpecification : ProtVariableSpecification\n",[396,2825,2826],{"class":398,"line":673},[396,2827,403],{},[396,2829,2830],{"class":398,"line":679},[396,2831,2832],{},"    public IssJsonVariableSpecification()\n",[396,2834,2835],{"class":398,"line":685},[396,2836,978],{},[396,2838,2839],{"class":398,"line":753},[396,2840,2841],{},"        Name = \"IssJsonVariableSpecification\";\n",[396,2843,2844],{"class":398,"line":889},[396,2845,1044],{},[396,2847,2848],{"class":398,"line":895},[396,2849,852],{"emptyLinePlaceholder":851},[396,2851,2852],{"class":398,"line":901},[396,2853,2854],{},"    \u002F\u002F\u002F \u003Csummary>Name of the field in the received JSON object, e.g. \"latitude\". Case-sensitive.\u003C\u002Fsummary>\n",[396,2856,2857],{"class":398,"line":907},[396,2858,2859],{},"    public string Path { get; set; } = string.Empty;\n",[396,2861,2862],{"class":398,"line":912},[396,2863,852],{"emptyLinePlaceholder":851},[396,2865,2866],{"class":398,"line":918},[396,2867,2868],{},"    public static IssJsonVariableSpecification Create(string commParams)\n",[396,2870,2871],{"class":398,"line":924},[396,2872,978],{},[396,2874,2875],{"class":398,"line":930},[396,2876,2877],{},"        var parameters = commParams\n",[396,2879,2880],{"class":398,"line":935},[396,2881,2882],{},"            .Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)\n",[396,2884,2885],{"class":398,"line":941},[396,2886,2887],{},"            .Select(parameter => parameter.Split('=', 2))\n",[396,2889,2890],{"class":398,"line":946},[396,2891,2892],{},"            .Where(parts => parts.Length == 2)\n",[396,2894,2895],{"class":398,"line":952},[396,2896,2897],{},"            .ToDictionary(parts => parts[0].Trim(), parts => parts[1].Trim().Trim('\"'), StringComparer.OrdinalIgnoreCase);\n",[396,2899,2900],{"class":398,"line":958},[396,2901,852],{"emptyLinePlaceholder":851},[396,2903,2904],{"class":398,"line":964},[396,2905,2906],{},"        if (!parameters.TryGetValue(\"path\", out var path) || string.IsNullOrWhiteSpace(path))\n",[396,2908,2909],{"class":398,"line":969},[396,2910,990],{},[396,2912,2913],{"class":398,"line":975},[396,2914,2915],{},"            throw new ArgumentException(\"Missing mandatory commParam 'path'.\");\n",[396,2917,2918],{"class":398,"line":981},[396,2919,1148],{},[396,2921,2922],{"class":398,"line":987},[396,2923,852],{"emptyLinePlaceholder":851},[396,2925,2926],{"class":398,"line":993},[396,2927,2928],{},"        return new IssJsonVariableSpecification { Path = path };\n",[396,2930,2931],{"class":398,"line":999},[396,2932,1044],{},[396,2934,2935],{"class":398,"line":1005},[396,2936,498],{},[360,2938,2940],{"id":2939},"how-the-members-relate","How the members relate",[2942,2943,2944,2960],"table",{},[2945,2946,2947],"thead",{},[2948,2949,2950,2954,2957],"tr",{},[2951,2952,2953],"th",{},"Step",[2951,2955,2956],{},"Who",[2951,2958,2959],{},"Members",[2961,2962,2963,2980,2997,3010,3029],"tbody",{},[2948,2964,2965,2969,2972],{},[2966,2967,2968],"td",{},"Adding the driver\u002Fprotocol in the UI",[2966,2970,2971],{},"plugin",[2966,2973,2974,2976,2977],{},[324,2975,765],{},", ",[324,2978,2979],{},"CreateDefaultCommParam",[2948,2981,2982,2985,2988],{},[2966,2983,2984],{},"Assigning a variable",[2966,2986,2987],{},"protocol",[2966,2989,2990,2993,2994],{},[324,2991,2992],{},"CreateProtocolVariable"," → the spec's ",[324,2995,2996],{},"Create",[2948,2998,2999,3002,3005],{},[2966,3000,3001],{},"Runtime start",[2966,3003,3004],{},"driver",[2966,3006,3007,3009],{},[324,3008,769],{}," → start of protocols → the polling loop",[2948,3011,3012,3015,3018],{},[2966,3013,3014],{},"Reading data",[2966,3016,3017],{},"driver → protocol",[2966,3019,3020,3021,3023,3024,3023,3026],{},"HTTP GET → ",[324,3022,1843],{}," → ",[324,3025,1858],{},[324,3027,3028],{},"NotifyValueChangedAsync",[2948,3030,3031,3034,3036],{},[2966,3032,3033],{},"Stop",[2966,3035,3004],{},[2966,3037,3038,3040,3041,3043],{},[324,3039,773],{}," (cancel the loop, stop protocols, dispose ",[324,3042,326],{},")",[360,3045,3047],{"id":3046},"what-the-example-deliberately-leaves-out","What the example deliberately leaves out",[302,3049,3050,3051,3053],{},"To stay as short as possible, three things are intentionally missing —\nand all three are shown in the\n",[368,3052,809],{"href":808},":",[314,3055,3056,3062,3068],{},[317,3057,3058,3061],{},[306,3059,3060],{},"the write path"," (an operator writes a value → the protocol encodes\nit → the driver transmits it),",[317,3063,3064,3067],{},[306,3065,3066],{},"a received-data queue"," with its own consumer — a necessity wherever\ndata arrives faster than it is processed,",[317,3069,3070,3073,3074,3077],{},[306,3071,3072],{},"a communication direction in commParams"," (",[324,3075,3076],{},"direction=\"read\u002Fwrite\"",")\n— everything here is read-only, so the parameter is not needed.",[302,3079,3080,3081,3083],{},"One side note: apart from the name, there is nothing ISS-specific in the\nprotocol. The same code would decode any flat JSON object — just change\nthe URL in the driver and the ",[324,3082,1862],{}," values.",[360,3085,3087],{"id":3086},"next","Next",[302,3089,3090,3092,3093,520,3096,793],{},[368,3091,237],{"href":808}," — the full\npattern including writes and the queue. The individual skeletons:\n",[368,3094,217],{"href":3095},"\u002Fcustom-extensions\u002Fcustom-driver",[368,3097,221],{"href":3098},"\u002Fcustom-extensions\u002Fcustom-protocol",[3100,3101,3102],"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);}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .s7hpK, html code.shiki .s7hpK{--shiki-default:#B31D28;--shiki-default-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic}",{"title":384,"searchDepth":423,"depth":423,"links":3104},[3105,3106,3107,3108,3109,3110,3111,3112,3113],{"id":362,"depth":406,"text":363},{"id":508,"depth":406,"text":509},{"id":593,"depth":406,"text":594},{"id":758,"depth":406,"text":759},{"id":1832,"depth":406,"text":1833},{"id":2762,"depth":406,"text":2763},{"id":2939,"depth":406,"text":2940},{"id":3046,"depth":406,"text":3047},{"id":3086,"depth":406,"text":3087},"The smallest working custom driver + protocol pair — the live position of the International Space Station from a public REST API.","md",{},{"title":229,"description":3114},"3zhHxrvV-sBE2lB_2qKjj_UCHOtnfrGegTtc3zvgA0Y",[3120,3121],{"title":225,"path":226,"stem":227,"navTitle":225,"children":-1},{"title":234,"path":235,"stem":236,"navTitle":237,"children":-1},1787589608768]