diff --git a/adaptor.go b/adaptor.go index d51a3996..963e5730 100644 --- a/adaptor.go +++ b/adaptor.go @@ -1,23 +1,47 @@ package gobot +// DigitalPinOptioner is the interface to provide the possibility to change pin behavior for the next usage +type DigitalPinOptioner interface { + // SetLabel change the pins label + SetLabel(string) (changed bool) + // SetDirectionOutput sets the pins direction to output with the given initial value + SetDirectionOutput(initialState int) (changed bool) + // SetDirectionInput sets the pins direction to input + SetDirectionInput() (changed bool) +} + +// DigitalPinOptionApplier is the interface to apply options to change pin behavior immediately +type DigitalPinOptionApplier interface { + // ApplyOptions apply all given options to the pin immediately + ApplyOptions(...func(DigitalPinOptioner) bool) error +} + // DigitalPinner is the interface for system gpio interactions type DigitalPinner interface { - // Export exports the pin for use by the operating system + // Export exports the pin for use by the adaptor Export() error - // Unexport unexports the pin and releases the pin from the operating system + // Unexport releases the pin from the adaptor, so it is free for the operating system Unexport() error - // Direction sets the direction for the pin - Direction(string) error // Read reads the current value of the pin Read() (int, error) // Write writes to the pin Write(int) error + // DigitalPinOptionApplier is the interface to change pin behavior immediately + DigitalPinOptionApplier } -// DigitalPinnerProvider is the interface that an Adaptor should implement to allow -// clients to obtain access to any DigitalPin's available on that board. +// DigitalPinValuer is the interface to get pin behavior for the next usage. The interface is and should be rarely used. +type DigitalPinValuer interface { + // DirectionBehavior gets the direction behavior when the pin is used the next time. + // This means its possibly not in this direction type at the moment. + DirectionBehavior() string +} + +// DigitalPinnerProvider is the interface that an Adaptor should implement to allow clients to obtain +// access to any DigitalPin's available on that board. If the pin is initially acquired, it is an input. +// Pin direction and other options can be changed afterwards by pin.ApplyOptions() at any time. type DigitalPinnerProvider interface { - DigitalPin(string, string) (DigitalPinner, error) + DigitalPin(id string) (DigitalPinner, error) } // PWMPinner is the interface for system PWM interactions @@ -48,7 +72,7 @@ type PWMPinner interface { // PWMPinnerProvider is the interface that an Adaptor should implement to allow // clients to obtain access to any PWMPin's available on that board. type PWMPinnerProvider interface { - PWMPin(string) (PWMPinner, error) + PWMPin(id string) (PWMPinner, error) } // Adaptor is the interface that describes an adaptor in gobot diff --git a/go.mod b/go.mod index 16755290..366c338c 100644 --- a/go.mod +++ b/go.mod @@ -16,6 +16,7 @@ require ( github.com/stretchr/testify v1.8.0 github.com/urfave/cli v1.22.10 github.com/veandco/go-sdl2 v0.4.25 + github.com/warthog618/gpiod v0.8.0 go.bug.st/serial v1.4.0 gocv.io/x/gocv v0.31.0 golang.org/x/net v0.1.0 diff --git a/go.sum b/go.sum index 0d2e5756..b5ad1e54 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,25 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgould/http v0.0.0-20190627042742-d268792bdee7/go.mod h1:BTqvVegvwifopl4KTEDth6Zezs9eR+lCWhvGKvkxJHE= github.com/bmizerany/pat v0.0.0-20210406213842-e4b6760bdd6f h1:gOO/tNZMjjvTKZWpY7YnXC72ULNLErRtp94LountVE8= github.com/bmizerany/pat v0.0.0-20210406213842-e4b6760bdd6f/go.mod h1:8rLXio+WjiTceGBHIoTvn60HIbs7Hm7bcHjyrSqYB9c= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/coreos/bbolt v1.3.3/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/etcd v3.3.15+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= @@ -10,6 +28,7 @@ github.com/creack/goselect v0.1.2/go.mod h1:a/NhLweNvqIYMuxcMOuWY516Cimucms3DglD github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/donovanhide/eventsource v0.0.0-20210830082556-c59027999da0 h1:C7t6eeMaEQVy6e8CarIhscYQlNmw5e3G36y7l7Y21Ao= github.com/donovanhide/eventsource v0.0.0-20210830082556-c59027999da0/go.mod h1:56wL82FO0bfMU5RvfXoIwSOP2ggqqxT+tAfNEIyxuHw= github.com/eclipse/paho.mqtt.golang v1.2.0/go.mod h1:H9keYFcgq3Qr5OUJm/JZI/i6U7joQ8SYLhZwfeOo6Ts= @@ -18,44 +37,89 @@ github.com/eclipse/paho.mqtt.golang v1.4.1/go.mod h1:JGt0RsEwEX+Xa/agj90YJ9d9DH2 github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/frankban/quicktest v1.10.2/go.mod h1:K+q6oSqb0W0Ininfk863uOk1lMy69l/P6txr3mVT54s= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/glerchundi/subcommands v0.0.0-20181212083838-923a6ccb11f8/go.mod h1:r0g3O7Y5lrWXgDfcFBRgnAKzjmPgTzwoMC2ieB345FY= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofrs/uuid v4.3.0+incompatible h1:CaSVZxm5B+7o45rtab4jC2G37WGYX1zQfuU2i6DSvnc= github.com/gofrs/uuid v4.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.5.0 h1:LUVKkCeviFUMKqHa4tXIIij/lbhnMbP7Fn5wKdKkRh4= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grpc-ecosystem/go-grpc-middleware v1.1.0/go.mod h1:f5nM7jw/oeRSadq3xCzHAvxcr8HZnzsqU6ILg/0NiiE= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.11.2/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/hajimehoshi/go-jisx0208 v1.0.0/go.mod h1:yYxEStHL7lt9uL+AbdWgW9gBumwieDoZCiB1f/0X0as= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hybridgroup/go-ardrone v0.0.0-20140402002621-b9750d8d7b78 h1:7of6LJZ4LF9AvF4bTiMr2I72KxodBf1BXrSD9Tz0lWU= github.com/hybridgroup/go-ardrone v0.0.0-20140402002621-b9750d8d7b78/go.mod h1:YllNbhGM1UEcySxCv1BWK5lre7QLmJJ+O0ADUOo2nbc= github.com/hybridgroup/mjpeg v0.0.0-20140228234708-4680f319790e h1:xCcwD5FOXul+j1dn8xD16nbrhJkkum/Cn+jTd/u1LhY= github.com/hybridgroup/mjpeg v0.0.0-20140228234708-4680f319790e/go.mod h1:eagM805MRKrioHYuU7iKLUyFPVKqVV6um5DAvCkUtXs= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/muka/go-bluetooth v0.0.0-20220830075246-0746e3a1ea53 h1:zfLHhuGzmSbthZ00FfbEjgAHUOOj7NGiITojMTCFy6U= github.com/muka/go-bluetooth v0.0.0-20220830075246-0746e3a1ea53/go.mod h1:dMCjicU6vRBk34dqOmIZm0aod6gUwZXOXzBROqGous0= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nats-io/jwt v0.3.0 h1:xdnzwFETV++jNc4W1mw//qFyJGb2ABOombmZJQS4+Qo= github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= github.com/nats-io/nats-server/v2 v2.1.0 h1:Yi0+ZhRPtPAGeIxFn5erIeJIV9wXA+JznfSxK621Fbk= @@ -71,14 +135,33 @@ github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/paypal/gatt v0.0.0-20151011220935-4ae819d591cf/go.mod h1:+AwQL2mK3Pd3S+TUwg0tYQjid0q1txyNUJuuSmz8Kdk= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.6.0/go.mod h1:5N711Q9dKgbdkxHL+MEfF31hpT7l0S0s/t2kKREewys= github.com/peterbourgon/ff/v3 v3.1.2/go.mod h1:XNJLY8EIl6MjMVjBS4F0+G0LYoAqs0DTa4rmHHukKDE= +github.com/pilebones/go-udev v0.0.0-20180820235104-043677e09b13 h1:Y+ynP+0QIjUejN2tsuIlWOJG1CThJy6amRuWlBL94Vg= +github.com/pilebones/go-udev v0.0.0-20180820235104-043677e09b13/go.mod h1:MXAPLpvZeTqLpU1eO6kFXzU0uBMooSGc1MPXAcBoy1M= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -89,13 +172,28 @@ github.com/saltosystems/winrt-go v0.0.0-20220913104103-712830fcd2ad/go.mod h1:Uv github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sigurn/crc8 v0.0.0-20220107193325-2243fe600f9f h1:1R9KdKjCNSd7F8iGTxIpoID9prlYH8nuNYKt0XvweHA= github.com/sigurn/crc8 v0.0.0-20220107193325-2243fe600f9f/go.mod h1:vQhwQ4meQEDfahT5kd61wLAF5AAeh5ZPLVI4JJ/tYo8= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.5.0/go.mod h1:+F7Ogzej0PZc/94MaYx/nvG9jOFMD2osvC3s+Squfpo= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -106,18 +204,31 @@ github.com/suapapa/go_eddystone v1.3.1/go.mod h1:bXC11TfJOS+3g3q/Uzd7FKd5g62STQE github.com/tdakkota/win32metadata v0.1.0/go.mod h1:77e6YvX0LIVW+O81fhWLnXAxxcyu/wdZdG7iwed7Fyk= github.com/tinygo-org/cbgo v0.0.4 h1:3D76CRYbH03Rudi8sEgs/YO0x3JIMdyq8jlQtk/44fU= github.com/tinygo-org/cbgo v0.0.4/go.mod h1:7+HgWIHd4nbAz0ESjGlJ1/v9LDU1Ox8MGzP9mah/fLk= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/urfave/cli v1.22.10 h1:p8Fspmz3iTctJstry1PYS3HVdllxnEzTEsgIgtxTrCk= github.com/urfave/cli v1.22.10/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/valyala/fastjson v1.6.3/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY= github.com/veandco/go-sdl2 v0.4.25 h1:J5ac3KKOccp/0xGJA1PaNYKPUcZm19IxhDGs8lJofPI= github.com/veandco/go-sdl2 v0.4.25/go.mod h1:OROqMhHD43nT4/i9crJukyVecjPNYYuCofep6SNiAjY= +github.com/warthog618/config v0.4.1/go.mod h1:IzcIkVay6dCubN3WBAJzPuqHyE1fTPxICvKTQ/2JA9g= +github.com/warthog618/gpiod v0.8.0 h1:qxH9XVvWHpTxzWFSndBcujFyNH5zVRzHM63tcmm85o4= +github.com/warthog618/gpiod v0.8.0/go.mod h1:a7Csa+IJtDBZ39++zC/6Srjo01qWejt/5velrDWuNkY= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.bug.st/serial v1.4.0 h1:IXHzPVbUBbql66lQZ1iV9LWzGXT5lh6S9gZxHK/KyQE= go.bug.st/serial v1.4.0/go.mod h1:z8CesKorE90Qr/oRSJiEuvzYRKol9r/anJZEb5kt304= +go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.2.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= gocv.io/x/gocv v0.31.0 h1:BHDtK8v+YPvoSPQTTiZB2fM/7BLg6511JqkruY2z6LQ= gocv.io/x/gocv v0.31.0/go.mod h1:oc6FvfYqfBp99p+yOEzs9tbYF9gOrAQSeL/dyIPefJU= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -127,10 +238,21 @@ golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b/go.mod h1:T9bdIzuCu7OtxOm golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU= golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= @@ -140,17 +262,29 @@ golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190927073244-c990c680b611/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -173,7 +307,15 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/time v0.0.0-20190921001708-c4c64cad1fd0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200925191224-5d1fdd8fa346/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= golang.org/x/tools v0.1.11/go.mod h1:SgwaegtQh8clINPpECJMqnxLv9I09HLqnW3RMqW0CA4= @@ -182,22 +324,39 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190927181202-20e1ac93f88c/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/ini.v1 v1.48.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= periph.io/x/conn/v3 v3.6.10 h1:gwU4ssmZkq1D/uz8hU91i/COo2c9DrRaS4PJZBbCd+c= periph.io/x/conn/v3 v3.6.10/go.mod h1:UqWNaPMosWmNCwtufoTSTTYhB2wXWsMRAJyo1PlxO4Q= periph.io/x/d2xx v0.0.4/go.mod h1:38Euaaj+s6l0faIRHh32a+PrjXvxFTFkPBEQI0TKg34= periph.io/x/host/v3 v3.7.2 h1:rCAUxkzy2xrzh18HP2AoVwTL/fEKqmcJ1icsZQGM58Q= periph.io/x/host/v3 v3.7.2/go.mod h1:nHMlzkPwmnHyP9Tn0I8FV+e0N3K7TjFXLZkIWzAicog= +sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= tinygo.org/x/bluetooth v0.6.0 h1:5RTUh28WBtWfRtwFcsDcdiCvlSWr9F7fHxRikQZW/Io= tinygo.org/x/bluetooth v0.6.0/go.mod h1:tiW1IiKOupcsvM2CX0PwLsf6aZRL+ciSIqP2YlgYOtQ= tinygo.org/x/drivers v0.14.0/go.mod h1:uT2svMq3EpBZpKkGO+NQHjxjGf1f42ra4OnMMwQL2aI= diff --git a/platforms/adaptors/digitalpinsadaptor.go b/platforms/adaptors/digitalpinsadaptor.go new file mode 100644 index 00000000..97e57338 --- /dev/null +++ b/platforms/adaptors/digitalpinsadaptor.go @@ -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 +} diff --git a/platforms/adaptors/digitalpinsadaptor_test.go b/platforms/adaptors/digitalpinsadaptor_test.go new file mode 100644 index 00000000..a7e4bdad --- /dev/null +++ b/platforms/adaptors/digitalpinsadaptor_test.go @@ -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() + } +} diff --git a/platforms/beaglebone/beaglebone_adaptor.go b/platforms/beaglebone/beaglebone_adaptor.go index 8c3c6f8a..8c33aac0 100644 --- a/platforms/beaglebone/beaglebone_adaptor.go +++ b/platforms/beaglebone/beaglebone_adaptor.go @@ -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) diff --git a/platforms/beaglebone/beaglebone_adaptor_test.go b/platforms/beaglebone/beaglebone_adaptor_test.go index 28222e60..ab4bec1c 100644 --- a/platforms/beaglebone/beaglebone_adaptor_test.go +++ b/platforms/beaglebone/beaglebone_adaptor_test.go @@ -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) { diff --git a/platforms/chip/chip_adaptor.go b/platforms/chip/chip_adaptor.go index 2bb5eddc..195112f1 100644 --- a/platforms/chip/chip_adaptor.go +++ b/platforms/chip/chip_adaptor.go @@ -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 +} diff --git a/platforms/dragonboard/dragonboard_adaptor.go b/platforms/dragonboard/dragonboard_adaptor.go index bd1b5085..282af61e 100644 --- a/platforms/dragonboard/dragonboard_adaptor.go +++ b/platforms/dragonboard/dragonboard_adaptor.go @@ -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 +} diff --git a/platforms/intel-iot/edison/edison_adaptor.go b/platforms/intel-iot/edison/edison_adaptor.go index 624f51df..94df487b 100644 --- a/platforms/intel-iot/edison/edison_adaptor.go +++ b/platforms/intel-iot/edison/edison_adaptor.go @@ -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 } diff --git a/platforms/intel-iot/edison/edison_adaptor_test.go b/platforms/intel-iot/edison/edison_adaptor_test.go index 31c0ecfc..e2d1355e 100644 --- a/platforms/intel-iot/edison/edison_adaptor_test.go +++ b/platforms/intel-iot/edison/edison_adaptor_test.go @@ -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) } diff --git a/platforms/intel-iot/joule/joule_adaptor.go b/platforms/intel-iot/joule/joule_adaptor.go index 2f43ad8f..5faab1e1 100644 --- a/platforms/intel-iot/joule/joule_adaptor.go +++ b/platforms/intel-iot/joule/joule_adaptor.go @@ -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 +} diff --git a/platforms/intel-iot/joule/joule_adaptor_test.go b/platforms/intel-iot/joule/joule_adaptor_test.go index 9cdf5436..7f4836d9 100644 --- a/platforms/intel-iot/joule/joule_adaptor_test.go +++ b/platforms/intel-iot/joule/joule_adaptor_test.go @@ -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) diff --git a/platforms/jetson/jetson_adaptor.go b/platforms/jetson/jetson_adaptor.go index d647a170..721eb473 100644 --- a/platforms/jetson/jetson_adaptor.go +++ b/platforms/jetson/jetson_adaptor.go @@ -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 +} diff --git a/platforms/jetson/jetson_adaptor_test.go b/platforms/jetson/jetson_adaptor_test.go index f3ecded2..1a75dd15 100644 --- a/platforms/jetson/jetson_adaptor_test.go +++ b/platforms/jetson/jetson_adaptor_test.go @@ -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) } diff --git a/platforms/raspi/raspi_adaptor.go b/platforms/raspi/raspi_adaptor.go index cf253963..adeb29e0 100644 --- a/platforms/raspi/raspi_adaptor.go +++ b/platforms/raspi/raspi_adaptor.go @@ -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 { diff --git a/platforms/raspi/raspi_adaptor_test.go b/platforms/raspi/raspi_adaptor_test.go index 76be09e5..a001c109 100644 --- a/platforms/raspi/raspi_adaptor_test.go +++ b/platforms/raspi/raspi_adaptor_test.go @@ -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) } diff --git a/platforms/tinkerboard/adaptor.go b/platforms/tinkerboard/adaptor.go index a17a8908..a663f650 100644 --- a/platforms/tinkerboard/adaptor.go +++ b/platforms/tinkerboard/adaptor.go @@ -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 +} diff --git a/platforms/tinkerboard/pin_map.go b/platforms/tinkerboard/pin_map.go index 921014cb..c1ce97cb 100644 --- a/platforms/tinkerboard/pin_map.go +++ b/platforms/tinkerboard/pin_map.go @@ -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{ diff --git a/platforms/upboard/up2/adaptor.go b/platforms/upboard/up2/adaptor.go index bfd38843..129a04a1 100644 --- a/platforms/upboard/up2/adaptor.go +++ b/platforms/upboard/up2/adaptor.go @@ -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 diff --git a/system/README.md b/system/README.md index 15906b1b..30c563d4 100644 --- a/system/README.md +++ b/system/README.md @@ -1,4 +1,4 @@ -# sysfs +# system This document describes some basics for developers. diff --git a/system/digitalpin_access.go b/system/digitalpin_access.go new file mode 100644 index 00000000..4d4eef6a --- /dev/null +++ b/system/digitalpin_access.go @@ -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 +} diff --git a/system/digitalpin_access_test.go b/system/digitalpin_access_test.go new file mode 100644 index 00000000..af0e3ee6 --- /dev/null +++ b/system/digitalpin_access_test.go @@ -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") +} diff --git a/system/digitalpin_bench_test.go b/system/digitalpin_bench_test.go index 9c7e7447..afc58df3 100644 --- a/system/digitalpin_bench_test.go +++ b/system/digitalpin_bench_test.go @@ -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++ { diff --git a/system/digitalpin_config.go b/system/digitalpin_config.go new file mode 100644 index 00000000..07176b9c --- /dev/null +++ b/system/digitalpin_config.go @@ -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 +} diff --git a/system/digitalpin_config_test.go b/system/digitalpin_config_test.go new file mode 100644 index 00000000..45b14457 --- /dev/null +++ b/system/digitalpin_config_test.go @@ -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) + }) + } +} diff --git a/system/digitalpin_gpiod.go b/system/digitalpin_gpiod.go new file mode 100644 index 00000000..d2ad5581 --- /dev/null +++ b/system/digitalpin_gpiod.go @@ -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]) +} diff --git a/system/digitalpin_gpiod_test.go b/system/digitalpin_gpiod_test.go new file mode 100644 index 00000000..12ed40db --- /dev/null +++ b/system/digitalpin_gpiod_test.go @@ -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 } diff --git a/system/digitalpin_mock.go b/system/digitalpin_mock.go new file mode 100644 index 00000000..a5752a35 --- /dev/null +++ b/system/digitalpin_mock.go @@ -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 +} diff --git a/system/digitalpin_sysfs.go b/system/digitalpin_sysfs.go index 05910b32..8c94c538 100644 --- a/system/digitalpin_sysfs.go +++ b/system/digitalpin_sysfs.go @@ -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 diff --git a/system/digitalpin_sysfs_test.go b/system/digitalpin_sysfs_test.go index 032e1365..29484106 100644 --- a/system/digitalpin_sysfs_test.go +++ b/system/digitalpin_sysfs_test.go @@ -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} diff --git a/system/doc.go b/system/doc.go index 359b5c09..138bd624 100644 --- a/system/doc.go +++ b/system/doc.go @@ -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 */ diff --git a/system/pwmpin.go b/system/pwmpin.go new file mode 100644 index 00000000..a7ea0afa --- /dev/null +++ b/system/pwmpin.go @@ -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) +} diff --git a/system/pwmpin_sysfs.go b/system/pwmpin_sysfs.go index d75a0864..321b465d 100644 --- a/system/pwmpin_sysfs.go +++ b/system/pwmpin_sysfs.go @@ -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, } } diff --git a/system/pwmpin_sysfs_test.go b/system/pwmpin_sysfs_test.go index 522f713f..3a6f0da6 100644 --- a/system/pwmpin_sysfs_test.go +++ b/system/pwmpin_sysfs_test.go @@ -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} diff --git a/system/system.go b/system/system.go index 8336652f..967a4f62 100644 --- a/system/system.go +++ b/system/system.go @@ -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) diff --git a/system/system_test.go b/system/system_test.go new file mode 100644 index 00000000..8936fdd8 --- /dev/null +++ b/system/system_test.go @@ -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) + } + }) + } +}