1
0
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:
Thomas Kohler 2022-11-27 16:06:09 +01:00 committed by GitHub
parent be92fb6f9e
commit 45ee9c3644
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 2107 additions and 683 deletions

View File

@ -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
View File

@ -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
View File

@ -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=

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

View 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()
}
}

View File

@ -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)

View File

@ -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) {

View File

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

View File

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

View File

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

View File

@ -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)
}

View File

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

View File

@ -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)

View File

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

View File

@ -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)
}

View File

@ -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 {

View File

@ -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)
}

View File

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

View File

@ -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{

View File

@ -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

View File

@ -1,4 +1,4 @@
# sysfs
# system
This document describes some basics for developers.

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

View 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")
}

View File

@ -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++ {

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

View 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
View 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])
}

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

View File

@ -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

View File

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

View File

@ -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
View 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)
}

View File

@ -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,
}
}

View File

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

View File

@ -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
View 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)
}
})
}
}