├── woohue ├── __init__.py ├── requirements.txt ├── config.py ├── nhlteams.json └── woohue.py ├── media ├── goal.gif ├── discovery.gif ├── lightselect.gif └── teamselect.gif ├── LICENSE.md ├── README.md ├── .gitattributes └── .gitignore /woohue/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /media/goal.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/librien/woohue/HEAD/media/goal.gif -------------------------------------------------------------------------------- /media/discovery.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/librien/woohue/HEAD/media/discovery.gif -------------------------------------------------------------------------------- /media/lightselect.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/librien/woohue/HEAD/media/lightselect.gif -------------------------------------------------------------------------------- /media/teamselect.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/librien/woohue/HEAD/media/teamselect.gif -------------------------------------------------------------------------------- /woohue/requirements.txt: -------------------------------------------------------------------------------- 1 | certifi==2018.11.29 2 | chardet==3.0.4 3 | idna==2.8 4 | phue==1.0 5 | pick==0.6.4 6 | pip==18.1 7 | requests==2.21.0 8 | rgbxy==0.5 9 | setuptools==39.0.1 10 | toml==0.10.0 11 | urllib3==1.24.2 12 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Kevin O'Brien 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Woohue 2 | 3 | **Woohue** is a Python program that retrieves information from the public NHL API, and triggers a goal light celebration on color capable Philips Hue lights. 4 | 5 | Note: The NHL live score API is updated every 60 seconds, thus if you're watching live on TV the celebration could be triggered up to 59 seconds after a goal is scored. I've had excellent results while watching through a streaming app such as NBC Sports. 6 | 7 | ## Installation 8 | I am currently working on setuptools installation. In the meantime: 9 | 10 | $ git clone https://github.com/librien/woohue 11 | $ cd woohue 12 | 13 | Recommended to create a virtualenv (>= Python 3.6) 14 | 15 | $ pip install -r requirements.txt 16 | 17 | Windows users will need to manually install curses to run configuration. 18 | 19 | $ pip install windows-curses 20 | 21 | ## Setup 22 | ### Start Bridge Discovery 23 | ![Automatically discover Hue bridge and color capable lights](media/discovery.gif) 24 | ### Select Lights 25 | ![Select your goal lights](media/lightselect.gif) 26 | ### Pick your team 27 | ![Select your favorite team](media/teamselect.gif) 28 | 29 | Upon successful writing of the config file, your goal lights should activate. 30 | 31 | ![Select your favorite team](media/goal.gif) 32 | 33 | ## Requirements 34 | - Add requirements 35 | 36 | ## Known Issues 37 | - Some terminals / console emulators may have issues with the curses library (I experienced errors with Cygwin / ConEmu). PuTTY has been tested and works for Raspberry Pi SSH connection. 38 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /woohue/config.py: -------------------------------------------------------------------------------- 1 | from phue import Bridge 2 | from pprint import pprint 3 | from pick import pick 4 | from itertools import cycle 5 | import json 6 | import time 7 | import toml 8 | import requests 9 | 10 | #config = configparser.ConfigParser() 11 | config = {} 12 | 13 | def main(): 14 | open_config() 15 | 16 | #def writeConfig(): 17 | 18 | def get_bridge_ip(): 19 | try: 20 | input('Please press the round button on your Hue Bridge and then press enter to continue.') 21 | print('Searching for Hue Bridge...') 22 | 23 | ''' 24 | Phue library not discovering correctly; retrieve IP address manually 25 | #bridge = Bridge(ip) 26 | #ip = bridge.get_ip_address() 27 | ''' 28 | ip = requests.get("https://www.meethue.com/api/nupnp").json()[0]['internalipaddress'] 29 | print('Hue Bridge discovered successfully.') 30 | print('Please ensure your lights are powered on and currently illuminated. Press enter to continue.') 31 | except Exception as e: 32 | print('Unable to find Hue Bridge') 33 | print(e) 34 | else: 35 | return ip 36 | 37 | def config_lights(ip): 38 | print('Retrieving lights...') 39 | bridge = Bridge(ip) 40 | lights = [] 41 | for light in bridge.lights: 42 | try: 43 | bridge.get_light(light.name, 'colormode') 44 | except KeyError: 45 | pass 46 | else: lights.append(light.name) 47 | 48 | try: 49 | title = 'Please use the spacebar to select at least one light from the choices below: ' 50 | selectedLights = pick(lights, title, multi_select=True, min_selection_count=1, indicator='->') 51 | except: 52 | print('Sorry, setup was unable to find any color capable hue lights connected to your bridge.') 53 | return 54 | else: 55 | goal_lights = {} 56 | goal_lights['Lights'] = [] 57 | for light in selectedLights: 58 | goal_lights['Lights'].append(light[0]) 59 | 60 | return goal_lights 61 | 62 | 63 | def set_teams(): 64 | 65 | with open('nhlteams.json') as data_file: 66 | data = json.loads(data_file.read()) 67 | title = 'Please use the spacebar to select a team from the choices below: ' 68 | options = data['teams'] 69 | def get_name(option): 70 | return option.get('team-name') 71 | team, teamIndex = pick(options, title, min_selection_count=1, indicator='->', options_map_func=get_name) 72 | teams = {} 73 | teams['Team'] = [] 74 | teams['Team'].append(team) 75 | return teams 76 | 77 | def open_config(): 78 | print("Loading configuration...") 79 | for i in range(0,100): 80 | while True: 81 | try: 82 | with open('config.toml') as configfile: 83 | config = toml.loads(configfile.read()) 84 | print('Configuration loaded succesfully...') 85 | return config 86 | 87 | except Exception as e: 88 | input("Please read the README file before beginning. Press enter to continue.") 89 | config = {} 90 | print('Unable to find existing configuration') 91 | print('Running woohue configuration setup...') 92 | ip = get_bridge_ip() 93 | config['Bridge'] = {'ip': ip} 94 | config['Goal_Lights'] = config_lights(ip) 95 | config['Teams'] = set_teams() 96 | try: 97 | with open('config.toml', 'w') as configfile: 98 | toml.dump(config, configfile) 99 | print('Configuration saved successfully') 100 | except Exception as e: 101 | print(e) 102 | 103 | if __name__ == "__main__": 104 | main() 105 | 106 | 107 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | project.fragment.lock.json 46 | artifacts/ 47 | 48 | *_i.c 49 | *_p.c 50 | *_i.h 51 | *.ilk 52 | *.meta 53 | *.obj 54 | *.pch 55 | *.pdb 56 | *.pgc 57 | *.pgd 58 | *.rsp 59 | *.sbr 60 | *.tlb 61 | *.tli 62 | *.tlh 63 | *.tmp 64 | *.tmp_proj 65 | *.log 66 | *.vspscc 67 | *.vssscc 68 | .builds 69 | *.pidb 70 | *.svclog 71 | *.scc 72 | 73 | # Chutzpah Test files 74 | _Chutzpah* 75 | 76 | # Visual C++ cache files 77 | ipch/ 78 | *.aps 79 | *.ncb 80 | *.opendb 81 | *.opensdf 82 | *.sdf 83 | *.cachefile 84 | *.VC.db 85 | *.VC.VC.opendb 86 | 87 | # Visual Studio profiler 88 | *.psess 89 | *.vsp 90 | *.vspx 91 | *.sap 92 | 93 | # TFS 2012 Local Workspace 94 | $tf/ 95 | 96 | # Guidance Automation Toolkit 97 | *.gpState 98 | 99 | # ReSharper is a .NET coding add-in 100 | _ReSharper*/ 101 | *.[Rr]e[Ss]harper 102 | *.DotSettings.user 103 | 104 | # JustCode is a .NET coding add-in 105 | .JustCode 106 | 107 | # TeamCity is a build add-in 108 | _TeamCity* 109 | 110 | # DotCover is a Code Coverage Tool 111 | *.dotCover 112 | 113 | # NCrunch 114 | _NCrunch_* 115 | .*crunch*.local.xml 116 | nCrunchTemp_* 117 | 118 | # MightyMoose 119 | *.mm.* 120 | AutoTest.Net/ 121 | 122 | # Web workbench (sass) 123 | .sass-cache/ 124 | 125 | # Installshield output folder 126 | [Ee]xpress/ 127 | 128 | # DocProject is a documentation generator add-in 129 | DocProject/buildhelp/ 130 | DocProject/Help/*.HxT 131 | DocProject/Help/*.HxC 132 | DocProject/Help/*.hhc 133 | DocProject/Help/*.hhk 134 | DocProject/Help/*.hhp 135 | DocProject/Help/Html2 136 | DocProject/Help/html 137 | 138 | # Click-Once directory 139 | publish/ 140 | 141 | # Publish Web Output 142 | *.[Pp]ublish.xml 143 | *.azurePubxml 144 | # TODO: Comment the next line if you want to checkin your web deploy settings 145 | # but database connection strings (with potential passwords) will be unencrypted 146 | #*.pubxml 147 | *.publishproj 148 | 149 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 150 | # checkin your Azure Web App publish settings, but sensitive information contained 151 | # in these scripts will be unencrypted 152 | PublishScripts/ 153 | 154 | # NuGet Packages 155 | *.nupkg 156 | # The packages folder can be ignored because of Package Restore 157 | **/packages/* 158 | # except build/, which is used as an MSBuild target. 159 | !**/packages/build/ 160 | # Uncomment if necessary however generally it will be regenerated when needed 161 | #!**/packages/repositories.config 162 | # NuGet v3's project.json files produces more ignoreable files 163 | *.nuget.props 164 | *.nuget.targets 165 | 166 | # Microsoft Azure Build Output 167 | csx/ 168 | *.build.csdef 169 | 170 | # Microsoft Azure Emulator 171 | ecf/ 172 | rcf/ 173 | 174 | # Windows Store app package directories and files 175 | AppPackages/ 176 | BundleArtifacts/ 177 | Package.StoreAssociation.xml 178 | _pkginfo.txt 179 | 180 | # Visual Studio cache files 181 | # files ending in .cache can be ignored 182 | *.[Cc]ache 183 | # but keep track of directories ending in .cache 184 | !*.[Cc]ache/ 185 | 186 | # Others 187 | ClientBin/ 188 | ~$* 189 | *~ 190 | *.dbmdl 191 | *.dbproj.schemaview 192 | *.jfm 193 | *.pfx 194 | *.publishsettings 195 | node_modules/ 196 | orleans.codegen.cs 197 | 198 | # Since there are multiple workflows, uncomment next line to ignore bower_components 199 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 200 | #bower_components/ 201 | 202 | # RIA/Silverlight projects 203 | Generated_Code/ 204 | 205 | # Backup & report files from converting an old project file 206 | # to a newer Visual Studio version. Backup files are not needed, 207 | # because we have git ;-) 208 | _UpgradeReport_Files/ 209 | Backup*/ 210 | UpgradeLog*.XML 211 | UpgradeLog*.htm 212 | 213 | # SQL Server files 214 | *.mdf 215 | *.ldf 216 | 217 | # Business Intelligence projects 218 | *.rdl.data 219 | *.bim.layout 220 | *.bim_*.settings 221 | 222 | # Microsoft Fakes 223 | FakesAssemblies/ 224 | 225 | # GhostDoc plugin setting file 226 | *.GhostDoc.xml 227 | 228 | # Node.js Tools for Visual Studio 229 | .ntvs_analysis.dat 230 | 231 | # Visual Studio 6 build log 232 | *.plg 233 | 234 | # Visual Studio 6 workspace options file 235 | *.opt 236 | 237 | # Visual Studio LightSwitch build output 238 | **/*.HTMLClient/GeneratedArtifacts 239 | **/*.DesktopClient/GeneratedArtifacts 240 | **/*.DesktopClient/ModelManifest.xml 241 | **/*.Server/GeneratedArtifacts 242 | **/*.Server/ModelManifest.xml 243 | _Pvt_Extensions 244 | 245 | # Paket dependency manager 246 | .paket/paket.exe 247 | paket-files/ 248 | 249 | # FAKE - F# Make 250 | .fake/ 251 | 252 | # JetBrains Rider 253 | .idea/ 254 | *.sln.iml 255 | 256 | # CodeRush 257 | .cr/ 258 | 259 | # Python Tools for Visual Studio (PTVS) 260 | __pycache__/ 261 | *.pyc 262 | /woohue/env 263 | /woohue/woohue.pyproj 264 | /woohue.sln 265 | *.sln 266 | *.pyproj 267 | /woohue/config.toml 268 | -------------------------------------------------------------------------------- /woohue/nhlteams.json: -------------------------------------------------------------------------------- 1 | { 2 | "teams": [ 3 | { 4 | "team-city": "Anaheim", 5 | "team-name": "Anaheim Ducks", 6 | "team-short": "ducks", 7 | "primary-color": "F47A38", 8 | "secondary-color": "B09862", 9 | "id": 24, 10 | "chant": "Let's Go Ducks!" 11 | }, 12 | { 13 | "team-name": "Arizona Coyotes", 14 | "team-short": "coyotes", 15 | "primary-color": "8C2633", 16 | "secondary-color": "E2D6B5", 17 | "id": 53, 18 | "chant": "Let's Go Coyotes!" 19 | }, 20 | { 21 | "team-name": "Boston Bruins", 22 | "team-short": "bruins", 23 | "primary-color": "FFB81C", 24 | "secondary-color": "000000", 25 | "id": 6, 26 | "chant": "Let's Go Bruins!" 27 | }, 28 | { 29 | "team-name": "Buffalo Sabres", 30 | "team-short": "sabres", 31 | "primary-color": "002654", 32 | "secondary-color": "FCB514", 33 | "id": 7, 34 | "chant": "Let's Go Sabres!" 35 | }, 36 | { 37 | "team-name": "Calgary Flames", 38 | "team-short": "flames", 39 | "primary-color": "C8102E", 40 | "secondary-color": "F1BE48", 41 | "id": 20, 42 | "chant": "Go Flames Go!" 43 | }, 44 | { 45 | "team-name": "Carolina Hurricanes", 46 | "team-short": "hurricanes", 47 | "primary-color": "CC0000", 48 | "secondary-color": "000000", 49 | "id": 12, 50 | "chant": "Let's Go Canes!" 51 | }, 52 | { 53 | "team-name": "Chicago Blackhawks", 54 | "team-short": "blackhawks", 55 | "primary-color": "CF0A2C", 56 | "secondary-color": "00833E", 57 | "id": 16, 58 | "chant": "Let's Go Hawks!" 59 | }, 60 | { 61 | "team-name": "Colorado Avalanche", 62 | "team-short": "avalanche", 63 | "primary-color": "6F263D", 64 | "secondary-color": "236192", 65 | "id": 21, 66 | "chant": "Let's Go Avs!" 67 | }, 68 | { 69 | "team-name": "Columbus Blue Jackets", 70 | "team-short": "bluejackets", 71 | "primary-color": "002654", 72 | "secondary-color": "CE1126", 73 | "id": 29, 74 | "chant": "C-B-J! C-B-J!" 75 | }, 76 | { 77 | "team-name": "Dallas Stars", 78 | "team-short": "stars", 79 | "primary-color": "006847", 80 | "secondary-color": "8F8F8C", 81 | "id": 25, 82 | "chant": "Let's Go Stars!" 83 | }, 84 | { 85 | "team-name": "Detroit Red Wings", 86 | "team-short": "redwings", 87 | "primary-color": "CE1126", 88 | "secondary-color": "FFFFFF", 89 | "id": 17, 90 | "chant": "Let's Go Red Wings!" 91 | }, 92 | { 93 | "team-name": "Edmonton Oilers", 94 | "team-short": "oilers", 95 | "primary-color": "041E42", 96 | "secondary-color": "FF4C00", 97 | "id": 22, 98 | "chant": "Let's Go Oilers!" 99 | }, 100 | { 101 | "team-name": "Florida Panthers", 102 | "team-short": "panthers", 103 | "primary-color": "041E42", 104 | "secondary-color": "C8102E", 105 | "id": 13, 106 | "chant": "Go Cats Go!" 107 | }, 108 | { 109 | "team-name": "Los Angeles Kings", 110 | "team-short": "kings", 111 | "primary-color": "111111", 112 | "secondary-color": "A2AAAD", 113 | "id": 26, 114 | "chant": "Let's Go Kings!" 115 | }, 116 | { 117 | "team-name": "Minnesota Wild", 118 | "team-short": "wild", 119 | "primary-color": "A6192E", 120 | "secondary-color": "154734", 121 | "id": 30, 122 | "chant": "Let's Go Wild!" 123 | }, 124 | { 125 | "team-name": "Montreal Canadiens", 126 | "team-short": "canadiens", 127 | "primary-color": "AF1E2D", 128 | "secondary-color": "192168", 129 | "id": 8, 130 | "chant": "Les Canadiens Sont La!" 131 | }, 132 | { 133 | "team-name": "Nashville Predators", 134 | "team-short": "predators", 135 | "primary-color": "FFB81C", 136 | "secondary-color": "041E42", 137 | "id": 18, 138 | "chant": "Let's Go Predators!" 139 | }, 140 | { 141 | "team-name": "New Jersey Devils", 142 | "team-short": "devils", 143 | "primary-color": "CE1126", 144 | "secondary-color": "000000", 145 | "id": 1, 146 | "chant": "Let's Go Devils!" 147 | }, 148 | { 149 | "team-name": "New York Islanders", 150 | "team-short": "islanders", 151 | "primary-color": "00539B", 152 | "secondary-color": "F47D30", 153 | "id": 2, 154 | "chant": "Let's Go Islanders!" 155 | }, 156 | { 157 | "team-name": "New York Rangers", 158 | "team-short": "rangers", 159 | "primary-color": "0038A8", 160 | "secondary-color": "CE1126", 161 | "id": 3, 162 | "chant": "Let's Go Rangers!" 163 | }, 164 | { 165 | "team-name": "Ottawa Senators", 166 | "team-short": "senators", 167 | "primary-color": "E31837", 168 | "secondary-color": "C69214", 169 | "id": 9, 170 | "chant": "Go Sens Go!" 171 | }, 172 | { 173 | "team-name": "Philadelphia Flyers", 174 | "team-short": "flyers", 175 | "primary-color": "F74902", 176 | "secondary-color": "000000", 177 | "id": 3, 178 | "chant": "Let's Go Flyers!" 179 | }, 180 | { 181 | "team-name": "Pittsburgh Penguins", 182 | "team-short": "penguins", 183 | "primary-color": "000000", 184 | "secondary-color": "CFC493", 185 | "id": 5, 186 | "chant": "Let's Go Pens!" 187 | }, 188 | { 189 | "team-name": "San Jose Sharks", 190 | "team-short": "sharks", 191 | "primary-color": "006D75", 192 | "secondary-color": "EA7200", 193 | "id": 28, 194 | "chant": "Let's Go Sharks!" 195 | }, 196 | { 197 | "team-name": "St. Louis Blues", 198 | "team-short": "blues", 199 | "primary-color": "002F87", 200 | "secondary-color": "FCB514", 201 | "id": 19, 202 | "chant": "Let's Go Blues!" 203 | }, 204 | { 205 | "team-name": "Tampa Bay Lightning", 206 | "team-short": "lightning", 207 | "primary-color": "002868", 208 | "secondary-color": "FFFFFF", 209 | "id": 14, 210 | "chant": "Let's Go Lightning!" 211 | }, 212 | { 213 | "team-name": "Toronto Maple Leafs", 214 | "team-short": "mapleleafs", 215 | "primary-color": "003E7E", 216 | "secondary-color": "FFFFFF", 217 | "id": 10, 218 | "chant": "Go Leafs Go!" 219 | }, 220 | { 221 | "team-name": "Vancouver Canucks", 222 | "team-short": "canucks", 223 | "primary-color": "001F5B", 224 | "secondary-color": "00843D", 225 | "id": 23, 226 | "chant": "Go Canucks Go!" 227 | }, 228 | { 229 | "team-name": "Vegas Golden Knights", 230 | "team-short": "goldenknights", 231 | "primary-color": "B4975A", 232 | "secondary-color": "333F42", 233 | "id": 54, 234 | "chant": "Let's Go Knights!" 235 | }, 236 | { 237 | "team-name": "Washington Capitals", 238 | "team-short": "capitals", 239 | "primary-color": "C8102E", 240 | "secondary-color": "041E42", 241 | "id": 15, 242 | "chant": "C-A-P-S CAPS! CAPS! CAPS!" 243 | }, 244 | { 245 | "team-name": "Winnipeg Jets", 246 | "team-short": "jets", 247 | "primary-color": "041E42", 248 | "secondary-color": "AC162C", 249 | "id": 52, 250 | "chant": "Go Jets Go!" 251 | } 252 | ] 253 | } -------------------------------------------------------------------------------- /woohue/woohue.py: -------------------------------------------------------------------------------- 1 | from pprint import pprint 2 | from urllib import request 3 | from optparse import OptionParser 4 | from itertools import cycle 5 | from rgbxy import Converter 6 | from recursivejson import extract_values 7 | import datetime 8 | import json 9 | import os 10 | import re 11 | import platform 12 | import sys 13 | import time 14 | import requests 15 | import socket 16 | import curses 17 | import config 18 | 19 | def run_once(f): 20 | def wrapper(*args, **kwargs): 21 | if not wrapper.has_run: 22 | wrapper.has_run = True 23 | return f(*args, **kwargs) 24 | wrapper.has_run = False 25 | return wrapper 26 | 27 | def clear_screen(): 28 | if platform.system() == 'Windows': 29 | os.system('cls') 30 | else: 31 | os.system('clear') 32 | 33 | def activate_goal_light(woohue_config, team): 34 | team_colors = [] 35 | team_colors.append(team['primary-color']) # Team primary color 36 | team_colors.append(team['secondary-color']) # Team secondary color 37 | existingState = [] 38 | for light in woohue_config.goal_lights: 39 | existingState.append(config.Bridge(woohue_config.ip).get_light(light, 'xy')) 40 | 41 | converter = Converter() 42 | for i, item in enumerate(team_colors): 43 | xy_color = converter.hex_to_xy(item) 44 | team_colors[i] = xy_color 45 | c = cycle(team_colors) 46 | for i in range(9): 47 | team_color = next(c) 48 | try: 49 | config.Bridge(woohue_config.ip).set_light(woohue_config.goal_lights, 'xy', team_color, transitiontime=0) 50 | except Exception as e: 51 | print(e) 52 | time.sleep(.5) 53 | 54 | #Restore previous color values 55 | for i in range(2): 56 | config.Bridge(woohue_config.ip).set_light(woohue_config.goal_lights[i], 'xy', existingState[i]) 57 | 58 | class configuration: 59 | def __init__(self, config_obj): 60 | self.config = config_obj 61 | self.ip = self.config['Bridge']['ip'] 62 | self.goal_lights = self.config['Goal_Lights']['Lights'] 63 | self.teams = self.config['Teams']['Team'] 64 | 65 | try: 66 | woohue_config = configuration(config.open_config()) 67 | except Exception as e: 68 | print(e) 69 | else: 70 | activate_goal_light(woohue_config, woohue_config.teams[0]) 71 | 72 | class game: 73 | def __init__(self, team): 74 | self.team = team 75 | self.initial_score = 0 76 | self.get_game() 77 | self.watch_game() 78 | 79 | @run_once 80 | def start_game(self): 81 | clear_screen() 82 | print("Game time!") 83 | self.chant() 84 | print(self.score_line) 85 | self.puck_in_play = False 86 | self.end_of_period = False 87 | self.beginning_of_period = False 88 | 89 | def chant(self): 90 | print("Let's go {}!".format(self.team['team-name'])) 91 | print(self.team['chant']) 92 | 93 | def get_ice_time(self, time_remaining, current_period): 94 | if time_remaining == "END": 95 | self.puck_in_play = False 96 | self.ice_time = time_remaining + " of " + current_period 97 | if self.end_of_period == False: 98 | print(self.ice_time) 99 | self.end_of_period = True 100 | 101 | elif time_remaining == "20:00" or (time_remaining == "05:00" and current_period == "OT"): 102 | self.end_of_period = False 103 | self.ice_time = "Start of " + current_period 104 | if self.beginning_of_period == False: 105 | print(self.ice_time) 106 | self.beginning_of_period = True 107 | 108 | elif time_remaining == "Final": 109 | self.puck_in_play = False 110 | self.ice_time = "Final" 111 | 112 | else: 113 | self.ice_time = time_remaining + " in " + current_period 114 | if self.puck_in_play == False: 115 | print(self.ice_time) 116 | self.puck_in_play = True 117 | self.end_of_period = False 118 | self.beginning_of_period = False 119 | 120 | def get_game(self): 121 | try: 122 | api_url = ("https://statsapi.web.nhl.com/api/v1/schedule?teamId="+str(self.team['id'])+"&expand=schedule.linescore") 123 | self.game = requests.get(api_url).json()['dates'][0]['games'][0] 124 | self.status = int(self.game['status']['statusCode']) 125 | self.game_id = self.game['gamePk'] 126 | self.game_time = datetime.datetime.strptime(self.game['gameDate'], "%Y-%m-%dT%H:%M:%SZ") 127 | self.puck_in_play = None 128 | self.score_line = self.game['teams']['away']['team']['name'] + " " + str(self.game['linescore']['teams']['away']['goals']) + " " + self.game['teams']['home']['team']['name'] + " " + str(self.game['linescore']['teams']['home']['goals']) 129 | def home_or_away(self): 130 | if self.team['id'] == self.game['teams']['home']['team']['id']: 131 | return "home" 132 | elif self.team['id'] == self.game['teams']['away']['team']['id']: 133 | return "away" 134 | self.home_or_away = home_or_away(self) 135 | self.score = self.game['linescore']['teams'][self.home_or_away]['goals'] 136 | self.initial_score = self.game['linescore']['teams'][home_or_away(self)]['goals'] 137 | except IndexError: 138 | next_game_url = ("https://statsapi.web.nhl.com/api/v1/teams/" + str(self.team['id']) + "?expand=team.schedule.next") 139 | game_sched = requests.get(next_game_url) 140 | next_game_date = extract_values(game_sched.json(), 'date') 141 | print("\rNo games scheduled for today. The next game hits the ice on "+ next_game_date) 142 | ''' 143 | Todo: Figure out a way to sleep / rerun script next day when a game is scheduled. 144 | Implement offseason handling? neat, no 145 | ''' 146 | input('Try running the program on gameday. Press enter to exit.') 147 | sys.exit() 148 | except Exception as e: 149 | print(e) 150 | #sys.exit() 151 | 152 | def update_game(self): 153 | try: 154 | api_url = ("https://statsapi.web.nhl.com/api/v1/schedule?teamId="+str(self.team['id'])+"&expand=schedule.linescore") 155 | self.game = requests.get(api_url).json()['dates'][0]['games'][0] 156 | self.status = int(self.game['status']['statusCode']) 157 | self.game_time = datetime.datetime.strptime(self.game['gameDate'], "%Y-%m-%dT%H:%M:%SZ") 158 | self.score = self.game['linescore']['teams'][self.home_or_away]['goals'] 159 | self.score_line = self.game['teams']['away']['team']['name'] + " " + str(self.game['linescore']['teams']['away']['goals']) + " " + self.game['teams']['home']['team']['name'] + " " + str(self.game['linescore']['teams']['home']['goals']) 160 | 161 | except Exception as e: 162 | print(e) 163 | 164 | def watch_game(self): 165 | current_time = datetime.datetime.utcnow() 166 | #activate_goal_light(woohue_config, self.team) #test goal light on program start 167 | while True: 168 | self.update_game() 169 | ''' 170 | NHL API Game status.statusCode legend: 171 | 1 - 172 | 2 - Pregame 173 | 3 - In Progress 174 | 4 - In Progress - Critical (OT) 175 | 5 - SO? 176 | 6 - Final 177 | ''' 178 | if (self.status < 2): 179 | self.ice_time = "Gameday" 180 | pause_time = self.game_time - current_time 181 | pause_time = pause_time.seconds 182 | for i in range(pause_time): 183 | sys.stdout.write("\rGame starting in approximately {0}.".format(str(datetime.timedelta(seconds=pause_time)))) 184 | sys.stdout.flush() 185 | pause_time -= 1 186 | time.sleep(1) 187 | elif (self.status == 2): 188 | self.ice_time = "Pregame" 189 | print("Pregame! Waiting for puckdrop...") 190 | time.sleep(30) 191 | elif (self.status > 2 and self.status < 6): 192 | ''' 193 | Todo: Implement way to show ice_time changes only once (end of periods, beginning of periods) 194 | ''' 195 | 196 | if (self.game['linescore']['currentPeriodTimeRemaining'] and self.game['linescore']['currentPeriodOrdinal']): 197 | self.start_game() 198 | self.get_ice_time(self.game['linescore']['currentPeriodTimeRemaining'], self.game['linescore']['currentPeriodOrdinal']) 199 | 200 | def watch_score(): 201 | if (self.score > self.initial_score): 202 | print('GOAL!') 203 | print(self.score_line) 204 | ''' 205 | Time of goal is not always accurate. 206 | It just returns the current time when API is updated. 207 | ''' 208 | print('Time of Goal: ' + self.ice_time) 209 | try: 210 | activate_goal_light(woohue_config, self.team) 211 | except Exception as e: 212 | print(e) 213 | self.initial_score = self.score 214 | self.chant() 215 | 216 | watch_score() 217 | time.sleep(10) 218 | 219 | elif (self.status >= 6): 220 | self.ice_time = "Final" 221 | print("Game is over") 222 | #time.sleep(30) 223 | print(self.score_line) 224 | break 225 | 226 | def main(): 227 | 228 | clear_screen() 229 | 230 | for team in woohue_config.teams: 231 | new_game = game(team) 232 | print("------------") 233 | 234 | def get_teams(): 235 | ''' 236 | Retrieves JSON object of NHL teams 237 | ''' 238 | api_url = ("https://statsapi.web.nhl.com/api/v1/teams") 239 | teams = requests.get(api_url).json() 240 | return teams 241 | 242 | if __name__ == "__main__": 243 | main() 244 | 245 | 246 | #pprint(get_teams()) 247 | --------------------------------------------------------------------------------