├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── accel3xdigital ├── README.md ├── accel3xdigital.go ├── accel3xdigital_test.go ├── examples │ └── monitor │ │ ├── main.go │ │ └── monitor └── protocol.go ├── dotstar ├── README.md ├── dotstar.go ├── dotstar_test.go └── examples │ ├── helloworld │ └── main.go │ └── random │ └── main.go ├── lcdrgbbacklight ├── README.md ├── characters.go ├── examples │ └── helloworld │ │ ├── helloworld │ │ └── main.go ├── lcdrgbbacklight.go └── protocol.go ├── monochromeoled ├── README.md ├── examples │ └── helloworld │ │ └── main.go └── monochromeoled.go ├── oled96x96 ├── README.md ├── examples │ └── helloworld │ │ └── main.go ├── fonts.go ├── oled96x96.go ├── oled96x96_test.go └── protocol.go └── piglow ├── README.md ├── example_test.go ├── examples └── strobe.go ├── piglow.go └── piglow_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 3 | - 1.6 4 | - tip -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Go libraries/drivers for IoT devices 2 | 3 | [![GoDoc](http://godoc.org/github.com/goiot/devices?status.svg)](http://godoc.org/github.com/goiot/devices) 4 | [![Build Status](https://travis-ci.org/goiot/devices.svg?branch=master)](https://travis-ci.org/goiot/devices) 5 | 6 | This repo contains a suite of libraries for IoT devices/sensors/actuators. The suite is meant to be as dependency free 7 | and as idiomatic as possible. 8 | 9 | If you are interested in helping, feel free to look at the open issues mentioning help needed. 10 | If you have questions on how you implement some of the features, don't hesitate to ask. If you are trying to integrate 11 | these libraries in your projects and have questions, please open an issue. 12 | 13 | Note that there are a LOT of IoT devices and while we would love to have libs for all of them, we will need your help. 14 | 15 | ## Supported devices 16 | 17 | ### [Grove](http://www.seeedstudio.com/wiki/Grove_System) 18 | 19 | * [3 Axis Digital Accelerometer](https://github.com/goiot/devices/tree/master/accel3xdigital) 20 | * [LCD RGB Backlight](https://github.com/goiot/devices/tree/master/lcdrgbbacklight) 21 | * [OLED 96 x 96](https://github.com/goiot/devices/tree/master/oled96x96) 22 | 23 | ### [Adafruit](https://www.adafruit.com/) 24 | 25 | * [DotStar RGB LED (APA102)](https://github.com/goiot/devices/tree/master/dotstar) 26 | * [Monochrome 0.96" 128x64 OLED graphic display (SSD1306)](https://github.com/goiot/devices/tree/master/monochromeoled) 27 | 28 | ### [Pimoroni](https://shop.pimoroni.com/) 29 | 30 | * [PiGlow](https://github.com/goiot/devices/tree/master/piglow) 31 | 32 | ### Generic 33 | 34 | The following libraries can be used on multiple devices that use the same underlying component. 35 | Often various manufacturers create their own version of a device using the same component. 36 | If you have device that doesn't have a driver listed above, look at the main component used and see 37 | it it matches one of the ones mentioned below. 38 | 39 | * [APA102 LED strip](https://github.com/goiot/devices/tree/master/dotstar) 40 | * [SSD1306 OLED](https://github.com/goiot/devices/tree/master/monochromeoled) 41 | 42 | ## Repo organization 43 | 44 | Device libraries are organized by manufacturers and should use names that easy to google or identify. 45 | Each device package contains a README file with references and details about the device (and usually a picture and links to datasheets). You will also find an examples folder with basic examples on how to use the library. 46 | 47 | ## Test setup 48 | 49 | Testing IoT devices is quite complicated, most of us use a [Raspberry Pi](https://www.raspberrypi.org/), connect the devices 50 | directly or via [shield](http://www.dexterindustries.com/grovepi/) and run the examples to test. Yes, it's far from perfect :( 51 | 52 | ## More information / Advanced topics 53 | 54 | Checkout the [wiki](https://github.com/goiot/devices/wiki) for more info. 55 | -------------------------------------------------------------------------------- /accel3xdigital/README.md: -------------------------------------------------------------------------------- 1 | #Grove - 3-Axis Digital Accelerometer(±1.5g) 2 | 3 | [![GoDoc](http://godoc.org/github.com/goiot/devices/accel3xdigital?status.svg)](http://godoc.org/github.com/goiot/devices/accel3xdigital) 4 | 5 | [Manufacturer info](http://www.seeedstudio.com/wiki/Grove_-_3-Axis_Digital_Accelerometer(%C2%B11.5g)) 6 | 7 | 3-Axis Digital Accelerometer is the key part in projects like orientation detection, gesture detection and Motion detection. 8 | This i2c 3-Axis Digital Accelerometer(±1.5g) is based on Freescale's low power consumption module, MMA7660FC. 9 | It features up to 10,000g high shock survivability and configurable Samples per Second rate. 10 | For applications that don't require too large measurement range, this is a great choice because it's durable, energy saving and cost-efficient. 11 | 12 | Besides detecting the position and acceleration of the 3 axis, this device can be used to detect taps and/or shakes. 13 | 14 | ![Grove - 3-Axis Digital Accelerometer(±1.5g)](http://www.seeedstudio.com/wiki/images/b/bb/3_aix_acc.jpg) 15 | 16 | ##Datasheets: 17 | 18 | * [MMA7660FC Datasheet](http://garden.seeedstudio.com/images/e/ee/MMA7660FC.pdf) 19 | -------------------------------------------------------------------------------- /accel3xdigital/accel3xdigital.go: -------------------------------------------------------------------------------- 1 | // Package accel3xdigital allows developers to read x,y,z and acceleratation. 2 | // Currently this library doesn't support any of the interrupts: Front/Back Interrupt, 3 | // Up/Down/Left/Right Interrupt, Tap Detection Interrupt, GINT (real-time motion tracking), Shake on X-axis, Shake on Y-axis, and 4 | // Shake on Z-axis. 5 | package accel3xdigital 6 | 7 | import ( 8 | "errors" 9 | "fmt" 10 | 11 | "golang.org/x/exp/io/i2c" 12 | "golang.org/x/exp/io/i2c/driver" 13 | ) 14 | 15 | var ( 16 | // ErrNotReady warns the user that the device isn't not (yet) ready 17 | ErrNotReady = errors.New("device is not ready") 18 | ) 19 | 20 | // Accel3xDigital reresents the Grove 3-Axis Digital Accelerometer(±1.5g) 21 | type Accel3xDigital struct { 22 | Device *i2c.Device 23 | State *State 24 | // TapEnabled lets devs know if the feature is on or off (default) 25 | TapEnabled bool 26 | } 27 | 28 | // Open connects to the passed driver and sets things up. 29 | // At this point the sensor will sample 32 times a second and will store its information in registries you can read from 30 | // by calling update. 31 | // Note that by default the tap detection is not on. You need to enable this feature manually. 32 | func Open(o driver.Opener) (*Accel3xDigital, error) { 33 | device, err := i2c.Open(o, addr) 34 | if err != nil { 35 | return nil, err 36 | } 37 | 38 | accel := &Accel3xDigital{Device: device, State: &State{}} 39 | if err := accel.ChangeMode(StandBy); err != nil { 40 | return accel, err 41 | } 42 | 43 | if err := accel.Device.Write([]byte{accelSr, accelAutoSleep32}); err != nil { 44 | return accel, err 45 | } 46 | 47 | if err := accel.Device.Write([]byte{accelMode, accelActive}); err != nil { 48 | return accel, err 49 | } 50 | 51 | return accel, nil 52 | } 53 | 54 | // ChangeMode allows developers to switch between standy and active (default). 55 | func (a *Accel3xDigital) ChangeMode(m Mode) (err error) { 56 | err = a.Device.Write([]byte{accelMode, byte(m)}) 57 | if err != nil { 58 | err = fmt.Errorf("failed to change mode - %v", err) 59 | } 60 | return err 61 | } 62 | 63 | // Enable tap enables checking for taps by increasing the sample rate 64 | func (a *Accel3xDigital) EnableTap() error { 65 | if err := a.ChangeMode(StandBy); err != nil { 66 | return err 67 | } 68 | 69 | if err := a.Device.Write([]byte{accelSr, accelAutoSleep120}); err != nil { 70 | return err 71 | } 72 | 73 | // set tap detection sensitivity (how many samples to check for) 74 | // we are setting the threashold at 80 samples knowing that we are sampling at 120hz 75 | if err := a.Device.Write([]byte{accelPd, 80}); err != nil { 76 | return err 77 | } 78 | 79 | if err := a.Device.Write([]byte{accelMode, accelActive}); err != nil { 80 | return err 81 | } 82 | a.TapEnabled = true 83 | return nil 84 | } 85 | 86 | // SetTapSensitivity sets the debounce filtering requirement to n adjacent tap detection tests to 87 | // be the same to trigger a tap event. 88 | // Note that the sampling rate is 120 samples/second when tap is enabled. 89 | func (a *Accel3xDigital) SetTapSensitivity(n uint8) error { 90 | if !a.TapEnabled { 91 | if err := a.EnableTap(); err != nil { 92 | return err 93 | } 94 | } 95 | 96 | if err := a.ChangeMode(StandBy); err != nil { 97 | return err 98 | } 99 | 100 | if err := a.Device.Write([]byte{accelPd, byte(n)}); err != nil { 101 | return err 102 | } 103 | 104 | if err := a.Device.Write([]byte{accelMode, accelActive}); err != nil { 105 | return err 106 | } 107 | 108 | return nil 109 | } 110 | 111 | // Update reads the sensor and update the state in memory 112 | func (a *Accel3xDigital) Update() error { 113 | err := a.Device.Read(stateBuff) 114 | if err != nil { 115 | return err 116 | } 117 | 118 | for i := 0; i < 3; i++ { 119 | val := stateBuff[i] 120 | if ((val >> 6) & 0x01) == 1 { 121 | return ErrNotReady 122 | } 123 | } 124 | 125 | a.State.X = float64((int8(stateBuff[0]) << 2)) / 4.0 126 | a.State.Y = float64((int8(stateBuff[1]) << 2)) / 4.0 127 | a.State.Z = float64((int8(stateBuff[2]) << 2)) / 4.0 128 | 129 | tilt := stateBuff[3] 130 | a.State.Front = (tilt & (1 << 0)) > 0 131 | a.State.Back = (tilt & (1 << 1)) > 0 132 | a.State.Tapped = (tilt & (1 << 5)) > 0 133 | a.State.Alert = (tilt & (1 << 6)) > 0 134 | a.State.Shaken = (tilt & (1 << 7)) > 0 135 | 136 | masks := [3]bool{ 137 | tilt&(1<<2) > 0, 138 | tilt&(1<<3) > 0, 139 | tilt&(1<<4) > 0, 140 | } 141 | 142 | switch masks { 143 | case downMask: 144 | a.State.Position = Down 145 | case upMask: 146 | a.State.Position = Up 147 | case leftMask: 148 | a.State.Position = Left 149 | case rightMask: 150 | a.State.Position = Right 151 | default: 152 | a.State.Position = Unknown 153 | } 154 | 155 | // report race conditions 156 | if a.State.Alert { 157 | return errors.New("error reading state, try again") 158 | } 159 | 160 | return nil 161 | } 162 | 163 | // Close puts the device on standby 164 | func (a *Accel3xDigital) Close() error { 165 | a.ChangeMode(StandBy) 166 | return a.Device.Close() 167 | } 168 | 169 | // State contains the last read state of the device 170 | type State struct { 171 | // Front is true if the equipment is lying on its front 172 | Front bool 173 | // Back is true if the equipment is lying on its back 174 | Back bool 175 | Position Position 176 | // Tapped reports that the device was tapped, after reading the data once, the flag is cleared. 177 | Tapped bool 178 | // Shaken reports that the device was shaken, after reading the data once, the flag is cleared. 179 | Shaken bool 180 | // Alert can be triggered if there was a race condition when we tried to read (the sensor was updating the data at the same time) 181 | // If you get an alert, ignore the data and read again. 182 | Alert bool 183 | // X axis value 184 | X float64 185 | // Y axis value 186 | Y float64 187 | // Z axis value 188 | Z float64 189 | } 190 | 191 | // Acceleration returns the acceleration (g) for each axis (x,y,z) 192 | func (s *State) Acceleration() (float64, float64, float64) { 193 | return s.X / 21.0, s.Y / 21.0, s.Y / 21.0 194 | } 195 | 196 | // String implements the stringer interface 197 | func (s *State) String() string { 198 | xg, yg, zg := s.Acceleration() 199 | orientation := "unknown" 200 | if s.Front { 201 | orientation = "front facing" 202 | } else if s.Back { 203 | orientation = "back facing" 204 | } 205 | return fmt.Sprintf(`Current State: 206 | X: %2.f, Y: %2.f, Z: %2.f 207 | Acceleration: 208 | X: %2.fg, Y: %2.fg, Z: %2.fg 209 | Orientation: %s 210 | Position: %s 211 | Was Shaken?: %t, 212 | Was tapped?: %t 213 | `, s.X, s.Y, s.Z, 214 | xg, yg, zg, 215 | orientation, 216 | s.Position, 217 | s.Shaken, 218 | s.Tapped) 219 | } 220 | -------------------------------------------------------------------------------- /accel3xdigital/accel3xdigital_test.go: -------------------------------------------------------------------------------- 1 | package accel3xdigital_test 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/goiot/devices/accel3xdigital" 8 | "golang.org/x/exp/io/i2c" 9 | ) 10 | 11 | func Example() { 12 | accel, err := accel3xdigital.Open(&i2c.Devfs{ 13 | Dev: "/dev/i2c-1", 14 | }) 15 | if err != nil { 16 | panic(err) 17 | } 18 | 19 | defer accel.Close() 20 | 21 | for i := 0; i < 20; i++ { 22 | accel.Update() 23 | fmt.Println(accel.State) 24 | time.Sleep(500 * time.Millisecond) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /accel3xdigital/examples/monitor/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/goiot/devices/accel3xdigital" 8 | "golang.org/x/exp/io/i2c" 9 | ) 10 | 11 | func main() { 12 | accel, err := accel3xdigital.Open(&i2c.Devfs{Dev: "/dev/i2c-1"}) 13 | if err != nil { 14 | panic(err) 15 | } 16 | 17 | // Enabling tap switches from 32 samples/second to 120 18 | err = accel.EnableTap() 19 | if err != nil { 20 | panic(err) 21 | } 22 | 23 | defer accel.Close() 24 | 25 | for i := 0; i < 20; i++ { 26 | if err := accel.Update(); err != nil { 27 | fmt.Println("Something went wrong updating the accelerometer value") 28 | } 29 | fmt.Println(accel.State) 30 | time.Sleep(500 * time.Millisecond) 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /accel3xdigital/examples/monitor/monitor: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goiot/devices/09d1226fc8ea3630f18dfe9864baaae8529ff44d/accel3xdigital/examples/monitor/monitor -------------------------------------------------------------------------------- /accel3xdigital/protocol.go: -------------------------------------------------------------------------------- 1 | package accel3xdigital 2 | 3 | // Mode The sensor has three power modes: Off Mode, Standby Mode, and Active Mode to offer the customer different power 4 | // consumption options. The sensor is only capable of running in one of these modes at a time. 5 | type Mode byte 6 | 7 | var ( 8 | downMask = [3]bool{true, false, true} 9 | upMask = [3]bool{false, true, true} 10 | leftMask = [3]bool{true, false, false} 11 | rightMask = [3]bool{false, true, false} 12 | 13 | stateBuff = make([]byte, 4) 14 | ) 15 | 16 | const ( 17 | // Standby Mode is ideal for battery operated products. When Standby Mode is active the device outputs are turned off 18 | // providing a significant reduction in operating current. When the device is in Standby Mode the current will be reduced to 19 | // approximately 3 µA. Standby Mode is entered as soon as both analog and digital power supplies are up. 20 | // In this mode, the device can read and write to the registers with I2C, but no new measurements can be taken. 21 | StandBy = Mode(accelStandBy) 22 | // Active Mode, continuous measurement on all three axes is enabled. In addition, the user can choose to enable: 23 | // Shake Detection, Tap Detection, Orientation Detection, and/or Auto-Wake/Sleep Feature and in this mode the digital analysis for 24 | // any of these functions is done. 25 | Active = Mode(accelActive) 26 | ) 27 | 28 | const ( 29 | // addr is the i2c address of this sensor 30 | addr = 0x4c 31 | 32 | accelX = 0x00 33 | accelY = 0x01 34 | accelZ = 0x02 35 | accelTilt = 0x03 36 | 37 | accelSrst = 0x04 38 | accelSpcnt = 0x05 39 | accelIntsu = 0x06 40 | 41 | accelMode = 0x07 42 | accelStandBy = 0x00 43 | accelActive = 0x01 44 | 45 | // sample rate 46 | accelSr = 0x08 47 | accelAutoSleep120 = 0x00 48 | accelAutoSleep64 = 0x01 49 | // 32 samples per second (default) 50 | accelAutoSleep32 = 0x02 51 | accelAutoSleep16 = 0x03 52 | accelAutoSleep8 = 0x04 53 | accelAutoSleep4 = 0x05 54 | accelAutoSleep2 = 0x06 55 | accelAutoSleep1 = 0x07 56 | 57 | accelPdet = 0x09 58 | accelPd = 0x0A 59 | ) 60 | 61 | // Position indicates the position of the sensor/device 62 | type Position int 63 | 64 | const ( 65 | // Unknown condition of up or down or left or right 66 | Unknown Position = iota 67 | // Left is true if in landscape mode to the left 68 | Left 69 | // Right is true if in landscape mode to the right 70 | Right 71 | // Down is true if standing vertically in inverted orientation 72 | Down 73 | // Up is true if standing vertically in normal orientation 74 | Up 75 | ) 76 | 77 | func (p Position) String() string { 78 | switch p { 79 | case Right: 80 | return "right" 81 | case Left: 82 | return "left" 83 | case Down: 84 | return "down" 85 | case Up: 86 | return "up" 87 | default: 88 | return "unkown" 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /dotstar/README.md: -------------------------------------------------------------------------------- 1 | # DotStar RGB LED 2 | 3 | [![GoDoc](http://godoc.org/github.com/goiot/devices/dotstar?status.png)](http://godoc.org/github.com/goiot/devices/dotstar) 4 | 5 | [Manufacturer info](https://www.adafruit.com/product/2238) 6 | 7 | DotStar LEDs are 5050-sized LEDs with an embedded microcontroller inside the LED. You can set the color/brightness of each LED to 24-bit color (8 bits each red green and blue). Each LED acts like a shift register, reading incoming color data on the input pins, and then shifting the previous color data out on the output pin. By sending a long string of data, you can control an infinite number of LEDs, just tack on more or cut off unwanted LEDs at the end. 8 | 9 | ![Adafruit DotStar](https://cdn-shop.adafruit.com/product-videos/320x240/2238-06.jpg) 10 | 11 | ##Datasheets: 12 | 13 | * [APA102 driver Datasheet](https://cdn-shop.adafruit.com/datasheets/APA102.pdf) -------------------------------------------------------------------------------- /dotstar/dotstar.go: -------------------------------------------------------------------------------- 1 | // Package dotstar implements a driver for the dotstar LEDs. 2 | package dotstar 3 | 4 | import ( 5 | "golang.org/x/exp/io/spi" 6 | "golang.org/x/exp/io/spi/driver" 7 | ) 8 | 9 | // RGBA represents the color of a dostar LED. 10 | type RGBA struct { 11 | R byte // R represents the red intensity. 12 | G byte // G represents the green intensity. 13 | B byte // B represents the blue intensity. 14 | A byte // A is the brightness of the LED. Must be between 0 and 15. 15 | } 16 | 17 | // LEDs represent a strip of dotstar LEDs. 18 | type LEDs struct { 19 | // Device is the underlying SPI bus that is used to communicate the 20 | // LED strip. Most users don't have to access this field. 21 | Device *spi.Device 22 | 23 | vals []RGBA 24 | } 25 | 26 | // Open opens a new LED strip with n dotstar LEDs. An LED strip 27 | // must be closed if no longer in use. 28 | func Open(o driver.Opener, n int) (*LEDs, error) { 29 | dev, err := spi.Open(o) 30 | if err != nil { 31 | return nil, err 32 | } 33 | 34 | if err := dev.SetMode(spi.Mode3); err != nil { 35 | dev.Close() 36 | return nil, err 37 | } 38 | 39 | if err := dev.SetBitsPerWord(8); err != nil { 40 | dev.Close() 41 | return nil, err 42 | } 43 | 44 | return &LEDs{ 45 | Device: dev, 46 | vals: make([]RGBA, n), 47 | }, nil 48 | } 49 | 50 | // SetRGBA sets the ith LED's color to the given RGBA value. 51 | // A call to Draw is required to transmit the new value 52 | // to the LED strip. 53 | func (d *LEDs) SetRGBA(i int, v RGBA) { 54 | d.vals[i] = v 55 | } 56 | 57 | // Draw displays the RGBA values set on the actual LED strip. 58 | func (d *LEDs) Draw() error { 59 | // TODO(jbd): dotstar allows other RGBA allignments, support those layouts. 60 | n := len(d.vals) 61 | tx := make([]byte, 4*(n+1)+(n/2+1)) 62 | tx[0] = 0x00 63 | tx[1] = 0x00 64 | tx[2] = 0x00 65 | tx[3] = 0x00 66 | 67 | for i, c := range d.vals { 68 | j := (i + 1) * 4 69 | tx[j] = 0xe0 + c.A 70 | tx[j+1] = c.B 71 | tx[j+2] = c.G 72 | tx[j+3] = c.R 73 | } 74 | 75 | // end frame with at least n/2 0xff vals 76 | for i := (n + 1) * 4; i < len(tx); i++ { 77 | tx[i] = 0xff 78 | } 79 | 80 | return d.Device.Tx(tx, nil) 81 | } 82 | 83 | // Close frees the underlying resources. It must be called once 84 | // the LED strip is no longer in use. 85 | func (d *LEDs) Close() error { 86 | return d.Device.Close() 87 | } 88 | -------------------------------------------------------------------------------- /dotstar/dotstar_test.go: -------------------------------------------------------------------------------- 1 | package dotstar_test 2 | 3 | import ( 4 | "github.com/goiot/devices/dotstar" 5 | "golang.org/x/exp/io/spi" 6 | ) 7 | 8 | func Example() { 9 | d, err := dotstar.Open( // a strip with 5 LEDs 10 | &spi.Devfs{Dev: "/dev/spi0.1", Mode: spi.Mode3}, 5) 11 | if err != nil { 12 | panic(err) 13 | } 14 | 15 | d.SetRGBA(0, dotstar.RGBA{255, 0, 0, 16}) // Red 16 | d.SetRGBA(1, dotstar.RGBA{0, 255, 0, 16}) // Green 17 | d.SetRGBA(2, dotstar.RGBA{0, 0, 255, 16}) // Blue 18 | d.SetRGBA(3, dotstar.RGBA{255, 0, 0, 8}) // Half dim red 19 | d.SetRGBA(4, dotstar.RGBA{0, 0, 255, 8}) // Half dim blue 20 | 21 | if err := d.Draw(); err != nil { 22 | panic(err) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /dotstar/examples/helloworld/main.go: -------------------------------------------------------------------------------- 1 | // +build linux 2 | 3 | // Package main contains a program that drives a dotstar LED strip. 4 | package main 5 | 6 | import ( 7 | "github.com/goiot/devices/dotstar" 8 | "golang.org/x/exp/io/spi" 9 | ) 10 | 11 | // n is the number of LEDs on the strip. 12 | const n = 100 13 | 14 | func main() { 15 | d, err := dotstar.Open(&spi.Devfs{Dev: "/dev/spi0.1", Mode: spi.Mode3}, n) 16 | if err != nil { 17 | panic(err) 18 | } 19 | 20 | for i := 0; i < n; i++ { 21 | d.SetRGBA(i, dotstar.RGBA{255, 0, 0, 16}) // Brightest red 22 | } 23 | 24 | if err := d.Draw(); err != nil { 25 | panic(err) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /dotstar/examples/random/main.go: -------------------------------------------------------------------------------- 1 | // Package main contains a program that drives a dotstar LED strip. 2 | // This example was written to run on a Raspberry Pi 3 | // Make sure SPI is enabled on in raspi-config 4 | // Connect Data to GPIO10, Clock to GPIO11 (and of course power (5v) and ground) 5 | // Your SPI address might be different, `ls` to /dev to see what addresses are available. 6 | package main 7 | 8 | import ( 9 | "fmt" 10 | "math/rand" 11 | "os" 12 | "os/signal" 13 | "syscall" 14 | "time" 15 | 16 | "github.com/goiot/devices/dotstar" 17 | "golang.org/x/exp/io/spi" 18 | ) 19 | 20 | // n is the number of LEDs on the strip. 21 | const n = 8 22 | 23 | var ( 24 | random = rand.New(rand.NewSource(time.Now().UnixNano())) 25 | ) 26 | 27 | func main() { 28 | d, err := dotstar.Open(&spi.Devfs{Dev: "/dev/spidev0.0", Mode: spi.Mode3}, n) 29 | if err != nil { 30 | panic(err) 31 | } 32 | 33 | // random blinking speed 34 | speed := time.Duration(random.Intn(700) + 100) 35 | ticker := time.NewTicker(time.Millisecond * speed) 36 | 37 | // catch signals and terminate the app 38 | sigc := make(chan os.Signal, 1) 39 | signal.Notify(sigc, 40 | syscall.SIGHUP, 41 | syscall.SIGINT, 42 | syscall.SIGTERM, 43 | syscall.SIGQUIT) 44 | 45 | // monitor for signals in the background 46 | go func() { 47 | s := <-sigc 48 | ticker.Stop() 49 | fmt.Println("\nreceived signal:", s) 50 | time.Sleep(speed) 51 | // turn off the LEDs 52 | for i := 0; i < n; i++ { 53 | d.SetRGBA(i, dotstar.RGBA{1, 1, 1, 0}) 54 | } 55 | d.Draw() 56 | time.Sleep(400 * time.Millisecond) 57 | d.Close() 58 | os.Exit(0) 59 | }() 60 | 61 | for _ = range ticker.C { 62 | // every x milliseconds, change the colors of each LED 63 | randLedColors(d) 64 | } 65 | } 66 | 67 | func randLedColors(d *dotstar.LEDs) { 68 | for i := 0; i < n; i++ { 69 | d.SetRGBA(i, dotstar.RGBA{ 70 | R: randByte(), 71 | G: randByte(), 72 | B: randByte(), 73 | A: randByte(), 74 | }) 75 | } 76 | 77 | if err := d.Draw(); err != nil { 78 | panic(err) 79 | } 80 | } 81 | 82 | func randByte() byte { 83 | return byte(random.Intn(256)) 84 | } 85 | -------------------------------------------------------------------------------- /lcdrgbbacklight/README.md: -------------------------------------------------------------------------------- 1 | #Grove - LCD RGB Backlight 2 | 3 | [![GoDoc](http://godoc.org/github.com/goiot/devices/lcdgobacklight?status.svg)](http://godoc.org/github.com/goiot/devices/lcdgobacklight) 4 | 5 | [Manufacturer info](http://www.seeedstudio.com/wiki/Grove_-_LCD_RGB_Backlight) 6 | 7 | This device is an i2c colorful RGB backlight display module with Grove compatible 8 | 4pin I2C interface. 9 | This display supports scrolling as well as English and Japanese fonts plus custom characters using [CGRAM](http://www.8051projects.net/lcd-interfacing/lcd-custom-character.php). 10 | 11 | ![Grove - LCD RGB Backlight](http://www.seeedstudio.com/wiki/images/thumb/0/03/Serial_LEC_RGB_Backlight_Lcd.jpg/500px-Serial_LEC_RGB_Backlight_Lcd.jpg) 12 | 13 | ##Datasheets: 14 | 15 | * [LCD Datasheet](http://www.seeedstudio.com/wiki/images/0/03/JHD1214Y_YG_1.0.pdf) 16 | * [BackLight Datasheet](http://www.seeedstudio.com/wiki/images/1/1c/PCA9633.pdf) -------------------------------------------------------------------------------- /lcdrgbbacklight/characters.go: -------------------------------------------------------------------------------- 1 | package lcdrgbbacklight 2 | 3 | // CustomLCDChars is a map of CGRAM characters that can be loaded 4 | // into a LCD screen to display custom characters. Some LCD screens such 5 | // as the Grove screen (jhd1313m1) isn't loaded with latin 1 characters. 6 | // It's up to the developer to load the set up to 8 custom characters and 7 | // update the input text so the character is swapped by a byte reflecting 8 | // the position of the custom character to use. 9 | // See SetCustomChar 10 | var CustomLCDChars = map[string][8]byte{ 11 | "é": [8]byte{130, 132, 142, 145, 159, 144, 142, 128}, 12 | "è": [8]byte{136, 132, 142, 145, 159, 144, 142, 128}, 13 | "ê": [8]byte{132, 138, 142, 145, 159, 144, 142, 128}, 14 | "à": [8]byte{136, 134, 128, 142, 145, 147, 141, 128}, 15 | "â": [8]byte{132, 138, 128, 142, 145, 147, 141, 128}, 16 | "á": [8]byte{2, 4, 14, 1, 15, 17, 15, 0}, 17 | "î": [8]byte{132, 138, 128, 140, 132, 132, 142, 128}, 18 | "í": [8]byte{2, 4, 12, 4, 4, 4, 14, 0}, 19 | "û": [8]byte{132, 138, 128, 145, 145, 147, 141, 128}, 20 | "ù": [8]byte{136, 134, 128, 145, 145, 147, 141, 128}, 21 | "ñ": [8]byte{14, 0, 22, 25, 17, 17, 17, 0}, 22 | "ó": [8]byte{2, 4, 14, 17, 17, 17, 14, 0}, 23 | "heart": [8]byte{0, 10, 31, 31, 31, 14, 4, 0}, 24 | "smiley": [8]byte{0, 0, 10, 0, 0, 17, 14, 0}, 25 | "frowney": [8]byte{0, 0, 10, 0, 0, 0, 14, 17}, 26 | } 27 | -------------------------------------------------------------------------------- /lcdrgbbacklight/examples/helloworld/helloworld: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goiot/devices/09d1226fc8ea3630f18dfe9864baaae8529ff44d/lcdrgbbacklight/examples/helloworld/helloworld -------------------------------------------------------------------------------- /lcdrgbbacklight/examples/helloworld/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/goiot/devices/lcdrgbbacklight" 7 | "golang.org/x/exp/io/i2c" 8 | ) 9 | 10 | func main() { 11 | display, err := lcdrgbbacklight.Open( 12 | &i2c.Devfs{Dev: "/dev/i2c-1"}, 13 | ) 14 | if err != nil { 15 | panic(err) 16 | } 17 | defer display.Close() 18 | 19 | display.Write("Hello World!") 20 | display.SetRGB(0, 255, 0) 21 | 22 | time.Sleep(5 * time.Second) 23 | 24 | display.Clear() 25 | display.Home() 26 | display.SetRGB(255, 124, 0) 27 | 28 | display.SetCustomChar(0, lcdrgbbacklight.CustomLCDChars["smiley"]) 29 | display.Write("goodbye\nhave a nice day " + string(byte(0))) 30 | 31 | ticker := time.NewTicker(time.Millisecond * 200) 32 | go func() { 33 | for _ = range ticker.C { 34 | display.Scroll(false) 35 | } 36 | }() 37 | time.Sleep(4 * time.Second) 38 | ticker.Stop() 39 | } 40 | -------------------------------------------------------------------------------- /lcdrgbbacklight/lcdrgbbacklight.go: -------------------------------------------------------------------------------- 1 | // Package lcdrgbbacklight implements a driver for the Grove LCD RGB Backlight display. 2 | package lcdrgbbacklight 3 | 4 | import ( 5 | "fmt" 6 | "time" 7 | 8 | "golang.org/x/exp/io/i2c" 9 | "golang.org/x/exp/io/i2c/driver" 10 | ) 11 | 12 | // LCDRGBBacklight is a driver for the Jhd1313m1 LCD display which has two i2c addresses, 13 | // one belongs to a controller and the other controls solely the backlight. 14 | type LCDRGBBacklight struct { 15 | LCD *i2c.Device 16 | RGB *i2c.Device 17 | } 18 | 19 | // Open connects to the lcd and rgb openers, connects and sets up. 20 | func Open(o driver.Opener) (*LCDRGBBacklight, error) { 21 | lcdD, err := i2c.Open(o, lcdAddr) 22 | if err != nil { 23 | return nil, fmt.Errorf("LCD driver failed to connect - %v", err) 24 | } 25 | 26 | rgbD, err := i2c.Open(o, rgbAddr) 27 | if err != nil { 28 | return nil, fmt.Errorf("RGB driver failed to connect - %v", err) 29 | } 30 | 31 | display := &LCDRGBBacklight{ 32 | LCD: lcdD, 33 | RGB: rgbD, 34 | } 35 | 36 | time.Sleep(50 * time.Millisecond) 37 | if err := lcdD.Write([]byte{lcdCmd, lcdFunctionSet | lcd2Line}); err != nil { 38 | return nil, fmt.Errorf("LCD failed to initialize (part 1) - %v", err) 39 | } 40 | 41 | time.Sleep(100 * time.Millisecond) 42 | if err := lcdD.Write([]byte{lcdCmd, lcdDisplayControl | lcdDisplayOn}); err != nil { 43 | return nil, fmt.Errorf("LCD failed to initialize (part 2) - %v", err) 44 | } 45 | 46 | time.Sleep(100 * time.Millisecond) 47 | if err := display.Clear(); err != nil { 48 | return nil, fmt.Errorf("display failed to clear - %v", err) 49 | } 50 | 51 | if err := lcdD.Write([]byte{lcdCmd, lcdEntryModeSet | lcdEntryLeft | lcdEntryShiftDecrement}); err != nil { 52 | return nil, fmt.Errorf("failed to initialize (part 3) - %v", err) 53 | } 54 | 55 | // registry 0 56 | if err := display.setReg(0x0, 0x1); err != nil { 57 | return nil, fmt.Errorf("failed to set registry 0 - %v", err) 58 | } 59 | 60 | // registry 1 61 | if err := display.setReg(0x1, 0x0); err != nil { 62 | return nil, fmt.Errorf("failed to set registry 1 - %v", err) 63 | } 64 | 65 | // registry 8 66 | if err := display.setReg(0x08, 0xAA); err != nil { 67 | return nil, fmt.Errorf("failed to set registry 8 - %v", err) 68 | } 69 | 70 | if err := display.SetRGB(255, 255, 255); err != nil { 71 | return nil, fmt.Errorf("failed to set registry 8 - %v", err) 72 | } 73 | 74 | return display, nil 75 | } 76 | 77 | // SetRGB sets the Red Green Blue value of backlit. 78 | func (d *LCDRGBBacklight) SetRGB(r, g, b int) error { 79 | if err := d.setReg(regRed, byte(r)); err != nil { 80 | return err 81 | } 82 | if err := d.setReg(regGreen, byte(g)); err != nil { 83 | return err 84 | } 85 | return d.setReg(regBlue, byte(b)) 86 | } 87 | 88 | // Clear clears the text on the lCD display. 89 | func (d *LCDRGBBacklight) Clear() error { 90 | err := d.command([]byte{lcdClearDisplay}) 91 | return err 92 | } 93 | 94 | // Home sets the cursor to the origin position on the display. 95 | func (d *LCDRGBBacklight) Home() error { 96 | err := d.command([]byte{lcdReturnHome}) 97 | // This wait fixes a race condition when calling home and clear back to back. 98 | time.Sleep(2 * time.Millisecond) 99 | return err 100 | } 101 | 102 | // Write displays the passed message on the screen. 103 | func (d *LCDRGBBacklight) Write(message string) error { 104 | // This wait fixes an odd bug where the clear function doesn't always work properly. 105 | time.Sleep(1 * time.Millisecond) 106 | for _, val := range message { 107 | if val == '\n' { 108 | if err := d.SetPosition(16); err != nil { 109 | return err 110 | } 111 | continue 112 | } 113 | if err := d.LCD.Write([]byte{lcdData, byte(val)}); err != nil { 114 | return err 115 | } 116 | } 117 | return nil 118 | } 119 | 120 | // SetPosition sets the cursor and the data display to pos. 121 | // 0..15 are the positions in the first display line. 122 | // 16..32 are the positions in the second display line. 123 | func (d *LCDRGBBacklight) SetPosition(pos int) (err error) { 124 | if pos < 0 || pos > 31 { 125 | err = ErrInvalidPosition 126 | return 127 | } 128 | offset := byte(pos) 129 | if pos >= 16 { 130 | offset -= 16 131 | offset |= lcd2ndLineOffset 132 | } 133 | err = d.command([]byte{lcdSetDdramAddr | offset}) 134 | return 135 | } 136 | 137 | // Scroll scrolls the text on the display 138 | func (d *LCDRGBBacklight) Scroll(leftToRight bool) error { 139 | if leftToRight { 140 | return d.command([]byte{lcdCursorShift | lcdDisplayMove | lcdMoveLeft}) 141 | } 142 | 143 | return d.command([]byte{lcdCursorShift | lcdDisplayMove | lcdMoveRight}) 144 | } 145 | 146 | // CustomChar sets one of the 8 CGRAM locations with a custom character. 147 | // The custom character can be used by writing a byte of value 0 to 7. 148 | // When you are using LCD as 5x8 dots in function set then you can define a total of 8 user defined patterns 149 | // (1 Byte for each row and 8 rows for each pattern). 150 | // Use http://www.8051projects.net/lcd-interfacing/lcd-custom-character.php to create your own 151 | // characters. 152 | // To use a custom character, write byte value of the custom character position as a string after 153 | // having setup the custom character. 154 | func (d *LCDRGBBacklight) SetCustomChar(pos int, charMap [8]byte) error { 155 | if pos > 7 { 156 | return fmt.Errorf("can't set a custom character at a position greater than 7") 157 | } 158 | location := uint8(pos) 159 | if err := d.command([]byte{lcdSetCgramAddr | (location << 3)}); err != nil { 160 | return err 161 | } 162 | 163 | return d.LCD.Write(append([]byte{lcdData}, charMap[:]...)) 164 | } 165 | 166 | // Close cleans up the connections 167 | func (d *LCDRGBBacklight) Close() error { 168 | d.Clear() 169 | d.SetRGB(0, 0, 0) 170 | if err := d.LCD.Close(); err != nil { 171 | return err 172 | } 173 | if err := d.RGB.Close(); err != nil { 174 | return err 175 | } 176 | return nil 177 | } 178 | -------------------------------------------------------------------------------- /lcdrgbbacklight/protocol.go: -------------------------------------------------------------------------------- 1 | package lcdrgbbacklight 2 | 3 | import "errors" 4 | 5 | const ( 6 | lcdAddr = 0x3E 7 | rgbAddr = 0x62 8 | ) 9 | 10 | const ( 11 | regRed = 0x04 12 | regGreen = 0x03 13 | regBlue = 0x02 14 | 15 | lcdClearDisplay = 0x01 16 | lcdReturnHome = 0x02 17 | lcdEntryModeSet = 0x04 18 | lcdDisplayControl = 0x08 19 | lcdCursorShift = 0x10 20 | lcdFunctionSet = 0x20 21 | lcdSetCgramAddr = 0x40 22 | lcdSetDdramAddr = 0x80 23 | lcdEntryRight = 0x00 24 | lcdEntryLeft = 0x02 25 | lcdEntryShiftIncrement = 0x01 26 | lcdEntryShiftDecrement = 0x00 27 | lcdDisplayOn = 0x04 28 | lcdDisplayOff = 0x00 29 | lcdCursorOn = 0x02 30 | lcdCursorOff = 0x00 31 | lcdBlinkOn = 0x01 32 | lcdBlinkOff = 0x00 33 | lcdDisplayMove = 0x08 34 | lcdCursorMove = 0x00 35 | lcdMoveRight = 0x04 36 | lcdMoveLeft = 0x00 37 | lcd2Line = 0x08 38 | lcdCmd = 0x80 39 | lcdData = 0x40 40 | 41 | lcd2ndLineOffset = 0x40 42 | ) 43 | 44 | var ( 45 | ErrInvalidPosition = errors.New("Invalid position value") 46 | ) 47 | 48 | func (d *LCDRGBBacklight) setReg(cmd byte, data byte) error { 49 | // TODO(mattetti): reuse a buffer instead of reallocating 50 | if err := d.RGB.Write([]byte{cmd, data}); err != nil { 51 | return err 52 | } 53 | return nil 54 | } 55 | 56 | // TODO(mattetti): change the arg to be variadic 57 | func (d *LCDRGBBacklight) command(buf []byte) error { 58 | return d.LCD.Write(append([]byte{lcdCmd}, buf...)) 59 | } 60 | -------------------------------------------------------------------------------- /monochromeoled/README.md: -------------------------------------------------------------------------------- 1 | WIP, the package is coming soon... 2 | -------------------------------------------------------------------------------- /monochromeoled/examples/helloworld/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "image" 5 | "os" 6 | 7 | _ "image/png" 8 | 9 | "github.com/goiot/devices/monochromeoled" 10 | "golang.org/x/exp/io/i2c" 11 | ) 12 | 13 | func main() { 14 | rc, err := os.Open("./golang.png") 15 | if err != nil { 16 | panic(err) 17 | } 18 | defer rc.Close() 19 | 20 | m, _, err := image.Decode(rc) 21 | if err != nil { 22 | panic(err) 23 | } 24 | 25 | d, err := monochromeoled.Open(&i2c.Devfs{Dev: "/dev/i2c-1"}) 26 | if err != nil { 27 | panic(err) 28 | } 29 | defer d.Close() 30 | 31 | // clear the display before putting on anything 32 | if err := d.Clear(); err != nil { 33 | panic(err) 34 | } 35 | if err := d.SetImage(0, 0, m); err != nil { 36 | panic(err) 37 | } 38 | if err := d.Draw(); err != nil { 39 | panic(err) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /monochromeoled/monochromeoled.go: -------------------------------------------------------------------------------- 1 | // Package monochromeoled contains an Adafruit Monochrome OLED (SSD1306) 2 | // display driver. 3 | package monochromeoled 4 | 5 | import ( 6 | "fmt" 7 | "image" 8 | 9 | "golang.org/x/exp/io/i2c" 10 | "golang.org/x/exp/io/i2c/driver" 11 | ) 12 | 13 | const ( 14 | ssd1306_LCDWIDTH = 128 15 | ssd1306_LCDHEIGHT = 64 16 | 17 | addr = 0x3C // addr is the I2C address of the device. 18 | 19 | // On or off registers. 20 | ssd1306_DISPLAY_ON = 0xAF 21 | ssd1306_DISPLAY_OFF = 0xAE 22 | 23 | // Scrolling registers. 24 | ssd1306_ACTIVATE_SCROLL = 0x2F 25 | ssd1306_DEACTIVATE_SCROLL = 0x2E 26 | ssd1306_SET_VERTICAL_SCROLL_AREA = 0xA3 27 | ssd1306_RIGHT_HORIZONTAL_SCROLL = 0x26 28 | ssd1306_LEFT_HORIZONTAL_SCROLL = 0x27 29 | ssd1306_VERTICAL_AND_RIGHT_HORIZONTAL_SCROLL = 0x29 30 | ssd1306_VERTICAL_AND_LEFT_HORIZONTAL_SCROLL = 0x2A 31 | ) 32 | 33 | // OLED represents an SSD1306 OLED display. 34 | type OLED struct { 35 | dev *i2c.Device 36 | 37 | w int // width of the display 38 | h int // height of the display 39 | buf []byte // each pixel is represented by a bit 40 | } 41 | 42 | var initSeq = []byte{ 43 | 0xae, 44 | 0x00 | 0x00, // row offset 45 | 0x10 | 0x00, // column offset 46 | 0xd5, 0x40, 47 | 0xa8, ssd1306_LCDHEIGHT - 1, 48 | 0xd3, 0x00, // set display offset to no offset 49 | 0x40 | 0, 50 | 0x8d, 0x14, 51 | 0x20, 0x0, 52 | 53 | 0xA0 | 0x1, 54 | 0xC8, 55 | 0xda, 0x12, 56 | 0x81, 0xcf, // set contrast 57 | 0x9d, 0xf1, 58 | 0xdb, 0x40, 59 | 0xa4, 0xa6, 60 | 61 | 0x2e, 62 | 0xaf, 63 | } 64 | 65 | // Open opens an SSD1306 OLED display. Once not in use, it needs to 66 | // be close by calling Close. 67 | // The default width is 128, height is 64 if zero values are given. 68 | func Open(o driver.Opener) (*OLED, error) { 69 | w := ssd1306_LCDWIDTH 70 | h := ssd1306_LCDHEIGHT 71 | dev, err := i2c.Open(o, addr) 72 | if err != nil { 73 | return nil, err 74 | } 75 | if err := dev.Write(initSeq); err != nil { 76 | return nil, err 77 | } 78 | buf := make([]byte, w*(h/8)+1) 79 | buf[0] = 0x40 // start frame of pixel data 80 | return &OLED{dev: dev, w: w, h: h, buf: buf}, nil 81 | } 82 | 83 | // On turns on the display if it is off. 84 | func (o *OLED) On() error { 85 | return o.dev.Write([]byte{ssd1306_DISPLAY_ON}) 86 | } 87 | 88 | // Off turns off the display if it is on. 89 | func (o *OLED) Off() error { 90 | return o.dev.Write([]byte{ssd1306_DISPLAY_OFF}) 91 | } 92 | 93 | // Clear clears the entire display. 94 | func (o *OLED) Clear() error { 95 | for i := 1; i < len(o.buf); i++ { 96 | o.buf[i] = 0 97 | } 98 | return o.Draw() 99 | } 100 | 101 | func (o *OLED) SetPixel(x, y int, v byte) error { 102 | if x >= o.w || y >= o.h { 103 | return fmt.Errorf("(x=%v, y=%v) is out of bounds on this %vx%v display", x, y, o.w, o.h) 104 | } 105 | if v > 1 { 106 | return fmt.Errorf("value needs to be either 0 or 1; given %v", v) 107 | } 108 | i := 1 + x + (y/8)*o.w 109 | if v == 0 { 110 | o.buf[i] &= ^(1 << uint((y & 7))) 111 | } else { 112 | o.buf[i] |= 1 << uint((y & 7)) 113 | } 114 | return nil 115 | } 116 | 117 | // SetImage draws an image on the display buffer starting from x, y. 118 | // A call to Draw is required to display it on the OLED display. 119 | func (o *OLED) SetImage(x, y int, img image.Image) error { 120 | imgW := img.Bounds().Dx() 121 | imgH := img.Bounds().Dy() 122 | 123 | endX := x + imgW 124 | endY := y + imgH 125 | 126 | if endX >= o.w { 127 | endX = o.w 128 | } 129 | if endY >= o.h { 130 | endY = o.h 131 | } 132 | 133 | var imgI, imgY int 134 | for i := x; i < endX; i++ { 135 | imgY = 0 136 | for j := y; j < endY; j++ { 137 | r, g, b, _ := img.At(imgI, imgY).RGBA() 138 | var v byte 139 | if r+g+b > 0 { 140 | v = 0x1 141 | } 142 | if err := o.SetPixel(i, j, v); err != nil { 143 | return err 144 | } 145 | imgY++ 146 | } 147 | imgI++ 148 | } 149 | return nil 150 | } 151 | 152 | // Draw draws the intermediate pixel buffer on the display. 153 | // See SetPixel and SetImage to mutate the buffer. 154 | func (o *OLED) Draw() error { 155 | if err := o.dev.Write([]byte{ 156 | 0xa4, // write mode 157 | 0x40 | 0, // start line = 0 158 | 0x21, 0, ssd1306_LCDWIDTH, 159 | 0x22, 0, 7, 160 | }); err != nil { // the write mode 161 | return err 162 | } 163 | return o.dev.Write(o.buf) 164 | } 165 | 166 | // StartScroll starts scrolling in the horizontal direction starting from 167 | // startY column to endY column. 168 | func (o *OLED) EnableScroll(startY, endY int) error { 169 | panic("not implemented") 170 | } 171 | 172 | // StopStrolls stops the scrolling on the display. 173 | func (o *OLED) DisableScroll() error { 174 | panic("not implemented") 175 | } 176 | 177 | // Width returns the display width. 178 | func (o *OLED) Width() int { return o.w } 179 | 180 | // Height returns the display height. 181 | func (o *OLED) Height() int { return o.h } 182 | 183 | // Close closes the display. 184 | func (o *OLED) Close() error { 185 | return o.dev.Close() 186 | } 187 | -------------------------------------------------------------------------------- /oled96x96/README.md: -------------------------------------------------------------------------------- 1 | #Grove - OLED 96×96 2 | 3 | [![GoDoc](http://godoc.org/github.com/goiot/devices/oled96x96?status.svg)](http://godoc.org/github.com/goiot/devices/oled96x96) 4 | 5 | [Manufacturer info](http://www.seeedstudio.com/wiki/Grove_-_OLED_Display_1.12%22) 6 | 7 | This device is an i2c 16 color grayscale 96×96 dot matrix OLED display module with Grove compatible 8 | 4pin I2C interface. 9 | Grove - OLED 96×96 is constructed with 96×96 dot matrix OLED module LY120 and SSD1327 driver IC. 10 | 11 | ![Grove - OLED 96×96](http://www.seeedstudio.com/wiki/images/thumb/9/90/Oled1281281.jpg/500px-Oled1281281.jpg) 12 | 13 | ##Datasheets: 14 | 15 | * [SSD1327 driver Datasheet](http://garden.seeedstudio.com/images/8/82/SSD1327_datasheet.pdf) -------------------------------------------------------------------------------- /oled96x96/examples/helloworld/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/goiot/devices/oled96x96" 7 | "golang.org/x/exp/io/i2c" 8 | ) 9 | 10 | func main() { 11 | display, err := oled96x96.Open(&i2c.Devfs{ 12 | Dev: "/dev/i2c-1", 13 | }) 14 | if err != nil { 15 | panic(err) 16 | } 17 | defer display.Close() 18 | 19 | display.Write("Hello World!") 20 | 21 | time.Sleep(5 * time.Second) 22 | display.PositionCursor(0, 0) 23 | display.Write("Ciao World!") 24 | time.Sleep(1 * time.Second) 25 | display.Inverse() 26 | time.Sleep(1 * time.Second) 27 | display.Normal() 28 | time.Sleep(1 * time.Second) 29 | 30 | display.Off() 31 | } 32 | -------------------------------------------------------------------------------- /oled96x96/fonts.go: -------------------------------------------------------------------------------- 1 | package oled96x96 2 | 3 | type Font [][8]byte 4 | 5 | // DefaultFont returns a basic ascii font. Developers can implement their own font. 6 | func DefaultFont() Font { 7 | return asciiFont 8 | } 9 | 10 | // asciiFont is a basic font. 11 | // 8x8 Font ASCII 32 - 127 Implemented 12 | var asciiFont = Font{ 13 | {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 14 | {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 15 | {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 16 | {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 17 | {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 18 | {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 19 | {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 20 | {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 21 | {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 22 | {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 23 | {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 24 | {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 25 | {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 26 | {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 27 | {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 28 | {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 29 | {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 30 | {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 31 | {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 32 | {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 33 | {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 34 | {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 35 | {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 36 | {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 37 | {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 38 | {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 39 | {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 40 | {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 41 | {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 42 | {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 43 | {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 44 | {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 45 | 46 | {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 47 | {0x00, 0x00, 0x5F, 0x00, 0x00, 0x00, 0x00, 0x00}, 48 | {0x00, 0x00, 0x07, 0x00, 0x07, 0x00, 0x00, 0x00}, 49 | {0x00, 0x14, 0x7F, 0x14, 0x7F, 0x14, 0x00, 0x00}, 50 | {0x00, 0x24, 0x2A, 0x7F, 0x2A, 0x12, 0x00, 0x00}, 51 | {0x00, 0x23, 0x13, 0x08, 0x64, 0x62, 0x00, 0x00}, 52 | {0x00, 0x36, 0x49, 0x55, 0x22, 0x50, 0x00, 0x00}, 53 | {0x00, 0x00, 0x05, 0x03, 0x00, 0x00, 0x00, 0x00}, 54 | {0x00, 0x1C, 0x22, 0x41, 0x00, 0x00, 0x00, 0x00}, 55 | {0x00, 0x41, 0x22, 0x1C, 0x00, 0x00, 0x00, 0x00}, 56 | {0x00, 0x08, 0x2A, 0x1C, 0x2A, 0x08, 0x00, 0x00}, 57 | {0x00, 0x08, 0x08, 0x3E, 0x08, 0x08, 0x00, 0x00}, 58 | {0x00, 0xA0, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00}, 59 | {0x00, 0x08, 0x08, 0x08, 0x08, 0x08, 0x00, 0x00}, 60 | {0x00, 0x60, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00}, 61 | {0x00, 0x20, 0x10, 0x08, 0x04, 0x02, 0x00, 0x00}, 62 | {0x00, 0x3E, 0x51, 0x49, 0x45, 0x3E, 0x00, 0x00}, 63 | {0x00, 0x00, 0x42, 0x7F, 0x40, 0x00, 0x00, 0x00}, 64 | {0x00, 0x62, 0x51, 0x49, 0x49, 0x46, 0x00, 0x00}, 65 | {0x00, 0x22, 0x41, 0x49, 0x49, 0x36, 0x00, 0x00}, 66 | {0x00, 0x18, 0x14, 0x12, 0x7F, 0x10, 0x00, 0x00}, 67 | {0x00, 0x27, 0x45, 0x45, 0x45, 0x39, 0x00, 0x00}, 68 | {0x00, 0x3C, 0x4A, 0x49, 0x49, 0x30, 0x00, 0x00}, 69 | {0x00, 0x01, 0x71, 0x09, 0x05, 0x03, 0x00, 0x00}, 70 | {0x00, 0x36, 0x49, 0x49, 0x49, 0x36, 0x00, 0x00}, 71 | {0x00, 0x06, 0x49, 0x49, 0x29, 0x1E, 0x00, 0x00}, 72 | {0x00, 0x00, 0x36, 0x36, 0x00, 0x00, 0x00, 0x00}, 73 | {0x00, 0x00, 0xAC, 0x6C, 0x00, 0x00, 0x00, 0x00}, 74 | {0x00, 0x08, 0x14, 0x22, 0x41, 0x00, 0x00, 0x00}, 75 | {0x00, 0x14, 0x14, 0x14, 0x14, 0x14, 0x00, 0x00}, 76 | {0x00, 0x41, 0x22, 0x14, 0x08, 0x00, 0x00, 0x00}, 77 | {0x00, 0x02, 0x01, 0x51, 0x09, 0x06, 0x00, 0x00}, 78 | {0x00, 0x32, 0x49, 0x79, 0x41, 0x3E, 0x00, 0x00}, 79 | {0x00, 0x7E, 0x09, 0x09, 0x09, 0x7E, 0x00, 0x00}, 80 | {0x00, 0x7F, 0x49, 0x49, 0x49, 0x36, 0x00, 0x00}, 81 | {0x00, 0x3E, 0x41, 0x41, 0x41, 0x22, 0x00, 0x00}, 82 | {0x00, 0x7F, 0x41, 0x41, 0x22, 0x1C, 0x00, 0x00}, 83 | {0x00, 0x7F, 0x49, 0x49, 0x49, 0x41, 0x00, 0x00}, 84 | {0x00, 0x7F, 0x09, 0x09, 0x09, 0x01, 0x00, 0x00}, 85 | {0x00, 0x3E, 0x41, 0x41, 0x51, 0x72, 0x00, 0x00}, 86 | {0x00, 0x7F, 0x08, 0x08, 0x08, 0x7F, 0x00, 0x00}, 87 | {0x00, 0x41, 0x7F, 0x41, 0x00, 0x00, 0x00, 0x00}, 88 | {0x00, 0x20, 0x40, 0x41, 0x3F, 0x01, 0x00, 0x00}, 89 | {0x00, 0x7F, 0x08, 0x14, 0x22, 0x41, 0x00, 0x00}, 90 | {0x00, 0x7F, 0x40, 0x40, 0x40, 0x40, 0x00, 0x00}, 91 | {0x00, 0x7F, 0x02, 0x0C, 0x02, 0x7F, 0x00, 0x00}, 92 | {0x00, 0x7F, 0x04, 0x08, 0x10, 0x7F, 0x00, 0x00}, 93 | {0x00, 0x3E, 0x41, 0x41, 0x41, 0x3E, 0x00, 0x00}, 94 | {0x00, 0x7F, 0x09, 0x09, 0x09, 0x06, 0x00, 0x00}, 95 | {0x00, 0x3E, 0x41, 0x51, 0x21, 0x5E, 0x00, 0x00}, 96 | {0x00, 0x7F, 0x09, 0x19, 0x29, 0x46, 0x00, 0x00}, 97 | {0x00, 0x26, 0x49, 0x49, 0x49, 0x32, 0x00, 0x00}, 98 | {0x00, 0x01, 0x01, 0x7F, 0x01, 0x01, 0x00, 0x00}, 99 | {0x00, 0x3F, 0x40, 0x40, 0x40, 0x3F, 0x00, 0x00}, 100 | {0x00, 0x1F, 0x20, 0x40, 0x20, 0x1F, 0x00, 0x00}, 101 | {0x00, 0x3F, 0x40, 0x38, 0x40, 0x3F, 0x00, 0x00}, 102 | {0x00, 0x63, 0x14, 0x08, 0x14, 0x63, 0x00, 0x00}, 103 | {0x00, 0x03, 0x04, 0x78, 0x04, 0x03, 0x00, 0x00}, 104 | {0x00, 0x61, 0x51, 0x49, 0x45, 0x43, 0x00, 0x00}, 105 | {0x00, 0x7F, 0x41, 0x41, 0x00, 0x00, 0x00, 0x00}, 106 | {0x00, 0x02, 0x04, 0x08, 0x10, 0x20, 0x00, 0x00}, 107 | {0x00, 0x41, 0x41, 0x7F, 0x00, 0x00, 0x00, 0x00}, 108 | {0x00, 0x04, 0x02, 0x01, 0x02, 0x04, 0x00, 0x00}, 109 | {0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00}, 110 | {0x00, 0x01, 0x02, 0x04, 0x00, 0x00, 0x00, 0x00}, 111 | {0x00, 0x20, 0x54, 0x54, 0x54, 0x78, 0x00, 0x00}, 112 | {0x00, 0x7F, 0x48, 0x44, 0x44, 0x38, 0x00, 0x00}, 113 | {0x00, 0x38, 0x44, 0x44, 0x28, 0x00, 0x00, 0x00}, 114 | {0x00, 0x38, 0x44, 0x44, 0x48, 0x7F, 0x00, 0x00}, 115 | {0x00, 0x38, 0x54, 0x54, 0x54, 0x18, 0x00, 0x00}, 116 | {0x00, 0x08, 0x7E, 0x09, 0x02, 0x00, 0x00, 0x00}, 117 | {0x00, 0x18, 0xA4, 0xA4, 0xA4, 0x7C, 0x00, 0x00}, 118 | {0x00, 0x7F, 0x08, 0x04, 0x04, 0x78, 0x00, 0x00}, 119 | {0x00, 0x00, 0x7D, 0x00, 0x00, 0x00, 0x00, 0x00}, 120 | {0x00, 0x80, 0x84, 0x7D, 0x00, 0x00, 0x00, 0x00}, 121 | {0x00, 0x7F, 0x10, 0x28, 0x44, 0x00, 0x00, 0x00}, 122 | {0x00, 0x41, 0x7F, 0x40, 0x00, 0x00, 0x00, 0x00}, 123 | {0x00, 0x7C, 0x04, 0x18, 0x04, 0x78, 0x00, 0x00}, 124 | {0x00, 0x7C, 0x08, 0x04, 0x7C, 0x00, 0x00, 0x00}, 125 | {0x00, 0x38, 0x44, 0x44, 0x38, 0x00, 0x00, 0x00}, 126 | {0x00, 0xFC, 0x24, 0x24, 0x18, 0x00, 0x00, 0x00}, 127 | {0x00, 0x18, 0x24, 0x24, 0xFC, 0x00, 0x00, 0x00}, 128 | {0x00, 0x00, 0x7C, 0x08, 0x04, 0x00, 0x00, 0x00}, 129 | {0x00, 0x48, 0x54, 0x54, 0x24, 0x00, 0x00, 0x00}, 130 | {0x00, 0x04, 0x7F, 0x44, 0x00, 0x00, 0x00, 0x00}, 131 | {0x00, 0x3C, 0x40, 0x40, 0x7C, 0x00, 0x00, 0x00}, 132 | {0x00, 0x1C, 0x20, 0x40, 0x20, 0x1C, 0x00, 0x00}, 133 | {0x00, 0x3C, 0x40, 0x30, 0x40, 0x3C, 0x00, 0x00}, 134 | {0x00, 0x44, 0x28, 0x10, 0x28, 0x44, 0x00, 0x00}, 135 | {0x00, 0x1C, 0xA0, 0xA0, 0x7C, 0x00, 0x00, 0x00}, 136 | {0x00, 0x44, 0x64, 0x54, 0x4C, 0x44, 0x00, 0x00}, 137 | {0x00, 0x08, 0x36, 0x41, 0x00, 0x00, 0x00, 0x00}, 138 | {0x00, 0x00, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00}, 139 | {0x00, 0x41, 0x36, 0x08, 0x00, 0x00, 0x00, 0x00}, 140 | {0x00, 0x02, 0x01, 0x01, 0x02, 0x01, 0x00, 0x00}, 141 | {0x00, 0x02, 0x05, 0x05, 0x02, 0x00, 0x00, 0x00}, 142 | } 143 | -------------------------------------------------------------------------------- /oled96x96/oled96x96.go: -------------------------------------------------------------------------------- 1 | // Package oled96x96 implements a driver for the Grove OLED grayscale 96x96 display. 2 | package oled96x96 3 | 4 | import ( 5 | "fmt" 6 | "time" 7 | 8 | "golang.org/x/exp/io/i2c" 9 | "golang.org/x/exp/io/i2c/driver" 10 | ) 11 | 12 | // OLED96x96 represents the Grove Oled 96x96 display. 13 | type OLED96x96 struct { 14 | Device *i2c.Device 15 | Font Font 16 | grayH byte 17 | grayL byte 18 | } 19 | 20 | // Open connects to the passed driver and sets things up. 21 | func Open(o driver.Opener) (*OLED96x96, error) { 22 | device, err := i2c.Open(o, address) 23 | if err != nil { 24 | return nil, err 25 | } 26 | 27 | display := &OLED96x96{Device: device, Font: DefaultFont()} 28 | 29 | // Unlock OLED driver IC MCU interface from entering command. i.e: Accept commands 30 | // Note: locking/unlocking could be exposed to developers later on if needed. 31 | if err := display.sendCmd(lockUnlockCmd, 0x12); err != nil { 32 | return display, fmt.Errorf("unlocking OLED interface failed - %v", err) 33 | } 34 | 35 | if err := display.Off(); err != nil { 36 | return display, fmt.Errorf("turning off the OLED disaply failed - %v", err) 37 | } 38 | 39 | // set multiplex ratio, in out case the display is a 96x96 (0x5f = 96) 40 | if err := display.sendCmd(0xA8, 0x5F); err != nil { 41 | return display, fmt.Errorf("setting mux ratio failed - %v", err) 42 | } 43 | 44 | // set the start line 45 | if err := display.sendCmd(startLineCmd, 0x00); err != nil { 46 | return display, fmt.Errorf("setting the start line failed - %v", err) 47 | } 48 | 49 | // set the display offset 50 | if err := display.sendCmd(displayOffsetCmd, 0x60); err != nil { 51 | return display, fmt.Errorf("setting the display offset failed - %v", err) 52 | } 53 | 54 | // set the remap to vertical mode 55 | if err := display.VerticalMode(); err != nil { 56 | return display, fmt.Errorf("setting vertical mode failed - %v", err) 57 | } 58 | 59 | // set the VDD regulator 60 | if err := display.sendCmd(0xAB, 0x01); err != nil { 61 | return display, fmt.Errorf("setting vdd internal failed - %v", err) 62 | } 63 | 64 | if err := display.ContrastLevel(100); err != nil { 65 | return display, fmt.Errorf("setting contrast failed - %v", err) 66 | } 67 | 68 | // set the phase length 69 | if err := display.sendCmd(0xB1, 0x51); err != nil { 70 | return display, fmt.Errorf("setting the phase length failed - %v", err) 71 | } 72 | 73 | // set front Clock Divider / Oscillator Frequency 74 | if err := display.sendCmd(0xB3, 0x01); err != nil { 75 | return display, fmt.Errorf("setting the display clock failed - %v", err) 76 | } 77 | 78 | // set linear gray scale table 79 | if err := display.sendCmd(0xB9); err != nil { 80 | return display, fmt.Errorf("setting the gray scale table failed - %v", err) 81 | } 82 | 83 | // set pre charge voltage 84 | if err := display.sendCmd(0xBC, 0x08); err != nil { 85 | return display, fmt.Errorf("setting pre charge voltage failed - %v", err) 86 | } 87 | 88 | if err := display.sendCmd(0xBE, 0x07); err != nil { 89 | return display, fmt.Errorf("setting VCOMH failed - %v", err) 90 | } 91 | 92 | if err := display.sendCmd(0xB6, 0x01); err != nil { 93 | return display, fmt.Errorf("setting second pre-charge period failed - %v", err) 94 | } 95 | 96 | if err := display.sendCmd(0xD5, 0x62); err != nil { 97 | return display, fmt.Errorf("enabling second precharge and internal vsl failed - %v", err) 98 | } 99 | 100 | if err := display.Normal(); err != nil { 101 | return display, fmt.Errorf("setting the display to normal failed - %v", err) 102 | } 103 | 104 | if err := display.DisableScroll(); err != nil { 105 | return display, fmt.Errorf("disabling scrolling failed - %v", err) 106 | } 107 | 108 | if err := display.On(); err != nil { 109 | return display, fmt.Errorf("turning on the display failed - %v", err) 110 | } 111 | 112 | time.Sleep(100 * time.Millisecond) 113 | 114 | // Row Address 115 | // This triple byte command specifies row start address and end address of the display data RAM. This 116 | // command also sets the row address pointer to row start address. This pointer is used to define the current 117 | // read/write row address in graphic display data RAM 118 | // start at 0 end at 95 119 | display.sendCmd(0x75, 0x00, 0x5f) 120 | 121 | // Column Address 122 | // This triple byte command specifies column start address and end address of the display data RAM. This 123 | // command also sets the column address pointer to column start address. This pointer is used to define the 124 | // current read/write column address in graphic display data RAM. If horizontal address increment mode is 125 | // enabled by command A0h, after finishing read/write one column data, it is incremented automatically to the 126 | // next column address. Whenever the column address pointer finishes accessing the end column address, it is 127 | // reset back to start column address and the row address is incremented to the next row. 128 | // 129 | // Start from 8th Column of driver IC. This is 0th Column for OLED 130 | // End at (8 + 47)th column. Each Column has 2 pixels(segments) 131 | // 132 | display.sendCmd(setColAddrCmd, 0x08, 0x37) 133 | 134 | display.Clear() 135 | 136 | // Init gray level for text. 137 | display.grayH = 0xF0 138 | display.grayL = 0x0F 139 | 140 | if err := display.Normal(); err != nil { 141 | return display, fmt.Errorf("setting the display to normal failed - %v", err) 142 | } 143 | 144 | if err := display.VerticalMode(); err != nil { 145 | return display, fmt.Errorf("setting vertical mode failed - %v", err) 146 | } 147 | 148 | if err := display.PositionCursor(0, 0); err != nil { 149 | return display, fmt.Errorf("resetting cursor's position failed - %v", err) 150 | } 151 | 152 | return display, nil 153 | } 154 | 155 | // Close takes care of cleaning things up. 156 | func (o *OLED96x96) Close() error { 157 | return o.Device.Close() 158 | } 159 | 160 | // Off turns the OLED panel display OFF 161 | func (o *OLED96x96) Off() error { 162 | return o.sendCmd(displayOffCmd) 163 | } 164 | 165 | // On turns the OLED panel display ON 166 | func (o *OLED96x96) On() error { 167 | return o.sendCmd(displayOnCmd) 168 | } 169 | 170 | // Clear clears the whole screen. Should be used before starting a fresh start or after scroll deactivation. 171 | // This function also sets the cursor to top left corner. 172 | func (o *OLED96x96) Clear() error { 173 | // 48*96 = 4608 174 | nullData := make([]byte, 4609) 175 | nullData[0] = dataCmd 176 | return o.Device.Write(nullData) 177 | } 178 | 179 | // Normal sets the display in mormal mode (colors aren't inversed) 180 | func (o *OLED96x96) Normal() error { 181 | return o.sendCmd(normalDisplayCmd) 182 | } 183 | 184 | // Inverse sets the display to inverse mode (colors are inversed) 185 | func (o *OLED96x96) Inverse() error { 186 | return o.sendCmd(inverseDisplayCmd) 187 | } 188 | 189 | // ContrastLevel sets the contrast ratio of OLED display. 190 | // The level can be any number between 0 - 255. 191 | func (o *OLED96x96) ContrastLevel(level int) error { 192 | if level < 0 || level > 255 { 193 | return fmt.Errorf("invalid contrast level: %d, should be between 0-255", level) 194 | } 195 | return o.sendCmd(contrastLevelCmd, byte(level)) 196 | } 197 | 198 | // HorizontalMode configures the display to horizontal addressing mode. 199 | func (o *OLED96x96) HorizontalMode() error { 200 | // horizontal mode 201 | if err := o.sendCmd(0xA0, 0x42); err != nil { 202 | return err 203 | } 204 | // row address (0 to 95) 205 | if err := o.sendCmd(0x75, 0x00, 0x5f); err != nil { 206 | return err 207 | } 208 | // col address 209 | if err := o.sendCmd(0x15, 0x08, 0x37); err != nil { 210 | return err 211 | } 212 | return nil 213 | } 214 | 215 | // VerticalMode configures the display to vertical addressing mode. 216 | // The display must be set to vertical mode before printing text. 217 | func (o *OLED96x96) VerticalMode() error { 218 | return o.sendCmd(0xA0, 0x46) 219 | } 220 | 221 | // PositionCursor sets the text's position (cursor) to Xth Text Row, Yth Text Column. 222 | // The 96x96 OLED is divided into 12 rows and 12 Columns of text. 223 | // These text row and columns should not be confused with the OLED's Row and Column. 224 | func (o *OLED96x96) PositionCursor(row, col int) error { 225 | // start at 8 226 | startCol := 0x08 + byte(col*4) 227 | if err := o.sendCmd(setColAddrCmd, startCol, 0x37); err != nil { 228 | return fmt.Errorf("failed to set the column - %v", err) 229 | } 230 | 231 | // Row Address 232 | if err := o.sendCmd(0x75, byte(row*8), 0x07+(byte(row*8))); err != nil { 233 | return fmt.Errorf("failed to set the row - %v", err) 234 | } 235 | 236 | return nil 237 | } 238 | 239 | // Write prints the content of the passed text at the cursor's. 240 | func (o *OLED96x96) Write(txt string) error { 241 | var c, bit1, bit2 byte 242 | letterLen := len(o.Font) 243 | 244 | // TODO (mattetti): support unicode 245 | pushChar := func(r rune) error { 246 | n := int(r) 247 | var j uint8 248 | for i := 0; i < 8; i = i + 2 { 249 | for j = 0; j < 8; j++ { 250 | c, bit1, bit2 = 0x00, 0x00, 0x00 251 | // Character is constructed two pixel at a time using vertical mode from the default 8x8 font 252 | // Guard to prevent using characters not supported in the used font. 253 | if n <= letterLen { 254 | bit1 = (o.Font[n][i] >> j) & 0x01 255 | bit2 = (o.Font[n][i+1] >> j) & 0x01 256 | } 257 | // Each bit is changed to a nibble 258 | if bit1 > 0 { 259 | c |= o.grayH 260 | } else { 261 | c |= 0x00 262 | } 263 | if bit2 > 0 { 264 | c |= o.grayL 265 | } else { 266 | c |= 0x00 267 | } 268 | if err := o.sendData(c); err != nil { 269 | return err 270 | } 271 | } 272 | } 273 | return nil 274 | } 275 | 276 | for _, b := range txt { 277 | if err := pushChar(b); err != nil { 278 | return err 279 | } 280 | } 281 | 282 | return nil 283 | } 284 | 285 | // DrawBitmap displays a binary bitmap on the OLED matrix. 286 | // The data is provided through a slice holding bitmap. 287 | func (o *OLED96x96) DrawBitmap(bitmap []byte) error { 288 | panic("not implemented") 289 | } 290 | 291 | // HorizontalScrollProperties defines the scrolling behavior. 292 | // StartRow must be in the 0-127 range 293 | // EndRow must be in the 0-127 range and greater than StartRow 294 | // StartColumn must be between 0 and 63. 295 | // EndColumn must be in the 0 and 63 range and greater than StartColumn 296 | func (o *OLED96x96) HorizontalScrollProperties( 297 | direction ScrollDirection, 298 | startRow int, 299 | endRow int, 300 | startColumn int, 301 | endColumn int, 302 | scrollSpeed ScrollSpeed) error { 303 | return nil 304 | } 305 | 306 | // EnableScroll enables and starts scrolling 307 | func (o *OLED96x96) EnableScroll() error { 308 | panic("not implemented") 309 | } 310 | 311 | // DisableScroll disables and stops scrolling 312 | func (o *OLED96x96) DisableScroll() error { 313 | panic("not implemented") 314 | } 315 | -------------------------------------------------------------------------------- /oled96x96/oled96x96_test.go: -------------------------------------------------------------------------------- 1 | package oled96x96_test 2 | 3 | import ( 4 | "github.com/goiot/devices/oled96x96" 5 | "golang.org/x/exp/io/i2c" 6 | ) 7 | 8 | func Example() { 9 | bus := &i2c.Devfs{ 10 | // change the following value if you use another bus 11 | Dev: "/dev/i2c-1", 12 | } 13 | 14 | display, err := oled96x96.Open(bus) 15 | if err != nil { 16 | panic(err) 17 | } 18 | defer display.Close() 19 | 20 | display.Write("Hello World!") 21 | } 22 | -------------------------------------------------------------------------------- /oled96x96/protocol.go: -------------------------------------------------------------------------------- 1 | package oled96x96 2 | 3 | // ScrollDirection is the type determining the scrolling direction of text 4 | type ScrollDirection byte 5 | 6 | // ScrollSpeed is the type determining the speed of scrolling 7 | type ScrollSpeed byte 8 | 9 | var ( 10 | ScrollLeft ScrollDirection = 0x00 11 | ScrollRight ScrollDirection = 0x01 12 | 13 | Scroll2Frames ScrollSpeed = 0x7 14 | Scroll3Frames ScrollSpeed = 0x4 15 | Scroll4Frames ScrollSpeed = 0x5 16 | Scroll5Frames ScrollSpeed = 0x0 17 | Scroll25Frames ScrollSpeed = 0x6 18 | Scroll64Frames ScrollSpeed = 0x1 19 | Scroll128Frames ScrollSpeed = 0x2 20 | Scroll256Frames ScrollSpeed = 0x3 21 | ) 22 | 23 | var ( 24 | // buffer sent to indicate the following data belongs to a command 25 | cmdCmdBuf = []byte{cmdCmd, 0x0} 26 | dataCmdBuf = []byte{dataCmd, 0x0} 27 | ) 28 | 29 | const ( 30 | // VerticalModeFlag = 01 31 | // HorizontalModeFlag = 02 32 | 33 | // address is the i2c address of the device 34 | address = 0x3c 35 | 36 | cmdCmd byte = 0x80 37 | dataCmd byte = 0x40 38 | lockUnlockCmd byte = 0xFD // takes a 2nd arg byte 39 | startLineCmd byte = 0xA1 // takes a 2nd arg byte 40 | displayOffCmd byte = 0xAE 41 | displayOnCmd byte = 0xAF 42 | displayOffsetCmd byte = 0xA2 // takes a 2nd arg byte 43 | setColAddrCmd byte = 0x15 // takes 3 arg bytes 44 | 45 | normalDisplayCmd byte = 0xA4 46 | inverseDisplayCmd byte = 0xA7 47 | activateScrollCmd byte = 0x2F 48 | dectivateScrollCmd byte = 0x2E 49 | contrastLevelCmd byte = 0x81 50 | ) 51 | 52 | // sendCmd sends the passed data preluded by the command byte 53 | func (o *OLED96x96) sendCmd(buf ...byte) error { 54 | for _, b := range buf { 55 | cmdCmdBuf[1] = b 56 | if err := o.Device.Write(cmdCmdBuf); err != nil { 57 | return err 58 | } 59 | } 60 | return nil 61 | } 62 | 63 | // sendData does what you expect it does and maybe even more 64 | func (o *OLED96x96) sendData(buf ...byte) error { 65 | for _, b := range buf { 66 | dataCmdBuf[1] = b 67 | if err := o.Device.Write(dataCmdBuf); err != nil { 68 | return err 69 | } 70 | } 71 | return nil 72 | } 73 | -------------------------------------------------------------------------------- /piglow/README.md: -------------------------------------------------------------------------------- 1 | #PiGlow 2 | 3 | [![GoDoc](http://godoc.org/github.com/goiot/devices/piglow?status.svg)](http://godoc.org/github.com/goiot/devices/piglow) 4 | 5 | [Manufacturer info](https://shop.pimoroni.com/products/piglow) 6 | 7 | ![PiGlow](https://cdn.shopify.com/s/files/1/0174/1800/products/PiGlow-3_1024x1024.gif?v=1424952533) 8 | 9 | The PiGlow is a small add on board for the Raspberry Pi that provides 18 individually controllable LEDs. This board uses the 10 | SN3218 8-bit 18-channel PWM chip to drive 18 surface mount LEDs. Communication is done via I2C over the GPIO header with a bus address of 0x54. 11 | Each LED can be set to a PWM value of between 0 and 255. 12 | 13 | ##Datasheet: 14 | 15 | * [SN3218 Datasheet](https://github.com/pimoroni/piglow/raw/master/sn3218-datasheet.pdf) 16 | -------------------------------------------------------------------------------- /piglow/example_test.go: -------------------------------------------------------------------------------- 1 | package piglow_test 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/goiot/devices/piglow" 7 | "golang.org/x/exp/io/i2c" 8 | ) 9 | 10 | func Example() { 11 | p, err := piglow.Open(&i2c.Devfs{Dev: "/dev/i2c-1"}) 12 | if err != nil { 13 | panic(err) 14 | } 15 | 16 | if err := p.Setup(); err != nil { 17 | panic(err) 18 | } 19 | 20 | brightness := 0 21 | for i := 0; i < 10; i++ { 22 | brightness ^= 1 23 | p.SetBrightness(brightness) 24 | time.Sleep(300 * time.Millisecond) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /piglow/examples/strobe.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/goiot/devices/piglow" 7 | "golang.org/x/exp/io/i2c" 8 | ) 9 | 10 | func main() { 11 | p, err := piglow.Open(&i2c.Devfs{Dev: "/dev/i2c-1"}) 12 | if err != nil { 13 | panic(err) 14 | } 15 | 16 | defer func() { 17 | p.Shutdown() 18 | p.Close() 19 | }() 20 | 21 | time.Sleep(50 * time.Millisecond) 22 | for i := 1; i <= 18; i++ { 23 | if err := p.SetLEDBrightness(i, 1); err != nil { 24 | panic(err) 25 | } 26 | time.Sleep(10 * time.Millisecond) 27 | } 28 | 29 | time.Sleep(50 * time.Millisecond) 30 | for i := 18; i > 0; i-- { 31 | if err := p.SetLEDBrightness(i, 0); err != nil { 32 | panic(err) 33 | } 34 | time.Sleep(10 * time.Millisecond) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /piglow/piglow.go: -------------------------------------------------------------------------------- 1 | // Package piglow implements a driver for the Pimoroni PiGlow. 2 | package piglow 3 | 4 | import ( 5 | "fmt" 6 | 7 | "golang.org/x/exp/io/i2c" 8 | "golang.org/x/exp/io/i2c/driver" 9 | ) 10 | 11 | const addr = 0x54 // address is the I2C address of the device. 12 | 13 | // PiGlow represents a PiGlow device 14 | type PiGlow struct { 15 | conn *i2c.Device 16 | } 17 | 18 | // Reset resets the internal registers 19 | func (p *PiGlow) Reset() error { 20 | return p.conn.Write([]byte{0x17, 0xFF}) 21 | } 22 | 23 | // Shutdown sets the software shutdown mode of the PiGlow 24 | func (p *PiGlow) Shutdown() error { 25 | return p.conn.Write([]byte{0x00, 0x00}) 26 | } 27 | 28 | // Enable enables the PiGlow for normal operations 29 | func (p *PiGlow) Enable() error { 30 | return p.conn.Write([]byte{0x00, 0x01}) 31 | } 32 | 33 | // Setup enables normal operations, resets the internal registers, and enables 34 | // all LED control registers 35 | func (p *PiGlow) Setup() error { 36 | if err := p.Reset(); err != nil { 37 | return err 38 | } 39 | if err := p.Enable(); err != nil { 40 | return err 41 | } 42 | if err := p.SetLEDControlRegister(1, 0xFF); err != nil { 43 | return err 44 | } 45 | if err := p.SetLEDControlRegister(2, 0xFF); err != nil { 46 | return err 47 | } 48 | if err := p.SetLEDControlRegister(3, 0xFF); err != nil { 49 | return err 50 | } 51 | return nil 52 | } 53 | 54 | // Open opens a new PiGlow. A PiGlow must be closed if no longer in use. 55 | // If the PiGlow has not been powered down since last use, it will be opened 56 | // with it's last programmed state. 57 | func Open(o driver.Opener) (*PiGlow, error) { 58 | conn, err := i2c.Open(o, addr) 59 | if err != nil { 60 | return nil, err 61 | } 62 | g := &PiGlow{conn: conn} 63 | return g, nil 64 | } 65 | 66 | // Close frees the underlying resources. It must be called once 67 | // the PiGlow is no longer in use. 68 | func (p *PiGlow) Close() error { 69 | return p.conn.Close() 70 | } 71 | 72 | // Green sets all the green LEDs to the level of 0-255. 73 | func (p *PiGlow) Green(level int) error { 74 | if err := p.conn.Write([]byte{0x04, byte(level)}); err != nil { 75 | return err 76 | } 77 | if err := p.conn.Write([]byte{0x06, byte(level)}); err != nil { 78 | return err 79 | } 80 | if err := p.conn.Write([]byte{0x0E, byte(level)}); err != nil { 81 | return err 82 | } 83 | if err := p.conn.Write([]byte{0x16, 0xFF}); err != nil { 84 | return err 85 | } 86 | return nil 87 | } 88 | 89 | // Blue sets all the blue LEDs to the level of 0-255. 90 | func (p *PiGlow) Blue(level int) error { 91 | if err := p.conn.Write([]byte{0x05, byte(level)}); err != nil { 92 | return err 93 | } 94 | if err := p.conn.Write([]byte{0x0C, byte(level)}); err != nil { 95 | return err 96 | } 97 | if err := p.conn.Write([]byte{0x0F, byte(level)}); err != nil { 98 | return err 99 | } 100 | if err := p.conn.Write([]byte{0x16, 0xFF}); err != nil { 101 | return err 102 | } 103 | return nil 104 | } 105 | 106 | // Yellow sets all the yellow LEDs to the level of 0-255. 107 | func (p *PiGlow) Yellow(level int) error { 108 | if err := p.conn.Write([]byte{0x03, byte(level)}); err != nil { 109 | return err 110 | } 111 | if err := p.conn.Write([]byte{0x09, byte(level)}); err != nil { 112 | return err 113 | } 114 | if err := p.conn.Write([]byte{0x10, byte(level)}); err != nil { 115 | return err 116 | } 117 | if err := p.conn.Write([]byte{0x16, 0xFF}); err != nil { 118 | return err 119 | } 120 | return nil 121 | } 122 | 123 | // Orange sets all the orange LEDs to the level of 0-255. 124 | func (p *PiGlow) Orange(level int) error { 125 | if err := p.conn.Write([]byte{0x02, byte(level)}); err != nil { 126 | return err 127 | } 128 | if err := p.conn.Write([]byte{0x08, byte(level)}); err != nil { 129 | return err 130 | } 131 | if err := p.conn.Write([]byte{0x11, byte(level)}); err != nil { 132 | return err 133 | } 134 | if err := p.conn.Write([]byte{0x16, 0xFF}); err != nil { 135 | return err 136 | } 137 | return nil 138 | } 139 | 140 | // White sets all the white LEDs to the level of 0-255. 141 | func (p *PiGlow) White(level int) error { 142 | if err := p.conn.Write([]byte{0x0A, byte(level)}); err != nil { 143 | return err 144 | } 145 | if err := p.conn.Write([]byte{0x0B, byte(level)}); err != nil { 146 | return err 147 | } 148 | if err := p.conn.Write([]byte{0x0D, byte(level)}); err != nil { 149 | return err 150 | } 151 | if err := p.conn.Write([]byte{0x16, 0xFF}); err != nil { 152 | return err 153 | } 154 | return nil 155 | } 156 | 157 | // Red sets all the red LEDs to the level of 0-255. 158 | func (p *PiGlow) Red(level int) error { 159 | if err := p.conn.Write([]byte{0x01, byte(level)}); err != nil { 160 | return err 161 | } 162 | if err := p.conn.Write([]byte{0x07, byte(level)}); err != nil { 163 | return err 164 | } 165 | if err := p.conn.Write([]byte{0x12, byte(level)}); err != nil { 166 | return err 167 | } 168 | return p.conn.Write([]byte{0x16, 0xFF}) 169 | } 170 | 171 | // SetLEDControlRegister sets the control register 1-3 to the bitmask enables. 172 | // bitmask definition: 173 | // 0 - LED disabled 174 | // 1 - LED enabled 175 | // LED Control Register 1 - LED channel 1 to 6 bits 0-5 176 | // LED Control Register 2 - LED channel 7 to 12 bits 0-5 177 | // LED Control Register 3 - LED channel 13 to 18 bits 0-5 178 | func (p *PiGlow) SetLEDControlRegister(register, enables int) error { 179 | var address byte 180 | 181 | switch register { 182 | case 1: 183 | address = 0x13 184 | case 2: 185 | address = 0x14 186 | case 3: 187 | address = 0x15 188 | default: 189 | return fmt.Errorf("%d is an unknown register", register) 190 | } 191 | 192 | if err := p.conn.Write([]byte{address, byte(enables)}); err != nil { 193 | return err 194 | } 195 | return p.conn.Write([]byte{0x16, 0xFF}) 196 | } 197 | 198 | // SetLEDBrightness sets the led 1-18 to the level 0-255. 199 | func (p *PiGlow) SetLEDBrightness(led, level int) error { 200 | if err := p.conn.Write([]byte{byte(led), byte(level)}); err != nil { 201 | return err 202 | } 203 | return p.conn.Write([]byte{0x16, 0xFF}) 204 | } 205 | 206 | // SetBrightness sets all the LEDs to the level 0-255. 207 | func (p *PiGlow) SetBrightness(level int) error { 208 | for i := 1; i <= 18; i++ { 209 | if err := p.conn.Write([]byte{byte(i), byte(level)}); err != nil { 210 | return err 211 | } 212 | } 213 | return p.conn.Write([]byte{0x16, 0xFF}) 214 | } 215 | -------------------------------------------------------------------------------- /piglow/piglow_test.go: -------------------------------------------------------------------------------- 1 | package piglow 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "reflect" 7 | "testing" 8 | 9 | "golang.org/x/exp/io/i2c/driver" 10 | ) 11 | 12 | type opener struct { 13 | buf *bytes.Buffer 14 | } 15 | 16 | func (o opener) Open(addr int, tenbit bool) (driver.Conn, error) { 17 | return &conn{buf: o.buf}, nil 18 | } 19 | 20 | type conn struct { 21 | buf *bytes.Buffer 22 | } 23 | 24 | func (c conn) Tx(w, r []byte) error { 25 | if w != nil { 26 | if _, err := c.buf.Write(w); err != nil { 27 | return err 28 | } 29 | } 30 | if r != nil { 31 | if _, err := c.buf.Read(r); err != nil { 32 | return err 33 | } 34 | } 35 | return nil 36 | } 37 | 38 | func (conn) Close() error { 39 | return nil 40 | } 41 | 42 | func openPiGlow(t *testing.T) (*PiGlow, *bytes.Buffer) { 43 | o := opener{ 44 | buf: bytes.NewBuffer([]byte{}), 45 | } 46 | device, err := Open(o) 47 | if err != nil { 48 | t.Fatal(err) 49 | } 50 | return device, o.buf 51 | } 52 | 53 | func assert(t *testing.T, want, got interface{}) { 54 | if !reflect.DeepEqual(want, got) { 55 | t.Fatalf("got = %v, want = %v", got, want) 56 | } 57 | } 58 | 59 | func TestGreen(t *testing.T) { 60 | device, buf := openPiGlow(t) 61 | if err := device.Green(1); err != nil { 62 | t.Fatal(err) 63 | } 64 | want := []byte{ 65 | 0x04, 0x01, 66 | 0x06, 0x01, 67 | 0x0E, 0x01, 68 | 0x16, 0xFF, 69 | } 70 | assert(t, want, buf.Bytes()) 71 | } 72 | 73 | func TestBlue(t *testing.T) { 74 | device, buf := openPiGlow(t) 75 | if err := device.Blue(1); err != nil { 76 | t.Fatal(err) 77 | } 78 | want := []byte{ 79 | 0x05, 0x01, 80 | 0x0C, 0x01, 81 | 0x0F, 0x01, 82 | 0x16, 0xFF, 83 | } 84 | assert(t, want, buf.Bytes()) 85 | } 86 | 87 | func TestYellow(t *testing.T) { 88 | device, buf := openPiGlow(t) 89 | if err := device.Yellow(1); err != nil { 90 | t.Fatal(err) 91 | } 92 | want := []byte{ 93 | 0x03, 0x01, 94 | 0x09, 0x01, 95 | 0x010, 0x01, 96 | 0x16, 0xFF, 97 | } 98 | assert(t, want, buf.Bytes()) 99 | } 100 | 101 | func TestOrange(t *testing.T) { 102 | device, buf := openPiGlow(t) 103 | if err := device.Orange(1); err != nil { 104 | t.Fatal(err) 105 | } 106 | want := []byte{ 107 | 0x02, 0x01, 108 | 0x08, 0x01, 109 | 0x011, 0x01, 110 | 0x16, 0xFF, 111 | } 112 | assert(t, want, buf.Bytes()) 113 | } 114 | 115 | func TestWhite(t *testing.T) { 116 | device, buf := openPiGlow(t) 117 | if err := device.White(1); err != nil { 118 | t.Fatal(err) 119 | } 120 | want := []byte{ 121 | 0x0A, 0x01, 122 | 0x0B, 0x01, 123 | 0x0D, 0x01, 124 | 0x16, 0xFF, 125 | } 126 | assert(t, want, buf.Bytes()) 127 | } 128 | 129 | func TestRed(t *testing.T) { 130 | device, buf := openPiGlow(t) 131 | if err := device.Red(1); err != nil { 132 | t.Fatal(err) 133 | } 134 | want := []byte{ 135 | 0x01, 0x01, 136 | 0x07, 0x01, 137 | 0x012, 0x01, 138 | 0x16, 0xFF, 139 | } 140 | assert(t, want, buf.Bytes()) 141 | } 142 | 143 | func TestSetBrightness(t *testing.T) { 144 | device, buf := openPiGlow(t) 145 | if err := device.SetBrightness(10); err != nil { 146 | t.Fatal(err) 147 | } 148 | want := []byte{ 149 | 0x01, 0x0A, 150 | 0x02, 0x0A, 151 | 0x03, 0x0A, 152 | 0x04, 0x0A, 153 | 0x05, 0x0A, 154 | 0x06, 0x0A, 155 | 0x07, 0x0A, 156 | 0x08, 0x0A, 157 | 0x09, 0x0A, 158 | 0x0a, 0x0A, 159 | 0x0b, 0x0A, 160 | 0x0c, 0x0A, 161 | 0x0d, 0x0A, 162 | 0x0e, 0x0A, 163 | 0x0f, 0x0A, 164 | 0x10, 0x0A, 165 | 0x11, 0x0A, 166 | 0x012, 0x0A, 167 | 0x16, 0xFF, 168 | } 169 | assert(t, want, buf.Bytes()) 170 | } 171 | 172 | func TestSetLEDBrightness(t *testing.T) { 173 | device, buf := openPiGlow(t) 174 | 175 | var states = []struct { 176 | led int 177 | level int 178 | want []byte 179 | }{ 180 | {1, 5, []byte{0x1, 5, 0x16, 0xFF}}, 181 | {2, 10, []byte{0x2, 10, 0x16, 0xFF}}, 182 | {3, 15, []byte{0x3, 15, 0x16, 0xFF}}, 183 | {4, 20, []byte{0x4, 20, 0x16, 0xFF}}, 184 | {5, 25, []byte{0x5, 25, 0x16, 0xFF}}, 185 | {6, 30, []byte{0x6, 30, 0x16, 0xFF}}, 186 | {7, 35, []byte{0x7, 35, 0x16, 0xFF}}, 187 | {8, 40, []byte{0x8, 40, 0x16, 0xFF}}, 188 | {9, 45, []byte{0x9, 45, 0x16, 0xFF}}, 189 | {10, 50, []byte{0x0A, 50, 0x16, 0xFF}}, 190 | {11, 55, []byte{0x0B, 55, 0x16, 0xFF}}, 191 | {12, 60, []byte{0x0C, 60, 0x16, 0xFF}}, 192 | {13, 65, []byte{0x0D, 65, 0x16, 0xFF}}, 193 | {14, 70, []byte{0x0E, 70, 0x16, 0xFF}}, 194 | {15, 75, []byte{0x0F, 75, 0x16, 0xFF}}, 195 | {16, 80, []byte{0x10, 80, 0x16, 0xFF}}, 196 | {17, 85, []byte{0x11, 85, 0x16, 0xFF}}, 197 | {18, 90, []byte{0x12, 90, 0x16, 0xFF}}, 198 | } 199 | 200 | for _, state := range states { 201 | buf.Reset() 202 | if err := device.SetLEDBrightness(state.led, state.level); err != nil { 203 | t.Log(err) 204 | } 205 | assert(t, state.want, buf.Bytes()) 206 | } 207 | } 208 | 209 | func TestReset(t *testing.T) { 210 | device, buf := openPiGlow(t) 211 | if err := device.Reset(); err != nil { 212 | t.Fatal(err) 213 | } 214 | want := []byte{0x17, 0xff} 215 | assert(t, want, buf.Bytes()) 216 | } 217 | 218 | func TestShutdown(t *testing.T) { 219 | device, buf := openPiGlow(t) 220 | if err := device.Shutdown(); err != nil { 221 | t.Fatal(err) 222 | } 223 | want := []byte{0x00, 0x00} 224 | assert(t, want, buf.Bytes()) 225 | } 226 | 227 | func TestEnable(t *testing.T) { 228 | device, buf := openPiGlow(t) 229 | if err := device.Enable(); err != nil { 230 | t.Fatal(err) 231 | } 232 | want := []byte{0x00, 0x01} 233 | assert(t, want, buf.Bytes()) 234 | } 235 | 236 | func TestSetLEDControlRegister(t *testing.T) { 237 | device, buf := openPiGlow(t) 238 | 239 | var states = []struct { 240 | register int 241 | enables int 242 | want []byte 243 | err error 244 | }{ 245 | {1, 0xFF, []byte{0x13, 0xFF, 0x16, 0xFF}, nil}, 246 | {2, 0xFF, []byte{0x14, 0xFF, 0x16, 0xFF}, nil}, 247 | {3, 0xFF, []byte{0x15, 0xFF, 0x16, 0xFF}, nil}, 248 | {0, 0xFF, []byte{}, errors.New("0 is an unknown register")}, 249 | } 250 | 251 | for _, state := range states { 252 | buf.Reset() 253 | 254 | err := device.SetLEDControlRegister(state.register, state.enables) 255 | assert(t, state.want, buf.Bytes()) 256 | assert(t, state.err, err) 257 | } 258 | } 259 | 260 | func TestSetup(t *testing.T) { 261 | device, buf := openPiGlow(t) 262 | if err := device.Setup(); err != nil { 263 | t.Fatal(err) 264 | } 265 | got := []byte{ 266 | 0x17, 0xFF, 267 | 0x00, 0x01, 268 | 0x13, 0xFF, 269 | 0x16, 0xFF, 270 | 0x14, 0xFF, 271 | 0x16, 0xFF, 272 | 0x15, 0xFF, 273 | 0x16, 0xFF, 274 | } 275 | assert(t, got, buf.Bytes()) 276 | } 277 | --------------------------------------------------------------------------------