├── .github └── workflows │ └── go.yml ├── .gitignore ├── LICENSE ├── README.md ├── _config.yml ├── bridge.go ├── bridge_test.go ├── go.mod ├── go.sum ├── group.go ├── group_test.go ├── light.go ├── light_test.go ├── scene.go ├── scene_test.go ├── schedule.go ├── schedule_test.go ├── sensor.go └── sensor_test.go /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | on: [push] 3 | jobs: 4 | 5 | build: 6 | name: Build 7 | runs-on: ubuntu-latest 8 | steps: 9 | 10 | - name: Set up Go 1.13 11 | uses: actions/setup-go@v1 12 | with: 13 | go-version: 1.13 14 | id: go 15 | 16 | - name: Check out code into the Go module directory 17 | uses: actions/checkout@v1 18 | 19 | - name: Get dependencies 20 | run: | 21 | go get -v -t -d ./... 22 | if [ -f Gopkg.toml ]; then 23 | curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh 24 | dep ensure 25 | fi 26 | 27 | - name: Build 28 | run: go build -v . 29 | 30 | #- name: Lint 31 | # run: | 32 | # export PATH=$PATH:$(go env GOPATH)/bin # temporary fix. See https://github.com/actions/setup-go/issues/14 33 | # go get -u golang.org/x/lint/golint 34 | # $GOPATH/bin/golint *.go 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | GoHue 294 | A library written in the Go Programming Language for the Philips Hue API. 295 | Copyright (C) 2016 Collin Guarino (Collinux) 296 | 297 | This program is free software; you can redistribute it and/or modify 298 | it under the terms of the GNU General Public License as published by 299 | the Free Software Foundation; either version 2 of the License, or 300 | (at your option) any later version. 301 | 302 | This program is distributed in the hope that it will be useful, 303 | but WITHOUT ANY WARRANTY; without even the implied warranty of 304 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 305 | GNU General Public License for more details. 306 | 307 | You should have received a copy of the GNU General Public License along 308 | with this program; if not, write to the Free Software Foundation, Inc., 309 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 310 | 311 | Also add information on how to contact you by electronic and paper mail. 312 | 313 | If the program is interactive, make it output a short notice like this 314 | when it starts in an interactive mode: 315 | 316 | Gnomovision version 69, Copyright (C) year name of author 317 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 318 | This is free software, and you are welcome to redistribute it 319 | under certain conditions; type `show c' for details. 320 | 321 | The hypothetical commands `show w' and `show c' should show the appropriate 322 | parts of the General Public License. Of course, the commands you use may 323 | be called something other than `show w' and `show c'; they could even be 324 | mouse-clicks or menu items--whatever suits your program. 325 | 326 | You should also get your employer (if you work as a programmer) or your 327 | school, if any, to sign a "copyright disclaimer" for the program, if 328 | necessary. Here is a sample; alter the names: 329 | 330 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 331 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 332 | 333 | {signature of Ty Coon}, 1 April 1989 334 | Ty Coon, President of Vice 335 | 336 | This General Public License does not permit incorporating your program into 337 | proprietary programs. If your program is a subroutine library, you may 338 | consider it more useful to permit linking proprietary applications with the 339 | library. If this is what you want to do, use the GNU Lesser General 340 | Public License instead of this License. 341 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GoHue 2 | Package hue interfaces Philips Hue devices to control lights, scenes, schedules, and groups. 3 | 4 | [![GoDoc](https://camo.githubusercontent.com/b3b2a2b7fad4e76052830945cd839a3bba5be723/687474703a2f2f696d672e736869656c64732e696f2f62616467652f676f646f632d7265666572656e63652d3532373242342e706e67)](https://godoc.org/github.com/Collinux/GoHue) 5 | [![Go Report Card](https://goreportcard.com/badge/github.com/Collinux/GoHue)](https://goreportcard.com/report/github.com/Collinux/GoHue) 6 | [![Build Status](https://img.shields.io/endpoint.svg?url=https%3A%2F%2Factions-badge.atrox.dev%2Fatrox%2Fsync-dotenv%2Fbadge)](https://github.com/collinux/gohue) 7 | 8 | ## See GoHue in action! 9 | ##### Have a cool project you made using GoHue? Add yours here in a pull request! 10 | [HueBeat](https://github.com/Mobilpadde/HueBeat) by [Mobilpadde](https://github.com/mobilpadde) - Light up a room in sync with your heartbeat. 11 | 12 | [BitHue](https://github.com/realytcracker/go-bithue) by [ytcracker](https://github.com/realytcracker) - Light color according to profit gain/loss in bitcoin price 13 | 14 | ## Installation 15 | ``` 16 | go get github.com/collinux/gohue 17 | ``` 18 | 19 | ## Usage 20 | ``` 21 | package main 22 | 23 | import ( 24 | "github.com/collinux/gohue" 25 | ) 26 | 27 | func main() { 28 | // It is recommended that you save the username from bridge.CreateUser 29 | // so you don't have to press the link button every time and re-auth. 30 | // When CreateUser is called it will print the generated user token. 31 | bridgesOnNetwork, _ := hue.FindBridges() 32 | bridge := bridgesOnNetwork[0] 33 | username, _ := bridge.CreateUser("someusernamehere") 34 | bridge.Login(username) 35 | 36 | lights, _ := bridge.GetAllLights() 37 | for _, light := range lights { 38 | light.SetBrightness(100) 39 | light.ColorLoop(true) 40 | } 41 | 42 | nightstandLight, _ := bridge.GetLightByName("Nightstand") 43 | nightstandLight.Blink(5) 44 | nightstandLight.SetName("Bedroom Lamp") 45 | 46 | lights[0].SetColor(hue.RED) 47 | lights[1].SetColor(hue.BLUE) 48 | lights[2].SetColor(hue.GREEN) 49 | 50 | for _, light := range lights { 51 | light.Off() 52 | } 53 | } 54 | ``` 55 | 56 | ## Testing & Validation 57 | 1. Set the environment variable "HUE_USER_TOKEN" 58 | (example: `export HUE_USER_TOKEN="sby7xirR87dUpnfzp1yGRVBeP0Zoxo3LH1krNxSi"`) 59 | 2. Run `go test *_test.go` 60 | 3. Wait for all covered tests to be executed (Estimated 15 seconds - may vary depending on network latency). 61 | 62 | ## Features 63 | ##### Lights 64 | - [x] Get all lights 65 | - [x] Get light by name 66 | - [x] Get light by index on bridge 67 | - [x] Get lights attributes and state 68 | - [x] Set lights attributes (rename) 69 | - [x] Set light state (color, effects, brightness, etc) 70 | - [x] Delete light 71 | - [x] Turn On, Off, Toggle 72 | - [x] Blink 73 | - [x] Colorloop On/Off 74 | 75 | ##### Bridge 76 | - [x] Create user 77 | - [x] Delete user 78 | - [x] Get configuration 79 | - [ ] Modify configuration 80 | - [ ] Get full state (datastore) 81 | - [x] Search for bridges 82 | - [x] Search for new lights 83 | - [ ] Get all timezones 84 | 85 | ##### Schedules 86 | - [x] Get all schedules 87 | - [x] Get schedule by ID 88 | - [x] Get schedule attributes 89 | - [ ] Create schedules 90 | - [ ] Set schedule attributes 91 | - [ ] Delete schedule 92 | 93 | ##### Scenes 94 | - [x] Get all scenes 95 | - [x] Get scene by ID 96 | - [x] Create scene 97 | - [ ] Modify scene 98 | - [ ] Recall scene 99 | - [ ] Delete scene 100 | 101 | ##### Groups 102 | - [ ] Get all groups 103 | - [ ] Create group 104 | - [ ] Get group attributes 105 | - [ ] Set group attributes 106 | - [ ] Set group state 107 | - [ ] Delete Group 108 | 109 | ##### Sensors 110 | - [x] Get all sensors 111 | - [ ] Create sensor 112 | - [ ] Find new sensors 113 | - [ ] Get new sensors 114 | - [ ] Get sensor 115 | - [x] Update sensor 116 | - [ ] Delete sensor 117 | - [ ] Change sensor configuration 118 | 119 | ##### Rules 120 | - [ ] Get all rules 121 | - [ ] Get rule 122 | - [ ] Create rule 123 | - [ ] Update rule 124 | - [ ] Delete rule 125 | 126 | ## API Documentation 127 | This repository is featured on the Philips Hue® developer site and was not developed by "Philips Lighting Holding B.V"... 128 | 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. 129 | 130 | ## License 131 | GoHue - Third party golang library for Philips Hue® gateway interface. 132 | Copyright (C) 2016 Collinux 133 | GPL version 2 or higher http://www.gnu.org/licenses/gpl.html 134 | 135 | ## Contributing 136 | Pull requests happily accepted on GitHub 137 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /bridge.go: -------------------------------------------------------------------------------- 1 | /* 2 | * bridge.go 3 | * GoHue library for Philips Hue 4 | * Copyright (C) 2016 Collin Guarino (Collinux) collinux[-at-]users.noreply.github.com 5 | * License: GPL version 2 or higher http://www.gnu.org/licenses/gpl.html 6 | */ 7 | // All things start with the bridge. You will find many Bridge.Func() items 8 | // to use once a bridge has been created and identified. 9 | // See the getting started guide on the Philips hue website: 10 | // http://www.developers.meethue.com/documentation/getting-started 11 | 12 | package hue 13 | 14 | import ( 15 | "bytes" 16 | "encoding/json" 17 | "encoding/xml" 18 | "errors" 19 | "fmt" 20 | "io" 21 | "io/ioutil" 22 | "log" 23 | "net/http" 24 | "runtime" 25 | "strconv" 26 | "strings" 27 | "time" 28 | ) 29 | 30 | // Bridge struct defines hardware that is used to communicate with the lights. 31 | type Bridge struct { 32 | IPAddress string `json:"internalipaddress"` 33 | Username string // Token from Bridge.CreateUser 34 | Info BridgeInfo 35 | } 36 | 37 | // BridgeInfo struct is the format for parsing xml from a bridge. 38 | type BridgeInfo struct { 39 | XMLName xml.Name `xml:"root"` 40 | Device struct { 41 | XMLName xml.Name `xml:"device"` 42 | DeviceType string `xml:"deviceType"` 43 | FriendlyName string `xml:"friendlyName"` 44 | Manufacturer string `xml:"manufacturer"` 45 | ManufacturerURL string `xml:"manufacturerURL"` 46 | ModelDescription string `xml:"modelDescription"` 47 | ModelName string `xml:"modelName"` 48 | ModelNumber string `xml:"modelNumber"` 49 | ModelURL string `xml:"modelURL"` 50 | SerialNumber string `xml:"serialNumber"` 51 | UDN string `xml:"UDN"` 52 | } `xml:"device"` 53 | } 54 | 55 | // Get sends an http GET to the bridge 56 | func (bridge *Bridge) Get(path string) ([]byte, io.Reader, error) { 57 | uri := fmt.Sprintf("http://" + bridge.IPAddress + path) 58 | client := &http.Client{Timeout: time.Second * 5} 59 | resp, err := client.Get(uri) 60 | 61 | if err != nil { 62 | err = errors.New("unable to access bridge") 63 | return []byte{}, nil, err 64 | } 65 | return HandleResponse(resp) 66 | } 67 | 68 | // Put sends an http PUT to the bridge with 69 | // a body formatted with parameters (in a generic interface) 70 | func (bridge *Bridge) Put(path string, params interface{}) ([]byte, io.Reader, error) { 71 | uri := fmt.Sprintf("http://" + bridge.IPAddress + path) 72 | client := &http.Client{Timeout: time.Second * 5} 73 | 74 | data, err := json.Marshal(params) 75 | if err != nil { 76 | err = errors.New("unable to marshal PUT request interface") 77 | return []byte{}, nil, err 78 | } 79 | //fmt.Println("\n\nPARAMS: ", params) 80 | 81 | request, _ := http.NewRequest("PUT", uri, bytes.NewReader(data)) 82 | resp, err := client.Do(request) 83 | if err != nil { 84 | err = errors.New("unable to access bridge") 85 | return []byte{}, nil, err 86 | } 87 | return HandleResponse(resp) 88 | } 89 | 90 | // Post sends an http POST to the bridge with 91 | // a body formatted with parameters (in a generic interface). 92 | // If `params` is nil then it will send an empty body with the post request. 93 | func (bridge *Bridge) Post(path string, params interface{}) ([]byte, io.Reader, error) { 94 | // Add the params to the request or allow an empty body 95 | request := []byte{} 96 | if params != nil { 97 | reqBody, err := json.Marshal(params) 98 | if err != nil { 99 | err = errors.New("unable to add POST body parameters due to json marshalling error") 100 | return []byte{}, nil, err 101 | } 102 | request = reqBody 103 | } 104 | // Send the request and handle the response 105 | uri := fmt.Sprintf("http://" + bridge.IPAddress + path) 106 | client := &http.Client{Timeout: time.Second * 5} 107 | resp, err := client.Post(uri, "text/json", bytes.NewReader(request)) 108 | 109 | if err != nil { 110 | err = errors.New("unable to access bridge") 111 | return []byte{}, nil, err 112 | } 113 | return HandleResponse(resp) 114 | } 115 | 116 | // Delete sends an http DELETE to the bridge 117 | func (bridge *Bridge) Delete(path string) error { 118 | uri := fmt.Sprintf("http://" + bridge.IPAddress + path) 119 | client := &http.Client{Timeout: time.Second * 5} 120 | req, _ := http.NewRequest("DELETE", uri, nil) 121 | resp, err := client.Do(req) 122 | 123 | if err != nil { 124 | err = errors.New("unable to access bridge") 125 | return err 126 | } 127 | _, _, err = HandleResponse(resp) 128 | return err 129 | } 130 | 131 | // HandleResponse manages the http.Response content from a 132 | // bridge Get/Put/Post/Delete by checking it for errors 133 | // and invalid return types. 134 | func HandleResponse(resp *http.Response) ([]byte, io.Reader, error) { 135 | body, err := ioutil.ReadAll(resp.Body) 136 | defer resp.Body.Close() 137 | if err != nil { 138 | trace("Error parsing bridge description xml.", nil) 139 | return []byte{}, nil, err 140 | } 141 | reader := bytes.NewReader(body) 142 | if strings.Contains(string(body), "\"error\"") { 143 | errString := string(body) 144 | errNum := errString[strings.Index(errString, "type\":")+6 : strings.Index(errString, ",\"address")] 145 | errDesc := errString[strings.Index(errString, "description\":\"")+14 : strings.Index(errString, "\"}}")] 146 | errOut := fmt.Sprintf("Error type %s: %s.", errNum, errDesc) 147 | err = errors.New(errOut) 148 | return []byte{}, nil, err 149 | } 150 | return body, reader, nil 151 | } 152 | 153 | // FindBridges will visit www.meethue.com/api/nupnp to see a list of 154 | // bridges on the local network. 155 | func FindBridges() ([]Bridge, error) { 156 | bridge := Bridge{IPAddress: "www.meethue.com"} 157 | body, _, err := bridge.Get("/api/nupnp") 158 | if err != nil { 159 | err = errors.New("unable to locate bridge") 160 | return []Bridge{}, err 161 | } 162 | bridges := []Bridge{} 163 | err = json.Unmarshal(body, &bridges) 164 | if err != nil { 165 | return bridges, errors.New("unable to parse FindBridges response") 166 | } 167 | if len(bridges) == 0 { 168 | return bridges, errors.New("no bridges found") 169 | } 170 | return bridges, nil 171 | } 172 | 173 | // NewBridge defines hardware that is compatible with Hue. 174 | // The function is the core of all functionality, it's necessary 175 | // to call `NewBridge` and `Login` or `CreateUser` to access any 176 | // lights, scenes, groups, etc. 177 | func NewBridge(ip string) (*Bridge, error) { 178 | bridge := Bridge{ 179 | IPAddress: ip, 180 | } 181 | // Test the connection by attempting to get the bridge info. 182 | err := bridge.GetInfo() 183 | if err != nil { 184 | return &Bridge{}, err 185 | } 186 | return &bridge, nil 187 | } 188 | 189 | // GetInfo retreives the description.xml file from the bridge. 190 | // This is used as a check to see if the bridge is accessible 191 | // and any error will be fatal as the bridge is required for nearly 192 | // all functions. 193 | func (bridge *Bridge) GetInfo() error { 194 | _, reader, err := bridge.Get("/description.xml") 195 | if err != nil { 196 | return err 197 | } 198 | data := BridgeInfo{} 199 | err = xml.NewDecoder(reader).Decode(&data) 200 | if err != nil { 201 | err = errors.New("Error: Unable to decode XML response from bridge. ") 202 | return err 203 | } 204 | bridge.Info = data 205 | return nil 206 | } 207 | 208 | // Login verifies that the username token has bridge access 209 | // and only assigns the bridge its Username value if verification is successful. 210 | func (bridge *Bridge) Login(username string) error { 211 | uri := fmt.Sprintf("/api/%s", username) 212 | _, _, err := bridge.Get(uri) 213 | if err != nil { 214 | return err 215 | } 216 | bridge.Username = username 217 | return nil 218 | } 219 | 220 | // CreateUser adds a new user token on the whitelist. 221 | // and returns this value as a string. 222 | // 223 | // The 'Bridge.Login` function **must be run** with 224 | // the user token as an argument. No functions can 225 | // be called until a valid user token is assigned as the 226 | // bridge's `Username` value. 227 | // 228 | // You cannot set a plaintext username, it must be a 229 | // generated user token. This was done by Philips Hue for security purposes. 230 | func (bridge *Bridge) CreateUser(deviceType string) (string, error) { 231 | params := map[string]string{"devicetype": deviceType} 232 | body, _, err := bridge.Post("/api", params) 233 | if err != nil { 234 | return "", err 235 | } 236 | content := string(body) 237 | username := content[strings.LastIndex(content, ":\"")+2 : strings.LastIndex(content, "\"")] 238 | bridge.Username = username 239 | return username, nil 240 | } 241 | 242 | // DeleteUser deletes a user given its USER KEY, not the string name. 243 | // See http://www.developers.meethue.com/documentation/configuration-api 244 | // for description on `username` deprecation in place of the devicetype key. 245 | func (bridge *Bridge) DeleteUser(username string) error { 246 | uri := fmt.Sprintf("/api/%s/config/whitelist/%s", bridge.Username, username) 247 | err := bridge.Delete(uri) 248 | if err != nil { 249 | return err 250 | } 251 | return nil 252 | } 253 | 254 | // GetAllLights retrieves the state of all lights that the bridge is aware of. 255 | func (bridge *Bridge) GetAllLights() ([]Light, error) { 256 | uri := fmt.Sprintf("/api/%s/lights", bridge.Username) 257 | body, _, err := bridge.Get(uri) 258 | if err != nil { 259 | return []Light{}, err 260 | } 261 | 262 | // An index is at the top of every Light in the array 263 | lightMap := map[string]Light{} 264 | err = json.Unmarshal(body, &lightMap) 265 | if err != nil { 266 | return []Light{}, errors.New("Unable to marshal GetAllLights response. ") 267 | } 268 | 269 | // Parse the index, add the light to the list, and return the array 270 | lights := []Light{} 271 | for index, light := range lightMap { 272 | light.Index, err = strconv.Atoi(index) 273 | if err != nil { 274 | return []Light{}, errors.New("Unable to convert light index to integer. ") 275 | } 276 | light.Bridge = bridge 277 | lights = append(lights, light) 278 | } 279 | return lights, nil 280 | } 281 | 282 | // GetLightByIndex returns a light struct containing data on 283 | // a light given its index stored on the bridge. This is used for 284 | // quickly updating an individual light. 285 | func (bridge *Bridge) GetLightByIndex(index int) (Light, error) { 286 | // Send an http GET and inspect the response 287 | uri := fmt.Sprintf("/api/%s/lights/%d", bridge.Username, index) 288 | body, _, err := bridge.Get(uri) 289 | if err != nil { 290 | return Light{}, err 291 | } 292 | if strings.Contains(string(body), "not available") { 293 | return Light{}, errors.New("Error: Light selection index out of bounds. ") 294 | } 295 | 296 | // Parse and load the response into the light array 297 | light := Light{} 298 | err = json.Unmarshal(body, &light) 299 | if err != nil { 300 | return Light{}, errors.New("Error: Unable to unmarshal light data. ") 301 | } 302 | light.Index = index 303 | light.Bridge = bridge 304 | return light, nil 305 | } 306 | 307 | // FindNewLights makes the bridge search the zigbee spectrum for 308 | // lights in the area and will add them to the list of lights available. 309 | // If successful these new lights can be used by `Bridge.GetAllLights` 310 | // 311 | // Notes from Philips Hue API documentation: 312 | // The bridge will search for 1 minute and will add a maximum of 15 new 313 | // lights. To add further lights, the command needs to be sent again after 314 | // the search has completed. If a search is already active, it will be 315 | // aborted and a new search will start. 316 | // http://www.developers.meethue.com/documentation/lights-api#13_search_for_new_lights 317 | func (bridge *Bridge) FindNewLights() error { 318 | uri := fmt.Sprintf("/api/%s/lights", bridge.Username) 319 | _, _, err := bridge.Post(uri, nil) 320 | if err != nil { 321 | return err 322 | } 323 | return nil 324 | } 325 | 326 | // GetLightByName returns a light struct containing data on a given name. 327 | func (bridge *Bridge) GetLightByName(name string) (Light, error) { 328 | lights, _ := bridge.GetAllLights() 329 | for _, light := range lights { 330 | if light.Name == name { 331 | return light, nil 332 | } 333 | } 334 | errOut := fmt.Sprintf("Error: Light name '%s' not found. ", name) 335 | return Light{}, errors.New(errOut) 336 | } 337 | 338 | 339 | // GetAllSensors retrieves the state of all sensors that the bridge is aware of. 340 | func (bridge *Bridge) GetAllSensors() ([]Sensor, error) { 341 | uri := fmt.Sprintf("/api/%s/sensors", bridge.Username) 342 | body, _, err := bridge.Get(uri) 343 | if err != nil { 344 | return []Sensor{}, err 345 | } 346 | 347 | // An index is at the top of every sensor in the array 348 | sensorList := map[string]Sensor{} 349 | err = json.Unmarshal(body, &sensorList) 350 | if err != nil { 351 | fmt.Print(err) 352 | return []Sensor{}, errors.New("Unable to marshal GetAllSensors response. ") 353 | } 354 | 355 | // Parse the index, add the sensor to the list, and return the array 356 | sensors := make([]Sensor, 0, len(sensorList)) 357 | for index, sensor := range sensorList { 358 | sensor.Index, err = strconv.Atoi(index) 359 | if err != nil { 360 | return []Sensor{}, errors.New("Unable to convert sensor index to integer. ") 361 | } 362 | sensor.Bridge = bridge 363 | sensors = append(sensors, sensor) 364 | } 365 | return sensors, nil 366 | } 367 | 368 | // GetSensorByIndex returns a sensor struct containing data on 369 | // a sensor given its index stored on the bridge. 370 | func (bridge *Bridge) GetSensorByIndex(index int) (Sensor, error) { 371 | // Send an http GET and inspect the response 372 | uri := fmt.Sprintf("/api/%s/sensors/%d", bridge.Username, index) 373 | body, _, err := bridge.Get(uri) 374 | if err != nil { 375 | return Sensor{}, err 376 | } 377 | if strings.Contains(string(body), "not available") { 378 | return Sensor{}, errors.New("Error: Sensor selection index out of bounds. ") 379 | } 380 | 381 | // Parse and load the response into the sensor array 382 | sensor := Sensor{} 383 | err = json.Unmarshal(body, &sensor) 384 | if err != nil { 385 | return Sensor{}, errors.New("Error: Unable to unmarshal light data. ") 386 | } 387 | sensor.Index = index 388 | sensor.Bridge = bridge 389 | return sensor, nil 390 | } 391 | 392 | // Log the date, time, file location, line number, and function. 393 | // Message can be "" or Err can be nil (not both) 394 | func trace(message string, err error) { 395 | pc := make([]uintptr, 10) 396 | runtime.Callers(2, pc) 397 | f := runtime.FuncForPC(pc[0]) 398 | file, line := f.FileLine(pc[0]) 399 | if err != nil { 400 | log.Printf("%s:%d %s: %s\n", file, line, f.Name(), err) 401 | } else { 402 | log.Printf("%s:%d %s: %s\n", file, line, f.Name(), message) 403 | } 404 | } 405 | -------------------------------------------------------------------------------- /bridge_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * bridge_test.go 3 | * GoHue library for Philips Hue 4 | * Copyright (C) 2016 Collin Guarino (Collinux) collinux[-at-]users.noreply.github.com 5 | * License: GPL version 2 or higher http://www.gnu.org/licenses/gpl.html 6 | */ 7 | 8 | package hue 9 | 10 | import ( 11 | "github.com/collinux/gohue" 12 | "testing" 13 | "os" 14 | ) 15 | 16 | func TestCreateUser(t *testing.T) { 17 | bridges, err := hue.FindBridges() 18 | if err != nil { 19 | t.Fatal(err) 20 | } 21 | bridge := bridges[0] 22 | username, _ := bridge.CreateUser("test") 23 | bridge.Login(username) 24 | //bridge.DeleteUser(bridge.Username) 25 | } 26 | 27 | func TestFindBridges(t *testing.T) { 28 | bridges, err := hue.FindBridges() 29 | if err != nil { 30 | t.Fatal(err) 31 | } 32 | t.Log(bridges) 33 | } 34 | 35 | func TestBridgeLogin(t *testing.T) { 36 | bridges, err := hue.FindBridges() 37 | if err != nil { 38 | t.Fatal(err) 39 | } 40 | if os.Getenv("HUE_USER_TOKEN") == "" { 41 | t.Fatal("The environment variable HUE_USER_TOKEN must be set to the value from bridge.CreateUser") 42 | } 43 | bridges[0].Login(os.Getenv("HUE_USER_TOKEN")) 44 | 45 | } 46 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/collinux/gohue 2 | 3 | require ( 4 | golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413 // indirect 5 | golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f // indirect 6 | golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 // indirect 7 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e // indirect 8 | golang.org/x/sys v0.0.0-20191206220618-eeba5f6aabab // indirect 9 | golang.org/x/text v0.3.2 // indirect 10 | golang.org/x/tools v0.0.0-20191209205957-115af5e89bf7 // indirect 11 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 // indirect 12 | ) 13 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 2 | golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 3 | golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f h1:J5lckAjkw6qYlOZNj90mLYNTEKDvWeuc1yieZ8qUzUE= 4 | golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= 5 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 6 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 7 | golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 8 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 9 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 10 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 11 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 12 | golang.org/x/sys v0.0.0-20191206220618-eeba5f6aabab/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 13 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 14 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 15 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 16 | golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 17 | golang.org/x/tools v0.0.0-20191209205957-115af5e89bf7 h1:3MdG0dIfJQXgcWrggGFC1ZW4EONthSyh+tgROXtsaCw= 18 | golang.org/x/tools v0.0.0-20191209205957-115af5e89bf7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 19 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 20 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 21 | -------------------------------------------------------------------------------- /group.go: -------------------------------------------------------------------------------- 1 | /* 2 | * group.go 3 | * GoHue library for Philips Hue 4 | * Copyright (C) 2016 Collin Guarino (Collinux) collinux[-at-]users.noreply.github.com 5 | * License: GPL version 2 or higher http://www.gnu.org/licenses/gpl.html 6 | */ 7 | // http://www.developers.meethue.com/documentation/groups-api 8 | 9 | package hue 10 | 11 | import ( 12 | "encoding/json" 13 | "fmt" 14 | ) 15 | 16 | // Action struct defines the state of a group 17 | type Action struct { 18 | Alert string `json:"alert,omitempty"` 19 | Bri int `json:"bri,omitempty"` 20 | Colormode string `json:"colormode,omitempty"` 21 | Ct int `json:"ct,omitempty"` 22 | Effect string `json:"effect,omitempty"` 23 | Hue *int `json:"hue,omitempty"` 24 | On *bool `json:"on,omitempty"` 25 | Sat *int `json:"sat,omitempty"` 26 | XY []float64 `json:"xy,omitempty"` 27 | Scene string `json:"scene,omitempty"` 28 | } 29 | 30 | // Group struct defines the attributes for a group of lights. 31 | type Group struct { 32 | Action Action `json:"action"` 33 | Lights []string `json:"lights"` 34 | Name string `json:"name"` 35 | Type string `json:"type"` 36 | } 37 | 38 | // GetGroups gets the attributes for each group of lights. 39 | // TODO: NOT TESTED, NOT FULLY IMPLEMENTED 40 | func (bridge *Bridge) GetGroups() ([]Group, error) { 41 | uri := fmt.Sprintf("/api/%s/groups", bridge.Username) 42 | body, _, err := bridge.Get(uri) 43 | if err != nil { 44 | return []Group{}, err 45 | } 46 | 47 | //fmt.Println("GROUP GET: ", string(body)) 48 | 49 | groups := map[string]Group{} 50 | err = json.Unmarshal(body, &groups) 51 | if err != nil { 52 | return []Group{}, err 53 | } 54 | //fmt.Println("GROUPS: ", groups) 55 | 56 | return []Group{}, nil 57 | } 58 | 59 | // SetGroupState sends an action to group 60 | func (bridge *Bridge) SetGroupState(group int, action *Action) error { 61 | uri := fmt.Sprintf("/api/%s/groups/%d/action", bridge.Username, group) 62 | _, _, err := bridge.Put(uri, action) 63 | if err != nil { 64 | return err 65 | } 66 | return nil 67 | } 68 | -------------------------------------------------------------------------------- /group_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * group_test.go 3 | * GoHue library for Philips Hue 4 | * Copyright (C) 2016 Collin Guarino (Collinux) collinux[-at-]users.noreply.github.com 5 | * License: GPL version 2 or higher http://www.gnu.org/licenses/gpl.html 6 | */ 7 | 8 | package hue 9 | 10 | import ( 11 | "github.com/collinux/gohue" 12 | "os" 13 | "testing" 14 | ) 15 | 16 | func TestGetGroups(t *testing.T) { 17 | bridges, err := hue.FindBridges() 18 | if err != nil { 19 | t.Fatal(err) 20 | } 21 | bridge := bridges[0] 22 | if os.Getenv("HUE_USER_TOKEN") == "" { 23 | t.Fatal("The environment variable HUE_USER_TOKEN must be set to the value from bridge.CreateUser") 24 | } 25 | bridge.Login(os.Getenv("HUE_USER_TOKEN")) 26 | groups, err := bridge.GetGroups() 27 | if err != nil { 28 | t.Fatal(err) 29 | } 30 | t.Log(groups) 31 | } 32 | -------------------------------------------------------------------------------- /light.go: -------------------------------------------------------------------------------- 1 | /* 2 | * light.go 3 | * GoHue library for Philips Hue 4 | * Copyright (C) 2016 Collin Guarino (Collinux) collinux[-at-]users.noreply.github.com 5 | * License: GPL version 2 or higher http://www.gnu.org/licenses/gpl.html 6 | */ 7 | // http://www.developers.meethue.com/documentation/lights-api 8 | 9 | package hue 10 | 11 | import ( 12 | "errors" 13 | "fmt" 14 | "time" 15 | ) 16 | 17 | // Light struct defines attributes of a light. 18 | type Light struct { 19 | State struct { 20 | On bool `json:"on"` // On or Off state of the light ("true" or "false") 21 | Bri uint8 `json:"bri"` // Brightness value 1-254 22 | Hue uint16 `json:"hue"` // Hue value 1-65535 23 | Saturation uint8 `json:"sat"` // Saturation value 0-254 24 | Effect string `json:"effect"` // "None" or "Colorloop" 25 | XY [2]float32 `json:"xy"` // Coordinates of color in CIE color space 26 | CT int `json:"ct"` // Mired Color Temperature (google it) 27 | Alert string `json:"alert"` // "selected" or "none" 28 | ColorMode string `json:"colormode"` // HS or XY mode for choosing color 29 | Reachable bool `json:"reachable"` 30 | } `json:"state"` 31 | Type string `json:"type"` 32 | Name string `json:"name"` 33 | ModelID string `json:"modelid"` 34 | ManufacturerName string `json:"manufacturername"` 35 | UniqueID string `json:"uniqueid"` 36 | SWVersion string `json:"swversion"` 37 | Index int // Set by index of light array response 38 | Bridge *Bridge // Set by the bridge when the light is found 39 | } 40 | 41 | // LightState used in Light.SetState to amend light attributes. 42 | type LightState struct { 43 | On bool `json:"on"` 44 | Bri uint8 `json:"bri,omitempty"` 45 | Hue uint16 `json:"hue,omitempty"` 46 | Sat uint8 `json:"sat,omitempty"` 47 | XY *[2]float32 `json:"xy,omitempty"` 48 | CT uint16 `json:"ct,omitempty"` 49 | Effect string `json:"effect,omitempty"` 50 | Alert string `json:"alert,omitempty"` 51 | TransitionTime string `json:"transitiontime,omitempty"` 52 | SaturationIncrement int16 `json:"sat_inc,omitempty"` 53 | HueIncrement int32 `json:"hue_inc,omitempty"` 54 | BrightnessIncrement int16 `json:"bri_inc,omitempty"` 55 | CTIncrement int32 `json:"ct_inc,omitempty"` 56 | XYIncrement *[2]float32 `json:"xy_inc,omitempty"` 57 | Name string `json:"name,omitempty"` 58 | } 59 | 60 | // SetName assigns a new name in the light's 61 | // attributes as recognized by the bridge. 62 | func (light *Light) SetName(name string) error { 63 | uri := fmt.Sprintf("/api/%s/lights/%d", light.Bridge.Username, light.Index) 64 | body := make(map[string]string) 65 | body["name"] = name 66 | _, _, err := light.Bridge.Put(uri, body) 67 | if err != nil { 68 | return err 69 | } 70 | return nil 71 | } 72 | 73 | // Off turns the light source off 74 | func (light *Light) Off() error { 75 | return light.SetState(LightState{On: false}) 76 | } 77 | 78 | // On turns the light source on 79 | func (light *Light) On() error { 80 | return light.SetState(LightState{On: true}) 81 | } 82 | 83 | // Toggle switches the light source on and off 84 | func (light *Light) Toggle() error { 85 | if light.State.On { 86 | return light.Off() 87 | } 88 | return light.On() 89 | } 90 | 91 | // Delete removes the light from the 92 | // list of lights available on the bridge. 93 | func (light *Light) Delete() error { 94 | uri := fmt.Sprintf("/api/%s/lights/%d", light.Bridge.Username, light.Index) 95 | err := light.Bridge.Delete(uri) 96 | if err != nil { 97 | return err 98 | } 99 | return nil 100 | } 101 | 102 | // Blink increases and decrease the brightness 103 | // repeatedly for a given seconds interval and return the 104 | // light back to its off or on state afterwards. 105 | // Note: time will vary based on connection speed and algorithm speed. 106 | func (light *Light) Blink(seconds int) error { 107 | originalPosition := light.State.On 108 | originalBrightness := light.State.Bri 109 | blinkMax := 75 // Percent brightness 110 | blinkMin := 25 // Percent brightness 111 | 112 | // Start with near maximum brightness and toggle between that and 113 | // a lesser brightness to create a blinking effect. 114 | for i := 0; i <= seconds*2; i++ { 115 | if i == 0 { 116 | err := light.SetBrightness(blinkMax) 117 | if err != nil { 118 | return err 119 | } 120 | } else if i%2 == 0 { 121 | err := light.SetBrightness(blinkMax) 122 | if err != nil { 123 | return err 124 | } 125 | } else { 126 | err := light.SetBrightness(blinkMin) 127 | if err != nil { 128 | return err 129 | } 130 | } 131 | time.Sleep(time.Second / 2) 132 | } 133 | 134 | // Return the light to its original on or off state and brightness 135 | if light.State.Bri != originalBrightness || light.State.On != originalPosition { 136 | light.SetState(LightState{On: originalPosition, Bri: originalBrightness}) 137 | } 138 | return nil 139 | } 140 | 141 | // ColorLoop sets the light state to 'colorloop' if `active` 142 | // is true or it sets the light state to "none" if `activate` is false. 143 | func (light *Light) ColorLoop(activate bool) error { 144 | var state = "none" 145 | if activate { 146 | state = "colorloop" 147 | } 148 | return light.SetState(LightState{On: true, Effect: state}) 149 | } 150 | 151 | // XY positions on the HSL color spectrum used in `Light.SetColor` 152 | // https://en.wikipedia.org/wiki/HSL_and_HSV 153 | var ( 154 | RED = &[2]float32{0.6915, 0.3083} 155 | YELLOW = &[2]float32{0.4023, 0.4725} 156 | ORANGE = &[2]float32{0.4693, 0.4007} 157 | GREEN = &[2]float32{0.1700, 0.7000} 158 | CYAN = &[2]float32{0.1610, 0.3549} 159 | BLUE = &[2]float32{0.1530, 0.0480} 160 | PURPLE = &[2]float32{0.2363, 0.1154} 161 | PINK = &[2]float32{0.3645, 0.1500} 162 | WHITE = &[2]float32{0.3227, 0.3290} 163 | ) 164 | 165 | // SetColor requires a selection from the above light 166 | // color variable section and sets the light to that XY HSL color 167 | func (light *Light) SetColor(color *[2]float32) error { 168 | lightState := LightState{On: true, XY: color} 169 | err := light.SetState(lightState) 170 | if err != nil { 171 | return err 172 | } 173 | return nil 174 | } 175 | 176 | // SetColorXY requires a selection from the above light 177 | // color variable section and sets the light to that XY HSL color 178 | // aliased for clarity 179 | func (light *Light) SetColorXY(color *[2]float32) { 180 | light.SetColor(color) 181 | } 182 | 183 | // SetColorHS requires a selection from the above light 184 | // color variable section and sets the light to the Hue value 185 | func (light *Light) SetColorHS(color uint16) error { 186 | lightState := LightState{On: true, Hue: color} 187 | err := light.SetState(lightState) 188 | if err != nil { 189 | return err 190 | } 191 | return nil 192 | } 193 | 194 | // Dim lowers the brightness by a percent. 195 | // Note the required value is an integer, for example "20" is converted to 20%. 196 | func (light *Light) Dim(percent int) error { 197 | if percent > 0 && percent <= 100 { 198 | originalBri := light.State.Bri 199 | decreaseBri := float32(originalBri) * float32((float32(percent) / 100.0)) 200 | newBri := uint8(originalBri - uint8(decreaseBri)) 201 | if newBri < 0 { 202 | newBri = 0 203 | } 204 | lightState := LightState{On: true, Bri: newBri} 205 | err := light.SetState(lightState) 206 | if err != nil { 207 | return err 208 | } 209 | return nil 210 | } 211 | return errors.New("Light.Dim percentage given is not between 1 and 100. ") 212 | } 213 | 214 | // SetBrightness sets the brightness to a percentage of the maximum 215 | // maximum brightness as an integer (`LightStruct.Bri between 1 and 254 inclusive`) 216 | func (light *Light) SetBrightness(percent int) error { 217 | if percent > 0 && percent <= 100 { 218 | brightness := uint8(float32(percent) * 2.54) // 100=254x --> 2.54 219 | lightState := LightState{On: true, Bri: brightness} 220 | err := light.SetState(lightState) 221 | if err != nil { 222 | return err 223 | } 224 | return nil 225 | } 226 | return errors.New("Light.SetBrightness percentage is not between 1 and 100. ") 227 | } 228 | 229 | // Brighten will increase LightStruct.Bri by a given percent (integer) 230 | func (light *Light) Brighten(percent int) error { 231 | if percent > 0 && percent <= 100 { 232 | originalBri := light.State.Bri 233 | increaseBri := float32(originalBri) * float32((float32(percent) / 100.0)) 234 | newBri := uint8(originalBri + uint8(increaseBri)) 235 | if newBri > 254 { // LightState.Bri must be between 1 and 254 inclusive 236 | newBri = 254 237 | } 238 | lightState := LightState{On: true, Bri: newBri} 239 | err := light.SetState(lightState) 240 | if err != nil { 241 | return err 242 | } 243 | return nil 244 | } 245 | return errors.New("Light.Brighten percentage is not between 1 and 100. ") 246 | } 247 | 248 | // SetState modifyies light attributes. See `LightState` struct for attributes. 249 | // Brightness must be between 1 and 254 (inclusive) 250 | // Hue must be between 0 and 65535 (inclusive) 251 | // Sat must be between 0 and 254 (inclusive) 252 | // See http://www.developers.meethue.com/documentation/lights-api for more info 253 | func (light *Light) SetState(newState LightState) error { 254 | uri := fmt.Sprintf("/api/%s/lights/%d/state", light.Bridge.Username, light.Index) 255 | _, _, err := light.Bridge.Put(uri, newState) 256 | if err != nil { 257 | return err 258 | } 259 | 260 | // Get the new light state and update the current Light struct 261 | *light, err = light.Bridge.GetLightByIndex(light.Index) 262 | if err != nil { 263 | return err 264 | } 265 | return nil 266 | } 267 | -------------------------------------------------------------------------------- /light_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * light_test.go 3 | * GoHue library for Philips Hue 4 | * Copyright (C) 2016 Collin Guarino (Collinux) collinux[-at-]users.noreply.github.com 5 | * License: GPL version 2 or higher http://www.gnu.org/licenses/gpl.html 6 | */ 7 | 8 | package hue 9 | 10 | import ( 11 | "github.com/collinux/gohue" 12 | "testing" 13 | "time" 14 | "os" 15 | ) 16 | 17 | func TestSetLightState(t *testing.T) { 18 | bridges, err := hue.FindBridges() 19 | if err != nil { 20 | t.Fatal(err) 21 | } 22 | bridge := bridges[0] 23 | bridge.Login(os.Getenv("HUE_USER_TOKEN")) 24 | //nameTest, err := bridge.GetLightByName("Desk Light") // Also tests GetAllLights 25 | //if err != nil { 26 | // t.Fatal(err) 27 | //} 28 | //_ = nameTest 29 | selectedLight, err := bridge.GetLightByIndex(1) 30 | if err != nil { 31 | t.Fatal(err) 32 | } 33 | 34 | selectedLight.On() 35 | selectedLight.SetBrightness(100) 36 | time.Sleep(time.Second) 37 | selectedLight.Off() 38 | time.Sleep(time.Second) 39 | selectedLight.Toggle() 40 | time.Sleep(time.Second) 41 | 42 | selectedLight.ColorLoop(false) 43 | 44 | selectedLight.SetName(selectedLight.Name) 45 | 46 | selectedLight.Blink(3) 47 | 48 | selectedLight.Dim(20) 49 | selectedLight.Brighten(20) 50 | 51 | // Skip validation of colors 52 | //selectedLight.SetColor(hue.RED) 53 | //time.Sleep(time.Second) 54 | //selectedLight.SetColor(hue.YELLOW) 55 | //time.Sleep(time.Second) 56 | //selectedLight.SetColor(hue.GREEN) 57 | //time.Sleep(time.Second) 58 | //selectedLight.SetColor(hue.WHITE) 59 | //time.Sleep(time.Second) 60 | //selectedLight.Off() 61 | 62 | // TODO 63 | // Skip validation of deleting and re-adding light 64 | // _ := selectedLight.Delete() 65 | } 66 | 67 | func TestFindNewLights(t *testing.T) { 68 | bridges, err := hue.FindBridges() 69 | if err != nil { 70 | t.Fatal(err) 71 | } 72 | bridge := bridges[0] 73 | if os.Getenv("HUE_USER_TOKEN") == "" { 74 | t.Fatal("The environment variable HUE_USER_TOKEN must be set to the value from bridge.CreateUser") 75 | } 76 | bridge.Login(os.Getenv("HUE_USER_TOKEN")) 77 | err = bridge.FindNewLights() 78 | if err != nil { 79 | t.Fatal(err) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /scene.go: -------------------------------------------------------------------------------- 1 | /* 2 | * scene.go 3 | * GoHue library for Philips Hue 4 | * Copyright (C) 2016 Collin Guarino (Collinux) collinux[-at-]users.noreply.github.com 5 | * License: GPL version 2 or higher http://www.gnu.org/licenses/gpl.html 6 | */ 7 | // http://www.developers.meethue.com/documentation/scenes-api 8 | 9 | package hue 10 | 11 | import ( 12 | "encoding/json" 13 | "errors" 14 | "fmt" 15 | ) 16 | 17 | // Scene struct defines attributes for Scene items 18 | type Scene struct { 19 | Appdata *struct { 20 | Data string `json:"data,omitempty"` 21 | Version int `json:"version,omitempty"` 22 | } `json:"appdata,omitempty"` 23 | Lastupdated string `json:"lastupdated,omitempty"` 24 | Lights []string `json:"lights,omitempty"` 25 | Locked bool `json:"locked,omitempty"` 26 | Name string `json:"name,omitempty"` 27 | Owner string `json:"owner,omitempty"` 28 | Picture string `json:"picture,omitempty"` 29 | Recycle bool `json:"recycle,omitempty"` 30 | Version int `json:"version,omitempty"` 31 | ID string `json:",omitempty"` 32 | } 33 | 34 | // GetAllScenes gets the attributes for all scenes. 35 | func (bridge *Bridge) GetAllScenes() ([]Scene, error) { 36 | uri := fmt.Sprintf("/api/%s/scenes", bridge.Username) 37 | body, _, err := bridge.Get(uri) 38 | if err != nil { 39 | return []Scene{}, err 40 | } 41 | 42 | scenes := map[string]Scene{} 43 | err = json.Unmarshal(body, &scenes) 44 | if err != nil { 45 | return []Scene{}, err 46 | } 47 | scenesList := []Scene{} 48 | for key, value := range scenes { 49 | scene := Scene{} 50 | scene = value 51 | scene.ID = key 52 | scenesList = append(scenesList, scene) 53 | } 54 | return scenesList, nil 55 | } 56 | 57 | // GetScene gets the attributes for an individual scene. 58 | // This is used to optimize time when updating the state of the scene. 59 | // Note: The ID is not an index, it's a unique key generated for each scene. 60 | func (bridge *Bridge) GetScene(id string) (Scene, error) { 61 | uri := fmt.Sprintf("/api/%s/scenes/%s", bridge.Username, id) 62 | body, _, err := bridge.Get(uri) 63 | if err != nil { 64 | return Scene{}, err 65 | } 66 | 67 | scene := Scene{} 68 | err = json.Unmarshal(body, &scene) 69 | if err != nil { 70 | return Scene{}, err 71 | } 72 | return scene, nil 73 | } 74 | 75 | // GetSceneByName gets the attributes for the scene identified by a name 76 | func (bridge *Bridge) GetSceneByName(name string) (Scene, error) { 77 | 78 | scenes, _ := bridge.GetAllScenes() 79 | 80 | // Iterate in reverse, as later entries seem to be the newest 81 | for i := len(scenes) - 1; i >= 0; i-- { 82 | if scenes[i].Name == name { 83 | return scenes[i], nil 84 | } 85 | } 86 | 87 | errOut := fmt.Sprintf("Error: Scene name '%s' not found. ", name) 88 | return Scene{}, errors.New(errOut) 89 | } 90 | 91 | // RecallScene recalls a scene 92 | func (bridge *Bridge) RecallScene(id string) error { 93 | action := &Action{Scene: id} 94 | return bridge.SetGroupState(0, action) 95 | } 96 | 97 | // RecallSceneByName recalls a scene 98 | func (bridge *Bridge) RecallSceneByName(name string) error { 99 | scene, err := bridge.GetSceneByName(name) 100 | if err != nil { 101 | return err 102 | } 103 | return bridge.RecallScene(scene.ID) 104 | } 105 | 106 | // CreateScene posts a new scene configuration to the bridge. 107 | func (bridge *Bridge) CreateScene(scene Scene) error { 108 | uri := fmt.Sprintf("/api/%s/scenes/", bridge.Username) 109 | _, _, err := bridge.Post(uri, scene) 110 | if err != nil { 111 | return err 112 | } 113 | return nil 114 | } 115 | 116 | // Bridge.ModifySceneState amends light states for lights 117 | // included in a scene list. See `Bridge.ModifyScene` for 118 | // changing the lights included in the scene list. 119 | // func (bridge *Bridge) ModifySceneState() error { 120 | // 121 | // } 122 | 123 | // Bridge.ModifyScene amends the lights included for a given scene or 124 | // it can be used to change the scene name. To amend light states for 125 | // lights included in a scene list see `Bridge.ModifySceneState`. 126 | // func (bridge *Bridge) ModifyScene() error { 127 | // uri := fmt.Sprintf("/api/%s/scenes/%s/lightstates/%s", 128 | // bridge.Username, oldScene.ID, ) 129 | // } 130 | -------------------------------------------------------------------------------- /scene_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * scene_test.go 3 | * GoHue library for Philips Hue 4 | * Copyright (C) 2016 Collin Guarino (Collinux) collinux[-at-]users.noreply.github.com 5 | * License: GPL version 2 or higher http://www.gnu.org/licenses/gpl.html 6 | */ 7 | 8 | package hue 9 | 10 | import ( 11 | "github.com/collinux/gohue" 12 | "testing" 13 | "os" 14 | ) 15 | 16 | func TestGetAllScenes(t *testing.T) { 17 | bridges, err := hue.FindBridges() 18 | if err != nil { 19 | t.Fatal(err) 20 | } 21 | bridge := bridges[0] 22 | if os.Getenv("HUE_USER_TOKEN") == "" { 23 | t.Fatal("The environment variable HUE_USER_TOKEN must be set to the value from bridge.CreateUser") 24 | } 25 | bridge.Login(os.Getenv("HUE_USER_TOKEN")) 26 | scenes, err := bridge.GetAllScenes() 27 | if err != nil { 28 | t.Fatal(err) 29 | } 30 | t.Log(scenes) 31 | } 32 | 33 | // TODO not functional 34 | // func TestCreateScene(t *testing.T) { 35 | // bridges, err := hue.FindBridges() 36 | // if err != nil { 37 | // t.Fatal(err) 38 | // } 39 | // bridge := bridges[0] 40 | // bridge.Login(os.Getenv("HUE_USER_TOKEN")) 41 | // scene := hue.Scene{Name: "Testing", Lights: []string{"1", "2"}} 42 | // err = bridge.CreateScene(scene) 43 | // if err != nil { 44 | // t.Fatal(err) 45 | // } 46 | // } 47 | -------------------------------------------------------------------------------- /schedule.go: -------------------------------------------------------------------------------- 1 | /* 2 | * schedule.go 3 | * GoHue library for Philips Hue 4 | * Copyright (C) 2016 Collin Guarino (Collinux) collinux[-at-]users.noreply.github.com 5 | * License: GPL version 2 or higher http://www.gnu.org/licenses/gpl.html 6 | */ 7 | // http://www.developers.meethue.com/documentation/schedules-api-0 8 | 9 | package hue 10 | 11 | import ( 12 | "encoding/json" 13 | "fmt" 14 | ) 15 | 16 | // Schedule struct defines attributes of Alarms and Timers 17 | type Schedule struct { 18 | Name string `json:"name"` 19 | Description string `json:"description"` 20 | Command struct { 21 | Address string `json:"address"` 22 | Body struct { 23 | Scene string `json:"scene"` 24 | } `json:"body"` 25 | Method string `json:"method"` 26 | } `json:"command"` 27 | Localtime string `json:"localtime"` 28 | Time string `json:"time"` 29 | Created string `json:"created"` 30 | Status string `json:"status"` 31 | Autodelete bool `json:"autodelete"` 32 | ID string 33 | } 34 | 35 | // GetAllSchedules gets Alarms and Timers in a Schedule struct. 36 | func (bridge *Bridge) GetAllSchedules() ([]Schedule, error) { 37 | uri := fmt.Sprintf("/api/%s/schedules", bridge.Username) 38 | body, _, err := bridge.Get(uri) 39 | if err != nil { 40 | return []Schedule{}, err 41 | } 42 | 43 | // Each index key is the topmost element of the json array. 44 | // Unmarshal the array, loop through each index key, and add it to the list 45 | schedules := map[string]Schedule{} 46 | err = json.Unmarshal(body, &schedules) 47 | if err != nil { 48 | return []Schedule{}, err 49 | } 50 | scheduleList := []Schedule{} 51 | for key, value := range schedules { 52 | schedule := Schedule{} 53 | schedule = value 54 | schedule.ID = key 55 | scheduleList = append(scheduleList, schedule) 56 | } 57 | return scheduleList, nil 58 | } 59 | 60 | // GetSchedule gets the attributes for an individual schedule. 61 | // This is used to optimize time when updating the state of a schedule item. 62 | // Note: The ID is not an index, it's a unique key generated for each schedule. 63 | func (bridge *Bridge) GetSchedule(id string) (Schedule, error) { 64 | uri := fmt.Sprintf("/api/%s/schedules/%s", bridge.Username, id) 65 | body, _, err := bridge.Get(uri) 66 | if err != nil { 67 | return Schedule{}, err 68 | } 69 | 70 | schedule := Schedule{} 71 | err = json.Unmarshal(body, &schedule) 72 | if err != nil { 73 | return Schedule{}, err 74 | } 75 | return schedule, nil 76 | } 77 | 78 | // CreateSchedule TODO: NOT TESTED, NOT FULLY IMPLEMENTED 79 | func (bridge *Bridge) CreateSchedule(schedule Schedule) error { 80 | uri := fmt.Sprintf("/api/%s/schedules", bridge.Username) 81 | body, _, err := bridge.Post(uri, schedule) 82 | if err != nil { 83 | return err 84 | } 85 | 86 | fmt.Println("CREATE SCHEDULE BODY: ", string(body)) 87 | return nil 88 | } 89 | 90 | // func (schedule *Schedule) Disable() { 91 | // 92 | // } 93 | // 94 | // func (schedule *Schedule) Enable() { 95 | // 96 | // } 97 | // 98 | // 99 | // func (bridge *Bridge) GetSchedule(index int) (interface{}, error) { 100 | // return []interface{}, nil 101 | // } 102 | // 103 | // func (bridge *Bridge) SetSchedule(index int, schedule interface{}) error { 104 | // return nil 105 | // } 106 | // 107 | // func (bridge *Bridge) DeleteSchedule(index int) error { 108 | // return nil 109 | // } 110 | -------------------------------------------------------------------------------- /schedule_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * schedule_test.go 3 | * GoHue library for Philips Hue 4 | * Copyright (C) 2016 Collin Guarino (Collinux) collinux[-at-]users.noreply.github.com 5 | * License: GPL version 2 or higher http://www.gnu.org/licenses/gpl.html 6 | */ 7 | 8 | package hue 9 | 10 | import ( 11 | "github.com/collinux/gohue" 12 | "os" 13 | "testing" 14 | ) 15 | 16 | func TestGetAllSchedules(t *testing.T) { 17 | bridges, err := hue.FindBridges() 18 | if err != nil { 19 | t.Fatal(err) 20 | } 21 | bridge := bridges[0] 22 | if os.Getenv("HUE_USER_TOKEN") == "" { 23 | t.Fatal("The environment variable HUE_USER_TOKEN must be set to the value from bridge.CreateUser") 24 | } 25 | bridge.Login(os.Getenv("HUE_USER_TOKEN")) 26 | schedules, err := bridge.GetAllSchedules() 27 | if err != nil { 28 | t.Fatal(err) 29 | } 30 | t.Log(schedules) 31 | } 32 | 33 | func TestGetSchedule(t *testing.T) { 34 | bridges, err := hue.FindBridges() 35 | if err != nil { 36 | t.Fatal(err) 37 | } 38 | bridge := bridges[0] 39 | if os.Getenv("HUE_USER_TOKEN") == "" { 40 | t.Fatal("The environment variable HUE_USER_TOKEN must be set to the value from bridge.CreateUser") 41 | } 42 | bridge.Login(os.Getenv("HUE_USER_TOKEN")) 43 | schedules, err := bridge.GetAllSchedules() 44 | if err != nil { 45 | t.Fatal(err) 46 | } 47 | schedule, err := bridge.GetSchedule(schedules[0].ID) 48 | if err != nil { 49 | t.Fatal(err) 50 | } 51 | t.Log(schedule) 52 | } 53 | 54 | // TODO 55 | // func TestCreateSchedule(t *testing.T) { } 56 | -------------------------------------------------------------------------------- /sensor.go: -------------------------------------------------------------------------------- 1 | /* 2 | * sensor.go 3 | * GoHue library for Philips Hue 4 | * Copyright (C) 2016 Collin Guarino (Collinux) collinux[-at-]users.noreply.github.com 5 | * License: GPL version 2 or higher http://www.gnu.org/licenses/gpl.html 6 | */ 7 | // https://developers.meethue.com/documentation/sensors-api 8 | 9 | package hue 10 | 11 | import ( 12 | "time" 13 | "strings" 14 | ) 15 | 16 | // special time type for unmarshal of lastupdated 17 | type UpdateTime struct { 18 | *time.Time 19 | } 20 | 21 | // implement Unmarshal interface 22 | // required for "none" as lastupdated in unused sensor 23 | func (u *UpdateTime) UnmarshalJSON(b []byte) error { 24 | s := strings.Trim(string(b), "\"") 25 | if s == "none" { 26 | *u = UpdateTime{&time.Time{}} 27 | return nil 28 | } 29 | t, err := time.Parse("2006-01-02T15:04:05", s) 30 | if err != nil { 31 | return err 32 | } 33 | *u = UpdateTime{&t} 34 | return nil 35 | } 36 | 37 | // Sensor struct defines attributes of a sensor. 38 | type Sensor struct { 39 | State struct { 40 | Daylight bool `json:"daylight"` // True if day & false if night 41 | LastUpdated UpdateTime `json:"lastupdated"` // Time of last update 42 | ButtonEvent uint16 `json:"buttonevent"` // ID of button event 43 | } `json:"state"` 44 | 45 | Config struct { 46 | On bool `json:"on"` // Turns the sensor on/off. When off, state changes of the sensor are not reflected in the sensor resource. 47 | Reachable bool `json:"reachable"` // Indicates whether communication with devices is possible 48 | Battery uint8 `json:"battery"` // The current battery state in percent, only for battery powered devices 49 | } `json:"config"` 50 | 51 | Type string `json:"type"` 52 | Name string `json:"name"` 53 | ModelID string `json:"modelid"` 54 | ManufacturerName string `json:"manufacturername"` 55 | UniqueID string `json:"uniqueid"` 56 | SWVersion string `json:"swversion"` 57 | Index int // Set by index of sensor array response 58 | Bridge *Bridge // Set by the bridge when the sensor is found 59 | } 60 | 61 | /// Refresh sensor attributes 62 | func (s *Sensor) Refresh() error { 63 | sensor, err := s.Bridge.GetSensorByIndex(s.Index) 64 | if err != nil { 65 | return err 66 | } 67 | 68 | s.State = sensor.State 69 | s.Config = sensor.Config 70 | s.SWVersion = sensor.SWVersion 71 | s.Name = sensor.Name 72 | return nil 73 | } 74 | -------------------------------------------------------------------------------- /sensor_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * sensor_test.go 3 | * GoHue library for Philips Hue 4 | * Copyright (C) 2016 Collin Guarino (Collinux) collinux[-at-]users.noreply.github.com 5 | * License: GPL version 2 or higher http://www.gnu.org/licenses/gpl.html 6 | */ 7 | 8 | package hue 9 | 10 | import ( 11 | "github.com/collinux/gohue" 12 | 13 | "testing" 14 | "os" 15 | "fmt" 16 | ) 17 | 18 | func TestGetAllSensors(t *testing.T) { 19 | bridges, err := hue.FindBridges() 20 | if err != nil { 21 | t.Fatal(err) 22 | } 23 | bridge := bridges[0] 24 | if os.Getenv("HUE_USER_TOKEN") == "" { 25 | t.Fatal("The environment variable HUE_USER_TOKEN must be set to the value from bridge.CreateUser") 26 | } 27 | bridge.Login(os.Getenv("HUE_USER_TOKEN")) 28 | 29 | sensors, err := bridge.GetAllSensors() 30 | if err != nil { 31 | t.Fatal(err) 32 | } 33 | 34 | for _, sensor := range sensors { 35 | fmt.Println(sensor.Name) 36 | fmt.Println(sensor.Config.Battery) 37 | fmt.Println(sensor.State.LastUpdated) 38 | fmt.Println("------") 39 | } 40 | } 41 | 42 | --------------------------------------------------------------------------------