diff --git a/.canon.yaml b/.canon.yaml index 798511f..18dfc83 100644 --- a/.canon.yaml +++ b/.canon.yaml @@ -1,5 +1,5 @@ # This file provides project-level configuration for the canon dev environment utility. https://github.com/viamrobotics/canon -MODULE: +uln2003: default: true image_amd64: ghcr.io/viamrobotics/rdk-devenv:amd64-cache image_arm64: ghcr.io/viamrobotics/rdk-devenv:arm64-cache @@ -9,7 +9,7 @@ MODULE: group: testbot persistent: true -MODULE-antique: +uln2003-antique: image_amd64: ghcr.io/viamrobotics/antique2:amd64-cache image_arm64: ghcr.io/viamrobotics/antique2:arm64-cache minimum_date: 2023-10-26T20:00:00.0Z diff --git a/.github/workflows/pr-labeler.yml b/.github/workflows/pr-labeler.yml index 3a6d0dd..6b8b4e9 100644 --- a/.github/workflows/pr-labeler.yml +++ b/.github/workflows/pr-labeler.yml @@ -21,14 +21,14 @@ jobs: script: | let prNumber = context.payload.pull_request && context.payload.pull_request.number; try { - await github.rest.issues.removeLabel({owner: "viam-modules", repo: "MODULE", issue_number: prNumber, name: "safe to test"}); + await github.rest.issues.removeLabel({owner: "viam-modules", repo: "uln2003", issue_number: prNumber, name: "safe to test"}); } catch (err) { core.info(`Non-fatal error ${err}, while trying to remove 'safe to test' label.`); } let orgResp = await github.rest.orgs.checkMembershipForUser({org: "viam-modules", username: context.payload.sender.login}); if (orgResp.status === 204) { // order of labeling events must be preserved, so two seperate calls - await github.rest.issues.addLabels({owner: "viam-modules", repo: "MODULE", issue_number: prNumber, labels: ["safe to test"]}); + await github.rest.issues.addLabels({owner: "viam-modules", repo: "uln2003", issue_number: prNumber, labels: ["safe to test"]}); return true; } return false; diff --git a/.github/workflows/pullrequest-trusted.yml b/.github/workflows/pullrequest-trusted.yml index cbde6c8..6921600 100644 --- a/.github/workflows/pullrequest-trusted.yml +++ b/.github/workflows/pullrequest-trusted.yml @@ -12,10 +12,10 @@ on: jobs: test: if: (github.event.label.name == 'safe to test' || github.event.label.name == 'appimage') - uses: viam-modules/MODULE/.github/workflows/test.yml@main + uses: viam-modules/uln2003/.github/workflows/test.yml@main secrets: MONGODB_TEST_OUTPUT_URI: ${{ secrets.MONGODB_TEST_OUTPUT_URI }} DOCKER_PUBLIC_READONLY_PAT: ${{ secrets.DOCKER_PUBLIC_READONLY_PAT }} license_finder: - uses: viam-modules/MODULE/.github/workflows/license_finder.yml@main + uses: viam-modules/uln2003/.github/workflows/license_finder.yml@main diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d7598d2..5f67046 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -46,10 +46,10 @@ jobs: - name: Build and package run: | - canon --profile MODULE + canon --profile uln2003 TARGET_OS=${{ matrix.platform }} TARGET_ARCH=${{ matrix.arch }} make module - - name: Upload MODULE module to registry + - name: Upload uln2003 module to registry uses: viamrobotics/upload-module@main with: meta-path: meta.json diff --git a/MODEL/MODEL.go b/MODEL/MODEL.go deleted file mode 100644 index e69de29..0000000 diff --git a/Makefile b/Makefile index a348bb3..9ed65a8 100644 --- a/Makefile +++ b/Makefile @@ -9,13 +9,13 @@ endif module: build rm -f $(BIN_OUTPUT_PATH)/module.tar.gz - tar czf $(BIN_OUTPUT_PATH)/module.tar.gz $(BIN_OUTPUT_PATH)/MODULE meta.json + tar czf $(BIN_OUTPUT_PATH)/module.tar.gz $(BIN_OUTPUT_PATH)/uln2003 meta.json build: build-go build-go: - rm -f $(BIN_OUTPUT_PATH)/MODULE - go build -tags no_cgo,osusergo,netgo -ldflags="-extldflags=-static $(COMMON_LDFLAGS)" -o $(BIN_OUTPUT_PATH)/MODULE main.go + rm -f $(BIN_OUTPUT_PATH)/uln2003 + go build -tags no_cgo,osusergo,netgo -ldflags="-extldflags=-static $(COMMON_LDFLAGS)" -o $(BIN_OUTPUT_PATH)/uln2003 main.go tool-install: GOBIN=`pwd`/$(TOOL_BIN) go install \ diff --git a/README.md b/README.md index ac25dd7..02e0ca7 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,16 @@ -# [`MODULE` module](https://github.com/viam-modules/MODULE) +# [`uln2003` module](https://github.com/viam-modules/uln2003) -This [MODULE module](https://app.viam.com/module/viam/MODULE) implements a MODULE [MODEL COMPONENT](), used for using the [`rdk:component:COMPONENT` API](https://docs.viam.com/appendix/apis/components/COMPONENT/). +This [uln2003 module](https://app.viam.com/module/viam/uln2003) implements a uln2003 [28byj-48 motor](), used for using the [`rdk:component:motor` API](https://docs.viam.com/appendix/apis/components/motor/). > [!NOTE] -> Before configuring your COMPONENT, you must [create a machine](https://docs.viam.com/cloud/machines/#add-a-new-machine). +> Before configuring your motor, you must [create a machine](https://docs.viam.com/cloud/machines/#add-a-new-machine). -## Configure your MODEL COMPONENT +## Configure your 28byj-48 motor Navigate to the [**CONFIGURE** tab](https://docs.viam.com/configure/) of your [machine](https://docs.viam.com/fleet/machines/) in the [Viam app](https://app.viam.com/). -[Add COMPONENT / MODULE:MODEL to your machine](https://docs.viam.com/configure/#components). +[Add motor / uln2003:28byj-48 to your machine](https://docs.viam.com/configure/#components). -On the new component panel, copy and paste the following attribute template into your COMPONENT's attributes field: +On the new component panel, copy and paste the following attribute template into your motor's attributes field: ```json { @@ -20,22 +20,22 @@ On the new component panel, copy and paste the following attribute template into ### Attributes -The following attributes are available for `viam:MODULE:MODEL` COMPONENTs: +The following attributes are available for `viam:uln2003:28byj-48` motors: | Attribute | Type | Required? | Description | | --------- | ---- | --------- | ---------- | -| `i2c_bus` | string | **Required** | The index of the I2C bus on the board that the COMPONENT is wired to. | -| `i2c_address` | string | Optional | Default: `0x77`. The [I2C device address](https://learn.adafruit.com/i2c-addresses/overview) of the COMPONENT. | +| `i2c_bus` | string | **Required** | The index of the I2C bus on the board that the motor is wired to. | +| `i2c_address` | string | Optional | Default: `0x77`. The [I2C device address](https://learn.adafruit.com/i2c-addresses/overview) of the motor. | ## Example configuration -### `viam:MODULE:MODEL` +### `viam:uln2003:28byj-48` ```json { - "name": "", - "model": "viam:MODULE:MODEL", - "type": "COMPONENT", + "name": "", + "model": "viam:uln2003:28byj-48", + "type": "motor", "namespace": "rdk", "attributes": { }, @@ -44,6 +44,6 @@ The following attributes are available for `viam:MODULE:MODEL` COMPONENTs: ``` ### Next Steps -- To test your COMPONENT, expand the **TEST** section of its configuration pane or go to the [**CONTROL** tab](https://docs.viam.com/fleet/control/). -- To write code against your COMPONENT, use one of the [available SDKs](https://docs.viam.com/sdks/). -- To view examples using a COMPONENT component, explore [these tutorials](https://docs.viam.com/tutorials/). \ No newline at end of file +- To test your motor, expand the **TEST** section of its configuration pane or go to the [**CONTROL** tab](https://docs.viam.com/fleet/control/). +- To write code against your motor, use one of the [available SDKs](https://docs.viam.com/sdks/). +- To view examples using a motor component, explore [these tutorials](https://docs.viam.com/tutorials/). \ No newline at end of file diff --git a/etc/golangci.yaml b/etc/golangci.yaml index 7db848f..3f58082 100644 --- a/etc/golangci.yaml +++ b/etc/golangci.yaml @@ -33,6 +33,7 @@ linters: - importas - interfacebloat - interfacer + - intrange - ireturn - maintidx - maligned diff --git a/go.mod b/go.mod index d903c1e..2a7d94e 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module MODULE +module uln2003 go 1.23.0 @@ -8,8 +8,11 @@ require ( github.com/edaniels/golinters v0.0.5-0.20220906153528-641155550742 github.com/fullstorydev/grpcurl v1.8.6 github.com/golangci/golangci-lint v1.61.0 + github.com/pkg/errors v0.9.1 github.com/rhysd/actionlint v1.6.24 + go.uber.org/multierr v1.11.0 go.viam.com/rdk v0.48.2 + go.viam.com/test v1.1.1-0.20220913152726-5da9916c08a2 go.viam.com/utils v0.1.110 gotest.tools/gotestsum v1.10.0 ) @@ -42,11 +45,15 @@ require ( github.com/alexkohler/nakedret/v2 v2.0.4 // indirect github.com/alexkohler/prealloc v1.0.0 // indirect github.com/alingse/asasalint v0.0.11 // indirect + github.com/apache/arrow/go/arrow v0.0.0-20201229220542-30ce2eb5d4dc // indirect github.com/ashanbrown/forbidigo v1.6.0 // indirect github.com/ashanbrown/makezero v1.1.1 // indirect + github.com/aybabtme/uniplot v0.0.0-20151203143629-039c559e5e7e // indirect + github.com/benbjohnson/clock v1.3.5 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bep/debounce v1.2.1 // indirect github.com/bkielbasa/cyclop v1.2.1 // indirect + github.com/blackjack/webcam v0.6.1 // indirect github.com/blizzy78/varnamelen v0.8.0 // indirect github.com/bluenviron/gortsplib/v4 v4.8.0 // indirect github.com/bombsimon/wsl/v4 v4.4.1 // indirect @@ -65,6 +72,8 @@ require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/charithe/durationcheck v0.0.10 // indirect github.com/chavacava/garif v0.1.0 // indirect + github.com/chewxy/hm v1.0.0 // indirect + github.com/chewxy/math32 v1.0.8 // indirect github.com/ckaznocha/intrange v0.2.0 // indirect github.com/cncf/xds/go v0.0.0-20240423153145-555b57ec207b // indirect github.com/curioswitch/go-reassign v0.2.0 // indirect @@ -73,6 +82,7 @@ require ( github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect github.com/denis-tingaikin/go-header v0.5.0 // indirect github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f // indirect + github.com/disintegration/imaging v1.6.2 // indirect github.com/dnephin/pflag v1.0.7 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/edaniels/golog v0.0.0-20230215213219-28954395e8d0 // indirect @@ -87,15 +97,22 @@ require ( github.com/fatih/structtag v1.2.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/firefart/nonamedreturns v1.0.5 // indirect + github.com/fogleman/gg v1.3.0 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/fzipp/gocyclo v0.6.0 // indirect + github.com/gen2brain/malgo v0.11.21 // indirect github.com/ghostiam/protogetter v0.3.6 // indirect + github.com/go-audio/audio v1.0.0 // indirect + github.com/go-audio/riff v1.0.0 // indirect + github.com/go-audio/transforms v0.0.0-20180121090939-51830ccc35a5 // indirect + github.com/go-audio/wav v1.1.0 // indirect github.com/go-critic/go-critic v0.11.4 // indirect github.com/go-fonts/liberation v0.3.0 // indirect github.com/go-gl/mathgl v1.0.0 // indirect github.com/go-latex/latex v0.0.0-20230307184459-12ec69307ad9 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-nlopt/nlopt v0.0.0-20230219125344-443d3362dcb5 // indirect github.com/go-pdf/fpdf v0.6.0 // indirect github.com/go-toolsmith/astcast v1.1.0 // indirect github.com/go-toolsmith/astcopy v1.1.0 // indirect @@ -109,6 +126,7 @@ require ( github.com/gobwas/glob v0.2.3 // indirect github.com/goccy/go-json v0.10.2 // indirect github.com/gofrs/flock v0.12.1 // indirect + github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt/v4 v4.5.0 // indirect github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 // indirect @@ -123,6 +141,7 @@ require ( github.com/golangci/revgrep v0.5.3 // indirect github.com/golangci/unconvert v0.0.0-20240309020433-c5143eacb3ed // indirect github.com/gonuts/binary v0.2.0 // indirect + github.com/google/flatbuffers v2.0.6+incompatible // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/google/s2a-go v0.1.8 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect @@ -169,6 +188,7 @@ require ( github.com/lestrrat-go/jwx v1.2.29 // indirect github.com/lestrrat-go/option v1.0.1 // indirect github.com/lib/pq v1.10.9 // indirect + github.com/lmittmann/ppm v1.0.2 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/lufeee/execinquery v1.2.1 // indirect github.com/macabu/inamedparam v0.1.3 // indirect @@ -186,6 +206,8 @@ require ( github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/montanaflynn/stats v0.7.0 // indirect github.com/moricho/tparallel v0.3.2 // indirect + github.com/muesli/clusters v0.0.0-20200529215643-2700303c1762 // indirect + github.com/muesli/kmeans v0.3.1 // indirect github.com/nakabonne/nestif v0.3.1 // indirect github.com/nishanths/exhaustive v0.12.0 // indirect github.com/nishanths/predeclared v0.2.2 // indirect @@ -199,6 +221,7 @@ require ( github.com/pion/interceptor v0.1.29 // indirect github.com/pion/logging v0.2.2 // indirect github.com/pion/mdns v0.0.12 // indirect + github.com/pion/mediadevices v0.6.4 // indirect github.com/pion/randutil v0.1.0 // indirect github.com/pion/rtcp v1.2.14 // indirect github.com/pion/rtp v1.8.7 // indirect @@ -208,7 +231,7 @@ require ( github.com/pion/stun v0.6.1 // indirect github.com/pion/transport/v2 v2.2.10 // indirect github.com/pion/turn/v2 v2.1.6 // indirect - github.com/pkg/errors v0.9.1 // indirect + github.com/pion/webrtc/v3 v3.2.36 // indirect github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/polyfloyd/go-errorlint v1.6.0 // indirect @@ -266,6 +289,8 @@ require ( github.com/xdg-go/scram v1.1.2 // indirect github.com/xdg-go/stringprep v1.0.4 // indirect github.com/xen0n/gosmopolitan v1.2.2 // indirect + github.com/xfmoulet/qoi v0.2.0 // indirect + github.com/xtgo/set v1.0.0 // indirect github.com/yagipy/maintidx v1.0.0 // indirect github.com/yeya24/promlinter v0.3.0 // indirect github.com/ykadowak/zerologlint v0.1.5 // indirect @@ -285,10 +310,9 @@ require ( go.opentelemetry.io/otel/trace v1.29.0 // indirect go.uber.org/automaxprocs v1.5.3 // indirect go.uber.org/goleak v1.3.0 // indirect - go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect go.viam.com/api v0.1.350 // indirect - go.viam.com/test v1.1.1-0.20220913152726-5da9916c08a2 // indirect + go4.org/unsafe/assume-no-moving-gc v0.0.0-20230525183740-e7c30c78aeb2 // indirect golang.org/x/crypto v0.28.0 // indirect golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e // indirect golang.org/x/exp/typeparams v0.0.0-20240314144324-c7f7c6466f7f // indirect @@ -302,6 +326,7 @@ require ( golang.org/x/text v0.19.0 // indirect golang.org/x/time v0.6.0 // indirect golang.org/x/tools v0.24.0 // indirect + golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect gonum.org/v1/gonum v0.12.0 // indirect gonum.org/v1/plot v0.12.0 // indirect google.golang.org/api v0.196.0 // indirect @@ -312,10 +337,16 @@ require ( google.golang.org/protobuf v1.34.2 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/square/go-jose.v2 v2.6.0 // indirect + gopkg.in/src-d/go-billy.v4 v4.3.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + gorgonia.org/tensor v0.9.24 // indirect + gorgonia.org/vecf32 v0.9.0 // indirect + gorgonia.org/vecf64 v0.9.0 // indirect honnef.co/go/tools v0.5.1 // indirect mvdan.cc/gofumpt v0.7.0 // indirect mvdan.cc/unparam v0.0.0-20240528143540-8a5130ca722f // indirect nhooyr.io/websocket v1.8.7 // indirect + periph.io/x/conn/v3 v3.7.0 // indirect + periph.io/x/host/v3 v3.8.1-0.20230331112814-9f0d9f7d76db // indirect ) diff --git a/go.sum b/go.sum index 2d19bd2..56a6cd1 100644 --- a/go.sum +++ b/go.sum @@ -233,6 +233,7 @@ github.com/chavacava/garif v0.1.0 h1:2JHa3hbYf5D9dsgseMKAmc/MZ109otzgNFk5s87H9Pc github.com/chavacava/garif v0.1.0/go.mod h1:XMyYCkEL58DF0oyW4qDjjnPWONs2HBqYKI+UIPD+Gww= github.com/chewxy/hm v1.0.0 h1:zy/TSv3LV2nD3dwUEQL2VhXeoXbb9QkpmdRAVUFiA6k= github.com/chewxy/hm v1.0.0/go.mod h1:qg9YI4q6Fkj/whwHR1D+bOGeF7SniIP40VweVepLjg0= +github.com/chewxy/math32 v1.0.0/go.mod h1:Miac6hA1ohdDUTagnvJy/q+aNnEk16qWUdb8ZVhvCN0= github.com/chewxy/math32 v1.0.8 h1:fU5E4Ec4Z+5RtRAi3TovSxUjQPkgRh+HbP7tKB2OFbM= github.com/chewxy/math32 v1.0.8/go.mod h1:dOB2rcuFrCn6UHrze36WSLVPKtzPMRAQvBvUwkSsLqs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= @@ -583,6 +584,7 @@ github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Z github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/certificate-transparency-go v1.0.21/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg= github.com/google/certificate-transparency-go v1.1.1/go.mod h1:FDKqPvSXawb2ecErVRrD+nfy23RCzyl7eqVCEmlT1Zs= +github.com/google/flatbuffers v1.11.0/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/flatbuffers v2.0.6+incompatible h1:XHFReMv7nFFusa+CEokzWbzaYocKXI6C7hdU5Kgh9Lw= github.com/google/flatbuffers v2.0.6+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -761,6 +763,8 @@ github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfC github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/jonboulle/clockwork v0.2.0/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= +github.com/jonboulle/clockwork v0.3.0 h1:9BSCMi8C+0qdApAp4auwX0RkLGUjs956h0EkuQymUhg= +github.com/jonboulle/clockwork v0.3.0/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= @@ -813,6 +817,7 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= @@ -968,6 +973,7 @@ github.com/moricho/tparallel v0.3.2/go.mod h1:OQ+K3b4Ln3l2TZveGCywybl68glfLEwFGq github.com/mozilla/scribe v0.0.0-20180711195314-fb71baf557c1/go.mod h1:FIczTrinKo8VaLxe6PWTPEXRXDIHz2QAwiaBaP5/4a8= github.com/mozilla/tls-observatory v0.0.0-20201209171846-0547674fceff/go.mod h1:SrKMQvPiws7F7iqYp8/TX+IhxCYhzr6N/1yb8cwHsGk= github.com/mozilla/tls-observatory v0.0.0-20210209181001-cf43108d6880/go.mod h1:FUqVoUPHSEdDR0MnFM3Dh8AU0pZHLXUD127SAJGER/s= +github.com/muesli/clusters v0.0.0-20180605185049-a07a36e67d36/go.mod h1:mw5KDqUj0eLj/6DUNINLVJNoPTFkEuGMHtJsXLviLkY= github.com/muesli/clusters v0.0.0-20200529215643-2700303c1762 h1:p4A2Jx7Lm3NV98VRMKlyWd3nqf8obft8NfXlAUmqd3I= github.com/muesli/clusters v0.0.0-20200529215643-2700303c1762/go.mod h1:mw5KDqUj0eLj/6DUNINLVJNoPTFkEuGMHtJsXLviLkY= github.com/muesli/kmeans v0.3.1 h1:KshLQ8wAETfLWOJKMuDCVYHnafddSa1kwGh/IypGIzY= @@ -1312,6 +1318,7 @@ github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v0.0.0-20170130113145-4d4bfba8f1d1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.1.4/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.2.0/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -1391,6 +1398,7 @@ github.com/viamrobotics/evdev v0.1.3/go.mod h1:N6nuZmPz7HEIpM7esNWwLxbYzqWqLSZkf github.com/viamrobotics/webrtc/v3 v3.99.10 h1:ykE14wm+HkqMD5Ozq4rvhzzfvnXAu14ak/HzA1OCzfY= github.com/viamrobotics/webrtc/v3 v3.99.10/go.mod h1:ziH7/S52IyYAeDdwUUl5ZTbuyKe47fWorAz+0z5w6NA= github.com/viki-org/dnscache v0.0.0-20130720023526-c70c1f23c5d8/go.mod h1:dniwbG03GafCjFohMDmz6Zc6oCuiqgH6tGNyXTkHzXE= +github.com/wcharczuk/go-chart/v2 v2.1.0/go.mod h1:yx7MvAVNcP/kN9lKXM/NTce4au4DFN99j6i1OwDclNA= github.com/wlynxg/anet v0.0.3 h1:PvR53psxFXstc12jelG6f1Lv4MWqE0tI76/hHGjh9rg= github.com/wlynxg/anet v0.0.3/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA= github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= @@ -1556,6 +1564,8 @@ golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMx golang.org/x/image v0.0.0-20190321063152-3fc05d484e9f/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20200927104501-e162460cd6b5/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20210607152325-775e3b0c77b9/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= golang.org/x/image v0.0.0-20211028202545-6944b10bf410/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= golang.org/x/image v0.19.0 h1:D9FX4QWkLfkeqaC62SonffIIuYdOk/UE2XKUBgRIBIQ= @@ -1628,6 +1638,7 @@ golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= @@ -1728,6 +1739,7 @@ golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201024232916-9f70ab9862d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1994,6 +2006,7 @@ google.golang.org/genproto v0.0.0-20200707001353-8e8330bf89df/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200911024640-645f7a48b24f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210126160654-44e461bb6506/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20240903143218-8af14fe29dc1 h1:BulPr26Jqjnd4eYDVe+YvyR7Yc2vJGkO5/0UxD0/jZU= google.golang.org/genproto v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:hL97c3SYopEHblzpxRL4lSs523++l8DYxGM1FQiYmb4= @@ -2028,6 +2041,7 @@ google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAG google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc v1.66.0 h1:DibZuoBznOxbDQxRINckZcUvnCEvrW9pcWIE2yF9r1c= google.golang.org/grpc v1.66.0/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v0.0.0-20200910201057-6591123024b3/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= diff --git a/main.go b/main.go index 9e7afc2..320a760 100644 --- a/main.go +++ b/main.go @@ -4,13 +4,16 @@ package main import ( "context" + "uln2003/uln28byj48" + + "go.viam.com/rdk/components/motor" "go.viam.com/rdk/logging" "go.viam.com/rdk/module" "go.viam.com/utils" ) func main() { - utils.ContextualMain(mainWithArgs, module.NewLoggerFromArgs("MODULE")) + utils.ContextualMain(mainWithArgs, module.NewLoggerFromArgs("uln2003")) } func mainWithArgs(ctx context.Context, args []string, logger logging.Logger) error { @@ -19,7 +22,7 @@ func mainWithArgs(ctx context.Context, args []string, logger logging.Logger) err return err } - if err = module.AddModelFromRegistry(ctx, COMPONENT.API, MODEL.Model); err != nil { + if err = module.AddModelFromRegistry(ctx, motor.API, uln28byj48.Model); err != nil { return err } diff --git a/meta.json b/meta.json index 404e3a8..609f720 100644 --- a/meta.json +++ b/meta.json @@ -1,14 +1,19 @@ { "$schema": "https://dl.viam.dev/module.schema.json", "module_id": "viam:uln2003", - "visibility": "private", - "url": "", - "description": "", + "visibility": "public", + "url": "https://github.com/viam-modules/uln2003", + "description": "Go module for uln2003 28byj-48 motor, compatible with Viam", "models": [ { - "api": "", - "model": "" + "api": "rdk:component:motor", + "model": "viam:uln2003:28byj-48" } ], - "entrypoint": "" + "build": { + "build": "make module", + "path": "bin/module.tar.gz", + "arch" : ["linux/arm64", "linux/amd64", "darwin/arm64"] + }, + "entrypoint": "bin/uln2003" } \ No newline at end of file diff --git a/old.json b/old.json deleted file mode 100644 index 7891d4f..0000000 --- a/old.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "module_id": "viam:MODULE", - "visibility": "public", - "url": "https://github.com/viam-modules/MODULE", - "description": "Go module for MODULE MODEL COMPONENT, compatible with Viam", - "models": [ - { - "api": "rdk:component:COMPONENT", - "model": "viam:MODULE:MODEL" - } - ], - "build": { - "build": "make module", - "path": "bin/module.tar.gz", - "arch" : ["linux/arm64", "linux/amd64", "darwin/arm64"] - }, - "entrypoint": "bin/MODULE" -} \ No newline at end of file diff --git a/uln28byj48/28byj-48.go b/uln28byj48/28byj-48.go new file mode 100644 index 0000000..aaa1e2a --- /dev/null +++ b/uln28byj48/28byj-48.go @@ -0,0 +1,473 @@ +// Package uln28byj48 implements a GPIO based +// stepper motor (model: 28byj-48) with uln2003 controler. +package uln28byj48 + +/* + Motor Name: 28byj-48 + Motor Controler: ULN2003 + Datasheet: + ULN2003: https://www.makerguides.com/wp-content/uploads/2019/04/ULN2003-Datasheet.pdf + 28byj-48: https://components101.com/sites/default/files/component_datasheet/28byj48-step-motor-datasheet.pdf + + This driver will drive the motor with half-step driving method (instead of full-step drive) for higher resolutions. + In half-step the current vector divides a circle into eight parts. The eight step switching sequence is shown in + stepSequence below. The motor takes 5.625*(1/64)° per step. For 360° the motor will take 4096 steps. + + The motor can run at a max speed of ~146rpm. Though it is recommended to not run the motor at max speed as it can + damage the gears. The max rpm of the motor shaft after gear reduction is ~15rpm. +*/ + +import ( + "context" + "math" + "sync" + "time" + + "github.com/pkg/errors" + "go.uber.org/multierr" + "go.viam.com/rdk/components/board" + "go.viam.com/rdk/components/motor" + "go.viam.com/rdk/logging" + "go.viam.com/rdk/operation" + "go.viam.com/rdk/resource" + "go.viam.com/utils" +) + +var ( + // Model for viam supported uln2003 28byj-48 sensor. + Model = resource.NewModel("viam", "uln2003", "28byj-48") + minDelayBetweenTicks = 100 * time.Microsecond // minimum sleep time between each ticks + maxRPM = 15.0 // max rpm of the 28byj-48 motor after gear reduction +) + +// stepSequence contains switching signal for uln2003 pins. +// Treversing through stepSequence once is one step. +var stepSequence = [8][4]bool{ + {false, false, false, true}, + {true, false, false, true}, + {true, false, false, false}, + {true, true, false, false}, + {false, true, false, false}, + {false, true, true, false}, + {false, false, true, false}, + {false, false, true, true}, +} + +// PinConfig defines the mapping of where motor are wired. +type PinConfig struct { + In1 string `json:"in1"` + In2 string `json:"in2"` + In3 string `json:"in3"` + In4 string `json:"in4"` +} + +// Config describes the configuration of a motor. +type Config struct { + Pins PinConfig `json:"pins"` + BoardName string `json:"board"` + TicksPerRotation int `json:"ticks_per_rotation"` +} + +// Validate ensures all parts of the config are valid. +func (conf *Config) Validate(path string) ([]string, error) { + var deps []string + if conf.BoardName == "" { + return nil, resource.NewConfigValidationFieldRequiredError(path, "board") + } + + if conf.Pins.In1 == "" { + return nil, resource.NewConfigValidationFieldRequiredError(path, "in1") + } + + if conf.Pins.In2 == "" { + return nil, resource.NewConfigValidationFieldRequiredError(path, "in2") + } + + if conf.Pins.In3 == "" { + return nil, resource.NewConfigValidationFieldRequiredError(path, "in3") + } + + if conf.Pins.In4 == "" { + return nil, resource.NewConfigValidationFieldRequiredError(path, "in4") + } + + deps = append(deps, conf.BoardName) + return deps, nil +} + +func init() { + resource.RegisterComponent(motor.API, Model, resource.Registration[motor.Motor, *Config]{ + Constructor: new28byj, + }) +} + +func new28byj( + ctx context.Context, deps resource.Dependencies, conf resource.Config, logger logging.Logger, +) (motor.Motor, error) { + mc, err := resource.NativeConfig[*Config](conf) + if err != nil { + return nil, err + } + + b, err := board.FromDependencies(deps, mc.BoardName) + if err != nil { + return nil, errors.Wrap(err, "expected board name in config for motor") + } + + if mc.TicksPerRotation <= 0 { + return nil, errors.New("expected ticks_per_rotation to be greater than zero in config for motor") + } + + m := &uln28byj{ + Named: conf.ResourceName().AsNamed(), + theBoard: b, + ticksPerRotation: mc.TicksPerRotation, + logger: logger, + motorName: conf.Name, + opMgr: operation.NewSingleOperationManager(), + } + + in1, err := b.GPIOPinByName(mc.Pins.In1) + if err != nil { + return nil, errors.Wrapf(err, "in in1 in motor (%s)", m.motorName) + } + m.in1 = in1 + + in2, err := b.GPIOPinByName(mc.Pins.In2) + if err != nil { + return nil, errors.Wrapf(err, "in in2 in motor (%s)", m.motorName) + } + m.in2 = in2 + + in3, err := b.GPIOPinByName(mc.Pins.In3) + if err != nil { + return nil, errors.Wrapf(err, "in in3 in motor (%s)", m.motorName) + } + m.in3 = in3 + + in4, err := b.GPIOPinByName(mc.Pins.In4) + if err != nil { + return nil, errors.Wrapf(err, "in in4 in motor (%s)", m.motorName) + } + m.in4 = in4 + + return m, nil +} + +// struct is named after the controler uln28byj. +type uln28byj struct { + resource.Named + resource.AlwaysRebuild + theBoard board.Board + ticksPerRotation int + in1, in2, in3, in4 board.GPIOPin + logger logging.Logger + motorName string + + // state + workers *utils.StoppableWorkers + lock sync.Mutex + opMgr *operation.SingleOperationManager + doRunDone func() + + stepPosition int64 + stepperDelay time.Duration + targetStepPosition int64 +} + +// doRun runs the motor till it reaches target step position. +func (m *uln28byj) doRun() { + // cancel doRun if it already exists + if m.doRunDone != nil { + m.doRunDone() + } + + // start a new doRun + var doRunCtx context.Context + doRunCtx, m.doRunDone = context.WithCancel(context.Background()) + m.workers = utils.NewBackgroundStoppableWorkers(func(ctx context.Context) { + for { + select { + case <-doRunCtx.Done(): + return + default: + } + + if m.getStepPosition() == m.getTargetStepPosition() { + if err := m.doStop(doRunCtx); err != nil { + m.logger.Errorf("error setting pins to zero %v", err) + return + } + } else { + err := m.doStep(doRunCtx, m.getStepPosition() < m.getTargetStepPosition()) + if err != nil { + m.logger.Errorf("error stepping %v", err) + return + } + } + } + }) +} + +// doStop sets all the pins to 0 to stop the motor. +func (m *uln28byj) doStop(ctx context.Context) error { + m.lock.Lock() + defer m.lock.Unlock() + return m.setPins(ctx, [4]bool{false, false, false, false}) +} + +// Depending on the direction, doStep will either treverse the stepSequence array in ascending +// or descending order. +func (m *uln28byj) doStep(ctx context.Context, forward bool) error { + m.lock.Lock() + defer m.lock.Unlock() + if forward { + m.stepPosition++ + } else { + m.stepPosition-- + } + + var nextStepSequence int + if m.stepPosition < 0 { + nextStepSequence = 7 + int(m.stepPosition%8) + } else { + nextStepSequence = int(m.stepPosition % 8) + } + + err := m.setPins(ctx, stepSequence[nextStepSequence]) + if err != nil { + return err + } + + time.Sleep(m.stepperDelay) + return nil +} + +// doTicks sets all 4 pins. +// must be called in locked context. +func (m *uln28byj) setPins(ctx context.Context, pins [4]bool) error { + err := multierr.Combine( + m.in1.Set(ctx, pins[0], nil), + m.in2.Set(ctx, pins[1], nil), + m.in3.Set(ctx, pins[2], nil), + m.in4.Set(ctx, pins[3], nil), + ) + + return err +} + +func (m *uln28byj) getTargetStepPosition() int64 { + m.lock.Lock() + defer m.lock.Unlock() + return m.targetStepPosition +} + +func (m *uln28byj) setTargetStepPosition(targetPos int64) { + m.lock.Lock() + defer m.lock.Unlock() + m.targetStepPosition = targetPos +} + +func (m *uln28byj) getStepPosition() int64 { + m.lock.Lock() + defer m.lock.Unlock() + return m.stepPosition +} + +func (m *uln28byj) setStepperDelay(delay time.Duration) { + m.lock.Lock() + defer m.lock.Unlock() + m.stepperDelay = delay +} + +// GoFor instructs the motor to go in a specific direction for a specific amount of +// revolutions at a given speed in revolutions per minute. Both the RPM and the revolutions +// can be assigned negative values to move in a backwards direction. Note: if both are negative +// the motor will spin in the forward direction. +func (m *uln28byj) GoFor(ctx context.Context, rpm, revolutions float64, extra map[string]interface{}) error { + ctx, done := m.opMgr.New(ctx) + defer done() + + warning, err := motor.CheckSpeed(rpm, maxRPM) + if warning != "" { + m.logger.CWarn(ctx, warning) + if err != nil { + m.logger.CError(ctx, err) + // only stop if we receive a zero RPM error + return m.Stop(ctx, extra) + } + // we do not get the zeroRPM error, but still want + // the motor to move at the maximum rpm + m.logger.CWarnf(ctx, "can only move at maxRPM of %v", maxRPM) + rpm = maxRPM * motor.GetSign(rpm) + } + + targetStepPosition, stepperDelay := m.goMath(rpm, revolutions) + m.setTargetStepPosition(targetStepPosition) + m.setStepperDelay(stepperDelay) + m.doRun() + + positionReached := func(ctx context.Context) (bool, error) { + return m.getTargetStepPosition() == m.getStepPosition(), nil + } + + err = m.opMgr.WaitForSuccess( + ctx, + m.stepperDelay, + positionReached, + ) + // Ignore the context canceled error - this occurs when the motor is stopped + // at the beginning of goForInternal + if !errors.Is(err, context.Canceled) { + return err + } + + return nil +} + +func (m *uln28byj) goMath(rpm, revolutions float64) (int64, time.Duration) { + var d int64 = 1 + + if math.Signbit(revolutions) != math.Signbit(rpm) { + d = -1 + } + + revolutions = math.Abs(revolutions) + rpm = math.Abs(rpm) * float64(d) + + targetPosition := m.getStepPosition() + int64(float64(d)*revolutions*float64(m.ticksPerRotation)) + stepperDelay := m.calcStepperDelay(rpm) + + return targetPosition, stepperDelay +} + +func (m *uln28byj) calcStepperDelay(rpm float64) time.Duration { + stepperDelay := time.Duration(int64((1/(math.Abs(rpm)*float64(m.ticksPerRotation)/60.0))*1000000)) * time.Microsecond + if stepperDelay < minDelayBetweenTicks { + m.logger.Debugf("Computed sleep time between ticks (%v) too short. Defaulting to %v", stepperDelay, minDelayBetweenTicks) + stepperDelay = minDelayBetweenTicks + } + return stepperDelay +} + +// GoTo instructs the motor to go to a specific position (provided in revolutions from home/zero), +// at a specific RPM. Regardless of the directionality of the RPM this function will move the motor +// towards the specified target. +func (m *uln28byj) GoTo(ctx context.Context, rpm, positionRevolutions float64, extra map[string]interface{}) error { + curPos, err := m.Position(ctx, extra) + if err != nil { + return errors.Wrapf(err, "error in GoTo from motor (%s)", m.motorName) + } + moveDistance := positionRevolutions - curPos + + m.logger.CDebugf(ctx, "Moving %v ticks at %v rpm", moveDistance, rpm) + + if moveDistance == 0 { + return nil + } + + return m.GoFor(ctx, math.Abs(rpm), moveDistance, extra) +} + +// SetRPM instructs the motor to move at the specified RPM indefinitely. +func (m *uln28byj) SetRPM(ctx context.Context, rpm float64, extra map[string]interface{}) error { + powerPct := rpm / maxRPM + return m.SetPower(ctx, powerPct, extra) +} + +// Set the current position (+/- offset) to be the new zero (home) position. +func (m *uln28byj) ResetZeroPosition(ctx context.Context, offset float64, extra map[string]interface{}) error { + newPosition := int64(-1 * offset * float64(m.ticksPerRotation)) + // use Stop to set the target position to the current position again + if err := m.Stop(ctx, extra); err != nil { + return err + } + m.lock.Lock() + defer m.lock.Unlock() + m.stepPosition = newPosition + m.targetStepPosition = newPosition + return nil +} + +// SetPower is invalid for this motor. +func (m *uln28byj) SetPower(ctx context.Context, powerPct float64, extra map[string]interface{}) error { + ctx, done := m.opMgr.New(ctx) + defer done() + + warning, err := motor.CheckSpeed(powerPct*maxRPM, maxRPM) + if warning != "" { + m.logger.CWarn(ctx, warning) + if err != nil { + m.logger.CError(ctx, err) + // only stop if we receive a zero RPM error + return m.Stop(ctx, extra) + } + } + + m.lock.Lock() + defer m.lock.Unlock() + direction := motor.GetSign(powerPct) // get the direction to set target to -ve/+ve Inf + m.targetStepPosition = int64(math.Inf(int(direction))) + powerPct = motor.ClampPower(powerPct) // ensure 1.0 max and -1.0 min + m.stepperDelay = m.calcStepperDelay(powerPct * maxRPM) + + m.doRun() + + return nil +} + +// Position reports the current step position of the motor. If it's not supported, the returned +// data is undefined. +func (m *uln28byj) Position(ctx context.Context, extra map[string]interface{}) (float64, error) { + m.lock.Lock() + defer m.lock.Unlock() + return float64(m.stepPosition) / float64(m.ticksPerRotation), nil +} + +// Properties returns the status of whether the motor supports certain optional properties. +func (m *uln28byj) Properties(ctx context.Context, extra map[string]interface{}) (motor.Properties, error) { + return motor.Properties{ + PositionReporting: true, + }, nil +} + +// IsMoving returns if the motor is currently moving. +func (m *uln28byj) IsMoving(ctx context.Context) (bool, error) { + m.lock.Lock() + defer m.lock.Unlock() + return m.stepPosition != m.targetStepPosition, nil +} + +// Stop turns the power to the motor off immediately, without any gradual step down. +func (m *uln28byj) Stop(ctx context.Context, extra map[string]interface{}) error { + if m.doRunDone != nil { + m.doRunDone() + } + m.lock.Lock() + defer m.lock.Unlock() + m.targetStepPosition = m.stepPosition + return nil +} + +// IsPowered returns whether or not the motor is currently on. It also returns the percent power +// that the motor has, but stepper motors only have this set to 0% or 100%, so it's a little +// redundant. +func (m *uln28byj) IsPowered(ctx context.Context, extra map[string]interface{}) (bool, float64, error) { + on, err := m.IsMoving(ctx) + if err != nil { + return on, 0.0, errors.Wrapf(err, "error in IsPowered from motor (%s)", m.motorName) + } + percent := 0.0 + if on { + percent = 1.0 + } + return on, percent, err +} + +func (m *uln28byj) Close(ctx context.Context) error { + if err := m.Stop(ctx, nil); err != nil { + return err + } + m.workers.Stop() + return nil +} diff --git a/uln28byj48/28byj-48_test.go b/uln28byj48/28byj-48_test.go new file mode 100644 index 0000000..b9ffe2f --- /dev/null +++ b/uln28byj48/28byj-48_test.go @@ -0,0 +1,402 @@ +package uln28byj48 + +import ( + "context" + "errors" + "fmt" + "testing" + "time" + + "go.viam.com/rdk/components/board" + "go.viam.com/rdk/logging" + "go.viam.com/rdk/resource" + "go.viam.com/rdk/testutils/inject" + "go.viam.com/test" +) + +const ( + testBoardName = "fake_board" +) + +func setupDependencies(t *testing.T) resource.Dependencies { + t.Helper() + + testBoard := &inject.Board{} + in1 := &mockGPIOPin{} + in2 := &mockGPIOPin{} + in3 := &mockGPIOPin{} + in4 := &mockGPIOPin{} + + testBoard.GPIOPinByNameFunc = func(pin string) (board.GPIOPin, error) { + switch pin { + case "1": + return in1, nil + case "2": + return in2, nil + case "3": + return in3, nil + case "4": + return in4, nil + } + return nil, errors.New("pin name not found") + } + deps := make(resource.Dependencies) + deps[board.Named(testBoardName)] = testBoard + return deps +} + +func TestValid(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + logger := logging.NewTestLogger(t) + deps := setupDependencies(t) + + mc := Config{ + Pins: PinConfig{ + In1: "1", + In2: "2", + In3: "3", + In4: "4", + }, + BoardName: testBoardName, + } + + c := resource.Config{ + Name: "fake_28byj", + ConvertedAttributes: &mc, + } + + // Create motor with no board and default config + t.Run("motor initializing test with no board and default config", func(t *testing.T) { + _, err := new28byj(ctx, deps, c, logger) + test.That(t, err, test.ShouldNotBeNil) + }) + + // Create motor with board and default config + t.Run("gpiostepper initializing test with board and default config", func(t *testing.T) { + _, err := new28byj(ctx, deps, c, logger) + test.That(t, err, test.ShouldNotBeNil) + }) + _, err := new28byj(ctx, deps, c, logger) + test.That(t, err, test.ShouldNotBeNil) + + mc.TicksPerRotation = 200 + + mm, err := new28byj(ctx, deps, c, logger) + test.That(t, err, test.ShouldBeNil) + + m := mm.(*uln28byj) + + t.Run("motor test supports position reporting", func(t *testing.T) { + properties, err := m.Properties(ctx, nil) + test.That(t, err, test.ShouldBeNil) + test.That(t, properties.PositionReporting, test.ShouldBeTrue) + }) + + t.Run("motor test isOn functionality", func(t *testing.T) { + on, powerPct, err := m.IsPowered(ctx, nil) + test.That(t, err, test.ShouldBeNil) + test.That(t, on, test.ShouldEqual, false) + test.That(t, powerPct, test.ShouldEqual, 0.0) + }) + + t.Run("motor testing with positive rpm and positive revolutions", func(t *testing.T) { + on, powerPct, err := m.IsPowered(ctx, nil) + test.That(t, err, test.ShouldBeNil) + test.That(t, on, test.ShouldEqual, false) + test.That(t, powerPct, test.ShouldEqual, 0.0) + + pos, err := m.Position(ctx, nil) + test.That(t, err, test.ShouldBeNil) + test.That(t, pos, test.ShouldEqual, 0) + }) + + t.Run("motor testing with negative rpm and positive revolutions", func(t *testing.T) { + on, powerPct, err := m.IsPowered(ctx, nil) + test.That(t, err, test.ShouldBeNil) + test.That(t, on, test.ShouldEqual, false) + test.That(t, powerPct, test.ShouldEqual, 0.0) + + pos, err := m.Position(ctx, nil) + test.That(t, err, test.ShouldBeNil) + test.That(t, pos, test.ShouldEqual, 0) + }) + + t.Run("motor testing with positive rpm and negative revolutions", func(t *testing.T) { + on, powerPct, err := m.IsPowered(ctx, nil) + test.That(t, err, test.ShouldBeNil) + test.That(t, on, test.ShouldEqual, false) + test.That(t, powerPct, test.ShouldEqual, 0.0) + + pos, err := m.Position(ctx, nil) + test.That(t, err, test.ShouldBeNil) + test.That(t, pos, test.ShouldEqual, 0) + }) + + t.Run("motor testing with negative rpm and negative revolutions", func(t *testing.T) { + on, powerPct, err := m.IsPowered(ctx, nil) + test.That(t, err, test.ShouldBeNil) + test.That(t, on, test.ShouldEqual, false) + test.That(t, powerPct, test.ShouldEqual, 0.0) + + pos, err := m.Position(ctx, nil) + test.That(t, err, test.ShouldBeNil) + test.That(t, pos, test.ShouldEqual, 0) + }) + + t.Run("motor testing with large # of revolutions", func(t *testing.T) { + on, powerPct, err := m.IsPowered(ctx, nil) + test.That(t, err, test.ShouldBeNil) + test.That(t, on, test.ShouldEqual, false) + test.That(t, powerPct, test.ShouldEqual, 0.0) + + err = m.Stop(ctx, nil) + test.That(t, err, test.ShouldBeNil) + + pos, err := m.Position(ctx, nil) + test.That(t, err, test.ShouldBeNil) + test.That(t, pos, test.ShouldBeGreaterThanOrEqualTo, 0) + test.That(t, pos, test.ShouldBeLessThan, 202) + }) + + cancel() +} + +func TestFunctions(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + logger, obs := logging.NewObservedTestLogger(t) + deps := setupDependencies(t) + + mc := Config{ + Pins: PinConfig{ + In1: "1", + In2: "2", + In3: "3", + In4: "4", + }, + BoardName: testBoardName, + TicksPerRotation: 100, + } + + c := resource.Config{ + Name: "fake_28byj", + ConvertedAttributes: &mc, + } + mm, _ := new28byj(ctx, deps, c, logger) + m := mm.(*uln28byj) + + t.Run("test goMath", func(t *testing.T) { + targetPos, stepperdelay := m.goMath(100, 100) + test.That(t, targetPos, test.ShouldEqual, 10000) + test.That(t, stepperdelay, test.ShouldEqual, (6 * time.Millisecond)) + + targetPos, stepperdelay = m.goMath(-100, 100) + test.That(t, targetPos, test.ShouldEqual, -10000) + test.That(t, stepperdelay, test.ShouldEqual, (6 * time.Millisecond)) + + targetPos, stepperdelay = m.goMath(-100, -100) + test.That(t, targetPos, test.ShouldEqual, 10000) + test.That(t, stepperdelay, test.ShouldEqual, (6 * time.Millisecond)) + + targetPos, stepperdelay = m.goMath(-2, 50) + test.That(t, targetPos, test.ShouldEqual, -5000) + test.That(t, stepperdelay, test.ShouldEqual, (300 * time.Millisecond)) + + targetPos, stepperdelay = m.goMath(1, 400) + test.That(t, targetPos, test.ShouldEqual, 40000) + test.That(t, stepperdelay, test.ShouldEqual, (600 * time.Millisecond)) + + targetPos, stepperdelay = m.goMath(400, 2) + test.That(t, targetPos, test.ShouldEqual, 200) + test.That(t, stepperdelay, test.ShouldEqual, (1500 * time.Microsecond)) + + targetPos, stepperdelay = m.goMath(0, 2) + test.That(t, targetPos, test.ShouldEqual, 200) + test.That(t, stepperdelay, test.ShouldEqual, (100 * time.Microsecond)) + }) + + t.Run("test calcStepperDelay", func(t *testing.T) { + stepperdelay := m.calcStepperDelay(100) + test.That(t, stepperdelay, test.ShouldEqual, (6 * time.Millisecond)) + + stepperdelay = m.calcStepperDelay(-100) + test.That(t, stepperdelay, test.ShouldEqual, (6 * time.Millisecond)) + + stepperdelay = m.calcStepperDelay(-2) + test.That(t, stepperdelay, test.ShouldEqual, (300 * time.Millisecond)) + + stepperdelay = m.calcStepperDelay(1) + test.That(t, stepperdelay, test.ShouldEqual, (600 * time.Millisecond)) + + stepperdelay = m.calcStepperDelay(400) + test.That(t, stepperdelay, test.ShouldEqual, (1500 * time.Microsecond)) + + stepperdelay = m.calcStepperDelay(0) + test.That(t, stepperdelay, test.ShouldEqual, (100 * time.Microsecond)) + }) + + t.Run("test position", func(t *testing.T) { + err := m.ResetZeroPosition(ctx, -0.03, nil) + test.That(t, err, test.ShouldBeNil) + pos, err := m.Position(ctx, nil) + test.That(t, err, test.ShouldBeNil) + test.That(t, pos, test.ShouldEqual, 0.03) + + err = m.ResetZeroPosition(ctx, 0.03, nil) + test.That(t, err, test.ShouldBeNil) + pos, err = m.Position(ctx, nil) + test.That(t, err, test.ShouldBeNil) + test.That(t, pos, test.ShouldEqual, -0.03) + + err = m.ResetZeroPosition(ctx, 0, nil) + test.That(t, err, test.ShouldBeNil) + pos, err = m.Position(ctx, nil) + test.That(t, err, test.ShouldBeNil) + test.That(t, pos, test.ShouldEqual, 0) + }) + + t.Run("test GoFor", func(t *testing.T) { + err := m.GoFor(ctx, 0, 1, nil) + test.That(t, err, test.ShouldBeNil) + allObs := obs.All() + latestLoggedEntry := allObs[len(allObs)-1] + test.That(t, fmt.Sprint(latestLoggedEntry), test.ShouldContainSubstring, "nearly 0") + + err = m.GoFor(ctx, -.009, 1, nil) + test.That(t, err, test.ShouldBeNil) + + err = m.GoFor(ctx, 146, 1, nil) + test.That(t, err, test.ShouldBeNil) + allObs = obs.All() + latestLoggedEntry = allObs[len(allObs)-2] + test.That(t, fmt.Sprint(latestLoggedEntry), test.ShouldContainSubstring, "nearly the max") + }) + + t.Run("test SetRPM", func(t *testing.T) { + err := m.SetRPM(ctx, 0, nil) + test.That(t, err, test.ShouldBeNil) + + allObs := obs.All() + latestLoggedEntry := allObs[len(allObs)-1] + test.That(t, fmt.Sprint(latestLoggedEntry), test.ShouldContainSubstring, "nearly 0") + + err = m.SetRPM(ctx, -.009, nil) + test.That(t, err, test.ShouldBeNil) + + err = m.SetRPM(ctx, 146, nil) + test.That(t, err, test.ShouldBeNil) + allObs = obs.All() + latestLoggedEntry = allObs[len(allObs)-1] + test.That(t, fmt.Sprint(latestLoggedEntry), test.ShouldContainSubstring, "nearly the max") + }) + + t.Run("test SetPower", func(t *testing.T) { + err := m.SetPower(ctx, 0, nil) + test.That(t, err, test.ShouldBeNil) + + allObs := obs.All() + latestLoggedEntry := allObs[len(allObs)-1] + test.That(t, fmt.Sprint(latestLoggedEntry), test.ShouldContainSubstring, "nearly 0") + + err = m.SetPower(ctx, -.009, nil) + test.That(t, err, test.ShouldBeNil) + + err = m.SetPower(ctx, 146, nil) + test.That(t, err, test.ShouldBeNil) + allObs = obs.All() + latestLoggedEntry = allObs[len(allObs)-1] + test.That(t, fmt.Sprint(latestLoggedEntry), test.ShouldContainSubstring, "nearly the max") + }) + + cancel() +} + +func TestState(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + logger := logging.NewTestLogger(t) + deps := setupDependencies(t) + + mc := Config{ + Pins: PinConfig{ + In1: "1", + In2: "2", + In3: "3", + In4: "4", + }, + BoardName: testBoardName, + TicksPerRotation: 100, + } + + c := resource.Config{ + Name: "fake_28byj", + ConvertedAttributes: &mc, + } + mm, _ := new28byj(ctx, deps, c, logger) + m := mm.(*uln28byj) + + t.Run("test state", func(t *testing.T) { + err := m.ResetZeroPosition(ctx, -0.09, nil) + test.That(t, err, test.ShouldBeNil) + b := m.theBoard + var pin1Arr []bool + var pin2Arr []bool + var pin3Arr []bool + var pin4Arr []bool + + arrIn1 := []bool{true, true, false, false, false, false, false, true} + arrIn2 := []bool{false, true, true, true, false, false, false, false} + arrIn3 := []bool{false, false, false, true, true, true, false, false} + arrIn4 := []bool{false, false, false, false, false, true, true, true} + + for i := 0; i < 8; i++ { + // moving forward. + err := m.doStep(ctx, true) + test.That(t, err, test.ShouldBeNil) + + PinOut1, err := b.GPIOPinByName("1") + test.That(t, err, test.ShouldBeNil) + pinStruct, ok := PinOut1.(*mockGPIOPin) + test.That(t, ok, test.ShouldBeTrue) + pin1Arr = pinStruct.pinStates + + PinOut2, err := b.GPIOPinByName("2") + test.That(t, err, test.ShouldBeNil) + pinStruct2, ok := PinOut2.(*mockGPIOPin) + test.That(t, ok, test.ShouldBeTrue) + pin2Arr = pinStruct2.pinStates + + PinOut3, err := b.GPIOPinByName("3") + test.That(t, err, test.ShouldBeNil) + pinStruct3, ok := PinOut3.(*mockGPIOPin) + test.That(t, ok, test.ShouldBeTrue) + pin3Arr = pinStruct3.pinStates + + PinOut4, err := b.GPIOPinByName("4") + test.That(t, err, test.ShouldBeNil) + pinStruct4, ok := PinOut4.(*mockGPIOPin) + test.That(t, ok, test.ShouldBeTrue) + pin4Arr = pinStruct4.pinStates + } + + m.logger.Info("pin1Arr ", pin1Arr) + m.logger.Info("pin2Arr ", pin2Arr) + m.logger.Info("pin3Arr ", pin3Arr) + m.logger.Info("pin4Arr ", pin4Arr) + + test.That(t, pin1Arr, test.ShouldResemble, arrIn1) + test.That(t, pin2Arr, test.ShouldResemble, arrIn2) + test.That(t, pin3Arr, test.ShouldResemble, arrIn3) + test.That(t, pin4Arr, test.ShouldResemble, arrIn4) + }) + + cancel() +} + +type mockGPIOPin struct { + board.GPIOPin + pinStates []bool +} + +func (m *mockGPIOPin) Set(ctx context.Context, high bool, extra map[string]interface{}) error { + m.pinStates = append(m.pinStates, high) + return nil +}