From 68ceb37768fadd55a89ed26f6d8a4efdd913457b Mon Sep 17 00:00:00 2001 From: umbynos Date: Thu, 9 Sep 2021 18:05:33 +0200 Subject: [PATCH 1/9] copy stuff from https://github.com/arduino/serial-discovery/ (TODO rename and adapt stuff around) --- .codespellrc | 9 + .github/workflows/check-go-task.yml | 147 ++++ .github/workflows/check-license.yml | 66 ++ .github/workflows/check-markdown-task.yml | 66 ++ .../check-prettier-formatting-task.yml | 219 ++++++ .github/workflows/check-taskfiles.yml | 63 ++ .github/workflows/check-workflows-task.yml | 35 + .github/workflows/check-yaml-task.yml | 85 +++ .github/workflows/publish-go-tester-task.yml | 111 +++ .github/workflows/release-go-task.yml | 73 ++ .github/workflows/spell-check-task.yml | 41 ++ .github/workflows/sync-labels.yml | 132 ++++ .gitignore | 13 + .markdown-link-check.json | 5 + .markdownlint.yml | 62 ++ .yamllint.yml | 74 ++ DistTasks.yml | 253 +++++++ LICENSE.txt | 674 ++++++++++++++++++ README.md | 245 ++++++- Taskfile.yml | 201 ++++++ args/args.go | 41 ++ go.mod | 13 + go.sum | 31 + main.go | 77 ++ poetry.lock | 88 +++ pyproject.toml | 16 + sync/sync.go | 83 +++ sync/sync_darwin.go | 114 +++ sync/sync_default.go | 27 + sync/sync_linux.go | 95 +++ sync/sync_windows.go | 217 ++++++ sync/zsyscall_windows.go | 118 +++ version/version.go | 66 ++ 33 files changed, 3559 insertions(+), 1 deletion(-) create mode 100644 .codespellrc create mode 100644 .github/workflows/check-go-task.yml create mode 100644 .github/workflows/check-license.yml create mode 100644 .github/workflows/check-markdown-task.yml create mode 100644 .github/workflows/check-prettier-formatting-task.yml create mode 100644 .github/workflows/check-taskfiles.yml create mode 100644 .github/workflows/check-workflows-task.yml create mode 100644 .github/workflows/check-yaml-task.yml create mode 100644 .github/workflows/publish-go-tester-task.yml create mode 100644 .github/workflows/release-go-task.yml create mode 100644 .github/workflows/spell-check-task.yml create mode 100644 .github/workflows/sync-labels.yml create mode 100644 .gitignore create mode 100644 .markdown-link-check.json create mode 100644 .markdownlint.yml create mode 100644 .yamllint.yml create mode 100644 DistTasks.yml create mode 100644 LICENSE.txt create mode 100755 Taskfile.yml create mode 100644 args/args.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 main.go create mode 100644 poetry.lock create mode 100644 pyproject.toml create mode 100644 sync/sync.go create mode 100644 sync/sync_darwin.go create mode 100644 sync/sync_default.go create mode 100644 sync/sync_linux.go create mode 100644 sync/sync_windows.go create mode 100644 sync/zsyscall_windows.go create mode 100644 version/version.go diff --git a/.codespellrc b/.codespellrc new file mode 100644 index 0000000..cf10116 --- /dev/null +++ b/.codespellrc @@ -0,0 +1,9 @@ +# Source: https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/assets/spell-check/.codespellrc +# See: https://github.com/codespell-project/codespell#using-a-config-file +[codespell] +# In the event of a false positive, add the problematic word, in all lowercase, to a comma-separated list here: +ignore-words-list = , +builtin = clear,informal,en-GB_to_en-US +check-filenames = +check-hidden = +skip = ./.git,./go.mod,./go.sum,./package-lock.json,./poetry.lock,./yarn.lock diff --git a/.github/workflows/check-go-task.yml b/.github/workflows/check-go-task.yml new file mode 100644 index 0000000..8dce497 --- /dev/null +++ b/.github/workflows/check-go-task.yml @@ -0,0 +1,147 @@ +# Source: https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/check-go-task.md +name: Check Go + +env: + # See: https://github.com/actions/setup-go/tree/v2#readme + GO_VERSION: "1.16" + +# See: https://docs.github.com/en/actions/reference/events-that-trigger-workflows +on: + push: + paths: + - ".github/workflows/check-go-task.ya?ml" + - "Taskfile.ya?ml" + - "go.mod" + - "go.sum" + - "**.go" + pull_request: + paths: + - ".github/workflows/check-go-task.ya?ml" + - "Taskfile.ya?ml" + - "go.mod" + - "go.sum" + - "**.go" + workflow_dispatch: + repository_dispatch: + +jobs: + check-errors: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Install Go + uses: actions/setup-go@v2 + with: + go-version: ${{ env.GO_VERSION }} + + - name: Install Task + uses: arduino/setup-task@v1 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + version: 3.x + + - name: Check for errors + run: task go:vet + + check-outdated: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Install Go + uses: actions/setup-go@v2 + with: + go-version: ${{ env.GO_VERSION }} + + - name: Install Task + uses: arduino/setup-task@v1 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + version: 3.x + + - name: Modernize usages of outdated APIs + run: task go:fix + + - name: Check if any fixes were needed + run: git diff --color --exit-code + + check-style: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Install Go + uses: actions/setup-go@v2 + with: + go-version: ${{ env.GO_VERSION }} + + - name: Install Task + uses: arduino/setup-task@v1 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + version: 3.x + + - name: Install golint + run: go install golang.org/x/lint/golint@latest + + - name: Check style + run: task --silent go:lint + + check-formatting: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Install Go + uses: actions/setup-go@v2 + with: + go-version: ${{ env.GO_VERSION }} + + - name: Install Task + uses: arduino/setup-task@v1 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + version: 3.x + + - name: Format code + run: task go:format + + - name: Check formatting + run: git diff --color --exit-code + + check-config: + name: check-config (${{ matrix.module.path }}) + runs-on: ubuntu-latest + + strategy: + fail-fast: false + + matrix: + module: + # TODO: add paths of all Go modules here + - path: ./ + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Install Go + uses: actions/setup-go@v2 + with: + go-version: ${{ env.GO_VERSION }} + + - name: Run go mod tidy + working-directory: ${{ matrix.module.path }} + run: go mod tidy + + - name: Check whether any tidying was needed + run: git diff --color --exit-code diff --git a/.github/workflows/check-license.yml b/.github/workflows/check-license.yml new file mode 100644 index 0000000..4be2eca --- /dev/null +++ b/.github/workflows/check-license.yml @@ -0,0 +1,66 @@ +# Source: https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/check-license.md +name: Check License + +env: + # TODO: Define the project's license file name here: + EXPECTED_LICENSE_FILENAME: LICENSE.txt + # SPDX identifier: https://spdx.org/licenses/ + # TODO: Define the project's license type here + EXPECTED_LICENSE_TYPE: GPL-3.0 + +# See: https://docs.github.com/en/actions/reference/events-that-trigger-workflows +on: + push: + paths: + - ".github/workflows/check-license.ya?ml" + # See: https://github.com/licensee/licensee/blob/master/docs/what-we-look-at.md#detecting-the-license-file + - "[cC][oO][pP][yY][iI][nN][gG]*" + - "[cC][oO][pP][yY][rR][iI][gG][hH][tH]*" + - "[lL][iI][cC][eE][nN][cCsS][eE]*" + - "[oO][fF][lL]*" + - "[pP][aA][tT][eE][nN][tT][sS]*" + pull_request: + paths: + - ".github/workflows/check-license.ya?ml" + - "[cC][oO][pP][yY][iI][nN][gG]*" + - "[cC][oO][pP][yY][rR][iI][gG][hH][tH]*" + - "[lL][iI][cC][eE][nN][cCsS][eE]*" + - "[oO][fF][lL]*" + - "[pP][aA][tT][eE][nN][tT][sS]*" + workflow_dispatch: + repository_dispatch: + +jobs: + check-license: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Install Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: ruby # Install latest version + + - name: Install licensee + run: gem install licensee + + - name: Check license file + run: | + EXIT_STATUS=0 + # See: https://github.com/licensee/licensee + LICENSEE_OUTPUT="$(licensee detect --json --confidence=100)" + DETECTED_LICENSE_FILE="$(echo "$LICENSEE_OUTPUT" | jq .matched_files[0].filename | tr --delete '\r')" + echo "Detected license file: $DETECTED_LICENSE_FILE" + if [ "$DETECTED_LICENSE_FILE" != "\"${EXPECTED_LICENSE_FILENAME}\"" ]; then + echo "::error file=${DETECTED_LICENSE_FILE}::detected license file $DETECTED_LICENSE_FILE doesn't match expected: $EXPECTED_LICENSE_FILENAME" + EXIT_STATUS=1 + fi + DETECTED_LICENSE_TYPE="$(echo "$LICENSEE_OUTPUT" | jq .matched_files[0].matched_license | tr --delete '\r')" + echo "Detected license type: $DETECTED_LICENSE_TYPE" + if [ "$DETECTED_LICENSE_TYPE" != "\"${EXPECTED_LICENSE_TYPE}\"" ]; then + echo "::error file=${DETECTED_LICENSE_FILE}::detected license type $DETECTED_LICENSE_TYPE doesn't match expected \"${EXPECTED_LICENSE_TYPE}\"" + EXIT_STATUS=1 + fi + exit $EXIT_STATUS diff --git a/.github/workflows/check-markdown-task.yml b/.github/workflows/check-markdown-task.yml new file mode 100644 index 0000000..1dd9350 --- /dev/null +++ b/.github/workflows/check-markdown-task.yml @@ -0,0 +1,66 @@ +# Source: https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/check-markdown-task.md +name: Check Markdown + +# See: https://docs.github.com/en/actions/reference/events-that-trigger-workflows +on: + push: + paths: + - ".github/workflows/check-markdown-task.ya?ml" + - ".markdown-link-check.json" + - "Taskfile.ya?ml" + - "**/.markdownlint*" + - "**.mdx?" + - "**.mkdn" + - "**.mdown" + - "**.markdown" + pull_request: + paths: + - ".github/workflows/check-markdown-task.ya?ml" + - ".markdown-link-check.json" + - "Taskfile.ya?ml" + - "**/.markdownlint*" + - "**.mdx?" + - "**.mkdn" + - "**.mdown" + - "**.markdown" + schedule: + # Run every Tuesday at 8 AM UTC to catch breakage caused by external changes. + - cron: "0 8 * * TUE" + workflow_dispatch: + repository_dispatch: + +jobs: + lint: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Initialize markdownlint-cli problem matcher + uses: xt0rted/markdownlint-problem-matcher@v1 + + - name: Install Task + uses: arduino/setup-task@v1 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + version: 3.x + + - name: Lint + run: task markdown:lint + + links: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Install Task + uses: arduino/setup-task@v1 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + version: 3.x + + - name: Check links + run: task --silent markdown:check-links diff --git a/.github/workflows/check-prettier-formatting-task.yml b/.github/workflows/check-prettier-formatting-task.yml new file mode 100644 index 0000000..caccbcf --- /dev/null +++ b/.github/workflows/check-prettier-formatting-task.yml @@ -0,0 +1,219 @@ +# Source: https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/check-prettier-formatting-task.md +name: Check Prettier Formatting + +# See: https://docs.github.com/en/free-pro-team@latest/actions/reference/events-that-trigger-workflows +on: + push: + paths: + - ".github/workflows/check-prettier-formatting-task.ya?ml" + - "Taskfile.ya?ml" + - "**/.prettierignore" + - "**/.prettierrc*" + # CSS + - "**.css" + - "**.wxss" + # PostCSS + - "**.pcss" + - "**.postcss" + # Less + - "**.less" + # SCSS + - "**.scss" + # GraphQL + - "**.graphqls?" + - "**.gql" + # handlebars + - "**.handlebars" + - "**.hbs" + # HTML + - "**.mjml" + - "**.html?" + - "**.html.hl" + - "**.st" + - "**.xht" + - "**.xhtml" + # Vue + - "**.vue" + # JavaScript + - "**.flow" + - "**._?jsb?" + - "**.bones" + - "**.cjs" + - "**.es6?" + - "**.frag" + - "**.gs" + - "**.jake" + - "**.jscad" + - "**.jsfl" + - "**.js[ms]" + - "**.[mn]js" + - "**.pac" + - "**.wxs" + - "**.[xs]s?js" + - "**.xsjslib" + # JSX + - "**.jsx" + # TypeScript + - "**.ts" + # TSX + - "**.tsx" + # JSON + - "**/.eslintrc" + - "**.json" + - "**.avsc" + - "**.geojson" + - "**.gltf" + - "**.har" + - "**.ice" + - "**.JSON-tmLanguage" + - "**.mcmeta" + - "**.tfstate" + - "**.topojson" + - "**.webapp" + - "**.webmanifest" + - "**.yyp?" + # JSONC + - "**/.babelrc" + - "**/.jscsrc" + - "**/.js[hl]intrc" + - "**.jsonc" + - "**.sublime-*" + # JSON5 + - "**.json5" + # Markdown + - "**.mdx?" + - "**.markdown" + - "**.mk?down" + - "**.mdwn" + - "**.mkdn?" + - "**.ronn" + - "**.workbook" + # YAML + - "**/.clang-format" + - "**/.clang-tidy" + - "**/.gemrc" + - "**/glide.lock" + - "**.ya?ml*" + - "**.mir" + - "**.reek" + - "**.rviz" + - "**.sublime-syntax" + - "**.syntax" + pull_request: + paths: + - ".github/workflows/check-prettier-formatting-task.ya?ml" + - "Taskfile.ya?ml" + - "**/.prettierignore" + - "**/.prettierrc*" + # CSS + - "**.css" + - "**.wxss" + # PostCSS + - "**.pcss" + - "**.postcss" + # Less + - "**.less" + # SCSS + - "**.scss" + # GraphQL + - "**.graphqls?" + - "**.gql" + # handlebars + - "**.handlebars" + - "**.hbs" + # HTML + - "**.mjml" + - "**.html?" + - "**.html.hl" + - "**.st" + - "**.xht" + - "**.xhtml" + # Vue + - "**.vue" + # JavaScript + - "**.flow" + - "**._?jsb?" + - "**.bones" + - "**.cjs" + - "**.es6?" + - "**.frag" + - "**.gs" + - "**.jake" + - "**.jscad" + - "**.jsfl" + - "**.js[ms]" + - "**.[mn]js" + - "**.pac" + - "**.wxs" + - "**.[xs]s?js" + - "**.xsjslib" + # JSX + - "**.jsx" + # TypeScript + - "**.ts" + # TSX + - "**.tsx" + # JSON + - "**/.eslintrc" + - "**.json" + - "**.avsc" + - "**.geojson" + - "**.gltf" + - "**.har" + - "**.ice" + - "**.JSON-tmLanguage" + - "**.mcmeta" + - "**.tfstate" + - "**.topojson" + - "**.webapp" + - "**.webmanifest" + - "**.yyp?" + # JSONC + - "**/.babelrc" + - "**/.jscsrc" + - "**/.js[hl]intrc" + - "**.jsonc" + - "**.sublime-*" + # JSON5 + - "**.json5" + # Markdown + - "**.mdx?" + - "**.markdown" + - "**.mk?down" + - "**.mdwn" + - "**.mkdn?" + - "**.ronn" + - "**.workbook" + # YAML + - "**/.clang-format" + - "**/.clang-tidy" + - "**/.gemrc" + - "**/glide.lock" + - "**.ya?ml*" + - "**.mir" + - "**.reek" + - "**.rviz" + - "**.sublime-syntax" + - "**.syntax" + workflow_dispatch: + repository_dispatch: + +jobs: + check: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Install Task + uses: arduino/setup-task@v1 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + version: 3.x + + - name: Format with Prettier + run: task general:format-prettier + + - name: Check formatting + run: git diff --color --exit-code diff --git a/.github/workflows/check-taskfiles.yml b/.github/workflows/check-taskfiles.yml new file mode 100644 index 0000000..29fe090 --- /dev/null +++ b/.github/workflows/check-taskfiles.yml @@ -0,0 +1,63 @@ +# Source: https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/check-taskfiles.md +name: Check Taskfiles + +# See: https://docs.github.com/en/actions/reference/events-that-trigger-workflows +on: + push: + paths: + - ".github/workflows/check-taskfiles.ya?ml" + - "**/Taskfile.ya?ml" + - "**/DistTasks.ya?ml" + pull_request: + paths: + - ".github/workflows/check-taskfiles.ya?ml" + - "**/Taskfile.ya?ml" + - "**/DistTasks.ya?ml" + schedule: + # Run every Tuesday at 8 AM UTC to catch breakage resulting from changes to the JSON schema. + - cron: "0 8 * * TUE" + workflow_dispatch: + repository_dispatch: + +jobs: + validate: + name: Validate ${{ matrix.file }} + runs-on: ubuntu-latest + + strategy: + fail-fast: false + + matrix: + file: + # TODO: add paths to any additional Taskfiles here + - ./**/Taskfile.yml + - ./DistTasks.yml + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Download JSON schema for Taskfiles + id: download-schema + uses: carlosperate/download-file-action@v1.0.3 + with: + # See: https://github.com/SchemaStore/schemastore/blob/master/src/schemas/json/taskfile.json + file-url: https://json.schemastore.org/taskfile.json + location: ${{ runner.temp }}/taskfile-schema + + - name: Install JSON schema validator + run: | + sudo npm install \ + --global \ + ajv-cli \ + ajv-formats + + - name: Validate ${{ matrix.file }} + run: | + # See: https://github.com/ajv-validator/ajv-cli#readme + ajv validate \ + --all-errors \ + --strict=false \ + -c ajv-formats \ + -s "${{ steps.download-schema.outputs.file-path }}" \ + -d "${{ matrix.file }}" diff --git a/.github/workflows/check-workflows-task.yml b/.github/workflows/check-workflows-task.yml new file mode 100644 index 0000000..5d433c4 --- /dev/null +++ b/.github/workflows/check-workflows-task.yml @@ -0,0 +1,35 @@ +# Source: https://github.com/arduino/tooling-project-assets/blob/master/workflow-templates/check-workflows-task.md +name: Check Workflows + +# See: https://docs.github.com/en/actions/reference/events-that-trigger-workflows +on: + push: + paths: + - ".github/workflows/*.ya?ml" + - "Taskfile.ya?ml" + pull_request: + paths: + - ".github/workflows/*.ya?ml" + - "Taskfile.ya?ml" + schedule: + # Run every Tuesday at 8 AM UTC to catch breakage resulting from changes to the JSON schema. + - cron: "0 8 * * TUE" + workflow_dispatch: + repository_dispatch: + +jobs: + validate: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Install Task + uses: arduino/setup-task@v1 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + version: 3.x + + - name: Validate workflows + run: task --silent ci:validate diff --git a/.github/workflows/check-yaml-task.yml b/.github/workflows/check-yaml-task.yml new file mode 100644 index 0000000..54b5f87 --- /dev/null +++ b/.github/workflows/check-yaml-task.yml @@ -0,0 +1,85 @@ +# Source: https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/check-yaml-task.md +name: Check YAML + +env: + # See: https://github.com/actions/setup-python/tree/v2#available-versions-of-python + PYTHON_VERSION: "3.9" + +# See: https://docs.github.com/en/actions/reference/events-that-trigger-workflows +on: + push: + paths: + - ".yamllint*" + - "poetry.lock" + - "pyproject.toml" + # Source: https://github.com/ikatyang/linguist-languages/blob/master/data/YAML.json (used by Prettier) + - "**/.clang-format" + - "**/.clang-tidy" + - "**/.gemrc" + - "**/glide.lock" + - "**.ya?ml*" + - "**.mir" + - "**.reek" + - "**.rviz" + - "**.sublime-syntax" + - "**.syntax" + pull_request: + paths: + - ".yamllint*" + - "poetry.lock" + - "pyproject.toml" + # Source: https://github.com/ikatyang/linguist-languages/blob/master/data/YAML.json (used by Prettier) + - "**/.clang-format" + - "**/.clang-tidy" + - "**/.gemrc" + - "**/glide.lock" + - "**.ya?ml*" + - "**.mir" + - "**.reek" + - "**.rviz" + - "**.sublime-syntax" + - "**.syntax" + workflow_dispatch: + repository_dispatch: + +jobs: + check: + name: ${{ matrix.configuration.name }} + runs-on: ubuntu-latest + + strategy: + fail-fast: false + + matrix: + configuration: + - name: Generate problem matcher output + # yamllint's "github" output type produces annotated diffs, but is not useful to humans reading the log. + format: github + # The other matrix job is used to set the result, so this job is configured to always pass. + continue-on-error: true + - name: Check formatting + # yamllint's "colored" output type is most suitable for humans reading the log. + format: colored + continue-on-error: false + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Install Python + uses: actions/setup-python@v2 + with: + python-version: ${{ env.PYTHON_VERSION }} + + - name: Install Poetry + run: pip install poetry + + - name: Install Task + uses: arduino/setup-task@v1 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + version: 3.x + + - name: Check YAML + continue-on-error: ${{ matrix.configuration.continue-on-error }} + run: task yaml:lint YAMLLINT_FORMAT=${{ matrix.configuration.format }} diff --git a/.github/workflows/publish-go-tester-task.yml b/.github/workflows/publish-go-tester-task.yml new file mode 100644 index 0000000..383aecb --- /dev/null +++ b/.github/workflows/publish-go-tester-task.yml @@ -0,0 +1,111 @@ +# Source: https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/publish-go-tester-task.md +name: Publish Tester Build + +# See: https://docs.github.com/en/free-pro-team@latest/actions/reference/events-that-trigger-workflows +on: + push: + paths: + - ".github/workflows/publish-go-tester-task.ya?ml" + - "go.mod" + - "go.sum" + - "Taskfile.ya?ml" + - "DistTasks.ya?ml" + - "**.go" + pull_request: + paths: + - ".github/workflows/publish-go-tester-task.ya?ml" + - "go.mod" + - "go.sum" + - "Taskfile.ya?ml" + - "DistTasks.ya?ml" + - "**.go" + workflow_dispatch: + repository_dispatch: + +env: + # As defined by the Taskfile's DIST_DIR variable + DIST_DIR: dist + BUILDS_ARTIFACT: build-artifacts + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + with: + fetch-depth: 0 + + - name: Install Task + uses: arduino/setup-task@v1 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + version: 3.x + + - name: Build + run: | + PACKAGE_NAME_PREFIX="test" + if [ "${{ github.event_name }}" = "pull_request" ]; then + PACKAGE_NAME_PREFIX="$PACKAGE_NAME_PREFIX-${{ github.event.number }}" + fi + PACKAGE_NAME_PREFIX="$PACKAGE_NAME_PREFIX-${{ github.sha }}-" + export PACKAGE_NAME_PREFIX + task dist:all + + # Transfer builds to artifacts job + - name: Upload combined builds artifact + uses: actions/upload-artifact@v2 + with: + path: ${{ env.DIST_DIR }} + name: ${{ env.BUILDS_ARTIFACT }} + + artifacts: + name: ${{ matrix.artifact.name }} artifact + needs: build + runs-on: ubuntu-latest + + strategy: + matrix: + artifact: + - path: "*checksums.txt" + name: checksums + - path: "*Linux_32bit.tar.gz" + name: Linux_X86-32 + - path: "*Linux_64bit.tar.gz" + name: Linux_X86-64 + - path: "*Linux_ARM64.tar.gz" + name: Linux_ARM64 + - path: "*Linux_ARMv6.tar.gz" + name: Linux_ARMv6 + - path: "*Linux_ARMv7.tar.gz" + name: Linux_ARMv7 + - path: "*macOS_64bit.tar.gz" + name: macOS_64 + - path: "*Windows_32bit.zip" + name: Windows_X86-32 + - path: "*Windows_64bit.zip" + name: Windows_X86-64 + + steps: + - name: Download combined builds artifact + uses: actions/download-artifact@v2 + with: + name: ${{ env.BUILDS_ARTIFACT }} + path: ${{ env.BUILDS_ARTIFACT }} + + - name: Upload individual build artifact + uses: actions/upload-artifact@v2 + with: + path: ${{ env.BUILDS_ARTIFACT }}/${{ matrix.artifact.path }} + name: ${{ matrix.artifact.name }} + + clean: + needs: artifacts + runs-on: ubuntu-latest + + steps: + - name: Remove unneeded combined builds artifact + uses: geekyeggo/delete-artifact@v1 + with: + name: ${{ env.BUILDS_ARTIFACT }} diff --git a/.github/workflows/release-go-task.yml b/.github/workflows/release-go-task.yml new file mode 100644 index 0000000..180b2d2 --- /dev/null +++ b/.github/workflows/release-go-task.yml @@ -0,0 +1,73 @@ +# Source: https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/release-go-task.md +name: Release + +env: + # As defined by the Taskfile's PROJECT_NAME variable + PROJECT_NAME: serial-discovery + # As defined by the Taskfile's DIST_DIR variable + DIST_DIR: dist + # The project's folder on Arduino's download server for uploading builds + AWS_PLUGIN_TARGET: /discovery/serial-discovery/ + ARTIFACT_NAME: dist + +on: + push: + tags: + - "v[0-9]+.[0-9]+.[0-9]+*" + +jobs: + create-release-artifacts: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + with: + fetch-depth: 0 + + - name: Create changelog + uses: arduino/create-changelog@v1 + with: + tag-regex: '^v[0-9]+\.[0-9]+\.[0-9]+.*$' + filter-regex: '^\[(skip|changelog)[ ,-](skip|changelog)\].*' + case-insensitive-regex: true + changelog-file-path: "${{ env.DIST_DIR }}/CHANGELOG.md" + + - name: Install Task + uses: arduino/setup-task@v1 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + version: 3.x + + - name: Build + run: task dist:all + + - name: Identify Prerelease + # This is a workaround while waiting for create-release action + # to implement auto pre-release based on tag + id: prerelease + run: | + wget -q -P /tmp https://github.com/fsaintjacques/semver-tool/archive/3.0.0.zip + unzip -p /tmp/3.0.0.zip semver-tool-3.0.0/src/semver >/tmp/semver && chmod +x /tmp/semver + if [[ "$(/tmp/semver get prerel "${GITHUB_REF/refs\/tags\//}")" ]]; then echo "::set-output name=IS_PRE::true"; fi + + - name: Create Github Release and upload artifacts + uses: ncipollo/release-action@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + bodyFile: ${{ env.DIST_DIR }}/CHANGELOG.md + draft: false + prerelease: ${{ steps.prerelease.outputs.IS_PRE }} + # NOTE: "Artifact is a directory" warnings are expected and don't indicate a problem + # (all the files we need are in the DIST_DIR root) + artifacts: ${{ env.DIST_DIR }}/* + + - name: Upload release files on Arduino downloads servers + uses: docker://plugins/s3 + env: + PLUGIN_SOURCE: "${{ env.DIST_DIR }}/*" + PLUGIN_TARGET: ${{ env.AWS_PLUGIN_TARGET }} + PLUGIN_STRIP_PREFIX: "${{ env.DIST_DIR }}/" + PLUGIN_BUCKET: ${{ secrets.DOWNLOADS_BUCKET }} + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} diff --git a/.github/workflows/spell-check-task.yml b/.github/workflows/spell-check-task.yml new file mode 100644 index 0000000..3b59435 --- /dev/null +++ b/.github/workflows/spell-check-task.yml @@ -0,0 +1,41 @@ +# Source: https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/spell-check-task.md +name: Spell Check + +env: + # See: https://github.com/actions/setup-python/tree/v2#available-versions-of-python + PYTHON_VERSION: "3.9" + +# See: https://docs.github.com/en/free-pro-team@latest/actions/reference/events-that-trigger-workflows +on: + push: + pull_request: + schedule: + # Run every Tuesday at 8 AM UTC to catch new misspelling detections resulting from dictionary updates. + - cron: "0 8 * * TUE" + workflow_dispatch: + repository_dispatch: + +jobs: + spellcheck: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Install Python + uses: actions/setup-python@v2 + with: + python-version: ${{ env.PYTHON_VERSION }} + + - name: Install Poetry + run: pip install poetry + + - name: Install Task + uses: arduino/setup-task@v1 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + version: 3.x + + - name: Spell check + run: task general:check-spelling diff --git a/.github/workflows/sync-labels.yml b/.github/workflows/sync-labels.yml new file mode 100644 index 0000000..fc5329f --- /dev/null +++ b/.github/workflows/sync-labels.yml @@ -0,0 +1,132 @@ +# Source: https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/sync-labels.md +name: Sync Labels + +# See: https://docs.github.com/en/actions/reference/events-that-trigger-workflows +on: + push: + paths: + - ".github/workflows/sync-labels.ya?ml" + - ".github/label-configuration-files/*.ya?ml" + pull_request: + paths: + - ".github/workflows/sync-labels.ya?ml" + - ".github/label-configuration-files/*.ya?ml" + schedule: + # Run daily at 8 AM UTC to sync with changes to shared label configurations. + - cron: "0 8 * * *" + workflow_dispatch: + repository_dispatch: + +env: + CONFIGURATIONS_FOLDER: .github/label-configuration-files + CONFIGURATIONS_ARTIFACT: label-configuration-files + +jobs: + check: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Download JSON schema for labels configuration file + id: download-schema + uses: carlosperate/download-file-action@v1.0.3 + with: + file-url: https://raw.githubusercontent.com/arduino/tooling-project-assets/main/workflow-templates/assets/sync-labels/arduino-tooling-gh-label-configuration-schema.json + location: ${{ runner.temp }}/label-configuration-schema + + - name: Install JSON schema validator + run: | + sudo npm install \ + --global \ + ajv-cli \ + ajv-formats + + - name: Validate local labels configuration + run: | + # See: https://github.com/ajv-validator/ajv-cli#readme + ajv validate \ + --all-errors \ + -c ajv-formats \ + -s "${{ steps.download-schema.outputs.file-path }}" \ + -d "${{ env.CONFIGURATIONS_FOLDER }}/*.{yml,yaml}" + + download: + needs: check + runs-on: ubuntu-latest + + strategy: + matrix: + filename: + # Filenames of the shared configurations to apply to the repository in addition to the local configuration. + # https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/assets/sync-labels + - universal.yml + + steps: + - name: Download + uses: carlosperate/download-file-action@v1.0.3 + with: + file-url: https://raw.githubusercontent.com/arduino/tooling-project-assets/main/workflow-templates/assets/sync-labels/${{ matrix.filename }} + + - name: Pass configuration files to next job via workflow artifact + uses: actions/upload-artifact@v2 + with: + path: | + *.yaml + *.yml + if-no-files-found: error + name: ${{ env.CONFIGURATIONS_ARTIFACT }} + + sync: + needs: download + runs-on: ubuntu-latest + + steps: + - name: Set environment variables + run: | + # See: https://docs.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-an-environment-variable + echo "MERGED_CONFIGURATION_PATH=${{ runner.temp }}/labels.yml" >> "$GITHUB_ENV" + + - name: Determine whether to dry run + id: dry-run + if: > + github.event == 'pull_request' || + github.ref != format('refs/heads/{0}', github.event.repository.default_branch) + run: | + # Use of this flag in the github-label-sync command will cause it to only check the validity of the + # configuration. + echo "::set-output name=flag::--dry-run" + + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Download configuration files artifact + uses: actions/download-artifact@v2 + with: + name: ${{ env.CONFIGURATIONS_ARTIFACT }} + path: ${{ env.CONFIGURATIONS_FOLDER }} + + - name: Remove unneeded artifact + uses: geekyeggo/delete-artifact@v1 + with: + name: ${{ env.CONFIGURATIONS_ARTIFACT }} + + - name: Merge label configuration files + run: | + # Merge all configuration files + shopt -s extglob + cat "${{ env.CONFIGURATIONS_FOLDER }}"/*.@(yml|yaml) > "${{ env.MERGED_CONFIGURATION_PATH }}" + + - name: Install github-label-sync + run: sudo npm install --global github-label-sync + + - name: Sync labels + env: + GITHUB_ACCESS_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + # See: https://github.com/Financial-Times/github-label-sync + github-label-sync \ + --labels "${{ env.MERGED_CONFIGURATION_PATH }}" \ + ${{ steps.dry-run.outputs.flag }} \ + ${{ github.repository }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ddde031 --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +# Build artifacts +serial-discovery +serial-discovery.exe +__pycache__/ + +# IDEs +.idea/ +.vscode/ +*.bak +*.code-workspace +*.sublime-workspace + +/dist diff --git a/.markdown-link-check.json b/.markdown-link-check.json new file mode 100644 index 0000000..da79879 --- /dev/null +++ b/.markdown-link-check.json @@ -0,0 +1,5 @@ +{ + "retryOn429": true, + "retryCount": 3, + "aliveStatusCodes": [200, 206] +} diff --git a/.markdownlint.yml b/.markdownlint.yml new file mode 100644 index 0000000..65b6ef7 --- /dev/null +++ b/.markdownlint.yml @@ -0,0 +1,62 @@ +# Source: https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/assets/check-markdown/.markdownlint.yml +# See: https://github.com/DavidAnson/markdownlint/blob/main/doc/Rules.md +# The code style defined in this file is the official standardized style to be used in all Arduino projects and should +# not be modified. +# Note: Rules disabled solely because they are redundant to Prettier are marked with a "Prettier" comment. + +default: false +MD001: false +MD002: false +MD003: false # Prettier +MD004: false # Prettier +MD005: false # Prettier +MD006: false # Prettier +MD007: false # Prettier +MD008: false # Prettier +MD009: + br_spaces: 0 + strict: true + list_item_empty_lines: false # Prettier +MD010: false # Prettier +MD011: true +MD012: false # Prettier +MD013: false +MD014: false +MD018: true +MD019: false # Prettier +MD020: true +MD021: false # Prettier +MD022: false # Prettier +MD023: false # Prettier +MD024: false +MD025: + level: 1 + front_matter_title: '^\s*"?title"?\s*[:=]' +MD026: false +MD027: false # Prettier +MD028: false +MD029: + style: one +MD030: + ul_single: 1 + ol_single: 1 + ul_multi: 1 + ol_multi: 1 +MD031: false # Prettier +MD032: false # Prettier +MD033: false +MD034: false +MD035: false # Prettier +MD036: false +MD037: true +MD038: true +MD039: true +MD040: false +MD041: false +MD042: true +MD043: false +MD044: false +MD045: true +MD046: + style: fenced +MD047: false # Prettier diff --git a/.yamllint.yml b/.yamllint.yml new file mode 100644 index 0000000..e235f87 --- /dev/null +++ b/.yamllint.yml @@ -0,0 +1,74 @@ +# Source: https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/assets/check-yaml/.yamllint.yml +# See: https://yamllint.readthedocs.io/en/stable/configuration.html +# The code style defined in this file is the official standardized style to be used in all Arduino tooling projects and +# should not be modified. +# Note: Rules disabled solely because they are redundant to Prettier are marked with a "Prettier" comment. + +rules: + braces: + level: error + forbid: non-empty + min-spaces-inside: -1 # Prettier + max-spaces-inside: -1 # Prettier + min-spaces-inside-empty: -1 # Prettier + max-spaces-inside-empty: -1 # Prettier + brackets: + level: error + forbid: non-empty + min-spaces-inside: -1 # Prettier + max-spaces-inside: -1 # Prettier + min-spaces-inside-empty: -1 # Prettier + max-spaces-inside-empty: -1 # Prettier + colons: disable # Prettier + commas: disable # Prettier + comments: disable # Prettier + comments-indentation: disable # Prettier + document-end: disable # Prettier + document-start: disable + empty-lines: disable # Prettier + empty-values: disable + hyphens: disable # Prettier + indentation: disable # Prettier + key-duplicates: disable # Prettier + key-ordering: disable + line-length: + level: warning + max: 120 + allow-non-breakable-words: true + allow-non-breakable-inline-mappings: true + new-line-at-end-of-file: disable # Prettier + new-lines: disable # Prettier + octal-values: + level: warning + forbid-implicit-octal: true + forbid-explicit-octal: false + quoted-strings: disable + trailing-spaces: disable # Prettier + truthy: + level: error + allowed-values: + - "true" + - "false" + - "on" # Used by GitHub Actions as a workflow key. + check-keys: true + +yaml-files: + # Source: https://github.com/ikatyang/linguist-languages/blob/master/data/YAML.json (used by Prettier) + - ".clang-format" + - ".clang-tidy" + - ".gemrc" + - ".yamllint" + - "glide.lock" + - "*.yml" + - "*.mir" + - "*.reek" + - "*.rviz" + - "*.sublime-syntax" + - "*.syntax" + - "*.yaml" + - "*.yaml-tmlanguage" + - "*.yaml.sed" + - "*.yml.mysql" + +ignore: | + /.git/ diff --git a/DistTasks.yml b/DistTasks.yml new file mode 100644 index 0000000..95edf2e --- /dev/null +++ b/DistTasks.yml @@ -0,0 +1,253 @@ +# Source: https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/assets/release-go-task/DistTasks.yml +version: "3" + +# This taskfile is ideally meant to be project agnostic and could be dropped in +# on other Go projects with minimal or no changes. +# +# To use it simply add the following lines to your main taskfile: +# includes: +# dist: ./DistTasks.yml +# +# The following variables must be declared in the including taskfile for the +# build process to work correctly: +# * DIST_DIR: the folder that will contain the final binaries and packages +# * PROJECT_NAME: the name of the project, used in package name +# * VERSION: the version of the project, used in package name and checksum file +# * LD_FLAGS: flags used at build time +# +# The project MUST contain a LICENSE.txt file in the root folder or packaging will fail. + +vars: + CONTAINER: "docker.elastic.co/beats-dev/golang-crossbuild" + GO_VERSION: "1.16.4" + CHECKSUM_FILE: "{{.VERSION}}-checksums.txt" + +tasks: + all: + desc: Build for distribution for all platforms + cmds: + - task: Windows_32bit + - task: Windows_64bit + - task: Linux_32bit + - task: Linux_64bit + - task: Linux_ARMv6 + - task: Linux_ARMv7 + - task: Linux_ARM64 + - task: macOS_64bit + + Windows_32bit: + desc: Builds Windows 32 bit binaries + dir: "{{.DIST_DIR}}" + cmds: + - | + docker run -v `pwd`/..:/home/build -w /home/build \ + -e CGO_ENABLED=1 \ + {{.CONTAINER}}:{{.CONTAINER_TAG}} \ + --build-cmd "{{.BUILD_COMMAND}}" \ + -p "{{.BUILD_PLATFORM}}" + + zip {{.PACKAGE_NAME}} {{.PLATFORM_DIR}}/{{.PROJECT_NAME}}.exe ../LICENSE.txt -j + sha256sum {{.PACKAGE_NAME}} >> {{.CHECKSUM_FILE}} + + vars: + PLATFORM_DIR: "{{.PROJECT_NAME}}_windows_386" + BUILD_COMMAND: "go build -o {{.DIST_DIR}}/{{.PLATFORM_DIR}}/{{.PROJECT_NAME}}.exe {{.LDFLAGS}}" + BUILD_PLATFORM: "windows/386" + CONTAINER_TAG: "{{.GO_VERSION}}-main" + PACKAGE_PLATFORM: "Windows_32bit" + PACKAGE_NAME: "{{.PROJECT_NAME}}_{{.VERSION}}_{{.PACKAGE_PLATFORM}}.zip" + + Windows_64bit: + desc: Builds Windows 64 bit binaries + dir: "{{.DIST_DIR}}" + cmds: + - | + docker run -v `pwd`/..:/home/build -w /home/build \ + -e CGO_ENABLED=1 \ + {{.CONTAINER}}:{{.CONTAINER_TAG}} \ + --build-cmd "{{.BUILD_COMMAND}}" \ + -p "{{.BUILD_PLATFORM}}" + + zip {{.PACKAGE_NAME}} {{.PLATFORM_DIR}}/{{.PROJECT_NAME}}.exe ../LICENSE.txt -j + sha256sum {{.PACKAGE_NAME}} >> {{.CHECKSUM_FILE}} + + vars: + PLATFORM_DIR: "{{.PROJECT_NAME}}_windows_amd64" + BUILD_COMMAND: "go build -o {{.DIST_DIR}}/{{.PLATFORM_DIR}}/{{.PROJECT_NAME}}.exe {{.LDFLAGS}}" + BUILD_PLATFORM: "windows/amd64" + CONTAINER_TAG: "{{.GO_VERSION}}-main" + PACKAGE_PLATFORM: "Windows_64bit" + PACKAGE_NAME: "{{.PROJECT_NAME}}_{{.VERSION}}_{{.PACKAGE_PLATFORM}}.zip" + + Linux_32bit: + desc: Builds Linux 32 bit binaries + dir: "{{.DIST_DIR}}" + cmds: + - | + docker run -v `pwd`/..:/home/build -w /home/build \ + -e CGO_ENABLED=1 \ + {{.CONTAINER}}:{{.CONTAINER_TAG}} \ + --build-cmd "{{.BUILD_COMMAND}}" \ + -p "{{.BUILD_PLATFORM}}" + + tar cz -C {{.PLATFORM_DIR}} {{.PROJECT_NAME}} -C ../.. LICENSE.txt -f {{.PACKAGE_NAME}} + sha256sum {{.PACKAGE_NAME}} >> {{.CHECKSUM_FILE}} + + vars: + PLATFORM_DIR: "{{.PROJECT_NAME}}_linux_amd32" + BUILD_COMMAND: "go build -o {{.DIST_DIR}}/{{.PLATFORM_DIR}}/{{.PROJECT_NAME}} {{.LDFLAGS}}" + BUILD_PLATFORM: "linux/386" + CONTAINER_TAG: "{{.GO_VERSION}}-main" + PACKAGE_PLATFORM: "Linux_32bit" + PACKAGE_NAME: "{{.PROJECT_NAME}}_{{.VERSION}}_{{.PACKAGE_PLATFORM}}.tar.gz" + + Linux_64bit: + desc: Builds Linux 64 bit binaries + dir: "{{.DIST_DIR}}" + cmds: + - | + docker run -v `pwd`/..:/home/build -w /home/build \ + -e CGO_ENABLED=1 \ + {{.CONTAINER}}:{{.CONTAINER_TAG}} \ + --build-cmd "{{.BUILD_COMMAND}}" \ + -p "{{.BUILD_PLATFORM}}" + + tar cz -C {{.PLATFORM_DIR}} {{.PROJECT_NAME}} -C ../.. LICENSE.txt -f {{.PACKAGE_NAME}} + sha256sum {{.PACKAGE_NAME}} >> {{.CHECKSUM_FILE}} + + vars: + PLATFORM_DIR: "{{.PROJECT_NAME}}_linux_amd64" + BUILD_COMMAND: "go build -o {{.DIST_DIR}}/{{.PLATFORM_DIR}}/{{.PROJECT_NAME}} {{.LDFLAGS}}" + BUILD_PLATFORM: "linux/amd64" + CONTAINER_TAG: "{{.GO_VERSION}}-main" + PACKAGE_PLATFORM: "Linux_64bit" + PACKAGE_NAME: "{{.PROJECT_NAME}}_{{.VERSION}}_{{.PACKAGE_PLATFORM}}.tar.gz" + + Linux_ARMv7: + desc: Builds Linux ARMv7 binaries + dir: "{{.DIST_DIR}}" + cmds: + - | + docker run -v `pwd`/..:/home/build -w /home/build \ + -e CGO_ENABLED=1 \ + {{.CONTAINER}}:{{.CONTAINER_TAG}} \ + --build-cmd "{{.BUILD_COMMAND}}" \ + -p "{{.BUILD_PLATFORM}}" + + tar cz -C {{.PLATFORM_DIR}} {{.PROJECT_NAME}} -C ../.. LICENSE.txt -f {{.PACKAGE_NAME}} + sha256sum {{.PACKAGE_NAME}} >> {{.CHECKSUM_FILE}} + + vars: + PLATFORM_DIR: "{{.PROJECT_NAME}}_linux_arm_7" + BUILD_COMMAND: "go build -o {{.DIST_DIR}}/{{.PLATFORM_DIR}}/{{.PROJECT_NAME}} {{.LDFLAGS}}" + BUILD_PLATFORM: "linux/armv7" + CONTAINER_TAG: "{{.GO_VERSION}}-armhf" + PACKAGE_PLATFORM: "Linux_ARMv7" + PACKAGE_NAME: "{{.PROJECT_NAME}}_{{.VERSION}}_{{.PACKAGE_PLATFORM}}.tar.gz" + + Linux_ARMv6: + desc: Builds Linux ARMv6 binaries + dir: "{{.DIST_DIR}}" + cmds: + - | + docker run -v `pwd`/..:/home/build -w /home/build \ + -e CGO_ENABLED=1 \ + {{.CONTAINER}}:{{.CONTAINER_TAG}} \ + --build-cmd "{{.BUILD_COMMAND}}" \ + -p "{{.BUILD_PLATFORM}}" + + tar cz -C {{.PLATFORM_DIR}} {{.PROJECT_NAME}} -C ../.. LICENSE.txt -f {{.PACKAGE_NAME}} + sha256sum {{.PACKAGE_NAME}} >> {{.CHECKSUM_FILE}} + + vars: + PLATFORM_DIR: "{{.PROJECT_NAME}}_linux_arm_6" + BUILD_COMMAND: "go build -o {{.DIST_DIR}}/{{.PLATFORM_DIR}}/{{.PROJECT_NAME}} {{.LDFLAGS}}" + BUILD_PLATFORM: "linux/armv6" + # We are experiencing the following error with ARMv6 build: + # + # # github.com/arduino/arduino-cli + # net(.text): unexpected relocation type 296 (R_ARM_V4BX) + # panic: runtime error: invalid memory address or nil pointer dereference + # [signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x51ae53] + # + # goroutine 1 [running]: + # cmd/link/internal/loader.(*Loader).SymName(0xc000095c00, 0x0, 0xc0000958d8, 0x5a0ac) + # /usr/local/go/src/cmd/link/internal/loader/loader.go:684 +0x53 + # cmd/link/internal/ld.dynrelocsym2(0xc000095880, 0x5a0ac) + # /usr/local/go/src/cmd/link/internal/ld/data.go:777 +0x295 + # cmd/link/internal/ld.(*dodataState).dynreloc2(0xc007df9800, 0xc000095880) + # /usr/local/go/src/cmd/link/internal/ld/data.go:794 +0x89 + # cmd/link/internal/ld.(*Link).dodata2(0xc000095880, 0xc007d00000, 0x60518, 0x60518) + # /usr/local/go/src/cmd/link/internal/ld/data.go:1434 +0x4d4 + # cmd/link/internal/ld.Main(0x8729a0, 0x4, 0x8, 0x1, 0xd, 0xe, 0x0, 0x0, 0x6d7737, 0x12, ...) + # /usr/local/go/src/cmd/link/internal/ld/main.go:302 +0x123a + # main.main() + # /usr/local/go/src/cmd/link/main.go:68 +0x1dc + # Error: failed building for linux/armv6: exit status 2 + # + # This seems to be a problem in the go builder 1.16.x that removed support for the R_ARM_V4BX instruction: + # https://github.com/golang/go/pull/44998 + # https://groups.google.com/g/golang-codereviews/c/yzN80xxwu2E + # + # Until there is a fix released we must use a recent gcc for Linux_ARMv6 build, so for this + # build we select the debian10 based container. + CONTAINER_TAG: "{{.GO_VERSION}}-armel-debian10" + PACKAGE_PLATFORM: "Linux_ARMv6" + PACKAGE_NAME: "{{.PROJECT_NAME}}_{{.VERSION}}_{{.PACKAGE_PLATFORM}}.tar.gz" + + Linux_ARM64: + desc: Builds Linux ARM64 binaries + dir: "{{.DIST_DIR}}" + cmds: + - | + docker run -v `pwd`/..:/home/build -w /home/build \ + -e CGO_ENABLED=1 \ + {{.CONTAINER}}:{{.CONTAINER_TAG}} \ + --build-cmd "{{.BUILD_COMMAND}}" \ + -p "{{.BUILD_PLATFORM}}" + + tar cz -C {{.PLATFORM_DIR}} {{.PROJECT_NAME}} -C ../.. LICENSE.txt -f {{.PACKAGE_NAME}} + sha256sum {{.PACKAGE_NAME}} >> {{.CHECKSUM_FILE}} + + vars: + PLATFORM_DIR: "{{.PROJECT_NAME}}_linux_arm_6" + BUILD_COMMAND: "go build -o {{.DIST_DIR}}/{{.PLATFORM_DIR}}/{{.PROJECT_NAME}} {{.LDFLAGS}}" + BUILD_PLATFORM: "linux/arm64" + CONTAINER_TAG: "{{.GO_VERSION}}-arm" + PACKAGE_PLATFORM: "Linux_ARM64" + PACKAGE_NAME: "{{.PROJECT_NAME}}_{{.VERSION}}_{{.PACKAGE_PLATFORM}}.tar.gz" + + macOS_64bit: + desc: Builds Mac OS X 64 bit binaries + dir: "{{.DIST_DIR}}" + cmds: + - | + docker run -v `pwd`/..:/home/build -w /home/build \ + -e CGO_ENABLED=1 \ + {{.CONTAINER}}:{{.CONTAINER_TAG}} \ + --build-cmd "{{.BUILD_COMMAND}}" \ + -p "{{.BUILD_PLATFORM}}" + + tar cz -C {{.PLATFORM_DIR}} {{.PROJECT_NAME}} -C ../.. LICENSE.txt -f {{.PACKAGE_NAME}} + sha256sum {{.PACKAGE_NAME}} >> {{.CHECKSUM_FILE}} + + vars: + PLATFORM_DIR: "{{.PROJECT_NAME}}_osx_darwin_amd64" + BUILD_COMMAND: "go build -o {{.DIST_DIR}}/{{.PLATFORM_DIR}}/{{.PROJECT_NAME}} {{.LDFLAGS}}" + BUILD_PLATFORM: "darwin/amd64" + # We are experiencing the following error with macOS_64bit build: + # + # Undefined symbols for architecture x86_64: + # "_clock_gettime", referenced from: + # _runtime.walltime_trampoline in go.o + # ld: symbol(s) not found for architecture x86_64 + # clang: error: linker command failed with exit code 1 (use -v to see invocation) + # + # The reason seems that go 1.16.x use a macos API which is available since 10.12 + # https://github.com/techknowlogick/xgo/issues/100#issuecomment-780894190 + # + # To compile it we need an SDK >=10.12 so we use the debian10 based container that + # has the SDK 10.14 installed. + CONTAINER_TAG: "{{.GO_VERSION}}-darwin-debian10" + PACKAGE_PLATFORM: "macOS_64bit" + PACKAGE_NAME: "{{.PROJECT_NAME}}_{{.VERSION}}_{{.PACKAGE_PLATFORM}}.tar.gz" diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/README.md b/README.md index e83c135..00f1d23 100644 --- a/README.md +++ b/README.md @@ -1 +1,244 @@ -# serial-monitor +# Arduino pluggable discovery for serial ports + +The `serial-discovery` tool is a command line program that interacts via stdio. It accepts commands as plain ASCII strings terminated with LF `\n` and sends response as JSON. + +## How to build + +Install a recent go environment (>=13.0) and run `go build`. The executable `serial-discovery` will be produced in your working directory. + +## Usage + +After startup, the tool waits for commands. The available commands are: `HELLO`, `START`, `STOP`, `QUIT`, `LIST` and `START_SYNC`. + +#### HELLO command + +The `HELLO` command is used to establish the pluggable discovery protocol between client and discovery. +The format of the command is: + +`HELLO ""` + +for example: + +`HELLO 1 "Arduino IDE"` + +or: + +`HELLO 1 "arduino-cli"` + +in this case the protocol version requested by the client is `1` (at the moment of writing there were no other revisions of the protocol). +The response to the command is: + +```json +{ + "eventType": "hello", + "protocolVersion": 1, + "message": "OK" +} +``` + +`protocolVersion` is the protocol version that the discovery is going to use in the remainder of the communication. + +#### START command + +The `START` starts the internal subroutines of the discovery that looks for ports. This command must be called before `LIST` or `START_SYNC`. The response to the start command is: + +```json +{ + "eventType": "start", + "message": "OK" +} +``` + +#### STOP command + +The `STOP` command stops the discovery internal subroutines and free some resources. This command should be called if the client wants to pause the discovery for a while. The response to the stop command is: + +```json +{ + "eventType": "stop", + "message": "OK" +} +``` + +#### QUIT command + +The `QUIT` command terminates the discovery. The response to quit is: + +```json +{ + "eventType": "quit", + "message": "OK" +} +``` + +after this output the tool quits. + +#### LIST command + +The `LIST` command returns a list of the currently available serial ports. The format of the response is the following: + +```json +{ + "eventType": "list", + "ports": [ + { + "address": "/dev/ttyACM0", + "label": "/dev/ttyACM0", + "properties": { + "pid": "0x804e", + "vid": "0x2341", + "serialNumber": "EBEABFD6514D32364E202020FF10181E" + }, + "protocol": "serial", + "protocolLabel": "Serial Port (USB)" + } + ] +} +``` + +The `ports` field contains a list of the available serial ports. If the serial port comes from an USB serial converter the USB VID/PID and USB SERIAL NUMBER properties are also reported inside `properties`. + +The list command is a one-shot command, if you need continuous monitoring of ports you should use `START_SYNC` command. + +#### START_SYNC command + +The `START_SYNC` command puts the tool in "events" mode: the discovery will send `add` and `remove` events each time a new port is detected or removed respectively. +The immediate response to the command is: + +```json +{ + "eventType": "start_sync", + "message": "OK" +} +``` + +after that the discovery enters in "events" mode. + +The `add` events looks like the following: + +```json +{ + "eventType": "add", + "port": { + "address": "/dev/ttyACM0", + "label": "/dev/ttyACM0", + "properties": { + "pid": "0x804e", + "vid": "0x2341", + "serialNumber": "EBEABFD6514D32364E202020FF10181E" + }, + "protocol": "serial", + "protocolLabel": "Serial Port (USB)" + } +} +``` + +it basically gather the same information as the `list` event but for a single port. After calling `START_SYNC` a bunch of `add` events may be generated in sequence to report all the ports available at the moment of the start. + +The `remove` event looks like this: + +```json +{ + "eventType": "remove", + "port": { + "address": "/dev/ttyACM0", + "protocol": "serial" + } +} +``` + +in this case only the `address` and `protocol` fields are reported. + +### Example of usage + +A possible transcript of the discovery usage: + +``` +$ ./serial-discovery +START +{ + "eventType": "start", + "message": "OK" +} +LIST +{ + "eventType": "list", + "ports": [ + { + "address": "/dev/ttyACM0", + "label": "/dev/ttyACM0", + "protocol": "serial", + "protocolLabel": "Serial Port (USB)", + "properties": { + "pid": "0x004e", + "serialNumber": "EBEABFD6514D32364E202020FF10181E", + "vid": "0x2341" + } + } + ] +} +START_SYNC +{ + "eventType": "start_sync", + "message": "OK" +} +{ <--- this event has been immediately sent + "eventType": "add", + "port": { + "address": "/dev/ttyACM0", + "label": "/dev/ttyACM0", + "protocol": "serial", + "protocolLabel": "Serial Port (USB)", + "properties": { + "pid": "0x004e", + "serialNumber": "EBEABFD6514D32364E202020FF10181E", + "vid": "0x2341" + } + } +} +{ <--- the board has been disconnected here + "eventType": "remove", + "port": { + "address": "/dev/ttyACM0", + "protocol": "serial" + } +} +{ <--- the board has been connected again + "eventType": "add", + "port": { + "address": "/dev/ttyACM0", + "label": "/dev/ttyACM0", + "protocol": "serial", + "protocolLabel": "Serial Port (USB)", + "properties": { + "pid": "0x004e", + "serialNumber": "EBEABFD6514D32364E202020FF10181E", + "vid": "0x2341" + } + } +} +QUIT +{ + "eventType": "quit", + "message": "OK" +} +$ +``` + +## Security + +If you think you found a vulnerability or other security-related bug in this project, please read our +[security policy](https://github.com/arduino/serial-discovery/security/policy) and report the bug to our Security Team 🛡️ +Thank you! + +e-mail contact: security@arduino.cc + +## License + +Copyright (c) 2018 ARDUINO SA (www.arduino.cc) + +The software is released under the GNU General Public License, which covers the main body +of the serial-discovery code. The terms of this license can be found at: +https://www.gnu.org/licenses/gpl-3.0.en.html + +See [LICENSE.txt](https://github.com/arduino/serial-discovery/blob/master/LICENSE.txt) for details. diff --git a/Taskfile.yml b/Taskfile.yml new file mode 100755 index 0000000..7583786 --- /dev/null +++ b/Taskfile.yml @@ -0,0 +1,201 @@ +version: "3" + +includes: + dist: ./DistTasks.yml + +vars: + DEFAULT_GO_PACKAGES: + sh: echo $(go list ./... | tr '\n' ' ') + PROJECT_NAME: "serial-discovery" + DIST_DIR: "dist" + # build vars + COMMIT: + sh: echo "$(git log --no-show-signature -n 1 --format=%h)" + TAG: + sh: echo "$(git tag --points-at=HEAD 2> /dev/null | head -n1)" + TIMESTAMP: + sh: echo "$(date -u +"%Y-%m-%dT%H:%M:%SZ")" + TIMESTAMP_SHORT: + sh: echo "{{now | date "20060102"}}" + VERSION: "{{if .NIGHTLY}}nightly-{{.TIMESTAMP_SHORT}}{{else if .TAG}}{{.TAG}}{{else}}{{.PACKAGE_NAME_PREFIX}}git-snapshot{{end}}" + CONFIGURATION_PACKAGE: github.com/arduino/serial-discovery/version + LDFLAGS: > + -ldflags + ' + -X {{.CONFIGURATION_PACKAGE}}.Version={{.VERSION}} + -X {{.CONFIGURATION_PACKAGE}}.Commit={{.COMMIT}} + -X {{.CONFIGURATION_PACKAGE}}.Timestamp={{.TIMESTAMP}} + ' + +tasks: + build: + desc: Build the project + cmds: + - go build -v {{.LDFLAGS}} + + # Source: https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/assets/check-go-task/Taskfile.yml + go:check: + desc: Check for problems with Go code + deps: + - task: go:vet + - task: go:lint + + # Source: https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/assets/check-go-task/Taskfile.yml + go:vet: + desc: Check for errors in Go code + cmds: + - go vet {{default .DEFAULT_GO_PACKAGES .GO_PACKAGES}} + + # Source: https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/assets/check-go-task/Taskfile.yml + go:fix: + desc: Modernize usages of outdated APIs + cmds: + - go fix {{default .DEFAULT_GO_PACKAGES .GO_PACKAGES}} + + # Source: https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/assets/check-go-task/Taskfile.yml + go:lint: + desc: Lint Go code + cmds: + - | + if ! which golint &>/dev/null; then + echo "golint not installed or not in PATH. Please install: https://github.com/golang/lint#installation" + exit 1 + fi + - | + golint \ + {{default "-min_confidence 0.8 -set_exit_status" .GO_LINT_FLAGS}} \ + {{default .DEFAULT_GO_PACKAGES .GO_PACKAGES}} + + # Source: https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/assets/check-go-task/Taskfile.yml + go:format: + desc: Format Go code + cmds: + - go fmt {{default .DEFAULT_GO_PACKAGES .GO_PACKAGES}} + + # Source: https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/assets/check-prettier-formatting-task/Taskfile.yml + general:format-prettier: + desc: Format all supported files with Prettier + cmds: + - npx prettier --write . + + # Source: https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/assets/poetry-task/Taskfile.yml + poetry:install-deps: + desc: Install dependencies managed by Poetry + cmds: + - poetry install --no-root + + # Source: https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/assets/poetry-task/Taskfile.yml + poetry:update-deps: + desc: Update all dependencies managed by Poetry to their newest versions + cmds: + - poetry update + + docs:generate: + desc: Create all generated documentation content + # This is an "umbrella" task used to call any documentation generation processes the project has. + # It can be left empty if there are none. + + # Source: https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/assets/check-markdown-task/Taskfile.yml + markdown:lint: + desc: Check for problems in Markdown files + cmds: + - npx markdownlint-cli "**/*.md" + + # Source: https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/assets/check-markdown-task/Taskfile.yml + markdown:fix: + desc: Automatically correct linting violations in Markdown files where possible + cmds: + - npx markdownlint-cli --fix "**/*.md" + + # Source: https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/assets/check-markdown-task/Taskfile.yml + markdown:check-links: + desc: Check for broken links + deps: + - task: docs:generate + cmds: + - | + if [[ "{{.OS}}" == "Windows_NT" ]]; then + # npx --call uses the native shell, which makes it too difficult to use npx for this application on Windows, + # so the Windows user is required to have markdown-link-check installed and in PATH. + if ! which markdown-link-check &>/dev/null; then + echo "markdown-link-check not found or not in PATH. Please install: https://github.com/tcort/markdown-link-check#readme" + exit 1 + fi + # Default behavior of the task on Windows is to exit the task when the first broken link causes a non-zero + # exit status, but it's better to check all links before exiting. + set +o errexit + STATUS=0 + # Using -regex instead of -name to avoid Task's behavior of globbing even when quoted on Windows + # The odd method for escaping . in the regex is required for windows compatibility because mvdan.cc/sh gives + # \ characters special treatment on Windows in an attempt to support them as path separators. + for file in $(find . -regex ".*[.]md"); do + markdown-link-check \ + --quiet \ + --config "./.markdown-link-check.json" \ + "$file" + STATUS=$(( $STATUS + $? )) + done + exit $STATUS + else + npx --package=markdown-link-check --call=' + STATUS=0 + for file in $(find . -regex ".*[.]md"); do + markdown-link-check \ + --quiet \ + --config "./.markdown-link-check.json" \ + "$file" + STATUS=$(( $STATUS + $? )) + done + exit $STATUS + ' + fi + + # Source: https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/assets/check-workflows-task/Taskfile.yml + ci:validate: + desc: Validate GitHub Actions workflows against their JSON schema + vars: + # Source: https://github.com/SchemaStore/schemastore/blob/master/src/schemas/json/github-workflow.json + WORKFLOW_SCHEMA_URL: https://json.schemastore.org/github-workflow + WORKFLOW_SCHEMA_PATH: + sh: mktemp -t workflow-schema-XXXXXXXXXX.json + WORKFLOWS_DATA_PATH: "./.github/workflows/*.{yml,yaml}" + cmds: + - | + wget \ + --quiet \ + --output-document="{{.WORKFLOW_SCHEMA_PATH}}" \ + {{.WORKFLOW_SCHEMA_URL}} + - | + npx \ + --package=ajv-cli \ + --package=ajv-formats \ + ajv validate \ + --all-errors \ + --strict=false \ + -c ajv-formats \ + -s "{{.WORKFLOW_SCHEMA_PATH}}" \ + -d "{{.WORKFLOWS_DATA_PATH}}" + + # Source: https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/assets/check-yaml-task/Taskfile.yml + yaml:lint: + desc: Check for problems with YAML files + deps: + - task: poetry:install-deps + cmds: + - poetry run yamllint --format {{default "colored" .YAMLLINT_FORMAT}} . + + # Source: https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/assets/spell-check-task/Taskfile.yml + general:check-spelling: + desc: Check for commonly misspelled words + deps: + - task: poetry:install-deps + cmds: + - poetry run codespell + + # Source: https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/assets/spell-check-task/Taskfile.yml + general:correct-spelling: + desc: Correct commonly misspelled words where possible + deps: + - task: poetry:install-deps + cmds: + - poetry run codespell --write-changes diff --git a/args/args.go b/args/args.go new file mode 100644 index 0000000..c6d2d77 --- /dev/null +++ b/args/args.go @@ -0,0 +1,41 @@ +// +// This file is part of serial-discovery. +// +// Copyright 2021 ARDUINO SA (http://www.arduino.cc/) +// +// This software is released under the GNU General Public License version 3, +// which covers the main part of arduino-cli. +// The terms of this license can be found at: +// https://www.gnu.org/licenses/gpl-3.0.en.html +// +// You can be released from the requirements of the above licenses by purchasing +// a commercial license. Buying such a license is mandatory if you want to modify or +// otherwise use the software for commercial activities involving the Arduino +// software without disclosing the source code of your own applications. To purchase +// a commercial license, send an email to license@arduino.cc. +// + +package args + +import ( + "fmt" + "os" +) + +// ShowVersion FIXMEDOC +var ShowVersion bool + +// Parse arguments passed by the user +func Parse() { + for _, arg := range os.Args[1:] { + if arg == "" { + continue + } + if arg == "-v" || arg == "--version" { + ShowVersion = true + continue + } + fmt.Fprintf(os.Stderr, "invalid argument: %s\n", arg) + os.Exit(1) + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..03a7ed8 --- /dev/null +++ b/go.mod @@ -0,0 +1,13 @@ +module github.com/arduino/serial-discovery + +require ( + github.com/arduino/go-paths-helper v1.6.1 // indirect + github.com/arduino/go-properties-orderedmap v1.6.0 + github.com/arduino/pluggable-discovery-protocol-handler/v2 v2.0.1 + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/s-urbaniak/uevent v1.0.1 + go.bug.st/serial v1.3.1 + golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069 +) + +go 1.16 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..d4eaf08 --- /dev/null +++ b/go.sum @@ -0,0 +1,31 @@ +github.com/arduino/go-paths-helper v1.0.1/go.mod h1:HpxtKph+g238EJHq4geEPv9p+gl3v5YYu35Yb+w31Ck= +github.com/arduino/go-paths-helper v1.6.1 h1:lha+/BuuBsx0qTZ3gy6IO1kU23lObWdQ/UItkzVWQ+0= +github.com/arduino/go-paths-helper v1.6.1/go.mod h1:V82BWgAAp4IbmlybxQdk9Bpkz8M4Qyx+RAFKaG9NuvU= +github.com/arduino/go-properties-orderedmap v1.4.0/go.mod h1:DKjD2VXY/NZmlingh4lSFMEYCVubfeArCsGPGDwb2yk= +github.com/arduino/go-properties-orderedmap v1.6.0 h1:gp2JoWRETtqwsZ+UHu/PBuYWYH2x2+d+uipDxS4WmvM= +github.com/arduino/go-properties-orderedmap v1.6.0/go.mod h1:DKjD2VXY/NZmlingh4lSFMEYCVubfeArCsGPGDwb2yk= +github.com/arduino/pluggable-discovery-protocol-handler/v2 v2.0.1 h1:THeCPZWeGfnvtPvxAiCqgEdRhdfERgycHd35NPP9UMQ= +github.com/arduino/pluggable-discovery-protocol-handler/v2 v2.0.1/go.mod h1:tQXPIAEHcc5k8HBsufLsWmju0D3WiJt07j+dFT55NGY= +github.com/creack/goselect v0.1.2 h1:2DNy14+JPjRBgPzAd1thbQp4BSIihxcBf0IXhQXDRa0= +github.com/creack/goselect v0.1.2/go.mod h1:a/NhLweNvqIYMuxcMOuWY516Cimucms3DglDzQP3hKY= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/s-urbaniak/uevent v1.0.1 h1:Zy6g16gR1m6iRB+scDtyExP9KRYnuSdTWOYhBE5O620= +github.com/s-urbaniak/uevent v1.0.1/go.mod h1:h83SVc1NQj/EubPaK4hWyiG217xLYr+fS+Jf10COrG0= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +go.bug.st/serial v1.3.1 h1:ziLU+w7RzBZKMGacM/6iY6P7bhxsoyaoVLWcfAdt6w4= +go.bug.st/serial v1.3.1/go.mod h1:8TT7u/SwwNIpJ8QaG4s+HTjFt9ReXs2cdOU7ZEk50Dk= +golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069 h1:siQdpVirKtzPhKl3lZWozZraCFObP8S1v6PRp0bLrtU= +golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/main.go b/main.go new file mode 100644 index 0000000..c8071d6 --- /dev/null +++ b/main.go @@ -0,0 +1,77 @@ +// +// This file is part of serial-discovery. +// +// Copyright 2018-2021 ARDUINO SA (http://www.arduino.cc/) +// +// This software is released under the GNU General Public License version 3, +// which covers the main part of arduino-cli. +// The terms of this license can be found at: +// https://www.gnu.org/licenses/gpl-3.0.en.html +// +// You can be released from the requirements of the above licenses by purchasing +// a commercial license. Buying such a license is mandatory if you want to modify or +// otherwise use the software for commercial activities involving the Arduino +// software without disclosing the source code of your own applications. To purchase +// a commercial license, send an email to license@arduino.cc. +// + +package main + +import ( + "fmt" + "os" + + discovery "github.com/arduino/pluggable-discovery-protocol-handler/v2" + "github.com/arduino/serial-discovery/args" + "github.com/arduino/serial-discovery/sync" + "github.com/arduino/serial-discovery/version" +) + +func main() { + args.Parse() + if args.ShowVersion { + fmt.Printf("%s\n", version.VersionInfo) + return + } + + serialDisc := &SerialDiscovery{} + disc := discovery.NewServer(serialDisc) + if err := disc.Run(os.Stdin, os.Stdout); err != nil { + fmt.Fprintf(os.Stderr, "Error: %s\n", err.Error()) + os.Exit(1) + } +} + +// SerialDiscovery is the implementation of the serial ports pluggable-discovery +type SerialDiscovery struct { + closeChan chan<- bool +} + +// Hello is the handler for the pluggable-discovery HELLO command +func (d *SerialDiscovery) Hello(userAgent string, protocolVersion int) error { + return nil +} + +// Quit is the handler for the pluggable-discovery QUIT command +func (d *SerialDiscovery) Quit() { +} + +// Stop is the handler for the pluggable-discovery STOP command +func (d *SerialDiscovery) Stop() error { + if d.closeChan != nil { + d.closeChan <- true + close(d.closeChan) + d.closeChan = nil + } + return nil +} + +// StartSync is the handler for the pluggable-discovery START_SYNC command +func (d *SerialDiscovery) StartSync(eventCB discovery.EventCallback, errorCB discovery.ErrorCallback) error { + close, err := sync.Start(eventCB, errorCB) + if err != nil { + return err + } + d.closeChan = close + return nil +} diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..a99920a --- /dev/null +++ b/poetry.lock @@ -0,0 +1,88 @@ +[[package]] +name = "codespell" +version = "2.1.0" +description = "Codespell" +category = "dev" +optional = false +python-versions = ">=3.5" + +[package.extras] +dev = ["check-manifest", "flake8", "pytest", "pytest-cov", "pytest-dependency"] +hard-encoding-detection = ["chardet"] + +[[package]] +name = "pathspec" +version = "0.9.0" +description = "Utility library for gitignore style pattern matching of file paths." +category = "dev" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" + +[[package]] +name = "pyyaml" +version = "5.4.1" +description = "YAML parser and emitter for Python" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" + +[[package]] +name = "yamllint" +version = "1.26.2" +description = "A linter for YAML files." +category = "dev" +optional = false +python-versions = ">=3.5" + +[package.dependencies] +pathspec = ">=0.5.3" +pyyaml = "*" + +[metadata] +lock-version = "1.1" +python-versions = "^3.9" +content-hash = "5e7ea9a9dbd153e58702281bb1e6efca9e8bddce3f608c3ffd59843f89984f6a" + +[metadata.files] +codespell = [ + {file = "codespell-2.1.0-py3-none-any.whl", hash = "sha256:b864c7d917316316ac24272ee992d7937c3519be4569209c5b60035ac5d569b5"}, + {file = "codespell-2.1.0.tar.gz", hash = "sha256:19d3fe5644fef3425777e66f225a8c82d39059dcfe9edb3349a8a2cf48383ee5"}, +] +pathspec = [ + {file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"}, + {file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"}, +] +pyyaml = [ + {file = "PyYAML-5.4.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922"}, + {file = "PyYAML-5.4.1-cp27-cp27m-win32.whl", hash = "sha256:129def1b7c1bf22faffd67b8f3724645203b79d8f4cc81f674654d9902cb4393"}, + {file = "PyYAML-5.4.1-cp27-cp27m-win_amd64.whl", hash = "sha256:4465124ef1b18d9ace298060f4eccc64b0850899ac4ac53294547536533800c8"}, + {file = "PyYAML-5.4.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185"}, + {file = "PyYAML-5.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253"}, + {file = "PyYAML-5.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:4e0583d24c881e14342eaf4ec5fbc97f934b999a6828693a99157fde912540cc"}, + {file = "PyYAML-5.4.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:72a01f726a9c7851ca9bfad6fd09ca4e090a023c00945ea05ba1638c09dc3347"}, + {file = "PyYAML-5.4.1-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:895f61ef02e8fed38159bb70f7e100e00f471eae2bc838cd0f4ebb21e28f8541"}, + {file = "PyYAML-5.4.1-cp36-cp36m-win32.whl", hash = "sha256:3bd0e463264cf257d1ffd2e40223b197271046d09dadf73a0fe82b9c1fc385a5"}, + {file = "PyYAML-5.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df"}, + {file = "PyYAML-5.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018"}, + {file = "PyYAML-5.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63"}, + {file = "PyYAML-5.4.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:cb333c16912324fd5f769fff6bc5de372e9e7a202247b48870bc251ed40239aa"}, + {file = "PyYAML-5.4.1-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:fe69978f3f768926cfa37b867e3843918e012cf83f680806599ddce33c2c68b0"}, + {file = "PyYAML-5.4.1-cp37-cp37m-win32.whl", hash = "sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b"}, + {file = "PyYAML-5.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf"}, + {file = "PyYAML-5.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46"}, + {file = "PyYAML-5.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb"}, + {file = "PyYAML-5.4.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:fd7f6999a8070df521b6384004ef42833b9bd62cfee11a09bda1079b4b704247"}, + {file = "PyYAML-5.4.1-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:bfb51918d4ff3d77c1c856a9699f8492c612cde32fd3bcd344af9be34999bfdc"}, + {file = "PyYAML-5.4.1-cp38-cp38-win32.whl", hash = "sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc"}, + {file = "PyYAML-5.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:0f5f5786c0e09baddcd8b4b45f20a7b5d61a7e7e99846e3c799b05c7c53fa696"}, + {file = "PyYAML-5.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:294db365efa064d00b8d1ef65d8ea2c3426ac366c0c4368d930bf1c5fb497f77"}, + {file = "PyYAML-5.4.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183"}, + {file = "PyYAML-5.4.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:d483ad4e639292c90170eb6f7783ad19490e7a8defb3e46f97dfe4bacae89122"}, + {file = "PyYAML-5.4.1-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:fdc842473cd33f45ff6bce46aea678a54e3d21f1b61a7750ce3c498eedfe25d6"}, + {file = "PyYAML-5.4.1-cp39-cp39-win32.whl", hash = "sha256:49d4cdd9065b9b6e206d0595fee27a96b5dd22618e7520c33204a4a3239d5b10"}, + {file = "PyYAML-5.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db"}, + {file = "PyYAML-5.4.1.tar.gz", hash = "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e"}, +] +yamllint = [ + {file = "yamllint-1.26.2.tar.gz", hash = "sha256:0b08a96750248fdf21f1e8193cb7787554ef75ed57b27f621cd6b3bf09af11a1"}, +] diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..bbc3143 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,16 @@ +[tool.poetry] +name = "serial-discovery" +version = "0.1.0" +description = "" +authors = ["Your Name "] + +[tool.poetry.dependencies] +python = "^3.9" + +[tool.poetry.dev-dependencies] +yamllint = "^1.26.2" +codespell = "^2.1.0" + +[build-system] +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api" diff --git a/sync/sync.go b/sync/sync.go new file mode 100644 index 0000000..370ca0f --- /dev/null +++ b/sync/sync.go @@ -0,0 +1,83 @@ +// +// This file is part of serial-discovery. +// +// Copyright 2018-2021 ARDUINO SA (http://www.arduino.cc/) +// +// This software is released under the GNU General Public License version 3, +// which covers the main part of arduino-cli. +// The terms of this license can be found at: +// https://www.gnu.org/licenses/gpl-3.0.en.html +// +// You can be released from the requirements of the above licenses by purchasing +// a commercial license. Buying such a license is mandatory if you want to modify or +// otherwise use the software for commercial activities involving the Arduino +// software without disclosing the source code of your own applications. To purchase +// a commercial license, send an email to license@arduino.cc. +// + +package sync + +import ( + "github.com/arduino/go-properties-orderedmap" + discovery "github.com/arduino/pluggable-discovery-protocol-handler/v2" + "go.bug.st/serial/enumerator" +) + +// processUpdates sends 'add' and 'remove' events by comparing two ports enumeration +// made at different times: +// - ports present in the new list but not in the old list are reported as 'added' +// - ports present in the old list but not in the new list are reported as 'removed' +func processUpdates(old, new []*enumerator.PortDetails, eventCB discovery.EventCallback) { + for _, oldPort := range old { + if !portListHas(new, oldPort) { + eventCB("remove", &discovery.Port{ + Address: oldPort.Name, + Protocol: "serial", + }) + } + } + + for _, newPort := range new { + if !portListHas(old, newPort) { + eventCB("add", toDiscoveryPort(newPort)) + } + } +} + +// portListHas checks if port is contained in list. The port metadata are +// compared in particular the port address, and vid/pid if the port is a usb port. +func portListHas(list []*enumerator.PortDetails, port *enumerator.PortDetails) bool { + for _, p := range list { + if port.Name == p.Name && port.IsUSB == p.IsUSB { + if p.IsUSB && + port.VID == p.VID && + port.PID == p.PID && + port.SerialNumber == p.SerialNumber { + return true + } + if !p.IsUSB { + return true + } + } + } + return false +} + +func toDiscoveryPort(port *enumerator.PortDetails) *discovery.Port { + protocolLabel := "Serial Port" + props := properties.NewMap() + if port.IsUSB { + protocolLabel += " (USB)" + props.Set("vid", "0x"+port.VID) + props.Set("pid", "0x"+port.PID) + props.Set("serialNumber", port.SerialNumber) + } + res := &discovery.Port{ + Address: port.Name, + AddressLabel: port.Name, + Protocol: "serial", + ProtocolLabel: protocolLabel, + Properties: props, + } + return res +} diff --git a/sync/sync_darwin.go b/sync/sync_darwin.go new file mode 100644 index 0000000..c39ac52 --- /dev/null +++ b/sync/sync_darwin.go @@ -0,0 +1,114 @@ +// +// This file is part of serial-discovery. +// +// Copyright 2018-2021 ARDUINO SA (http://www.arduino.cc/) +// +// This software is released under the GNU General Public License version 3, +// which covers the main part of arduino-cli. +// The terms of this license can be found at: +// https://www.gnu.org/licenses/gpl-3.0.en.html +// +// You can be released from the requirements of the above licenses by purchasing +// a commercial license. Buying such a license is mandatory if you want to modify or +// otherwise use the software for commercial activities involving the Arduino +// software without disclosing the source code of your own applications. To purchase +// a commercial license, send an email to license@arduino.cc. +// + +package sync + +import ( + "fmt" + "syscall" + + discovery "github.com/arduino/pluggable-discovery-protocol-handler/v2" + "go.bug.st/serial/enumerator" +) + +// Start the sync process, successful events will be passed to eventCB, errors to errorCB. +// Returns a channel used to stop the sync process. +// Returns error if sync process can't be started. +func Start(eventCB discovery.EventCallback, errorCB discovery.ErrorCallback) (chan<- bool, error) { + // create kqueue + kq, err := syscall.Kqueue() + if err != nil { + return nil, err + } + + // open folder + fd, err := syscall.Open("/dev", syscall.O_RDONLY, 0) + if err != nil { + return nil, err + } + + // build kevent + ev1 := syscall.Kevent_t{ + Ident: uint64(fd), + Filter: syscall.EVFILT_VNODE, + Flags: syscall.EV_ADD | syscall.EV_ENABLE | syscall.EV_ONESHOT, + Fflags: syscall.NOTE_DELETE | syscall.NOTE_WRITE, + Data: 0, + Udata: nil, + } + + // Run synchronous event emitter + closeChan := make(chan bool) + + go func() { + defer syscall.Close(fd) + defer syscall.Close(kq) + + // Output initial port state: get the current port list to send as initial "add" events + current, err := enumerator.GetDetailedPortsList() + if err != nil { + errorCB(err.Error()) + return + } + for _, port := range current { + eventCB("add", toDiscoveryPort(port)) + } + + // wait for events + events := make([]syscall.Kevent_t, 10) + + for { + t100ms := syscall.Timespec{Nsec: 100_000_000, Sec: 0} + n, err := syscall.Kevent(kq, []syscall.Kevent_t{ev1}, events, &t100ms) + select { + case <-closeChan: + return + default: + } + if err == syscall.EINTR { + continue + } + if err != nil { + errorCB(fmt.Sprintf("Error decoding serial event: %s", err)) + break + } + if n <= 0 { + continue + } + + // if there is an event retry up to 5 times + var enumeratorErr error + for retries := 0; retries < 5; retries++ { + updates, err := enumerator.GetDetailedPortsList() + if err != nil { + enumeratorErr = err + break + } + processUpdates(current, updates, eventCB) + current = updates + } + if enumeratorErr != nil { + errorCB(fmt.Sprintf("Error enumerating serial ports: %s", enumeratorErr)) + break + } + } + + <-closeChan + }() + + return closeChan, nil +} diff --git a/sync/sync_default.go b/sync/sync_default.go new file mode 100644 index 0000000..743ac65 --- /dev/null +++ b/sync/sync_default.go @@ -0,0 +1,27 @@ +// +// This file is part of serial-discovery. +// +// Copyright 2018 ARDUINO SA (http://www.arduino.cc/) +// +// This software is released under the GNU General Public License version 3, +// which covers the main part of arduino-cli. +// The terms of this license can be found at: +// https://www.gnu.org/licenses/gpl-3.0.en.html +// +// You can be released from the requirements of the above licenses by purchasing +// a commercial license. Buying such a license is mandatory if you want to modify or +// otherwise use the software for commercial activities involving the Arduino +// software without disclosing the source code of your own applications. To purchase +// a commercial license, send an email to license@arduino.cc. +// + +// +build !linux,!windows,!darwin + +package sync + +import "fmt" + +// Start fallback implementation +func Start() (chan<- bool, error) { + return nil, fmt.Errorf("Command START_SYNC not supported") +} diff --git a/sync/sync_linux.go b/sync/sync_linux.go new file mode 100644 index 0000000..908511f --- /dev/null +++ b/sync/sync_linux.go @@ -0,0 +1,95 @@ +// +// This file is part of serial-discovery. +// +// Copyright 2018-2021 ARDUINO SA (http://www.arduino.cc/) +// +// This software is released under the GNU General Public License version 3, +// which covers the main part of arduino-cli. +// The terms of this license can be found at: +// https://www.gnu.org/licenses/gpl-3.0.en.html +// +// You can be released from the requirements of the above licenses by purchasing +// a commercial license. Buying such a license is mandatory if you want to modify or +// otherwise use the software for commercial activities involving the Arduino +// software without disclosing the source code of your own applications. To purchase +// a commercial license, send an email to license@arduino.cc. +// + +package sync + +import ( + "fmt" + "io" + + discovery "github.com/arduino/pluggable-discovery-protocol-handler/v2" + "github.com/s-urbaniak/uevent" + "go.bug.st/serial/enumerator" +) + +// Start the sync process, successful events will be passed to eventCB, errors to errorCB. +// Returns a channel used to stop the sync process. +// Returns error if sync process can't be started. +func Start(eventCB discovery.EventCallback, errorCB discovery.ErrorCallback) (chan<- bool, error) { + // Get the current port list to send as initial "add" events + current, err := enumerator.GetDetailedPortsList() + if err != nil { + return nil, err + } + + // Start sync reader from udev + syncReader, err := uevent.NewReader() + if err != nil { + return nil, err + } + + closeChan := make(chan bool) + go func() { + <-closeChan + syncReader.Close() + }() + + // Run synchronous event emitter + go func() { + // Output initial port state + for _, port := range current { + eventCB("add", toDiscoveryPort(port)) + } + + dec := uevent.NewDecoder(syncReader) + for { + evt, err := dec.Decode() + if err == io.EOF { + // The underlying syncReader has been closed + // so there's nothing else to read + return + } else if err != nil { + errorCB(fmt.Sprintf("Error decoding serial event: %s", err)) + return + } + if evt.Subsystem != "tty" { + continue + } + changedPort := "/dev/" + evt.Vars["DEVNAME"] + if evt.Action == "add" { + portList, err := enumerator.GetDetailedPortsList() + if err != nil { + continue + } + for _, port := range portList { + if port.IsUSB && port.Name == changedPort { + eventCB("add", toDiscoveryPort(port)) + break + } + } + } + if evt.Action == "remove" { + eventCB("remove", &discovery.Port{ + Address: changedPort, + Protocol: "serial", + }) + } + } + }() + + return closeChan, nil +} diff --git a/sync/sync_windows.go b/sync/sync_windows.go new file mode 100644 index 0000000..31cb1df --- /dev/null +++ b/sync/sync_windows.go @@ -0,0 +1,217 @@ +// +// This file is part of serial-discovery. +// +// Copyright 2018-2021 ARDUINO SA (http://www.arduino.cc/) +// +// This software is released under the GNU General Public License version 3, +// which covers the main part of arduino-cli. +// The terms of this license can be found at: +// https://www.gnu.org/licenses/gpl-3.0.en.html +// +// You can be released from the requirements of the above licenses by purchasing +// a commercial license. Buying such a license is mandatory if you want to modify or +// otherwise use the software for commercial activities involving the Arduino +// software without disclosing the source code of your own applications. To purchase +// a commercial license, send an email to license@arduino.cc. +// + +package sync + +import ( + "fmt" + "runtime" + "syscall" + "time" + "unsafe" + + discovery "github.com/arduino/pluggable-discovery-protocol-handler/v2" + "go.bug.st/serial/enumerator" +) + +//go:generate go run golang.org/x/sys/windows/mkwinsyscall -output zsyscall_windows.go sync_windows.go + +//sys getModuleHandle(moduleName *byte) (handle syscall.Handle, err error) = GetModuleHandleA +//sys registerClass(wndClass *wndClass) (atom uint16, err error) = user32.RegisterClassA +//sys defWindowProc(hwnd syscall.Handle, msg uint32, wParam uintptr, lParam uintptr) (lResult uintptr) = user32.DefWindowProcW +//sys createWindowEx(exstyle uint32, className *byte, windowText *byte, style uint32, x int32, y int32, width int32, height int32, parent syscall.Handle, menu syscall.Handle, hInstance syscall.Handle, lpParam uintptr) (hwnd syscall.Handle, err error) = user32.CreateWindowExA +//sys registerDeviceNotification(recipient syscall.Handle, filter *devBroadcastDeviceInterface, flags uint32) (devHandle syscall.Handle, err error) = user32.RegisterDeviceNotificationA +//sys getMessage(msg *msg, hwnd syscall.Handle, msgFilterMin uint32, msgFilterMax uint32) (res int32, err error) = user32.GetMessageA +//sys translateMessage(msg *msg) (res bool) = user32.TranslateMessage +//sys dispatchMessage(msg *msg) (res int32, err error) = user32.DispatchMessageA + +type wndClass struct { + style uint32 + wndProc uintptr + clsExtra int32 + wndExtra int32 + instance syscall.Handle + icon syscall.Handle + cursor syscall.Handle + brBackground syscall.Handle + menuName *byte + className *byte +} + +type point struct { + x int32 + y int32 +} + +type msg struct { + hwnd syscall.Handle + message uint32 + wParam uintptr + lParam uintptr + time int32 + pt point + lPrivate int32 +} + +const wsExDlgModalFrame = 0x00000001 +const wsExTopmost = 0x00000008 +const wsExTransparent = 0x00000020 +const wsExMDIChild = 0x00000040 +const wsExToolWindow = 0x00000080 +const wsExAppWindow = 0x00040000 +const wsExLayered = 0x00080000 + +type guid struct { + data1 uint32 + data2 uint16 + data3 uint16 + data4 [8]byte +} + +type devBroadcastDeviceInterface struct { + dwSize uint32 + dwDeviceType uint32 + dwReserved uint32 + classGUID guid + szName uint16 +} + +//var usbEventGUID = guid{???} // TODO + +const deviceNotifyWindowHandle = 0 +const deviceNotifySserviceHandle = 1 +const deviceNotifyAllInterfaceClasses = 4 + +const dbtDevtypeDeviceInterface = 5 + +func init() { + runtime.LockOSThread() +} + +// Start the sync process, successful events will be passed to eventCB, errors to errorCB. +// Returns a channel used to stop the sync process. +// Returns error if sync process can't be started. +func Start(eventCB discovery.EventCallback, errorCB discovery.ErrorCallback) (chan<- bool, error) { + startResult := make(chan error) + event := make(chan bool, 1) + go func() { + initAndRunWindowHandler(startResult, event) + }() + if err := <-startResult; err != nil { + return nil, err + } + go func() { + current, err := enumerator.GetDetailedPortsList() + if err != nil { + errorCB(fmt.Sprintf("Error enumerating serial ports: %s", err)) + return + } + for _, port := range current { + eventCB("add", toDiscoveryPort(port)) + } + + for { + <-event + + // Wait 100 ms to pile up events + time.Sleep(100 * time.Millisecond) + select { + case <-event: + // Just one event could be queued because the channel has size 1 + // (more events coming after this one are discarded on send) + default: + } + + // Send updates + updates, err := enumerator.GetDetailedPortsList() + if err != nil { + errorCB(fmt.Sprintf("Error enumerating serial ports: %s", err)) + return + } + processUpdates(current, updates, eventCB) + current = updates + } + }() + quit := make(chan bool) + go func() { + <-quit + // TODO: implement termination channel + }() + return quit, nil +} + +func initAndRunWindowHandler(startResult chan<- error, event chan<- bool) { + handle, err := getModuleHandle(nil) + if err != nil { + startResult <- err + return + } + + wndProc := func(hwnd syscall.Handle, msg uint32, wParam uintptr, lParam uintptr) uintptr { + select { + case event <- true: + default: + } + return defWindowProc(hwnd, msg, wParam, lParam) + } + + className := syscall.StringBytePtr("serialdiscovery") + windowClass := &wndClass{ + instance: handle, + className: className, + wndProc: syscall.NewCallback(wndProc), + } + if _, err := registerClass(windowClass); err != nil { + startResult <- fmt.Errorf("registering new window: %s", err) + return + } + + hwnd, err := createWindowEx(wsExTopmost, className, className, 0, 0, 0, 0, 0, 0, 0, 0, 0) + if err != nil { + startResult <- fmt.Errorf("creating window: %s", err) + return + } + + notificationFilter := devBroadcastDeviceInterface{ + dwDeviceType: dbtDevtypeDeviceInterface, + // TODO: Filter USB events using the correct GUID + } + notificationFilter.dwSize = uint32(unsafe.Sizeof(notificationFilter)) + + if _, err := registerDeviceNotification( + hwnd, + ¬ificationFilter, + deviceNotifyWindowHandle|deviceNotifyAllInterfaceClasses); err != nil { + startResult <- fmt.Errorf("registering for devices notification: %s", err) + return + } + + startResult <- nil + + var m msg + for { + if res, err := getMessage(&m, hwnd, 0, 0); res == 0 || res == -1 { + if err != nil { + // TODO: send err and stop sync mode. + // fmt.Println(err) + } + break + } + translateMessage(&m) + dispatchMessage(&m) + } +} diff --git a/sync/zsyscall_windows.go b/sync/zsyscall_windows.go new file mode 100644 index 0000000..79abf45 --- /dev/null +++ b/sync/zsyscall_windows.go @@ -0,0 +1,118 @@ +// Code generated by 'go generate'; DO NOT EDIT. + +package sync + +import ( + "syscall" + "unsafe" + + "golang.org/x/sys/windows" +) + +var _ unsafe.Pointer + +// Do the interface allocations only once for common +// Errno values. +const ( + errnoERROR_IO_PENDING = 997 +) + +var ( + errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING) + errERROR_EINVAL error = syscall.EINVAL +) + +// errnoErr returns common boxed Errno values, to prevent +// allocations at runtime. +func errnoErr(e syscall.Errno) error { + switch e { + case 0: + return errERROR_EINVAL + case errnoERROR_IO_PENDING: + return errERROR_IO_PENDING + } + // TODO: add more here, after collecting data on the common + // error values see on Windows. (perhaps when running + // all.bat?) + return e +} + +var ( + modkernel32 = windows.NewLazySystemDLL("kernel32.dll") + moduser32 = windows.NewLazySystemDLL("user32.dll") + + procGetModuleHandleA = modkernel32.NewProc("GetModuleHandleA") + procCreateWindowExA = moduser32.NewProc("CreateWindowExA") + procDefWindowProcW = moduser32.NewProc("DefWindowProcW") + procDispatchMessageA = moduser32.NewProc("DispatchMessageA") + procGetMessageA = moduser32.NewProc("GetMessageA") + procRegisterClassA = moduser32.NewProc("RegisterClassA") + procRegisterDeviceNotificationA = moduser32.NewProc("RegisterDeviceNotificationA") + procTranslateMessage = moduser32.NewProc("TranslateMessage") +) + +func getModuleHandle(moduleName *byte) (handle syscall.Handle, err error) { + r0, _, e1 := syscall.Syscall(procGetModuleHandleA.Addr(), 1, uintptr(unsafe.Pointer(moduleName)), 0, 0) + handle = syscall.Handle(r0) + if handle == 0 { + err = errnoErr(e1) + } + return +} + +func createWindowEx(exstyle uint32, className *byte, windowText *byte, style uint32, x int32, y int32, width int32, height int32, parent syscall.Handle, menu syscall.Handle, hInstance syscall.Handle, lpParam uintptr) (hwnd syscall.Handle, err error) { + r0, _, e1 := syscall.Syscall12(procCreateWindowExA.Addr(), 12, uintptr(exstyle), uintptr(unsafe.Pointer(className)), uintptr(unsafe.Pointer(windowText)), uintptr(style), uintptr(x), uintptr(y), uintptr(width), uintptr(height), uintptr(parent), uintptr(menu), uintptr(hInstance), uintptr(lpParam)) + hwnd = syscall.Handle(r0) + if hwnd == 0 { + err = errnoErr(e1) + } + return +} + +func defWindowProc(hwnd syscall.Handle, msg uint32, wParam uintptr, lParam uintptr) (lResult uintptr) { + r0, _, _ := syscall.Syscall6(procDefWindowProcW.Addr(), 4, uintptr(hwnd), uintptr(msg), uintptr(wParam), uintptr(lParam), 0, 0) + lResult = uintptr(r0) + return +} + +func dispatchMessage(msg *msg) (res int32, err error) { + r0, _, e1 := syscall.Syscall(procDispatchMessageA.Addr(), 1, uintptr(unsafe.Pointer(msg)), 0, 0) + res = int32(r0) + if res == 0 { + err = errnoErr(e1) + } + return +} + +func getMessage(msg *msg, hwnd syscall.Handle, msgFilterMin uint32, msgFilterMax uint32) (res int32, err error) { + r0, _, e1 := syscall.Syscall6(procGetMessageA.Addr(), 4, uintptr(unsafe.Pointer(msg)), uintptr(hwnd), uintptr(msgFilterMin), uintptr(msgFilterMax), 0, 0) + res = int32(r0) + if res == 0 { + err = errnoErr(e1) + } + return +} + +func registerClass(wndClass *wndClass) (atom uint16, err error) { + r0, _, e1 := syscall.Syscall(procRegisterClassA.Addr(), 1, uintptr(unsafe.Pointer(wndClass)), 0, 0) + atom = uint16(r0) + if atom == 0 { + err = errnoErr(e1) + } + return +} + +func registerDeviceNotification(recipient syscall.Handle, filter *devBroadcastDeviceInterface, flags uint32) (devHandle syscall.Handle, err error) { + r0, _, e1 := syscall.Syscall(procRegisterDeviceNotificationA.Addr(), 3, uintptr(recipient), uintptr(unsafe.Pointer(filter)), uintptr(flags)) + devHandle = syscall.Handle(r0) + if devHandle == 0 { + err = errnoErr(e1) + } + return +} + +func translateMessage(msg *msg) (res bool) { + r0, _, _ := syscall.Syscall(procTranslateMessage.Addr(), 1, uintptr(unsafe.Pointer(msg)), 0, 0) + res = r0 != 0 + return +} diff --git a/version/version.go b/version/version.go new file mode 100644 index 0000000..39ba7da --- /dev/null +++ b/version/version.go @@ -0,0 +1,66 @@ +// +// This file is part of serial-discovery. +// +// Copyright 2021 ARDUINO SA (http://www.arduino.cc/) +// +// This software is released under the GNU General Public License version 3, +// which covers the main part of arduino-cli. +// The terms of this license can be found at: +// https://www.gnu.org/licenses/gpl-3.0.en.html +// +// You can be released from the requirements of the above licenses by purchasing +// a commercial license. Buying such a license is mandatory if you want to modify or +// otherwise use the software for commercial activities involving the Arduino +// software without disclosing the source code of your own applications. To purchase +// a commercial license, send an email to license@arduino.cc. +// + +package version + +import ( + "fmt" + "os" + "path/filepath" +) + +// VersionInfo FIXMEDOC +var VersionInfo = newInfo(filepath.Base(os.Args[0])) + +var ( + defaultVersionString = "0.0.0-git" + // Version FIXMEDOC + Version = "" + // Commit FIXMEDOC + Commit = "" + // Timestamp FIXMEDOC + Timestamp = "" +) + +// Info FIXMEDOC +type Info struct { + Application string `json:"Application"` + VersionString string `json:"VersionString"` + Commit string `json:"Commit"` + Date string `json:"Date"` +} + +// NewInfo FIXMEDOC +func newInfo(application string) *Info { + return &Info{ + Application: application, + VersionString: Version, + Commit: Commit, + Date: Timestamp, + } +} + +func (i *Info) String() string { + return fmt.Sprintf("%s Version: %s Commit: %s Date: %s", i.Application, i.VersionString, i.Commit, i.Date) +} + +//nolint:gochecknoinits +func init() { + if Version == "" { + Version = defaultVersionString + } +} From 4422d6897efd20d694314bd90b285113a7fbe337 Mon Sep 17 00:00:00 2001 From: Umberto Baldi Date: Fri, 10 Sep 2021 11:42:34 +0200 Subject: [PATCH 2/9] replace discovery with monitor (TODO adapt code and README.md) --- .github/workflows/release-go-task.yml | 4 ++-- .gitignore | 4 ++-- README.md | 22 +++++++++++----------- Taskfile.yml | 4 ++-- args/args.go | 2 +- go.mod | 2 +- main.go | 8 ++++---- pyproject.toml | 2 +- sync/sync.go | 2 +- sync/sync_darwin.go | 2 +- sync/sync_default.go | 3 ++- sync/sync_linux.go | 2 +- sync/sync_windows.go | 2 +- version/version.go | 2 +- 14 files changed, 31 insertions(+), 30 deletions(-) mode change 100755 => 100644 Taskfile.yml diff --git a/.github/workflows/release-go-task.yml b/.github/workflows/release-go-task.yml index 180b2d2..e462202 100644 --- a/.github/workflows/release-go-task.yml +++ b/.github/workflows/release-go-task.yml @@ -3,11 +3,11 @@ name: Release env: # As defined by the Taskfile's PROJECT_NAME variable - PROJECT_NAME: serial-discovery + PROJECT_NAME: serial-monitor # As defined by the Taskfile's DIST_DIR variable DIST_DIR: dist # The project's folder on Arduino's download server for uploading builds - AWS_PLUGIN_TARGET: /discovery/serial-discovery/ + AWS_PLUGIN_TARGET: /monitor/serial-monitor/ ARTIFACT_NAME: dist on: diff --git a/.gitignore b/.gitignore index ddde031..f4bf7ea 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ # Build artifacts -serial-discovery -serial-discovery.exe +serial-monitor +serial-monitor.exe __pycache__/ # IDEs diff --git a/README.md b/README.md index 00f1d23..f2edde3 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ -# Arduino pluggable discovery for serial ports +# Arduino pluggable monitor for serial ports -The `serial-discovery` tool is a command line program that interacts via stdio. It accepts commands as plain ASCII strings terminated with LF `\n` and sends response as JSON. +The `serial-monitor` tool is a command line program that interacts via stdio. It accepts commands as plain ASCII strings terminated with LF `\n` and sends response as JSON. ## How to build -Install a recent go environment (>=13.0) and run `go build`. The executable `serial-discovery` will be produced in your working directory. +Install a recent go environment (>=13.0) and run `go build`. The executable `serial-monitor` will be produced in your working directory. ## Usage @@ -12,7 +12,7 @@ After startup, the tool waits for commands. The available commands are: `HELLO`, #### HELLO command -The `HELLO` command is used to establish the pluggable discovery protocol between client and discovery. +The `HELLO` command is used to establish the pluggable monitor protocol between client and monitor. The format of the command is: `HELLO ""` @@ -36,7 +36,7 @@ The response to the command is: } ``` -`protocolVersion` is the protocol version that the discovery is going to use in the remainder of the communication. +`protocolVersion` is the protocol version that the monitor is going to use in the remainder of the communication. #### START command @@ -62,7 +62,7 @@ The `STOP` command stops the discovery internal subroutines and free some resour #### QUIT command -The `QUIT` command terminates the discovery. The response to quit is: +The `QUIT` command terminates the monitor. The response to quit is: ```json { @@ -151,10 +151,10 @@ in this case only the `address` and `protocol` fields are reported. ### Example of usage -A possible transcript of the discovery usage: +A possible transcript of the monitor usage: ``` -$ ./serial-discovery +$ ./serial-monitor START { "eventType": "start", @@ -228,7 +228,7 @@ $ ## Security If you think you found a vulnerability or other security-related bug in this project, please read our -[security policy](https://github.com/arduino/serial-discovery/security/policy) and report the bug to our Security Team 🛡️ +[security policy](https://github.com/arduino/serial-monitor/security/policy) and report the bug to our Security Team 🛡️ Thank you! e-mail contact: security@arduino.cc @@ -238,7 +238,7 @@ e-mail contact: security@arduino.cc Copyright (c) 2018 ARDUINO SA (www.arduino.cc) The software is released under the GNU General Public License, which covers the main body -of the serial-discovery code. The terms of this license can be found at: +of the serial-monitor code. The terms of this license can be found at: https://www.gnu.org/licenses/gpl-3.0.en.html -See [LICENSE.txt](https://github.com/arduino/serial-discovery/blob/master/LICENSE.txt) for details. +See [LICENSE.txt](https://github.com/arduino/serial-monitor/blob/master/LICENSE.txt) for details. diff --git a/Taskfile.yml b/Taskfile.yml old mode 100755 new mode 100644 index 7583786..b44bcb6 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -6,7 +6,7 @@ includes: vars: DEFAULT_GO_PACKAGES: sh: echo $(go list ./... | tr '\n' ' ') - PROJECT_NAME: "serial-discovery" + PROJECT_NAME: "serial-monitor" DIST_DIR: "dist" # build vars COMMIT: @@ -18,7 +18,7 @@ vars: TIMESTAMP_SHORT: sh: echo "{{now | date "20060102"}}" VERSION: "{{if .NIGHTLY}}nightly-{{.TIMESTAMP_SHORT}}{{else if .TAG}}{{.TAG}}{{else}}{{.PACKAGE_NAME_PREFIX}}git-snapshot{{end}}" - CONFIGURATION_PACKAGE: github.com/arduino/serial-discovery/version + CONFIGURATION_PACKAGE: github.com/arduino/serial-monitor/version LDFLAGS: > -ldflags ' diff --git a/args/args.go b/args/args.go index c6d2d77..998a32b 100644 --- a/args/args.go +++ b/args/args.go @@ -1,5 +1,5 @@ // -// This file is part of serial-discovery. +// This file is part of serial-monitor. // // Copyright 2021 ARDUINO SA (http://www.arduino.cc/) // diff --git a/go.mod b/go.mod index 03a7ed8..f0e419f 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/arduino/serial-discovery +module github.com/arduino/serial-monitor require ( github.com/arduino/go-paths-helper v1.6.1 // indirect diff --git a/main.go b/main.go index c8071d6..6c34408 100644 --- a/main.go +++ b/main.go @@ -1,5 +1,5 @@ // -// This file is part of serial-discovery. +// This file is part of serial-monitor. // // Copyright 2018-2021 ARDUINO SA (http://www.arduino.cc/) // @@ -22,9 +22,9 @@ import ( "os" discovery "github.com/arduino/pluggable-discovery-protocol-handler/v2" - "github.com/arduino/serial-discovery/args" - "github.com/arduino/serial-discovery/sync" - "github.com/arduino/serial-discovery/version" + "github.com/arduino/serial-monitor/args" + "github.com/arduino/serial-monitor/sync" + "github.com/arduino/serial-monitor/version" ) func main() { diff --git a/pyproject.toml b/pyproject.toml index bbc3143..71902be 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [tool.poetry] -name = "serial-discovery" +name = "serial-monitor" version = "0.1.0" description = "" authors = ["Your Name "] diff --git a/sync/sync.go b/sync/sync.go index 370ca0f..353afd6 100644 --- a/sync/sync.go +++ b/sync/sync.go @@ -1,5 +1,5 @@ // -// This file is part of serial-discovery. +// This file is part of serial-monitor. // // Copyright 2018-2021 ARDUINO SA (http://www.arduino.cc/) // diff --git a/sync/sync_darwin.go b/sync/sync_darwin.go index c39ac52..5396fe5 100644 --- a/sync/sync_darwin.go +++ b/sync/sync_darwin.go @@ -1,5 +1,5 @@ // -// This file is part of serial-discovery. +// This file is part of serial-monitor. // // Copyright 2018-2021 ARDUINO SA (http://www.arduino.cc/) // diff --git a/sync/sync_default.go b/sync/sync_default.go index 743ac65..6702fe5 100644 --- a/sync/sync_default.go +++ b/sync/sync_default.go @@ -1,5 +1,5 @@ // -// This file is part of serial-discovery. +// This file is part of serial-monitor. // // Copyright 2018 ARDUINO SA (http://www.arduino.cc/) // @@ -15,6 +15,7 @@ // a commercial license, send an email to license@arduino.cc. // +//go:build !linux && !windows && !darwin // +build !linux,!windows,!darwin package sync diff --git a/sync/sync_linux.go b/sync/sync_linux.go index 908511f..0f98d4b 100644 --- a/sync/sync_linux.go +++ b/sync/sync_linux.go @@ -1,5 +1,5 @@ // -// This file is part of serial-discovery. +// This file is part of serial-monitor. // // Copyright 2018-2021 ARDUINO SA (http://www.arduino.cc/) // diff --git a/sync/sync_windows.go b/sync/sync_windows.go index 31cb1df..4adcc1d 100644 --- a/sync/sync_windows.go +++ b/sync/sync_windows.go @@ -1,5 +1,5 @@ // -// This file is part of serial-discovery. +// This file is part of serial-monitor. // // Copyright 2018-2021 ARDUINO SA (http://www.arduino.cc/) // diff --git a/version/version.go b/version/version.go index 39ba7da..f4b78d2 100644 --- a/version/version.go +++ b/version/version.go @@ -1,5 +1,5 @@ // -// This file is part of serial-discovery. +// This file is part of serial-monitor. // // Copyright 2021 ARDUINO SA (http://www.arduino.cc/) // From 32855643e642d15398d1a3b9c307c17d854a0753 Mon Sep 17 00:00:00 2001 From: Umberto Baldi Date: Fri, 10 Sep 2021 12:05:57 +0200 Subject: [PATCH 3/9] adapt README.md (links not working) --- README.md | 338 +++++++++++++++++++++++++++++++++++------------------- 1 file changed, 219 insertions(+), 119 deletions(-) diff --git a/README.md b/README.md index f2edde3..44aeba1 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Install a recent go environment (>=13.0) and run `go build`. The executable `ser ## Usage -After startup, the tool waits for commands. The available commands are: `HELLO`, `START`, `STOP`, `QUIT`, `LIST` and `START_SYNC`. +After startup, the tool waits for commands. The available commands are: HELLO, DESCRIBE, CONFIGURE, OPEN, CLOSE and QUIT. #### HELLO command @@ -38,193 +38,293 @@ The response to the command is: `protocolVersion` is the protocol version that the monitor is going to use in the remainder of the communication. -#### START command +#### DESCRIBE command -The `START` starts the internal subroutines of the discovery that looks for ports. This command must be called before `LIST` or `START_SYNC`. The response to the start command is: +The `DESCRIBE` command returns a description of the communication port. The description will have metadata about the port configuration, and which parameters are available: ```json { - "eventType": "start", - "message": "OK" + "event": "describe", + "message": "ok", + "port_description": { + "protocol": "serial", + "configuration_parameters": { + "baudrate": { + "label": "Baudrate", + "type": "enum", + "values": [ + "300", + "600", + "750", + "1200", + "2400", + "4800", + "9600", + "19200", + "38400", + "57600", + "115200", + "230400", + "460800", + "500000", + "921600", + "1000000", + "2000000" + ], + "selected": "9600" + }, + "parity": { + "label": "Parity", + "type": "enum", + "values": ["N", "E", "O", "M", "S"], + "selected": "N" + }, + "bits": { + "label": "Data bits", + "type": "enum", + "values": ["5", "6", "7", "8", "9"], + "selected": "8" + }, + "stop_bits": { + "label": "Stop bits", + "type": "enum", + "values": ["1", "1.5", "2"], + "selected": "1" + } + } + } } ``` -#### STOP command +Each parameter has a unique name (`baudrate`, `parity`, etc...), a `type` (in this case only enum but more types will be added in the future), and the `selected` value for each parameter. -The `STOP` command stops the discovery internal subroutines and free some resources. This command should be called if the client wants to pause the discovery for a while. The response to the stop command is: +The parameter name can not contain spaces, and the allowed characters in the name are alphanumerics, underscore `_`, dot `.`, and dash `-`. -```json +The `enum` types must have a list of possible `values`. + +The client/IDE may expose these configuration values to the user via a config file or a GUI, in this case the `label` field may be used for a user readable description of the parameter. + +#### CONFIGURE command + +The `CONFIGURE` command sets configuration parameters for the communication port. The parameters can be changed one at a time and the syntax is: + +`CONFIGURE ` + +The response to the command is: + +```JSON { - "eventType": "stop", - "message": "OK" + "event": "configure", + "message": "ok", } ``` -#### QUIT command +or if there is an error: -The `QUIT` command terminates the monitor. The response to quit is: - -```json +```JSON { - "eventType": "quit", - "message": "OK" + "event": "configure", + "error": true, + "message": "invalid value for parameter baudrate: 123456" } ``` -after this output the tool quits. +The currently selected parameters may be obtained using the `DESCRIBE` command. -#### LIST command +#### OPEN command -The `LIST` command returns a list of the currently available serial ports. The format of the response is the following: +The `OPEN` command opens a communication with the board, the data exchanged with the board will be transferred to the Client/IDE via TCP/IP. -```json +The Client/IDE must first TCP-Listen to a randomly selected port and send it to the monitor tool as part of the OPEN command. The syntax of the OPEN command is: + +`OPEN ` + +For example, let's suppose that the Client/IDE wants to communicate with the serial port `/dev/ttyACM0` then the sequence of actions to perform will be the following: + +1. the Client/IDE must first listen to a random TCP port (let's suppose it chose `32123`) +1. the Client/IDE sends the command `OPEN 127.0.0.1:32123 /dev/ttyACM0` to the monitor tool +1. the monitor tool opens `/dev/ttyACM0` +1. the monitor tool connects via TCP/IP to `127.0.0.1:32123` and start streaming data back and forth + +The answer to the `OPEN` command is: + +```JSON { - "eventType": "list", - "ports": [ - { - "address": "/dev/ttyACM0", - "label": "/dev/ttyACM0", - "properties": { - "pid": "0x804e", - "vid": "0x2341", - "serialNumber": "EBEABFD6514D32364E202020FF10181E" - }, - "protocol": "serial", - "protocolLabel": "Serial Port (USB)" - } - ] + "event": "open", + "message": "ok" } ``` -The `ports` field contains a list of the available serial ports. If the serial port comes from an USB serial converter the USB VID/PID and USB SERIAL NUMBER properties are also reported inside `properties`. +If the monitor tool cannot communicate with the board, or if the tool can not connect back to the TCP port, or if any other error condition happens: -The list command is a one-shot command, if you need continuous monitoring of ports you should use `START_SYNC` command. +```JSON +{ + "event": "open", + "error": true, + "message": "unknown port /dev/ttyACM23" +} +``` -#### START_SYNC command +The board port will be opened using the parameters previously set through the `CONFIGURE` command. -The `START_SYNC` command puts the tool in "events" mode: the discovery will send `add` and `remove` events each time a new port is detected or removed respectively. -The immediate response to the command is: +Once the port is opened, it may be unexpectedly closed at any time due to hardware failure, or because the Client/IDE closes the TCP/IP connection. In this case an asynchronous `port_closed` message must be generated from the monitor tool: -```json +```JSON { - "eventType": "start_sync", - "message": "OK" + "event": "port_closed", + "message": "serial port disappeared!" } ``` -after that the discovery enters in "events" mode. +or -The `add` events looks like the following: +```JSON +{ + "event": "port_closed", + "message": "lost TCP/IP connection with the client!" +} +``` -```json +#### CLOSE command + +The `CLOSE` command will close the currently opened port and close the TCP/IP connection used to communicate with the Client/IDE. The answer to the command is: + +```JSON { - "eventType": "add", - "port": { - "address": "/dev/ttyACM0", - "label": "/dev/ttyACM0", - "properties": { - "pid": "0x804e", - "vid": "0x2341", - "serialNumber": "EBEABFD6514D32364E202020FF10181E" - }, - "protocol": "serial", - "protocolLabel": "Serial Port (USB)" - } + "event": "close", + "message": "ok" +} +``` + +or in case of error + +```JSON +{ + "event": "close", + "error": true, + "message": "port already closed" } ``` -it basically gather the same information as the `list` event but for a single port. After calling `START_SYNC` a bunch of `add` events may be generated in sequence to report all the ports available at the moment of the start. +#### QUIT command -The `remove` event looks like this: +The `QUIT` command terminates the monitor. The response to `QUIT` is: -```json +```JSON { - "eventType": "remove", - "port": { - "address": "/dev/ttyACM0", - "protocol": "serial" - } + "eventType": "quit", + "message": "OK" } ``` -in this case only the `address` and `protocol` fields are reported. +after this output the monitor exits. This command is supposed to always succeed. + +#### Invalid commands + +If the client sends an invalid or malformed command, the monitor should answer with: + +```JSON +{ + "eventType": "command_error", + "error": true, + "message": "Unknown command XXXX" +} +``` ### Example of usage A possible transcript of the monitor usage: ``` -$ ./serial-monitor -START +HELLO 1 "test" { - "eventType": "start", - "message": "OK" + "eventType": "hello", + "message": "OK", + "protocolVersion": 1 } -LIST -{ - "eventType": "list", - "ports": [ - { - "address": "/dev/ttyACM0", - "label": "/dev/ttyACM0", - "protocol": "serial", - "protocolLabel": "Serial Port (USB)", - "properties": { - "pid": "0x004e", - "serialNumber": "EBEABFD6514D32364E202020FF10181E", - "vid": "0x2341" +DESCRIBE +{ + "eventType": "describe", + "message": "OK", + "port_description": { + "protocol": "test", + "configuration_parameters": { + "echo": { + "label": "echo", + "type": "enum", + "value": [ + "on", + "off" + ], + "selected": "on" + }, + "speed": { + "label": "Baudrate", + "type": "enum", + "value": [ + "9600", + "19200", + "38400", + "57600", + "115200" + ], + "selected": "9600" } } - ] + } } -START_SYNC +CONFIGURE speed 19200 { - "eventType": "start_sync", + "eventType": "configure", "message": "OK" } -{ <--- this event has been immediately sent - "eventType": "add", - "port": { - "address": "/dev/ttyACM0", - "label": "/dev/ttyACM0", - "protocol": "serial", - "protocolLabel": "Serial Port (USB)", - "properties": { - "pid": "0x004e", - "serialNumber": "EBEABFD6514D32364E202020FF10181E", - "vid": "0x2341" +DESCRIBE +{ + "eventType": "describe", + "message": "OK", + "port_description": { + "protocol": "test", + "configuration_parameters": { + "echo": { + "label": "echo", + "type": "enum", + "value": [ + "on", + "off" + ], + "selected": "on" + }, + "speed": { + "label": "Baudrate", + "type": "enum", + "value": [ + "9600", + "19200", + "38400", + "57600", + "115200" + ], + "selected": "19200" + } } } } -{ <--- the board has been disconnected here - "eventType": "remove", - "port": { - "address": "/dev/ttyACM0", - "protocol": "serial" - } -} -{ <--- the board has been connected again - "eventType": "add", - "port": { - "address": "/dev/ttyACM0", - "label": "/dev/ttyACM0", - "protocol": "serial", - "protocolLabel": "Serial Port (USB)", - "properties": { - "pid": "0x004e", - "serialNumber": "EBEABFD6514D32364E202020FF10181E", - "vid": "0x2341" - } - } +OPEN 127.0.0.1:5678 "test" +{ + "eventType": "open", + "message": "OK" } -QUIT +CLOSE { - "eventType": "quit", + "eventType": "close", "message": "OK" } +QUIT $ ``` +On another terminal tab to test it you can run `nc -l -p 5678` before running the `OPEN 127.0.0.1:5678 "test"` command. After that you can write messages in that terminal tab and see them being echoed. + ## Security If you think you found a vulnerability or other security-related bug in this project, please read our From 4a7b7a02610cb9930eec09b3e27a1c27dca530c0 Mon Sep 17 00:00:00 2001 From: Umberto Baldi Date: Fri, 10 Sep 2021 18:03:18 +0200 Subject: [PATCH 4/9] add first implementation --- go.mod | 8 +- go.sum | 19 ++-- main.go | 129 ++++++++++++++++++----- sync/sync.go | 83 --------------- sync/sync_darwin.go | 114 -------------------- sync/sync_default.go | 28 ----- sync/sync_linux.go | 95 ----------------- sync/sync_windows.go | 217 --------------------------------------- sync/zsyscall_windows.go | 118 --------------------- 9 files changed, 118 insertions(+), 693 deletions(-) delete mode 100644 sync/sync.go delete mode 100644 sync/sync_darwin.go delete mode 100644 sync/sync_default.go delete mode 100644 sync/sync_linux.go delete mode 100644 sync/sync_windows.go delete mode 100644 sync/zsyscall_windows.go diff --git a/go.mod b/go.mod index f0e419f..00c777c 100644 --- a/go.mod +++ b/go.mod @@ -2,12 +2,10 @@ module github.com/arduino/serial-monitor require ( github.com/arduino/go-paths-helper v1.6.1 // indirect - github.com/arduino/go-properties-orderedmap v1.6.0 - github.com/arduino/pluggable-discovery-protocol-handler/v2 v2.0.1 + github.com/arduino/go-properties-orderedmap v1.6.0 // indirect + github.com/arduino/pluggable-monitor-protocol-handler v0.9.0 github.com/davecgh/go-spew v1.1.1 // indirect - github.com/s-urbaniak/uevent v1.0.1 - go.bug.st/serial v1.3.1 - golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069 + go.bug.st/serial v1.3.2 ) go 1.16 diff --git a/go.sum b/go.sum index d4eaf08..678dbe1 100644 --- a/go.sum +++ b/go.sum @@ -4,28 +4,29 @@ github.com/arduino/go-paths-helper v1.6.1/go.mod h1:V82BWgAAp4IbmlybxQdk9Bpkz8M4 github.com/arduino/go-properties-orderedmap v1.4.0/go.mod h1:DKjD2VXY/NZmlingh4lSFMEYCVubfeArCsGPGDwb2yk= github.com/arduino/go-properties-orderedmap v1.6.0 h1:gp2JoWRETtqwsZ+UHu/PBuYWYH2x2+d+uipDxS4WmvM= github.com/arduino/go-properties-orderedmap v1.6.0/go.mod h1:DKjD2VXY/NZmlingh4lSFMEYCVubfeArCsGPGDwb2yk= -github.com/arduino/pluggable-discovery-protocol-handler/v2 v2.0.1 h1:THeCPZWeGfnvtPvxAiCqgEdRhdfERgycHd35NPP9UMQ= -github.com/arduino/pluggable-discovery-protocol-handler/v2 v2.0.1/go.mod h1:tQXPIAEHcc5k8HBsufLsWmju0D3WiJt07j+dFT55NGY= +github.com/arduino/pluggable-monitor-protocol-handler v0.9.0 h1:N2wk5D//4wVQIGVtyLH2wMufJa6+q9bl0P1/G3DKYXM= +github.com/arduino/pluggable-monitor-protocol-handler v0.9.0/go.mod h1:nWW6xPvKZH2PRGaIxolKOcYVBuh5mOCam3avYaaH7No= github.com/creack/goselect v0.1.2 h1:2DNy14+JPjRBgPzAd1thbQp4BSIihxcBf0IXhQXDRa0= github.com/creack/goselect v0.1.2/go.mod h1:a/NhLweNvqIYMuxcMOuWY516Cimucms3DglDzQP3hKY= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/s-urbaniak/uevent v1.0.1 h1:Zy6g16gR1m6iRB+scDtyExP9KRYnuSdTWOYhBE5O620= -github.com/s-urbaniak/uevent v1.0.1/go.mod h1:h83SVc1NQj/EubPaK4hWyiG217xLYr+fS+Jf10COrG0= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -go.bug.st/serial v1.3.1 h1:ziLU+w7RzBZKMGacM/6iY6P7bhxsoyaoVLWcfAdt6w4= -go.bug.st/serial v1.3.1/go.mod h1:8TT7u/SwwNIpJ8QaG4s+HTjFt9ReXs2cdOU7ZEk50Dk= -golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069 h1:siQdpVirKtzPhKl3lZWozZraCFObP8S1v6PRp0bLrtU= -golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +go.bug.st/serial v1.3.2 h1:6BFZZd/wngoL5PPYYTrFUounF54SIkykHpT98eq6zvk= +go.bug.st/serial v1.3.2/go.mod h1:jDkjqASf/qSjmaOxHSHljwUQ6eHo/ZX/bxJLQqSlvZg= +golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf h1:2ucpDCmfkl8Bd/FsLtiD653Wf96cW37s+iGx93zsu4k= +golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/main.go b/main.go index 6c34408..18bec28 100644 --- a/main.go +++ b/main.go @@ -18,15 +18,50 @@ package main import ( + "errors" "fmt" + "io" "os" + "strconv" - discovery "github.com/arduino/pluggable-discovery-protocol-handler/v2" + monitor "github.com/arduino/pluggable-monitor-protocol-handler" "github.com/arduino/serial-monitor/args" - "github.com/arduino/serial-monitor/sync" "github.com/arduino/serial-monitor/version" + "go.bug.st/serial" ) +var serialSettings = &monitor.PortDescriptor{ + Protocol: "serial", + ConfigurationParameter: map[string]*monitor.PortParameterDescriptor{ + "baudrate": { + Label: "Baudrate", + Type: "enum", + Values: []string{"300", "600", "750", "1200", "2400", "4800", "9600", "19200", "38400", "57600", "115200", "230400", "460800", "500000", "921600", "1000000", "2000000"}, + Selected: "9600", + }, + "parity": { + Label: "Parity", + Type: "enum", + Values: []string{"N", "E", "O", "M", "S"}, + Selected: "N", + }, + "bits": { + Label: "Data bits", + Type: "enum", + Values: []string{"5", "6", "7", "8", "9"}, + Selected: "8", + }, + "stop_bits": { + Label: "Stop bits", + Type: "enum", + Values: []string{"1", "1.5", "2"}, + Selected: "1", + }, + }, +} + +var openedPort serial.Port + func main() { args.Parse() if args.ShowVersion { @@ -34,44 +69,90 @@ func main() { return } - serialDisc := &SerialDiscovery{} - disc := discovery.NewServer(serialDisc) - if err := disc.Run(os.Stdin, os.Stdout); err != nil { + serialMonitor := &SerialMonitor{} + monitorServer := monitor.NewServer(serialMonitor) + if err := monitorServer.Run(os.Stdin, os.Stdout); err != nil { fmt.Fprintf(os.Stderr, "Error: %s\n", err.Error()) os.Exit(1) } } -// SerialDiscovery is the implementation of the serial ports pluggable-discovery -type SerialDiscovery struct { - closeChan chan<- bool +// SerialMonitor is the implementation of the serial ports pluggable-monitor +type SerialMonitor struct { + closeChan chan<- bool //TODO maybe useless } -// Hello is the handler for the pluggable-discovery HELLO command -func (d *SerialDiscovery) Hello(userAgent string, protocolVersion int) error { +// Hello is the handler for the pluggable-monitor HELLO command +func (d *SerialMonitor) Hello(userAgent string, protocol int) error { return nil } -// Quit is the handler for the pluggable-discovery QUIT command -func (d *SerialDiscovery) Quit() { +// Describe is the handler for the pluggable-monitor DESCRIBE command +func (d *SerialMonitor) Describe() (*monitor.PortDescriptor, error) { + return serialSettings, nil } -// Stop is the handler for the pluggable-discovery STOP command -func (d *SerialDiscovery) Stop() error { - if d.closeChan != nil { - d.closeChan <- true - close(d.closeChan) - d.closeChan = nil +// Configure is the handler for the pluggable-monitor CONFIGURE command +func (d *SerialMonitor) Configure(parameterName string, value string) error { + if serialSettings.ConfigurationParameter[parameterName] == nil { + return fmt.Errorf("could not find parameter named %s", parameterName) } - return nil + values := serialSettings.ConfigurationParameter[parameterName].Values + for _, i := range values { + if i == value { + serialSettings.ConfigurationParameter[parameterName].Selected = value + if openedPort != nil { + err := openedPort.SetMode(getMode()) + if err != nil { + return errors.New(err.Error()) + + } + } + return nil + } + } + return fmt.Errorf("invalid value for parameter %s: %s", parameterName, value) } -// StartSync is the handler for the pluggable-discovery START_SYNC command -func (d *SerialDiscovery) StartSync(eventCB discovery.EventCallback, errorCB discovery.ErrorCallback) error { - close, err := sync.Start(eventCB, errorCB) +// Open is the handler for the pluggable-monitor OPEN command +func (d *SerialMonitor) Open(boardPort string) (io.ReadWriter, error) { + if openedPort != nil { + return nil, fmt.Errorf("port already opened: %s", boardPort) + } + openedPort, err := serial.Open(boardPort, getMode()) if err != nil { - return err + fmt.Print(boardPort) + openedPort = nil + return nil, errors.New(err.Error()) + } - d.closeChan = close + return openedPort, nil +} + +// Close is the handler for the pluggable-monitor CLOSE command +func (d *SerialMonitor) Close() error { + if openedPort == nil { + return errors.New("port already closed") + } + openedPort.Close() + openedPort = nil return nil } + +// Quit is the handler for the pluggable-monitor QUIT command +func (d *SerialMonitor) Quit() {} + +func getMode() *serial.Mode { + baud, _ := strconv.Atoi(serialSettings.ConfigurationParameter["baudrate"].Selected) + parity, _ := strconv.Atoi(serialSettings.ConfigurationParameter["parity"].Selected) + dataBits, _ := strconv.Atoi(serialSettings.ConfigurationParameter["bits"].Selected) + stopBits, _ := strconv.Atoi(serialSettings.ConfigurationParameter["stop_bits"].Selected) + + mode := &serial.Mode{ + BaudRate: baud, + Parity: serial.Parity(parity), + DataBits: dataBits, + StopBits: serial.StopBits(stopBits), + } + return mode +} diff --git a/sync/sync.go b/sync/sync.go deleted file mode 100644 index 353afd6..0000000 --- a/sync/sync.go +++ /dev/null @@ -1,83 +0,0 @@ -// -// This file is part of serial-monitor. -// -// Copyright 2018-2021 ARDUINO SA (http://www.arduino.cc/) -// -// This software is released under the GNU General Public License version 3, -// which covers the main part of arduino-cli. -// The terms of this license can be found at: -// https://www.gnu.org/licenses/gpl-3.0.en.html -// -// You can be released from the requirements of the above licenses by purchasing -// a commercial license. Buying such a license is mandatory if you want to modify or -// otherwise use the software for commercial activities involving the Arduino -// software without disclosing the source code of your own applications. To purchase -// a commercial license, send an email to license@arduino.cc. -// - -package sync - -import ( - "github.com/arduino/go-properties-orderedmap" - discovery "github.com/arduino/pluggable-discovery-protocol-handler/v2" - "go.bug.st/serial/enumerator" -) - -// processUpdates sends 'add' and 'remove' events by comparing two ports enumeration -// made at different times: -// - ports present in the new list but not in the old list are reported as 'added' -// - ports present in the old list but not in the new list are reported as 'removed' -func processUpdates(old, new []*enumerator.PortDetails, eventCB discovery.EventCallback) { - for _, oldPort := range old { - if !portListHas(new, oldPort) { - eventCB("remove", &discovery.Port{ - Address: oldPort.Name, - Protocol: "serial", - }) - } - } - - for _, newPort := range new { - if !portListHas(old, newPort) { - eventCB("add", toDiscoveryPort(newPort)) - } - } -} - -// portListHas checks if port is contained in list. The port metadata are -// compared in particular the port address, and vid/pid if the port is a usb port. -func portListHas(list []*enumerator.PortDetails, port *enumerator.PortDetails) bool { - for _, p := range list { - if port.Name == p.Name && port.IsUSB == p.IsUSB { - if p.IsUSB && - port.VID == p.VID && - port.PID == p.PID && - port.SerialNumber == p.SerialNumber { - return true - } - if !p.IsUSB { - return true - } - } - } - return false -} - -func toDiscoveryPort(port *enumerator.PortDetails) *discovery.Port { - protocolLabel := "Serial Port" - props := properties.NewMap() - if port.IsUSB { - protocolLabel += " (USB)" - props.Set("vid", "0x"+port.VID) - props.Set("pid", "0x"+port.PID) - props.Set("serialNumber", port.SerialNumber) - } - res := &discovery.Port{ - Address: port.Name, - AddressLabel: port.Name, - Protocol: "serial", - ProtocolLabel: protocolLabel, - Properties: props, - } - return res -} diff --git a/sync/sync_darwin.go b/sync/sync_darwin.go deleted file mode 100644 index 5396fe5..0000000 --- a/sync/sync_darwin.go +++ /dev/null @@ -1,114 +0,0 @@ -// -// This file is part of serial-monitor. -// -// Copyright 2018-2021 ARDUINO SA (http://www.arduino.cc/) -// -// This software is released under the GNU General Public License version 3, -// which covers the main part of arduino-cli. -// The terms of this license can be found at: -// https://www.gnu.org/licenses/gpl-3.0.en.html -// -// You can be released from the requirements of the above licenses by purchasing -// a commercial license. Buying such a license is mandatory if you want to modify or -// otherwise use the software for commercial activities involving the Arduino -// software without disclosing the source code of your own applications. To purchase -// a commercial license, send an email to license@arduino.cc. -// - -package sync - -import ( - "fmt" - "syscall" - - discovery "github.com/arduino/pluggable-discovery-protocol-handler/v2" - "go.bug.st/serial/enumerator" -) - -// Start the sync process, successful events will be passed to eventCB, errors to errorCB. -// Returns a channel used to stop the sync process. -// Returns error if sync process can't be started. -func Start(eventCB discovery.EventCallback, errorCB discovery.ErrorCallback) (chan<- bool, error) { - // create kqueue - kq, err := syscall.Kqueue() - if err != nil { - return nil, err - } - - // open folder - fd, err := syscall.Open("/dev", syscall.O_RDONLY, 0) - if err != nil { - return nil, err - } - - // build kevent - ev1 := syscall.Kevent_t{ - Ident: uint64(fd), - Filter: syscall.EVFILT_VNODE, - Flags: syscall.EV_ADD | syscall.EV_ENABLE | syscall.EV_ONESHOT, - Fflags: syscall.NOTE_DELETE | syscall.NOTE_WRITE, - Data: 0, - Udata: nil, - } - - // Run synchronous event emitter - closeChan := make(chan bool) - - go func() { - defer syscall.Close(fd) - defer syscall.Close(kq) - - // Output initial port state: get the current port list to send as initial "add" events - current, err := enumerator.GetDetailedPortsList() - if err != nil { - errorCB(err.Error()) - return - } - for _, port := range current { - eventCB("add", toDiscoveryPort(port)) - } - - // wait for events - events := make([]syscall.Kevent_t, 10) - - for { - t100ms := syscall.Timespec{Nsec: 100_000_000, Sec: 0} - n, err := syscall.Kevent(kq, []syscall.Kevent_t{ev1}, events, &t100ms) - select { - case <-closeChan: - return - default: - } - if err == syscall.EINTR { - continue - } - if err != nil { - errorCB(fmt.Sprintf("Error decoding serial event: %s", err)) - break - } - if n <= 0 { - continue - } - - // if there is an event retry up to 5 times - var enumeratorErr error - for retries := 0; retries < 5; retries++ { - updates, err := enumerator.GetDetailedPortsList() - if err != nil { - enumeratorErr = err - break - } - processUpdates(current, updates, eventCB) - current = updates - } - if enumeratorErr != nil { - errorCB(fmt.Sprintf("Error enumerating serial ports: %s", enumeratorErr)) - break - } - } - - <-closeChan - }() - - return closeChan, nil -} diff --git a/sync/sync_default.go b/sync/sync_default.go deleted file mode 100644 index 6702fe5..0000000 --- a/sync/sync_default.go +++ /dev/null @@ -1,28 +0,0 @@ -// -// This file is part of serial-monitor. -// -// Copyright 2018 ARDUINO SA (http://www.arduino.cc/) -// -// This software is released under the GNU General Public License version 3, -// which covers the main part of arduino-cli. -// The terms of this license can be found at: -// https://www.gnu.org/licenses/gpl-3.0.en.html -// -// You can be released from the requirements of the above licenses by purchasing -// a commercial license. Buying such a license is mandatory if you want to modify or -// otherwise use the software for commercial activities involving the Arduino -// software without disclosing the source code of your own applications. To purchase -// a commercial license, send an email to license@arduino.cc. -// - -//go:build !linux && !windows && !darwin -// +build !linux,!windows,!darwin - -package sync - -import "fmt" - -// Start fallback implementation -func Start() (chan<- bool, error) { - return nil, fmt.Errorf("Command START_SYNC not supported") -} diff --git a/sync/sync_linux.go b/sync/sync_linux.go deleted file mode 100644 index 0f98d4b..0000000 --- a/sync/sync_linux.go +++ /dev/null @@ -1,95 +0,0 @@ -// -// This file is part of serial-monitor. -// -// Copyright 2018-2021 ARDUINO SA (http://www.arduino.cc/) -// -// This software is released under the GNU General Public License version 3, -// which covers the main part of arduino-cli. -// The terms of this license can be found at: -// https://www.gnu.org/licenses/gpl-3.0.en.html -// -// You can be released from the requirements of the above licenses by purchasing -// a commercial license. Buying such a license is mandatory if you want to modify or -// otherwise use the software for commercial activities involving the Arduino -// software without disclosing the source code of your own applications. To purchase -// a commercial license, send an email to license@arduino.cc. -// - -package sync - -import ( - "fmt" - "io" - - discovery "github.com/arduino/pluggable-discovery-protocol-handler/v2" - "github.com/s-urbaniak/uevent" - "go.bug.st/serial/enumerator" -) - -// Start the sync process, successful events will be passed to eventCB, errors to errorCB. -// Returns a channel used to stop the sync process. -// Returns error if sync process can't be started. -func Start(eventCB discovery.EventCallback, errorCB discovery.ErrorCallback) (chan<- bool, error) { - // Get the current port list to send as initial "add" events - current, err := enumerator.GetDetailedPortsList() - if err != nil { - return nil, err - } - - // Start sync reader from udev - syncReader, err := uevent.NewReader() - if err != nil { - return nil, err - } - - closeChan := make(chan bool) - go func() { - <-closeChan - syncReader.Close() - }() - - // Run synchronous event emitter - go func() { - // Output initial port state - for _, port := range current { - eventCB("add", toDiscoveryPort(port)) - } - - dec := uevent.NewDecoder(syncReader) - for { - evt, err := dec.Decode() - if err == io.EOF { - // The underlying syncReader has been closed - // so there's nothing else to read - return - } else if err != nil { - errorCB(fmt.Sprintf("Error decoding serial event: %s", err)) - return - } - if evt.Subsystem != "tty" { - continue - } - changedPort := "/dev/" + evt.Vars["DEVNAME"] - if evt.Action == "add" { - portList, err := enumerator.GetDetailedPortsList() - if err != nil { - continue - } - for _, port := range portList { - if port.IsUSB && port.Name == changedPort { - eventCB("add", toDiscoveryPort(port)) - break - } - } - } - if evt.Action == "remove" { - eventCB("remove", &discovery.Port{ - Address: changedPort, - Protocol: "serial", - }) - } - } - }() - - return closeChan, nil -} diff --git a/sync/sync_windows.go b/sync/sync_windows.go deleted file mode 100644 index 4adcc1d..0000000 --- a/sync/sync_windows.go +++ /dev/null @@ -1,217 +0,0 @@ -// -// This file is part of serial-monitor. -// -// Copyright 2018-2021 ARDUINO SA (http://www.arduino.cc/) -// -// This software is released under the GNU General Public License version 3, -// which covers the main part of arduino-cli. -// The terms of this license can be found at: -// https://www.gnu.org/licenses/gpl-3.0.en.html -// -// You can be released from the requirements of the above licenses by purchasing -// a commercial license. Buying such a license is mandatory if you want to modify or -// otherwise use the software for commercial activities involving the Arduino -// software without disclosing the source code of your own applications. To purchase -// a commercial license, send an email to license@arduino.cc. -// - -package sync - -import ( - "fmt" - "runtime" - "syscall" - "time" - "unsafe" - - discovery "github.com/arduino/pluggable-discovery-protocol-handler/v2" - "go.bug.st/serial/enumerator" -) - -//go:generate go run golang.org/x/sys/windows/mkwinsyscall -output zsyscall_windows.go sync_windows.go - -//sys getModuleHandle(moduleName *byte) (handle syscall.Handle, err error) = GetModuleHandleA -//sys registerClass(wndClass *wndClass) (atom uint16, err error) = user32.RegisterClassA -//sys defWindowProc(hwnd syscall.Handle, msg uint32, wParam uintptr, lParam uintptr) (lResult uintptr) = user32.DefWindowProcW -//sys createWindowEx(exstyle uint32, className *byte, windowText *byte, style uint32, x int32, y int32, width int32, height int32, parent syscall.Handle, menu syscall.Handle, hInstance syscall.Handle, lpParam uintptr) (hwnd syscall.Handle, err error) = user32.CreateWindowExA -//sys registerDeviceNotification(recipient syscall.Handle, filter *devBroadcastDeviceInterface, flags uint32) (devHandle syscall.Handle, err error) = user32.RegisterDeviceNotificationA -//sys getMessage(msg *msg, hwnd syscall.Handle, msgFilterMin uint32, msgFilterMax uint32) (res int32, err error) = user32.GetMessageA -//sys translateMessage(msg *msg) (res bool) = user32.TranslateMessage -//sys dispatchMessage(msg *msg) (res int32, err error) = user32.DispatchMessageA - -type wndClass struct { - style uint32 - wndProc uintptr - clsExtra int32 - wndExtra int32 - instance syscall.Handle - icon syscall.Handle - cursor syscall.Handle - brBackground syscall.Handle - menuName *byte - className *byte -} - -type point struct { - x int32 - y int32 -} - -type msg struct { - hwnd syscall.Handle - message uint32 - wParam uintptr - lParam uintptr - time int32 - pt point - lPrivate int32 -} - -const wsExDlgModalFrame = 0x00000001 -const wsExTopmost = 0x00000008 -const wsExTransparent = 0x00000020 -const wsExMDIChild = 0x00000040 -const wsExToolWindow = 0x00000080 -const wsExAppWindow = 0x00040000 -const wsExLayered = 0x00080000 - -type guid struct { - data1 uint32 - data2 uint16 - data3 uint16 - data4 [8]byte -} - -type devBroadcastDeviceInterface struct { - dwSize uint32 - dwDeviceType uint32 - dwReserved uint32 - classGUID guid - szName uint16 -} - -//var usbEventGUID = guid{???} // TODO - -const deviceNotifyWindowHandle = 0 -const deviceNotifySserviceHandle = 1 -const deviceNotifyAllInterfaceClasses = 4 - -const dbtDevtypeDeviceInterface = 5 - -func init() { - runtime.LockOSThread() -} - -// Start the sync process, successful events will be passed to eventCB, errors to errorCB. -// Returns a channel used to stop the sync process. -// Returns error if sync process can't be started. -func Start(eventCB discovery.EventCallback, errorCB discovery.ErrorCallback) (chan<- bool, error) { - startResult := make(chan error) - event := make(chan bool, 1) - go func() { - initAndRunWindowHandler(startResult, event) - }() - if err := <-startResult; err != nil { - return nil, err - } - go func() { - current, err := enumerator.GetDetailedPortsList() - if err != nil { - errorCB(fmt.Sprintf("Error enumerating serial ports: %s", err)) - return - } - for _, port := range current { - eventCB("add", toDiscoveryPort(port)) - } - - for { - <-event - - // Wait 100 ms to pile up events - time.Sleep(100 * time.Millisecond) - select { - case <-event: - // Just one event could be queued because the channel has size 1 - // (more events coming after this one are discarded on send) - default: - } - - // Send updates - updates, err := enumerator.GetDetailedPortsList() - if err != nil { - errorCB(fmt.Sprintf("Error enumerating serial ports: %s", err)) - return - } - processUpdates(current, updates, eventCB) - current = updates - } - }() - quit := make(chan bool) - go func() { - <-quit - // TODO: implement termination channel - }() - return quit, nil -} - -func initAndRunWindowHandler(startResult chan<- error, event chan<- bool) { - handle, err := getModuleHandle(nil) - if err != nil { - startResult <- err - return - } - - wndProc := func(hwnd syscall.Handle, msg uint32, wParam uintptr, lParam uintptr) uintptr { - select { - case event <- true: - default: - } - return defWindowProc(hwnd, msg, wParam, lParam) - } - - className := syscall.StringBytePtr("serialdiscovery") - windowClass := &wndClass{ - instance: handle, - className: className, - wndProc: syscall.NewCallback(wndProc), - } - if _, err := registerClass(windowClass); err != nil { - startResult <- fmt.Errorf("registering new window: %s", err) - return - } - - hwnd, err := createWindowEx(wsExTopmost, className, className, 0, 0, 0, 0, 0, 0, 0, 0, 0) - if err != nil { - startResult <- fmt.Errorf("creating window: %s", err) - return - } - - notificationFilter := devBroadcastDeviceInterface{ - dwDeviceType: dbtDevtypeDeviceInterface, - // TODO: Filter USB events using the correct GUID - } - notificationFilter.dwSize = uint32(unsafe.Sizeof(notificationFilter)) - - if _, err := registerDeviceNotification( - hwnd, - ¬ificationFilter, - deviceNotifyWindowHandle|deviceNotifyAllInterfaceClasses); err != nil { - startResult <- fmt.Errorf("registering for devices notification: %s", err) - return - } - - startResult <- nil - - var m msg - for { - if res, err := getMessage(&m, hwnd, 0, 0); res == 0 || res == -1 { - if err != nil { - // TODO: send err and stop sync mode. - // fmt.Println(err) - } - break - } - translateMessage(&m) - dispatchMessage(&m) - } -} diff --git a/sync/zsyscall_windows.go b/sync/zsyscall_windows.go deleted file mode 100644 index 79abf45..0000000 --- a/sync/zsyscall_windows.go +++ /dev/null @@ -1,118 +0,0 @@ -// Code generated by 'go generate'; DO NOT EDIT. - -package sync - -import ( - "syscall" - "unsafe" - - "golang.org/x/sys/windows" -) - -var _ unsafe.Pointer - -// Do the interface allocations only once for common -// Errno values. -const ( - errnoERROR_IO_PENDING = 997 -) - -var ( - errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING) - errERROR_EINVAL error = syscall.EINVAL -) - -// errnoErr returns common boxed Errno values, to prevent -// allocations at runtime. -func errnoErr(e syscall.Errno) error { - switch e { - case 0: - return errERROR_EINVAL - case errnoERROR_IO_PENDING: - return errERROR_IO_PENDING - } - // TODO: add more here, after collecting data on the common - // error values see on Windows. (perhaps when running - // all.bat?) - return e -} - -var ( - modkernel32 = windows.NewLazySystemDLL("kernel32.dll") - moduser32 = windows.NewLazySystemDLL("user32.dll") - - procGetModuleHandleA = modkernel32.NewProc("GetModuleHandleA") - procCreateWindowExA = moduser32.NewProc("CreateWindowExA") - procDefWindowProcW = moduser32.NewProc("DefWindowProcW") - procDispatchMessageA = moduser32.NewProc("DispatchMessageA") - procGetMessageA = moduser32.NewProc("GetMessageA") - procRegisterClassA = moduser32.NewProc("RegisterClassA") - procRegisterDeviceNotificationA = moduser32.NewProc("RegisterDeviceNotificationA") - procTranslateMessage = moduser32.NewProc("TranslateMessage") -) - -func getModuleHandle(moduleName *byte) (handle syscall.Handle, err error) { - r0, _, e1 := syscall.Syscall(procGetModuleHandleA.Addr(), 1, uintptr(unsafe.Pointer(moduleName)), 0, 0) - handle = syscall.Handle(r0) - if handle == 0 { - err = errnoErr(e1) - } - return -} - -func createWindowEx(exstyle uint32, className *byte, windowText *byte, style uint32, x int32, y int32, width int32, height int32, parent syscall.Handle, menu syscall.Handle, hInstance syscall.Handle, lpParam uintptr) (hwnd syscall.Handle, err error) { - r0, _, e1 := syscall.Syscall12(procCreateWindowExA.Addr(), 12, uintptr(exstyle), uintptr(unsafe.Pointer(className)), uintptr(unsafe.Pointer(windowText)), uintptr(style), uintptr(x), uintptr(y), uintptr(width), uintptr(height), uintptr(parent), uintptr(menu), uintptr(hInstance), uintptr(lpParam)) - hwnd = syscall.Handle(r0) - if hwnd == 0 { - err = errnoErr(e1) - } - return -} - -func defWindowProc(hwnd syscall.Handle, msg uint32, wParam uintptr, lParam uintptr) (lResult uintptr) { - r0, _, _ := syscall.Syscall6(procDefWindowProcW.Addr(), 4, uintptr(hwnd), uintptr(msg), uintptr(wParam), uintptr(lParam), 0, 0) - lResult = uintptr(r0) - return -} - -func dispatchMessage(msg *msg) (res int32, err error) { - r0, _, e1 := syscall.Syscall(procDispatchMessageA.Addr(), 1, uintptr(unsafe.Pointer(msg)), 0, 0) - res = int32(r0) - if res == 0 { - err = errnoErr(e1) - } - return -} - -func getMessage(msg *msg, hwnd syscall.Handle, msgFilterMin uint32, msgFilterMax uint32) (res int32, err error) { - r0, _, e1 := syscall.Syscall6(procGetMessageA.Addr(), 4, uintptr(unsafe.Pointer(msg)), uintptr(hwnd), uintptr(msgFilterMin), uintptr(msgFilterMax), 0, 0) - res = int32(r0) - if res == 0 { - err = errnoErr(e1) - } - return -} - -func registerClass(wndClass *wndClass) (atom uint16, err error) { - r0, _, e1 := syscall.Syscall(procRegisterClassA.Addr(), 1, uintptr(unsafe.Pointer(wndClass)), 0, 0) - atom = uint16(r0) - if atom == 0 { - err = errnoErr(e1) - } - return -} - -func registerDeviceNotification(recipient syscall.Handle, filter *devBroadcastDeviceInterface, flags uint32) (devHandle syscall.Handle, err error) { - r0, _, e1 := syscall.Syscall(procRegisterDeviceNotificationA.Addr(), 3, uintptr(recipient), uintptr(unsafe.Pointer(filter)), uintptr(flags)) - devHandle = syscall.Handle(r0) - if devHandle == 0 { - err = errnoErr(e1) - } - return -} - -func translateMessage(msg *msg) (res bool) { - r0, _, _ := syscall.Syscall(procTranslateMessage.Addr(), 1, uintptr(unsafe.Pointer(msg)), 0, 0) - res = r0 != 0 - return -} From 6050c1eec60ca316018d14005b6b43f5b39b8feb Mon Sep 17 00:00:00 2001 From: Umberto Baldi Date: Mon, 13 Sep 2021 12:13:40 +0200 Subject: [PATCH 5/9] fix open --- main.go | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/main.go b/main.go index 18bec28..3ad211e 100644 --- a/main.go +++ b/main.go @@ -144,15 +144,35 @@ func (d *SerialMonitor) Quit() {} func getMode() *serial.Mode { baud, _ := strconv.Atoi(serialSettings.ConfigurationParameter["baudrate"].Selected) - parity, _ := strconv.Atoi(serialSettings.ConfigurationParameter["parity"].Selected) + var parity serial.Parity + switch serialSettings.ConfigurationParameter["parity"].Selected { + case "N": + parity = serial.NoParity + case "E": + parity = serial.EvenParity + case "O": + parity = serial.OddParity + case "M": + parity = serial.MarkParity + case "S": + parity = serial.SpaceParity + } dataBits, _ := strconv.Atoi(serialSettings.ConfigurationParameter["bits"].Selected) - stopBits, _ := strconv.Atoi(serialSettings.ConfigurationParameter["stop_bits"].Selected) + var stopBits serial.StopBits + switch serialSettings.ConfigurationParameter["stop_bits"].Selected { + case "1": + stopBits = serial.OneStopBit + case "1.5": + stopBits = serial.OnePointFiveStopBits + case "2": + stopBits = serial.TwoStopBits + } mode := &serial.Mode{ BaudRate: baud, - Parity: serial.Parity(parity), + Parity: parity, DataBits: dataBits, - StopBits: serial.StopBits(stopBits), + StopBits: stopBits, } return mode } From 3227c71817850150cf93ff05abdfaeaeeed9e4f2 Mon Sep 17 00:00:00 2001 From: Umberto Baldi Date: Mon, 13 Sep 2021 12:20:23 +0200 Subject: [PATCH 6/9] apply suggestions from code review --- main.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/main.go b/main.go index 3ad211e..d864083 100644 --- a/main.go +++ b/main.go @@ -100,14 +100,13 @@ func (d *SerialMonitor) Configure(parameterName string, value string) error { values := serialSettings.ConfigurationParameter[parameterName].Values for _, i := range values { if i == value { - serialSettings.ConfigurationParameter[parameterName].Selected = value if openedPort != nil { err := openedPort.SetMode(getMode()) if err != nil { return errors.New(err.Error()) - } } + serialSettings.ConfigurationParameter[parameterName].Selected = value return nil } } @@ -121,9 +120,8 @@ func (d *SerialMonitor) Open(boardPort string) (io.ReadWriter, error) { } openedPort, err := serial.Open(boardPort, getMode()) if err != nil { - fmt.Print(boardPort) openedPort = nil - return nil, errors.New(err.Error()) + return nil, err } return openedPort, nil From 883e7f069741e0b2514f44a6ba36c7332ddc0e53 Mon Sep 17 00:00:00 2001 From: Umberto Baldi Date: Mon, 13 Sep 2021 16:14:08 +0200 Subject: [PATCH 7/9] general enhancements --- main.go | 111 +++++++++++++++++++++++++++++--------------------------- 1 file changed, 58 insertions(+), 53 deletions(-) diff --git a/main.go b/main.go index d864083..8c4b7fb 100644 --- a/main.go +++ b/main.go @@ -30,38 +30,6 @@ import ( "go.bug.st/serial" ) -var serialSettings = &monitor.PortDescriptor{ - Protocol: "serial", - ConfigurationParameter: map[string]*monitor.PortParameterDescriptor{ - "baudrate": { - Label: "Baudrate", - Type: "enum", - Values: []string{"300", "600", "750", "1200", "2400", "4800", "9600", "19200", "38400", "57600", "115200", "230400", "460800", "500000", "921600", "1000000", "2000000"}, - Selected: "9600", - }, - "parity": { - Label: "Parity", - Type: "enum", - Values: []string{"N", "E", "O", "M", "S"}, - Selected: "N", - }, - "bits": { - Label: "Data bits", - Type: "enum", - Values: []string{"5", "6", "7", "8", "9"}, - Selected: "8", - }, - "stop_bits": { - Label: "Stop bits", - Type: "enum", - Values: []string{"1", "1.5", "2"}, - Selected: "1", - }, - }, -} - -var openedPort serial.Port - func main() { args.Parse() if args.ShowVersion { @@ -69,8 +37,7 @@ func main() { return } - serialMonitor := &SerialMonitor{} - monitorServer := monitor.NewServer(serialMonitor) + monitorServer := monitor.NewServer(NewSerialMonitor()) if err := monitorServer.Run(os.Stdin, os.Stdout); err != nil { fmt.Fprintf(os.Stderr, "Error: %s\n", err.Error()) os.Exit(1) @@ -79,7 +46,44 @@ func main() { // SerialMonitor is the implementation of the serial ports pluggable-monitor type SerialMonitor struct { - closeChan chan<- bool //TODO maybe useless + serialPort serial.Port + serialSettings *monitor.PortDescriptor + openedPort bool +} + +func NewSerialMonitor() *SerialMonitor { + return &SerialMonitor{ + serialSettings: &monitor.PortDescriptor{ + Protocol: "serial", + ConfigurationParameter: map[string]*monitor.PortParameterDescriptor{ + "baudrate": { + Label: "Baudrate", + Type: "enum", + Values: []string{"300", "600", "750", "1200", "2400", "4800", "9600", "19200", "38400", "57600", "115200", "230400", "460800", "500000", "921600", "1000000", "2000000"}, + Selected: "9600", + }, + "parity": { + Label: "Parity", + Type: "enum", + Values: []string{"N", "E", "O", "M", "S"}, + Selected: "N", + }, + "bits": { + Label: "Data bits", + Type: "enum", + Values: []string{"5", "6", "7", "8", "9"}, + Selected: "8", + }, + "stop_bits": { + Label: "Stop bits", + Type: "enum", + Values: []string{"1", "1.5", "2"}, + Selected: "1", + }, + }, + }, + openedPort: false, + } } // Hello is the handler for the pluggable-monitor HELLO command @@ -89,24 +93,24 @@ func (d *SerialMonitor) Hello(userAgent string, protocol int) error { // Describe is the handler for the pluggable-monitor DESCRIBE command func (d *SerialMonitor) Describe() (*monitor.PortDescriptor, error) { - return serialSettings, nil + return d.serialSettings, nil } // Configure is the handler for the pluggable-monitor CONFIGURE command func (d *SerialMonitor) Configure(parameterName string, value string) error { - if serialSettings.ConfigurationParameter[parameterName] == nil { + if d.serialSettings.ConfigurationParameter[parameterName] == nil { return fmt.Errorf("could not find parameter named %s", parameterName) } - values := serialSettings.ConfigurationParameter[parameterName].Values + values := d.serialSettings.ConfigurationParameter[parameterName].Values for _, i := range values { if i == value { - if openedPort != nil { - err := openedPort.SetMode(getMode()) + if d.openedPort { + err := d.serialPort.SetMode(d.getMode()) if err != nil { return errors.New(err.Error()) } } - serialSettings.ConfigurationParameter[parameterName].Selected = value + d.serialSettings.ConfigurationParameter[parameterName].Selected = value return nil } } @@ -115,35 +119,36 @@ func (d *SerialMonitor) Configure(parameterName string, value string) error { // Open is the handler for the pluggable-monitor OPEN command func (d *SerialMonitor) Open(boardPort string) (io.ReadWriter, error) { - if openedPort != nil { + if d.openedPort { return nil, fmt.Errorf("port already opened: %s", boardPort) } - openedPort, err := serial.Open(boardPort, getMode()) + serialPort, err := serial.Open(boardPort, d.getMode()) if err != nil { - openedPort = nil return nil, err } - return openedPort, nil + d.openedPort = true + d.serialPort = serialPort + return d.serialPort, nil } // Close is the handler for the pluggable-monitor CLOSE command func (d *SerialMonitor) Close() error { - if openedPort == nil { + if !d.openedPort { return errors.New("port already closed") } - openedPort.Close() - openedPort = nil + d.serialPort.Close() + d.openedPort = false return nil } // Quit is the handler for the pluggable-monitor QUIT command func (d *SerialMonitor) Quit() {} -func getMode() *serial.Mode { - baud, _ := strconv.Atoi(serialSettings.ConfigurationParameter["baudrate"].Selected) +func (d *SerialMonitor) getMode() *serial.Mode { + baud, _ := strconv.Atoi(d.serialSettings.ConfigurationParameter["baudrate"].Selected) var parity serial.Parity - switch serialSettings.ConfigurationParameter["parity"].Selected { + switch d.serialSettings.ConfigurationParameter["parity"].Selected { case "N": parity = serial.NoParity case "E": @@ -155,9 +160,9 @@ func getMode() *serial.Mode { case "S": parity = serial.SpaceParity } - dataBits, _ := strconv.Atoi(serialSettings.ConfigurationParameter["bits"].Selected) + dataBits, _ := strconv.Atoi(d.serialSettings.ConfigurationParameter["bits"].Selected) var stopBits serial.StopBits - switch serialSettings.ConfigurationParameter["stop_bits"].Selected { + switch d.serialSettings.ConfigurationParameter["stop_bits"].Selected { case "1": stopBits = serial.OneStopBit case "1.5": From fdc9298af168381d46a33d05b303da3c2177cce1 Mon Sep 17 00:00:00 2001 From: Umberto Baldi Date: Mon, 13 Sep 2021 16:18:10 +0200 Subject: [PATCH 8/9] add comment --- main.go | 1 + 1 file changed, 1 insertion(+) diff --git a/main.go b/main.go index 8c4b7fb..4d8ae87 100644 --- a/main.go +++ b/main.go @@ -51,6 +51,7 @@ type SerialMonitor struct { openedPort bool } +// NewSerialMonitor will initialize and return a SerialMonitor func NewSerialMonitor() *SerialMonitor { return &SerialMonitor{ serialSettings: &monitor.PortDescriptor{ From 47ba3f65e3e2fdb498a5b571eb65830678bf7414 Mon Sep 17 00:00:00 2001 From: Umberto Baldi Date: Tue, 14 Sep 2021 15:03:30 +0200 Subject: [PATCH 9/9] fix value not being set correctly --- main.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/main.go b/main.go index 4d8ae87..0f2b12b 100644 --- a/main.go +++ b/main.go @@ -105,13 +105,15 @@ func (d *SerialMonitor) Configure(parameterName string, value string) error { values := d.serialSettings.ConfigurationParameter[parameterName].Values for _, i := range values { if i == value { + oldValue := d.serialSettings.ConfigurationParameter[parameterName].Selected + d.serialSettings.ConfigurationParameter[parameterName].Selected = value if d.openedPort { err := d.serialPort.SetMode(d.getMode()) if err != nil { + d.serialSettings.ConfigurationParameter[parameterName].Selected = oldValue return errors.New(err.Error()) } } - d.serialSettings.ConfigurationParameter[parameterName].Selected = value return nil } }