diff options
Diffstat (limited to 'vendor/github.com')
-rw-r--r-- | vendor/github.com/BurntSushi/xdg/COPYING | 19 | ||||
-rw-r--r-- | vendor/github.com/BurntSushi/xdg/README | 9 | ||||
-rw-r--r-- | vendor/github.com/BurntSushi/xdg/xdg.go | 236 | ||||
-rw-r--r-- | vendor/github.com/benburwell/gohue/.gitignore | 1 | ||||
-rw-r--r-- | vendor/github.com/benburwell/gohue/LICENSE | 340 | ||||
-rw-r--r-- | vendor/github.com/benburwell/gohue/README.md | 129 | ||||
-rw-r--r-- | vendor/github.com/benburwell/gohue/bridge.go | 342 | ||||
-rw-r--r-- | vendor/github.com/benburwell/gohue/group.go | 67 | ||||
-rw-r--r-- | vendor/github.com/benburwell/gohue/light.go | 266 | ||||
-rw-r--r-- | vendor/github.com/benburwell/gohue/scene.go | 129 | ||||
-rw-r--r-- | vendor/github.com/benburwell/gohue/schedule.go | 109 | ||||
-rw-r--r-- | vendor/github.com/cpucycle/astrotime/README.md | 44 | ||||
-rwxr-xr-x | vendor/github.com/cpucycle/astrotime/astrotime.go | 405 |
13 files changed, 2096 insertions, 0 deletions
diff --git a/vendor/github.com/BurntSushi/xdg/COPYING b/vendor/github.com/BurntSushi/xdg/COPYING new file mode 100644 index 0000000..b570b82 --- /dev/null +++ b/vendor/github.com/BurntSushi/xdg/COPYING @@ -0,0 +1,19 @@ + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + Version 2, December 2004 + + Copyright (C) 2004 Sam Hocevar <sam@hocevar.net> + + Everyone is permitted to copy and distribute verbatim or modified + copies of this license document, and changing it is allowed as long + as the name is changed. + + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. You just DO WHAT THE FUCK YOU WANT TO. + +This program is free software. It comes without any warranty, to +the extent permitted by applicable law. You can redistribute it +and/or modify it under the terms of the Do What The Fuck You Want +To Public License, Version 2, as published by Sam Hocevar. See +http://sam.zoy.org/wtfpl/COPYING for more details. diff --git a/vendor/github.com/BurntSushi/xdg/README b/vendor/github.com/BurntSushi/xdg/README new file mode 100644 index 0000000..0dffd6c --- /dev/null +++ b/vendor/github.com/BurntSushi/xdg/README @@ -0,0 +1,9 @@ +Provides convenience functions for reading configuration and data files +according to the XDG Base Directory Specification: + +http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html + +Install with + + go get github.com/BurntSushi/xdg + diff --git a/vendor/github.com/BurntSushi/xdg/xdg.go b/vendor/github.com/BurntSushi/xdg/xdg.go new file mode 100644 index 0000000..fddceb9 --- /dev/null +++ b/vendor/github.com/BurntSushi/xdg/xdg.go @@ -0,0 +1,236 @@ +package xdg + +import ( + "fmt" + "go/build" + "io/ioutil" + "os" + "path" + "strings" +) + +// Paths determines which directories to search. The first directory +// containing a matching file is used. Here is the order: +// +// Override is always checked first. +// +// Directories specified in the XDG spec are searched after "XDGSuffix" is +// appended. +// +// For configuration files, these are: +// +// $XDG_CONFIG_HOME (or $HOME/.config when not set) +// Directories in $XDG_CONFIG_DIRS (or /etc/xdg when not set) +// +// For data files, these are: +// +// $XDG_DATA_HOME (or $HOME/.local/share when not set) +// Directories in $XDG_DATA_DIRS (or /usr/local/share:/usr/share when not set) +// +// For runtime files, these are: +// +// $XDG_RUNTIME_DIR (or /tmp when not set; implementation defined) +// +// Finally, the directory specified by GoImportPath is searched in all +// source directories reported by the `go/build` package. +type Paths struct { + // When non-empty, this will be the first directory searched. + Override string + + // The suffix path appended to XDG directories. + // i.e., "wingo" and NOT "/home/andrew/.config/wingo" + XDGSuffix string + + // The path in which your data files live inside your project directory. + // This should include your Go import path plus the directory containing + // files in your repo. This is used as a last resort to find files. + // (And it will only work if your package was installed using the GOPATH + // environment.) + // + // N.B. XDGSuffix is not used here, + // i.e., "github.com/BurntSushi/wingo/config" + GoImportPath string +} + +// MustPanic takes the return values of ConfigFile or DataFile, reads the file +// into a []byte, and returns the bytes. +// +// If the operation does not succeed, it panics. +func (ps Paths) MustPanic(fpath string, err error) []byte { + if err != nil { + panic(err) + } + bs, err := ioutil.ReadFile(fpath) + if err != nil { + panic(err) + } + return bs +} + +// MustError is like MustPanic, but instead of panicing when something goes +// wrong, it prints the error to stderr and calls os.Exit(1). +func (ps Paths) MustError(fpath string, err error) []byte { + if err != nil { + fmt.Fprintf(os.Stderr, "Could not read %s: %s", fpath, err) + os.Exit(1) + } + bs, err := ioutil.ReadFile(fpath) + if err != nil { + fmt.Fprintf(os.Stderr, "Could not read %s: %s", fpath, err) + os.Exit(1) + } + return bs +} + +// ConfigFile returns a file path containing the configuration file +// specified. If one cannot be found, an error will be returned which +// contains a list of all file paths searched. +func (ps Paths) ConfigFile(name string) (string, error) { + home := os.Getenv("HOME") + xdgHome := os.Getenv("XDG_CONFIG_HOME") + xdgDirs := os.Getenv("XDG_CONFIG_DIRS") + + // We're going to accumulate a list of directories for places to inspect + // for configuration files. Basically, this includes following the + // xdg basedir spec for the XDG_CONFIG_HOME and XDG_CONFIG_DIRS environment + // variables. + try := make([]string, 0) + + // from override + if len(ps.Override) > 0 { + try = append(try, ps.Override) + } + + // XDG_CONFIG_HOME + if len(xdgHome) > 0 && strings.HasPrefix(xdgHome, "/") { + try = append(try, path.Join(xdgHome, ps.XDGSuffix)) + } else if len(home) > 0 { + try = append(try, path.Join(home, ".config", ps.XDGSuffix)) + } + + // XDG_CONFIG_DIRS + if len(xdgDirs) > 0 { + for _, p := range strings.Split(xdgDirs, ":") { + // XDG basedir spec does not allow relative paths + if !strings.HasPrefix(p, "/") { + continue + } + try = append(try, path.Join(p, ps.XDGSuffix)) + } + } else { + try = append(try, path.Join("/", "etc", "xdg", ps.XDGSuffix)) + } + + // Add directories from GOPATH. Last resort. + for _, dir := range build.Default.SrcDirs() { + d := path.Join(dir, ps.GoImportPath) + try = append(try, d) + } + + return searchPaths(try, name) +} + +// DataFile returns a file path containing the data file +// specified. If one cannot be found, an error will be returned which +// contains a list of all file paths searched. +func (ps Paths) DataFile(name string) (string, error) { + home := os.Getenv("HOME") + xdgHome := os.Getenv("XDG_DATA_HOME") + xdgDirs := os.Getenv("XDG_DATA_DIRS") + + // We're going to accumulate a list of directories for places to inspect + // for data files. Basically, this includes following the + // xdg basedir spec for the XDG_DATA_HOME and XDG_DATA_DIRS environment + // variables. + try := make([]string, 0) + + // from override + if len(ps.Override) > 0 { + try = append(try, ps.Override) + } + + // XDG_DATA_HOME + if len(xdgHome) > 0 && strings.HasPrefix(xdgHome, "/") { + try = append(try, path.Join(xdgHome, ps.XDGSuffix)) + } else if len(home) > 0 { + try = append(try, path.Join(home, ".local", "share", ps.XDGSuffix)) + } + + // XDG_CONFIG_DIRS + if len(xdgDirs) > 0 { + for _, p := range strings.Split(xdgDirs, ":") { + // XDG basedir spec does not allow relative paths + if !strings.HasPrefix(p, "/") { + continue + } + try = append(try, path.Join(p, ps.XDGSuffix)) + } + } else { + try = append(try, path.Join("/", "usr", "local", "share", ps.XDGSuffix)) + try = append(try, path.Join("/", "usr", "share", ps.XDGSuffix)) + } + + // Add directories from GOPATH. Last resort. + for _, dir := range build.Default.SrcDirs() { + d := path.Join(dir, ps.GoImportPath) + try = append(try, d) + } + + return searchPaths(try, name) +} + +// RuntimeFile returns a file path containing the runtime file +// specified. If one cannot be found, an error will be returned which +// contains a list of all file paths searched. +func (ps Paths) RuntimeFile(name string) (string, error) { + xdgRuntime := os.Getenv("XDG_RUNTIME_DIR") + + try := make([]string, 0) + + // from override + if len(ps.Override) > 0 { + try = append(try, ps.Override) + } + + // XDG_RUNTIME_DIR + if len(xdgRuntime) > 0 { + try = append(try, path.Join(xdgRuntime, ps.XDGSuffix)) + } else { + try = append(try, path.Join(os.TempDir(), ps.XDGSuffix)) + } + + // Add directories from GOPATH. Last resort. + for _, dir := range build.Default.SrcDirs() { + d := path.Join(dir, ps.GoImportPath) + try = append(try, d) + } + + return searchPaths(try, name) +} + +func searchPaths(paths []string, suffix string) (string, error) { + // Now use the first one and keep track of the ones we've tried. + tried := make([]string, 0, len(paths)) + for _, dir := range paths { + if len(dir) == 0 { + continue + } + + fpath := path.Join(dir, suffix) + if exists(fpath) { + return fpath, nil + } else { + tried = append(tried, fpath) + } + } + + // Show the user where we've looked for config files... + triedStr := strings.Join(tried, ", ") + return "", fmt.Errorf("Could not find a '%s' file. Tried "+ + "the following paths: %s", suffix, triedStr) +} + +func exists(p string) bool { + _, err := os.Stat(p) + return err == nil || os.IsExist(err) +} diff --git a/vendor/github.com/benburwell/gohue/.gitignore b/vendor/github.com/benburwell/gohue/.gitignore new file mode 100644 index 0000000..1377554 --- /dev/null +++ b/vendor/github.com/benburwell/gohue/.gitignore @@ -0,0 +1 @@ +*.swp diff --git a/vendor/github.com/benburwell/gohue/LICENSE b/vendor/github.com/benburwell/gohue/LICENSE new file mode 100644 index 0000000..798f51c --- /dev/null +++ b/vendor/github.com/benburwell/gohue/LICENSE @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., <http://fsf.org/> + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + GoHue + A library written in the Go Programming Language for the Philips Hue API. + Copyright (C) 2016 Collin Guarino (Collinux) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + {signature of Ty Coon}, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/vendor/github.com/benburwell/gohue/README.md b/vendor/github.com/benburwell/gohue/README.md new file mode 100644 index 0000000..06b9bbd --- /dev/null +++ b/vendor/github.com/benburwell/gohue/README.md @@ -0,0 +1,129 @@ +# GoHue +Package hue interfaces Philips Hue devices to control lights, scenes, schedules, and groups. + +[![GoDoc](https://camo.githubusercontent.com/b3b2a2b7fad4e76052830945cd839a3bba5be723/687474703a2f2f696d672e736869656c64732e696f2f62616467652f676f646f632d7265666572656e63652d3532373242342e706e67)](https://godoc.org/github.com/Collinux/GoHue) +[![Go Report Card](https://goreportcard.com/badge/github.com/Collinux/GoHue)](https://goreportcard.com/report/github.com/Collinux/GoHue) + +## See GoHue in action! +##### Have a cool project you made using GoHue? Add yours here in a pull request! +[HueBeat](https://github.com/Mobilpadde/HueBeat) by [Mobilpadde](https://github.com/mobilpadde) - Light up a room in sync with your heartbeat. + +[BitHue](https://github.com/realytcracker/go-bithue) by [ytcracker](https://github.com/realytcracker) - Light color according to profit gain/loss in bitcoin price + +## Installation +``` +go get github.com/collinux/GoHue +``` + +## Usage +``` +package main + +import ( + "github.com/collinux/GoHue" +) + +func main() { + // It is recommended that you save the username from bridge.CreateUser + // so you don't have to press the link button every time and re-auth. + // When CreateUser is called it will print the generated user token. + bridgesOnNetwork, _ := hue.FindBridges() + bridge := bridgesOnNetwork[0] + username, _ := bridge.CreateUser("someusernamehere") + bridge.Login(username) + + lights, _ := bridge.GetAllLights() + for _, light := range lights { + light.SetBrightness(100) + light.ColorLoop(true) + } + + nightstandLight, _ := bridge.GetLightByName("Nightstand") + nightstandLight.Blink(5) + nightstandLight.SetName("Bedroom Lamp") + + lights[0].SetColor(hue.RED) + lights[1].SetColor(hue.BLUE) + lights[2].SetColor(hue.GREEN) + + for _, light := range lights { + light.Off() + } +} +``` + +## Features +##### Lights +- [x] Get all lights +- [x] Get light by name +- [x] Get light by index on bridge +- [x] Get lights attributes and state +- [x] Set lights attributes (rename) +- [x] Set light state (color, effects, brightness, etc) +- [x] Delete light +- [x] Turn On, Off, Toggle +- [x] Blink +- [x] Colorloop On/Off + +##### Bridge +- [x] Create user +- [x] Delete user +- [x] Get configuration +- [ ] Modify configuration +- [ ] Get full state (datastore) +- [x] Search for bridges +- [x] Search for new lights +- [ ] Get all timezones + +##### Schedules +- [x] Get all schedules +- [x] Get schedule by ID +- [x] Get schedule attributes +- [ ] Create schedules +- [ ] Set schedule attributes +- [ ] Delete schedule + +##### Scenes +- [x] Get all scenes +- [x] Get scene by ID +- [x] Create scene +- [ ] Modify scene +- [ ] Recall scene +- [ ] Delete scene + +##### Groups +- [ ] Get all groups +- [ ] Create group +- [ ] Get group attributes +- [ ] Set group attributes +- [ ] Set group state +- [ ] Delete Group + +##### Sensors +- [ ] Get all sensors +- [ ] Create sensor +- [ ] Find new sensors +- [ ] Get new sensors +- [ ] Get sensor +- [ ] Update sensor +- [ ] Delete sensor +- [ ] Change sensor configuration + +##### Rules +- [ ] Get all rules +- [ ] Get rule +- [ ] Create rule +- [ ] Update rule +- [ ] Delete rule + +## API Documentation +This repository is featured on the Philips Hue® developer site and was not developed by "Philips Lighting Holding B.V"... +for official Hue® documentation check out the [Philips Hue® website](http://www.developers.meethue.com/philips-hue-api). This codebase comes with no guaranetees. Use at your own risk. + +## License +GoHue - Third party golang library for Philips Hue® gateway interface. +Copyright (C) 2016 Collinux +GPL version 2 or higher http://www.gnu.org/licenses/gpl.html + +## Contributing +Pull requests happily accepted on GitHub diff --git a/vendor/github.com/benburwell/gohue/bridge.go b/vendor/github.com/benburwell/gohue/bridge.go new file mode 100644 index 0000000..c89d5f4 --- /dev/null +++ b/vendor/github.com/benburwell/gohue/bridge.go @@ -0,0 +1,342 @@ +/* +* bridge.go +* GoHue library for Philips Hue +* Copyright (C) 2016 Collin Guarino (Collinux) collin.guarino@gmail.com +* License: GPL version 2 or higher http://www.gnu.org/licenses/gpl.html + */ +// All things start with the bridge. You will find many Bridge.Func() items +// to use once a bridge has been created and identified. +// See the getting started guide on the Philips hue website: +// http://www.developers.meethue.com/documentation/getting-started + +package hue + +import ( + "bytes" + "encoding/json" + "encoding/xml" + "errors" + "fmt" + "io" + "io/ioutil" + "log" + "net/http" + "runtime" + "strconv" + "strings" + "time" +) + +// Bridge struct defines hardware that is used to communicate with the lights. +type Bridge struct { + IPAddress string `json:"internalipaddress"` + Username string + Info BridgeInfo +} + +// BridgeInfo struct is the format for parsing xml from a bridge. +type BridgeInfo struct { + XMLName xml.Name `xml:"root"` + Device struct { + XMLName xml.Name `xml:"device"` + DeviceType string `xml:"deviceType"` + FriendlyName string `xml:"friendlyName"` + Manufacturer string `xml:"manufacturer"` + ManufacturerURL string `xml:"manufacturerURL"` + ModelDescription string `xml:"modelDescription"` + ModelName string `xml:"modelName"` + ModelNumber string `xml:"modelNumber"` + ModelURL string `xml:"modelURL"` + SerialNumber string `xml:"serialNumber"` + UDN string `xml:"UDN"` + } `xml:"device"` +} + +// Get sends an http GET to the bridge +func (bridge *Bridge) Get(path string) ([]byte, io.Reader, error) { + uri := fmt.Sprintf("http://" + bridge.IPAddress + path) + client := &http.Client{Timeout: time.Second * 5} + resp, err := client.Get(uri) + + if err != nil { + err = errors.New("unable to access bridge") + return []byte{}, nil, err + } + return HandleResponse(resp) +} + +// Put sends an http PUT to the bridge with +// a body formatted with parameters (in a generic interface) +func (bridge *Bridge) Put(path string, params interface{}) ([]byte, io.Reader, error) { + uri := fmt.Sprintf("http://" + bridge.IPAddress + path) + client := &http.Client{Timeout: time.Second * 5} + + data, err := json.Marshal(params) + if err != nil { + err = errors.New("unable to marshal PUT request interface") + return []byte{}, nil, err + } + //fmt.Println("\n\nPARAMS: ", params) + + request, _ := http.NewRequest("PUT", uri, bytes.NewReader(data)) + resp, err := client.Do(request) + if err != nil { + err = errors.New("unable to access bridge") + return []byte{}, nil, err + } + return HandleResponse(resp) +} + +// Post sends an http POST to the bridge with +// a body formatted with parameters (in a generic interface). +// If `params` is nil then it will send an empty body with the post request. +func (bridge *Bridge) Post(path string, params interface{}) ([]byte, io.Reader, error) { + // Add the params to the request or allow an empty body + request := []byte{} + if params != nil { + reqBody, err := json.Marshal(params) + if err != nil { + err = errors.New("unable to add POST body parameters due to json marshalling error") + return []byte{}, nil, err + } + request = reqBody + } + // Send the request and handle the response + uri := fmt.Sprintf("http://" + bridge.IPAddress + path) + client := &http.Client{Timeout: time.Second * 5} + resp, err := client.Post(uri, "text/json", bytes.NewReader(request)) + + if err != nil { + err = errors.New("unable to access bridge") + return []byte{}, nil, err + } + return HandleResponse(resp) +} + +// Delete sends an http DELETE to the bridge +func (bridge *Bridge) Delete(path string) error { + uri := fmt.Sprintf("http://" + bridge.IPAddress + path) + client := &http.Client{Timeout: time.Second * 5} + req, _ := http.NewRequest("DELETE", uri, nil) + resp, err := client.Do(req) + + if err != nil { + err = errors.New("unable to access bridge") + return err + } + _, _, err = HandleResponse(resp) + return err +} + +// HandleResponse manages the http.Response content from a +// bridge Get/Put/Post/Delete by checking it for errors +// and invalid return types. +func HandleResponse(resp *http.Response) ([]byte, io.Reader, error) { + body, err := ioutil.ReadAll(resp.Body) + defer resp.Body.Close() + if err != nil { + trace("Error parsing bridge description xml.", nil) + return []byte{}, nil, err + } + reader := bytes.NewReader(body) + if strings.Contains(string(body), "\"error\"") { + errString := string(body) + errNum := errString[strings.Index(errString, "type\":")+6 : strings.Index(errString, ",\"address")] + errDesc := errString[strings.Index(errString, "description\":\"")+14 : strings.Index(errString, "\"}}")] + errOut := fmt.Sprintf("Error type %s: %s.", errNum, errDesc) + err = errors.New(errOut) + return []byte{}, nil, err + } + return body, reader, nil +} + +// FindBridges will visit www.meethue.com/api/nupnp to see a list of +// bridges on the local network. +func FindBridges() ([]Bridge, error) { + bridge := Bridge{IPAddress: "www.meethue.com"} + body, _, err := bridge.Get("/api/nupnp") + if err != nil { + err = errors.New("unable to locate bridge") + return []Bridge{}, err + } + bridges := []Bridge{} + err = json.Unmarshal(body, &bridges) + if err != nil { + return []Bridge{}, errors.New("unable to parse FindBridges response") + } + return bridges, nil +} + +// NewBridge defines hardware that is compatible with Hue. +// The function is the core of all functionality, it's necessary +// to call `NewBridge` and `Login` or `CreateUser` to access any +// lights, scenes, groups, etc. +func NewBridge(ip string) (*Bridge, error) { + bridge := Bridge{ + IPAddress: ip, + } + // Test the connection by attempting to get the bridge info. + err := bridge.GetInfo() + if err != nil { + return &Bridge{}, err + } + return &bridge, nil +} + +// GetInfo retreives the description.xml file from the bridge. +// This is used as a check to see if the bridge is accessible +// and any error will be fatal as the bridge is required for nearly +// all functions. +func (bridge *Bridge) GetInfo() error { + _, reader, err := bridge.Get("/description.xml") + if err != nil { + return err + } + data := BridgeInfo{} + err = xml.NewDecoder(reader).Decode(&data) + if err != nil { + err = errors.New("Error: Unable to decode XML response from bridge. ") + return err + } + bridge.Info = data + return nil +} + +// Login verifies that the username token has bridge access +// and only assigns the bridge its Username value if verification is successful. +func (bridge *Bridge) Login(username string) error { + uri := fmt.Sprintf("/api/%s", username) + _, _, err := bridge.Get(uri) + if err != nil { + return err + } + bridge.Username = username + return nil +} + +// CreateUser adds a new user token on the whitelist. +// The token is the first return value in this function which must +// be used with `Bridge.Login`. You cannot use a plaintext username +// like the argument provided in this function. +// This was done by Philips Hue for security reasons. +func (bridge *Bridge) CreateUser(deviceType string) (string, error) { + params := map[string]string{"devicetype": deviceType} + body, _, err := bridge.Post("/api", params) + if err != nil { + return "", err + } + content := string(body) + username := content[strings.LastIndex(content, ":\"")+2 : strings.LastIndex(content, "\"")] + bridge.Username = username + return username, nil +} + +// DeleteUser deletes a user given its USER KEY, not the string name. +// See http://www.developers.meethue.com/documentation/configuration-api +// for description on `username` deprecation in place of the devicetype key. +func (bridge *Bridge) DeleteUser(username string) error { + uri := fmt.Sprintf("/api/%s/config/whitelist/%s", bridge.Username, username) + err := bridge.Delete(uri) + if err != nil { + return err + } + return nil +} + +// GetAllLights retreives the state of all lights that the bridge is aware of. +func (bridge *Bridge) GetAllLights() ([]Light, error) { + uri := fmt.Sprintf("/api/%s/lights", bridge.Username) + body, _, err := bridge.Get(uri) + if err != nil { + return []Light{}, err + } + + // An index is at the top of every Light in the array + lightMap := map[string]Light{} + err = json.Unmarshal(body, &lightMap) + if err != nil { + return []Light{}, errors.New("Unable to marshal GetAllLights response. ") + } + + // Parse the index, add the light to the list, and return the array + lights := []Light{} + for index, light := range lightMap { + light.Index, err = strconv.Atoi(index) + if err != nil { + return []Light{}, errors.New("Unable to convert light index to integer. ") + } + light.Bridge = bridge + lights = append(lights, light) + } + return lights, nil +} + +// GetLightByIndex returns a light struct containing data on +// a light given its index stored on the bridge. This is used for +// quickly updating an individual light. +func (bridge *Bridge) GetLightByIndex(index int) (Light, error) { + // Send an http GET and inspect the response + uri := fmt.Sprintf("/api/%s/lights/%d", bridge.Username, index) + body, _, err := bridge.Get(uri) + if err != nil { + return Light{}, err + } + if strings.Contains(string(body), "not available") { + return Light{}, errors.New("Error: Light selection index out of bounds. ") + } + + // Parse and load the response into the light array + light := Light{} + err = json.Unmarshal(body, &light) + if err != nil { + return Light{}, errors.New("Error: Unable to unmarshal light data. ") + } + light.Index = index + light.Bridge = bridge + return light, nil +} + +// FindNewLights makes the bridge search the zigbee spectrum for +// lights in the area and will add them to the list of lights available. +// If successful these new lights can be used by `Bridge.GetAllLights` +// +// Notes from Philips Hue API documentation: +// The bridge will search for 1 minute and will add a maximum of 15 new +// lights. To add further lights, the command needs to be sent again after +// the search has completed. If a search is already active, it will be +// aborted and a new search will start. +// http://www.developers.meethue.com/documentation/lights-api#13_search_for_new_lights +func (bridge *Bridge) FindNewLights() error { + uri := fmt.Sprintf("/api/%s/lights", bridge.Username) + _, _, err := bridge.Post(uri, nil) + if err != nil { + return err + } + return nil +} + +// GetLightByName returns a light struct containing data on a given name. +func (bridge *Bridge) GetLightByName(name string) (Light, error) { + lights, _ := bridge.GetAllLights() + for _, light := range lights { + if light.Name == name { + return light, nil + } + } + errOut := fmt.Sprintf("Error: Light name '%s' not found. ", name) + return Light{}, errors.New(errOut) +} + +// Log the date, time, file location, line number, and function. +// Message can be "" or Err can be nil (not both) +func trace(message string, err error) { + pc := make([]uintptr, 10) + runtime.Callers(2, pc) + f := runtime.FuncForPC(pc[0]) + file, line := f.FileLine(pc[0]) + if err != nil { + log.Printf("%s:%d %s: %s\n", file, line, f.Name(), err) + } else { + log.Printf("%s:%d %s: %s\n", file, line, f.Name(), message) + } +} diff --git a/vendor/github.com/benburwell/gohue/group.go b/vendor/github.com/benburwell/gohue/group.go new file mode 100644 index 0000000..db878d6 --- /dev/null +++ b/vendor/github.com/benburwell/gohue/group.go @@ -0,0 +1,67 @@ +/* +* group.go +* GoHue library for Philips Hue +* Copyright (C) 2016 Collin Guarino (Collinux) collin.guarino@gmail.com +* License: GPL version 2 or higher http://www.gnu.org/licenses/gpl.html + */ +// http://www.developers.meethue.com/documentation/groups-api + +package hue + +import ( + "encoding/json" + "fmt" +) + +// Action struct defines the state of a group +type Action struct { + Alert string `json:"alert,omitempty"` + Bri int `json:"bri,omitempty"` + Colormode string `json:"colormode,omitempty"` + Ct int `json:"ct,omitempty"` + Effect string `json:"effect,omitempty"` + Hue *int `json:"hue,omitempty"` + On *bool `json:"on,omitempty"` + Sat *int `json:"sat,omitempty"` + XY []float64 `json:"xy,omitempty"` + Scene string `json:"scene,omitempty"` +} + +// Group struct defines the attributes for a group of lights. +type Group struct { + Action Action `json:"action"` + Lights []string `json:"lights"` + Name string `json:"name"` + Type string `json:"type"` +} + +// GetGroups gets the attributes for each group of lights. +// TODO: NOT TESTED, NOT FULLY IMPLEMENTED +func (bridge *Bridge) GetGroups() ([]Group, error) { + uri := fmt.Sprintf("/api/%s/groups", bridge.Username) + body, _, err := bridge.Get(uri) + if err != nil { + return []Group{}, err + } + + //fmt.Println("GROUP GET: ", string(body)) + + groups := map[string]Group{} + err = json.Unmarshal(body, &groups) + if err != nil { + return []Group{}, err + } + //fmt.Println("GROUPS: ", groups) + + return []Group{}, nil +} + +// SetGroupState sends an action to group +func (bridge *Bridge) SetGroupState(group int, action *Action) error { + uri := fmt.Sprintf("/api/%s/groups/%d/action", bridge.Username, group) + _, _, err := bridge.Put(uri, action) + if err != nil { + return err + } + return nil +} diff --git a/vendor/github.com/benburwell/gohue/light.go b/vendor/github.com/benburwell/gohue/light.go new file mode 100644 index 0000000..aef5954 --- /dev/null +++ b/vendor/github.com/benburwell/gohue/light.go @@ -0,0 +1,266 @@ +/* +* light.go +* GoHue library for Philips Hue +* Copyright (C) 2016 Collin Guarino (Collinux) collin.guarino@gmail.com +* License: GPL version 2 or higher http://www.gnu.org/licenses/gpl.html + */ +// http://www.developers.meethue.com/documentation/lights-api + +package hue + +import ( + "errors" + "fmt" + "time" +) + +// Light struct defines attributes of a light. +type Light struct { + State struct { + On bool `json:"on"` // On or Off state of the light ("true" or "false") + Bri uint8 `json:"bri"` // Brightness value 1-254 + Hue uint16 `json:"hue"` // Hue value 1-65535 + Saturation uint8 `json:"sat"` // Saturation value 0-254 + Effect string `json:"effect"` // "None" or "Colorloop" + XY [2]float32 `json:"xy"` // Coordinates of color in CIE color space + CT int `json:"ct"` // Mired Color Temperature (google it) + Alert string `json:"alert"` // "selected" or "none" + ColorMode string `json:"colormode"` // HS or XY mode for choosing color + Reachable bool `json:"reachable"` + } `json:"state"` + Type string `json:"type"` + Name string `json:"name"` + ModelID string `json:"modelid"` + ManufacturerName string `json:"manufacturername"` + UniqueID string `json:"uniqueid"` + SWVersion string `json:"swversion"` + Index int // Set by index of light array response + Bridge *Bridge // Set by the bridge when the light is found +} + +// LightState used in Light.SetState to amend light attributes. +type LightState struct { + On bool `json:"on"` + Bri uint8 `json:"bri,omitempty"` + Hue uint16 `json:"hue,omitempty"` + Sat uint8 `json:"sat,omitempty"` + XY *[2]float32 `json:"xy,omitempty"` + CT uint16 `json:"ct,omitempty"` + Effect string `json:"effect,omitempty"` + Alert string `json:"alert,omitempty"` + TransitionTime string `json:"transitiontime,omitempty"` + SaturationIncrement int16 `json:"sat_inc,omitempty"` + HueIncrement int32 `json:"hue_inc,omitempty"` + BrightnessIncrement int16 `json:"bri_inc,omitempty"` + CTIncrement int32 `json:"ct_inc,omitempty"` + XYIncrement *[2]float32 `json:"xy_inc,omitempty"` + Name string `json:"name,omitempty"` +} + +// SetName assigns a new name in the light's +// attributes as recognized by the bridge. +func (light *Light) SetName(name string) error { + uri := fmt.Sprintf("/api/%s/lights/%d", light.Bridge.Username, light.Index) + body := make(map[string]string) + body["name"] = name + _, _, err := light.Bridge.Put(uri, body) + if err != nil { + return err + } + return nil +} + +// Off turns the light source off +func (light *Light) Off() error { + return light.SetState(LightState{On: false}) +} + +// On turns the light source on +func (light *Light) On() error { + return light.SetState(LightState{On: true}) +} + +// Toggle switches the light source on and off +func (light *Light) Toggle() error { + if light.State.On { + return light.Off() + } + return light.On() +} + +// Delete removes the light from the +// list of lights available on the bridge. +func (light *Light) Delete() error { + uri := fmt.Sprintf("/api/%s/lights/%d", light.Bridge.Username, light.Index) + err := light.Bridge.Delete(uri) + if err != nil { + return err + } + return nil +} + +// Blink increases and decrease the brightness +// repeatedly for a given seconds interval and return the +// light back to its off or on state afterwards. +// Note: time will vary based on connection speed and algorithm speed. +func (light *Light) Blink(seconds int) error { + originalPosition := light.State.On + originalBrightness := light.State.Bri + blinkMax := 75 // Percent brightness + blinkMin := 25 // Percent brightness + + // Start with near maximum brightness and toggle between that and + // a lesser brightness to create a blinking effect. + for i := 0; i <= seconds*2; i++ { + if i == 0 { + err := light.SetBrightness(blinkMax) + if err != nil { + return err + } + } else if i%2 == 0 { + err := light.SetBrightness(blinkMax) + if err != nil { + return err + } + } else { + err := light.SetBrightness(blinkMin) + if err != nil { + return err + } + } + time.Sleep(time.Second / 2) + } + + // Return the light to its original on or off state and brightness + if light.State.Bri != originalBrightness || light.State.On != originalPosition { + light.SetState(LightState{On: originalPosition, Bri: originalBrightness}) + } + return nil +} + +// ColorLoop sets the light state to 'colorloop' if `active` +// is true or it sets the light state to "none" if `activate` is false. +func (light *Light) ColorLoop(activate bool) error { + var state = "none" + if activate { + state = "colorloop" + } + return light.SetState(LightState{On: true, Effect: state}) +} + +// XY positions on the HSL color spectrum used in `Light.SetColor` +// https://en.wikipedia.org/wiki/HSL_and_HSV +var ( + RED = &[2]float32{0.6915, 0.3083} + YELLOW = &[2]float32{0.4023, 0.4725} + ORANGE = &[2]float32{0.4693, 0.4007} + GREEN = &[2]float32{0.1700, 0.7000} + CYAN = &[2]float32{0.1610, 0.3549} + BLUE = &[2]float32{0.1530, 0.0480} + PURPLE = &[2]float32{0.2363, 0.1154} + PINK = &[2]float32{0.3645, 0.1500} + WHITE = &[2]float32{0.3227, 0.3290} +) + +// SetColor requires a selection from the above light +// color variable section and sets the light to that XY HSL color +func (light *Light) SetColor(color *[2]float32) error { + lightState := LightState{On: true, XY: color} + err := light.SetState(lightState) + if err != nil { + return err + } + return nil +} + +// SetColorXY requires a selection from the above light +// color variable section and sets the light to that XY HSL color +// aliased for clarity +func (light *Light) SetColorXY(color *[2]float32) { + light.SetColor(color) +} + +// SetColorHS requires a selection from the above light +// color variable section and sets the light to the Hue value +func (light *Light) SetColorHS(color uint16) error { + lightState := LightState{On: true, Hue: color} + err := light.SetState(lightState) + if err != nil { + return err + } + return nil +} + +// Dim lowers the brightness by a percent. +// Note the required value is an integer, for example "20" is converted to 20%. +func (light *Light) Dim(percent int) error { + if percent > 0 && percent <= 100 { + originalBri := light.State.Bri + decreaseBri := float32(originalBri) * float32((float32(percent) / 100.0)) + newBri := uint8(originalBri - uint8(decreaseBri)) + if newBri < 0 { + newBri = 0 + } + lightState := LightState{On: true, Bri: newBri} + err := light.SetState(lightState) + if err != nil { + return err + } + return nil + } + return errors.New("Light.Dim percentage given is not between 1 and 100. ") +} + +// SetBrightness sets the brightness to a percentage of the maximum +// maximum brightness as an integer (`LightStruct.Bri between 1 and 254 inclusive`) +func (light *Light) SetBrightness(percent int) error { + if percent > 0 && percent <= 100 { + brightness := uint8(float32(percent) * 2.54) // 100=254x --> 2.54 + lightState := LightState{On: true, Bri: brightness} + err := light.SetState(lightState) + if err != nil { + return err + } + return nil + } + return errors.New("Light.SetBrightness percentage is not between 1 and 100. ") +} + +// Brighten will increase LightStruct.Bri by a given percent (integer) +func (light *Light) Brighten(percent int) error { + if percent > 0 && percent <= 100 { + originalBri := light.State.Bri + increaseBri := float32(originalBri) * float32((float32(percent) / 100.0)) + newBri := uint8(originalBri + uint8(increaseBri)) + if newBri > 254 { // LightState.Bri must be between 1 and 254 inclusive + newBri = 254 + } + lightState := LightState{On: true, Bri: newBri} + err := light.SetState(lightState) + if err != nil { + return err + } + return nil + } + return errors.New("Light.Brighten percentage is not between 1 and 100. ") +} + +// SetState modifyies light attributes. See `LightState` struct for attributes. +// Brightness must be between 1 and 254 (inclusive) +// Hue must be between 0 and 65535 (inclusive) +// Sat must be between 0 and 254 (inclusive) +// See http://www.developers.meethue.com/documentation/lights-api for more info +func (light *Light) SetState(newState LightState) error { + uri := fmt.Sprintf("/api/%s/lights/%d/state", light.Bridge.Username, light.Index) + _, _, err := light.Bridge.Put(uri, newState) + if err != nil { + return err + } + + // Get the new light state and update the current Light struct + *light, err = light.Bridge.GetLightByIndex(light.Index) + if err != nil { + return err + } + return nil +} diff --git a/vendor/github.com/benburwell/gohue/scene.go b/vendor/github.com/benburwell/gohue/scene.go new file mode 100644 index 0000000..e53b2a3 --- /dev/null +++ b/vendor/github.com/benburwell/gohue/scene.go @@ -0,0 +1,129 @@ +/* +* scene.go +* GoHue library for Philips Hue +* Copyright (C) 2016 Collin Guarino (Collinux) collin.guarino@gmail.com +* License: GPL version 2 or higher http://www.gnu.org/licenses/gpl.html + */ +// http://www.developers.meethue.com/documentation/scenes-api + +package hue + +import ( + "encoding/json" + "errors" + "fmt" +) + +// Scene struct defines attributes for Scene items +type Scene struct { + Appdata *struct { + Data string `json:"data,omitempty"` + Version int `json:"version,omitempty"` + } `json:"appdata,omitempty"` + Lastupdated string `json:"lastupdated,omitempty"` + Lights []string `json:"lights,omitempty"` + Locked bool `json:"locked,omitempty"` + Name string `json:"name,omitempty"` + Owner string `json:"owner,omitempty"` + Picture string `json:"picture,omitempty"` + Recycle bool `json:"recycle,omitempty"` + Version int `json:"version,omitempty"` + ID string `json:",omitempty"` +} + +// GetAllScenes gets the attributes for all scenes. +func (bridge *Bridge) GetAllScenes() ([]Scene, error) { + uri := fmt.Sprintf("/api/%s/scenes", bridge.Username) + body, _, err := bridge.Get(uri) + if err != nil { + return []Scene{}, err + } + + scenes := map[string]Scene{} + err = json.Unmarshal(body, &scenes) + if err != nil { + return []Scene{}, err + } + scenesList := []Scene{} + for key, value := range scenes { + scene := Scene{} + scene = value + scene.ID = key + scenesList = append(scenesList, scene) + } + return scenesList, nil +} + +// GetScene gets the attributes for an individual scene. +// This is used to optimize time when updating the state of the scene. +// Note: The ID is not an index, it's a unique key generated for each scene. +func (bridge *Bridge) GetScene(id string) (Scene, error) { + uri := fmt.Sprintf("/api/%s/scenes/%s", bridge.Username, id) + body, _, err := bridge.Get(uri) + if err != nil { + return Scene{}, err + } + + scene := Scene{} + err = json.Unmarshal(body, &scene) + if err != nil { + return Scene{}, err + } + return scene, nil +} + +// GetSceneByName gets the attributes for the scene identified by a name +func (bridge *Bridge) GetSceneByName(name string) (Scene, error) { + + scenes, _ := bridge.GetAllScenes() + + // Iterate in reverse, as later entries seem to be the newest + for i := len(scenes) - 1; i >= 0; i-- { + if scenes[i].Name == name { + return scenes[i], nil + } + } + + errOut := fmt.Sprintf("Error: Scene name '%s' not found. ", name) + return Scene{}, errors.New(errOut) +} + +// RecallScene recalls a scene +func (bridge *Bridge) RecallScene(id string) error { + action := &Action{Scene: id} + return bridge.SetGroupState(0, action) +} + +// RecallSceneByName recalls a scene +func (bridge *Bridge) RecallSceneByName(name string) error { + scene, err := bridge.GetSceneByName(name) + if err != nil { + return err + } + return bridge.RecallScene(scene.ID) +} + +// CreateScene posts a new scene configuration to the bridge. +func (bridge *Bridge) CreateScene(scene Scene) error { + uri := fmt.Sprintf("/api/%s/scenes/", bridge.Username) + _, _, err := bridge.Post(uri, scene) + if err != nil { + return err + } + return nil +} + +// Bridge.ModifySceneState amends light states for lights +// included in a scene list. See `Bridge.ModifyScene` for +// changing the lights included in the scene list. +// func (bridge *Bridge) ModifySceneState() error { +// +// } + +// Bridge.ModifyScene amends the lights included for a given scene or +// it can be used to change the scene name. To amend light states for +// lights included in a scene list see `Bridge.ModifySceneState`. +// func (bridge *Bridge) ModifyScene() error { +// uri := fmt.Sprintf("/api/%s/scenes/%s/lightstates/%s", +// bridge.Username, oldScene.ID, ) +// } diff --git a/vendor/github.com/benburwell/gohue/schedule.go b/vendor/github.com/benburwell/gohue/schedule.go new file mode 100644 index 0000000..52315f9 --- /dev/null +++ b/vendor/github.com/benburwell/gohue/schedule.go @@ -0,0 +1,109 @@ +/* +* schedule.go +* GoHue library for Philips Hue +* Copyright (C) 2016 Collin Guarino (Collinux) collin.guarino@gmail.com +* License: GPL version 2 or higher http://www.gnu.org/licenses/gpl.html + */ +// http://www.developers.meethue.com/documentation/schedules-api-0 + +package hue + +import ( + "encoding/json" + "fmt" +) + +// Schedule struct defines attributes of Alarms and Timers +type Schedule struct { + Name string `json:"name"` + Description string `json:"description"` + Command struct { + Address string `json:"address"` + Body struct { + Scene string `json:"scene"` + } `json:"body"` + Method string `json:"method"` + } `json:"command"` + Localtime string `json:"localtime"` + Time string `json:"time"` + Created string `json:"created"` + Status string `json:"status"` + Autodelete bool `json:"autodelete"` + ID string +} + +// GetAllSchedules gets Alarms and Timers in a Schedule struct. +func (bridge *Bridge) GetAllSchedules() ([]Schedule, error) { + uri := fmt.Sprintf("/api/%s/schedules", bridge.Username) + body, _, err := bridge.Get(uri) + if err != nil { + return []Schedule{}, err + } + + // Each index key is the topmost element of the json array. + // Unmarshal the array, loop through each index key, and add it to the list + schedules := map[string]Schedule{} + err = json.Unmarshal(body, &schedules) + if err != nil { + return []Schedule{}, err + } + scheduleList := []Schedule{} + for key, value := range schedules { + schedule := Schedule{} + schedule = value + schedule.ID = key + scheduleList = append(scheduleList, schedule) + } + return scheduleList, nil +} + +// GetSchedule gets the attributes for an individual schedule. +// This is used to optimize time when updating the state of a schedule item. +// Note: The ID is not an index, it's a unique key generated for each schedule. +func (bridge *Bridge) GetSchedule(id string) (Schedule, error) { + uri := fmt.Sprintf("/api/%s/schedules/%s", bridge.Username, id) + body, _, err := bridge.Get(uri) + if err != nil { + return Schedule{}, err + } + + schedule := Schedule{} + err = json.Unmarshal(body, &schedule) + if err != nil { + return Schedule{}, err + } + return schedule, nil +} + +// CreateSchedule TODO: NOT TESTED, NOT FULLY IMPLEMENTED +func (bridge *Bridge) CreateSchedule(schedule Schedule) error { + uri := fmt.Sprintf("/api/%s/schedules", bridge.Username) + body, _, err := bridge.Post(uri, schedule) + if err != nil { + return err + } + + fmt.Println("CREATE SCHEDULE BODY: ", string(body)) + return nil +} + +// func (schedule *Schedule) Disable() { +// +// } +// +// func (schedule *Schedule) Enable() { +// +// } +// +// +// func (bridge *Bridge) GetSchedule(index int) (interface{}, error) { +// return []interface{}, nil +// } +// +// func (bridge *Bridge) SetSchedule(index int, schedule interface{}) error { +// return nil +// } +// +// func (bridge *Bridge) DeleteSchedule(index int) error { +// return nil +// } diff --git a/vendor/github.com/cpucycle/astrotime/README.md b/vendor/github.com/cpucycle/astrotime/README.md new file mode 100644 index 0000000..88c00ab --- /dev/null +++ b/vendor/github.com/cpucycle/astrotime/README.md @@ -0,0 +1,44 @@ +astrotime +========= + +NOAA astrological algorithms for sunrise and sunset ported to Go + + +FUNCTIONS: + +// CalcSunrise calculates the sunrise, in local time, on the day t at the +// location specified in longitude and latitude. +func CalcSunrise(t time.Time, latitude float64, longitude float64) time.Time + +// NextSunrise returns date/time of the next sunrise after tAfter +func NextSunrise(tAfter time.Time, latitude float64, longitude float64) (tSunrise time.Time) + +// CalcSunset calculates the sunset, in local time, on the day t at the +// location specified in longitude and latitude. +func CalcSunset(t time.Time, latitude float64, longitude float64) time.Time + +// NextSunset returns date/time of the next sunset after tAfter +func NextSunset(tAfter time.Time, latitude float64, longitude float64) (tSunset time.Time) + + +EXAMPLE: + +```go +package main + +import ( + "astrotime" + "fmt" + "time" +) + +const LATITUDE = float64(38.8895) +const LONGITUDE = float64(77.0352) + +func main() { + t := astrotime.NextSunrise(time.Now(), LATITUDE, LONGITUDE) + + tzname, _ := t.Zone() + fmt.Printf("The next sunrise at the Washington Monument is %d:%02d %s on %d/%d/%d.\n", t.Hour(), t.Minute(), tzname, t.Month(), t.Day(), t.Year()) +} +``` diff --git a/vendor/github.com/cpucycle/astrotime/astrotime.go b/vendor/github.com/cpucycle/astrotime/astrotime.go new file mode 100755 index 0000000..f2164a7 --- /dev/null +++ b/vendor/github.com/cpucycle/astrotime/astrotime.go @@ -0,0 +1,405 @@ +// NAA - NOAA's Astronomical Algorithms +package astrotime + +// (JavaScript web page +// http://www.srrb.noaa.gov/highlights/sunrise/sunrise.html by +// Chris Cornwall, Aaron Horiuchi and Chris Lehman) +// Ported to C++ by Pete Gray (petegray@ieee.org), July 2006 +// Released as Open Source and can be used in any way, as long as the +// above description remains in place. + +import ( + "math" + "time" +) + +// Conversions +const ( + RadToDeg = 180 / math.Pi + DegToRad = math.Pi / 180 + RadToGrad = 200 / math.Pi + GradToDeg = math.Pi / 200 +) + +// More time constants +const ( + OneDay = time.Hour * 24 +) + +// CalcJD converts a time.Time object to a julian date +func CalcJD(t time.Time) float64 { + y, m, d, hh, mm, ss, ms := t.Year(), int(t.Month()), t.Day(), t.Hour(), t.Minute(), t.Second(), t.Nanosecond()/1e6 + + // Calc integer part (days) + jday := (1461*(y+4800+(m-14)/12))/4 + (367*(m-2-12*((m-14)/12)))/12 - (3*((y+4900+(m-14)/12)/100))/4 + d - 32075 + + // Calc floating point part (fraction of a day) + jdatetime := float64(jday) + (float64(hh)-12.0)/24.0 + (float64(mm) / 1440.0) + (float64(ss) / 86400.0) + (float64(ms) / 86400000.0) + + // Adjust to UT + _, zoneOffset := t.Zone() + + return jdatetime + float64(zoneOffset)/86400 +} + +// Name: calcTimeJulianCent +// Type: Function +// Purpose: convert Julian Day to centuries since J2000.0. +// Arguments: +// jd : the Julian Day to convert +// Return value: +// the T value corresponding to the Julian Day + +func calcTimeJulianCent(t float64) float64 { + return (t - 2451545.0) / 36525.0 +} + +// Name: calcJDFromJulianCent +// Type: Function +// Purpose: convert centuries since J2000.0 to Julian Day. +// Arguments: +// t : number of Julian centuries since J2000.0 +// Return value: +// the Julian Day corresponding to the t value + +func calcJDFromJulianCent(t float64) float64 { + return t*36525.0 + 2451545.0 +} + +// Name: calGeomMeanLongSun +// Type: Function +// Purpose: calculate the Geometric Mean Longitude of the Sun +// Arguments: +// t : number of Julian centuries since J2000.0 +// Return value: +// the Geometric Mean Longitude of the Sun in degrees + +func calcGeomMeanLongSun(t float64) float64 { + L0 := 280.46646 + t*(36000.76983+0.0003032*t) + for L0 > 360.0 { + L0 -= 360.0 + } + for L0 < 0.0 { + L0 += 360.0 + } + return L0 +} + +// Name: calcMeanObliquityOfEcliptic +// Type: Function +// Purpose: calculate the mean obliquity of the ecliptic +// Arguments: +// t : number of Julian centuries since J2000.0 +// Return value: +// mean obliquity in degrees + +func calcMeanObliquityOfEcliptic(t float64) float64 { + seconds := 21.448 - t*(46.8150+t*(0.00059-t*(0.001813))) + return 23.0 + (26.0+(seconds/60.0))/60.0 +} + +// Name: calcObliquityCorrection +// Type: Function +// Purpose: calculate the corrected obliquity of the ecliptic +// Arguments: +// t : number of Julian centuries since J2000.0 +// Return value: +// corrected obliquity in degrees + +func calcObliquityCorrection(t float64) float64 { + e0 := calcMeanObliquityOfEcliptic(t) + omega := 125.04 - 1934.136*t + return e0 + 0.00256*math.Cos(omega*DegToRad) +} + +// Name: calcEccentricityEarthOrbit +// Type: Function +// Purpose: calculate the eccentricity of earth's orbit +// Arguments: +// t : number of Julian centuries since J2000.0 +// Return value: +// the unitless eccentricity + +func calcEccentricityEarthOrbit(t float64) float64 { + return 0.016708634 - t*(0.000042037+0.0000001267*t) +} + +// Name: calGeomAnomalySun +// Type: Function +// Purpose: calculate the Geometric Mean Anomaly of the Sun +// Arguments: +// t : number of Julian centuries since J2000.0 +// Return value: +// the Geometric Mean Anomaly of the Sun in degrees + +func calcGeomMeanAnomalySun(t float64) float64 { + return 357.52911 + t*(35999.05029-0.0001537*t) +} + +// Name: calcEquationOfTime +// Type: Function +// Purpose: calculate the difference between true solar time and mean +// solar time +//Arguments: +// t : number of Julian centuries since J2000.0 +// Return value: +// equation of time in minutes of time + +func calcEquationOfTime(t float64) float64 { + epsilon := calcObliquityCorrection(t) + l0 := calcGeomMeanLongSun(t) + e := calcEccentricityEarthOrbit(t) + m := calcGeomMeanAnomalySun(t) + + y := math.Tan(DegToRad * epsilon / 2.0) + y *= y + + sin2l0 := math.Sin(2.0 * DegToRad * l0) + sinm := math.Sin(DegToRad * m) + cos2l0 := math.Cos(2.0 * DegToRad * l0) + sin4l0 := math.Sin(4.0 * DegToRad * l0) + sin2m := math.Sin(2.0 * DegToRad * m) + + Etime := y*sin2l0 - 2.0*e*sinm + 4.0*e*y*sinm*cos2l0 - 0.5*y*y*sin4l0 - 1.25*e*e*sin2m + + return RadToDeg * Etime * 4.0 +} + +// Name: calcSunEqOfCenter +// Type: Function +// Purpose: calculate the equation of center for the sun +// Arguments: +// t : number of Julian centuries since J2000.0 +// Return value: +// in degrees + +func calcSunEqOfCenter(t float64) float64 { + m := calcGeomMeanAnomalySun(t) + mrad := DegToRad * m + sinm := math.Sin(mrad) + sin2m := math.Sin(mrad + mrad) + sin3m := math.Sin(mrad + mrad + mrad) + return sinm*(1.914602-t*(0.004817+0.000014*t)) + sin2m*(0.019993-0.000101*t) + sin3m*0.000289 +} + +// Name: calcSunTrueLong +// Type: Function +// Purpose: calculate the true longitude of the sun +// Arguments: +// t : number of Julian centuries since J2000.0 +// Return value: +// sun's true longitude in degrees + +func calcSunTrueLong(t float64) float64 { + l0 := calcGeomMeanLongSun(t) + c := calcSunEqOfCenter(t) + return l0 + c +} + +// Name: calcSunApparentLong +// Type: Function +// Purpose: calculate the apparent longitude of the sun +// Arguments: +// t : number of Julian centuries since J2000.0 +// Return value: +// sun's apparent longitude in degrees + +func calcSunApparentLong(t float64) float64 { + o := calcSunTrueLong(t) + omega := 125.04 - 1934.136*t + return o - 0.00569 - 0.00478*math.Sin(DegToRad*omega) +} + +// Name: calcSunDeclination +// Type: Function +// Purpose: calculate the declination of the sun +// Arguments: +// t : number of Julian centuries since J2000.0 +// Return value: +// sun's declination in degrees + +func calcSunDeclination(t float64) float64 { + e := calcObliquityCorrection(t) + lambda := calcSunApparentLong(t) + sint := math.Sin(DegToRad*e) * math.Sin(DegToRad*lambda) + return RadToDeg * math.Asin(sint) +} + +// Name: calcHourAngleSunrise +// Type: Function +// Purpose: calculate the hour angle of the sun at sunrise for the +// latitude +//Arguments: +// lat : latitude of observer in degrees +// solarDec : declination angle of sun in degrees +//Return value: +// hour angle of sunrise in radians + +func calcHourAngleSunrise(lat float64, solarDec float64) float64 { + latRad := DegToRad * lat + sdRad := DegToRad * solarDec + return (math.Acos(math.Cos(DegToRad*90.833)/(math.Cos(latRad)*math.Cos(sdRad)) - math.Tan(latRad)*math.Tan(sdRad))) +} + +// Name: calcSolNoonUTC +// Type: Function +// Purpose: calculate the Universal Coordinated Time (UTC) of solar +// noon for the given day at the given location on earth +// Arguments: +// t : number of Julian centuries since J2000.0 +// longitude : longitude of observer in degrees +// Return value: +// time in minutes from zero Z + +func calcSolNoonUTC(t float64, longitude float64) float64 { + // First pass uses approximate solar noon to calculate eqtime + tnoon := calcTimeJulianCent(calcJDFromJulianCent(t) + longitude/360.0) + eqTime := calcEquationOfTime(tnoon) + solNoonUTC := 720 + (longitude * 4) - eqTime + newt := calcTimeJulianCent(calcJDFromJulianCent(t) - 0.5 + solNoonUTC/1440.0) + eqTime = calcEquationOfTime(newt) + return 720 + (longitude * 4) - eqTime +} + +// Name: calcSunriseUTC +// Type: Function +// Purpose: calculate the Universal Coordinated Time (UTC) of sunrise +// for the given day at the given location on earth +// Arguments: +// JD : julian day +// latitude : latitude of observer in degrees +// longitude : longitude of observer in degrees +// Return value: +// time in minutes from zero Z + +// Calculate the UTC sunrise for the given day at the given location +func calcSunriseUTC(jd float64, latitude float64, longitude float64) float64 { + + t := calcTimeJulianCent(jd) + + // *** Find the time of solar noon at the location, and use + // that declination. This is better than start of the + // Julian day + + noonmin := calcSolNoonUTC(t, longitude) + tnoon := calcTimeJulianCent(jd + noonmin/1440.0) + + // *** First pass to approximate sunrise (using solar noon) + + eqTime := calcEquationOfTime(tnoon) + solarDec := calcSunDeclination(tnoon) + hourAngle := calcHourAngleSunrise(latitude, solarDec) + + delta := longitude - RadToDeg*hourAngle + timeDiff := 4 * delta + timeUTC := 720 + timeDiff - eqTime + + // *** Second pass includes fractional jday in gamma calc + + newt := calcTimeJulianCent(calcJDFromJulianCent(t) + timeUTC/1440.0) + eqTime = calcEquationOfTime(newt) + solarDec = calcSunDeclination(newt) + hourAngle = calcHourAngleSunrise(latitude, solarDec) + delta = longitude - RadToDeg*hourAngle + timeDiff = 4 * delta + timeUTC = 720 + timeDiff - eqTime + return timeUTC +} + +// CalcSunrise calculates the sunrise, in local time, on the day t at the +// location specified in longitude and latitude. +func CalcSunrise(t time.Time, latitude float64, longitude float64) time.Time { + jd := CalcJD(t) + sunriseUTC := time.Duration(math.Floor(calcSunriseUTC(jd, latitude, longitude)*60) * 1e9) + loc, _ := time.LoadLocation("UTC") + return time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, loc).Add(sunriseUTC).In(t.Location()) +} + +// Name: calcHourAngleSunset +// Type: Function +// Purpose: calculate the hour angle of the sun at sunset for the +// latitude +// Arguments: +// lat : latitude of observer in degrees +// solarDec : declination angle of sun in degrees +// Return value: +// hour angle of sunset in radians + +func calcHourAngleSunset(lat float64, solarDec float64) float64 { + latRad := DegToRad * lat + sdRad := DegToRad * solarDec + + HA := (math.Acos(math.Cos(DegToRad*90.833)/(math.Cos(latRad)*math.Cos(sdRad)) - math.Tan(latRad)*math.Tan(sdRad))) + + return -HA // in radians +} + +// Name: calcSunsetUTC +// Type: Function +// Purpose: calculate the Universal Coordinated Time (UTC) of sunset +// for the given day at the given location on earth +//Arguments: +// JD : julian day +// latitude : latitude of observer in degrees +// longitude : longitude of observer in degrees +// Return value: +// time in minutes from zero Z + +func calcSunsetUTC(jd float64, latitude float64, longitude float64) float64 { + t := calcTimeJulianCent(jd) + + // *** Find the time of solar noon at the location, and use + // that declination. This is better than start of the + // Julian day + + noonmin := calcSolNoonUTC(t, longitude) + tnoon := calcTimeJulianCent(jd + noonmin/1440.0) + + // First calculates sunrise and approx length of day + + eqTime := calcEquationOfTime(tnoon) + solarDec := calcSunDeclination(tnoon) + hourAngle := calcHourAngleSunset(latitude, solarDec) + + delta := longitude - RadToDeg*hourAngle + timeDiff := 4 * delta + timeUTC := 720 + timeDiff - eqTime + + // first pass used to include fractional day in gamma calc + + newt := calcTimeJulianCent(calcJDFromJulianCent(t) + timeUTC/1440.0) + eqTime = calcEquationOfTime(newt) + solarDec = calcSunDeclination(newt) + hourAngle = calcHourAngleSunset(latitude, solarDec) + + delta = longitude - RadToDeg*hourAngle + timeDiff = 4 * delta + return 720 + timeDiff - eqTime +} + +// CalcSunset calculates the sunset, in local time, on the day t at the +// location specified in longitude and latitude. +func CalcSunset(t time.Time, latitude float64, longitude float64) time.Time { + jd := CalcJD(t) + sunsetUTC := time.Duration(math.Floor(calcSunsetUTC(jd, latitude, longitude)*60) * 1e9) + loc, _ := time.LoadLocation("UTC") + return time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, loc).Add(sunsetUTC).In(t.Location()) +} + +// NextSunrise returns date/time of the next sunrise after tAfter +func NextSunrise(tAfter time.Time, latitude float64, longitude float64) (tSunrise time.Time) { + tSunrise = CalcSunrise(tAfter, latitude, longitude) + if tAfter.After(tSunrise) { + tSunrise = CalcSunrise(tAfter.Add(OneDay), latitude, longitude) + } + return +} + +// NextSunset returns date/time of the next sunset after tAfter +func NextSunset(tAfter time.Time, latitude float64, longitude float64) (tSunset time.Time) { + tSunset = CalcSunset(tAfter, latitude, longitude) + if tAfter.After(tSunset) { + tSunset = CalcSunset(tAfter.Add(OneDay), latitude, longitude) + } + return +} |