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
+}