github.com Open in urlscan Pro
140.82.112.4  Public Scan

Submitted URL: https://www.automation-gateway.com/
Effective URL: https://github.com/vogler75/automation-gateway/
Submission: On July 23 via automatic, source certstream-suspicious — Scanned from US

Form analysis 3 forms found in the DOM

GET

<form id="query-builder-test-form" action="" accept-charset="UTF-8" method="get">
  <query-builder data-target="qbsearch-input.queryBuilder" id="query-builder-query-builder-test" data-filter-key=":" data-view-component="true" class="QueryBuilder search-query-builder" data-min-width="300" data-catalyst="">
    <div class="FormControl FormControl--fullWidth">
      <label id="query-builder-test-label" for="query-builder-test" class="FormControl-label sr-only"> Search </label>
      <div class="QueryBuilder-StyledInput width-fit " data-target="query-builder.styledInput">
        <span id="query-builder-test-leadingvisual-wrap" class="FormControl-input-leadingVisualWrap QueryBuilder-leadingVisualWrap">
          <svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-search FormControl-input-leadingVisual">
            <path d="M10.68 11.74a6 6 0 0 1-7.922-8.982 6 6 0 0 1 8.982 7.922l3.04 3.04a.749.749 0 0 1-.326 1.275.749.749 0 0 1-.734-.215ZM11.5 7a4.499 4.499 0 1 0-8.997 0A4.499 4.499 0 0 0 11.5 7Z"></path>
          </svg>
        </span>
        <div data-target="query-builder.styledInputContainer" class="QueryBuilder-StyledInputContainer">
          <div aria-hidden="true" class="QueryBuilder-StyledInputContent" data-target="query-builder.styledInputContent"></div>
          <div class="QueryBuilder-InputWrapper">
            <div aria-hidden="true" class="QueryBuilder-Sizer" data-target="query-builder.sizer"><span></span></div>
            <input id="query-builder-test" name="query-builder-test" value="" autocomplete="off" type="text" role="combobox" spellcheck="false" aria-expanded="false" aria-describedby="validation-03cd9b9f-1f9a-4552-95fc-008b2c93d1d9"
              data-target="query-builder.input" data-action="
          input:query-builder#inputChange
          blur:query-builder#inputBlur
          keydown:query-builder#inputKeydown
          focus:query-builder#inputFocus
        " data-view-component="true" class="FormControl-input QueryBuilder-Input FormControl-medium" aria-controls="query-builder-test-results" aria-autocomplete="list" aria-haspopup="listbox" style="width: 300px;">
          </div>
        </div>
        <span class="sr-only" id="query-builder-test-clear">Clear</span>
        <button role="button" id="query-builder-test-clear-button" aria-labelledby="query-builder-test-clear query-builder-test-label" data-target="query-builder.clearButton" data-action="
                click:query-builder#clear
                focus:query-builder#clearButtonFocus
                blur:query-builder#clearButtonBlur
              " variant="small" hidden="" type="button" data-view-component="true" class="Button Button--iconOnly Button--invisible Button--medium mr-1 px-2 py-0 d-flex flex-items-center rounded-1 color-fg-muted"> <svg aria-hidden="true" height="16"
            viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-x-circle-fill Button-visual">
            <path
              d="M2.343 13.657A8 8 0 1 1 13.658 2.343 8 8 0 0 1 2.343 13.657ZM6.03 4.97a.751.751 0 0 0-1.042.018.751.751 0 0 0-.018 1.042L6.94 8 4.97 9.97a.749.749 0 0 0 .326 1.275.749.749 0 0 0 .734-.215L8 9.06l1.97 1.97a.749.749 0 0 0 1.275-.326.749.749 0 0 0-.215-.734L9.06 8l1.97-1.97a.749.749 0 0 0-.326-1.275.749.749 0 0 0-.734.215L8 6.94Z">
            </path>
          </svg>
        </button>
      </div>
      <template id="search-icon">
        <svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-search">
          <path d="M10.68 11.74a6 6 0 0 1-7.922-8.982 6 6 0 0 1 8.982 7.922l3.04 3.04a.749.749 0 0 1-.326 1.275.749.749 0 0 1-.734-.215ZM11.5 7a4.499 4.499 0 1 0-8.997 0A4.499 4.499 0 0 0 11.5 7Z"></path>
        </svg>
      </template>
      <template id="code-icon">
        <svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-code">
          <path
            d="m11.28 3.22 4.25 4.25a.75.75 0 0 1 0 1.06l-4.25 4.25a.749.749 0 0 1-1.275-.326.749.749 0 0 1 .215-.734L13.94 8l-3.72-3.72a.749.749 0 0 1 .326-1.275.749.749 0 0 1 .734.215Zm-6.56 0a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042L2.06 8l3.72 3.72a.749.749 0 0 1-.326 1.275.749.749 0 0 1-.734-.215L.47 8.53a.75.75 0 0 1 0-1.06Z">
          </path>
        </svg>
      </template>
      <template id="file-code-icon">
        <svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-file-code">
          <path
            d="M4 1.75C4 .784 4.784 0 5.75 0h5.586c.464 0 .909.184 1.237.513l2.914 2.914c.329.328.513.773.513 1.237v8.586A1.75 1.75 0 0 1 14.25 15h-9a.75.75 0 0 1 0-1.5h9a.25.25 0 0 0 .25-.25V6h-2.75A1.75 1.75 0 0 1 10 4.25V1.5H5.75a.25.25 0 0 0-.25.25v2.5a.75.75 0 0 1-1.5 0Zm1.72 4.97a.75.75 0 0 1 1.06 0l2 2a.75.75 0 0 1 0 1.06l-2 2a.749.749 0 0 1-1.275-.326.749.749 0 0 1 .215-.734l1.47-1.47-1.47-1.47a.75.75 0 0 1 0-1.06ZM3.28 7.78 1.81 9.25l1.47 1.47a.751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018l-2-2a.75.75 0 0 1 0-1.06l2-2a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042Zm8.22-6.218V4.25c0 .138.112.25.25.25h2.688l-.011-.013-2.914-2.914-.013-.011Z">
          </path>
        </svg>
      </template>
      <template id="history-icon">
        <svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-history">
          <path
            d="m.427 1.927 1.215 1.215a8.002 8.002 0 1 1-1.6 5.685.75.75 0 1 1 1.493-.154 6.5 6.5 0 1 0 1.18-4.458l1.358 1.358A.25.25 0 0 1 3.896 6H.25A.25.25 0 0 1 0 5.75V2.104a.25.25 0 0 1 .427-.177ZM7.75 4a.75.75 0 0 1 .75.75v2.992l2.028.812a.75.75 0 0 1-.557 1.392l-2.5-1A.751.751 0 0 1 7 8.25v-3.5A.75.75 0 0 1 7.75 4Z">
          </path>
        </svg>
      </template>
      <template id="repo-icon">
        <svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-repo">
          <path
            d="M2 2.5A2.5 2.5 0 0 1 4.5 0h8.75a.75.75 0 0 1 .75.75v12.5a.75.75 0 0 1-.75.75h-2.5a.75.75 0 0 1 0-1.5h1.75v-2h-8a1 1 0 0 0-.714 1.7.75.75 0 1 1-1.072 1.05A2.495 2.495 0 0 1 2 11.5Zm10.5-1h-8a1 1 0 0 0-1 1v6.708A2.486 2.486 0 0 1 4.5 9h8ZM5 12.25a.25.25 0 0 1 .25-.25h3.5a.25.25 0 0 1 .25.25v3.25a.25.25 0 0 1-.4.2l-1.45-1.087a.249.249 0 0 0-.3 0L5.4 15.7a.25.25 0 0 1-.4-.2Z">
          </path>
        </svg>
      </template>
      <template id="bookmark-icon">
        <svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-bookmark">
          <path
            d="M3 2.75C3 1.784 3.784 1 4.75 1h6.5c.966 0 1.75.784 1.75 1.75v11.5a.75.75 0 0 1-1.227.579L8 11.722l-3.773 3.107A.751.751 0 0 1 3 14.25Zm1.75-.25a.25.25 0 0 0-.25.25v9.91l3.023-2.489a.75.75 0 0 1 .954 0l3.023 2.49V2.75a.25.25 0 0 0-.25-.25Z">
          </path>
        </svg>
      </template>
      <template id="plus-circle-icon">
        <svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-plus-circle">
          <path d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0ZM1.5 8a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0Zm7.25-3.25v2.5h2.5a.75.75 0 0 1 0 1.5h-2.5v2.5a.75.75 0 0 1-1.5 0v-2.5h-2.5a.75.75 0 0 1 0-1.5h2.5v-2.5a.75.75 0 0 1 1.5 0Z"></path>
        </svg>
      </template>
      <template id="circle-icon">
        <svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-dot-fill">
          <path d="M8 4a4 4 0 1 1 0 8 4 4 0 0 1 0-8Z"></path>
        </svg>
      </template>
      <template id="trash-icon">
        <svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-trash">
          <path
            d="M11 1.75V3h2.25a.75.75 0 0 1 0 1.5H2.75a.75.75 0 0 1 0-1.5H5V1.75C5 .784 5.784 0 6.75 0h2.5C10.216 0 11 .784 11 1.75ZM4.496 6.675l.66 6.6a.25.25 0 0 0 .249.225h5.19a.25.25 0 0 0 .249-.225l.66-6.6a.75.75 0 0 1 1.492.149l-.66 6.6A1.748 1.748 0 0 1 10.595 15h-5.19a1.75 1.75 0 0 1-1.741-1.575l-.66-6.6a.75.75 0 1 1 1.492-.15ZM6.5 1.75V3h3V1.75a.25.25 0 0 0-.25-.25h-2.5a.25.25 0 0 0-.25.25Z">
          </path>
        </svg>
      </template>
      <template id="team-icon">
        <svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-people">
          <path
            d="M2 5.5a3.5 3.5 0 1 1 5.898 2.549 5.508 5.508 0 0 1 3.034 4.084.75.75 0 1 1-1.482.235 4 4 0 0 0-7.9 0 .75.75 0 0 1-1.482-.236A5.507 5.507 0 0 1 3.102 8.05 3.493 3.493 0 0 1 2 5.5ZM11 4a3.001 3.001 0 0 1 2.22 5.018 5.01 5.01 0 0 1 2.56 3.012.749.749 0 0 1-.885.954.752.752 0 0 1-.549-.514 3.507 3.507 0 0 0-2.522-2.372.75.75 0 0 1-.574-.73v-.352a.75.75 0 0 1 .416-.672A1.5 1.5 0 0 0 11 5.5.75.75 0 0 1 11 4Zm-5.5-.5a2 2 0 1 0-.001 3.999A2 2 0 0 0 5.5 3.5Z">
          </path>
        </svg>
      </template>
      <template id="project-icon">
        <svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-project">
          <path
            d="M1.75 0h12.5C15.216 0 16 .784 16 1.75v12.5A1.75 1.75 0 0 1 14.25 16H1.75A1.75 1.75 0 0 1 0 14.25V1.75C0 .784.784 0 1.75 0ZM1.5 1.75v12.5c0 .138.112.25.25.25h12.5a.25.25 0 0 0 .25-.25V1.75a.25.25 0 0 0-.25-.25H1.75a.25.25 0 0 0-.25.25ZM11.75 3a.75.75 0 0 1 .75.75v7.5a.75.75 0 0 1-1.5 0v-7.5a.75.75 0 0 1 .75-.75Zm-8.25.75a.75.75 0 0 1 1.5 0v5.5a.75.75 0 0 1-1.5 0ZM8 3a.75.75 0 0 1 .75.75v3.5a.75.75 0 0 1-1.5 0v-3.5A.75.75 0 0 1 8 3Z">
          </path>
        </svg>
      </template>
      <template id="pencil-icon">
        <svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-pencil">
          <path
            d="M11.013 1.427a1.75 1.75 0 0 1 2.474 0l1.086 1.086a1.75 1.75 0 0 1 0 2.474l-8.61 8.61c-.21.21-.47.364-.756.445l-3.251.93a.75.75 0 0 1-.927-.928l.929-3.25c.081-.286.235-.547.445-.758l8.61-8.61Zm.176 4.823L9.75 4.81l-6.286 6.287a.253.253 0 0 0-.064.108l-.558 1.953 1.953-.558a.253.253 0 0 0 .108-.064Zm1.238-3.763a.25.25 0 0 0-.354 0L10.811 3.75l1.439 1.44 1.263-1.263a.25.25 0 0 0 0-.354Z">
          </path>
        </svg>
      </template>
      <template id="copilot-icon">
        <svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-copilot">
          <path
            d="M7.998 15.035c-4.562 0-7.873-2.914-7.998-3.749V9.338c.085-.628.677-1.686 1.588-2.065.013-.07.024-.143.036-.218.029-.183.06-.384.126-.612-.201-.508-.254-1.084-.254-1.656 0-.87.128-1.769.693-2.484.579-.733 1.494-1.124 2.724-1.261 1.206-.134 2.262.034 2.944.765.05.053.096.108.139.165.044-.057.094-.112.143-.165.682-.731 1.738-.899 2.944-.765 1.23.137 2.145.528 2.724 1.261.566.715.693 1.614.693 2.484 0 .572-.053 1.148-.254 1.656.066.228.098.429.126.612.012.076.024.148.037.218.924.385 1.522 1.471 1.591 2.095v1.872c0 .766-3.351 3.795-8.002 3.795Zm0-1.485c2.28 0 4.584-1.11 5.002-1.433V7.862l-.023-.116c-.49.21-1.075.291-1.727.291-1.146 0-2.059-.327-2.71-.991A3.222 3.222 0 0 1 8 6.303a3.24 3.24 0 0 1-.544.743c-.65.664-1.563.991-2.71.991-.652 0-1.236-.081-1.727-.291l-.023.116v4.255c.419.323 2.722 1.433 5.002 1.433ZM6.762 2.83c-.193-.206-.637-.413-1.682-.297-1.019.113-1.479.404-1.713.7-.247.312-.369.789-.369 1.554 0 .793.129 1.171.308 1.371.162.181.519.379 1.442.379.853 0 1.339-.235 1.638-.54.315-.322.527-.827.617-1.553.117-.935-.037-1.395-.241-1.614Zm4.155-.297c-1.044-.116-1.488.091-1.681.297-.204.219-.359.679-.242 1.614.091.726.303 1.231.618 1.553.299.305.784.54 1.638.54.922 0 1.28-.198 1.442-.379.179-.2.308-.578.308-1.371 0-.765-.123-1.242-.37-1.554-.233-.296-.693-.587-1.713-.7Z">
          </path>
          <path d="M6.25 9.037a.75.75 0 0 1 .75.75v1.501a.75.75 0 0 1-1.5 0V9.787a.75.75 0 0 1 .75-.75Zm4.25.75v1.501a.75.75 0 0 1-1.5 0V9.787a.75.75 0 0 1 1.5 0Z"></path>
        </svg>
      </template>
      <template id="copilot-error-icon">
        <svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-copilot-error">
          <path
            d="M16 11.24c0 .112-.072.274-.21.467L13 9.688V7.862l-.023-.116c-.49.21-1.075.291-1.727.291-.198 0-.388-.009-.571-.029L6.833 5.226a4.01 4.01 0 0 0 .17-.782c.117-.935-.037-1.395-.241-1.614-.193-.206-.637-.413-1.682-.297-.683.076-1.115.231-1.395.415l-1.257-.91c.579-.564 1.413-.877 2.485-.996 1.206-.134 2.262.034 2.944.765.05.053.096.108.139.165.044-.057.094-.112.143-.165.682-.731 1.738-.899 2.944-.765 1.23.137 2.145.528 2.724 1.261.566.715.693 1.614.693 2.484 0 .572-.053 1.148-.254 1.656.066.228.098.429.126.612.012.076.024.148.037.218.924.385 1.522 1.471 1.591 2.095Zm-5.083-8.707c-1.044-.116-1.488.091-1.681.297-.204.219-.359.679-.242 1.614.091.726.303 1.231.618 1.553.299.305.784.54 1.638.54.922 0 1.28-.198 1.442-.379.179-.2.308-.578.308-1.371 0-.765-.123-1.242-.37-1.554-.233-.296-.693-.587-1.713-.7Zm2.511 11.074c-1.393.776-3.272 1.428-5.43 1.428-4.562 0-7.873-2.914-7.998-3.749V9.338c.085-.628.677-1.686 1.588-2.065.013-.07.024-.143.036-.218.029-.183.06-.384.126-.612-.18-.455-.241-.963-.252-1.475L.31 4.107A.747.747 0 0 1 0 3.509V3.49a.748.748 0 0 1 .625-.73c.156-.026.306.047.435.139l14.667 10.578a.592.592 0 0 1 .227.264.752.752 0 0 1 .046.249v.022a.75.75 0 0 1-1.19.596Zm-1.367-.991L5.635 7.964a5.128 5.128 0 0 1-.889.073c-.652 0-1.236-.081-1.727-.291l-.023.116v4.255c.419.323 2.722 1.433 5.002 1.433 1.539 0 3.089-.505 4.063-.934Z">
          </path>
        </svg>
      </template>
      <template id="workflow-icon">
        <svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-workflow">
          <path
            d="M0 1.75C0 .784.784 0 1.75 0h3.5C6.216 0 7 .784 7 1.75v3.5A1.75 1.75 0 0 1 5.25 7H4v4a1 1 0 0 0 1 1h4v-1.25C9 9.784 9.784 9 10.75 9h3.5c.966 0 1.75.784 1.75 1.75v3.5A1.75 1.75 0 0 1 14.25 16h-3.5A1.75 1.75 0 0 1 9 14.25v-.75H5A2.5 2.5 0 0 1 2.5 11V7h-.75A1.75 1.75 0 0 1 0 5.25Zm1.75-.25a.25.25 0 0 0-.25.25v3.5c0 .138.112.25.25.25h3.5a.25.25 0 0 0 .25-.25v-3.5a.25.25 0 0 0-.25-.25Zm9 9a.25.25 0 0 0-.25.25v3.5c0 .138.112.25.25.25h3.5a.25.25 0 0 0 .25-.25v-3.5a.25.25 0 0 0-.25-.25Z">
          </path>
        </svg>
      </template>
      <template id="book-icon">
        <svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-book">
          <path
            d="M0 1.75A.75.75 0 0 1 .75 1h4.253c1.227 0 2.317.59 3 1.501A3.743 3.743 0 0 1 11.006 1h4.245a.75.75 0 0 1 .75.75v10.5a.75.75 0 0 1-.75.75h-4.507a2.25 2.25 0 0 0-1.591.659l-.622.621a.75.75 0 0 1-1.06 0l-.622-.621A2.25 2.25 0 0 0 5.258 13H.75a.75.75 0 0 1-.75-.75Zm7.251 10.324.004-5.073-.002-2.253A2.25 2.25 0 0 0 5.003 2.5H1.5v9h3.757a3.75 3.75 0 0 1 1.994.574ZM8.755 4.75l-.004 7.322a3.752 3.752 0 0 1 1.992-.572H14.5v-9h-3.495a2.25 2.25 0 0 0-2.25 2.25Z">
          </path>
        </svg>
      </template>
      <template id="code-review-icon">
        <svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-code-review">
          <path
            d="M1.75 1h12.5c.966 0 1.75.784 1.75 1.75v8.5A1.75 1.75 0 0 1 14.25 13H8.061l-2.574 2.573A1.458 1.458 0 0 1 3 14.543V13H1.75A1.75 1.75 0 0 1 0 11.25v-8.5C0 1.784.784 1 1.75 1ZM1.5 2.75v8.5c0 .138.112.25.25.25h2a.75.75 0 0 1 .75.75v2.19l2.72-2.72a.749.749 0 0 1 .53-.22h6.5a.25.25 0 0 0 .25-.25v-8.5a.25.25 0 0 0-.25-.25H1.75a.25.25 0 0 0-.25.25Zm5.28 1.72a.75.75 0 0 1 0 1.06L5.31 7l1.47 1.47a.751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018l-2-2a.75.75 0 0 1 0-1.06l2-2a.75.75 0 0 1 1.06 0Zm2.44 0a.75.75 0 0 1 1.06 0l2 2a.75.75 0 0 1 0 1.06l-2 2a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042L10.69 7 9.22 5.53a.75.75 0 0 1 0-1.06Z">
          </path>
        </svg>
      </template>
      <template id="codespaces-icon">
        <svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-codespaces">
          <path
            d="M0 11.25c0-.966.784-1.75 1.75-1.75h12.5c.966 0 1.75.784 1.75 1.75v3A1.75 1.75 0 0 1 14.25 16H1.75A1.75 1.75 0 0 1 0 14.25Zm2-9.5C2 .784 2.784 0 3.75 0h8.5C13.216 0 14 .784 14 1.75v5a1.75 1.75 0 0 1-1.75 1.75h-8.5A1.75 1.75 0 0 1 2 6.75Zm1.75-.25a.25.25 0 0 0-.25.25v5c0 .138.112.25.25.25h8.5a.25.25 0 0 0 .25-.25v-5a.25.25 0 0 0-.25-.25Zm-2 9.5a.25.25 0 0 0-.25.25v3c0 .138.112.25.25.25h12.5a.25.25 0 0 0 .25-.25v-3a.25.25 0 0 0-.25-.25Z">
          </path>
          <path d="M7 12.75a.75.75 0 0 1 .75-.75h4.5a.75.75 0 0 1 0 1.5h-4.5a.75.75 0 0 1-.75-.75Zm-4 0a.75.75 0 0 1 .75-.75h.5a.75.75 0 0 1 0 1.5h-.5a.75.75 0 0 1-.75-.75Z"></path>
        </svg>
      </template>
      <template id="comment-icon">
        <svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-comment">
          <path
            d="M1 2.75C1 1.784 1.784 1 2.75 1h10.5c.966 0 1.75.784 1.75 1.75v7.5A1.75 1.75 0 0 1 13.25 12H9.06l-2.573 2.573A1.458 1.458 0 0 1 4 13.543V12H2.75A1.75 1.75 0 0 1 1 10.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h2a.75.75 0 0 1 .75.75v2.19l2.72-2.72a.749.749 0 0 1 .53-.22h4.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z">
          </path>
        </svg>
      </template>
      <template id="comment-discussion-icon">
        <svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-comment-discussion">
          <path
            d="M1.75 1h8.5c.966 0 1.75.784 1.75 1.75v5.5A1.75 1.75 0 0 1 10.25 10H7.061l-2.574 2.573A1.458 1.458 0 0 1 2 11.543V10h-.25A1.75 1.75 0 0 1 0 8.25v-5.5C0 1.784.784 1 1.75 1ZM1.5 2.75v5.5c0 .138.112.25.25.25h1a.75.75 0 0 1 .75.75v2.19l2.72-2.72a.749.749 0 0 1 .53-.22h3.5a.25.25 0 0 0 .25-.25v-5.5a.25.25 0 0 0-.25-.25h-8.5a.25.25 0 0 0-.25.25Zm13 2a.25.25 0 0 0-.25-.25h-.5a.75.75 0 0 1 0-1.5h.5c.966 0 1.75.784 1.75 1.75v5.5A1.75 1.75 0 0 1 14.25 12H14v1.543a1.458 1.458 0 0 1-2.487 1.03L9.22 12.28a.749.749 0 0 1 .326-1.275.749.749 0 0 1 .734.215l2.22 2.22v-2.19a.75.75 0 0 1 .75-.75h1a.25.25 0 0 0 .25-.25Z">
          </path>
        </svg>
      </template>
      <template id="organization-icon">
        <svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-organization">
          <path
            d="M1.75 16A1.75 1.75 0 0 1 0 14.25V1.75C0 .784.784 0 1.75 0h8.5C11.216 0 12 .784 12 1.75v12.5c0 .085-.006.168-.018.25h2.268a.25.25 0 0 0 .25-.25V8.285a.25.25 0 0 0-.111-.208l-1.055-.703a.749.749 0 1 1 .832-1.248l1.055.703c.487.325.779.871.779 1.456v5.965A1.75 1.75 0 0 1 14.25 16h-3.5a.766.766 0 0 1-.197-.026c-.099.017-.2.026-.303.026h-3a.75.75 0 0 1-.75-.75V14h-1v1.25a.75.75 0 0 1-.75.75Zm-.25-1.75c0 .138.112.25.25.25H4v-1.25a.75.75 0 0 1 .75-.75h2.5a.75.75 0 0 1 .75.75v1.25h2.25a.25.25 0 0 0 .25-.25V1.75a.25.25 0 0 0-.25-.25h-8.5a.25.25 0 0 0-.25.25ZM3.75 6h.5a.75.75 0 0 1 0 1.5h-.5a.75.75 0 0 1 0-1.5ZM3 3.75A.75.75 0 0 1 3.75 3h.5a.75.75 0 0 1 0 1.5h-.5A.75.75 0 0 1 3 3.75Zm4 3A.75.75 0 0 1 7.75 6h.5a.75.75 0 0 1 0 1.5h-.5A.75.75 0 0 1 7 6.75ZM7.75 3h.5a.75.75 0 0 1 0 1.5h-.5a.75.75 0 0 1 0-1.5ZM3 9.75A.75.75 0 0 1 3.75 9h.5a.75.75 0 0 1 0 1.5h-.5A.75.75 0 0 1 3 9.75ZM7.75 9h.5a.75.75 0 0 1 0 1.5h-.5a.75.75 0 0 1 0-1.5Z">
          </path>
        </svg>
      </template>
      <template id="rocket-icon">
        <svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-rocket">
          <path
            d="M14.064 0h.186C15.216 0 16 .784 16 1.75v.186a8.752 8.752 0 0 1-2.564 6.186l-.458.459c-.314.314-.641.616-.979.904v3.207c0 .608-.315 1.172-.833 1.49l-2.774 1.707a.749.749 0 0 1-1.11-.418l-.954-3.102a1.214 1.214 0 0 1-.145-.125L3.754 9.816a1.218 1.218 0 0 1-.124-.145L.528 8.717a.749.749 0 0 1-.418-1.11l1.71-2.774A1.748 1.748 0 0 1 3.31 4h3.204c.288-.338.59-.665.904-.979l.459-.458A8.749 8.749 0 0 1 14.064 0ZM8.938 3.623h-.002l-.458.458c-.76.76-1.437 1.598-2.02 2.5l-1.5 2.317 2.143 2.143 2.317-1.5c.902-.583 1.74-1.26 2.499-2.02l.459-.458a7.25 7.25 0 0 0 2.123-5.127V1.75a.25.25 0 0 0-.25-.25h-.186a7.249 7.249 0 0 0-5.125 2.123ZM3.56 14.56c-.732.732-2.334 1.045-3.005 1.148a.234.234 0 0 1-.201-.064.234.234 0 0 1-.064-.201c.103-.671.416-2.273 1.15-3.003a1.502 1.502 0 1 1 2.12 2.12Zm6.94-3.935c-.088.06-.177.118-.266.175l-2.35 1.521.548 1.783 1.949-1.2a.25.25 0 0 0 .119-.213ZM3.678 8.116 5.2 5.766c.058-.09.117-.178.176-.266H3.309a.25.25 0 0 0-.213.119l-1.2 1.95ZM12 5a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z">
          </path>
        </svg>
      </template>
      <template id="shield-check-icon">
        <svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-shield-check">
          <path
            d="m8.533.133 5.25 1.68A1.75 1.75 0 0 1 15 3.48V7c0 1.566-.32 3.182-1.303 4.682-.983 1.498-2.585 2.813-5.032 3.855a1.697 1.697 0 0 1-1.33 0c-2.447-1.042-4.049-2.357-5.032-3.855C1.32 10.182 1 8.566 1 7V3.48a1.75 1.75 0 0 1 1.217-1.667l5.25-1.68a1.748 1.748 0 0 1 1.066 0Zm-.61 1.429.001.001-5.25 1.68a.251.251 0 0 0-.174.237V7c0 1.36.275 2.666 1.057 3.859.784 1.194 2.121 2.342 4.366 3.298a.196.196 0 0 0 .154 0c2.245-.957 3.582-2.103 4.366-3.297C13.225 9.666 13.5 8.358 13.5 7V3.48a.25.25 0 0 0-.174-.238l-5.25-1.68a.25.25 0 0 0-.153 0ZM11.28 6.28l-3.5 3.5a.75.75 0 0 1-1.06 0l-1.5-1.5a.749.749 0 0 1 .326-1.275.749.749 0 0 1 .734.215l.97.97 2.97-2.97a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042Z">
          </path>
        </svg>
      </template>
      <template id="heart-icon">
        <svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-heart">
          <path
            d="m8 14.25.345.666a.75.75 0 0 1-.69 0l-.008-.004-.018-.01a7.152 7.152 0 0 1-.31-.17 22.055 22.055 0 0 1-3.434-2.414C2.045 10.731 0 8.35 0 5.5 0 2.836 2.086 1 4.25 1 5.797 1 7.153 1.802 8 3.02 8.847 1.802 10.203 1 11.75 1 13.914 1 16 2.836 16 5.5c0 2.85-2.045 5.231-3.885 6.818a22.066 22.066 0 0 1-3.744 2.584l-.018.01-.006.003h-.002ZM4.25 2.5c-1.336 0-2.75 1.164-2.75 3 0 2.15 1.58 4.144 3.365 5.682A20.58 20.58 0 0 0 8 13.393a20.58 20.58 0 0 0 3.135-2.211C12.92 9.644 14.5 7.65 14.5 5.5c0-1.836-1.414-3-2.75-3-1.373 0-2.609.986-3.029 2.456a.749.749 0 0 1-1.442 0C6.859 3.486 5.623 2.5 4.25 2.5Z">
          </path>
        </svg>
      </template>
      <template id="server-icon">
        <svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-server">
          <path
            d="M1.75 1h12.5c.966 0 1.75.784 1.75 1.75v4c0 .372-.116.717-.314 1 .198.283.314.628.314 1v4a1.75 1.75 0 0 1-1.75 1.75H1.75A1.75 1.75 0 0 1 0 12.75v-4c0-.358.109-.707.314-1a1.739 1.739 0 0 1-.314-1v-4C0 1.784.784 1 1.75 1ZM1.5 2.75v4c0 .138.112.25.25.25h12.5a.25.25 0 0 0 .25-.25v-4a.25.25 0 0 0-.25-.25H1.75a.25.25 0 0 0-.25.25Zm.25 5.75a.25.25 0 0 0-.25.25v4c0 .138.112.25.25.25h12.5a.25.25 0 0 0 .25-.25v-4a.25.25 0 0 0-.25-.25ZM7 4.75A.75.75 0 0 1 7.75 4h4.5a.75.75 0 0 1 0 1.5h-4.5A.75.75 0 0 1 7 4.75ZM7.75 10h4.5a.75.75 0 0 1 0 1.5h-4.5a.75.75 0 0 1 0-1.5ZM3 4.75A.75.75 0 0 1 3.75 4h.5a.75.75 0 0 1 0 1.5h-.5A.75.75 0 0 1 3 4.75ZM3.75 10h.5a.75.75 0 0 1 0 1.5h-.5a.75.75 0 0 1 0-1.5Z">
          </path>
        </svg>
      </template>
      <template id="globe-icon">
        <svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-globe">
          <path
            d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0ZM5.78 8.75a9.64 9.64 0 0 0 1.363 4.177c.255.426.542.832.857 1.215.245-.296.551-.705.857-1.215A9.64 9.64 0 0 0 10.22 8.75Zm4.44-1.5a9.64 9.64 0 0 0-1.363-4.177c-.307-.51-.612-.919-.857-1.215a9.927 9.927 0 0 0-.857 1.215A9.64 9.64 0 0 0 5.78 7.25Zm-5.944 1.5H1.543a6.507 6.507 0 0 0 4.666 5.5c-.123-.181-.24-.365-.352-.552-.715-1.192-1.437-2.874-1.581-4.948Zm-2.733-1.5h2.733c.144-2.074.866-3.756 1.58-4.948.12-.197.237-.381.353-.552a6.507 6.507 0 0 0-4.666 5.5Zm10.181 1.5c-.144 2.074-.866 3.756-1.58 4.948-.12.197-.237.381-.353.552a6.507 6.507 0 0 0 4.666-5.5Zm2.733-1.5a6.507 6.507 0 0 0-4.666-5.5c.123.181.24.365.353.552.714 1.192 1.436 2.874 1.58 4.948Z">
          </path>
        </svg>
      </template>
      <template id="issue-opened-icon">
        <svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-issue-opened">
          <path d="M8 9.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3Z"></path>
          <path d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0ZM1.5 8a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0Z"></path>
        </svg>
      </template>
      <template id="device-mobile-icon">
        <svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-device-mobile">
          <path
            d="M3.75 0h8.5C13.216 0 14 .784 14 1.75v12.5A1.75 1.75 0 0 1 12.25 16h-8.5A1.75 1.75 0 0 1 2 14.25V1.75C2 .784 2.784 0 3.75 0ZM3.5 1.75v12.5c0 .138.112.25.25.25h8.5a.25.25 0 0 0 .25-.25V1.75a.25.25 0 0 0-.25-.25h-8.5a.25.25 0 0 0-.25.25ZM8 13a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z">
          </path>
        </svg>
      </template>
      <template id="package-icon">
        <svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-package">
          <path
            d="m8.878.392 5.25 3.045c.54.314.872.89.872 1.514v6.098a1.75 1.75 0 0 1-.872 1.514l-5.25 3.045a1.75 1.75 0 0 1-1.756 0l-5.25-3.045A1.75 1.75 0 0 1 1 11.049V4.951c0-.624.332-1.201.872-1.514L7.122.392a1.75 1.75 0 0 1 1.756 0ZM7.875 1.69l-4.63 2.685L8 7.133l4.755-2.758-4.63-2.685a.248.248 0 0 0-.25 0ZM2.5 5.677v5.372c0 .09.047.171.125.216l4.625 2.683V8.432Zm6.25 8.271 4.625-2.683a.25.25 0 0 0 .125-.216V5.677L8.75 8.432Z">
          </path>
        </svg>
      </template>
      <template id="credit-card-icon">
        <svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-credit-card">
          <path d="M10.75 9a.75.75 0 0 0 0 1.5h1.5a.75.75 0 0 0 0-1.5h-1.5Z"></path>
          <path
            d="M0 3.75C0 2.784.784 2 1.75 2h12.5c.966 0 1.75.784 1.75 1.75v8.5A1.75 1.75 0 0 1 14.25 14H1.75A1.75 1.75 0 0 1 0 12.25ZM14.5 6.5h-13v5.75c0 .138.112.25.25.25h12.5a.25.25 0 0 0 .25-.25Zm0-2.75a.25.25 0 0 0-.25-.25H1.75a.25.25 0 0 0-.25.25V5h13Z">
          </path>
        </svg>
      </template>
      <template id="play-icon">
        <svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-play">
          <path d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0ZM1.5 8a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0Zm4.879-2.773 4.264 2.559a.25.25 0 0 1 0 .428l-4.264 2.559A.25.25 0 0 1 6 10.559V5.442a.25.25 0 0 1 .379-.215Z"></path>
        </svg>
      </template>
      <template id="gift-icon">
        <svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-gift">
          <path
            d="M2 2.75A2.75 2.75 0 0 1 4.75 0c.983 0 1.873.42 2.57 1.232.268.318.497.668.68 1.042.183-.375.411-.725.68-1.044C9.376.42 10.266 0 11.25 0a2.75 2.75 0 0 1 2.45 4h.55c.966 0 1.75.784 1.75 1.75v2c0 .698-.409 1.301-1 1.582v4.918A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25V9.332C.409 9.05 0 8.448 0 7.75v-2C0 4.784.784 4 1.75 4h.55c-.192-.375-.3-.8-.3-1.25ZM7.25 9.5H2.5v4.75c0 .138.112.25.25.25h4.5Zm1.5 0v5h4.5a.25.25 0 0 0 .25-.25V9.5Zm0-4V8h5.5a.25.25 0 0 0 .25-.25v-2a.25.25 0 0 0-.25-.25Zm-7 0a.25.25 0 0 0-.25.25v2c0 .138.112.25.25.25h5.5V5.5h-5.5Zm3-4a1.25 1.25 0 0 0 0 2.5h2.309c-.233-.818-.542-1.401-.878-1.793-.43-.502-.915-.707-1.431-.707ZM8.941 4h2.309a1.25 1.25 0 0 0 0-2.5c-.516 0-1 .205-1.43.707-.337.392-.646.975-.879 1.793Z">
          </path>
        </svg>
      </template>
      <template id="code-square-icon">
        <svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-code-square">
          <path
            d="M0 1.75C0 .784.784 0 1.75 0h12.5C15.216 0 16 .784 16 1.75v12.5A1.75 1.75 0 0 1 14.25 16H1.75A1.75 1.75 0 0 1 0 14.25Zm1.75-.25a.25.25 0 0 0-.25.25v12.5c0 .138.112.25.25.25h12.5a.25.25 0 0 0 .25-.25V1.75a.25.25 0 0 0-.25-.25Zm7.47 3.97a.75.75 0 0 1 1.06 0l2 2a.75.75 0 0 1 0 1.06l-2 2a.749.749 0 0 1-1.275-.326.749.749 0 0 1 .215-.734L10.69 8 9.22 6.53a.75.75 0 0 1 0-1.06ZM6.78 6.53 5.31 8l1.47 1.47a.749.749 0 0 1-.326 1.275.749.749 0 0 1-.734-.215l-2-2a.75.75 0 0 1 0-1.06l2-2a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042Z">
          </path>
        </svg>
      </template>
      <template id="device-desktop-icon">
        <svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-device-desktop">
          <path
            d="M14.25 1c.966 0 1.75.784 1.75 1.75v7.5A1.75 1.75 0 0 1 14.25 12h-3.727c.099 1.041.52 1.872 1.292 2.757A.752.752 0 0 1 11.25 16h-6.5a.75.75 0 0 1-.565-1.243c.772-.885 1.192-1.716 1.292-2.757H1.75A1.75 1.75 0 0 1 0 10.25v-7.5C0 1.784.784 1 1.75 1ZM1.75 2.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h12.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25ZM9.018 12H6.982a5.72 5.72 0 0 1-.765 2.5h3.566a5.72 5.72 0 0 1-.765-2.5Z">
          </path>
        </svg>
      </template>
      <div class="position-relative">
        <ul role="listbox" class="ActionListWrap QueryBuilder-ListWrap" aria-label="Suggestions" data-action="
                    combobox-commit:query-builder#comboboxCommit
                    mousedown:query-builder#resultsMousedown
                  " data-target="query-builder.resultsList" data-persist-list="false" id="query-builder-test-results"></ul>
      </div>
      <div class="FormControl-inlineValidation" id="validation-03cd9b9f-1f9a-4552-95fc-008b2c93d1d9" hidden="hidden">
        <span class="FormControl-inlineValidation--visual">
          <svg aria-hidden="true" height="12" viewBox="0 0 12 12" version="1.1" width="12" data-view-component="true" class="octicon octicon-alert-fill">
            <path d="M4.855.708c.5-.896 1.79-.896 2.29 0l4.675 8.351a1.312 1.312 0 0 1-1.146 1.954H1.33A1.313 1.313 0 0 1 .183 9.058ZM7 7V3H5v4Zm-1 3a1 1 0 1 0 0-2 1 1 0 0 0 0 2Z"></path>
          </svg>
        </span>
        <span></span>
      </div>
    </div>
    <div data-target="query-builder.screenReaderFeedback" aria-live="polite" aria-atomic="true" class="sr-only"></div>
  </query-builder>
</form>

POST /search/feedback

<form id="code-search-feedback-form" data-turbo="false" action="/search/feedback" accept-charset="UTF-8" method="post"><input type="hidden" data-csrf="true" name="authenticity_token"
    value="to41f042TJNkNsUmmjjxmHnMoc7FyooTYVy3xnVpu51TugJkpytBTO9/v1r04D+w9dMa6ZZzK3iQ2gIeFiUElg==">
  <p>We read every piece of feedback, and take your input very seriously.</p>
  <textarea name="feedback" class="form-control width-full mb-2" style="height: 120px" id="feedback"></textarea>
  <input name="include_email" id="include_email" aria-label="Include my email address so I can be contacted" class="form-control mr-2" type="checkbox">
  <label for="include_email" style="font-weight: normal">Include my email address so I can be contacted</label>
</form>

POST /search/custom_scopes

<form id="custom-scopes-dialog-form" data-turbo="false" action="/search/custom_scopes" accept-charset="UTF-8" method="post"><input type="hidden" data-csrf="true" name="authenticity_token"
    value="3VmTx7GHjqd7XhgkxJUhaKlTFtMKVQIt4nxh5tOgXK8rUEg65GZcwHUcskuxGFhGxx7joxKjyM5FvOYTWyV58w==">
  <div data-target="custom-scopes.customScopesModalDialogFlash"></div>
  <input type="hidden" id="custom_scope_id" name="custom_scope_id" data-target="custom-scopes.customScopesIdField">
  <div class="form-group">
    <label for="custom_scope_name">Name</label>
    <auto-check src="/search/custom_scopes/check_name" required="">
      <input type="text" name="custom_scope_name" id="custom_scope_name" data-target="custom-scopes.customScopesNameField" class="form-control" autocomplete="off" placeholder="github-ruby" required="" maxlength="50" spellcheck="false">
      <input type="hidden" data-csrf="true" value="H4yjZc0PSquBwpX3OPVOpjKboutNkepR93cNhrss+p4YmxO3OXBsDlmeej33XL+Yr6ZRruUCsg9lnsZGNjAJNw==">
    </auto-check>
  </div>
  <div class="form-group">
    <label for="custom_scope_query">Query</label>
    <input type="text" name="custom_scope_query" id="custom_scope_query" data-target="custom-scopes.customScopesQueryField" class="form-control" autocomplete="off" placeholder="(repo:mona/a OR repo:mona/b) AND lang:python" required=""
      maxlength="500">
  </div>
  <p class="text-small color-fg-muted"> To see all available qualifiers, see our <a class="Link--inTextBlock" href="https://docs.github.com/search-github/github-code-search/understanding-github-code-search-syntax">documentation</a>. </p>
</form>

Text Content

Skip to content



NAVIGATION MENU

Toggle navigation
Sign in
 * Product
    * Actions
      Automate any workflow
    * Packages
      Host and manage packages
    * Security
      Find and fix vulnerabilities
    * Codespaces
      Instant dev environments
    * GitHub Copilot
      Write better code with AI
    * Code review
      Manage code changes
    * Issues
      Plan and track work
    * Discussions
      Collaborate outside of code
   
   Explore
    * All features
    * Documentation
    * GitHub Skills
    * Blog

 * Solutions
   By size
    * Enterprise
    * Teams
    * Startups
   
   By industry
    * Healthcare
    * Financial services
    * Manufacturing
   
   By use case
    * CI/CD & Automation
    * DevOps
    * DevSecOps

 * Resources
   Topics
    * AI
    * DevOps
    * Innersource
    * Open Source
    * Security
    * Software Development
   
   Explore
    * Learning Pathways
    * White papers, Ebooks, Webinars
    * Customer Stories
    * Partners

 * Open Source
    * GitHub Sponsors
      Fund open source developers
   
    * The ReadME Project
      GitHub community articles
   
   Repositories
    * Topics
    * Trending
    * Collections

 * Enterprise
    * Enterprise platform
      AI-powered developer platform
   
   Available add-ons
    * Advanced Security
      Enterprise-grade security features
    * GitHub Copilot
      Enterprise-grade AI features
    * Premium Support
      Enterprise-grade 24/7 support

 * Pricing


Search or jump to...



SEARCH CODE, REPOSITORIES, USERS, ISSUES, PULL REQUESTS...

Search

Clear



Search syntax tips




PROVIDE FEEDBACK



We read every piece of feedback, and take your input very seriously.

Include my email address so I can be contacted
Cancel Submit feedback


SAVED SEARCHES


USE SAVED SEARCHES TO FILTER YOUR RESULTS MORE QUICKLY


Name
Query

To see all available qualifiers, see our documentation.


Cancel Create saved search
Sign in
Sign up
You signed in with another tab or window. Reload to refresh your session. You
signed out in another tab or window. Reload to refresh your session. You
switched accounts on another tab or window. Reload to refresh your session.
Dismiss alert

{{ message }}
vogler75 / automation-gateway Public
 * Sponsor
 * Notifications You must be signed in to change notification settings
 * Fork 32
 * Star 208

A OPC UA gateway which gives you access to your OPC UA values via MQTT or
GraphQL (HTTP). If you have an OPC UA server in your PLC, or a SCADA system with
an OPC UA server, you can query data from there via MQTT and GraphQL (HTTP). In
addition, the gateway can also log value changes from OPC UA nodes in an
InfluxDB, IoTDB, Kafka, and others.


LICENSE

GPL-3.0 license
208 stars 32 forks Branches Tags Activity
Star
Notifications You must be signed in to change notification settings
 * Code
 * Issues 2
 * Pull requests 0
 * Actions
 * Projects 0
 * Security
 * Insights

Additional navigation options
 * Code
 * Issues
 * Pull requests
 * Actions
 * Projects
 * Security
 * Insights


VOGLER75/AUTOMATION-GATEWAY

This commit does not belong to any branch on this repository, and may belong to
a fork outside of the repository.
 master
8 Branches
47 Tags

Go to file



Code



FOLDERS AND FILES

NameName
Last commit message
Last commit date


LATEST COMMIT

vogler75
Update README.md
Jul 20, 2024
61702f9 · Jul 20, 2024


HISTORY

478 Commits


.github
.github
Create FUNDING.yml
Feb 24, 2023
doc
doc
Update on rework of disk buffering
Jul 16, 2024
docker
docker
Dockerfile and build native changes
Jul 18, 2024
native
native
Update on native build
Jul 20, 2024
source
source
Config example with all available loggers
Jul 20, 2024
.gitignore
.gitignore
Fix in driver base and rework of MqttDriver
Oct 21, 2023
LICENSE
LICENSE
Create LICENSE
Feb 12, 2021
README.md
README.md
Update README.md
Jul 20, 2024
View all files


REPOSITORY FILES NAVIGATION

 * README
 * GPL-3.0 license


FRANKENSTEIN AUTOMATION GATEWAY

Connect one or more OPC UA servers, PLC4X devices or MQTT brokers to the gateway
and access the data with a GraphQL, a MQTT, or an OPC UA client. The Gateway
additionally offers functionality to log value changes from OPC UA, MQTT and
PLC4X in a range of databases and platforms, including QuestDB, InfluxDB, Kafka,
among others. Tested with up to 250000 value changes per second on comodity
hardware.

Available logger sinks:

 * TimescaleDB (JDBC)

 * InfluxDB

 * IoTDB

 * Neo4J

 * JDBC (PostgreSQL, MySQL, SQL Server)

 * OpenSearch (ElasticSearch)

 * Kafka

 * MQTT

The Gateway passes values through, it does not store data internally. The MQTT
Broker is not a fully complient MQTT Broker. It does not keep values in memory.
If you subscribe to a virtual MQTT-Topic, which must follow certain rules, then
it will connect to the tags in OPC UA or PLC4X and will pass the values to the
client. If multiple clients subscribe to the same virtual topic, then only one
subscription to the device is made and the Gateway will distribute the values to
all the clients.

The Gateway also has an integrated OPC UA server. You can define what kind of
data from MQTT brokers, other OPC UA servers and from PLC4X devices you want to
have in the integraded OPC UA server.

Docker images can be found on Docker Hub.

News and Blog posts can be found here.

Here is a tutorial on YouTube.




CONTENT

 * Build and Run
 * Configuration
 * OPCUA Schema in GraphQL
 * Topic Mapping
 * Logger Configuration
 * OPCUA Driver
 * PLC4X Driver
 * MQTT Driver
 * Build Docker Image
 * Version History


BUILD AND RUN

It needs Java 17 or higher. There is an issue with Java 8 update 292 and
bouncycastl encryption. here you can find more information about this issue..

You can open the project in IntelliJ IDEA IDE and build it there or use grade to
build it from command line. Use the included "gradlew" command. Tt will download
and use the right gradle version.

> cd source/app
> gradlew build

> export GATEWAY_CONFIG=config.yaml  # Set configuration file (default is config.yaml)
> gradlew run




You can also pass the configuration filename as an argument.

App is with GraphQL, MQTT and the OPC UA connections.
There is also "App-plc4x" which includes the plc4x connectivity.


CONFIGURATION

See config.yaml in the app directory for an example how to configure the
Gateway. You can pass a configuration file name to the program as the first
argument or by setting a environment variable GATEWAY_CONFIG. If no argument is
given then config.yaml will be used.

 * There is a YAML schema in the doc directory. This can be used with Visual
   Studio Code to create a valid YAML configuration for the gateway.

 * In Visual Studio Codee install the "YAML Language Support by Red Hat"
   Extension. In the settings of this extension you will find the "schema"
   section, open the settings.json and add one line to the yaml.schemas
   sections:

yaml.schemas": {        
        "your-path-to-gateway/doc/yaml-json-schema.json": ["config*.yaml"]
}




If you enable GraphiQL, a graphical ui to build and execute GraphQL queries,
then you can access GraphiQL with

> http://localhost:4000/graphiql/ ! trailing slash is important !

Servers:
  Mqtt:
    - Id: Mqtt
      Port: 1883
      Host: 0.0.0.0
      LogLevel: INFO # ALL | INFO
      
  OpcUa:
    - Port: 4841
      LogLevel: INFO
      Topics:
        - Topic: opc/demo1/path/#
        - Topic: opc/demo2/path/#  

  GraphQL:
    - Id: GraphQL
      Port: 4000
      LogLevel: INFO
      GraphiQL: true





OPCUA SCHEMA IN GRAPHQL

The GraphQL server can read the OPC UA object schema and convert it to a GraphQL
schema. The starting NodeIds can be set to reduce the amount of browsed items.
Browsing can take some while if the OPC UA server holds a huge structure of
tags!

Servers:
  GraphQL:
    - Id: GraphQL
      Port: 4000
      Enabled: true
      LogLevel: INFO                    # ALL | INFO
      GraphiQL: true
      WriteSchemaToFile: false
      Schemas:                          # This systems will be browsed and converted to GraphQL
        - System: "unified"             # Id of OPC UA system, must correlate with the drivers id
          FieldName: BrowseName         # Use "BrowseName" or "DisplayName" as item name in GraphQL
          RootNodes: 
            - ns=2;s=Simulation         # Node will be browsed and added to GraphQL schema 
            - ns=2;s=SimulationMass     # Node will be browsed and added to GraphQL schema          




Drivers:
  OpcUa
  - Id: "unified" 
    Enabled: true
    LogLevel: INFO
    EndpointUrl: "opc.tcp://scada-server:4890"
    UpdateEndpointUrl: scada-server
    SecurityPolicyUri: http://opcfoundation.org/UA/SecurityPolicy#None  




Example GraphQL Query with two OPC UA systems:

{
  Systems {
    unified {
      HmiRuntime {
        HMI_RT_5 {
          Structure_instances{
            A1 {
              Velocity { ...Value }
              RefPoint { ...Value }
            }
          }
        }
      }
    }    
    ignition {
      Tag_Providers {
        default {
          Pump_1 {
            flow { ...Value }
            speed { ...Value }
          }
        }
      }
    }
  }
}

fragment Value on Node {
  Value {
    Value
    SourceTime
  }
}






TOPIC MAPPING

With the built in MQTT Interface you can get access to the connected OPC UA
servers by subscribing to MQTT Topics.

The Topic name follows a certain rule. When a MQTT client subscribes to such
MQTT topics, then the Gateway will create a subscription to the Node in the OPC
UA Server. If multiple clients subscribe to the same Topic/Node, then the
Gateway will act as a distributor and only one connection to the node is made.

Note: In the following examples "test" is the Id of the OPC UA Client in the
configuration file.

Note: Remove the blanks between the slashe! Just here for better readabilty.

Using the NodeId

> opc / test / node / ns=2;s=ExampleDP_Float.ExampleDP_Arg1
> opc / test / node / 2 / ExampleDP_Float.ExampleDP_Arg1

Value as JSON with timestamp, quality, ...

> opc / test / node:json / ns=2;s=ExampleDP_Float.ExampleDP_Arg1
> opc / test / node:json / 2 / ExampleDP_Float.ExampleDP_Arg1

Using the browse path instead of the NodeId

> opc / test / path / root-node-id / browse-name / browse-name /...

Wildcard "+" can also be used as a browsename

> opc / ua / path:json / ns=1;s=16|Tags / +

"Objects" can be used as root node and will be replace with "i=85"

> opc / test / path / Objects / Test / Test00003 / float
> opc / test / path / Objects / Test / Test00003 / +

Be careful when using wildcards when there are a lot of nodes, it can lead to a
lot of browsing round trips

> opc / test / path / Objects / Test / + / float


LOGGER CONFIGURATION

Loggers for different type of sinks can be defined in the configuration file.
All of them share a common configuration and can have additonal sink specific
configuration. Sink specific configuration can be found in the example
configuration files or in the version history.

In the "Logging" section we can specify the Topics which should be logged to the
sink. The Topics follow the same rule as MQTT Topics and will map to sources
like OPC UA or PLC4X. See Topic Mapping.

Loggers:
  InfluxDB:
    - Id: influx1
      Enabled: true
      Url: http://192.168.1.13:8086
      Database: test
      Username: ""
      Password: ""
      WriteParameters:
        QueueSize: 20000
        BlockSize: 10000
      Logging:
        - Topic: opc/opc1/path/Objects/Demo/SimulationMass/SimulationMass_SByte/+
        - Topic: opc/opc1/path/Objects/Demo/SimulationMass/SimulationMass_Byte/+
  




Every logger has an internal topic where the throughput is updated every second.
Topic: logger/logger-id/metrics Value: {"Input v/s":20932,"Output v/s":20932} #
just an example


OPCUA DRIVER

See config directory for more example configurations.

Drivers:
  OpcUa:
  - Id: "unified"
    Enabled: true
    LogLevel: INFO
    EndpointUrl:  "opc.tcp://desktop-9o6hthf:4890"
    SecurityPolicy: Basic128Rsa15
    SubscriptionSamplingInterval: 0
    UsernameProvider:
      Username: opcuauser
      Password: password1
    WriteParameters:
      QueueSize: 1000
      BlockSize: 100
      WithTime: true
    MonitoringParameters:
      BufferSize: 10
      SamplingInterval: 0.0
      DiscardOldest: true





PLC4X DRIVER

You the application app-plc4x to get values from various source supported by
PLC4X. See config.yaml in each folder of the app. Because some PLC4X
drivers/plc's do not support subscriptions we have added a simple polling
options (currently only one polling time per connection).

> cd app-plc4x
cat config.yaml
Drivers:
  Plc4x:
  - Id: "machine1"
    Enabled: true
    Url: "modbus://127.0.0.1:502"
    Polling:
      Time: 1000 # ms
      Timeout: 900 # ms
      OldNew: true
    WriteTimeout: 100 # ms
    ReadTimeout: 100 # ms
    LogLevel: ALL

> gradle run




Example GraphQL Query:

{
  a: NodeValue(
    Type: Plc,
    System: "mod"
    NodeId: "coil:1"
  ) {
    Value
  }
  b: NodeValue(
    Type: Plc
    System: "mod"
    NodeId: "holding-register:1:INT"
  ) {
    Value
    SourceTime
  }  
}




Example MQTT Topic:

> plc/mod/node:json/holding-register:1:INT
> plc/mod/node/holding-register:2:INT
> plc/mod/node:json/coil:1
> plc/mod/node/coil:1


MQTT DRIVER

MQTT Brokers can be connected with MQTT Driver. Withte the RAW Format the values
are strings. JSON Format will be the Gateway's JSON format. If you add
"CustomJson" to the config, then the JSON format can be customized. You can set
JSON-Paths (without $.) to get the value out of a JSON. Currently only JSON
objects are supported (not arrays).

Drivers:  
  Mqtt:
    - Id: "mqtt1"
      LogLevel: INFO
      Host: 192.168.1.3
      Port: 1883
      Format: Json
      CustomJson: 
          Value: "Value"
          TimestampMs: "TimeMS"    
          TimestampIso: "TimeISO"





BUILD DOCKER IMAGE

You have to build the program before with gradle. Then you can use the shell
script docker/build.sh to build a docker image.
docker run --rm --name gateway -p 4000:4000 -p 1883:1883 -v
$PWD/config.yaml:/app/config.yaml gateway

> C:\Workspace\automation-gateway\source> gradle build
> C:\Workspace\automation-gateway\docker> build.bat
> C:\Workspace\automation-gateway\docker\examples\hazelcast> docker compose up
> -d


VERSION HISTORY

 * 1.33 Rework of Logging
 * 1.32 Zenoh Logger
 * 1.31 QuestDB Logger
 * 1.30 OpenSearch Logger
 * 1.29 Extended OPC UA Browsing
 * 1.28 Various changes and code rework
 * 1.27 Add target option to MQTT Logger for UNS
 * 1.26 Reactivated Neo4j Logger
 * 1.25 MQTT Driver Custom JSON Format
 * 1.24 Added OPC UA server
 * 1.23 Upgrade to VertX 4.4.6
 * 1.22 Config file changes
 * 1.21.2 Fixes and SparkplugB for Kafka & MQTT Logger
 * 1.21.1 Fixes and SparkplugB for MQTT Client
 * 1.21 IoTDB, MQTT SparkplugB Logger, YAML Schema, Native-Image
 * 1.20.3 Moved Neo4J to separate branches
 * 1.20.2 Modifed JSON Format of Kafka Logger
 * 1.20.1 Kafka properties in the config file
 * 1.20 Cleanup and GraalVM Native Build
 * 1.19 Neo4j Logger
 * 1.18.3 Added MQTT Websocket Option and simple Authentication
 * 1.18.2 Raw value to engineering value conversion for PLC4X driver
 * 1.18.1 Features and fixes in PLC4X driver
 * 1.18 Removed Apache Ignite
 * 1.17 Added CrateDB as supported JDBC database for logging
 * 1.16 JDBC Logger to write field values to relational databases
 * 1.15 Nats Logger to write field values to a Nats server
 * 1.14 Fixes and optimizations
 * 1.13 MQTT Logger to write field values to a MQTT Broker
 * 1.12 MQTT Driver with Groovy script transformer
 * 1.11 Apache Kafka Database Logger
 * 1.10 Apache IoTDB Database Logger
 * 1.9 Apache Ignite as Cluster option and Ignite as Memory-Store
 * 1.8 Upgrade to VertX 4.0.3
 * 1.7 DDS Driver (subscribe and publish)
 * 1.6 Added GraphiQL (http://localhost:4000/graphiql/)
 * 1.5 OPC UA Schemas to GraphQL Schema Importer


1.33 REWORK OF LOGGING

Logging ensures now no data loss. If a connection is lost, values are
temporarily stored in memory or on disk and written once the connection is
restored. The default storage is memory. To specify the storage type, use the
QueueType parameter in WriteParameters. If DISK is chosen, QueueSize sets the
file size in bytes. If memory is used, QueueSize is the number of data points.
This specifies the maximum space available for buffered data. The file will be
pre-allocated to this size. Currently, each tag value consumes around 1400 bytes
due to Java Object serialization, but we must optimize this to decrease storage
requirements.

 Jdbc:
    - Id: postgres      
      Enabled: false
      Url: jdbc:postgresql://linux0:5432/scada
      WriteParameters:
        QueueType: DISK
        QueueSize: 1073741824 # Filesize in bytes (1GB)
        DiskPath: /data/buffers # Storage location for disk files




We have removed Zenoh and DuckDB from the main branch for maintenance purposes.
We also removed native QuestDB support because the gateway cannot be compiled to
native code using GraalVM with the QuestDB client.


1.32 ZENOH LOGGER

Added logger for Zenoh.

For Zenoh only the default configuration settings are currently available. Zenoh
is also commented out in the "App", because Zenoh uses native libs and the size
of the libs is high. Also the native libs maybe hinder the compilation of a
native executable with GraalVM (not tested). If you need Zenoh, then go to the
App.kt and remove the comments from the Zenoh related lines. You need to set the
following variables in ~/.gradle/gradle.properties, because the current Zenoh
Java libs are not yet available at maven and must be fetched from github.

github_user=xxx
github_token=xxx




Also comment in the github maven repository for Zenoh in the settings.gradle
file in the source directory


1.31 QUESTDB LOGGER

Logger for QuestDB.

You should create the logging table before you start the logger.

CREATE TABLE gateway (
    time timestamp,
    system symbol,
    address symbol,
    value double,
    text varchar
) TIMESTAMP(time) PARTITION BY MONTH;
ALTER TABLE gateway DEDUP ENABLE UPSERT KEYS(time, system, address)
ALTER TABLE gateway ALTER COLUMN system ADD INDEX;
ALTER TABLE gateway ALTER COLUMN address ADD INDEX;




Example configuration:

Loggers:
  QuestDB:
    - Id: Qdb0
      Enabled: true
      Config: http::addr=nuc1.rocworks.local:9001;
      Table: home1
      Logging:
        - Topic: mqtt/home/path/Original/#





1.30 OPENSEARCH LOGGER

Logger for OpenSearch / Elasticsearch.

You must create an index template <index name> with an index pattern "<index
name>-*" with the following JSON index mapping:

{
  "properties": {
    "topicName": { "type": "text" },
    "systemType": { "type": "text" },
    "systemName": { "type": "text" },
    "topicType": { "type": "text" },
    "topicPath": { "type": "text" },
    "topicNode": { "type": "text" },
    "browsePath": { "type": "text" },
    "valueAsString": { "type": "text" },
    "valueAsNumber": { "type": "double" },
    "statusCode": { "type": "text" },
    "sourceTime": { "type": "date" },
    "serverTime": { "type": "date" }
  }
}




Example configuration:

Loggers:
  OpenSearch:
    - Id: Search1
      Enabled: true
      LogLevel: INFO
      Host: linux0
      Port: 9200
      Index: gateway
      Logging:
        - Topic: mqtt/home/path/Original/#





1.29 EXTENDED OPC UA BROWSING

Nodes of type Variable will now be browsed. Before browsing stopped, when it
reached a node of type Variable. But some OPC UA servers, like the one from
SIEMENS PLCs, are using a Variable node type for User-Defined-Datatypes or
Structs.


1.28 VARIOUS CHANGES AND CODE REWORK

 * Upgrade to PLC4X 0.9
 * Upgrade to Vertx 4.5.1
 * Added Duckdb Logger
 * Added HSQLDB Support for JDBC Logger
 * Added ExecuteSQL GraphQL function
 * IoTDB Logger improvements
 * Rework of SparkplugB messages
 * Rework of BrowsePath in messages


1.27 ADD TARGET OPTION TO MQTT LOGGER FOR UNS

With the "Target" option at the Logging Topics, a transformation of the input
topic to the target topic can now be done. With that a Unified Namespace (UNS)
can be created at the target MQTT broker, with incomding non UNS topic names.

If a wildcard is used at the source topic, like
opc/home1/path/Objects/Mqtt/home/Original/Gas/#, then the browsed/resolved names
can be added to the end of the target topic. Just add a wildcard "#" also at the
end of the target topic. If there is no wildcard at the target, then all the
resolved topics of the source are all written to the same target topic.

Loggers:
  Mqtt:
    - Id: "mqtt1"
      Enabled: true
      Host: 192.168.1.4
      Port: 1883
      Topic: demo
      Format: JsonSimple
      LogLevel: INFO
      Retained: false
      Logging:
        - Topic: opc/demo2/path/Objects/Demo/SimulationMass/SimulationMass_Boolean/Boolean_00
          Target: uns/rocworks/site1/area1/line1/sim1/bool00
        - Topic: opc/demo2/path/Objects/Demo/SimulationMass/SimulationMass_Byte/#
          Target: uns/rocworks/site1/area1/line1/sim2/#
        - Topic: opc/home1/path/Objects/Mqtt/home/Original/Gas/#
          Target: uns/rocworks/site1/area1/line1/gas/#
        - Topic: opc/home1/path/Objects/Mqtt/home/Original/Meter_Input/#
          Target: uns/rocworks/site1/area1/line1/meter/input/#
        - Topic: opc/home1/path/Objects/Mqtt/home/Original/Meter_Output/#
          Target: uns/rocworks/site1/area1/line1/meter/output/#
        - Topic: opc/home1/path/Objects/Mqtt/home/Original/PV/#
          Target: uns/rocworks/site1/area1/line1/pv/# 





1.26 REACTIVATED NEO4J LOGGER

Added Neo4j as an option to log values from MQTT or OPC UA to the graph
database. Additionally the OPC UA node structure can also be replicated to the
graph database. This will be done only once at the startup of the Automation
Gateway. For MQTT the node structure will be built during runtime, as new topics
are coming in, the structure will be created.

Loggers:
  Neo4j:
    - Id: neo4j
      Enabled: true
      Url: bolt://nuc1.rocworks.local:7687
      Username: "neo4j"
      Password: "neo4j"
      Schemas:
        - System: demo1
          RootNodes:
            - "ns=2;s=Variables"
        - System: demo2
          RootNodes:
            - "ns=2;s=Demo"
      Logging:
        - Topic: mqtt/mqtt1/path/Original/#
        - Topic: opc/demo1/path/Objects/Variables/#
        - Topic: opc/demo2/path/Objects/Demo/SimulationMass/#






1.25 MQTT DRIVER CUSTOM JSON FORMAT

With format JSON it is now possible to define the JSON-Path for the value and
for the timestamp in milliseconds since epoch or ISO 8601. If CustomJson is not
defined, then the JSON content is a defined JSON format of the Gateway. The
format is used for reading and writing.

Drivers:  
  Mqtt:
    - Id: "mqtt1"
      LogLevel: INFO
      Host: 192.168.1.3
      Port: 1883
      Format: Json
      CustomJson: 
          Value: "Value"
          TimestampMs: "TimeMS"    
          TimestampIso: "TimeISO"






1.24 ADDED OPC UA SERVER

The Gateway now also has an integrated OPC UA server. You can define what kind
of data from MQTT brokers, other OPC UA servers and from PLC4X devices you want
to have in the integraded OPC UA server. Data will be mapped to structured nodes
in the OPC UA server. It is also possible to change the values in the OPC UA
server and the changed values will be written back to the source (MQTT broker,
other OPC UA server, PLC4X connected device).

There is now also a GraphQL interface for the configuration of the gateway. This
GraphQL Server can be enabled by setting the environment variable
"GATEWAY_CONFIG_PORT=9999". 9999 will be the port for the configuration GraphQL
serer. This configuration GraphQL server can be used to build a configuration Ui
for the gateway.


1.23 UPGRADE TO VERTX 4.4.6

The GraphQL server websocket subprotocol now changed to the new one:
"graphql-transport-ws". The older Apollo subprotocol "graphql-ws" is not
supported anymore.

Note: In some GraphQL context the naming of the two protocols can be confusing.
From apollographql.com: Confusingly, the subscriptions-transport-ws library
calls its WebSocket subprotocol graphql-ws, and the graphql-ws library calls its
subprotocol graphql-transport-ws! The names of the protocol and the websocket
subprotocol are exchangend.


1.22 CONFIG FILE CHANGES

!!! Config file structure has changed !!!

To have a consistant layout of the config file, it was necessary to change the
structure of it. Existing config files must be changed! Please use the Visual
Studio Code YAML plugin to change your existing configs. See Configuration.

Example of the new config file:

Servers:
  GraphQL:
    - Id: "GraphQL"        
  Mqtt:
    - Id: "Mqtt"

Drivers:   
  Mqtt:
    - Id: remote
      Host: bd9c43f59b7a42deba3248fca439f378.s1.eu.hivemq.cloud
      Port: 8883

  OpcUa:
    - Id: demo1
      EndpointUrl: "opc.tcp://192.168.1.3:62540/server"
      SecurityPolicy: None

    - Id: demo2
      EndpointUrl: "opc.tcp://192.168.1.3:62541"
      SecurityPolicy: None     

Loggers:    
  InfluxDB:
    - Id: InfluxLogger1      
      Url: http://nuc1b.rocworks.local:8086
      Database: test
      Logging:
        - Topic: opc/demo1/path/Objects/Variables/#
        - Topic: opc/demo2/path/Objects/Demo/SimulationMass/#
        - Topic: mqtt/remote/path/Austria/Sparkplug/#

  Mqtt:
    - Id: MqttLogger1
      Host: linux0.rocworks.local
      Port: 1883
      Format: Raw
      Topic: test1
      Logging:
        - Topic: mqtt/remote/path/Austria/Sparkplug/#
  





1.21.2 FIXES AND SPARKPLUGB FOR KAFKA & MQTT LOGGER

Kafka and MQTT Logger can now publish SparkplugB message format.


1.21.1 FIXES AND SPARKPLUGB FOR MQTT DRIVER

There is now a Format option for the MQTT driver. It can now read SparkplugB
messages from topics. You can use now a logger to log values from a MQTT broker
which are in SparkplugB message format. You can also write values from GraphQL
or publish a value from the MQTT server to the MQTT driver. If the format of the
MQTT driver is set to SparkplugB, it will publish a SparkplubB message.

Driver:
  Mqtt:
    - Id: "mqttclient1"
      Host: linux0.rocworks.local
      Port: 1883    
      Format: SparkplugB # RAW | JSON




MQTT Publish Example:

 * mqtt/mqttclient1/node:value/Enterprise/Test => Hallo World
 * mqtt/mqttclient1/node:json/Enterprise/Test => {"value": "Hello World",
   "sourceTime":"2023-10-19T18:23:55.389Z"}


1.21 IOTDB, MQTT SPARKPLUGB LOGGER, YAML SCHEMA, NATIVE-IMAGE

 * IoTDB is now again available as data logger.
 * SparkplubB message format for MQTT logger.

  Logger:
    - Id: mqtt1
      Type: Mqtt
      Enabled: true
      LogLevel: INFO       
      Mqtt:
        Host: linux0.rocworks.local
        Port: 1883
        Topic: Enterprise/Site/Area/Line
        Format: Json
        BulkMessages: false                   
      Logging:
        - Topic: opc/demo1/path/Objects/Variables/#
        - Topic: opc/demo2/path/Objects/Demo/SimulationMass/#



 * YAML json schema is now availabe in the doc directory. It can be used with
   the "YAML Language Support by Red Hat" Extension. In the settings find the
   Schema section, open the settings.json and add one line to the yaml.schemas
   sections:

yaml.schemas": {        
        "your-path-to-gateway/doc/yaml-json-schema.json": ["config*.yaml"]
}



 * Update to Gradle 8.4 with new build files.
 * Native image build was upgraded to GraalVM 17 and Java 17.
 * Native image works with Mqtt,Kafka,InfluxDB and IoTDB.
 * Logger configurations have now a separate object for the type specific
   configurations (but the old style yaml format is still supported). This was
   necessary for the YAML json schema.

- Id: iotdb1
      Type: IoTDB
      Enabled: false
      IoTDB: # same name as Type
        Host: linux0.rocworks.local
        Port: 6667
        Database: root.gateway
        Username: "root"
        Password: "root" 
      LogLevel: INFO       




 * For published data we use now a DataPoint type instead of JSON. With the JSON
   format we have lost the origin datatype of the source. By using the new
   DataPoint type (Topic+TopicValue) the data type is preserved.
 * Upgrade from Vert.X 4.2.4 to 4.2.7


1.20.3 MOVED NEO4J TO SEPARATE BRANCHES

Neo4J is now in a separate branch and is removed from the main branch


1.20.2 MODIFED JSON FORMAT OF KAFKA LOGGER

Added times in ms epoch and also added the value as double and as string.

{
	"nodeId": "ns=2;i=3",
	"systemName": "scadaopcua",
	"topicName": "opc/scadaopcua/path/Objects/Home/#",
	"browsePath": "Objects/Home/Gas/Daily",
	"sourceTime": "2023-07-14T09:53:43.945894Z",
	"serverTime": "1601-01-01T00:00:00Z",
	"sourceTimeMs": 1689328423945,
	"serverTimeMs": -11644473600000,
	"value": 2,
	"valueAsString": "2",
	"valueAsDouble": 2,
	"statusCode": "0"
}





1.20.1 KAFKA PROPERTIES IN THE CONFIG FILE

It can be configured with a bunch of properties as described in the official
Apache Kafka documentation.

Put the properites and values below "Configs" - see example below where the
"batch.size" is set to 10000.

But be careful, you will not get an error message if you set an unknow property,
so be sure to use right name of the property.

Database:
  Logger:
    - Id: kafka1
      Type: Kafka
      Enabled: true
      Servers: nuc1.rocworks.local:9092
      Configs:  # you can set any valid Kafka producer property here
        batch.size: 10000
      WriteParameters:
        QueueSize: 20000
        BlockSize: 10000
      Logging:
        - Topic: opc/opc1/path/Objects/Demo/SimulationMass/SimulationMass_SByte/+
        - Topic: opc/opc1/path/Objects/Demo/SimulationMass/SimulationMass_Byte/+





1.20 CLEANUP AND GRAALVM NATIVE BUILD

GraalVM Native Image build is now possible, see directory "native". Removed
various unused features and upgraded libraries to the latest versions. For the
native build it was needed to replace SLF4J logging with the standard Java
logging.

Removed Features:

 * Clustering
 * NATS
 * DDS
 * IoTDB: it leads to debug(!) log messages of other components, did not invest
   time to find out how to get rid of this behaviour.
 * MQTTDriver: it uses Groovy and I could get it running natively compiled, so I
   had to remove it to get rid of Groovy

Fixed bug: Topic data class now contains two separate fields "path" and "node".
Before there was only one field "address". This also fixed a Bug when
connecting/disconnecting of topics with wildcards.


1.19 NEO4J LOGGER

!! NOT AVAILABLE ANYMORE !! Separate Branch !!
Added Neo4j as an option to log values from OPC UA to the graph database.
Additionally the OPC UA node structure can also be replicated to the graph
database. This will be done only once at the startup of the Automation Gateway.

Database:
  Logger:
    - Id: neo4j
      Enabled: true
      Type: Neo4j
      Url: bolt://nuc1.rocworks.local:7687
      Username: "neo4j"
      Password: "manager"
      Schemas:
        - System: opc1  # Replicate node structure to the graph database
          RootNodes:
            - "ns=2;s=Demo"  # This node and everything below this node
        - System: winccoa1  # Replicate the nodes starting from "i=85" (Objects) node
      WriteParameters:
        BlockSize: 1000
      Logging:
        - Topic: opc/opc1/path/Objects/Demo/SimulationMass/SimulationMass_Float/+
        - Topic: opc/opc1/path/Objects/Demo/SimulationMass/SimulationMass_Double/+
        - Topic: opc/opc1/path/Objects/Demo/SimulationMass/SimulationMass_Int16/+
        - Topic: opc/winccoa1/path/Objects/PUMP1/#
        - Topic: opc/winccoa1/path/Objects/ExampleDP_Int/#







1.18.3 ADDED MQTT WEBSOCKET OPTION AND SIMPLE AUTHENTICATION

Added the option to enable a Websocket listener for the MQTT server. The
Websocket listener is listening on the endpoint "/mqtt". Example MQTT Client
Url: "ws://your-host-or-ip/mqtt"

MqttServer:
  Listeners:
    - Id: Mqtt  # Tcp listener without authentication
      Port: 1883

    - Id: MqttWs  # Websocket listener with authentication
      Port: 1884
      Websocket: true  
      Username: system  # If empty, then any username can be used
      Password: manager  # If empty, then any or no password can be used





1.18.2 RAW VALUE TO ENGINEERING VALUE CONVERSION FOR PLC4X DRIVER

!! NOT AVAILABLE ANYMORE !!
It is now possible to define Groovy functions to convert raw values to
engineering values. Every incoming or outgoing value will be passed through the
defined functions. The functions can currently only be defined at the connection
level, so every value coming from this PLC connection goes through the
conversion functions.

Plc4x:
  Drivers:
    - Id: "niryo"
      Enabled: true
      Url: "modbus://192.168.1.9:5020"
      Polling:
        Time: 100
        Timeout: 90
        OldNew: true
      WriteTimeout: 100
      ReadTimeout: 100
      LogLevel: ALL
      Value:
        Reader: > # Here is the function for incoming values
          def x = value as int;
          return x > 32767 ? 32767 - x : x
        Writer: > # Here is the function for outgoing values
          def x = value as int;
          return x < 0 ? 32767 - x : x




If you need different functions for different addresses then you have to
implement a switch/case statement. The functions have two arguments: "address"
and "value".

Here is an example where a value conversion is done only for some addresses:

      Value:
        Reader: >
          def x = value as int;
          def xs = ["input-register:1:UINT", "input-register:2:UINT", "input-register:2:UINT"];
          if (xs.contains(address)) {
            println "convert!"
            return x > 32767 ? 32767 - x : x;
          } else {
            println("default")
            return x;
          } 





1.18.1 FEATURES AND FIXES IN PLC4X DRIVER

Auto reconnect to the PLC if the connection is lost.
Read/Write/Poll only if the connection to the PLC is up.
Fixed issue when writing a value fails (stopped the writing thread).
Fixed issue in unsubscribe with polling option (polling item was not removed).
Fixed issue when a client disconnects (unsubscribe with a list of topics
failed).
Added new configuration settings for the driver: timeout in ms for write, read,
polling.

Plc4x:
  Drivers:
    - Id: "machine1"
      Enabled: true
      Url: "modbus://127.0.0.1:502"
      Polling:
        Time: 1000
        Timeout: 900 # ms
        OldNew: true
      WriteTimeout: 100 # ms
      ReadTimeout: 100 # ms
      LogLevel: INFO





1.18 REMOVED APACHE IGNITE

Apache Ignite was removed due to its size and maintenance requirements. We don't
know of anyone using the Ignite option, so we decided to remove it.


1.17 ADDED CRATEDB AS SUPPORTED JDBC DATABASE FOR LOGGING

CrateDB is now also supported as JDBC database for logging. If the table
(default name "events") does not exists, it will be created partitioned by the
month of the source time with four shareds. But you can create the table
manually in advance with the settings of your needs.

CREATE TABLE IF NOT EXISTS $sqlTableName (
  "sys" TEXT, 
  "nodeid" TEXT,          
  "sourcetime" TIMESTAMP WITH TIME ZONE,
  "servertime" TIMESTAMP WITH TIME ZONE,
  "sourcetime_month" TIMESTAMP WITH TIME ZONE GENERATED ALWAYS AS date_trunc('month', "sourcetime"),
  "numericvalue" DOUBLE,
  "stringvalue" TEXT,
  "status" TEXT,
  PRIMARY KEY (sourcetime_month, sourcetime, sys, nodeid)
) CLUSTERED INTO 4 SHARDS PARTITIONED BY ("sourcetime_month");   





1.16 JDBC LOGGER TO WRITE FIELD VALUES TO RELATIONAL DATABASES

Added the option to log values to a JDBC compliant relational database. You have
to add the JDBC driver to your classpath and set the appropriate JDBC URL path
in the configuration file. PostgreSQL, MySQL and Microsoft SQL Server JDBC
drivers are already included in the build.gradle file (see
lib-jdbc/build.gradle) and also appropriate SQL statements are implemented for
those relational databases. If you use other JDBC drivers you can add the driver
to the lib-jdbc/build.gradle file as runtime only dependency and you may specify
SQL statements for insert and select in the configuration file.

You can specify the table name in the config file with the option
"SqlTableName", if you do not specify the table name then "events" will be used
as default name.

Specify JDBC drivers in the lib-jdbc/build.gradle file:

    runtimeOnly group: 'org.postgresql', name: 'postgresql', version: 'x.x.x'
    runtimeOnly group: 'mysql', name: 'mysql-connector-java', version: 'x.x.x'
    runtimeOnly group: 'com.microsoft.sqlserver', name: 'mssql-jdbc', version: 'x.x.x.jre11'




Create a table with this structure. For PostgreSQL, MySQL and Microsoft SQL
Server the table will be created on startup automatically.

  CREATE TABLE IF NOT EXISTS public.events
  (
      sys character varying(30) NOT NULL,
      nodeid character varying(30) NOT NULL,
      sourcetime timestamp without time zone NOT NULL,
      servertime timestamp without time zone NOT NULL,
      numericvalue numeric,
      stringvalue text,
      status character varying(30) ,
      CONSTRAINT pk_events PRIMARY KEY (system, nodeid, sourcetime)
  )
  TABLESPACE ts_scada;




Configuration of JDBC database logger:

Database:
  Logger:
    - Id: postgres
      Type: Jdbc
      Enabled: true
      Url: jdbc:postgresql://nuc1:5432/scada
      Username: system
      Password: manager
      SqlTableName: events     
      WriteParameters:
        QueueSize: 20000
        BlockSize: 10000
      Logging:
        - Topic: opc/opc1/path/Objects/Demo/SimulationMass/SimulationMass_SByte/+
        - Topic: opc/opc1/path/Objects/Demo/SimulationMass/SimulationMass_Byte/+




Because the SQL dialect can be slightly different with other databases, you can
specify the insert and select SQL statement in the config file:

Database:
  Logger:
    - Id: other
      Type: Jdbc
      Enabled: true
      Url: jdbc:other://nuc1:1111/scada
      Username: system
      Password: manager
      SqlTableName: events     
      SqlInsertStatement: > 
        INSERT INTO events (sys, nodeid, sourcetime, servertime, numericvalue, stringvalue, status)
         VALUES (?, ?, ?, ?, ?, ?, ?)
         ON CONFLICT ON CONSTRAINT PK_EVENTS DO NOTHING  
      SqlQueryStatement: >
        SELECT sourcetime, servertime, numericvalue, stringvalue, status
         FROM events
         WHERE sys = ? AND nodeid = ? AND sourcetime >= ? AND sourcetime <= ? 





1.15 NATS LOGGER TO WRITE FIELD VALUES TO A NATS SERVER

!! NOT AVAILABLE ANYMORE !!
Added a Nats Logger to write field values to a Nats server. It is like a
database logger, but it writes the values to a configurable Nats server. Any
values which get into Frankenstein (OPC UA, PLC4X, DDS, MQTT) by a Driver can be
logged to a Nats server. The values are stored in JSON format.

Database:
  Logger:
    - Id: nats1
      Type: Nats
      Enabled: true
      Url: "nats://nuc1:4222"
      WriteParameters:
        QueueSize: 20000
        BlockSize: 10000
      Logging:
        - Topic: opc/opc1/path/Objects/Demo/SimulationMass/SimulationMass_SByte/+
        - Topic: opc/opc1/path/Objects/Demo/SimulationMass/SimulationMass_Byte/+





1.14 FIXES AND OPTIMIZATIONS

 * MQTT Driver: Caching of NodeIds in MQTT Driver (topic to NodeId cache).
 * MQTT Server and Driver: max message size can be set in config file (e.g. for
   Video streaming).
 * GraphQL Define one or more starting/root NodeIds for GraphQL OPC UA schema
   import.
 * GraphQL: Renamed "NodeIds" to "RootNodes" in Schema section (for OCP UA )
 * Renamed TopicValueDDS to TopicValueJson.


1.13 MQTT LOGGER TO WRITE FIELD VALUES TO A MQTT BROKER

Added a MQTT Logger to write field values to a MQTT Broker. It is like a
database logger, but it writes the values to a configurable MQTT Broker. Any
values which get into Frankenstein (OPC UA, PLC4X, DDS, MQTT) by a Driver can be
logged to a MQTT Broker. The values are stored in JSON format.

Database:
  Logger:
    - Id: mqtt1
      Type: Mqtt
      Enabled: true
      Host: 192.168.1.169
      Port: 1883
      Ssl: false
      WriteParameters:
        QueueSize: 20000
        BlockSize: 10000
      Logging:
        - Topic: opc/smarthome/path/Meter_Input/WattAct
        - Topic: opc/smarthome/path/Meter_Output/WattAct
        - Topic: opc/smarthome/path/PV/Spot/+





1.12 MQTT DRIVER WITH GROOVY SCRIPT TRANSFORMER

!! NOT AVAILABLE ANYMORE !!
Added a inital version of a MQTT Driver to get values from a MQTT Broker into
Frankenstein. A Groovy script can be used to transform the values to an OPC UA
format, so that Frankenstein can be used to log those value to databases.
Functionality is currently very limited, only subscribe is implemented.

In this example we transform values of a MQTT Broker from this format:
{"TimeMS":1620327963328,"Value":10.277357833719135} to our internal
TopicValueOpc format by using a Groovy script and then log some topic values to
an InfluxDB.

MqttClient:
  - Id: "mqtt1"
    Enabled: true
    LogLevel: INFO
    Host: 192.168.1.6
    Port: 1883
    Ssl: false
    Value:
      Format: JSON
      Script: >
        return [ 
          className: "TopicValueOpc",
          sourceTime: Instant.ofEpochMilli(source.TimeMS).toString(),
          serverTime: Instant.now().toString(),
          value: source.Value,
          dataTypeId: 0,
          statusCode: 0 ]

Database:
  Logger:
    - Id: influx1
      Type: InfluxDB
      Enabled: true
      Url: http://192.168.1.13:8086
      Database: test
      Username: ""
      Password: ""
      WriteParameters:
        QueueSize: 20000
        BlockSize: 10000
      Logging:
        - Topic: mqtt/mqtt1/path/Meter_Input/WattAct
        - Topic: mqtt/mqtt1/path/Meter_Output/WattAct
        - Topic: mqtt/mqtt1/path/PV/Spot/+          





1.11 APACHE KAFKA DATABASE LOGGER

Added Apache Kafka as tag logger option, all incoming value changes of the
configured topics will be published to an Apache Kafka Broker. How to can be
found here

Database:
  Logger:
    - Id: kafka1
      Type: Kafka
      Servers: server2:9092
      WriteParameters:
        QueueSize: 20000
        BlockSize: 10000
      Logging:
        - Topic: opc/opc1/path/Objects/Demo/SimulationMass/SimulationMass_SByte/+
        - Topic: opc/opc1/path/Objects/Demo/SimulationMass/SimulationMass_Byte/+




You can also use KSQL to analyze the tag stream. Connect to a ksql cli:

docker exec -ti ksqldb-cli ksql http://ksqldb-server:8088




Create a stream for your logger. Each logger has its own Kafka-Topic. Topic name
is equal to the source id of the tag (opc/opc1/...)

CREATE STREAM opc1(
  browsePath VARCHAR KEY, 
  sourceTime VARCHAR, 
  value DOUBLE, 
  statusCode VARCHAR
) WITH (
  KEY_FORMAT='KAFKA',
  KAFKA_TOPIC='opc1', 
  VALUE_FORMAT='JSON',
  TIMESTAMP='sourceTime',TIMESTAMP_FORMAT='yyyy-MM-dd''T''HH:mm:ss[.n]X'
);




Example of a simple quey:

SELECT node, COUNT(*) 
FROM opc1 
WINDOW SESSION (10 SECONDS) 
WHERE node like '%_00' 
GROUP BY node 
EMIT CHANGES;





1.10 APACHE IOTDB DATABASE LOGGER

!! NOT AVAILABLE ANYMORE !! Separate Branch !!
Added Apache IoTDB as tag logger option.

Database:
  Logger:
   - Id: iotdb1
      Type: IoTDB
      Host: server2
      Port: 6667
      Database: root.scada1
      Username: "root"
      Password: "root"
      WriteParameters:
        QueueSize: 20000
        BlockSize: 10000
      Logging:
        - Topic: opc/opc1/path/Objects/Demo/SimulationMass/SimulationMass_SByte/+
        - Topic: opc/opc1/path/Objects/Demo/SimulationMass/SimulationMass_Byte/+





1.9 APACHE IGNITE AS CLUSTER OPTION AND IGNITE AS MEMORY-STORE

Added Apache Ignite as an option for clustering and also to use the Apache
Ignite Distributed In Memory Cache for storing last and history values coming
from OPC UA or other sources. With that enabled it is possible to do SQL queries
on the process values. The cache node stores historical value changes for a
defined timerange. So older values are purged on a regular basis (configurable
in the configuration file). It is configurable which topics should be stored in
the Apache Ignite Cache.

Cache:
  - Id: "global"
    Enabled: true
    LogLevel: INFO
    SqlIndexMaxInlineSize: 1000
    StoreHistoryValues: true
    Systems:
      - SystemType: Opc
        SystemName: "opcua1"
        PurgeEverySeconds: 10
        KeepLastSeconds: 180
      - SystemType: Opc
        SystemName: "opcua2"
        PurgeEverySeconds: 10        
        KeepLastSeconds: 180                                    
    Logging:
      - Topic: opc/opcua1/path/Objects/Demo/SimulationMass/SimulationMass_Double/+
      - Topic: opc/opcua2/path/Objects/Demo/SimulationMass/SimulationMass_Double/+




Updates on the tables are currently not possible. It is implemented that updates
on the updatevalue columns should write the values to the source OPC UA server,
but unfortunately it leads to unexpected behaviour of the Ignite Cluster.

There is a command line query tool available in the Apache Ignite Distribution.
But you can also download the JDBC driver or ODBC drive from the Apache Ignite
Website and use any other JDBC/ODBC client tool.

> C:\Tools\apache-ignite-2.9.1-bin\bin\sqlline.bat -u
> jdbc:ignite:thin://192.168.1.18

Example Queries:

select * from global.opcnode where nodeid like '%Mass%'  

select systemname, count(*) 
from global.opcvalue
 group by systemname;   

select systemname, count(*), min(sourcetime), max(sourcetime) 
from global.opcvaluehistory 
group by systemname;  




There are Docker examples available in the docker/examples directory.

> C:\Workspace\automation-gateway\source> gradle build
> C:\Workspace\automation-gateway\docker> build.bat
> C:\Workspace\automation-gateway\docker\examples\ignite> docker compose up -d


1.8 UPGRADE TO VERTX 4.0.3

Upgraded to VertX 4.0.3 and splitted the value type to a base class with
subclasses for Opc, Plc and DDS. The app names have been changed, the clustered
apps are now named with "cluster". DDS values can now be logged to InfluxDB.


1.7 DDS DRIVER (SUBSCRIBE AND PUBLISH)

!! NOT AVAILABLE ANYMORE !!
Added a first version of DDS support. Currently only MQTT subscribe and publish
to DDS topics are functional. It is the app-dds application, the app-gateway
must also be up and running.

You need to install OpenDDS and build it with Java support. And you also have to
compile your DDS IDL files with Java support. See the ReadMe.txt in the idl
directory of app-dds.

Example MQTT Topic:

> dds/system-id/path/topic-type-name/topic-name
> dds/demo/path/shape/Circle
> dds/demo/path/shape/Square

Configuration

DDS:
  Domains:
    - Id: "demo"
      Enabled: true
      LogLevel: ALL
      DCPSConfigFile: rtps.ini
      Domain: 0
      TopicTypes:
        - Id: "shape"
          TopicTypeName: "org.omg.dds.demo.ShapeType"
        - Id: "message"
          TopicTypeName: "org.omg.dds.demo.Message"





1.6 ADDED GRAPHIQL (HTTP://LOCALHOST:4000/GRAPHIQL/)

Added GraphiQL to the Gateway and optionally write the browsed schemas (OPC UA
and generated GraphQL scheam) to files.

GraphQLServer:
  Listeners:
    - Port: 4000
      LogLevel: ALL
      GraphiQL: true  # Enable GraphiQL
      WriteSchemaToFile: false  # Write GraphQL Schema to a file
      Schemas:
        - System: ignition
          FieldName: BrowseName # BrowseName | DisplayName
OpcUaClient:
  - Id: "ignition"
    Enabled: true





1.5 OPC UA SCHEMAS TO GRAPHQL SCHEMA IMPORTER

Support multiple OPC UA schemas in GraphQL. Be sure that you have set
BrowseOnStartup: true for the OPC UA servers which you want to embed in the
GraphQL schema. Additionally it can be defined which OPC UA field should be
taken as the GraphQL field name: it can be "BrowseName" or "DisplayName". But be
careful, the DisplayName must not be unique below a node, so it can lead to an
invalid schema.

GraphQLServer:
  Listeners:
    - Port: 4000
      LogLevel: ALL
      Schemas:
        - System: ignition
          FieldName: BrowseName # BrowseName | DisplayName
OpcUaClient:
  - Id: "ignition"
    Enabled: true





ABOUT

A OPC UA gateway which gives you access to your OPC UA values via MQTT or
GraphQL (HTTP). If you have an OPC UA server in your PLC, or a SCADA system with
an OPC UA server, you can query data from there via MQTT and GraphQL (HTTP). In
addition, the gateway can also log value changes from OPC UA nodes in an
InfluxDB, IoTDB, Kafka, and others.


TOPICS

java graphql mqtt influxdb neo4j jdbc gateway cratedb opc-ua opcua plc4x iotdb


RESOURCES

Readme


LICENSE

GPL-3.0 license
Activity


STARS

208 stars


WATCHERS

21 watching


FORKS

32 forks
Report repository


RELEASES

47 tags


SPONSOR THIS PROJECT

vogler75 Andreas Vogler
Sponsor
Learn more about GitHub Sponsors


PACKAGES 0

No packages published




LANGUAGES


 * Kotlin 92.7%
 * Java 3.9%
 * Batchfile 2.2%
 * Other 1.2%


FOOTER

© 2024 GitHub, Inc.


FOOTER NAVIGATION

 * Terms
 * Privacy
 * Security
 * Status
 * Docs
 * Contact
 * Manage cookies
 * Do not share my personal information

You can’t perform that action at this time.