mirror of
https://github.com/hybridgroup/gobot.git
synced 2025-04-24 13:48:49 +08:00
gpio: add support for new character device Kernel ABI
This commit is contained in:
parent
be92fb6f9e
commit
45ee9c3644
40
adaptor.go
40
adaptor.go
@ -1,23 +1,47 @@
|
||||
package gobot
|
||||
|
||||
// DigitalPinOptioner is the interface to provide the possibility to change pin behavior for the next usage
|
||||
type DigitalPinOptioner interface {
|
||||
// SetLabel change the pins label
|
||||
SetLabel(string) (changed bool)
|
||||
// SetDirectionOutput sets the pins direction to output with the given initial value
|
||||
SetDirectionOutput(initialState int) (changed bool)
|
||||
// SetDirectionInput sets the pins direction to input
|
||||
SetDirectionInput() (changed bool)
|
||||
}
|
||||
|
||||
// DigitalPinOptionApplier is the interface to apply options to change pin behavior immediately
|
||||
type DigitalPinOptionApplier interface {
|
||||
// ApplyOptions apply all given options to the pin immediately
|
||||
ApplyOptions(...func(DigitalPinOptioner) bool) error
|
||||
}
|
||||
|
||||
// DigitalPinner is the interface for system gpio interactions
|
||||
type DigitalPinner interface {
|
||||
// Export exports the pin for use by the operating system
|
||||
// Export exports the pin for use by the adaptor
|
||||
Export() error
|
||||
// Unexport unexports the pin and releases the pin from the operating system
|
||||
// Unexport releases the pin from the adaptor, so it is free for the operating system
|
||||
Unexport() error
|
||||
// Direction sets the direction for the pin
|
||||
Direction(string) error
|
||||
// Read reads the current value of the pin
|
||||
Read() (int, error)
|
||||
// Write writes to the pin
|
||||
Write(int) error
|
||||
// DigitalPinOptionApplier is the interface to change pin behavior immediately
|
||||
DigitalPinOptionApplier
|
||||
}
|
||||
|
||||
// DigitalPinnerProvider is the interface that an Adaptor should implement to allow
|
||||
// clients to obtain access to any DigitalPin's available on that board.
|
||||
// DigitalPinValuer is the interface to get pin behavior for the next usage. The interface is and should be rarely used.
|
||||
type DigitalPinValuer interface {
|
||||
// DirectionBehavior gets the direction behavior when the pin is used the next time.
|
||||
// This means its possibly not in this direction type at the moment.
|
||||
DirectionBehavior() string
|
||||
}
|
||||
|
||||
// DigitalPinnerProvider is the interface that an Adaptor should implement to allow clients to obtain
|
||||
// access to any DigitalPin's available on that board. If the pin is initially acquired, it is an input.
|
||||
// Pin direction and other options can be changed afterwards by pin.ApplyOptions() at any time.
|
||||
type DigitalPinnerProvider interface {
|
||||
DigitalPin(string, string) (DigitalPinner, error)
|
||||
DigitalPin(id string) (DigitalPinner, error)
|
||||
}
|
||||
|
||||
// PWMPinner is the interface for system PWM interactions
|
||||
@ -48,7 +72,7 @@ type PWMPinner interface {
|
||||
// PWMPinnerProvider is the interface that an Adaptor should implement to allow
|
||||
// clients to obtain access to any PWMPin's available on that board.
|
||||
type PWMPinnerProvider interface {
|
||||
PWMPin(string) (PWMPinner, error)
|
||||
PWMPin(id string) (PWMPinner, error)
|
||||
}
|
||||
|
||||
// Adaptor is the interface that describes an adaptor in gobot
|
||||
|
1
go.mod
1
go.mod
@ -16,6 +16,7 @@ require (
|
||||
github.com/stretchr/testify v1.8.0
|
||||
github.com/urfave/cli v1.22.10
|
||||
github.com/veandco/go-sdl2 v0.4.25
|
||||
github.com/warthog618/gpiod v0.8.0
|
||||
go.bug.st/serial v1.4.0
|
||||
gocv.io/x/gocv v0.31.0
|
||||
golang.org/x/net v0.1.0
|
||||
|
159
go.sum
159
go.sum
@ -1,7 +1,25 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/bgould/http v0.0.0-20190627042742-d268792bdee7/go.mod h1:BTqvVegvwifopl4KTEDth6Zezs9eR+lCWhvGKvkxJHE=
|
||||
github.com/bmizerany/pat v0.0.0-20210406213842-e4b6760bdd6f h1:gOO/tNZMjjvTKZWpY7YnXC72ULNLErRtp94LountVE8=
|
||||
github.com/bmizerany/pat v0.0.0-20210406213842-e4b6760bdd6f/go.mod h1:8rLXio+WjiTceGBHIoTvn60HIbs7Hm7bcHjyrSqYB9c=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/coreos/bbolt v1.3.3/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/etcd v3.3.15+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
|
||||
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||
github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk=
|
||||
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
@ -10,6 +28,7 @@ github.com/creack/goselect v0.1.2/go.mod h1:a/NhLweNvqIYMuxcMOuWY516Cimucms3DglD
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/donovanhide/eventsource v0.0.0-20210830082556-c59027999da0 h1:C7t6eeMaEQVy6e8CarIhscYQlNmw5e3G36y7l7Y21Ao=
|
||||
github.com/donovanhide/eventsource v0.0.0-20210830082556-c59027999da0/go.mod h1:56wL82FO0bfMU5RvfXoIwSOP2ggqqxT+tAfNEIyxuHw=
|
||||
github.com/eclipse/paho.mqtt.golang v1.2.0/go.mod h1:H9keYFcgq3Qr5OUJm/JZI/i6U7joQ8SYLhZwfeOo6Ts=
|
||||
@ -18,44 +37,89 @@ github.com/eclipse/paho.mqtt.golang v1.4.1/go.mod h1:JGt0RsEwEX+Xa/agj90YJ9d9DH2
|
||||
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
|
||||
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
|
||||
github.com/frankban/quicktest v1.10.2/go.mod h1:K+q6oSqb0W0Ininfk863uOk1lMy69l/P6txr3mVT54s=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/glerchundi/subcommands v0.0.0-20181212083838-923a6ccb11f8/go.mod h1:r0g3O7Y5lrWXgDfcFBRgnAKzjmPgTzwoMC2ieB345FY=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
|
||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
|
||||
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gofrs/uuid v4.3.0+incompatible h1:CaSVZxm5B+7o45rtab4jC2G37WGYX1zQfuU2i6DSvnc=
|
||||
github.com/gofrs/uuid v4.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||
github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.5.0 h1:LUVKkCeviFUMKqHa4tXIIij/lbhnMbP7Fn5wKdKkRh4=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.1.0/go.mod h1:f5nM7jw/oeRSadq3xCzHAvxcr8HZnzsqU6ILg/0NiiE=
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.11.2/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||
github.com/hajimehoshi/go-jisx0208 v1.0.0/go.mod h1:yYxEStHL7lt9uL+AbdWgW9gBumwieDoZCiB1f/0X0as=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
|
||||
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
|
||||
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/hybridgroup/go-ardrone v0.0.0-20140402002621-b9750d8d7b78 h1:7of6LJZ4LF9AvF4bTiMr2I72KxodBf1BXrSD9Tz0lWU=
|
||||
github.com/hybridgroup/go-ardrone v0.0.0-20140402002621-b9750d8d7b78/go.mod h1:YllNbhGM1UEcySxCv1BWK5lre7QLmJJ+O0ADUOo2nbc=
|
||||
github.com/hybridgroup/mjpeg v0.0.0-20140228234708-4680f319790e h1:xCcwD5FOXul+j1dn8xD16nbrhJkkum/Cn+jTd/u1LhY=
|
||||
github.com/hybridgroup/mjpeg v0.0.0-20140228234708-4680f319790e/go.mod h1:eagM805MRKrioHYuU7iKLUyFPVKqVV6um5DAvCkUtXs=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||
github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8=
|
||||
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=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/muka/go-bluetooth v0.0.0-20220830075246-0746e3a1ea53 h1:zfLHhuGzmSbthZ00FfbEjgAHUOOj7NGiITojMTCFy6U=
|
||||
github.com/muka/go-bluetooth v0.0.0-20220830075246-0746e3a1ea53/go.mod h1:dMCjicU6vRBk34dqOmIZm0aod6gUwZXOXzBROqGous0=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/nats-io/jwt v0.3.0 h1:xdnzwFETV++jNc4W1mw//qFyJGb2ABOombmZJQS4+Qo=
|
||||
github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
|
||||
github.com/nats-io/nats-server/v2 v2.1.0 h1:Yi0+ZhRPtPAGeIxFn5erIeJIV9wXA+JznfSxK621Fbk=
|
||||
@ -71,14 +135,33 @@ github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
|
||||
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/paypal/gatt v0.0.0-20151011220935-4ae819d591cf/go.mod h1:+AwQL2mK3Pd3S+TUwg0tYQjid0q1txyNUJuuSmz8Kdk=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/pelletier/go-toml v1.6.0/go.mod h1:5N711Q9dKgbdkxHL+MEfF31hpT7l0S0s/t2kKREewys=
|
||||
github.com/peterbourgon/ff/v3 v3.1.2/go.mod h1:XNJLY8EIl6MjMVjBS4F0+G0LYoAqs0DTa4rmHHukKDE=
|
||||
github.com/pilebones/go-udev v0.0.0-20180820235104-043677e09b13 h1:Y+ynP+0QIjUejN2tsuIlWOJG1CThJy6amRuWlBL94Vg=
|
||||
github.com/pilebones/go-udev v0.0.0-20180820235104-043677e09b13/go.mod h1:MXAPLpvZeTqLpU1eO6kFXzU0uBMooSGc1MPXAcBoy1M=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||
github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
|
||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||
github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo=
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
@ -89,13 +172,28 @@ github.com/saltosystems/winrt-go v0.0.0-20220913104103-712830fcd2ad/go.mod h1:Uv
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/sigurn/crc8 v0.0.0-20220107193325-2243fe600f9f h1:1R9KdKjCNSd7F8iGTxIpoID9prlYH8nuNYKt0XvweHA=
|
||||
github.com/sigurn/crc8 v0.0.0-20220107193325-2243fe600f9f/go.mod h1:vQhwQ4meQEDfahT5kd61wLAF5AAeh5ZPLVI4JJ/tYo8=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/sirupsen/logrus v1.5.0/go.mod h1:+F7Ogzej0PZc/94MaYx/nvG9jOFMD2osvC3s+Squfpo=
|
||||
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
||||
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
|
||||
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
|
||||
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
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=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
@ -106,18 +204,31 @@ github.com/suapapa/go_eddystone v1.3.1/go.mod h1:bXC11TfJOS+3g3q/Uzd7FKd5g62STQE
|
||||
github.com/tdakkota/win32metadata v0.1.0/go.mod h1:77e6YvX0LIVW+O81fhWLnXAxxcyu/wdZdG7iwed7Fyk=
|
||||
github.com/tinygo-org/cbgo v0.0.4 h1:3D76CRYbH03Rudi8sEgs/YO0x3JIMdyq8jlQtk/44fU=
|
||||
github.com/tinygo-org/cbgo v0.0.4/go.mod h1:7+HgWIHd4nbAz0ESjGlJ1/v9LDU1Ox8MGzP9mah/fLk=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
||||
github.com/urfave/cli v1.22.10 h1:p8Fspmz3iTctJstry1PYS3HVdllxnEzTEsgIgtxTrCk=
|
||||
github.com/urfave/cli v1.22.10/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
github.com/valyala/fastjson v1.6.3/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY=
|
||||
github.com/veandco/go-sdl2 v0.4.25 h1:J5ac3KKOccp/0xGJA1PaNYKPUcZm19IxhDGs8lJofPI=
|
||||
github.com/veandco/go-sdl2 v0.4.25/go.mod h1:OROqMhHD43nT4/i9crJukyVecjPNYYuCofep6SNiAjY=
|
||||
github.com/warthog618/config v0.4.1/go.mod h1:IzcIkVay6dCubN3WBAJzPuqHyE1fTPxICvKTQ/2JA9g=
|
||||
github.com/warthog618/gpiod v0.8.0 h1:qxH9XVvWHpTxzWFSndBcujFyNH5zVRzHM63tcmm85o4=
|
||||
github.com/warthog618/gpiod v0.8.0/go.mod h1:a7Csa+IJtDBZ39++zC/6Srjo01qWejt/5velrDWuNkY=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
go.bug.st/serial v1.4.0 h1:IXHzPVbUBbql66lQZ1iV9LWzGXT5lh6S9gZxHK/KyQE=
|
||||
go.bug.st/serial v1.4.0/go.mod h1:z8CesKorE90Qr/oRSJiEuvzYRKol9r/anJZEb5kt304=
|
||||
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
go.uber.org/multierr v1.2.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
gocv.io/x/gocv v0.31.0 h1:BHDtK8v+YPvoSPQTTiZB2fM/7BLg6511JqkruY2z6LQ=
|
||||
gocv.io/x/gocv v0.31.0/go.mod h1:oc6FvfYqfBp99p+yOEzs9tbYF9gOrAQSeL/dyIPefJU=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
@ -127,10 +238,21 @@ golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b/go.mod h1:T9bdIzuCu7OtxOm
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU=
|
||||
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
@ -140,17 +262,29 @@ golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qx
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0=
|
||||
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190927073244-c990c680b611/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@ -173,7 +307,15 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/time v0.0.0-20190921001708-c4c64cad1fd0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200925191224-5d1fdd8fa346/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
|
||||
golang.org/x/tools v0.1.11/go.mod h1:SgwaegtQh8clINPpECJMqnxLv9I09HLqnW3RMqW0CA4=
|
||||
@ -182,22 +324,39 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190927181202-20e1ac93f88c/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
|
||||
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/ini.v1 v1.48.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
periph.io/x/conn/v3 v3.6.10 h1:gwU4ssmZkq1D/uz8hU91i/COo2c9DrRaS4PJZBbCd+c=
|
||||
periph.io/x/conn/v3 v3.6.10/go.mod h1:UqWNaPMosWmNCwtufoTSTTYhB2wXWsMRAJyo1PlxO4Q=
|
||||
periph.io/x/d2xx v0.0.4/go.mod h1:38Euaaj+s6l0faIRHh32a+PrjXvxFTFkPBEQI0TKg34=
|
||||
periph.io/x/host/v3 v3.7.2 h1:rCAUxkzy2xrzh18HP2AoVwTL/fEKqmcJ1icsZQGM58Q=
|
||||
periph.io/x/host/v3 v3.7.2/go.mod h1:nHMlzkPwmnHyP9Tn0I8FV+e0N3K7TjFXLZkIWzAicog=
|
||||
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
|
||||
tinygo.org/x/bluetooth v0.6.0 h1:5RTUh28WBtWfRtwFcsDcdiCvlSWr9F7fHxRikQZW/Io=
|
||||
tinygo.org/x/bluetooth v0.6.0/go.mod h1:tiW1IiKOupcsvM2CX0PwLsf6aZRL+ciSIqP2YlgYOtQ=
|
||||
tinygo.org/x/drivers v0.14.0/go.mod h1:uT2svMq3EpBZpKkGO+NQHjxjGf1f42ra4OnMMwQL2aI=
|
||||
|
137
platforms/adaptors/digitalpinsadaptor.go
Normal file
137
platforms/adaptors/digitalpinsadaptor.go
Normal file
@ -0,0 +1,137 @@
|
||||
package adaptors
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"gobot.io/x/gobot"
|
||||
"gobot.io/x/gobot/system"
|
||||
)
|
||||
|
||||
type translator func(pin string) (chip string, line int, err error)
|
||||
type creator func(chip string, line int, o ...func(gobot.DigitalPinOptioner) bool) gobot.DigitalPinner
|
||||
|
||||
type digitalPinsOption interface {
|
||||
setCreator(creator) // needed e.g. by Beaglebone adaptor
|
||||
}
|
||||
|
||||
// DigitalPinsAdaptor is a adaptor for digital pins, normally used for composition in platforms.
|
||||
type DigitalPinsAdaptor struct {
|
||||
sys *system.Accesser
|
||||
translate translator
|
||||
create creator
|
||||
pins map[string]gobot.DigitalPinner
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
// NewDigitalPinsAdaptor provides the access to digital pins of the board. It supports sysfs and gpiod system drivers.
|
||||
// This is decided by the given accesser. The translator is used to adapt the header naming, which is given by user, to
|
||||
// the internal file name or chip/line nomenclature. This varies by each platform. If for some reasons the default
|
||||
// creator is not suitable, it can be given by the option "WithPinCreator()". This is especially needed, if some values
|
||||
// needs to be adjusted after the pin was created. E.g. for Beaglebone platform.
|
||||
func NewDigitalPinsAdaptor(sys *system.Accesser, t translator, options ...func(digitalPinsOption)) *DigitalPinsAdaptor {
|
||||
s := &DigitalPinsAdaptor{
|
||||
translate: t,
|
||||
create: sys.NewDigitalPin,
|
||||
}
|
||||
for _, option := range options {
|
||||
option(s)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// WithPinCreator can be used to substitute the default creator.
|
||||
func WithPinCreator(pc creator) func(digitalPinsOption) {
|
||||
return func(a digitalPinsOption) {
|
||||
a.setCreator(pc)
|
||||
}
|
||||
}
|
||||
|
||||
// Connect prepare new connection to digital pins.
|
||||
func (a *DigitalPinsAdaptor) Connect() error {
|
||||
a.mutex.Lock()
|
||||
defer a.mutex.Unlock()
|
||||
|
||||
a.pins = make(map[string]gobot.DigitalPinner)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Finalize closes connection to digital pins
|
||||
func (a *DigitalPinsAdaptor) Finalize() (err error) {
|
||||
a.mutex.Lock()
|
||||
defer a.mutex.Unlock()
|
||||
|
||||
for _, pin := range a.pins {
|
||||
if pin != nil {
|
||||
if e := pin.Unexport(); e != nil {
|
||||
err = multierror.Append(err, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
a.pins = nil
|
||||
return
|
||||
}
|
||||
|
||||
// DigitalPin returns a digital pin. If the pin is initially acquired, it is an input.
|
||||
// Pin direction and other options can be changed afterwards by pin.ApplyOptions() at any time.
|
||||
func (a *DigitalPinsAdaptor) DigitalPin(id string) (gobot.DigitalPinner, error) {
|
||||
a.mutex.Lock()
|
||||
defer a.mutex.Unlock()
|
||||
|
||||
return a.digitalPin(id)
|
||||
}
|
||||
|
||||
// DigitalRead reads digital value from pin
|
||||
func (a *DigitalPinsAdaptor) DigitalRead(id string) (int, error) {
|
||||
a.mutex.Lock()
|
||||
defer a.mutex.Unlock()
|
||||
|
||||
pin, err := a.digitalPin(id, system.WithDirectionInput())
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return pin.Read()
|
||||
}
|
||||
|
||||
// DigitalWrite writes digital value to specified pin
|
||||
func (a *DigitalPinsAdaptor) DigitalWrite(id string, val byte) error {
|
||||
a.mutex.Lock()
|
||||
defer a.mutex.Unlock()
|
||||
|
||||
pin, err := a.digitalPin(id, system.WithDirectionOutput(int(val)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return pin.Write(int(val))
|
||||
}
|
||||
|
||||
func (a *DigitalPinsAdaptor) setCreator(pc creator) {
|
||||
a.create = pc
|
||||
}
|
||||
|
||||
func (a *DigitalPinsAdaptor) digitalPin(id string, o ...func(gobot.DigitalPinOptioner) bool) (gobot.DigitalPinner, error) {
|
||||
if a.pins == nil {
|
||||
return nil, fmt.Errorf("not connected")
|
||||
}
|
||||
|
||||
pin := a.pins[id]
|
||||
|
||||
if pin == nil {
|
||||
chip, line, err := a.translate(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pin = a.create(chip, line, o...)
|
||||
if err = pin.Export(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
a.pins[id] = pin
|
||||
} else {
|
||||
if err := pin.ApplyOptions(o...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return pin, nil
|
||||
}
|
164
platforms/adaptors/digitalpinsadaptor_test.go
Normal file
164
platforms/adaptors/digitalpinsadaptor_test.go
Normal file
@ -0,0 +1,164 @@
|
||||
package adaptors
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"runtime"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"gobot.io/x/gobot"
|
||||
"gobot.io/x/gobot/drivers/gpio"
|
||||
"gobot.io/x/gobot/gobottest"
|
||||
"gobot.io/x/gobot/system"
|
||||
)
|
||||
|
||||
// make sure that this adaptor fulfills all the required interfaces
|
||||
var _ gobot.DigitalPinnerProvider = (*DigitalPinsAdaptor)(nil)
|
||||
var _ gpio.DigitalReader = (*DigitalPinsAdaptor)(nil)
|
||||
var _ gpio.DigitalWriter = (*DigitalPinsAdaptor)(nil)
|
||||
|
||||
func initTestAdaptorWithMockedFilesystem(mockPaths []string) (*DigitalPinsAdaptor, *system.MockFilesystem) {
|
||||
sys := system.NewAccesser()
|
||||
fs := sys.UseMockFilesystem(mockPaths)
|
||||
a := NewDigitalPinsAdaptor(sys, testTranslator)
|
||||
return a, fs
|
||||
}
|
||||
|
||||
func testTranslator(pin string) (string, int, error) {
|
||||
line, err := strconv.Atoi(pin)
|
||||
if err != nil {
|
||||
return "", 0, fmt.Errorf("not a valid pin")
|
||||
}
|
||||
line = line + 11 // just for tests
|
||||
return "", line, err
|
||||
}
|
||||
|
||||
func TestConnect(t *testing.T) {
|
||||
translate := func(pin string) (chip string, line int, err error) { return }
|
||||
sys := system.NewAccesser()
|
||||
|
||||
a := NewDigitalPinsAdaptor(sys, translate)
|
||||
gobottest.Assert(t, a.pins, (map[string]gobot.DigitalPinner)(nil))
|
||||
|
||||
a.Connect()
|
||||
gobottest.Refute(t, a.pins, (map[string]gobot.DigitalPinner)(nil))
|
||||
gobottest.Assert(t, len(a.pins), 0)
|
||||
}
|
||||
|
||||
func TestFinalize(t *testing.T) {
|
||||
// arrange
|
||||
mockedPaths := []string{
|
||||
"/sys/class/gpio/export",
|
||||
"/sys/class/gpio/unexport",
|
||||
"/sys/class/gpio/gpio14/direction",
|
||||
"/sys/class/gpio/gpio14/value",
|
||||
}
|
||||
a, fs := initTestAdaptorWithMockedFilesystem(mockedPaths)
|
||||
// assert that finalize before connect is working
|
||||
gobottest.Assert(t, a.Finalize(), nil)
|
||||
// arrange
|
||||
gobottest.Assert(t, a.Connect(), nil)
|
||||
gobottest.Assert(t, a.DigitalWrite("3", 1), nil)
|
||||
gobottest.Assert(t, len(a.pins), 1)
|
||||
// act
|
||||
err := a.Finalize()
|
||||
// assert
|
||||
gobottest.Assert(t, err, nil)
|
||||
gobottest.Assert(t, len(a.pins), 0)
|
||||
// assert that finalize after finalize is working
|
||||
gobottest.Assert(t, a.Finalize(), nil)
|
||||
// arrange missing sysfs file
|
||||
gobottest.Assert(t, a.Connect(), nil)
|
||||
gobottest.Assert(t, a.DigitalWrite("3", 2), nil)
|
||||
delete(fs.Files, "/sys/class/gpio/unexport")
|
||||
err = a.Finalize()
|
||||
gobottest.Assert(t, strings.Contains(err.Error(), "/sys/class/gpio/unexport: No such file"), true)
|
||||
}
|
||||
|
||||
func TestReConnect(t *testing.T) {
|
||||
// arrange
|
||||
mockedPaths := []string{
|
||||
"/sys/class/gpio/export",
|
||||
"/sys/class/gpio/unexport",
|
||||
"/sys/class/gpio/gpio15/direction",
|
||||
"/sys/class/gpio/gpio15/value",
|
||||
}
|
||||
a, _ := initTestAdaptorWithMockedFilesystem(mockedPaths)
|
||||
a.Connect()
|
||||
gobottest.Assert(t, a.DigitalWrite("4", 1), nil)
|
||||
gobottest.Assert(t, len(a.pins), 1)
|
||||
gobottest.Assert(t, a.Finalize(), nil)
|
||||
// act
|
||||
err := a.Connect()
|
||||
// assert
|
||||
gobottest.Assert(t, err, nil)
|
||||
gobottest.Assert(t, len(a.pins), 0)
|
||||
}
|
||||
|
||||
func TestDigitalIO(t *testing.T) {
|
||||
mockedPaths := []string{
|
||||
"/sys/class/gpio/export",
|
||||
"/sys/class/gpio/unexport",
|
||||
"/sys/class/gpio/gpio18/value",
|
||||
"/sys/class/gpio/gpio18/direction",
|
||||
"/sys/class/gpio/gpio24/value",
|
||||
"/sys/class/gpio/gpio24/direction",
|
||||
}
|
||||
a, fs := initTestAdaptorWithMockedFilesystem(mockedPaths)
|
||||
err := a.DigitalWrite("7", 1)
|
||||
gobottest.Assert(t, err.Error(), "not connected")
|
||||
|
||||
a.Connect()
|
||||
|
||||
err = a.DigitalWrite("7", 1)
|
||||
gobottest.Assert(t, err, nil)
|
||||
gobottest.Assert(t, fs.Files["/sys/class/gpio/gpio18/value"].Contents, "1")
|
||||
|
||||
err = a.DigitalWrite("13", 1)
|
||||
gobottest.Assert(t, err, nil)
|
||||
|
||||
i, err := a.DigitalRead("13")
|
||||
gobottest.Assert(t, err, nil)
|
||||
gobottest.Assert(t, i, 1)
|
||||
|
||||
gobottest.Assert(t, a.DigitalWrite("notexist", 1), errors.New("not a valid pin"))
|
||||
|
||||
fs.WithReadError = true
|
||||
_, err = a.DigitalRead("13")
|
||||
gobottest.Assert(t, err, errors.New("read error"))
|
||||
|
||||
fs.WithWriteError = true
|
||||
_, err = a.DigitalRead("7")
|
||||
gobottest.Assert(t, err, errors.New("write error"))
|
||||
}
|
||||
|
||||
func TestDigitalPinConcurrency(t *testing.T) {
|
||||
oldProcs := runtime.GOMAXPROCS(0)
|
||||
runtime.GOMAXPROCS(8)
|
||||
defer runtime.GOMAXPROCS(oldProcs)
|
||||
|
||||
translate := func(pin string) (string, int, error) { line, err := strconv.Atoi(pin); return "", line, err }
|
||||
sys := system.NewAccesser()
|
||||
|
||||
for retry := 0; retry < 20; retry++ {
|
||||
|
||||
a := NewDigitalPinsAdaptor(sys, translate)
|
||||
a.Connect()
|
||||
var wg sync.WaitGroup
|
||||
|
||||
for i := 0; i < 20; i++ {
|
||||
wg.Add(1)
|
||||
pinAsString := strconv.Itoa(i)
|
||||
go func(pin string) {
|
||||
defer wg.Done()
|
||||
a.DigitalPin(pin)
|
||||
}(pinAsString)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
}
|
@ -28,7 +28,7 @@ type Adaptor struct {
|
||||
name string
|
||||
sys *system.Accesser
|
||||
mutex sync.Mutex
|
||||
digitalPins []gobot.DigitalPinner
|
||||
digitalPins map[string]gobot.DigitalPinner
|
||||
pwmPins map[string]gobot.PWMPinner
|
||||
i2cBuses map[int]i2c.I2cDevice
|
||||
usrLed string
|
||||
@ -45,7 +45,7 @@ func NewAdaptor() *Adaptor {
|
||||
b := &Adaptor{
|
||||
name: gobot.DefaultName("BeagleboneBlack"),
|
||||
sys: system.NewAccesser(),
|
||||
digitalPins: make([]gobot.DigitalPinner, 120),
|
||||
digitalPins: make(map[string]gobot.DigitalPinner, 120),
|
||||
pwmPins: make(map[string]gobot.PWMPinner),
|
||||
i2cBuses: make(map[int]i2c.I2cDevice),
|
||||
pinMap: bbbPinMap,
|
||||
@ -136,57 +136,47 @@ func (b *Adaptor) ServoWrite(pin string, angle byte) (err error) {
|
||||
}
|
||||
|
||||
// DigitalRead returns a digital value from specified pin
|
||||
func (b *Adaptor) DigitalRead(pin string) (val int, err error) {
|
||||
sysPin, err := b.DigitalPin(pin, system.IN)
|
||||
func (b *Adaptor) DigitalRead(id string) (int, error) {
|
||||
b.mutex.Lock()
|
||||
defer b.mutex.Unlock()
|
||||
|
||||
pin, err := b.digitalPin(id, system.WithDirectionInput())
|
||||
if err != nil {
|
||||
return
|
||||
return 0, err
|
||||
}
|
||||
return sysPin.Read()
|
||||
return pin.Read()
|
||||
}
|
||||
|
||||
// DigitalWrite writes a digital value to specified pin.
|
||||
// valid usr pin values are usr0, usr1, usr2 and usr3
|
||||
func (b *Adaptor) DigitalWrite(pin string, val byte) (err error) {
|
||||
if strings.Contains(pin, "usr") {
|
||||
fi, e := b.sys.OpenFile(b.usrLed+pin+"/brightness", os.O_WRONLY|os.O_APPEND, 0666)
|
||||
func (b *Adaptor) DigitalWrite(id string, val byte) error {
|
||||
b.mutex.Lock()
|
||||
defer b.mutex.Unlock()
|
||||
|
||||
if strings.Contains(id, "usr") {
|
||||
fi, e := b.sys.OpenFile(b.usrLed+id+"/brightness", os.O_WRONLY|os.O_APPEND, 0666)
|
||||
defer fi.Close()
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
_, err = fi.WriteString(strconv.Itoa(int(val)))
|
||||
_, err := fi.WriteString(strconv.Itoa(int(val)))
|
||||
return err
|
||||
}
|
||||
sysPin, err := b.DigitalPin(pin, system.OUT)
|
||||
|
||||
pin, err := b.digitalPin(id, system.WithDirectionOutput(int(val)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return sysPin.Write(int(val))
|
||||
return pin.Write(int(val))
|
||||
}
|
||||
|
||||
// DigitalPin retrieves digital pin value by name
|
||||
func (b *Adaptor) DigitalPin(pin string, dir string) (gobot.DigitalPinner, error) {
|
||||
// DigitalPin returns a digital pin. If the pin is initially acquired, it is an input.
|
||||
// Pin direction and other options can be changed afterwards by pin.ApplyOptions() at any time.
|
||||
func (b *Adaptor) DigitalPin(id string) (gobot.DigitalPinner, error) {
|
||||
b.mutex.Lock()
|
||||
defer b.mutex.Unlock()
|
||||
|
||||
i, err := b.translatePin(pin)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if b.digitalPins[i] == nil {
|
||||
b.digitalPins[i] = b.sys.NewDigitalPin(i)
|
||||
if err = b.muxPin(pin, "gpio"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err := b.digitalPins[i].Export()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if err = b.digitalPins[i].Direction(dir); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b.digitalPins[i], nil
|
||||
return b.digitalPin(id)
|
||||
}
|
||||
|
||||
// PWMPin returns matched pwmPin for specified pin number
|
||||
@ -309,7 +299,7 @@ func (b *Adaptor) GetSpiDefaultMaxSpeed() int64 {
|
||||
}
|
||||
|
||||
// translatePin converts digital pin name to pin position
|
||||
func (b *Adaptor) translatePin(pin string) (value int, err error) {
|
||||
func (b *Adaptor) translateDigitalPin(pin string) (value int, err error) {
|
||||
if val, ok := b.pinMap[pin]; ok {
|
||||
value = val
|
||||
} else {
|
||||
@ -337,6 +327,30 @@ func (b *Adaptor) translateAnalogPin(pin string) (value string, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (b *Adaptor) digitalPin(id string, o ...func(gobot.DigitalPinOptioner) bool) (gobot.DigitalPinner, error) {
|
||||
pin := b.digitalPins[id]
|
||||
if pin == nil {
|
||||
i, err := b.translateDigitalPin(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pin = b.sys.NewDigitalPin("", i, o...)
|
||||
if err := b.muxPin(id, "gpio"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := pin.Export(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b.digitalPins[id] = pin
|
||||
} else {
|
||||
if err := pin.ApplyOptions(o...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return pin, nil
|
||||
}
|
||||
|
||||
func (b *Adaptor) muxPin(pin, cmd string) error {
|
||||
path := fmt.Sprintf("/sys/devices/platform/ocp/ocp:%s_pinmux/state", pin)
|
||||
fi, e := b.sys.OpenFile(path, os.O_WRONLY, 0666)
|
||||
|
@ -240,8 +240,9 @@ func TestDigitalPinDirectionFileError(t *testing.T) {
|
||||
err := a.DigitalWrite("P9_12", 1)
|
||||
gobottest.Assert(t, strings.Contains(err.Error(), "/sys/class/gpio/gpio60/direction: No such file."), true)
|
||||
|
||||
// no pin added after previous problem, so no pin to unexport in finalize
|
||||
err = a.Finalize()
|
||||
gobottest.Assert(t, strings.Contains(err.Error(), "/sys/class/gpio/unexport: No such file."), true)
|
||||
gobottest.Assert(t, nil, err)
|
||||
}
|
||||
|
||||
func TestDigitalPinFinalizeFileError(t *testing.T) {
|
||||
|
@ -103,24 +103,39 @@ func (c *Adaptor) Finalize() (err error) {
|
||||
// Valids pins are the XIO-P0 through XIO-P7 pins from the
|
||||
// extender (pins 13-20 on header 14), as well as the SoC pins
|
||||
// aka all the other pins.
|
||||
func (c *Adaptor) DigitalRead(pin string) (val int, err error) {
|
||||
sysPin, err := c.DigitalPin(pin, system.IN)
|
||||
func (c *Adaptor) DigitalRead(id string) (int, error) {
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
|
||||
pin, err := c.digitalPin(id, system.WithDirectionInput())
|
||||
if err != nil {
|
||||
return
|
||||
return 0, err
|
||||
}
|
||||
return sysPin.Read()
|
||||
return pin.Read()
|
||||
}
|
||||
|
||||
// DigitalWrite writes digital value to the specified pin.
|
||||
// Valids pins are the XIO-P0 through XIO-P7 pins from the
|
||||
// extender (pins 13-20 on header 14), as well as the SoC pins
|
||||
// aka all the other pins.
|
||||
func (c *Adaptor) DigitalWrite(pin string, val byte) (err error) {
|
||||
sysPin, err := c.DigitalPin(pin, system.OUT)
|
||||
func (c *Adaptor) DigitalWrite(id string, val byte) error {
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
|
||||
pin, err := c.digitalPin(id, system.WithDirectionOutput(int(val)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return sysPin.Write(int(val))
|
||||
return pin.Write(int(val))
|
||||
}
|
||||
|
||||
// DigitalPin returns a digital pin. If the pin is initially acquired, it is an input.
|
||||
// Pin direction and other options can be changed afterwards by pin.ApplyOptions() at any time.
|
||||
func (c *Adaptor) DigitalPin(id string) (gobot.DigitalPinner, error) {
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
|
||||
return c.digitalPin(id)
|
||||
}
|
||||
|
||||
// GetConnection returns a connection to a device on a specified bus.
|
||||
@ -143,31 +158,6 @@ func (c *Adaptor) GetDefaultBus() int {
|
||||
return 1
|
||||
}
|
||||
|
||||
// DigitalPin returns matched digitalPin for specified values
|
||||
func (c *Adaptor) DigitalPin(pin string, dir string) (gobot.DigitalPinner, error) {
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
|
||||
i, err := c.translatePin(pin)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if c.digitalPins[i] == nil {
|
||||
c.digitalPins[i] = c.sys.NewDigitalPin(i)
|
||||
if err = c.digitalPins[i].Export(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if err = c.digitalPins[i].Direction(dir); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return c.digitalPins[i], nil
|
||||
}
|
||||
|
||||
// PWMPin returns matched pwmPin for specified pin number
|
||||
func (c *Adaptor) PWMPin(pin string) (gobot.PWMPinner, error) {
|
||||
c.mutex.Lock()
|
||||
@ -291,11 +281,34 @@ func getXIOBase() (baseAddr int, err error) {
|
||||
return baseAddr, nil
|
||||
}
|
||||
|
||||
func (c *Adaptor) translatePin(pin string) (i int, err error) {
|
||||
if val, ok := c.pinmap[pin]; ok {
|
||||
func (c *Adaptor) translateDigitalPin(id string) (i int, err error) {
|
||||
if val, ok := c.pinmap[id]; ok {
|
||||
i = val.pin
|
||||
} else {
|
||||
err = errors.New("Not a valid pin")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Adaptor) digitalPin(id string, o ...func(gobot.DigitalPinOptioner) bool) (gobot.DigitalPinner, error) {
|
||||
i, err := c.translateDigitalPin(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pin := c.digitalPins[i]
|
||||
|
||||
if pin == nil {
|
||||
pin = c.sys.NewDigitalPin("", i, o...)
|
||||
if err = pin.Export(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.digitalPins[i] = pin
|
||||
} else {
|
||||
if err := pin.ApplyOptions(o...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return pin, nil
|
||||
}
|
||||
|
@ -83,53 +83,43 @@ func (c *Adaptor) Finalize() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// DigitalPin returns matched digitalPin for specified values
|
||||
func (c *Adaptor) DigitalPin(pin string, dir string) (gobot.DigitalPinner, error) {
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
|
||||
i, err := c.translatePin(pin)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if c.digitalPins[i] == nil {
|
||||
c.digitalPins[i] = c.sys.NewDigitalPin(i)
|
||||
if err = c.digitalPins[i].Export(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if err = c.digitalPins[i].Direction(dir); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return c.digitalPins[i], nil
|
||||
}
|
||||
|
||||
// DigitalRead reads digital value to the specified pin.
|
||||
// Valids pins are the GPIO_A through GPIO_L pins from the
|
||||
// extender (pins 23-34 on header J8), as well as the SoC pins
|
||||
// aka all the other pins, APQ GPIO_0-GPIO_122 and PM_MPP_0-4.
|
||||
func (c *Adaptor) DigitalRead(pin string) (val int, err error) {
|
||||
sysPin, err := c.DigitalPin(pin, system.IN)
|
||||
func (c *Adaptor) DigitalRead(id string) (int, error) {
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
|
||||
pin, err := c.digitalPin(id, system.WithDirectionInput())
|
||||
if err != nil {
|
||||
return
|
||||
return 0, err
|
||||
}
|
||||
return sysPin.Read()
|
||||
return pin.Read()
|
||||
}
|
||||
|
||||
// DigitalWrite writes digital value to the specified pin.
|
||||
// Valids pins are the GPIO_A through GPIO_L pins from the
|
||||
// extender (pins 23-34 on header J8), as well as the SoC pins
|
||||
// aka all the other pins, APQ GPIO_0-GPIO_122 and PM_MPP_0-4.
|
||||
func (c *Adaptor) DigitalWrite(pin string, val byte) (err error) {
|
||||
sysPin, err := c.DigitalPin(pin, system.OUT)
|
||||
func (c *Adaptor) DigitalWrite(id string, val byte) error {
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
|
||||
pin, err := c.digitalPin(id, system.WithDirectionOutput(int(val)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return sysPin.Write(int(val))
|
||||
return pin.Write(int(val))
|
||||
}
|
||||
|
||||
// DigitalPin returns a digital pin. If the pin is initially acquired, it is an input.
|
||||
// Pin direction and other options can be changed afterwards by pin.ApplyOptions() at any time.
|
||||
func (c *Adaptor) DigitalPin(id string) (gobot.DigitalPinner, error) {
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
|
||||
return c.digitalPin(id)
|
||||
}
|
||||
|
||||
// GetConnection returns a connection to a device on a specified bus.
|
||||
@ -161,11 +151,33 @@ func (c *Adaptor) setPins() {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Adaptor) translatePin(pin string) (i int, err error) {
|
||||
if val, ok := c.pinMap[pin]; ok {
|
||||
func (c *Adaptor) translateDigitalPin(id string) (i int, err error) {
|
||||
if val, ok := c.pinMap[id]; ok {
|
||||
i = val
|
||||
} else {
|
||||
err = errors.New("Not a valid pin")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Adaptor) digitalPin(id string, o ...func(gobot.DigitalPinOptioner) bool) (gobot.DigitalPinner, error) {
|
||||
i, err := c.translateDigitalPin(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pin := c.digitalPins[i]
|
||||
if pin == nil {
|
||||
pin = c.sys.NewDigitalPin("", i, o...)
|
||||
if err = pin.Export(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.digitalPins[i] = pin
|
||||
} else {
|
||||
if err := pin.ApplyOptions(o...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return pin, nil
|
||||
}
|
||||
|
@ -61,39 +61,33 @@ func (e *Adaptor) Board() string { return e.board }
|
||||
func (e *Adaptor) SetBoard(n string) { e.board = n }
|
||||
|
||||
// Connect initializes the Edison for use with the Arduino breakout board
|
||||
func (e *Adaptor) Connect() (err error) {
|
||||
func (e *Adaptor) Connect() error {
|
||||
e.digitalPins = make(map[int]gobot.DigitalPinner)
|
||||
e.pwmPins = make(map[int]gobot.PWMPinner)
|
||||
|
||||
if e.Board() == "arduino" || e.Board() == "" {
|
||||
aerr := e.checkForArduino()
|
||||
if aerr != nil {
|
||||
return aerr
|
||||
}
|
||||
e.board = "arduino"
|
||||
}
|
||||
|
||||
switch e.Board() {
|
||||
switch e.board {
|
||||
case "sparkfun":
|
||||
e.pinmap = sparkfunPinMap
|
||||
case "arduino":
|
||||
case "arduino", "":
|
||||
e.board = "arduino"
|
||||
e.pinmap = arduinoPinMap
|
||||
if errs := e.arduinoSetup(); errs != nil {
|
||||
err = multierror.Append(err, errs)
|
||||
if err := e.arduinoSetup(); err != nil {
|
||||
return err
|
||||
}
|
||||
case "miniboard":
|
||||
e.pinmap = miniboardPinMap
|
||||
default:
|
||||
errs := errors.New("Unknown board type: " + e.Board())
|
||||
err = multierror.Append(err, errs)
|
||||
return errors.New("Unknown board type: " + e.Board())
|
||||
}
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
// Finalize releases all i2c devices and exported analog, digital, pwm pins.
|
||||
func (e *Adaptor) Finalize() (err error) {
|
||||
if errs := e.tristate.Unexport(); errs != nil {
|
||||
err = multierror.Append(err, errs)
|
||||
if e.tristate != nil {
|
||||
if errs := e.tristate.Unexport(); errs != nil {
|
||||
err = multierror.Append(err, errs)
|
||||
}
|
||||
}
|
||||
for _, pin := range e.digitalPins {
|
||||
if pin != nil {
|
||||
@ -122,7 +116,10 @@ func (e *Adaptor) Finalize() (err error) {
|
||||
|
||||
// DigitalRead reads digital value from pin
|
||||
func (e *Adaptor) DigitalRead(pin string) (i int, err error) {
|
||||
sysPin, err := e.DigitalPin(pin, "in")
|
||||
e.mutex.Lock()
|
||||
defer e.mutex.Unlock()
|
||||
|
||||
sysPin, err := e.digitalPin(pin, system.WithDirectionInput())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@ -131,13 +128,25 @@ func (e *Adaptor) DigitalRead(pin string) (i int, err error) {
|
||||
|
||||
// DigitalWrite writes a value to the pin. Acceptable values are 1 or 0.
|
||||
func (e *Adaptor) DigitalWrite(pin string, val byte) (err error) {
|
||||
sysPin, err := e.DigitalPin(pin, "out")
|
||||
e.mutex.Lock()
|
||||
defer e.mutex.Unlock()
|
||||
|
||||
sysPin, err := e.digitalPin(pin, system.WithDirectionOutput(int(val)))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return sysPin.Write(int(val))
|
||||
}
|
||||
|
||||
// DigitalPin returns a digital pin. If the pin is initially acquired, it is an input.
|
||||
// Pin direction and other options can be changed afterwards by pin.ApplyOptions() at any time.
|
||||
func (e *Adaptor) DigitalPin(id string) (gobot.DigitalPinner, error) {
|
||||
e.mutex.Lock()
|
||||
defer e.mutex.Unlock()
|
||||
|
||||
return e.digitalPin(id)
|
||||
}
|
||||
|
||||
// PwmWrite writes the 0-254 value to the specified pin
|
||||
func (e *Adaptor) PwmWrite(pin string, val byte) (err error) {
|
||||
pwmPin, err := e.PWMPin(pin)
|
||||
@ -192,80 +201,6 @@ func (e *Adaptor) GetDefaultBus() int {
|
||||
return 1
|
||||
}
|
||||
|
||||
// DigitalPin returns matched system.DigitalPin for specified values
|
||||
func (e *Adaptor) DigitalPin(pin string, dir string) (gobot.DigitalPinner, error) {
|
||||
e.mutex.Lock()
|
||||
defer e.mutex.Unlock()
|
||||
|
||||
i := e.pinmap[pin]
|
||||
var err error
|
||||
if e.digitalPins[i.pin] == nil {
|
||||
if e.digitalPins[i.pin], err = e.newExportedPin(i.pin); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if i.resistor > 0 {
|
||||
if e.digitalPins[i.resistor], err = e.newExportedPin(i.resistor); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if i.levelShifter > 0 {
|
||||
if e.digitalPins[i.levelShifter], err = e.newExportedPin(i.levelShifter); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if len(i.mux) > 0 {
|
||||
for _, mux := range i.mux {
|
||||
if e.digitalPins[mux.pin], err = e.newExportedPin(mux.pin); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = pinWrite(e.digitalPins[mux.pin], system.OUT, mux.value); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if dir == "in" {
|
||||
if err = e.digitalPins[i.pin].Direction(system.IN); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if i.resistor > 0 {
|
||||
if err = pinWrite(e.digitalPins[i.resistor], system.OUT, system.LOW); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if i.levelShifter > 0 {
|
||||
if err = pinWrite(e.digitalPins[i.levelShifter], system.OUT, system.LOW); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
} else if dir == "out" {
|
||||
if err = e.digitalPins[i.pin].Direction(system.OUT); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if i.resistor > 0 {
|
||||
if err = e.digitalPins[i.resistor].Direction(system.IN); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if i.levelShifter > 0 {
|
||||
err = pinWrite(e.digitalPins[i.levelShifter], system.OUT, system.HIGH)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
return e.digitalPins[i.pin], nil
|
||||
}
|
||||
|
||||
// PWMPin returns a system.PWMPin
|
||||
func (e *Adaptor) PWMPin(pin string) (gobot.PWMPinner, error) {
|
||||
sysPin := e.pinmap[pin]
|
||||
@ -295,115 +230,82 @@ func (e *Adaptor) PWMPin(pin string) (gobot.PWMPinner, error) {
|
||||
return nil, errors.New("Not a PWM pin")
|
||||
}
|
||||
|
||||
// TODO: also check to see if device labels for
|
||||
// /sys/class/gpio/gpiochip{200,216,232,248}/label == "pcal9555a"
|
||||
func (e *Adaptor) checkForArduino() error {
|
||||
if err := e.exportTristatePin(); err != nil {
|
||||
func (e *Adaptor) newUnexportedDigitalPin(i int, o ...func(gobot.DigitalPinOptioner) bool) error {
|
||||
io := e.sys.NewDigitalPin("", i, o...)
|
||||
if err := io.Export(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return io.Unexport()
|
||||
}
|
||||
|
||||
func (e *Adaptor) newExportedPin(pin int) (gobot.DigitalPinner, error) {
|
||||
sysPin := e.sys.NewDigitalPin(pin)
|
||||
func (e *Adaptor) newExportedDigitalPin(pin int, o ...func(gobot.DigitalPinOptioner) bool) (gobot.DigitalPinner, error) {
|
||||
sysPin := e.sys.NewDigitalPin("", pin, o...)
|
||||
err := sysPin.Export()
|
||||
return sysPin, err
|
||||
}
|
||||
|
||||
func (e *Adaptor) exportTristatePin() (err error) {
|
||||
e.tristate, err = e.newExportedPin(214)
|
||||
return
|
||||
}
|
||||
|
||||
// arduinoSetup does needed setup for the Arduino compatible breakout board
|
||||
func (e *Adaptor) arduinoSetup() (err error) {
|
||||
if err = e.exportTristatePin(); err != nil {
|
||||
func (e *Adaptor) arduinoSetup() error {
|
||||
// TODO: also check to see if device labels for
|
||||
// /sys/class/gpio/gpiochip{200,216,232,248}/label == "pcal9555a"
|
||||
|
||||
tpin, err := e.newExportedDigitalPin(214, system.WithDirectionOutput(system.LOW))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = pinWrite(e.tristate, system.OUT, system.LOW)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
e.tristate = tpin
|
||||
|
||||
for _, i := range []int{263, 262} {
|
||||
if err = e.newDigitalPin(i, system.HIGH); err != nil {
|
||||
if err := e.newUnexportedDigitalPin(i, system.WithDirectionOutput(system.HIGH)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, i := range []int{240, 241, 242, 243} {
|
||||
if err = e.newDigitalPin(i, system.LOW); err != nil {
|
||||
if err := e.newUnexportedDigitalPin(i, system.WithDirectionOutput(system.LOW)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, i := range []string{"111", "115", "114", "109"} {
|
||||
if err = e.changePinMode(i, "1"); err != nil {
|
||||
if err := e.changePinMode(i, "1"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, i := range []string{"131", "129", "40"} {
|
||||
if err = e.changePinMode(i, "0"); err != nil {
|
||||
if err := e.changePinMode(i, "0"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err = e.tristate.Write(system.HIGH)
|
||||
return
|
||||
return e.tristate.Write(system.HIGH)
|
||||
}
|
||||
|
||||
func (e *Adaptor) arduinoI2CSetup() (err error) {
|
||||
if err = e.tristate.Write(system.LOW); err != nil {
|
||||
return
|
||||
func (e *Adaptor) arduinoI2CSetup() error {
|
||||
if err := e.tristate.Write(system.LOW); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, i := range []int{14, 165, 212, 213} {
|
||||
io := e.sys.NewDigitalPin(i)
|
||||
if err = io.Export(); err != nil {
|
||||
return
|
||||
}
|
||||
if err = io.Direction(system.IN); err != nil {
|
||||
return
|
||||
}
|
||||
if err = io.Unexport(); err != nil {
|
||||
return
|
||||
if err := e.newUnexportedDigitalPin(i, system.WithDirectionInput()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, i := range []int{236, 237, 204, 205} {
|
||||
if err = e.newDigitalPin(i, system.LOW); err != nil {
|
||||
if err := e.newUnexportedDigitalPin(i, system.WithDirectionOutput(system.LOW)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, i := range []string{"28", "27"} {
|
||||
if err = e.changePinMode(i, "1"); err != nil {
|
||||
return
|
||||
if err := e.changePinMode(i, "1"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err = e.tristate.Write(system.HIGH); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (e *Adaptor) newDigitalPin(i int, level int) (err error) {
|
||||
io := e.sys.NewDigitalPin(i)
|
||||
if err = io.Export(); err != nil {
|
||||
return
|
||||
}
|
||||
if err = io.Direction(system.OUT); err != nil {
|
||||
return
|
||||
}
|
||||
if err = io.Write(level); err != nil {
|
||||
return
|
||||
}
|
||||
err = io.Unexport()
|
||||
return
|
||||
return e.tristate.Write(system.HIGH)
|
||||
}
|
||||
|
||||
func (e *Adaptor) writeFile(path string, data []byte) (i int, err error) {
|
||||
@ -438,10 +340,63 @@ func (e *Adaptor) changePinMode(pin, mode string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// pinWrite sets Direction and writes level for a specific pin
|
||||
func pinWrite(pin gobot.DigitalPinner, dir string, level int) error {
|
||||
if err := pin.Direction(dir); err != nil {
|
||||
return err
|
||||
func (e *Adaptor) digitalPin(id string, o ...func(gobot.DigitalPinOptioner) bool) (gobot.DigitalPinner, error) {
|
||||
i := e.pinmap[id]
|
||||
|
||||
err := e.ensureDigitalPin(i.pin, o...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return pin.Write(level)
|
||||
pin := e.digitalPins[i.pin]
|
||||
vpin, ok := pin.(gobot.DigitalPinValuer)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("can not determine the direction behavior")
|
||||
}
|
||||
dir := vpin.DirectionBehavior()
|
||||
if i.resistor > 0 {
|
||||
rop := system.WithDirectionOutput(system.LOW)
|
||||
if dir == system.OUT {
|
||||
rop = system.WithDirectionInput()
|
||||
}
|
||||
if err := e.ensureDigitalPin(i.resistor, rop); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if i.levelShifter > 0 {
|
||||
lop := system.WithDirectionOutput(system.LOW)
|
||||
if dir == system.OUT {
|
||||
lop = system.WithDirectionOutput(system.HIGH)
|
||||
}
|
||||
if err := e.ensureDigitalPin(i.levelShifter, lop); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if len(i.mux) > 0 {
|
||||
for _, mux := range i.mux {
|
||||
if err := e.ensureDigitalPin(mux.pin, system.WithDirectionOutput(mux.value)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return pin, nil
|
||||
}
|
||||
|
||||
func (e *Adaptor) ensureDigitalPin(idx int, o ...func(gobot.DigitalPinOptioner) bool) error {
|
||||
pin := e.digitalPins[idx]
|
||||
var err error
|
||||
if pin == nil {
|
||||
pin, err = e.newExportedDigitalPin(idx, o...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
e.digitalPins[idx] = pin
|
||||
} else {
|
||||
if err := pin.ApplyOptions(o...); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -269,12 +269,11 @@ func TestConnectUnknown(t *testing.T) {
|
||||
a.SetBoard("wha")
|
||||
|
||||
err := a.Connect()
|
||||
gobottest.Assert(t, strings.Contains(err.Error(), "1 error occurred"), true)
|
||||
gobottest.Assert(t, strings.Contains(err.Error(), "Unknown board type: wha"), true)
|
||||
}
|
||||
|
||||
func TestFinalize(t *testing.T) {
|
||||
a, _ := initTestAdaptorWithMockedFilesystem()
|
||||
a, fs := initTestAdaptorWithMockedFilesystem()
|
||||
|
||||
a.DigitalWrite("3", 1)
|
||||
a.PwmWrite("5", 100)
|
||||
@ -282,11 +281,10 @@ func TestFinalize(t *testing.T) {
|
||||
a.GetConnection(0xff, 6)
|
||||
gobottest.Assert(t, a.Finalize(), nil)
|
||||
|
||||
a = NewAdaptor()
|
||||
a.sys.UseMockFilesystem([]string{})
|
||||
a.Connect()
|
||||
// remove one file to force Finalize error
|
||||
delete(fs.Files, "/sys/class/gpio/unexport")
|
||||
err := a.Finalize()
|
||||
gobottest.Assert(t, strings.Contains(err.Error(), "1 error occurred"), true)
|
||||
gobottest.Assert(t, strings.Contains(err.Error(), "4 errors occurred"), true)
|
||||
gobottest.Assert(t, strings.Contains(err.Error(), "/sys/class/gpio/unexport"), true)
|
||||
}
|
||||
|
||||
@ -322,7 +320,7 @@ func TestDigitalPinInFileError(t *testing.T) {
|
||||
delete(fs.Files, "/sys/class/gpio/gpio40/direction")
|
||||
a.Connect()
|
||||
|
||||
_, err := a.DigitalPin("13", "in")
|
||||
_, err := a.DigitalPin("13")
|
||||
gobottest.Assert(t, strings.Contains(err.Error(), "No such file"), true)
|
||||
|
||||
}
|
||||
@ -334,7 +332,7 @@ func TestDigitalPinInResistorFileError(t *testing.T) {
|
||||
delete(fs.Files, "/sys/class/gpio/gpio229/direction")
|
||||
a.Connect()
|
||||
|
||||
_, err := a.DigitalPin("13", "in")
|
||||
_, err := a.DigitalPin("13")
|
||||
gobottest.Assert(t, strings.Contains(err.Error(), "No such file"), true)
|
||||
}
|
||||
|
||||
@ -345,7 +343,7 @@ func TestDigitalPinInLevelShifterFileError(t *testing.T) {
|
||||
delete(fs.Files, "/sys/class/gpio/gpio261/direction")
|
||||
a.Connect()
|
||||
|
||||
_, err := a.DigitalPin("13", "in")
|
||||
_, err := a.DigitalPin("13")
|
||||
gobottest.Assert(t, strings.Contains(err.Error(), "No such file"), true)
|
||||
}
|
||||
|
||||
@ -356,7 +354,7 @@ func TestDigitalPinInMuxFileError(t *testing.T) {
|
||||
delete(fs.Files, "/sys/class/gpio/gpio243/direction")
|
||||
a.Connect()
|
||||
|
||||
_, err := a.DigitalPin("13", "in")
|
||||
_, err := a.DigitalPin("13")
|
||||
gobottest.Assert(t, strings.Contains(err.Error(), "No such file"), true)
|
||||
}
|
||||
|
||||
|
@ -82,47 +82,38 @@ func (e *Adaptor) Finalize() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// DigitalPin returns matched digitalPin for specified values
|
||||
func (e *Adaptor) DigitalPin(pin string, dir string) (gobot.DigitalPinner, error) {
|
||||
// DigitalRead reads digital value from pin
|
||||
func (e *Adaptor) DigitalRead(id string) (int, error) {
|
||||
e.mutex.Lock()
|
||||
defer e.mutex.Unlock()
|
||||
|
||||
i := sysfsPinMap[pin]
|
||||
if e.digitalPins[i.pin] == nil {
|
||||
e.digitalPins[i.pin] = e.sys.NewDigitalPin(i.pin)
|
||||
if err := e.digitalPins[i.pin].Export(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if dir == "in" {
|
||||
if err := e.digitalPins[i.pin].Direction(system.IN); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else if dir == "out" {
|
||||
if err := e.digitalPins[i.pin].Direction(system.OUT); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return e.digitalPins[i.pin], nil
|
||||
}
|
||||
|
||||
// DigitalRead reads digital value from pin
|
||||
func (e *Adaptor) DigitalRead(pin string) (i int, err error) {
|
||||
sysPin, err := e.DigitalPin(pin, "in")
|
||||
pin, err := e.digitalPin(id, system.WithDirectionInput())
|
||||
if err != nil {
|
||||
return
|
||||
return 0, err
|
||||
}
|
||||
return sysPin.Read()
|
||||
return pin.Read()
|
||||
}
|
||||
|
||||
// DigitalWrite writes a value to the pin. Acceptable values are 1 or 0.
|
||||
func (e *Adaptor) DigitalWrite(pin string, val byte) (err error) {
|
||||
sysPin, err := e.DigitalPin(pin, "out")
|
||||
func (e *Adaptor) DigitalWrite(id string, val byte) error {
|
||||
e.mutex.Lock()
|
||||
defer e.mutex.Unlock()
|
||||
|
||||
pin, err := e.digitalPin(id, system.WithDirectionOutput(int(val)))
|
||||
if err != nil {
|
||||
return
|
||||
return err
|
||||
}
|
||||
return sysPin.Write(int(val))
|
||||
|
||||
return pin.Write(int(val))
|
||||
}
|
||||
|
||||
// DigitalPin returns a digital pin. If the pin is initially acquired, it is an input.
|
||||
// Pin direction and other options can be changed afterwards by pin.ApplyOptions() at any time.
|
||||
func (e *Adaptor) DigitalPin(id string) (gobot.DigitalPinner, error) {
|
||||
e.mutex.Lock()
|
||||
defer e.mutex.Unlock()
|
||||
|
||||
return e.digitalPin(id)
|
||||
}
|
||||
|
||||
// PwmWrite writes the 0-254 value to the specified pin
|
||||
@ -184,3 +175,21 @@ func (e *Adaptor) GetConnection(address int, bus int) (connection i2c.Connection
|
||||
func (e *Adaptor) GetDefaultBus() int {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (e *Adaptor) digitalPin(id string, o ...func(gobot.DigitalPinOptioner) bool) (gobot.DigitalPinner, error) {
|
||||
i := sysfsPinMap[id]
|
||||
pin := e.digitalPins[i.pin]
|
||||
|
||||
if pin == nil {
|
||||
pin = e.sys.NewDigitalPin("", i.pin, o...)
|
||||
if err := pin.Export(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
e.digitalPins[i.pin] = pin
|
||||
} else {
|
||||
if err := pin.ApplyOptions(o...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return pin, nil
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package joule
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@ -133,10 +134,14 @@ func TestFinalize(t *testing.T) {
|
||||
func TestDigitalIO(t *testing.T) {
|
||||
a, fs := initTestAdaptorWithMockedFilesystem()
|
||||
|
||||
log.Println("now test write")
|
||||
a.DigitalWrite("J12_1", 1)
|
||||
gobottest.Assert(t, fs.Files["/sys/class/gpio/gpio451/value"].Contents, "1")
|
||||
|
||||
log.Println("now test write")
|
||||
a.DigitalWrite("J12_1", 0)
|
||||
|
||||
log.Println("now test read")
|
||||
i, err := a.DigitalRead("J12_1")
|
||||
gobottest.Assert(t, err, nil)
|
||||
gobottest.Assert(t, i, 0)
|
||||
|
@ -93,43 +93,37 @@ func (j *Adaptor) Finalize() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// DigitalPin returns matched digitalPin for specified values
|
||||
func (j *Adaptor) DigitalPin(pin string, dir string) (gobot.DigitalPinner, error) {
|
||||
i, err := j.translatePin(pin)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
currentPin, err := j.getExportedDigitalPin(i, dir)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = currentPin.Direction(dir); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return currentPin, nil
|
||||
}
|
||||
|
||||
// DigitalRead reads digital value from pin
|
||||
func (j *Adaptor) DigitalRead(pin string) (val int, err error) {
|
||||
sysPin, err := j.DigitalPin(pin, system.IN)
|
||||
func (j *Adaptor) DigitalRead(id string) (int, error) {
|
||||
j.mutex.Lock()
|
||||
defer j.mutex.Unlock()
|
||||
|
||||
pin, err := j.digitalPin(id, system.WithDirectionInput())
|
||||
if err != nil {
|
||||
return
|
||||
return 0, err
|
||||
}
|
||||
return sysPin.Read()
|
||||
return pin.Read()
|
||||
}
|
||||
|
||||
// DigitalWrite writes digital value to specified pin
|
||||
func (j *Adaptor) DigitalWrite(pin string, val byte) (err error) {
|
||||
sysPin, err := j.DigitalPin(pin, system.OUT)
|
||||
func (j *Adaptor) DigitalWrite(id string, val byte) error {
|
||||
j.mutex.Lock()
|
||||
defer j.mutex.Unlock()
|
||||
|
||||
pin, err := j.digitalPin(id, system.WithDirectionOutput(int(val)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return sysPin.Write(int(val))
|
||||
return pin.Write(int(val))
|
||||
}
|
||||
|
||||
// DigitalPin returns a digital pin. If the pin is initially acquired, it is an input.
|
||||
// Pin direction and other options can be changed afterwards by pin.ApplyOptions() at any time.
|
||||
func (j *Adaptor) DigitalPin(id string) (gobot.DigitalPinner, error) {
|
||||
j.mutex.Lock()
|
||||
defer j.mutex.Unlock()
|
||||
|
||||
return j.digitalPin(id)
|
||||
}
|
||||
|
||||
// GetConnection returns an i2c connection to a device on a specified bus.
|
||||
@ -237,20 +231,6 @@ func (j *Adaptor) ServoWrite(pin string, angle byte) (err error) {
|
||||
return sysPin.SetDutyCycle(duty)
|
||||
}
|
||||
|
||||
func (j *Adaptor) getExportedDigitalPin(translatedPin int, dir string) (gobot.DigitalPinner, error) {
|
||||
j.mutex.Lock()
|
||||
defer j.mutex.Unlock()
|
||||
|
||||
if j.digitalPins[translatedPin] == nil {
|
||||
j.digitalPins[translatedPin] = j.sys.NewDigitalPin(translatedPin)
|
||||
if err := j.digitalPins[translatedPin].Export(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return j.digitalPins[translatedPin], nil
|
||||
}
|
||||
|
||||
func (j *Adaptor) getI2cBus(bus int) (_ i2c.I2cDevice, err error) {
|
||||
j.mutex.Lock()
|
||||
defer j.mutex.Unlock()
|
||||
@ -271,3 +251,25 @@ func (j *Adaptor) translatePin(pin string) (i int, err error) {
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (j *Adaptor) digitalPin(id string, o ...func(gobot.DigitalPinOptioner) bool) (gobot.DigitalPinner, error) {
|
||||
i, err := j.translatePin(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pin := j.digitalPins[i]
|
||||
if pin == nil {
|
||||
pin = j.sys.NewDigitalPin("", i, o...)
|
||||
if err := pin.Export(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
j.digitalPins[i] = pin
|
||||
} else {
|
||||
if err := pin.ApplyOptions(o...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return pin, nil
|
||||
}
|
||||
|
@ -70,21 +70,24 @@ func TestDigitalIO(t *testing.T) {
|
||||
}
|
||||
a, fs := initTestAdaptorWithMockedFilesystem(mockPaths)
|
||||
|
||||
a.DigitalWrite("7", 1)
|
||||
err := a.DigitalWrite("7", 1)
|
||||
gobottest.Assert(t, err, nil)
|
||||
gobottest.Assert(t, fs.Files["/sys/class/gpio/gpio216/value"].Contents, "1")
|
||||
|
||||
a.DigitalWrite("13", 1)
|
||||
i, _ := a.DigitalRead("13")
|
||||
err = a.DigitalWrite("13", 1)
|
||||
gobottest.Assert(t, err, nil)
|
||||
i, err := a.DigitalRead("13")
|
||||
gobottest.Assert(t, err, nil)
|
||||
gobottest.Assert(t, i, 1)
|
||||
|
||||
gobottest.Assert(t, a.DigitalWrite("notexist", 1), errors.New("Not a valid pin"))
|
||||
|
||||
fs.WithReadError = true
|
||||
_, err := a.DigitalRead("7")
|
||||
_, err = a.DigitalRead("13")
|
||||
gobottest.Assert(t, err, errors.New("read error"))
|
||||
|
||||
fs.WithWriteError = true
|
||||
_, err = a.DigitalRead("13")
|
||||
_, err = a.DigitalRead("7")
|
||||
gobottest.Assert(t, err, errors.New("write error"))
|
||||
}
|
||||
|
||||
@ -140,7 +143,7 @@ func TestDigitalPinConcurrency(t *testing.T) {
|
||||
pinAsString := strconv.Itoa(i)
|
||||
go func(pin string) {
|
||||
defer wg.Done()
|
||||
a.DigitalPin(pin, system.IN)
|
||||
a.DigitalPin(pin)
|
||||
}(pinAsString)
|
||||
}
|
||||
|
||||
|
@ -12,6 +12,7 @@ import (
|
||||
"gobot.io/x/gobot"
|
||||
"gobot.io/x/gobot/drivers/i2c"
|
||||
"gobot.io/x/gobot/drivers/spi"
|
||||
"gobot.io/x/gobot/platforms/adaptors"
|
||||
"gobot.io/x/gobot/system"
|
||||
)
|
||||
|
||||
@ -19,11 +20,11 @@ const infoFile = "/proc/cpuinfo"
|
||||
|
||||
// Adaptor is the Gobot Adaptor for the Raspberry Pi
|
||||
type Adaptor struct {
|
||||
name string
|
||||
mutex sync.Mutex
|
||||
sys *system.Accesser
|
||||
revision string
|
||||
digitalPins map[int]gobot.DigitalPinner
|
||||
name string
|
||||
mutex sync.Mutex
|
||||
sys *system.Accesser
|
||||
revision string
|
||||
*adaptors.DigitalPinsAdaptor
|
||||
pwmPins map[int]gobot.PWMPinner
|
||||
i2cBuses [2]i2c.I2cDevice
|
||||
spiDevices [2]spi.Connection
|
||||
@ -33,14 +34,14 @@ type Adaptor struct {
|
||||
|
||||
// NewAdaptor creates a Raspi Adaptor
|
||||
func NewAdaptor() *Adaptor {
|
||||
sys := system.NewAccesser("cdev")
|
||||
r := &Adaptor{
|
||||
name: gobot.DefaultName("RaspberryPi"),
|
||||
sys: system.NewAccesser(),
|
||||
digitalPins: make(map[int]gobot.DigitalPinner),
|
||||
sys: sys,
|
||||
pwmPins: make(map[int]gobot.PWMPinner),
|
||||
PiBlasterPeriod: 10000000,
|
||||
}
|
||||
|
||||
r.DigitalPinsAdaptor = adaptors.NewDigitalPinsAdaptor(sys, r.getPinTranslatorFunction())
|
||||
return r
|
||||
}
|
||||
|
||||
@ -60,21 +61,19 @@ func (r *Adaptor) SetName(n string) {
|
||||
r.name = n
|
||||
}
|
||||
|
||||
// Connect do nothing at the moment
|
||||
func (r *Adaptor) Connect() error { return nil }
|
||||
// Connect create new connection to board and pins.
|
||||
func (r *Adaptor) Connect() error {
|
||||
err := r.DigitalPinsAdaptor.Connect()
|
||||
return err
|
||||
}
|
||||
|
||||
// Finalize closes connection to board and pins
|
||||
func (r *Adaptor) Finalize() (err error) {
|
||||
func (r *Adaptor) Finalize() error {
|
||||
r.mutex.Lock()
|
||||
defer r.mutex.Unlock()
|
||||
|
||||
for _, pin := range r.digitalPins {
|
||||
if pin != nil {
|
||||
if perr := pin.Unexport(); err != nil {
|
||||
err = multierror.Append(err, perr)
|
||||
}
|
||||
}
|
||||
}
|
||||
err := r.DigitalPinsAdaptor.Finalize()
|
||||
|
||||
for _, pin := range r.pwmPins {
|
||||
if pin != nil {
|
||||
if perr := pin.Unexport(); err != nil {
|
||||
@ -96,60 +95,7 @@ func (r *Adaptor) Finalize() (err error) {
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DigitalPin returns matched digitalPin for specified values
|
||||
func (r *Adaptor) DigitalPin(pin string, dir string) (gobot.DigitalPinner, error) {
|
||||
i, err := r.translatePin(pin)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
currentPin, err := r.getExportedDigitalPin(i, dir)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = currentPin.Direction(dir); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return currentPin, nil
|
||||
}
|
||||
|
||||
func (r *Adaptor) getExportedDigitalPin(translatedPin int, dir string) (gobot.DigitalPinner, error) {
|
||||
r.mutex.Lock()
|
||||
defer r.mutex.Unlock()
|
||||
|
||||
if r.digitalPins[translatedPin] == nil {
|
||||
r.digitalPins[translatedPin] = r.sys.NewDigitalPin(translatedPin)
|
||||
if err := r.digitalPins[translatedPin].Export(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return r.digitalPins[translatedPin], nil
|
||||
}
|
||||
|
||||
// DigitalRead reads digital value from pin
|
||||
func (r *Adaptor) DigitalRead(pin string) (val int, err error) {
|
||||
sysPin, err := r.DigitalPin(pin, system.IN)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return sysPin.Read()
|
||||
}
|
||||
|
||||
// DigitalWrite writes digital value to specified pin
|
||||
func (r *Adaptor) DigitalWrite(pin string, val byte) error {
|
||||
sysPin, err := r.DigitalPin(pin, system.OUT)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return sysPin.Write(int(val))
|
||||
return err
|
||||
}
|
||||
|
||||
// GetConnection returns an i2c connection to a device on a specified bus.
|
||||
@ -228,7 +174,8 @@ func (r *Adaptor) GetSpiDefaultMaxSpeed() int64 {
|
||||
|
||||
// PWMPin returns a raspi.PWMPin which provides the gobot.PWMPinner interface
|
||||
func (r *Adaptor) PWMPin(pin string) (gobot.PWMPinner, error) {
|
||||
i, err := r.translatePin(pin)
|
||||
tf := r.getPinTranslatorFunction()
|
||||
_, i, err := tf(pin)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -266,16 +213,20 @@ func (r *Adaptor) ServoWrite(pin string, angle byte) (err error) {
|
||||
return sysPin.SetDutyCycle(duty)
|
||||
}
|
||||
|
||||
func (r *Adaptor) translatePin(pin string) (i int, err error) {
|
||||
if val, ok := pins[pin][r.readRevision()]; ok {
|
||||
i = val
|
||||
} else if val, ok := pins[pin]["*"]; ok {
|
||||
i = val
|
||||
} else {
|
||||
err = errors.New("Not a valid pin")
|
||||
return
|
||||
func (r *Adaptor) getPinTranslatorFunction() func(string) (string, int, error) {
|
||||
return func(pin string) (chip string, line int, err error) {
|
||||
if val, ok := pins[pin][r.readRevision()]; ok {
|
||||
line = val
|
||||
} else if val, ok := pins[pin]["*"]; ok {
|
||||
line = val
|
||||
} else {
|
||||
err = errors.New("Not a valid pin")
|
||||
return
|
||||
}
|
||||
// TODO: Pi1 model B has only this single "gpiochip0", a change of the translator is needed,
|
||||
// to support different chips with different revisions
|
||||
return "gpiochip0", line, nil
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (r *Adaptor) readRevision() string {
|
||||
|
@ -32,6 +32,7 @@ var _ spi.Connector = (*Adaptor)(nil)
|
||||
func initTestAdaptorWithMockedFilesystem(mockPaths []string) (*Adaptor, *system.MockFilesystem) {
|
||||
a := NewAdaptor()
|
||||
fs := a.sys.UseMockFilesystem(mockPaths)
|
||||
a.Connect()
|
||||
return a, fs
|
||||
}
|
||||
|
||||
@ -227,7 +228,7 @@ func TestDigitalPinConcurrency(t *testing.T) {
|
||||
pinAsString := strconv.Itoa(i)
|
||||
go func(pin string) {
|
||||
defer wg.Done()
|
||||
a.DigitalPin(pin, system.IN)
|
||||
a.DigitalPin(pin)
|
||||
}(pinAsString)
|
||||
}
|
||||
|
||||
|
@ -39,7 +39,7 @@ type Adaptor struct {
|
||||
func NewAdaptor() *Adaptor {
|
||||
c := &Adaptor{
|
||||
name: gobot.DefaultName("Tinker Board"),
|
||||
sys: system.NewAccesser(),
|
||||
sys: system.NewAccesser("cdev"),
|
||||
}
|
||||
|
||||
c.setPins()
|
||||
@ -88,21 +88,36 @@ func (c *Adaptor) Finalize() (err error) {
|
||||
}
|
||||
|
||||
// DigitalRead reads digital value from the specified pin.
|
||||
func (c *Adaptor) DigitalRead(pin string) (val int, err error) {
|
||||
sysPin, err := c.DigitalPin(pin, system.IN)
|
||||
func (c *Adaptor) DigitalRead(id string) (int, error) {
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
|
||||
pin, err := c.digitalPin(id, system.WithDirectionInput())
|
||||
if err != nil {
|
||||
return
|
||||
return 0, err
|
||||
}
|
||||
return sysPin.Read()
|
||||
return pin.Read()
|
||||
}
|
||||
|
||||
// DigitalWrite writes digital value to the specified pin.
|
||||
func (c *Adaptor) DigitalWrite(pin string, val byte) (err error) {
|
||||
sysPin, err := c.DigitalPin(pin, system.OUT)
|
||||
func (c *Adaptor) DigitalWrite(id string, val byte) error {
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
|
||||
pin, err := c.digitalPin(id, system.WithDirectionOutput(int(val)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return sysPin.Write(int(val))
|
||||
return pin.Write(int(val))
|
||||
}
|
||||
|
||||
// DigitalPin returns a digital pin. If the pin is initially acquired, it is an input.
|
||||
// Pin direction and other options can be changed afterwards by pin.ApplyOptions() at any time.
|
||||
func (c *Adaptor) DigitalPin(id string) (gobot.DigitalPinner, error) {
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
|
||||
return c.digitalPin(id)
|
||||
}
|
||||
|
||||
// PwmWrite writes a PWM signal to the specified pin.
|
||||
@ -161,30 +176,6 @@ func (c *Adaptor) SetPeriod(pin string, period uint32) error {
|
||||
return setPeriod(pwmPin, period)
|
||||
}
|
||||
|
||||
// DigitalPin returns matched digitalPin for specified values.
|
||||
func (c *Adaptor) DigitalPin(pin string, dir string) (gobot.DigitalPinner, error) {
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
|
||||
i, err := c.translatePin(pin)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if c.digitalPins[pin] == nil {
|
||||
c.digitalPins[pin] = c.sys.NewDigitalPin(i)
|
||||
if err = c.digitalPins[pin].Export(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if err = c.digitalPins[pin].Direction(dir); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return c.digitalPins[pin], nil
|
||||
}
|
||||
|
||||
// PWMPin initializes the pin for PWM and returns matched pwmPin for specified pin number.
|
||||
// It implements the PWMPinnerProvider interface.
|
||||
func (c *Adaptor) PWMPin(pin string) (gobot.PWMPinner, error) {
|
||||
@ -310,12 +301,18 @@ func (c *Adaptor) setPins() {
|
||||
c.pwmPins = make(map[string]gobot.PWMPinner)
|
||||
}
|
||||
|
||||
func (c *Adaptor) translatePin(pin string) (sysPinNo int, err error) {
|
||||
sysPinNo, ok := gpioPinDefinitions[pin]
|
||||
func (c *Adaptor) translatePin(pin string) (chip string, line int, err error) {
|
||||
pindef, ok := gpioPinDefinitions[pin]
|
||||
if !ok {
|
||||
err = fmt.Errorf("Not a valid pin")
|
||||
return "", -1, fmt.Errorf("Not a valid pin")
|
||||
}
|
||||
return
|
||||
|
||||
if c.sys.IsSysfsDigitalPinAccess() {
|
||||
return "", pindef.sysfs, nil
|
||||
}
|
||||
chip = fmt.Sprintf("gpiochip%d", pindef.cdev.chip)
|
||||
line = int(pindef.cdev.line)
|
||||
return chip, line, nil
|
||||
}
|
||||
|
||||
func (c *Adaptor) translatePwmPin(pin string) (pwmPin pwmPinDefinition, err error) {
|
||||
@ -343,3 +340,25 @@ func (p pwmPinDefinition) findDir(sys system.Accesser) (dir string, err error) {
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Adaptor) digitalPin(id string, o ...func(gobot.DigitalPinOptioner) bool) (gobot.DigitalPinner, error) {
|
||||
chip, line, err := c.translatePin(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pin := c.digitalPins[id]
|
||||
if pin == nil {
|
||||
pin = c.sys.NewDigitalPin(chip, line, o...)
|
||||
if err = pin.Export(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.digitalPins[id] = pin
|
||||
} else {
|
||||
if err := pin.ApplyOptions(o...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return pin, nil
|
||||
}
|
||||
|
@ -1,34 +1,47 @@
|
||||
package tinkerboard
|
||||
|
||||
var gpioPinDefinitions = map[string]int{
|
||||
"7": 17, // GPIO0_C1_CLKOUT
|
||||
"10": 160, // GPIO5_B0_UART1RX
|
||||
"8": 161, // GPIO5_B1_UART1TX
|
||||
"16": 162, // GPIO5_B2_UART1CTSN
|
||||
"18": 163, // GPIO5_B3_UART1RTSN
|
||||
"11": 164, // GPIO5_B4_SPI0CLK_UART4CTSN
|
||||
"29": 165, // GPIO5_B5_SPI0CSN_UART4RTSN
|
||||
"13": 166, // GPIO5_B6_SPI0_TXD_UART4TX
|
||||
"15": 167, // GPIO5_B7_SPI0_RXD_UART4RX
|
||||
"31": 168, // GPIO5_C0_SPI0CSN1
|
||||
"22": 171, // GPIO5_C3
|
||||
"12": 184, // GPIO6_A0_PCM/I2S_CLK
|
||||
"35": 185, // GPIO6_A1_PCM/I2S_FS
|
||||
"38": 187, // GPIO6_A3_PCM/I2S_SDI
|
||||
"40": 188, // GPIO6_A4_PCM/I2S_SDO
|
||||
"36": 223, // GPIO7_A7_UART3RX
|
||||
"37": 224, // GPIO7_B0_UART3TX
|
||||
"27": 233, // GPIO7_C1_I2C4_SDA
|
||||
"28": 234, // GPIO7_C2_I2C_SCL
|
||||
"33": 238, // GPIO7_C6_UART2RX_PWM2
|
||||
"32": 239, // GPIO7_C7_UART2TX_PWM3
|
||||
"26": 251, // GPIO8_A3_SPI2CSN1
|
||||
"3": 252, // GPIO8_A4_I2C1_SDA
|
||||
"5": 253, // GPIO8_A5_I2C1_SCL
|
||||
"23": 254, // GPIO8_A6_SPI2CLK
|
||||
"24": 255, // GPIO8_A7_SPI2CSN0
|
||||
"21": 256, // GPIO8_B0_SPI2RXD
|
||||
"19": 257, // GPIO8_B1_SPI2TXD
|
||||
type cdevPin struct {
|
||||
chip uint8
|
||||
line uint8
|
||||
}
|
||||
|
||||
type gpioPinDefinition struct {
|
||||
sysfs int
|
||||
cdev cdevPin
|
||||
}
|
||||
|
||||
// notes for character device
|
||||
// pins: A=0+Nr, B=8+Nr, C=16+Nr
|
||||
// tested: armbian Linux, OK: work as input and output, IN: work only as input
|
||||
var gpioPinDefinitions = map[string]gpioPinDefinition{
|
||||
"7": gpioPinDefinition{sysfs: 17, cdev: cdevPin{chip: 0, line: 17}}, // GPIO0_C1_CLKOUT - OK
|
||||
"10": gpioPinDefinition{sysfs: 160, cdev: cdevPin{chip: 5, line: 8}}, // GPIO5_B0_UART1RX - IN, initial 1
|
||||
"8": gpioPinDefinition{sysfs: 161, cdev: cdevPin{chip: 5, line: 9}}, // GPIO5_B1_UART1TX - NO, initial 1
|
||||
"16": gpioPinDefinition{sysfs: 162, cdev: cdevPin{chip: 5, line: 10}}, // GPIO5_B2_UART1CTSN - NO, initial 0
|
||||
"18": gpioPinDefinition{sysfs: 163, cdev: cdevPin{chip: 5, line: 11}}, // GPIO5_B3_UART1RTSN - NO, initial 0
|
||||
"11": gpioPinDefinition{sysfs: 164, cdev: cdevPin{chip: 5, line: 12}}, // GPIO5_B4_SPI0CLK_UART4CTSN - NO, initial 0
|
||||
"29": gpioPinDefinition{sysfs: 165, cdev: cdevPin{chip: 5, line: 13}}, // GPIO5_B5_SPI0CSN_UART4RTSN - NO, initial 0
|
||||
"13": gpioPinDefinition{sysfs: 166, cdev: cdevPin{chip: 5, line: 14}}, // GPIO5_B6_SPI0_TXD_UART4TX - NO, initial 1
|
||||
"15": gpioPinDefinition{sysfs: 167, cdev: cdevPin{chip: 5, line: 15}}, // GPIO5_B7_SPI0_RXD_UART4RX - IN, initial 1
|
||||
"31": gpioPinDefinition{sysfs: 168, cdev: cdevPin{chip: 5, line: 16}}, // GPIO5_C0_SPI0CSN1 - OK
|
||||
"22": gpioPinDefinition{sysfs: 171, cdev: cdevPin{chip: 5, line: 19}}, // GPIO5_C3 - OK
|
||||
"12": gpioPinDefinition{sysfs: 184, cdev: cdevPin{chip: 6, line: 0}}, // GPIO6_A0_PCM/I2S_CLK - NO, initial 1
|
||||
"35": gpioPinDefinition{sysfs: 185, cdev: cdevPin{chip: 6, line: 1}}, // GPIO6_A1_PCM/I2S_FS - NO, initial 0
|
||||
"38": gpioPinDefinition{sysfs: 187, cdev: cdevPin{chip: 6, line: 3}}, // GPIO6_A3_PCM/I2S_SDI - IN, initial 1
|
||||
"40": gpioPinDefinition{sysfs: 188, cdev: cdevPin{chip: 6, line: 4}}, // GPIO6_A4_PCM/I2S_SDO - NO, initial 0
|
||||
"36": gpioPinDefinition{sysfs: 223, cdev: cdevPin{chip: 7, line: 7}}, // GPIO7_A7_UART3RX - IN, initial 1
|
||||
"37": gpioPinDefinition{sysfs: 224, cdev: cdevPin{chip: 7, line: 8}}, // GPIO7_B0_UART3TX - NO, initial 1
|
||||
"27": gpioPinDefinition{sysfs: 233, cdev: cdevPin{chip: 7, line: 17}}, // GPIO7_C1_I2C4_SDA - OK if I2C4 off
|
||||
"28": gpioPinDefinition{sysfs: 234, cdev: cdevPin{chip: 7, line: 18}}, // GPIO7_C2_I2C_SCL - OK if I2C4 off
|
||||
"33": gpioPinDefinition{sysfs: 238, cdev: cdevPin{chip: 7, line: 22}}, // GPIO7_C6_UART2RX_PWM2 - IN, initial 1
|
||||
"32": gpioPinDefinition{sysfs: 239, cdev: cdevPin{chip: 7, line: 23}}, // GPIO7_C7_UART2TX_PWM3 - NO, initial 1
|
||||
"26": gpioPinDefinition{sysfs: 251, cdev: cdevPin{chip: 8, line: 3}}, // GPIO8_A3_SPI2CSN1 - OK
|
||||
"3": gpioPinDefinition{sysfs: 252, cdev: cdevPin{chip: 8, line: 4}}, // GPIO8_A4_I2C1_SDA - OK if I2C1 off
|
||||
"5": gpioPinDefinition{sysfs: 253, cdev: cdevPin{chip: 8, line: 5}}, // GPIO8_A5_I2C1_SCL - OK if I2C1 off
|
||||
"23": gpioPinDefinition{sysfs: 254, cdev: cdevPin{chip: 8, line: 6}}, // GPIO8_A6_SPI2CLK - OK
|
||||
"24": gpioPinDefinition{sysfs: 255, cdev: cdevPin{chip: 8, line: 7}}, // GPIO8_A7_SPI2CSN0 - OK
|
||||
"21": gpioPinDefinition{sysfs: 256, cdev: cdevPin{chip: 8, line: 8}}, // GPIO8_B0_SPI2RXD - OK
|
||||
"19": gpioPinDefinition{sysfs: 257, cdev: cdevPin{chip: 8, line: 9}}, // GPIO8_B1_SPI2TXD - OK
|
||||
}
|
||||
|
||||
var pwmPinDefinitions = map[string]pwmPinDefinition{
|
||||
|
@ -112,33 +112,48 @@ func (c *Adaptor) Finalize() (err error) {
|
||||
}
|
||||
|
||||
// DigitalRead reads digital value from the specified pin.
|
||||
func (c *Adaptor) DigitalRead(pin string) (val int, err error) {
|
||||
sysPin, err := c.DigitalPin(pin, system.IN)
|
||||
func (c *Adaptor) DigitalRead(id string) (int, error) {
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
|
||||
pin, err := c.digitalPin(id, system.WithDirectionInput())
|
||||
if err != nil {
|
||||
return
|
||||
return 0, err
|
||||
}
|
||||
return sysPin.Read()
|
||||
return pin.Read()
|
||||
}
|
||||
|
||||
// DigitalWrite writes digital value to the specified pin.
|
||||
func (c *Adaptor) DigitalWrite(pin string, val byte) (err error) {
|
||||
func (c *Adaptor) DigitalWrite(id string, val byte) error {
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
|
||||
// is it one of the built-in LEDs?
|
||||
if pin == LEDRed || pin == LEDBlue || pin == LEDGreen || pin == LEDYellow {
|
||||
pinPath := fmt.Sprintf(c.ledPath, pin)
|
||||
if id == LEDRed || id == LEDBlue || id == LEDGreen || id == LEDYellow {
|
||||
pinPath := fmt.Sprintf(c.ledPath, id)
|
||||
fi, e := c.sys.OpenFile(pinPath, os.O_WRONLY|os.O_APPEND, 0666)
|
||||
defer fi.Close()
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
_, err = fi.WriteString(strconv.Itoa(int(val)))
|
||||
_, err := fi.WriteString(strconv.Itoa(int(val)))
|
||||
return err
|
||||
}
|
||||
// one of the normal GPIO pins, then
|
||||
sysPin, err := c.DigitalPin(pin, system.OUT)
|
||||
|
||||
pin, err := c.digitalPin(id, system.WithDirectionOutput(int(val)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return sysPin.Write(int(val))
|
||||
return pin.Write(int(val))
|
||||
}
|
||||
|
||||
// DigitalPin returns a digital pin. If the pin is initially acquired, it is an input.
|
||||
// Pin direction and other options can be changed afterwards by pin.ApplyOptions() at any time.
|
||||
func (c *Adaptor) DigitalPin(id string) (gobot.DigitalPinner, error) {
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
|
||||
return c.digitalPin(id)
|
||||
}
|
||||
|
||||
// PwmWrite writes a PWM signal to the specified pin
|
||||
@ -171,29 +186,26 @@ func (c *Adaptor) ServoWrite(pin string, angle byte) (err error) {
|
||||
return pwmPin.SetDutyCycle(duty)
|
||||
}
|
||||
|
||||
// DigitalPin returns matched digitalPin for specified values
|
||||
func (c *Adaptor) DigitalPin(pin string, dir string) (gobot.DigitalPinner, error) {
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
|
||||
i, err := c.translatePin(pin)
|
||||
|
||||
func (c *Adaptor) digitalPin(id string, o ...func(gobot.DigitalPinOptioner) bool) (gobot.DigitalPinner, error) {
|
||||
i, err := c.translatePin(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if c.digitalPins[i] == nil {
|
||||
c.digitalPins[i] = c.sys.NewDigitalPin(i)
|
||||
if err = c.digitalPins[i].Export(); err != nil {
|
||||
pin := c.digitalPins[i]
|
||||
if pin == nil {
|
||||
pin = c.sys.NewDigitalPin("", i, o...)
|
||||
if err = pin.Export(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.digitalPins[i] = pin
|
||||
} else {
|
||||
if err := pin.ApplyOptions(o...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if err = c.digitalPins[i].Direction(dir); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return c.digitalPins[i], nil
|
||||
return pin, nil
|
||||
}
|
||||
|
||||
// PWMPin returns matched pwmPin for specified pin number
|
||||
|
@ -1,4 +1,4 @@
|
||||
# sysfs
|
||||
# system
|
||||
|
||||
This document describes some basics for developers.
|
||||
|
||||
|
50
system/digitalpin_access.go
Normal file
50
system/digitalpin_access.go
Normal file
@ -0,0 +1,50 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"gobot.io/x/gobot"
|
||||
)
|
||||
|
||||
// sysfsDitalPinHandler represents the sysfs implementation
|
||||
type sysfsDigitalPinAccess struct {
|
||||
fs filesystem
|
||||
}
|
||||
|
||||
// gpiodDigitalPinAccess represents the character device implementation
|
||||
type gpiodDigitalPinAccess struct {
|
||||
fs filesystem
|
||||
chips []string
|
||||
}
|
||||
|
||||
func (h *sysfsDigitalPinAccess) isSupported() bool {
|
||||
// currently this is supported by all Kernels
|
||||
return true
|
||||
}
|
||||
|
||||
func (h *sysfsDigitalPinAccess) createPin(chip string, pin int,
|
||||
o ...func(gobot.DigitalPinOptioner) bool) gobot.DigitalPinner {
|
||||
return newDigitalPinSysfs(h.fs, strconv.Itoa(pin), o...)
|
||||
}
|
||||
|
||||
func (h *sysfsDigitalPinAccess) setFs(fs filesystem) {
|
||||
h.fs = fs
|
||||
}
|
||||
|
||||
func (h *gpiodDigitalPinAccess) isSupported() bool {
|
||||
chips, err := h.fs.find("/dev", "gpiochip")
|
||||
if err != nil || len(chips) == 0 {
|
||||
return false
|
||||
}
|
||||
h.chips = chips
|
||||
return true
|
||||
}
|
||||
|
||||
func (h *gpiodDigitalPinAccess) createPin(chip string, pin int,
|
||||
o ...func(gobot.DigitalPinOptioner) bool) gobot.DigitalPinner {
|
||||
return newDigitalPinGpiod(chip, pin, o...)
|
||||
}
|
||||
|
||||
func (h *gpiodDigitalPinAccess) setFs(fs filesystem) {
|
||||
h.fs = fs
|
||||
}
|
102
system/digitalpin_access_test.go
Normal file
102
system/digitalpin_access_test.go
Normal file
@ -0,0 +1,102 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"gobot.io/x/gobot/gobottest"
|
||||
)
|
||||
|
||||
func Test_isSupportedSysfs(t *testing.T) {
|
||||
// arrange
|
||||
dpa := sysfsDigitalPinAccess{}
|
||||
// act
|
||||
got := dpa.isSupported()
|
||||
// assert
|
||||
gobottest.Assert(t, got, true)
|
||||
}
|
||||
|
||||
func Test_isSupportedGpiod(t *testing.T) {
|
||||
var tests = map[string]struct {
|
||||
mockPaths []string
|
||||
want bool
|
||||
}{
|
||||
"supported": {
|
||||
mockPaths: []string{"/sys/class/gpio/", "/dev/gpiochip3"},
|
||||
want: true,
|
||||
},
|
||||
"not_supported": {
|
||||
mockPaths: []string{"/sys/class/gpio/"},
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
for name, tc := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
// arrange
|
||||
fs := newMockFilesystem(tc.mockPaths)
|
||||
dpa := gpiodDigitalPinAccess{fs: fs}
|
||||
// act
|
||||
got := dpa.isSupported()
|
||||
// assert
|
||||
gobottest.Assert(t, got, tc.want)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_createAsSysfs(t *testing.T) {
|
||||
// arrange
|
||||
dpa := sysfsDigitalPinAccess{}
|
||||
// act
|
||||
dp := dpa.createPin("chip", 8)
|
||||
// assert
|
||||
gobottest.Refute(t, dp, nil)
|
||||
dps := dp.(*digitalPinSysfs)
|
||||
// chip is dropped
|
||||
gobottest.Assert(t, dps.label, "gpio8")
|
||||
}
|
||||
|
||||
func Test_createAsGpiod(t *testing.T) {
|
||||
// arrange
|
||||
const (
|
||||
pin = 18
|
||||
label = "gobotio18"
|
||||
chip = "gpiochip1"
|
||||
)
|
||||
dpa := gpiodDigitalPinAccess{}
|
||||
// act
|
||||
dp := dpa.createPin(chip, 18)
|
||||
// assert
|
||||
gobottest.Refute(t, dp, nil)
|
||||
dpg := dp.(*digitalPinGpiod)
|
||||
gobottest.Assert(t, dpg.label, label)
|
||||
gobottest.Assert(t, dpg.chipName, chip)
|
||||
}
|
||||
|
||||
func Test_createPinWithOptionsSysfs(t *testing.T) {
|
||||
// This is a general test, that options are applied by using "create" with the WithLabel() option.
|
||||
// All other configuration options will be tested in tests for "digitalPinConfig".
|
||||
//
|
||||
// arrange
|
||||
const label = "my sysfs label"
|
||||
dpa := sysfsDigitalPinAccess{}
|
||||
// act
|
||||
dp := dpa.createPin("", 9, WithLabel(label))
|
||||
dps := dp.(*digitalPinSysfs)
|
||||
// assert
|
||||
gobottest.Assert(t, dps.label, label)
|
||||
}
|
||||
|
||||
func Test_createPinWithOptionsGpiod(t *testing.T) {
|
||||
// This is a general test, that options are applied by using "create" with the WithLabel() option.
|
||||
// All other configuration options will be tested in tests for "digitalPinConfig".
|
||||
//
|
||||
// arrange
|
||||
const label = "my gpiod label"
|
||||
dpa := gpiodDigitalPinAccess{}
|
||||
// act
|
||||
dp := dpa.createPin("", 19, WithLabel(label))
|
||||
dpg := dp.(*digitalPinGpiod)
|
||||
// assert
|
||||
gobottest.Assert(t, dpg.label, label)
|
||||
// test fallback for empty chip
|
||||
gobottest.Assert(t, dpg.chipName, "gpiochip0")
|
||||
}
|
@ -14,7 +14,7 @@ func BenchmarkDigitalRead(b *testing.B) {
|
||||
}
|
||||
a.UseMockFilesystem(mockPaths)
|
||||
|
||||
pin := a.NewDigitalPin(10)
|
||||
pin := a.NewDigitalPin("", 10)
|
||||
pin.Write(1)
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
|
77
system/digitalpin_config.go
Normal file
77
system/digitalpin_config.go
Normal file
@ -0,0 +1,77 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"gobot.io/x/gobot"
|
||||
)
|
||||
|
||||
const (
|
||||
// IN gpio direction
|
||||
IN = "in"
|
||||
// OUT gpio direction
|
||||
OUT = "out"
|
||||
// HIGH gpio level
|
||||
HIGH = 1
|
||||
// LOW gpio level
|
||||
LOW = 0
|
||||
)
|
||||
|
||||
type digitalPinConfig struct {
|
||||
label string
|
||||
direction string
|
||||
outInitialState int
|
||||
}
|
||||
|
||||
func newDigitalPinConfig(label string, options ...func(gobot.DigitalPinOptioner) bool) *digitalPinConfig {
|
||||
cfg := &digitalPinConfig{
|
||||
label: label,
|
||||
direction: IN,
|
||||
}
|
||||
for _, option := range options {
|
||||
option(cfg)
|
||||
}
|
||||
return cfg
|
||||
}
|
||||
|
||||
// WithLabel use a pin label, which will replace the default label "gobotio#".
|
||||
func WithLabel(label string) func(gobot.DigitalPinOptioner) bool {
|
||||
return func(d gobot.DigitalPinOptioner) bool { return d.SetLabel(label) }
|
||||
}
|
||||
|
||||
// WithDirectionOutput initializes the pin as output instead of the default "input".
|
||||
func WithDirectionOutput(initial int) func(gobot.DigitalPinOptioner) bool {
|
||||
return func(d gobot.DigitalPinOptioner) bool { return d.SetDirectionOutput(initial) }
|
||||
}
|
||||
|
||||
// WithDirectionInput initializes the pin as input.
|
||||
func WithDirectionInput() func(gobot.DigitalPinOptioner) bool {
|
||||
return func(d gobot.DigitalPinOptioner) bool { return d.SetDirectionInput() }
|
||||
}
|
||||
|
||||
// SetLabel sets the label to use for next reconfigure. The function is intended to use by WithLabel().
|
||||
func (d *digitalPinConfig) SetLabel(label string) bool {
|
||||
if d.label == label {
|
||||
return false
|
||||
}
|
||||
d.label = label
|
||||
return true
|
||||
}
|
||||
|
||||
// SetDirectionOutput sets the direction to output for next reconfigure. The function is intended to use by WithLabel().
|
||||
func (d *digitalPinConfig) SetDirectionOutput(initial int) bool {
|
||||
if d.direction == OUT {
|
||||
// in this case also the initial value will not be written
|
||||
return false
|
||||
}
|
||||
d.direction = OUT
|
||||
d.outInitialState = initial
|
||||
return true
|
||||
}
|
||||
|
||||
// SetDirectionInput sets the direction to input for next reconfigure. The function is intended to use by WithLabel().
|
||||
func (d *digitalPinConfig) SetDirectionInput() bool {
|
||||
if d.direction == IN {
|
||||
return false
|
||||
}
|
||||
d.direction = IN
|
||||
return true
|
||||
}
|
117
system/digitalpin_config_test.go
Normal file
117
system/digitalpin_config_test.go
Normal file
@ -0,0 +1,117 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"gobot.io/x/gobot"
|
||||
"gobot.io/x/gobot/gobottest"
|
||||
)
|
||||
|
||||
var _ gobot.DigitalPinOptioner = (*digitalPinConfig)(nil)
|
||||
|
||||
func Test_newDigitalPinConfig(t *testing.T) {
|
||||
// arrange
|
||||
const (
|
||||
label = "gobotio17"
|
||||
)
|
||||
// act
|
||||
d := newDigitalPinConfig(label)
|
||||
// assert
|
||||
gobottest.Refute(t, d, nil)
|
||||
gobottest.Assert(t, d.label, label)
|
||||
gobottest.Assert(t, d.direction, IN)
|
||||
gobottest.Assert(t, d.outInitialState, 0)
|
||||
}
|
||||
|
||||
func TestWithLabel(t *testing.T) {
|
||||
const (
|
||||
oldLabel = "old label"
|
||||
newLabel = "my optional label"
|
||||
)
|
||||
var tests = map[string]struct {
|
||||
setLabel string
|
||||
want bool
|
||||
}{
|
||||
"no_change": {
|
||||
setLabel: oldLabel,
|
||||
},
|
||||
"change": {
|
||||
setLabel: newLabel,
|
||||
want: true,
|
||||
},
|
||||
}
|
||||
for name, tc := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
// arrange
|
||||
dpc := &digitalPinConfig{label: oldLabel}
|
||||
// act
|
||||
got := WithLabel(tc.setLabel)(dpc)
|
||||
// assert
|
||||
gobottest.Assert(t, got, tc.want)
|
||||
gobottest.Assert(t, dpc.label, tc.setLabel)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestWithDirectionOutput(t *testing.T) {
|
||||
const (
|
||||
// values other than 0, 1 are normally not useful, just to test
|
||||
oldVal = 3
|
||||
newVal = 5
|
||||
)
|
||||
var tests = map[string]struct {
|
||||
oldDir string
|
||||
want bool
|
||||
wantVal int
|
||||
}{
|
||||
"no_change": {
|
||||
oldDir: "out",
|
||||
wantVal: oldVal,
|
||||
},
|
||||
"change": {
|
||||
oldDir: "in",
|
||||
want: true,
|
||||
wantVal: newVal,
|
||||
},
|
||||
}
|
||||
for name, tc := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
// arrange
|
||||
dpc := &digitalPinConfig{direction: tc.oldDir, outInitialState: oldVal}
|
||||
// act
|
||||
got := WithDirectionOutput(newVal)(dpc)
|
||||
// assert
|
||||
gobottest.Assert(t, got, tc.want)
|
||||
gobottest.Assert(t, dpc.direction, "out")
|
||||
gobottest.Assert(t, dpc.outInitialState, tc.wantVal)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestWithDirectionInput(t *testing.T) {
|
||||
var tests = map[string]struct {
|
||||
oldDir string
|
||||
want bool
|
||||
}{
|
||||
"no_change": {
|
||||
oldDir: "in",
|
||||
},
|
||||
"change": {
|
||||
oldDir: "out",
|
||||
want: true,
|
||||
},
|
||||
}
|
||||
for name, tc := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
// arrange
|
||||
const initValOut = 2 // 2 is normally not useful, just to test that is not touched
|
||||
dpc := &digitalPinConfig{direction: tc.oldDir, outInitialState: initValOut}
|
||||
// act
|
||||
got := WithDirectionInput()(dpc)
|
||||
// assert
|
||||
gobottest.Assert(t, got, tc.want)
|
||||
gobottest.Assert(t, dpc.direction, "in")
|
||||
gobottest.Assert(t, dpc.outInitialState, initValOut)
|
||||
})
|
||||
}
|
||||
}
|
224
system/digitalpin_gpiod.go
Normal file
224
system/digitalpin_gpiod.go
Normal file
@ -0,0 +1,224 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/warthog618/gpiod"
|
||||
"gobot.io/x/gobot"
|
||||
)
|
||||
|
||||
const systemGpiodDebug = true
|
||||
|
||||
type cdevLine interface {
|
||||
SetValue(value int) error
|
||||
Value() (int, error)
|
||||
Close() error
|
||||
}
|
||||
|
||||
type digitalPinGpiod struct {
|
||||
chipName string
|
||||
pin int
|
||||
*digitalPinConfig
|
||||
line cdevLine
|
||||
}
|
||||
|
||||
var used = map[bool]string{true: "used", false: "unused"}
|
||||
var activeLow = map[bool]string{true: "low", false: "high"}
|
||||
var debounced = map[bool]string{true: "debounced", false: "not debounced"}
|
||||
|
||||
var direction = map[gpiod.LineDirection]string{gpiod.LineDirectionUnknown: "unknown direction",
|
||||
gpiod.LineDirectionInput: "input", gpiod.LineDirectionOutput: "output"}
|
||||
|
||||
var drive = map[gpiod.LineDrive]string{gpiod.LineDrivePushPull: "push-pull", gpiod.LineDriveOpenDrain: "open-drain",
|
||||
gpiod.LineDriveOpenSource: "open-source"}
|
||||
|
||||
var bias = map[gpiod.LineBias]string{gpiod.LineBiasUnknown: "unknown", gpiod.LineBiasDisabled: "disabled",
|
||||
gpiod.LineBiasPullUp: "pull-up", gpiod.LineBiasPullDown: "pull-down"}
|
||||
|
||||
var edgeDetect = map[gpiod.LineEdge]string{gpiod.LineEdgeNone: "no", gpiod.LineEdgeRising: "rising",
|
||||
gpiod.LineEdgeFalling: "falling", gpiod.LineEdgeBoth: "both"}
|
||||
|
||||
var eventClock = map[gpiod.LineEventClock]string{gpiod.LineEventClockMonotonic: "monotonic",
|
||||
gpiod.LineEventClockRealtime: "realtime"}
|
||||
|
||||
// newDigitalPinGpiod returns a digital pin given the pin number, with the label "gobotio" followed by the pin number.
|
||||
// The pin label can be modified optionally. The pin is handled by the character device Kernel ABI.
|
||||
func newDigitalPinGpiod(chipName string, pin int, options ...func(gobot.DigitalPinOptioner) bool) *digitalPinGpiod {
|
||||
if chipName == "" {
|
||||
chipName = "gpiochip0"
|
||||
}
|
||||
cfg := newDigitalPinConfig("gobotio"+strconv.Itoa(int(pin)), options...)
|
||||
d := &digitalPinGpiod{
|
||||
chipName: chipName,
|
||||
pin: pin,
|
||||
digitalPinConfig: cfg,
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
// ApplyOptions apply all given options to the pin immediately. Implements interface gobot.DigitalPinOptionApplier.
|
||||
func (d *digitalPinGpiod) ApplyOptions(options ...func(gobot.DigitalPinOptioner) bool) error {
|
||||
anyChange := false
|
||||
for _, option := range options {
|
||||
anyChange = anyChange || option(d)
|
||||
}
|
||||
if anyChange {
|
||||
return d.reconfigure(false)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DirectionBehavior gets the direction behavior when the pin is used the next time. This means its possibly not in
|
||||
// this direction type at the moment. Implements the interface gobot.DigitalPinValuer, but should be rarely used.
|
||||
func (d *digitalPinGpiod) DirectionBehavior() string {
|
||||
return d.direction
|
||||
}
|
||||
|
||||
// Export sets the pin as used by this driver. Implements the interface gobot.DigitalPinner.
|
||||
func (d *digitalPinGpiod) Export() error {
|
||||
err := d.reconfigure(false)
|
||||
if err != nil {
|
||||
return fmt.Errorf("gpiod.Export(): %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Unexport releases the pin as input. Implements the interface gobot.DigitalPinner.
|
||||
func (d *digitalPinGpiod) Unexport() error {
|
||||
var errs []string
|
||||
if d.line != nil {
|
||||
if err := d.reconfigure(true); err != nil {
|
||||
errs = append(errs, err.Error())
|
||||
}
|
||||
if err := d.line.Close(); err != nil {
|
||||
err = fmt.Errorf("gpiod.Unexport()-line.Close(): %v", err)
|
||||
errs = append(errs, err.Error())
|
||||
}
|
||||
}
|
||||
if len(errs) == 0 {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf(strings.Join(errs, ","))
|
||||
}
|
||||
|
||||
// Write writes the given value to the character device. Implements the interface gobot.DigitalPinner.
|
||||
func (d *digitalPinGpiod) Write(val int) error {
|
||||
if val < 0 {
|
||||
val = 0
|
||||
}
|
||||
if val > 1 {
|
||||
val = 1
|
||||
}
|
||||
|
||||
err := d.line.SetValue(val)
|
||||
if err != nil {
|
||||
return fmt.Errorf("gpiod.Write(): %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Read reads the given value from character device. Implements the interface gobot.DigitalPinner.
|
||||
func (d *digitalPinGpiod) Read() (int, error) {
|
||||
val, err := d.line.Value()
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("gpiod.Read(): %v", err)
|
||||
}
|
||||
return val, err
|
||||
}
|
||||
|
||||
// ListLines is used for development purposes.
|
||||
func (d *digitalPinGpiod) ListLines() error {
|
||||
c, err := gpiod.NewChip(d.chipName, gpiod.WithConsumer(d.label))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i := 0; i < c.Lines(); i++ {
|
||||
li, err := c.LineInfo(i)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(fmtLine(li))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// List is used for development purposes.
|
||||
func (d *digitalPinGpiod) List() error {
|
||||
c, err := gpiod.NewChip(d.chipName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer c.Close()
|
||||
l, err := c.RequestLine(d.pin)
|
||||
if err != nil && l != nil {
|
||||
l.Close()
|
||||
l = nil
|
||||
}
|
||||
li, err := l.Info()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(fmtLine(li))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *digitalPinGpiod) reconfigure(forceInput bool) error {
|
||||
// cleanup old line
|
||||
if d.line != nil {
|
||||
d.line.Close()
|
||||
}
|
||||
d.line = nil
|
||||
|
||||
// acquire chip, temporary
|
||||
// the given label is applied to all lines, which are requested on the chip
|
||||
gpiodChip, err := gpiod.NewChip(d.chipName, gpiod.WithConsumer(d.label))
|
||||
id := fmt.Sprintf("%s-%d", d.chipName, d.pin)
|
||||
if err != nil {
|
||||
return fmt.Errorf("gpiod.reconfigure(%s)-lib.NewChip(%s): %v", id, d.chipName, err)
|
||||
}
|
||||
defer gpiodChip.Close()
|
||||
|
||||
// acquire line
|
||||
gpiodLine, err := gpiodChip.RequestLine(d.pin)
|
||||
if err != nil {
|
||||
if gpiodLine != nil {
|
||||
gpiodLine.Close()
|
||||
}
|
||||
d.line = nil
|
||||
|
||||
return fmt.Errorf("gpiod.reconfigure(%s)-c.RequestLine(%d): %v", id, d.pin, err)
|
||||
}
|
||||
d.line = gpiodLine
|
||||
|
||||
// configure line
|
||||
if d.direction == IN || forceInput {
|
||||
if err := gpiodLine.Reconfigure(gpiod.AsInput); err != nil {
|
||||
return fmt.Errorf("gpiod.reconfigure(%s)-l.Reconfigure(gpiod.AsInput): %v", id, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := gpiodLine.Reconfigure(gpiod.AsOutput(d.outInitialState)); err != nil {
|
||||
return fmt.Errorf("gpiod.reconfigure(%s)-l.Reconfigure(gpiod.AsOutput(%d)): %v", id, d.outInitialState, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func fmtLine(li gpiod.LineInfo) string {
|
||||
var consumer string
|
||||
if li.Consumer != "" {
|
||||
consumer = fmt.Sprintf(" by '%s'", li.Consumer)
|
||||
}
|
||||
return fmt.Sprintf("++ Info line %d '%s', %s%s ++\n Config: %s\n",
|
||||
li.Offset, li.Name, used[li.Used], consumer, fmtLineConfig(li.Config))
|
||||
}
|
||||
|
||||
func fmtLineConfig(cfg gpiod.LineConfig) string {
|
||||
t := "active-%s, %s, %s, %s bias, %s edge detect, %s, debounce-period: %v, %s event clock"
|
||||
return fmt.Sprintf(t, activeLow[cfg.ActiveLow], direction[cfg.Direction], drive[cfg.Drive], bias[cfg.Bias],
|
||||
edgeDetect[cfg.EdgeDetection], debounced[cfg.Debounced], cfg.DebouncePeriod, eventClock[cfg.EventClock])
|
||||
}
|
161
system/digitalpin_gpiod_test.go
Normal file
161
system/digitalpin_gpiod_test.go
Normal file
@ -0,0 +1,161 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"gobot.io/x/gobot"
|
||||
"gobot.io/x/gobot/gobottest"
|
||||
)
|
||||
|
||||
var _ gobot.DigitalPinner = (*digitalPinGpiod)(nil)
|
||||
var _ gobot.DigitalPinValuer = (*digitalPinGpiod)(nil)
|
||||
var _ gobot.DigitalPinOptioner = (*digitalPinGpiod)(nil)
|
||||
var _ gobot.DigitalPinOptionApplier = (*digitalPinGpiod)(nil)
|
||||
|
||||
func Test_newDigitalPinGpiod(t *testing.T) {
|
||||
// arrange
|
||||
const (
|
||||
chip = "gpiochip0"
|
||||
pin = 17
|
||||
label = "gobotio17"
|
||||
)
|
||||
// act
|
||||
d := newDigitalPinGpiod(chip, pin)
|
||||
// assert
|
||||
gobottest.Refute(t, d, nil)
|
||||
gobottest.Assert(t, d.chipName, chip)
|
||||
gobottest.Assert(t, d.pin, pin)
|
||||
gobottest.Assert(t, d.label, label)
|
||||
gobottest.Assert(t, d.direction, IN)
|
||||
gobottest.Assert(t, d.outInitialState, 0)
|
||||
}
|
||||
|
||||
func Test_newDigitalPinGpiodWithOptions(t *testing.T) {
|
||||
// This is a general test, that options are applied by using "newDigitalPinGpiod" with the WithLabel() option.
|
||||
// All other configuration options will be tested in tests for "digitalPinConfig".
|
||||
//
|
||||
// arrange
|
||||
const label = "my own label"
|
||||
// act
|
||||
dp := newDigitalPinGpiod("", 9, WithLabel(label))
|
||||
// assert
|
||||
gobottest.Assert(t, dp.label, label)
|
||||
}
|
||||
|
||||
func TestApplyOptions(t *testing.T) {
|
||||
// currently the gpiod.Chip has no interface for RequestLine(),
|
||||
// so we can only test without trigger of reconfigure
|
||||
// arrange
|
||||
d := &digitalPinGpiod{digitalPinConfig: &digitalPinConfig{direction: "in"}}
|
||||
// act
|
||||
d.ApplyOptions(WithDirectionInput())
|
||||
// assert
|
||||
gobottest.Assert(t, d.digitalPinConfig.direction, "in")
|
||||
}
|
||||
|
||||
func TestUnexport(t *testing.T) {
|
||||
// currently the gpiod.Chip has no interface for RequestLine(),
|
||||
// so we can only test without trigger of reconfigure
|
||||
// arrange
|
||||
dp := newDigitalPinGpiod("", 4)
|
||||
dp.line = nil // ensures no reconfigure
|
||||
// act
|
||||
err := dp.Unexport()
|
||||
// assert
|
||||
gobottest.Assert(t, err, nil)
|
||||
}
|
||||
|
||||
func TestWrite(t *testing.T) {
|
||||
var tests = map[string]struct {
|
||||
val int
|
||||
simErr error
|
||||
want int
|
||||
wantErr []string
|
||||
}{
|
||||
"write_zero": {
|
||||
val: 0,
|
||||
want: 0,
|
||||
},
|
||||
"write_one": {
|
||||
val: 1,
|
||||
want: 1,
|
||||
},
|
||||
"write_minus_one": {
|
||||
val: -1,
|
||||
want: 0,
|
||||
},
|
||||
"write_two": {
|
||||
val: 2,
|
||||
want: 1,
|
||||
},
|
||||
"write_with_err": {
|
||||
simErr: fmt.Errorf("a write err"),
|
||||
wantErr: []string{"a write err", "gpiod.Write"},
|
||||
},
|
||||
}
|
||||
for name, tc := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
// arrange
|
||||
dp := newDigitalPinGpiod("", 4)
|
||||
lm := &lineMock{lastVal: 10, simErr: tc.simErr}
|
||||
dp.line = lm
|
||||
// act
|
||||
err := dp.Write(tc.val)
|
||||
// assert
|
||||
if tc.wantErr != nil {
|
||||
for _, want := range tc.wantErr {
|
||||
gobottest.Assert(t, strings.Contains(err.Error(), want), true)
|
||||
}
|
||||
} else {
|
||||
gobottest.Assert(t, err, nil)
|
||||
}
|
||||
gobottest.Assert(t, lm.lastVal, tc.want)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRead(t *testing.T) {
|
||||
var tests = map[string]struct {
|
||||
simVal int
|
||||
simErr error
|
||||
wantErr []string
|
||||
}{
|
||||
"read_ok": {
|
||||
simVal: 3,
|
||||
},
|
||||
"write_with_err": {
|
||||
simErr: fmt.Errorf("a read err"),
|
||||
wantErr: []string{"a read err", "gpiod.Read"},
|
||||
},
|
||||
}
|
||||
for name, tc := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
// arrange
|
||||
dp := newDigitalPinGpiod("", 4)
|
||||
lm := &lineMock{lastVal: tc.simVal, simErr: tc.simErr}
|
||||
dp.line = lm
|
||||
// act
|
||||
got, err := dp.Read()
|
||||
// assert
|
||||
if tc.wantErr != nil {
|
||||
for _, want := range tc.wantErr {
|
||||
gobottest.Assert(t, strings.Contains(err.Error(), want), true)
|
||||
}
|
||||
} else {
|
||||
gobottest.Assert(t, err, nil)
|
||||
}
|
||||
gobottest.Assert(t, tc.simVal, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type lineMock struct {
|
||||
lastVal int
|
||||
simErr error
|
||||
}
|
||||
|
||||
func (lm *lineMock) SetValue(value int) error { lm.lastVal = value; return lm.simErr }
|
||||
func (lm *lineMock) Value() (int, error) { return lm.lastVal, lm.simErr }
|
||||
func (*lineMock) Close() error { return nil }
|
52
system/digitalpin_mock.go
Normal file
52
system/digitalpin_mock.go
Normal file
@ -0,0 +1,52 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"gobot.io/x/gobot"
|
||||
)
|
||||
|
||||
type mockDigitalPinHandler struct {
|
||||
fs *MockFilesystem
|
||||
}
|
||||
|
||||
type digitalPinMock struct{}
|
||||
|
||||
func (h *mockDigitalPinHandler) isSupported() bool { return true }
|
||||
|
||||
func (h *mockDigitalPinHandler) createPin(chip string, pin int,
|
||||
o ...func(gobot.DigitalPinOptioner) bool) gobot.DigitalPinner {
|
||||
dpm := &digitalPinMock{}
|
||||
return dpm
|
||||
}
|
||||
|
||||
func (h *mockDigitalPinHandler) setFs(fs filesystem) {
|
||||
// do nothing
|
||||
return
|
||||
}
|
||||
|
||||
func (d *digitalPinMock) ApplyOptions(options ...func(gobot.DigitalPinOptioner) bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *digitalPinMock) DirectionBehavior() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// Write writes the given value to the character device
|
||||
func (d *digitalPinMock) Write(b int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Read reads the given value from character device
|
||||
func (d *digitalPinMock) Read() (n int, err error) {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Export sets the pin as exported with the configured direction
|
||||
func (d *digitalPinMock) Export() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Unexport release the pin
|
||||
func (d *digitalPinMock) Unexport() error {
|
||||
return nil
|
||||
}
|
@ -7,132 +7,78 @@ import (
|
||||
"strconv"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"gobot.io/x/gobot"
|
||||
)
|
||||
|
||||
const (
|
||||
// IN gpio direction
|
||||
IN = "in"
|
||||
// OUT gpio direction
|
||||
OUT = "out"
|
||||
// HIGH gpio level
|
||||
HIGH = 1
|
||||
// LOW gpio level
|
||||
LOW = 0
|
||||
// gpioPath default linux gpio path
|
||||
// gpioPath default linux sysfs gpio path
|
||||
gpioPath = "/sys/class/gpio"
|
||||
)
|
||||
|
||||
var errNotExported = errors.New("pin has not been exported")
|
||||
|
||||
// DigitalPin represents a digital pin
|
||||
type DigitalPin struct {
|
||||
pin string
|
||||
label string
|
||||
|
||||
value File
|
||||
direction File
|
||||
|
||||
// digitalPin represents a digital pin
|
||||
type digitalPinSysfs struct {
|
||||
pin string
|
||||
*digitalPinConfig
|
||||
fs filesystem
|
||||
|
||||
dirFile File
|
||||
valFile File
|
||||
}
|
||||
|
||||
// NewDigitalPin returns a DigitalPin given the pin number. The name of the sysfs file will prepend "gpio"
|
||||
// to the pin number, eg. a pin number of 10 will have a name of "gpio10"
|
||||
func (a *Accesser) NewDigitalPin(pin int) *DigitalPin {
|
||||
d := &DigitalPin{
|
||||
pin: strconv.Itoa(pin),
|
||||
fs: a.fs,
|
||||
// newDigitalPinSysfs returns a digital pin using for the given number. The name of the sysfs file will prepend "gpio"
|
||||
// to the pin number, eg. a pin number of 10 will have a name of "gpio10". The pin is handled by the sysfs Kernel ABI.
|
||||
func newDigitalPinSysfs(fs filesystem, pin string, options ...func(gobot.DigitalPinOptioner) bool) *digitalPinSysfs {
|
||||
cfg := newDigitalPinConfig("gpio"+pin, options...)
|
||||
d := &digitalPinSysfs{
|
||||
pin: pin,
|
||||
digitalPinConfig: cfg,
|
||||
fs: fs,
|
||||
}
|
||||
d.label = "gpio" + d.pin
|
||||
|
||||
return d
|
||||
}
|
||||
|
||||
// Direction sets (writes) the direction of the digital pin
|
||||
func (d *DigitalPin) Direction(dir string) error {
|
||||
_, err := writeFile(d.direction, []byte(dir))
|
||||
// ApplyOptions apply all given options to the pin immediately. Implements interface gobot.DigitalPinOptionApplier.
|
||||
func (d *digitalPinSysfs) ApplyOptions(options ...func(gobot.DigitalPinOptioner) bool) error {
|
||||
anyChange := false
|
||||
for _, option := range options {
|
||||
anyChange = anyChange || option(d)
|
||||
}
|
||||
if anyChange {
|
||||
return d.reconfigure()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DirectionBehavior gets the direction behavior when the pin is used the next time. This means its possibly not in
|
||||
// this direction type at the moment. Implements the interface gobot.DigitalPinValuer, but should be rarely used.
|
||||
func (d *digitalPinSysfs) DirectionBehavior() string {
|
||||
return d.direction
|
||||
}
|
||||
|
||||
// Export sets the pin as exported with the configured direction
|
||||
func (d *digitalPinSysfs) Export() error {
|
||||
err := d.reconfigure()
|
||||
return err
|
||||
}
|
||||
|
||||
// Write writes the given value to the character device
|
||||
func (d *DigitalPin) Write(b int) error {
|
||||
_, err := writeFile(d.value, []byte(strconv.Itoa(b)))
|
||||
return err
|
||||
}
|
||||
|
||||
// Read reads the given value from character device
|
||||
func (d *DigitalPin) Read() (n int, err error) {
|
||||
buf, err := readFile(d.value)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return strconv.Atoi(string(buf[0]))
|
||||
}
|
||||
|
||||
// Export sets the pin as exported
|
||||
func (d *DigitalPin) Export() error {
|
||||
export, err := d.fs.openFile(gpioPath+"/export", os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer export.Close()
|
||||
|
||||
_, err = writeFile(export, []byte(d.pin))
|
||||
if err != nil {
|
||||
// If EBUSY then the pin has already been exported
|
||||
e, ok := err.(*os.PathError)
|
||||
if !ok || e.Err != syscall.EBUSY {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if d.direction != nil {
|
||||
d.direction.Close()
|
||||
}
|
||||
|
||||
attempt := 0
|
||||
for {
|
||||
attempt++
|
||||
d.direction, err = d.fs.openFile(fmt.Sprintf("%v/%v/direction", gpioPath, d.label), os.O_RDWR, 0644)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
if attempt > 10 {
|
||||
return err
|
||||
}
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
|
||||
if d.value != nil {
|
||||
d.value.Close()
|
||||
}
|
||||
if err == nil {
|
||||
d.value, err = d.fs.openFile(fmt.Sprintf("%v/%v/value", gpioPath, d.label), os.O_RDWR, 0644)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
// Should we unexport here?
|
||||
// If we don't unexport we should make sure to close d.direction and d.value here
|
||||
d.Unexport()
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Unexport sets the pin as unexported
|
||||
func (d *DigitalPin) Unexport() error {
|
||||
// Unexport release the pin
|
||||
func (d *digitalPinSysfs) Unexport() error {
|
||||
unexport, err := d.fs.openFile(gpioPath+"/unexport", os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer unexport.Close()
|
||||
|
||||
if d.direction != nil {
|
||||
d.direction.Close()
|
||||
d.direction = nil
|
||||
if d.dirFile != nil {
|
||||
d.dirFile.Close()
|
||||
d.dirFile = nil
|
||||
}
|
||||
if d.value != nil {
|
||||
d.value.Close()
|
||||
d.value = nil
|
||||
if d.valFile != nil {
|
||||
d.valFile.Close()
|
||||
d.valFile = nil
|
||||
}
|
||||
|
||||
_, err = writeFile(unexport, []byte(d.pin))
|
||||
@ -147,6 +93,81 @@ func (d *DigitalPin) Unexport() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Write writes the given value to the character device
|
||||
func (d *digitalPinSysfs) Write(b int) error {
|
||||
_, err := writeFile(d.valFile, []byte(strconv.Itoa(b)))
|
||||
return err
|
||||
}
|
||||
|
||||
// Read reads the given value from character device
|
||||
func (d *digitalPinSysfs) Read() (int, error) {
|
||||
buf, err := readFile(d.valFile)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return strconv.Atoi(string(buf[0]))
|
||||
}
|
||||
|
||||
func (d *digitalPinSysfs) reconfigure() error {
|
||||
exportFile, err := d.fs.openFile(gpioPath+"/export", os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer exportFile.Close()
|
||||
|
||||
_, err = writeFile(exportFile, []byte(d.pin))
|
||||
if err != nil {
|
||||
// If EBUSY then the pin has already been exported
|
||||
e, ok := err.(*os.PathError)
|
||||
if !ok || e.Err != syscall.EBUSY {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if d.dirFile != nil {
|
||||
d.dirFile.Close()
|
||||
}
|
||||
|
||||
attempt := 0
|
||||
for {
|
||||
attempt++
|
||||
d.dirFile, err = d.fs.openFile(fmt.Sprintf("%s/%s/direction", gpioPath, d.label), os.O_RDWR, 0644)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
if attempt > 10 {
|
||||
return err
|
||||
}
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
|
||||
if d.valFile != nil {
|
||||
d.valFile.Close()
|
||||
}
|
||||
if err == nil {
|
||||
d.valFile, err = d.fs.openFile(fmt.Sprintf("%s/%s/value", gpioPath, d.label), os.O_RDWR, 0644)
|
||||
}
|
||||
|
||||
// configure line
|
||||
if err == nil {
|
||||
err = d.writeDirectionWithInitialOutput()
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
d.Unexport()
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *digitalPinSysfs) writeDirectionWithInitialOutput() error {
|
||||
if _, err := writeFile(d.dirFile, []byte(d.direction)); err != nil || d.direction == IN {
|
||||
return err
|
||||
}
|
||||
_, err := writeFile(d.valFile, []byte(strconv.Itoa(d.outInitialState)))
|
||||
return err
|
||||
}
|
||||
|
||||
// Linux sysfs / GPIO specific sysfs docs.
|
||||
// https://www.kernel.org/doc/Documentation/filesystems/sysfs.txt
|
||||
// https://www.kernel.org/doc/Documentation/gpio/sysfs.txt
|
||||
|
@ -6,23 +6,28 @@ import (
|
||||
"syscall"
|
||||
"testing"
|
||||
|
||||
"gobot.io/x/gobot"
|
||||
"gobot.io/x/gobot/gobottest"
|
||||
)
|
||||
|
||||
var _ gobot.DigitalPinner = (*digitalPinSysfs)(nil)
|
||||
var _ gobot.DigitalPinValuer = (*digitalPinSysfs)(nil)
|
||||
var _ gobot.DigitalPinOptioner = (*digitalPinSysfs)(nil)
|
||||
var _ gobot.DigitalPinOptionApplier = (*digitalPinSysfs)(nil)
|
||||
|
||||
func TestDigitalPin(t *testing.T) {
|
||||
a := NewAccesser()
|
||||
mockPaths := []string{
|
||||
"/sys/class/gpio/export",
|
||||
"/sys/class/gpio/unexport",
|
||||
"/sys/class/gpio/gpio10/value",
|
||||
"/sys/class/gpio/gpio10/direction",
|
||||
}
|
||||
fs := a.UseMockFilesystem(mockPaths)
|
||||
fs := newMockFilesystem(mockPaths)
|
||||
|
||||
pin := a.NewDigitalPin(10)
|
||||
pin := newDigitalPinSysfs(fs, "10")
|
||||
gobottest.Assert(t, pin.pin, "10")
|
||||
gobottest.Assert(t, pin.label, "gpio10")
|
||||
gobottest.Assert(t, pin.value, nil)
|
||||
gobottest.Assert(t, pin.valFile, nil)
|
||||
|
||||
err := pin.Unexport()
|
||||
gobottest.Assert(t, err, nil)
|
||||
@ -31,20 +36,20 @@ func TestDigitalPin(t *testing.T) {
|
||||
err = pin.Export()
|
||||
gobottest.Assert(t, err, nil)
|
||||
gobottest.Assert(t, fs.Files["/sys/class/gpio/export"].Contents, "10")
|
||||
gobottest.Refute(t, pin.value, nil)
|
||||
gobottest.Refute(t, pin.valFile, nil)
|
||||
|
||||
err = pin.Write(1)
|
||||
gobottest.Assert(t, err, nil)
|
||||
gobottest.Assert(t, fs.Files["/sys/class/gpio/gpio10/value"].Contents, "1")
|
||||
|
||||
err = pin.Direction(IN)
|
||||
err = pin.ApplyOptions(WithDirectionInput())
|
||||
gobottest.Assert(t, err, nil)
|
||||
gobottest.Assert(t, fs.Files["/sys/class/gpio/gpio10/direction"].Contents, "in")
|
||||
|
||||
data, _ := pin.Read()
|
||||
gobottest.Assert(t, 1, data)
|
||||
|
||||
pin2 := a.NewDigitalPin(30)
|
||||
pin2 := newDigitalPinSysfs(fs, "30")
|
||||
err = pin2.Write(1)
|
||||
gobottest.Assert(t, err.Error(), "pin has not been exported")
|
||||
|
||||
@ -66,30 +71,34 @@ func TestDigitalPin(t *testing.T) {
|
||||
err = pin.Unexport()
|
||||
gobottest.Assert(t, err.(*os.PathError).Err, errors.New("write error"))
|
||||
|
||||
// assert a busy error is dropped (just means "already exported")
|
||||
cnt := 0
|
||||
writeFile = func(File, []byte) (int, error) {
|
||||
return 0, &os.PathError{Err: syscall.EBUSY}
|
||||
cnt++
|
||||
if cnt == 1 {
|
||||
return 0, &os.PathError{Err: syscall.EBUSY}
|
||||
}
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
err = pin.Export()
|
||||
gobottest.Assert(t, err, nil)
|
||||
|
||||
// assert write error on export
|
||||
writeFile = func(File, []byte) (int, error) {
|
||||
return 0, &os.PathError{Err: errors.New("write error")}
|
||||
}
|
||||
|
||||
err = pin.Export()
|
||||
gobottest.Assert(t, err.(*os.PathError).Err, errors.New("write error"))
|
||||
}
|
||||
|
||||
func TestDigitalPinExportError(t *testing.T) {
|
||||
a := NewAccesser()
|
||||
mockPaths := []string{
|
||||
"/sys/class/gpio/export",
|
||||
"/sys/class/gpio/gpio11/direction",
|
||||
}
|
||||
a.UseMockFilesystem(mockPaths)
|
||||
fs := newMockFilesystem(mockPaths)
|
||||
|
||||
pin := a.NewDigitalPin(10)
|
||||
pin := newDigitalPinSysfs(fs, "10")
|
||||
|
||||
writeFile = func(File, []byte) (int, error) {
|
||||
return 0, &os.PathError{Err: syscall.EBUSY}
|
||||
@ -100,13 +109,12 @@ func TestDigitalPinExportError(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDigitalPinUnexportError(t *testing.T) {
|
||||
a := NewAccesser()
|
||||
mockPaths := []string{
|
||||
"/sys/class/gpio/unexport",
|
||||
}
|
||||
a.UseMockFilesystem(mockPaths)
|
||||
fs := newMockFilesystem(mockPaths)
|
||||
|
||||
pin := a.NewDigitalPin(10)
|
||||
pin := newDigitalPinSysfs(fs, "10")
|
||||
|
||||
writeFile = func(File, []byte) (int, error) {
|
||||
return 0, &os.PathError{Err: syscall.EBUSY}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
package system provides generic access to Linux gpio, i2c and filesystem.
|
||||
Package system provides generic access to Linux gpio, i2c and filesystem.
|
||||
|
||||
It is intended to be used while implementing support for a single board Linux computer
|
||||
*/
|
||||
|
10
system/pwmpin.go
Normal file
10
system/pwmpin.go
Normal file
@ -0,0 +1,10 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"gobot.io/x/gobot"
|
||||
)
|
||||
|
||||
// NewPWMPin returns a new system pwmPin.
|
||||
func (a *Accesser) NewPWMPin(path string, pin int) gobot.PWMPinner {
|
||||
return newPWMPinSysfs(a.fs, path, pin)
|
||||
}
|
@ -32,15 +32,15 @@ type PWMPin struct {
|
||||
fs filesystem
|
||||
}
|
||||
|
||||
// NewPWMPin returns a new pwmPin
|
||||
func (a *Accesser) NewPWMPin(path string, pin int) *PWMPin {
|
||||
// newPWMPinSysfs returns a new pwmPin, working with sysfs file access.
|
||||
func newPWMPinSysfs(fs filesystem, path string, pin int) *PWMPin {
|
||||
return &PWMPin{
|
||||
pin: strconv.Itoa(pin),
|
||||
enabled: false,
|
||||
path: path,
|
||||
read: readPwmFile,
|
||||
write: writePwmFile,
|
||||
fs: a.fs,
|
||||
fs: fs,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -12,7 +12,6 @@ import (
|
||||
var _ gobot.PWMPinner = (*PWMPin)(nil)
|
||||
|
||||
func TestPwmPin(t *testing.T) {
|
||||
a := NewAccesser()
|
||||
mockedPaths := []string{
|
||||
"/sys/class/pwm/pwmchip0/export",
|
||||
"/sys/class/pwm/pwmchip0/unexport",
|
||||
@ -21,9 +20,9 @@ func TestPwmPin(t *testing.T) {
|
||||
"/sys/class/pwm/pwmchip0/pwm10/duty_cycle",
|
||||
"/sys/class/pwm/pwmchip0/pwm10/polarity",
|
||||
}
|
||||
fs := a.UseMockFilesystem(mockedPaths)
|
||||
fs := newMockFilesystem(mockedPaths)
|
||||
|
||||
pin := a.NewPWMPin("/sys/class/pwm/pwmchip0", 10)
|
||||
pin := newPWMPinSysfs(fs, "/sys/class/pwm/pwmchip0", 10)
|
||||
gobottest.Assert(t, pin.pin, "10")
|
||||
|
||||
err := pin.Unexport()
|
||||
@ -66,7 +65,6 @@ func TestPwmPin(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestPwmPinAlreadyExported(t *testing.T) {
|
||||
a := NewAccesser()
|
||||
mockedPaths := []string{
|
||||
"/sys/class/pwm/pwmchip0/export",
|
||||
"/sys/class/pwm/pwmchip0/unexport",
|
||||
@ -74,9 +72,9 @@ func TestPwmPinAlreadyExported(t *testing.T) {
|
||||
"/sys/class/pwm/pwmchip0/pwm10/period",
|
||||
"/sys/class/pwm/pwmchip0/pwm10/duty_cycle",
|
||||
}
|
||||
a.UseMockFilesystem(mockedPaths)
|
||||
fs := newMockFilesystem(mockedPaths)
|
||||
|
||||
pin := a.NewPWMPin("/sys/class/pwm/pwmchip0", 10)
|
||||
pin := newPWMPinSysfs(fs, "/sys/class/pwm/pwmchip0", 10)
|
||||
pin.write = func(filesystem, string, []byte) (int, error) {
|
||||
return 0, &os.PathError{Err: syscall.EBUSY}
|
||||
}
|
||||
@ -86,7 +84,6 @@ func TestPwmPinAlreadyExported(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestPwmPinExportError(t *testing.T) {
|
||||
a := NewAccesser()
|
||||
mockedPaths := []string{
|
||||
"/sys/class/pwm/pwmchip0/export",
|
||||
"/sys/class/pwm/pwmchip0/unexport",
|
||||
@ -94,9 +91,9 @@ func TestPwmPinExportError(t *testing.T) {
|
||||
"/sys/class/pwm/pwmchip0/pwm10/period",
|
||||
"/sys/class/pwm/pwmchip0/pwm10/duty_cycle",
|
||||
}
|
||||
a.UseMockFilesystem(mockedPaths)
|
||||
fs := newMockFilesystem(mockedPaths)
|
||||
|
||||
pin := a.NewPWMPin("/sys/class/pwm/pwmchip0", 10)
|
||||
pin := newPWMPinSysfs(fs, "/sys/class/pwm/pwmchip0", 10)
|
||||
|
||||
pin.write = func(filesystem, string, []byte) (int, error) {
|
||||
return 0, &os.PathError{Err: syscall.EFAULT}
|
||||
@ -108,7 +105,6 @@ func TestPwmPinExportError(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestPwmPinUnxportError(t *testing.T) {
|
||||
a := NewAccesser()
|
||||
mockedPaths := []string{
|
||||
"/sys/class/pwm/pwmchip0/export",
|
||||
"/sys/class/pwm/pwmchip0/unexport",
|
||||
@ -116,9 +112,9 @@ func TestPwmPinUnxportError(t *testing.T) {
|
||||
"/sys/class/pwm/pwmchip0/pwm10/period",
|
||||
"/sys/class/pwm/pwmchip0/pwm10/duty_cycle",
|
||||
}
|
||||
a.UseMockFilesystem(mockedPaths)
|
||||
fs := newMockFilesystem(mockedPaths)
|
||||
|
||||
pin := a.NewPWMPin("/sys/class/pwm/pwmchip0", 10)
|
||||
pin := newPWMPinSysfs(fs, "/sys/class/pwm/pwmchip0", 10)
|
||||
|
||||
pin.write = func(filesystem, string, []byte) (int, error) {
|
||||
return 0, &os.PathError{Err: syscall.EBUSY}
|
||||
@ -129,7 +125,6 @@ func TestPwmPinUnxportError(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestPwmPinPeriodError(t *testing.T) {
|
||||
a := NewAccesser()
|
||||
mockedPaths := []string{
|
||||
"/sys/class/pwm/pwmchip0/export",
|
||||
"/sys/class/pwm/pwmchip0/unexport",
|
||||
@ -137,9 +132,9 @@ func TestPwmPinPeriodError(t *testing.T) {
|
||||
"/sys/class/pwm/pwmchip0/pwm10/period",
|
||||
"/sys/class/pwm/pwmchip0/pwm10/duty_cycle",
|
||||
}
|
||||
a.UseMockFilesystem(mockedPaths)
|
||||
fs := newMockFilesystem(mockedPaths)
|
||||
|
||||
pin := a.NewPWMPin("/sys/class/pwm/pwmchip0", 10)
|
||||
pin := newPWMPinSysfs(fs, "/sys/class/pwm/pwmchip0", 10)
|
||||
|
||||
pin.read = func(filesystem, string) ([]byte, error) {
|
||||
return nil, &os.PathError{Err: syscall.EBUSY}
|
||||
@ -150,7 +145,6 @@ func TestPwmPinPeriodError(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestPwmPinPolarityError(t *testing.T) {
|
||||
a := NewAccesser()
|
||||
mockedPaths := []string{
|
||||
"/sys/class/pwm/pwmchip0/export",
|
||||
"/sys/class/pwm/pwmchip0/unexport",
|
||||
@ -158,9 +152,9 @@ func TestPwmPinPolarityError(t *testing.T) {
|
||||
"/sys/class/pwm/pwmchip0/pwm10/period",
|
||||
"/sys/class/pwm/pwmchip0/pwm10/duty_cycle",
|
||||
}
|
||||
a.UseMockFilesystem(mockedPaths)
|
||||
fs := newMockFilesystem(mockedPaths)
|
||||
|
||||
pin := a.NewPWMPin("/sys/class/pwm/pwmchip0", 10)
|
||||
pin := newPWMPinSysfs(fs, "/sys/class/pwm/pwmchip0", 10)
|
||||
|
||||
pin.read = func(filesystem, string) ([]byte, error) {
|
||||
return nil, &os.PathError{Err: syscall.EBUSY}
|
||||
@ -171,7 +165,6 @@ func TestPwmPinPolarityError(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestPwmPinDutyCycleError(t *testing.T) {
|
||||
a := NewAccesser()
|
||||
mockedPaths := []string{
|
||||
"/sys/class/pwm/pwmchip0/export",
|
||||
"/sys/class/pwm/pwmchip0/unexport",
|
||||
@ -179,9 +172,9 @@ func TestPwmPinDutyCycleError(t *testing.T) {
|
||||
"/sys/class/pwm/pwmchip0/pwm10/period",
|
||||
"/sys/class/pwm/pwmchip0/pwm10/duty_cycle",
|
||||
}
|
||||
a.UseMockFilesystem(mockedPaths)
|
||||
fs := newMockFilesystem(mockedPaths)
|
||||
|
||||
pin := a.NewPWMPin("/sys/class/pwm/pwmchip0", 10)
|
||||
pin := newPWMPinSysfs(fs, "/sys/class/pwm/pwmchip0", 10)
|
||||
|
||||
pin.read = func(filesystem, string) ([]byte, error) {
|
||||
return nil, &os.PathError{Err: syscall.EBUSY}
|
||||
|
@ -1,11 +1,16 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"gobot.io/x/gobot"
|
||||
)
|
||||
|
||||
const systemDebug = false
|
||||
|
||||
// A File represents basic IO interactions with the underlying file system
|
||||
type File interface {
|
||||
Write(b []byte) (n int, err error)
|
||||
@ -32,21 +37,56 @@ type systemCaller interface {
|
||||
syscall(trap uintptr, f File, signal uintptr, payload unsafe.Pointer) (r1, r2 uintptr, err syscall.Errno)
|
||||
}
|
||||
|
||||
// Accesser provides access to system calls and filesystem
|
||||
type Accesser struct {
|
||||
sys systemCaller
|
||||
fs filesystem
|
||||
type digitalPinAccesser interface {
|
||||
isSupported() bool
|
||||
createPin(chip string, pin int, o ...func(gobot.DigitalPinOptioner) bool) gobot.DigitalPinner
|
||||
setFs(fs filesystem)
|
||||
}
|
||||
|
||||
// NewAccesser returns a accesser to native system call and native file system
|
||||
func NewAccesser() *Accesser {
|
||||
// Accesser provides access to system calls and filesystem
|
||||
type Accesser struct {
|
||||
sys systemCaller
|
||||
fs filesystem
|
||||
digitalPinAccess digitalPinAccesser
|
||||
}
|
||||
|
||||
// NewAccesser returns a accesser to native system call, native file system and the chosen digital pin access.
|
||||
// Digital pin accesser can be empty or "sysfs", otherwise it will be automatically chosen.
|
||||
func NewAccesser(digitalPinAccesser ...string) *Accesser {
|
||||
s := Accesser{
|
||||
sys: &nativeSyscall{},
|
||||
fs: &nativeFilesystem{},
|
||||
}
|
||||
a := "sysfs"
|
||||
if len(digitalPinAccesser) > 0 && digitalPinAccesser[0] != "" {
|
||||
a = digitalPinAccesser[0]
|
||||
}
|
||||
if a != "sysfs" {
|
||||
dpa := &gpiodDigitalPinAccess{fs: s.fs}
|
||||
if dpa.isSupported() {
|
||||
s.digitalPinAccess = dpa
|
||||
if systemDebug {
|
||||
fmt.Printf("use gpiod driver for digital pins with this chips: %v\n", dpa.chips)
|
||||
}
|
||||
return &s
|
||||
}
|
||||
if systemDebug {
|
||||
fmt.Println("gpiod driver not supported, fallback to sysfs")
|
||||
}
|
||||
}
|
||||
s.digitalPinAccess = &sysfsDigitalPinAccess{fs: s.fs}
|
||||
return &s
|
||||
}
|
||||
|
||||
// UseMockDigitalPinWithMockFs sets the digital pin handler accesser to the mocked one. Used only for tests.
|
||||
func (a *Accesser) UseMockDigitalPinWithMockFs(files []string) *mockDigitalPinHandler {
|
||||
fs := newMockFilesystem(files)
|
||||
mdph := &mockDigitalPinHandler{fs: fs}
|
||||
a.fs = fs
|
||||
a.digitalPinAccess = mdph
|
||||
return mdph
|
||||
}
|
||||
|
||||
// UseMockSyscall sets the Syscall implementation of the accesser to the mocked one. Used only for tests.
|
||||
func (a *Accesser) UseMockSyscall() *mockSyscall {
|
||||
msc := &mockSyscall{}
|
||||
@ -58,9 +98,25 @@ func (a *Accesser) UseMockSyscall() *mockSyscall {
|
||||
func (a *Accesser) UseMockFilesystem(files []string) *MockFilesystem {
|
||||
fs := newMockFilesystem(files)
|
||||
a.fs = fs
|
||||
a.digitalPinAccess.setFs(fs)
|
||||
return fs
|
||||
}
|
||||
|
||||
// NewDigitalPin returns a new system digital pin given the pin number and an optional pin label.
|
||||
// If no label is supplied a default label will prepend to the pin number.
|
||||
func (a *Accesser) NewDigitalPin(chip string, pin int,
|
||||
o ...func(gobot.DigitalPinOptioner) bool) gobot.DigitalPinner {
|
||||
return a.digitalPinAccess.createPin(chip, pin, o...)
|
||||
}
|
||||
|
||||
// IsSysfsDigitalPinAccess returns whether the used digital pin accesser is a sysfs one.
|
||||
func (a *Accesser) IsSysfsDigitalPinAccess() bool {
|
||||
if _, ok := a.digitalPinAccess.(*sysfsDigitalPinAccess); ok {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// OpenFile opens file of given name from native or the mocked file system
|
||||
func (a *Accesser) OpenFile(name string, flag int, perm os.FileMode) (File, error) {
|
||||
return a.fs.openFile(name, flag, perm)
|
||||
|
60
system/system_test.go
Normal file
60
system/system_test.go
Normal file
@ -0,0 +1,60 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"gobot.io/x/gobot/gobottest"
|
||||
)
|
||||
|
||||
func TestNewAccesser_IsSysfsDigitalPinAccess(t *testing.T) {
|
||||
const gpiodTestCase = "accesser_gpiod"
|
||||
var tests = map[string]struct {
|
||||
accesser string
|
||||
wantSys bool
|
||||
}{
|
||||
"default_accesser_sysfs": {
|
||||
wantSys: true,
|
||||
},
|
||||
"accesser_sysfs": {
|
||||
accesser: "sysfs",
|
||||
wantSys: true,
|
||||
},
|
||||
gpiodTestCase: {
|
||||
accesser: "cdev",
|
||||
wantSys: false,
|
||||
},
|
||||
}
|
||||
for name, tc := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
// arrange
|
||||
if name == gpiodTestCase {
|
||||
// there is no mock at this level, so if the system do not support
|
||||
// character device gpio, we skip the test
|
||||
dpa := &gpiodDigitalPinAccess{fs: &nativeFilesystem{}}
|
||||
if !dpa.isSupported() {
|
||||
t.Skip()
|
||||
}
|
||||
}
|
||||
// act
|
||||
a := NewAccesser(tc.accesser)
|
||||
got := a.IsSysfsDigitalPinAccess()
|
||||
// assert
|
||||
nativeSys := a.sys.(*nativeSyscall)
|
||||
nativeFsSys := a.fs.(*nativeFilesystem)
|
||||
gobottest.Refute(t, a, nil)
|
||||
gobottest.Refute(t, nativeSys, nil)
|
||||
gobottest.Refute(t, nativeFsSys, nil)
|
||||
if tc.wantSys {
|
||||
gobottest.Assert(t, got, true)
|
||||
dpaSys := a.digitalPinAccess.(*sysfsDigitalPinAccess)
|
||||
gobottest.Refute(t, dpaSys, nil)
|
||||
gobottest.Assert(t, dpaSys.fs, nativeFsSys)
|
||||
} else {
|
||||
gobottest.Assert(t, got, false)
|
||||
dpaGpiod := a.digitalPinAccess.(*gpiodDigitalPinAccess)
|
||||
gobottest.Refute(t, dpaGpiod, nil)
|
||||
gobottest.Assert(t, dpaGpiod.fs, nativeFsSys)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user