├── README.md ├── community-documentation └── raspberry_pi_os_lite_install.md ├── community-scripts ├── Lumicube Interface │ ├── LumicubeInterface.py │ └── example │ │ └── example.py ├── Resistor clock.py ├── cube_runner.py ├── cylon.py ├── digital_clock_v1.py ├── digital_rain.py ├── display_helpers.py ├── game_of_life_clock.py ├── game_of_life_phased.py ├── gameday.py ├── glitter.py ├── google_api.py ├── identify_panels v2.py ├── identify_panels.py ├── iss-tracker-lumi.py ├── minecraft_pic.py ├── minecraft_random_pic.py ├── mlb.py ├── nest_thermostat.py ├── pacman.py ├── vesuvius.py ├── weather.py └── yagol.py ├── examples ├── autumn_scene.py ├── binary_clock.py ├── button.py ├── chiptune.py ├── conways_game_of_life.py ├── equaliser.py ├── land_grab.py ├── lava_lamp.py ├── pi_status_screen.py ├── rain.py ├── rainbow.py ├── ripples.py ├── scrolling_clock.py ├── tapping_ripples.py ├── voice_recognition.py ├── water_level.py └── windmill.py └── official-documentation └── api.txt /README.md: -------------------------------------------------------------------------------- 1 |
9 | 10 | # LumiCube 11 | 12 | This repository contains some example code and documentation for the LumiCube: 13 | * https://www.kickstarter.com/projects/1202256831/lumicube-an-led-cube-kit-for-the-raspberry-pi 14 | * https://www.indiegogo.com/projects/lumicube-an-led-cube-kit-for-the-raspberry-pi 15 | 16 | More detailed information can be found in our project booklet: 17 | * https://abstractfoundry.com/lumicube/manual.pdf 18 | 19 | For getting started head to our resources page: 20 | * https://abstractfoundry.com/lumicube/resources 21 | 22 | (Note: the only supported operating system at the moment is the full 32bit Raspberry Pi OS.) 23 | 24 | The source code for the software running on the Raspberry Pi, including our web-based IDE, is available in a separate project: 25 | * https://github.com/abstractfoundry/lumicube-daemon 26 | 27 | ## Community Contributions 28 | 29 | Documentation: 30 | * https://github.com/abstractfoundry/lumicube/tree/main/community-documentation 31 | 32 | Scripts: 33 | * https://github.com/abstractfoundry/lumicube/tree/main/community-scripts 34 | 35 | ## Simulator 36 | 37 | It is also possible to run _some_ of these examples projects using our online simulator: 38 | * https://abstractfoundry.com/lumicube/simulator 39 | 40 | ## Example projects 41 | 42 | The “examples” folder contains a number of example projects, some of which we used in our Kickstarter and Indiegogo video: 43 | 44 | ### Level 1 (easiest) 45 | 46 | #### button.py 47 | Use the button to switch the cube’s colour between red and blue. 48 | 49 | #### chiptune.py 50 | Play a randomly generated tune. 51 | 52 | #### pi_status_screen.py 53 | Display some Raspberry Pi stats on the screen: CPU temperature, disk usage, etc. 54 | 55 | #### rainbow.py 56 | Continually change the cube's colour. 57 | 58 | #### scrolling_clock.py 59 | Every 10 seconds scroll the time across the LEDs. 60 | 61 | #### voice_recognition.py 62 | Basic voice recognition and text-to-speech. 63 | 64 | ### Level 2 (after you’ve learnt the basics) 65 | 66 | #### autumn_scene.py 67 | Autumn animation (leaves falling from a pixel-art tree). 68 | 69 | #### binary_clock.py 70 | A simple binary clock (see https://en.wikipedia.org/wiki/Binary_clock). 71 | 72 | #### conways_game_of_life.py 73 | Run Conway’s Game of Life on the LEDs (see https://en.wikipedia.org/wiki/Conway's_Game_of_Life). 74 | 75 | #### equaliser.py 76 | Colourful equaliser using the microphone. 77 | 78 | #### land_grab.py 79 | Several computer players randomly walk the LED grid, colouring squares until they get stuck. 80 | 81 | #### lava_lamp.py 82 | Use OpenSimplex noise to generate a lava lamp like effect (see https://en.wikipedia.org/wiki/Simplex_noise). 83 | 84 | #### rain.py 85 | Rain animation. 86 | 87 | #### ripples.py 88 | Random circular ripple animation. 89 | 90 | #### tapping_ripples.py 91 | Ripple animation which responds to tapping or moving the cube. 92 | 93 | #### water_level.py 94 | Virtual water-level effect which responds to the cube's orientation. 95 | 96 | #### windmill.py 97 | Blow at the back of the cube to make the windmill animation turn. 98 | 99 | ## Documentation 100 | 101 | ### Python API 102 | 103 | See the LumiCube manual for a detailed explanation of the modules, methods and fields: 104 | * https://abstractfoundry.com/lumicube/manual.pdf 105 | 106 | A brief summary of the API is also given here: 107 | * https://github.com/abstractfoundry/lumicube/blob/main/documentation/api.txt 108 | 109 | ### REST API 110 | 111 | The API is broken down into: 112 | - modules (e.g. speaker, microphone, screen) 113 | - methods (e.g. play_tone(), draw_rectangle()) 114 | - fields (e.g. volume, brightness) 115 | 116 | All of the modules, methods, and fields in the Python API (see the product manual, linked above) also exist in the REST API. 117 | 118 | #### Getting or setting a field 119 | 120 | ``` 121 | [GET/POST] http://
31 | # if you open the final file in the editor the code may see it and try to read it immediately before you've saved it.
32 | # the code will be used to fetch a refresh token and access token, which it will used to access the device api. Even if you restart it will use the saved token.
33 | # I've found that the token is good for about a week, after which you'll see the lock and question mark display - look at the console for instructions.
34 | #
35 | # Useful links:
36 | # - controlling a nest thermostat with python : https://www.wouternieuwerth.nl/controlling-a-google-nest-thermostat-with-python/
37 | # - Device Access Guides : https://developers.google.com/nest/device-access/registration
38 | # - Thermostat API - https://developers.google.com/nest/device-access/api/thermostat
39 | #
40 | # Author: Kevin Haney
41 | # Date: 12/9/2022
42 | # v1.0
43 | # history:
44 | # - 1.0 - original (v1)
45 |
46 | import os
47 | import time
48 | import requests
49 | import json
50 | from datetime import datetime, timedelta
51 |
52 | class Thermostat:
53 | def __init__(self, t):
54 | self.device_id = ''
55 | self.name = ''
56 | self.humidity = 0
57 | self.connectivity = ''
58 | self.mode = ''
59 | self.eco_mode = False
60 | self.fan_on = False
61 | self.setpoint_eco_heat = 0
62 | self.setpoint_eco_cool = 0
63 | self.status = ''
64 | self.scale = 'F'
65 | self.setpoint_heat = 0
66 | self.setpoint_cool = 0
67 | self.ambient_temperature = 0
68 |
69 | self.device_id = t['name']
70 | if 'traits' in t:
71 | traits = t['traits']
72 | self.name = traits['sdm.devices.traits.Info']['customName']
73 | scale = traits['sdm.devices.traits.Settings']['temperatureScale']
74 | if scale == 'FAHRENHEIT':
75 | self.scale = 'F'
76 | else:
77 | self.scale = 'C'
78 | self.humidity = traits['sdm.devices.traits.Humidity']['ambientHumidityPercent']
79 | self.connectivity = traits['sdm.devices.traits.Connectivity']['status']
80 | self.mode = traits['sdm.devices.traits.ThermostatMode']['mode']
81 | self.fan_on = traits['sdm.devices.traits.Fan']['timerMode'] != 'OFF'
82 | self.eco_mode = traits['sdm.devices.traits.ThermostatEco']['mode'] == 'MANUAL_ECO'
83 | self.setpoint_eco_heat = traits['sdm.devices.traits.ThermostatEco']['heatCelsius']
84 | if self.scale == 'F':
85 | self.setpoint_eco_heat = Thermostat.cToF(self.setpoint_eco_heat)
86 | self.setpoint_eco_cool = traits['sdm.devices.traits.ThermostatEco']['coolCelsius']
87 | if self.scale == 'F':
88 | self.setpoint_eco_cool = Thermostat.cToF(self.setpoint_eco_cool)
89 | self.status = traits['sdm.devices.traits.ThermostatHvac']['status']
90 | setpoint = traits['sdm.devices.traits.ThermostatTemperatureSetpoint']
91 | if 'heatCelsius' in setpoint:
92 | self.setpoint_heat = setpoint['heatCelsius']
93 | if self.scale == 'F':
94 | self.setpoint_heat = Thermostat.cToF(self.setpoint_heat)
95 | if 'coolCelsius' in setpoint:
96 | self.setpoint_cool = setpoint['coolCelsius']
97 | if self.scale == 'F':
98 | self.setpoint_cool = Thermostat.cToF(self.setpoint_cool)
99 | self.ambient_temperature = traits['sdm.devices.traits.Temperature']['ambientTemperatureCelsius']
100 | if self.scale == 'F':
101 | self.ambient_temperature = Thermostat.cToF(self.ambient_temperature)
102 |
103 |
104 | def __str__(self):
105 | fan = 'off'
106 | if self.fan_on:
107 | fan = 'on'
108 | info = f'{self.name} -- mode: {self.mode}, status: {self.status}, fan: {fan}, '
109 | if self.eco_mode:
110 | info += 'eco set: ' + f'{self.setpoint_eco_heat:3.1f} {self.scale} to {self.setpoint_eco_cool:3.1f} {self.scale}, '
111 | else:
112 | if self.mode == 'HEAT':
113 | info += 'set: ' + f'{self.setpoint_heat:3.1f} {self.scale}, '
114 | elif self.mode == 'COOL':
115 | info += 'set: ' + f'{self.setpoint_cool:3.1f} {self.scale}, '
116 | elif self.mode == 'HEATCOOL':
117 | info += 'set: ' + f'{self.setpoint_heat:3.1f} {self.scale} to {self.setpoint_cool:3.1f} {self.scale}, '
118 | info += 'current ' + f'{self.ambient_temperature:3.1f} {self.scale}, '
119 | info += f'humidity {self.humidity:3.1f} %'
120 | return info
121 |
122 | @staticmethod
123 | def cToF(celsius):
124 | return ((9/5)*celsius) + 32
125 |
126 | @staticmethod
127 | def fToC(farenheit):
128 | return (farenheit-32)*5/9
129 |
130 | class GoogleApi:
131 | ACCESS_TOKEN_LIFETIME_MINUTES = 30
132 |
133 | def __init__(self, working_directory):
134 | self.working_directory = working_directory
135 | self._config_file_name = self.working_directory+'/google/.nest-config'
136 | try:
137 | with open(self._config_file_name,'r') as config_file:
138 | config_json = json.load(config_file)
139 | print('found config')
140 | self.project_id = config_json['project_id']
141 | self.client_id = config_json['client_id']
142 | self.client_secret = config_json['client_secret']
143 |
144 | except FileNotFoundError:
145 | print('did not find config file at',self._config_file_name)
146 | self.refresh_token = ''
147 | return
148 |
149 | self.access_token = ''
150 | self.access_token_expires = datetime.now()
151 | self.working_directory = working_directory
152 | self._token_file_name = self.working_directory+'/google/.refreshtoken'
153 |
154 | self.refresh_token = ''
155 |
156 | try:
157 | tokenFile = open(self._token_file_name,'r')
158 | self.refresh_token = tokenFile.read()
159 | tokenFile.close()
160 | print('found token')
161 | #print(self.refresh_token)
162 | except FileNotFoundError:
163 | print('no token file found in',self._token_file_name)
164 | self.refresh_token = ''
165 |
166 | if self.refresh_token != '':
167 | self.access_token = self.fetch_access_token()
168 |
169 | def is_permissioned(self):
170 | return self.refresh_token != ''
171 |
172 | def get_permission_url(self):
173 | redirect_uri = 'https://www.google.com'
174 | return 'https://nestservices.google.com/partnerconnections/'+self.project_id+'/auth?redirect_uri='+redirect_uri+'&access_type=offline&prompt=consent&client_id='+self.client_id+'&response_type=code&scope=https://www.googleapis.com/auth/sdm.service'
175 |
176 | def get_permission(self):
177 | redirect_uri = 'https://www.google.com'
178 | url = 'https://nestservices.google.com/partnerconnections/'+self.project_id+'/auth?redirect_uri='+redirect_uri+'&access_type=offline&prompt=consent&client_id='+self.client_id+'&response_type=code&scope=https://www.googleapis.com/auth/sdm.service'
179 |
180 | print("Go to this URL to log in:", flush=True)
181 | print(url)
182 |
183 | code = ''
184 | code_file_name = self.working_directory + '/google/.code'
185 | if os.path.exists(code_file_name):
186 | os.remove(code_file_name)
187 |
188 | while code == '':
189 | print(f'waiting for code in {code_file_name}',flush=True)
190 | try:
191 | code_file = open(code_file_name,'r')
192 | code = code_file.read()
193 | except FileNotFoundError:
194 | time.sleep(10)
195 |
196 | print(f'got code{code}',flush=True)
197 | self.fetch_refresh_token(code)
198 | return self.is_permissioned()
199 |
200 | def fetch_refresh_token(self,code):
201 | redirect_uri = 'https://www.google.com'
202 | print('fetch refresh token')
203 |
204 | params = (
205 | ('client_id', self.client_id),
206 | ('client_secret', self.client_secret),
207 | ('code', code),
208 | ('grant_type', 'authorization_code'),
209 | # get a new access token
210 | ('redirect_uri', redirect_uri),
211 | )
212 | response = requests.post('https://www.googleapis.com/oauth2/v4/token', params=params)
213 | response_json = response.json()
214 |
215 | self.access_token = response_json['token_type'] + ' ' + str(response_json['access_token'])
216 | self.access_token_expires = datetime.now() + timedelta(minutes=self.ACCESS_TOKEN_LIFETIME_MINUTES)
217 | self.refresh_token = response_json['refresh_token']
218 | print('created token')
219 | #save it
220 | refresh_file = open(self._token_file_name,'w')
221 | refresh_file.write(self.refresh_token)
222 | refresh_file.close()
223 | print('saved token to',self._token_file_name)
224 | return self.refresh_token
225 |
226 | def fetch_access_token(self):
227 | # get a new access token
228 | params = (
229 | ('client_id', self.client_id),
230 | ('client_secret', self.client_secret),
231 | ('refresh_token', self.refresh_token),
232 | ('grant_type', 'refresh_token'),
233 | )
234 |
235 | response = requests.post('https://www.googleapis.com/oauth2/v4/token', params=params)
236 | if response.status_code != 200:
237 | print(response,response.status_code,response.reason)
238 | if response.status_code == 400:
239 | self.refresh_token = ''
240 | self.access_token = ''
241 | return ''
242 |
243 | response_json = response.json()
244 | # print(response_json)
245 | self.access_token = response_json['token_type'] + ' ' + response_json['access_token']
246 | self.access_token_expires = datetime.now() + timedelta(minutes=self.ACCESS_TOKEN_LIFETIME_MINUTES)
247 | #print('new Access token: ' + self.access_token,'expires',self.access_token_expires)
248 | return self.access_token
249 |
250 | def check_access_token(self):
251 | if self.access_token == '' or datetime.now() > self.access_token_expires:
252 | print('refreshing access token')
253 | self.fetch_access_token()
254 |
255 | return self.access_token != ''
256 |
257 | def fetch_thermostats(self):
258 | if self.check_access_token() == False:
259 | print('could not refresh access token')
260 | return []
261 |
262 | # Get devices
263 | url_get_devices = 'https://smartdevicemanagement.googleapis.com/v1/enterprises/' + self.project_id + '/devices'
264 | headers = {
265 | 'Content-Type': 'application/json',
266 | 'Authorization': self.access_token,
267 | }
268 |
269 | response = requests.get(url_get_devices, headers=headers)
270 | if response.status_code != 200:
271 | print(response,response.status_code,response.reason)
272 | response_json = response.json()
273 |
274 | #print(response_json)
275 | thermostats = []
276 | devices = response_json['devices']
277 | #print('found',len(devices),'devices')
278 | for d in devices:
279 | if 'type' in d and d['type'] == 'sdm.devices.types.THERMOSTAT':
280 | traits = d['traits']
281 | #print(traits)
282 | # print('device :',traits['sdm.devices.traits.Info']['customName'])
283 | # for t in traits:
284 | # for x in traits[t]:
285 | # print(t,x,traits[t][x])
286 | thermostats.append(Thermostat(d))
287 | return thermostats
288 |
289 | def fetch_thermostat(self, t):
290 | if self.check_access_token() == False:
291 | print('could not refresh access token')
292 | return None
293 |
294 | # Get devices
295 | url_get_device = 'https://smartdevicemanagement.googleapis.com/v1/' + t.device_id
296 | headers = {
297 | 'Content-Type': 'application/json',
298 | 'Authorization': self.access_token,
299 | }
300 |
301 | success = False
302 | while not success:
303 | response = requests.get(url_get_device, headers=headers)
304 | if response.status_code != 200:
305 | print(response,response.status_code,response.reason)
306 | time.sleep(30)
307 | else:
308 | success = True
309 | response_json = response.json()
310 | #print(response,response_json)
311 |
312 | d = response_json
313 | #print(d)
314 | if 'type' in d and d['type'] == 'sdm.devices.types.THERMOSTAT':
315 | traits = d['traits']
316 | #print(traits)
317 | # print('device :',traits['sdm.devices.traits.Info']['customName'])
318 | # for t in traits:
319 | # for x in traits[t]:
320 | # print(t,x,traits[t][x])
321 | return Thermostat(d)
322 |
323 | def change_setpoint(self, change, thermostat, heat):
324 | if self.check_access_token() == False:
325 | print('could not refresh access token')
326 | return False
327 |
328 | current_setpoint = None
329 | params = None
330 | if thermostat.eco_mode:
331 | print('eco mode not supported yet for change_setpoint()',flush=True)
332 | return
333 | elif thermostat.mode == 'HEATCOOL':
334 | if thermostat.eco_mode:
335 | setpoint_heat = thermostat.setpoint_eco_heat
336 | setpoint_cool = thermostat.setpoint_eco_cool
337 | else:
338 | setpoint_heat = thermostat.setpoint_heat
339 | setpoint_cool = thermostat.setpoint_cool
340 |
341 | if heat:
342 | new_setpoint_heat = change + setpoint_heat
343 | new_setpoint_cool = setpoint_cool
344 | else:
345 | new_setpoint_heat = setpoint_heat
346 | new_setpoint_cool = change + setpoint_cool
347 |
348 | print(f'changing range from {setpoint_heat:3.1f} {thermostat.scale} - {setpoint_cool:3.1f} {thermostat.scale} to {new_setpoint_heat:3.1f} {thermostat.scale} - {new_setpoint_cool:3.1f} {thermostat.scale}')
349 | if thermostat.scale == 'F':
350 | new_setpoint_heat = Thermostat.fToC(new_setpoint_heat)
351 | new_setpoint_cool = Thermostat.fToC(new_setpoint_cool)
352 | command = 'SetRange'
353 | params = { 'heatCelsius': new_setpoint_heat , 'coolCelsius' : new_setpoint_cool }
354 | elif thermostat.mode == 'HEAT':
355 | command = 'SetHeat'
356 | point = 'heatCelsius'
357 | current_setpoint = thermostat.setpoint_heat
358 | elif thermostat.mode == 'COOL':
359 | command = 'SetCool'
360 | point = 'coolCelsius'
361 | current_setpoint = thermostat.setpoint_cool
362 | elif thermostat.mode == 'OFF':
363 | return
364 | else:
365 | print(thermostat.mode,'not supported yet for change_setpoint()',flush=True)
366 | return
367 |
368 | if params == None:
369 | new_setpoint = change + current_setpoint
370 | print(f'changing from {current_setpoint:3.1f} {thermostat.scale} to {new_setpoint:3.1f} {thermostat.scale}',flush=True)
371 | if thermostat.scale == 'F':
372 | new_setpoint = Thermostat.fToC(new_setpoint)
373 | params = { point : new_setpoint }
374 |
375 | url_exec_command = 'https://smartdevicemanagement.googleapis.com/v1/' + thermostat.device_id + ':executeCommand'
376 | headers = {
377 | 'Content-Type': 'application/json',
378 | 'Authorization': self.access_token,
379 | }
380 |
381 | data = {'command': 'sdm.devices.commands.ThermostatTemperatureSetpoint.' + command,
382 | 'params': params }
383 | #print(data)
384 | response = requests.post(url_exec_command, headers=headers, data=json.dumps(data))
385 | if response.status_code != 200:
386 | print(response,response.status_code,response.reason,flush=True)
387 | return False
388 | return True
389 |
390 | def change_mode(self, new_mode, thermostat):
391 | if new_mode == 'ECO':
392 | command = 'ThermostatEco.SetMode'
393 | params = { 'mode' : 'MANUAL_ECO' }
394 | elif new_mode == 'FAN':
395 | command = 'Fan.SetTimer'
396 | # fan is a toggle - if it was on, turn it off, it if was off, turn it on
397 | if thermostat.fan_on:
398 | params = { 'timerMode' : 'OFF' }
399 | else:
400 | params = { 'timerMode' : 'ON' }
401 | else:
402 | command = 'ThermostatMode.SetMode'
403 | params = { 'mode' : new_mode }
404 |
405 | url_exec_command = 'https://smartdevicemanagement.googleapis.com/v1/' + thermostat.device_id + ':executeCommand'
406 | headers = {
407 | 'Content-Type': 'application/json',
408 | 'Authorization': self.access_token,
409 | }
410 |
411 | data = {'command': 'sdm.devices.commands.' + command,
412 | 'params': params }
413 | #print(data)
414 | response = requests.post(url_exec_command, headers=headers, data=json.dumps(data))
415 | if response.status_code != 200:
416 | print(response,response.status_code,response.reason,flush=True)
417 | return False
418 | return True
419 |
420 |
421 | if __name__ == "__main__":
422 | workingDirectory = os.getcwd()
423 | print(workingDirectory)
424 |
425 | api = GoogleApi(workingDirectory)
426 | if api.is_permissioned() == False:
427 | if api.get_permission() == False:
428 | print('could not get permision')
429 |
430 | thermostats = api.fetch_thermostats()
431 | t = thermostats[0]
432 | print(t)
433 | print('temp down')
434 | api.change_setpoint(-1,t, True)
435 | time.sleep(5)
436 | t = api.fetch_thermostat(t)
437 | print(t)
438 | print('temp up')
439 | api.change_setpoint(1,t, False)
440 | time.sleep(5)
441 | print(t)
442 |
443 | last = ''
444 | while True:
445 | for d in thermostats:
446 | t = api.fetch_thermostat(d)
447 | text = f'{t}'
448 | if last != text:
449 | print(text)
450 | last = text
451 |
452 |
453 | time.sleep(30)
454 |
--------------------------------------------------------------------------------
/community-scripts/identify_panels v2.py:
--------------------------------------------------------------------------------
1 | # Identify panels: left, red; right, green; top blue; and shared corners
2 |
3 | display.set_panel("left", [[grey, red, red, red, red, red, red, white],
4 | [red, red, cyan, red, red, red, red, red],
5 | [red, red, cyan, red, red, red, red, red],
6 | [red, red, cyan, red, red, red, red, red],
7 | [red, red, cyan, red, red, red, red, red],
8 | [red, red, cyan, red, red, red, red, red],
9 | [red, red, cyan, cyan, cyan, cyan, red, red],
10 | [red, red, red, red, red, red, red, orange]])
11 | display.set_panel("right", [[white, green, green, green, green, green, green, pink],
12 | [green, green, magenta, magenta, magenta, green, green, green],
13 | [green, green, magenta, green, green, magenta, green, green],
14 | [green, green, magenta, green, green, magenta, green, green],
15 | [green, green, magenta, magenta, magenta, green, green, green],
16 | [green, green, magenta, green, green, magenta, green, green],
17 | [green, green, magenta, green, green, magenta, green, green],
18 | [orange, green, green, green, green, green, green, green]])
19 | display.set_panel("top", [[blue, blue, blue, blue, blue, blue, blue, pink],
20 | [blue, blue, yellow, yellow, yellow, blue, blue, blue],
21 | [blue, blue, blue, yellow, blue, blue, blue, blue],
22 | [blue, blue, blue, yellow, blue, blue, blue, blue],
23 | [blue, blue, blue, yellow, blue, blue, blue, blue],
24 | [blue, blue, blue, yellow, blue, blue, blue, blue],
25 | [blue, blue, blue, yellow, blue, blue, blue, blue],
26 | [grey, blue, blue, blue, blue, blue, blue, white]])
27 | # Display status on screen
28 |
29 | def to_text(value):
30 | return str("{:.1f}".format(value))
31 |
32 | screen.draw_rectangle(0, 0, 320, 240, black)
33 | height = 36
34 | while True:
35 | text = ("IP address: " + pi.ip_address() + "\n"
36 | + "CPU temp : " + to_text(pi.cpu_temp()) + "\n"
37 | + "CPU usage : " + to_text(pi.cpu_percent()) + "\n"
38 | + "RAM usage : " + to_text(pi.ram_percent_used()) +"\n"
39 | + "Disk usage: " + to_text(pi.disk_percent()) + "\n")
40 | screen.write_text(10, 18, text, 1, white, black)
41 | time.sleep(5)
42 |
--------------------------------------------------------------------------------
/community-scripts/identify_panels.py:
--------------------------------------------------------------------------------
1 | # Identify panels: left, red; right, green; top blue.
2 |
3 | display.set_panel("left", [[red, red, red, red, red, red, red, red],
4 | [red, red, cyan, red, red, red, red, red],
5 | [red, red, cyan, red, red, red, red, red],
6 | [red, red, cyan, red, red, red, red, red],
7 | [red, red, cyan, red, red, red, red, red],
8 | [red, red, cyan, red, red, red, red, red],
9 | [red, red, cyan, cyan, cyan, cyan, red, red],
10 | [red, red, red, red, red, red, red, red]])
11 | display.set_panel("right", [[green, green, green, green, green, green, green, green],
12 | [green, green, magenta, magenta, magenta, green, green, green],
13 | [green, green, magenta, green, green, magenta, green, green],
14 | [green, green, magenta, green, green, magenta, green, green],
15 | [green, green, magenta, magenta, magenta, green, green, green],
16 | [green, green, magenta, green, green, magenta, green, green],
17 | [green, green, magenta, green, green, magenta, green, green],
18 | [green, green, green, green, green, green, green, green]])
19 | display.set_panel("top", [[blue, blue, blue, blue, blue, blue, blue, blue],
20 | [blue, blue, yellow, yellow, yellow, blue, blue, blue],
21 | [blue, blue, blue, yellow, blue, blue, blue, blue],
22 | [blue, blue, blue, yellow, blue, blue, blue, blue],
23 | [blue, blue, blue, yellow, blue, blue, blue, blue],
24 | [blue, blue, blue, yellow, blue, blue, blue, blue],
25 | [blue, blue, blue, yellow, blue, blue, blue, blue],
26 | [blue, blue, blue, blue, blue, blue, blue, blue]])
27 | # Display status on screen
28 |
29 | def to_text(value):
30 | return str("{:.1f}".format(value))
31 |
32 | screen.draw_rectangle(0, 0, 320, 240, black)
33 | height = 36
34 | while True:
35 | text = ("IP address: " + pi.ip_address() + "\n"
36 | + "CPU temp : " + to_text(pi.cpu_temp()) + "\n"
37 | + "CPU usage : " + to_text(pi.cpu_percent()) + "\n"
38 | + "RAM usage : " + to_text(pi.ram_percent_used()) +"\n"
39 | + "Disk usage: " + to_text(pi.disk_percent()) + "\n")
40 | screen.write_text(10, 18, text, 1, white, black)
41 | time.sleep(5)
42 |
43 |
--------------------------------------------------------------------------------
/community-scripts/iss-tracker-lumi.py:
--------------------------------------------------------------------------------
1 | """
2 | iss-tracker-lumi.py
3 |
4 | Track the International Space Station.
5 |
6 | Additional Installation Packages
7 |
8 | You will need to pip install the following packages:
9 | pip install requests==2.25.1
10 | pip install geopy==2.2.0
11 |
12 | Display 8-bit ISS, it's current location (latitude / longitude) and distance from a given location.
13 |
14 | This project was inspired by my son. He has a large interest in space and wanted a light system to track the ISS.
15 | The original version of this will light up a strip of 20 leds. This version was built when a co-worker sent me a
16 | link the the LumiCube.
17 |
18 | You will need to set the latitude, longitude, sound_file, and sound_file_length in the current_properties dictionary.
19 |
20 | You can get your Latitude and Longitude from: https://www.latlong.net
21 |
22 | latitude and longitude are float values
23 | sound_file is a string and the file will need to be in the Desktop/ directory of the user you created when setting up
24 | your LumiCube.
25 | sound_length is an integer specifying the length of the sound file in seconds
26 |
27 | Properties you can set:
28 | latitude : Float of latitude of location you are at. i.e. 51.507351
29 | longitude : Float of longitude of location you are at. i.e. -0.127758
30 | radius : If the ISS is withing this float value it will either be Overhead or Visible
31 | The default is 1300 (miles). If you change from mi to km, you need to change this to 2092 (kilometers)
32 | distance_measure : mi for miles, km for kilometers
33 | do_not_disturb :
34 | start : Hour you want the do not disturb to start in 24 hour format. i.e. 22
35 | end : Hour you want the do not disturb to end in 24 hour format. i.e. 9
36 | sound_file : The name of the file in Desktop/ directory that will be played when Overhead or Visible
37 | sound_file_length : The length in seconds of the sound file.
38 | """
39 | __author__ = "Rick Feldmann"
40 | __email__ = "wrfeldmann@icloud.com"
41 | __status__ = "Production"
42 | __version__ = "1.0"
43 |
44 | import logging.handlers
45 | import os
46 | import requests
47 | import time
48 |
49 | from datetime import datetime
50 | from geopy import distance
51 |
52 | properties = dict()
53 | properties["latitude"] = 39.2383
54 | properties["longitude"] = -77.4511
55 | properties["radius"] = 1300.0
56 | properties["distance_measure"] = "mi"
57 | properties["do_not_disturb"] = dict()
58 | properties["do_not_disturb"]["start"] = 22
59 | properties["do_not_disturb"]["end"] = 9
60 | properties["sound_file"] = "2001_Space_Odyssey.mp3"
61 | properties["sound_file_length"] = 78
62 |
63 |
64 | class BuildPanel(object):
65 | def __init__(self):
66 | return
67 |
68 | def create_iss_panel(self, color, rotate):
69 | """
70 | Creates an 8-bit representation of the ISS.
71 |
72 | @param: LumiCube Color
73 | @param: Boolean True or False to build the ISS rotated 90 degrees
74 |
75 | @return: 8x8 list with the appropriate colors to represent the ISS
76 | No rotation Rotated 90 degrees
77 |
78 | X X X X XXXXXXXX
79 | X X X X XX
80 | X X X X XXXXXXXX
81 | XXXXXXXX XX
82 | XXXXXXXX XX
83 | X X X X XXXXXXXX
84 | X X X X XX
85 | X X X X XXXXXXXX
86 | """
87 | panel = list()
88 | if rotate is True:
89 | for row_index in range(0, 8, 1):
90 | row = list()
91 | for cell_index in range(0, 8, 1):
92 | if row_index in [0, 2, 5, 7] and cell_index in [0, 1, 2, 5, 6, 7]:
93 | row.append(color)
94 | if row_index in [1, 3, 4, 6] and cell_index in [0, 1, 2, 5, 6, 7]:
95 | row.append(black)
96 | if row_index in [0, 1, 2, 3, 4, 5, 6, 7] and cell_index in [3, 4]:
97 | row.append(color)
98 | panel.append(row)
99 | else:
100 | for row_index in range(0, 8, 1):
101 | row = list()
102 | for cell_index in range(0, 8, 1):
103 | if row_index in [0, 1, 2, 5, 6, 7] and cell_index in [0, 2, 5, 7]:
104 | row.append(color)
105 | if row_index in [0, 1, 2, 5, 6, 7] and cell_index in [1, 3, 4, 6]:
106 | row.append(black)
107 | if row_index in [3, 4] and cell_index in [0, 1, 2, 3, 4, 5, 6, 7]:
108 | row.append(color)
109 | panel.append(row)
110 | return panel
111 |
112 |
113 | class CheckDoNotDisturb(object):
114 | def __init__(self):
115 | return
116 |
117 | def check_do_not_disturb(self, current_datetime):
118 | """Check to see if it the run is during the do not disturb time."""
119 | return_value = False
120 | current_hour = current_datetime.hour
121 | if current_hour > properties["do_not_disturb"]["start"]:
122 | return_value = True
123 | elif current_hour < properties["do_not_disturb"]["end"]:
124 | return_value = True
125 | return return_value
126 |
127 |
128 | class ISSUtils(object):
129 | def __init__(self):
130 | return
131 |
132 | def current_ISS_location(self):
133 | """Get the current ISS Location."""
134 | try:
135 | response = requests.get(url="http://api.open-notify.org/iss-now.json")
136 | data = response.json()
137 | current_iss_latitude = float(data["iss_position"]["latitude"])
138 | current_iss_longitude = float(data["iss_position"]["longitude"])
139 | except:
140 | current_iss_latitude = 0.0
141 | current_iss_longitude = 0.0
142 | return current_iss_latitude, current_iss_longitude
143 |
144 | def iss_distance(self, current_iss_latitude, current_iss_longitude):
145 | """Get the distance you are from the ISS."""
146 | my_location = (properties["latitude"], properties["longitude"])
147 | iss_location = (current_iss_latitude, current_iss_longitude)
148 | if properties["distance_measure"] == "mi":
149 | return_distance = distance.distance(my_location, iss_location).miles
150 | else:
151 | return_distance = distance.distance(my_location, iss_location).km
152 | return return_distance
153 |
154 | def near_ISS(self, distance):
155 | """Calculate if we are near enough that it is Overhead."""
156 | message = "Far Away"
157 | true_false = False
158 | if distance <= properties["radius"]:
159 | message = "Overhead"
160 | true_false = True
161 | return true_false, message
162 |
163 |
164 | class LoggingUtils(object):
165 | def __init__(self):
166 | """
167 | Initialize logging.
168 |
169 | File is Desktop/loggs/iss-tracker.log
170 |
171 | I use this file for homebridge integration with Apple HomeKit so I can trigger additional Apple Home devices
172 | to perform various things.
173 | """
174 | self.log_dir = "logs/"
175 | self.log_file = "{0}/{1}{2}".format(os.getcwd(), self.log_dir, "iss-tracker.log")
176 | self.logger = logging.getLogger("issLogger")
177 | if not os.path.exists(self.log_dir):
178 | os.makedirs(self.log_dir)
179 |
180 | def configureLogging(self, level="INFO"):
181 | """Configure and return the logger."""
182 | self.logger.setLevel(logging.INFO)
183 | rotating_log_file_handler = logging.handlers.RotatingFileHandler(self.log_file,
184 | maxBytes=1048576,
185 | backupCount=5)
186 | rotating_log_file_formatter = logging.Formatter("%(message)s")
187 | rotating_log_file_handler.setFormatter(rotating_log_file_formatter)
188 | self.logger.addHandler(rotating_log_file_handler)
189 | return self.logger
190 |
191 |
192 | class SoundUtils(object):
193 | def __init__(self):
194 | return
195 |
196 | def check_playing_sound(self, play_sound, message):
197 | """
198 | Say the message passed in and, if not playing, play the sound.
199 |
200 | :param play_sound:
201 | :param message:
202 | :return:
203 | """
204 | speaker.say("The International Space Station is {0}".format(message))
205 | if not play_sound and os.path.exists(properties["sound_file"]):
206 | self.visible_sound_start_time = datetime.now()
207 | speaker.play(properties["sound_file"])
208 | play_sound = True
209 | time.sleep(1.0)
210 | elif play_sound:
211 | current_time = datetime.now()
212 | time_difference = abs(current_time - self.visible_sound_start_time)
213 | if time_difference.seconds > properties["sound_file_length"]:
214 | play_sound = False
215 | return play_sound
216 |
217 |
218 | class SunriseSunsetUtils(object):
219 | def __init__(self):
220 | return
221 |
222 | def getTimestamp(self, dt):
223 | return datetime.timestamp(datetime.fromisoformat(dt))
224 |
225 | def is_twilight(self, current_time):
226 | """
227 | Determines if the ISS is visible.
228 |
229 | :param current_time:
230 | :param args:
231 | :return:
232 | """
233 | sunrise_sunset_base_url = "https://api.sunrise-sunset.org/json"
234 | sunrise_sunset_url = "{0}?lat={1}&lng={2}&formatted=0".format(sunrise_sunset_base_url,
235 | properties["latitude"],
236 | properties["longitude"])
237 | try:
238 | response = requests.get(sunrise_sunset_url)
239 | data = response.json()
240 | astronomical_twilight_begin_timestamp = self.getTimestamp(
241 | data["results"]["astronomical_twilight_begin"])
242 | sunrise_timestamp = self.getTimestamp(data["results"]["sunrise"])
243 | sunset_timestamp = self.getTimestamp(data["results"]["sunset"])
244 | astronomical_twilight_end_timestamp = self.getTimestamp(data["results"]["astronomical_twilight_end"])
245 | except:
246 | astronomical_twilight_begin_timestamp = 0
247 | sunrise_timestamp = 0
248 | sunset_timestamp = 0
249 | astronomical_twilight_end_timestamp = 0
250 | timezone_offset = float(current_time.isoformat().split("T")[1][-6:].replace(":", "."))
251 | time_now = self.getTimestamp(current_time.isoformat())
252 | astronomical_twilight_begin_timestamp = astronomical_twilight_begin_timestamp + (timezone_offset * 3600)
253 | sunrise_timestamp = sunrise_timestamp + (timezone_offset * 3600)
254 | sunset_timestamp = sunset_timestamp + (timezone_offset * 3600)
255 | astronomical_twilight_end_timestamp = astronomical_twilight_end_timestamp + (timezone_offset * 3600)
256 | true_false = False
257 | if (time_now >= astronomical_twilight_begin_timestamp and time_now <= sunrise_timestamp) or \
258 | (time_now >= sunset_timestamp and time_now <= astronomical_twilight_end_timestamp):
259 | true_false = True
260 | return true_false
261 |
262 |
263 | build_panel = BuildPanel()
264 | check_do_not_disturb = CheckDoNotDisturb()
265 | iss_utils = ISSUtils()
266 | logging_utils = LoggingUtils()
267 | sound_utils = SoundUtils()
268 | sunrise_sunset_utils = SunriseSunsetUtils()
269 |
270 | # Configure and return a logger from logging_utils
271 | logger = logging_utils.configureLogging()
272 |
273 | # Create the panels necessary
274 | blue_panel_1 = build_panel.create_iss_panel(blue, False)
275 | blue_panel_2 = build_panel.create_iss_panel(blue, True)
276 | green_panel_1 = build_panel.create_iss_panel(green, False)
277 | green_panel_2 = build_panel.create_iss_panel(green, True)
278 | grey_panel_1 = build_panel.create_iss_panel(grey, False)
279 | grey_panel_2 = build_panel.create_iss_panel(grey, True)
280 | black_panel = build_panel.create_iss_panel(black, False)
281 |
282 | # Initialize other variables
283 | panel_cycle = False
284 | heading_printed = False
285 | playing_sound = False
286 |
287 | display.set_all(black)
288 |
289 | """Fun little startup to show the ISS."""
290 | for i in range(1, 5, 1):
291 | display.set_panel("top", grey_panel_1)
292 | display.set_panel("left", green_panel_1)
293 | display.set_panel("right", blue_panel_1)
294 | time.sleep(.5)
295 | display.set_panel("top", grey_panel_2)
296 | display.set_panel("left", green_panel_2)
297 | display.set_panel("right", blue_panel_2)
298 | time.sleep(.5)
299 |
300 | display.set_all(black)
301 |
302 | """Infinite loop to keep tracking the ISS."""
303 | while True:
304 | now = datetime.now().astimezone()
305 | display_now = now.strftime("%Y-%m-%d %H:%M:%S %Z")
306 | iss_latitude, iss_longitude = iss_utils.current_ISS_location()
307 | distance_iss = iss_utils.iss_distance(iss_latitude, iss_longitude)
308 | is_near, message = iss_utils.near_ISS(distance_iss)
309 | is_visible = sunrise_sunset_utils.is_twilight(now)
310 | if is_visible and is_near:
311 | message = "Visible"
312 | str_iss_latitude = "{0:.2f}N".format(iss_latitude)
313 | if iss_latitude < 0:
314 | str_iss_latitude = "{0:.2f}S".format(abs(iss_latitude))
315 | str_iss_longitude = "{0:.2f}E".format(iss_longitude)
316 | if iss_longitude < 0:
317 | str_iss_longitude = "{0:.2f}W".format(abs(iss_longitude))
318 | str_distance_iss = "{0:.0f}".format(distance_iss)
319 | do_not_disturb = check_do_not_disturb.check_do_not_disturb(now)
320 | speaker.volume = 25
321 | if not heading_printed:
322 | heading_line = "| {0} | {1} | {2} | {3} | {4} |".format("Current Time".ljust(23, " "),
323 | "ISS Latitude".rjust(12, " "),
324 | "ISS Longitude".rjust(13, " "),
325 | "Distance".rjust(8, " "),
326 | "Message".ljust(8, " "))
327 | print(heading_line)
328 | logger.info(heading_line)
329 | data_line = "| {0} | {1} | {2} | {3} | {4} |".format(display_now.ljust(23, " "),
330 | str_iss_latitude.rjust(12, " "),
331 | str_iss_longitude.rjust(13, " "),
332 | str_distance_iss.rjust(8, " "),
333 | message.ljust(8, " "))
334 | print(data_line)
335 | logger.info(data_line)
336 | heading_printed = True
337 | lumi_message = "{0} - {1} - {2}{3} - {4}".format(str_iss_latitude,
338 | str_iss_longitude,
339 | str_distance_iss,
340 | properties["distance_measure"],
341 | message)
342 | if is_visible and is_near:
343 | display.set_panel("top", green_panel_1)
344 | if not do_not_disturb:
345 | playing_sound = sound_utils.check_playing_sound(playing_sound, message)
346 | display.scroll_text(lumi_message, green, black, .5)
347 | display.set_panel("left", green_panel_1)
348 | display.set_panel("right", green_panel_1)
349 | elif is_near:
350 | display.set_panel("top", blue_panel_1)
351 | if not do_not_disturb:
352 | playing_sound = sound_utils.check_playing_sound(playing_sound, message)
353 | display.scroll_text(lumi_message, blue, black, .5)
354 | display.set_panel("left", blue_panel_1)
355 | display.set_panel("right", blue_panel_1)
356 | else:
357 | display.set_panel("top", black_panel)
358 | if panel_cycle:
359 | display.set_panel("top", grey_panel_1)
360 | panel_cycle = False
361 | else:
362 | display.set_panel("top", grey_panel_2)
363 | panel_cycle = True
364 | lumi_message = "{0} - {1} - {2}{3}".format(str_iss_latitude,
365 | str_iss_longitude,
366 | str_distance_iss,
367 | properties["distance_measure"])
368 | display.scroll_text(lumi_message, grey, black, .5)
369 | display.set_panel("left", grey_panel_1)
370 | display.set_panel("right", grey_panel_1)
371 | time.sleep(4)
372 |
--------------------------------------------------------------------------------
/community-scripts/minecraft_pic.py:
--------------------------------------------------------------------------------
1 | # Draw some minecraft
2 |
3 | # Predefined colors
4 |
5 | B = black
6 | G = grey
7 | w = white
8 | r = red
9 | o = orange
10 | y = yellow
11 | g = green
12 | c = cyan
13 | b = blue
14 | m = magenta
15 | p = pink
16 | P = purple
17 |
18 | # Image definitions
19 |
20 | enderman = [
21 | [B,B,B,B,B,B,B,B],
22 | [B,B,B,B,B,B,B,B],
23 | [B,B,B,B,B,B,B,B],
24 | [B,B,B,B,B,B,B,B],
25 | [m,P,m,B,B,m,P,m],
26 | [B,B,B,B,B,B,B,B],
27 | [B,B,B,B,B,B,B,B],
28 | [B,B,B,B,B,B,B,B],
29 | ]
30 |
31 | creeper = [
32 | [g,g,g,g,g,g,g,g],
33 | [g,g,g,g,g,g,g,g],
34 | [g,B,B,g,g,B,B,g],
35 | [g,B,B,g,g,B,B,g],
36 | [g,g,g,B,B,g,g,g],
37 | [g,g,B,B,B,B,g,g],
38 | [g,g,B,g,g,B,g,g],
39 | [g,g,B,g,g,B,g,g],
40 | ]
41 |
42 | mushroomcow = [
43 | [r,r,r,w,w,w,G,r],
44 | [r,r,r,w,w,w,r,r],
45 | [B,B,r,w,w,r,B,B],
46 | [B,B,r,G,r,r,B,B],
47 | [r,r,r,r,r,r,r,r],
48 | [r,r,w,w,w,w,r,r],
49 | [r,w,B,G,G,B,w,r],
50 | [r,w,G,B,B,G,w,r],
51 | ]
52 |
53 | # Display images
54 |
55 | display.set_panel("left", alex)
56 | display.set_panel("right", creeper)
57 | display.set_panel("top", mushroomcow)
58 |
59 |
--------------------------------------------------------------------------------
/community-scripts/minecraft_random_pic.py:
--------------------------------------------------------------------------------
1 | # Definition of time = https://docs.python.org/3/library/time.html
2 | # Definition of random = https://docs.python.org/3/library/random.html
3 |
4 | import time
5 | import random
6 |
7 | # Define some hex colors
8 |
9 | black = 0x000000
10 | brown = 0xA38000
11 | grey = 0x808080
12 | lightgrey = 0xEFEDED
13 | mediumgrey = 0xBBBBCC
14 | white = 0xFFFFFF
15 | red = 0xFF0000
16 | orange = 0xFF8C00
17 | yellow = 0xFFFF00
18 | green = 0x00FF00
19 | cyan = 0x00FFFF
20 | blue = 0x0000FF
21 | magenta = 0xFF00FF
22 | pink = 0xFF007F
23 | skin = 0xF4CCCC
24 | purple = 0x800080
25 |
26 | # Define shorts for the colors for easier handling
27 |
28 | B = black
29 | Br = brown
30 | G = grey
31 | lG = lightgrey
32 | mG = mediumgrey
33 | w = white
34 | r = red
35 | o = orange
36 | y = yellow
37 | g = green
38 | c = cyan
39 | b = blue
40 | m = magenta
41 | p = pink
42 | P = purple
43 | s = skin
44 |
45 | # define the pixel/led images
46 | #
47 | # top
48 | # r
49 | # l i
50 | # e g
51 | # f h
52 | # t t
53 | # bottom
54 |
55 | sheep = [
56 | [w,w,w,w,w,w,w,w],
57 | [w,lG,lG,mG,mG,mG,mG,w],
58 | [w,Br,Br,Br,Br,Br,Br,w],
59 | [w,B,w,Br,Br,w,B,w],
60 | [w,Br,Br,w,w,Br,Br,w],
61 | [w,lG,Br,s,s,Br,lG,w],
62 | [w,lG,Br,s,s,Br,lG,w],
63 | [w,w,w,w,w,w,w,w],
64 | ]
65 |
66 | enderman = [
67 | [B,B,B,B,B,B,B,B],
68 | [B,B,B,B,B,B,B,B],
69 | [B,B,B,B,B,B,B,B],
70 | [B,B,B,B,B,B,B,B],
71 | [m,P,m,B,B,m,P,m],
72 | [B,B,B,B,B,B,B,B],
73 | [B,B,B,B,B,B,B,B],
74 | [B,B,B,B,B,B,B,B],
75 | ]
76 |
77 | alex = [
78 | [o,o,o,o,o,o,o,o],
79 | [o,o,o,o,o,o,o,o],
80 | [o,o,o,o,s,s,o,o],
81 | [o,o,o,s,s,s,s,o],
82 | [s,B,w,s,s,B,w,s],
83 | [s,s,s,s,s,s,s,s],
84 | [s,s,s,m,m,s,s,s],
85 | [s,s,s,s,s,s,s,s],
86 | ]
87 |
88 | creeper = [
89 | [g,g,g,g,g,g,g,g],
90 | [g,g,g,g,g,g,g,g],
91 | [g,B,B,g,g,B,B,g],
92 | [g,B,B,g,g,B,B,g],
93 | [g,g,g,B,B,g,g,g],
94 | [g,g,B,B,B,B,g,g],
95 | [g,g,B,g,g,B,g,g],
96 | [g,g,B,g,g,B,g,g],
97 | ]
98 |
99 | mushroomcow = [
100 | [r,r,r,w,w,w,mG,r],
101 | [r,r,r,w,w,lG,r,r],
102 | [B,B,r,w,lG,r,B,B],
103 | [B,B,r,mG,r,r,B,B],
104 | [r,r,r,r,r,r,r,r],
105 | [r,r,w,w,w,w,r,r],
106 | [r,w,B,G,G,B,w,r],
107 | [r,w,G,B,B,G,w,r],
108 | ]
109 |
110 | cow = [
111 | [Br,Br,Br,w,w,G,G,Br],
112 | [Br,Br,Br,Br,Br,Br,Br,Br],
113 | [lG,lG,Br,Br,Br,Br,Br,Br],
114 | [B,w,Br,Br,Br,Br,w,B],
115 | [Br,Br,Br,Br,Br,Br,Br,Br],
116 | [Br,Br,Br,Br,Br,Br,Br,Br],
117 | [Br,Br,Br,Br,Br,Br,Br,Br],
118 | [Br,Br,Br,Br,Br,Br,Br,Br],
119 | ]
120 |
121 | cow = [
122 | [Br,Br,Br,Br,B,lG,Br,Br],
123 | [w,w,Br,Br,w,lG,Br,Br],
124 | [lG,B,w,Br,Br,Br,Br,Br],
125 | [lG,G,w,Br,Br,Br,w,w],
126 | [lG,G,w,Br,Br,Br,w,w],
127 | [lG,B,w,Br,Br,Br,lG,G],
128 | [w,w,Br,Br,w,lG,Br,G],
129 | [Br,Br,Br,Br,B,lG,Br,Br],
130 | ]
131 |
132 | # Make a list named "items" of the images
133 | # https://docs.python.org/3/tutorial/datastructures.html
134 |
135 | items = [sheep, enderman, alex, creeper, cow, mushroomcow]
136 |
137 | # Use "while" to create a loop
138 | # https://docs.python.org/3/reference/compound_stmts.html?#while
139 |
140 | # Take a random item out of the list
141 | # https://docs.python.org/3/library/random.html?#random.choice
142 |
143 | while True:
144 | random_item = random.choice(items)
145 | display.set_panel("left", random_item)
146 | random_item = random.choice(items)
147 | display.set_panel("right", random_item)
148 | random_item = random.choice(items)
149 | display.set_panel("top", random_item)
150 | time.sleep(10)
151 |
152 | # Sleep for 10 seconds before returning to the beginning of the loop again
153 |
154 |
155 |
156 |
--------------------------------------------------------------------------------
/community-scripts/mlb.py:
--------------------------------------------------------------------------------
1 | # classes and method useful for getting data from the mlb api
2 | # see mlb copyright information here http://gdx.mlb.com/components/copyright.txt
3 | # Author : Kevin Haney
4 | # Date : 10/23/2922
5 | # Version : 1.0
6 | #
7 | from datetime import datetime, timedelta
8 | from dateutil.parser import parse
9 | import requests
10 | import time
11 |
12 | class Team:
13 | def __init__(self, teamNode):
14 | self.name = ""
15 | self.abbreviation = ""
16 | if 'name' in teamNode:
17 | self.name = teamNode['name']
18 | if 'abbreviation' in teamNode:
19 | self.abbreviation = teamNode['abbreviation']
20 |
21 | class Inning:
22 | def __init__(self, awayScore, homeScore):
23 | self.awayScore = awayScore
24 | self.homeScore = homeScore
25 |
26 | class LineScore:
27 | def __init__(self, liveData):
28 | self.innings = []
29 | self.currentInning = 0
30 | self.isTopInning = False
31 | if 'linescore' in liveData:
32 | lineScore = liveData['linescore']
33 | if 'currentInning' in lineScore:
34 | self.currentInning = lineScore['currentInning']
35 | self.isTopInning = lineScore['isTopInning']
36 | for inning in lineScore['innings']:
37 | awayRuns = 0
38 | homeRuns = 0
39 | if 'runs' in inning['away']:
40 | awayRuns = inning['away']['runs']
41 | if 'runs' in inning['home']:
42 | homeRuns = inning['home']['runs']
43 | self.innings.append(Inning(awayRuns, homeRuns))
44 |
45 | class LiveData:
46 | def __init__(self, gamePk):
47 | self.gamePk = gamePk
48 | self.indicator = "---"
49 | self.inning = 0
50 | self.balls = 0
51 | self.strikes = 0
52 | self.outs = 0
53 | self.score = " "
54 | self.awayScore = 0
55 | self.homeScore = 0
56 | self.onFirst = False
57 | self.onSecond = False
58 | self.onThird = False
59 | self.lineScore = None
60 |
61 | #live = requests.get("https://statsapi.mlb.com/api/v1.1/game/"+str(gamePk)+"/feed/live").json()
62 | live = {}
63 | url = "https://statsapi.mlb.com/api/v1.1/game/"+str(gamePk)+"/feed/live"
64 | try:
65 | live = requests.get(url).json()
66 | except Exception as e:
67 | print("Exception getting",url,"\n",e)
68 | self.status = "Error"
69 | self.awayTeam = Team({})
70 | self.homeTeam = Team({})
71 | self.lineScore = {}
72 |
73 | if 'gameData' in live:
74 | gameData = live['gameData']
75 | away = gameData['teams']['away']
76 | home = gameData['teams']['home']
77 | self.status = gameData['status']['detailedState']
78 |
79 | self.awayTeam = Team(away)
80 | self.homeTeam = Team(home)
81 |
82 | currentPlay = {}
83 | liveData = live['liveData']
84 | if 'currentPlay' in liveData['plays']:
85 | currentPlay = liveData['plays']['currentPlay']
86 | self._set_game_data(currentPlay)
87 | self.lineScore = LineScore(liveData)
88 |
89 | def _set_game_data(self, currentPlay):
90 | if 'result' in currentPlay:
91 | self.awayScore = currentPlay['result']['awayScore']
92 | self.homeScore = currentPlay['result']['homeScore']
93 | self.score = str(self.awayScore) + "-" + str(self.homeScore)
94 |
95 | if self.status == "In Progress":
96 | about = currentPlay['about']
97 | count = currentPlay['count']
98 |
99 | isTop = about['isTopInning']
100 | if isTop == "true":
101 | self.indicator = "top"
102 | else:
103 | self.indicator = "bot"
104 |
105 | self.balls = count['balls']
106 | self.strikes = count['strikes']
107 | self.outs = count['outs']
108 |
109 | self.inning = about['inning']
110 | if self.outs == 3:
111 | #print(currentPlay)
112 | #print(self.outs,self.indicator)
113 | if self.indicator == "top":
114 | self.indicator = "mid"
115 | else:
116 | self.indicator = "end"
117 | self.outs = 0
118 | self.balls = 0
119 | self.strikes = 0
120 |
121 | self.onFirst = False
122 | self.onSecond = False
123 | self.onThird = False
124 |
125 | if 'matchup' in currentPlay:
126 | matchup = currentPlay['matchup']
127 | #print(matchup)
128 | if 'postOnFirst' in matchup:
129 | self.onFirst = True
130 | if 'postOnSecond' in matchup:
131 | self.onSecond = True
132 | if 'postOnThird' in matchup:
133 | self.onThird = True
134 |
135 | def __str__(self):
136 | inning = " " + self.indicator + " " + str(self.inning)
137 | outs = " " + str(self.outs) + " outs"
138 | count = " " + str(self.balls) + " balls, " + str(self.strikes) + " strikes"
139 | score = " " + self.score
140 | boxScore = ""
141 | header = ""
142 | topScore = ""
143 | botScore = ""
144 | if self.status == "In Progress":
145 | for i in range(len(self.lineScore.innings)):
146 | header += str(i+1) + " "
147 | topScore += str(self.lineScore.innings[i].awayScore) + " "
148 | botScore += str(self.lineScore.innings[i].homeScore) + " "
149 | boxScore = "\n" + header + "\n" + topScore + "\n" + botScore
150 |
151 | if self.status != "In Progress":
152 | if self.indicator == "---" or self.indicator == "mid" or self.indicator == "end":
153 | inning = ""
154 | outs = ""
155 | count = ""
156 |
157 | if self.status == "Scheduled" or self.status == "Pre-Game" or self.status == "Delayed Start":
158 | score = ""
159 | innings = ""
160 |
161 | return self.status + score + inning + outs + count + boxScore
162 |
163 | class Game:
164 | def datetime_from_utc_to_local(utc_datetime):
165 | now_timestamp = time.time()
166 | offset = datetime.fromtimestamp(now_timestamp) - datetime.utcfromtimestamp(now_timestamp)
167 | return utc_datetime + offset
168 |
169 | def __init__(self, homeTeam, awayTeam, startTime):
170 | self.home = homeTeam
171 | self.away = awayTeam
172 | self.startTime = Game.datetime_from_utc_to_local(startTime)
173 |
174 | def get_games_on_date(date):
175 | results = {}
176 | games = {}
177 | url = "http://statsapi.mlb.com/api/v1/schedule/games?sportId=1&date=" + date
178 | try:
179 | games = requests.get(url).json()
180 | except Exception as e:
181 | print("Exception getting",url,"\n",e)
182 |
183 | if 'dates' in games:
184 | for d in games['dates']:
185 | for g in d['games']:
186 | gamePk = g['gamePk']
187 |
188 | liveData = LiveData(gamePk)
189 |
190 | results[gamePk] = Game(liveData.homeTeam, liveData.awayTeam, parse(g['gameDate']))
191 |
192 | return results
193 |
194 |
195 | if __name__ == "__main__":
196 | date = datetime.now().strftime("%m/%d/%Y")
197 | #date = (datetime.now() + timedelta(days=-1)).strftime("%m/%d/%Y")
198 | #date = "10/23/2022"
199 | #date = "10/28/2022"
200 |
201 | games = get_games_on_date(date)
202 | if len(games) == 0:
203 | message = "No games scheduled for" + date
204 | print(message)
205 |
206 | for gamePk, game in games.items():
207 | print(gamePk, game.away.name,"at",game.home.name,datetime.strftime(game.startTime,'%I:%m'))
208 | liveData = LiveData(gamePk)
209 | print(liveData)
210 | if liveData.status == "In Progress":
211 | while liveData.status == "In Progress":
212 | liveData = LiveData(gamePk)
213 | print(liveData)
214 | time.sleep(5)
--------------------------------------------------------------------------------
/community-scripts/nest_thermostat.py:
--------------------------------------------------------------------------------
1 | # Nest Thermostat
2 | # uses the google_api.py and display_helpers.py modules to communicate with google to access nest thermostat information.
3 | # Display current set point (or range for eco and HEATCOOL modes) and humidity.
4 | # Will display in Celsius or Farenheit base on how the thermostat is configured.
5 | # the top display will show the current mode (orange waves for heat mode, blue for cool mode. other modes are "eco", fan and off).
6 | # if the lumicube has buttons, the top and bottom buttons can be used to increase or decrease the current setpoint.
7 | # the middle button will change the mode - how change mode works:
8 | # - press the middle button, the mode icon on top will flash. You have 30 seconds to change the mode with the top and bottom buttons.
9 | # - Each time you press the top or bottom button, it'll scroll forward or back in the mode options, changing the top display to the next possible mode.
10 | # - if you do select a different mode, do change to that mode, you must hit the center button again. The mode will be changed and the top display
11 | # will update to the new mode.
12 | # setpoint color on the left will change to orange if currently heating, and blue if currently cooling.
13 | # if in "range" mode (eco mode or heatcool mode) there will be an orange or blue indicator to indicate which range we're seeing.
14 | #
15 | # you WILL need a google cloud account, and will need to
16 | # - create a new google cloud project - https://console.cloud.google.com/
17 | # - enable the Smart Device Management API on that project
18 | # - create a Web Application type credential - this will give you the oauth client_id and client_secret
19 | # - on the oauth consent screen add a google user that has permission to access the project
20 | # - create a device access project in the device access console - https://console.nest.google.com/device-access/project-list
21 | # you'll need to provide the oauth client id you got in the previous step
22 | # after this project is created, you'll have the project_id you'll need for configuration
23 | # The program will look for it's configuration in the google folder in the home directory (usually /home/pi/Desktop)
24 | # - create the google folder in the home folder
25 | # - create a file called .nest-config in the google folder, with the folling format (add your project and client id values):
26 | # {
27 | # "project_id" : "",
28 | # "client_id" : "",
29 | # "client_secret" : ""
30 | # }
31 | #
32 | # when you run the first time, the lumicube will show a lock and question mark on screens.
33 | # look at the console for a url
34 | # - copy that url to a browser
35 | # - you'll need to log in with an authorized google account, probably the one you used to create the project with, or another that you have authorized
36 | # - allow the lumicube project to access the data
37 | # - if all goes well, you should be redirected to the redirect_url that you configured - IN that url will be code. copy that code from the URL
38 | # check the console logs again, it should be asking you to put the code in a specific file
39 | # - create that file and add the code (I usually use echo to output to the file)
40 | # - note that I would recommend editing a file of a different name, and renaming it when done, or just use echo "
41 | # if you open the final file in the editor the code may see it and try to read it immediately before you've saved it.
42 | # the code will be used to fetch a refresh token and access token, which it will used to access the device api. Even if you restart it will use the saved token.
43 | # I've found that the token is good for about a week, after which you'll see the lock and question mark display - look at the console for instructions.
44 | #
45 | # Useful links:
46 | # - controlling a nest thermostat with python : https://www.wouternieuwerth.nl/controlling-a-google-nest-thermostat-with-python/
47 | # - Device Access Guides : https://developers.google.com/nest/device-access/registration
48 | # - Thermostat API - https://developers.google.com/nest/device-access/api/thermostat
49 | #
50 | # Author: Kevin Haney
51 | # Date: 12/9/2022
52 | # v1.0
53 | # history:
54 | # - 1.0 - original (v1)
55 |
56 | import os
57 | import time
58 | import requests
59 | import json
60 | import threading
61 |
62 | import sys
63 | sys.path.insert(0, '/home/pi/AbstractFoundry/Daemon/Scripts')
64 | import display_helpers as helpers
65 | import google_api as google
66 |
67 | # right panel
68 | question = [(8,6,1),(8,5,1),(8,4,1), (8,7,2),(8,6,2),(8,5,2),(8,4,2),(8,3,2), (8,7,3),(8,6,3),(8,3,3),(8,2,3),(8,0,3), (8,7,4),(8,6,4),(8,3,4),(8,2,4),(8,0,4),
69 | (8,7,5),(8,6,5),(8,5,5), (8,6,6),(8,5,6)]
70 | # left panel(6,0,8),
71 | lock = [(1,0,8),(1,1,8),(1,2,8),(1,3,8),(1,4,8), (2,0,8),(2,1,8),(2,2,8),(2,3,8),(2,4,8),(2,5,8),(2,6,8),(2,7,8), (3,0,8),(3,1,8),(3,2,8),(3,3,8),(3,4,8),(3,7,8),
72 | (4,0,8),(4,1,8),(4,2,8),(4,3,8),(4,4,8),(4,7,8), (5,0,8),(5,1,8),(5,2,8),(5,3,8),(5,4,8),(5,5,8),(5,6,8),(5,7,8), (6,0,8),(6,1,8),(6,2,8),(6,3,8),(6,4,8)]
73 | lockShade = [(2,1,8),(2,3,8), (3,1,8),(3,3,8), (4,1,8),(4,3,8), (5,1,8),(5,3,8)]
74 |
75 | #top panel
76 | fan = [(0,8,0),(1,8,0),(6,8,0),(7,8,0), (0,8,1),(1,8,1),(2,8,1),(5,8,1),(6,8,1),(7,8,1), (1,8,2),(2,8,2),(5,8,2),(6,8,2),
77 | (3,8,3),(4,8,3), (3,8,4),(4,8,4), (1,8,5),(2,8,5),(5,8,5),(6,8,5), (0,8,6),(1,8,6),(2,8,6),(5,8,6),(6,8,6),(7,8,6),
78 | (0,8,7),(1,8,7),(6,8,7),(7,8,7)]
79 |
80 | topWave = [(0,8,2),(0,8,3),(0,8,6),(0,8,7), (1,8,1),(1,8,2),(1,8,5), (2,8,0),(2,8,1),(2,8,4),(2,8,5), (3,8,0),(3,8,3), (4,8,2),(4,8,3), (5,8,1), (6,8,0),(6,8,1)]
81 |
82 | botWave = [(1,8,6),(1,8,7), (2,8,6), (3,8,4),(3,8,5), (4,8,4),(4,8,7), (5,8,2),(5,8,3),(5,8,6),(5,8,7), (6,8,2),(6,8,5),(6,8,6), (7,8,0),(7,8,1),(7,8,4),(7,8,5)]
83 |
84 | eco1 = [(1,8,3),(1,8,4),(1,8,5),(1,8,6), (2,8,2),(2,8,3),(2,8,4),(2,8,5),(2,8,6), (3,8,2),(3,8,3),(3,8,4),(3,8,5),(3,8,6),
85 | (4,8,1),(4,8,2),(4,8,3),(4,8,4),(4,8,5),(4,8,6), (5,8,1),(5,8,2),(5,8,3),(5,8,4),(5,8,5), (6,8,1),(6,8,2),(6,8,3)]
86 | eco2 = [(2,8,5),(3,8,4),(4,8,3),(5,8,2)]
87 |
88 | def set_display_mode(leds, status):
89 | for x in range(8):
90 | for z in range(8):
91 | leds[(x,8,z)] = black
92 |
93 | if status == None:
94 | display.set_3d(leds)
95 | return
96 |
97 | if status == 'FAN':
98 | for p in fan:
99 | leds[p] = white
100 | elif status == 'HEAT':
101 | for p in topWave:
102 | leds[p] = orange
103 | for p in botWave:
104 | leds[p] = orange
105 | elif status == 'COOL':
106 | for p in topWave:
107 | leds[p] = blue
108 | for p in botWave:
109 | leds[p] = blue
110 | elif status == 'HEATCOOL':
111 | for p in topWave:
112 | leds[p] = orange
113 | for p in botWave:
114 | leds[p] = blue
115 | elif status == 'ECO':
116 | for p in eco1:
117 | leds[p] = green
118 | for p in eco2:
119 | leds[p] = hsv_colour(.225,.88,.392) # 0c2264 decimal 12, 34, 100 hsv 225 88 39.2
120 | else:
121 | for p in topWave:
122 | leds[p] = grey
123 | for p in botWave:
124 | leds[p] = grey
125 | display.set_3d(leds)
126 |
127 | def wait_on_buttons(delay):
128 | global button_warned
129 | action = None
130 | try:
131 | action = buttons.get_next_action(delay)
132 | except:
133 | if not button_warned:
134 | print('This lumicube does not appear to have buttons?')
135 | button_warned = True
136 | time.sleep(delay)
137 |
138 | return action
139 |
140 | def get_current_mode(thermostat):
141 | if thermostat.fan_on:
142 | return 'FAN'
143 | elif thermostat.eco_mode:
144 | return 'ECO'
145 | return thermostat.mode
146 |
147 | def blink_thread(leds,mode):
148 | while True:
149 | set_display_mode(leds,None)
150 | time.sleep(1)
151 | set_display_mode(leds,mode)
152 | time.sleep(1)
153 |
154 | if stop_blink_thread:
155 | break
156 |
157 | def get_next_mode(mode,forward=True):
158 | modes = ['OFF','HEAT','COOL','HEATCOOL','ECO','FAN']
159 |
160 | for i in range(len(modes)):
161 | if modes[i] == mode:
162 | break
163 |
164 | if forward:
165 | i += 1
166 | if i == len(modes):
167 | i=0
168 | else:
169 | i -= 1
170 | if i < 0:
171 | i = len(modes)-1
172 |
173 | return modes[i]
174 |
175 | # once they press the middle button, we're in wait mode.
176 | # we'll flash the current selected mode with a thread.
177 | # we'll then wait for 30 seconds for another button to be pressed.
178 | # if they push top or bottom button, we'll tell the blink thread to stop and wait,
179 | # change the display mode to the next available mode, and start that blinking
180 | # if they pressed the middle button, we'll stop the blink, then change the mode of the thermostat to the currently selected mode and return.
181 | # if 30 seconds elapses before they press a button, we exit without any changes
182 | def change_mode(leds,thermostat):
183 | global stop_blink_thread
184 | current_mode = get_current_mode(thermostat)
185 | new_mode = current_mode
186 | waiting = True
187 | while waiting:
188 | stop_blink_thread = False
189 | t1 = threading.Thread(target=blink_thread, args=(leds,new_mode))
190 | t1.start()
191 |
192 | button = wait_on_buttons(10)
193 | stop_blink_thread = True
194 | t1.join()
195 |
196 | if button == None:
197 | print('no selection')
198 | set_display_mode(leds,current_mode)
199 | waiting = False
200 | elif button == 'top':
201 | new_mode = get_next_mode(new_mode,True)
202 | set_display_mode(leds,new_mode)
203 | elif button == 'bottom':
204 | new_mode = get_next_mode(new_mode,False)
205 | elif button == 'middle':
206 | if current_mode != new_mode or new_mode == 'FAN':
207 | print(f'changing mode from {current_mode} to {new_mode}')
208 | api.change_mode(new_mode,thermostat)
209 | else:
210 | print('no change')
211 | waiting = False
212 |
213 | def handle_action(leds,action,thermostat,heat):
214 | if action == None:
215 | return False
216 | if action == 'top':
217 | api.change_setpoint(1,thermostat,heat)
218 | elif action == 'bottom':
219 | api.change_setpoint(-1,thermostat,heat)
220 | elif action == 'middle':
221 | change_mode(leds,thermostat)
222 | time.sleep(1)
223 | return True
224 |
225 | def display_test(leds):
226 | set_display_mode(leds,'HEAT')
227 | time.sleep(3)
228 | set_display_mode(leds,'COOL')
229 | time.sleep(3)
230 | set_display_mode(leds,'HEATCOOL')
231 | time.sleep(3)
232 | set_display_mode(leds,'FAN')
233 | time.sleep(3)
234 | set_display_mode(leds,'ECO')
235 | time.sleep(3)
236 | set_display_mode(leds,'OFF')
237 | time.sleep(3)
238 |
239 | def show_security(leds):
240 | display.set_all(black)
241 | helpers.set_left(leds, lock, yellow, black, True)
242 | lockshadow = hsv_colour(.14,.063,.812) # cfc532, decimal 207, 197, 50 hsv 14 6.3 81.2
243 | helpers.set_left(leds, lockShade, lockshadow, black, False)
244 | helpers.set_right(leds, question, red, black, True)
245 | display.set_3d(leds)
246 |
247 | def get_set_temperature(t, heat):
248 | if t.mode == 'OFF':
249 | return None
250 |
251 | if t.eco_mode:
252 | if heat:
253 | return t.setpoint_eco_heat
254 | else:
255 | return t.setpoint_eco_cool
256 | if t.mode == 'HEAT':
257 | return t.setpoint_heat
258 | if t.mode == 'COOL':
259 | return t.setpoint_cool
260 | if t.mode == 'HEATCOOL':
261 | if t.status == 'HEATING':
262 | return t.setpoint_heat
263 | elif t.status == 'COOLING':
264 | return t.setpoint_cool
265 | else:
266 | if heat:
267 | return t.setpoint_heat
268 | else:
269 | return t.setpoint_cool
270 |
271 | return None
272 |
273 | def get_font_color(t):
274 | font = grey
275 | if t.status == 'HEATING':
276 | font = orange
277 | elif t.status == 'COOLING':
278 | font = blue
279 |
280 | return font
281 |
282 | def get_mode(t):
283 | mode = t.mode
284 | if t.fan_on:
285 | mode = 'FAN'
286 | if t.eco_mode:
287 | mode = 'ECO'
288 |
289 | return mode
290 |
291 | class Settings:
292 | def __init__(self, delay, humidity_every):
293 | # amount of time between updates
294 | self.delay = delay
295 | # normally show ambient temperature on the right, but every 'humidity_every' cycle show humidity
296 | self.humidity_every = humidity_every
297 |
298 | if __name__ == "__main__":
299 | # if they don't have buttons, print a warning but just once
300 | button_warned = False
301 | settings = Settings(delay=10,humidity_every=12)
302 | humidity_count = 0
303 |
304 | display.set_all(black)
305 |
306 | working_directory = os.getcwd()
307 | print('working directory is ',working_directory)
308 |
309 | # initialize our led dictionary^M
310 | leds = {}
311 | for x in range(9):
312 | for y in range(9):
313 | for z in range(9):
314 | leds[(x,y,z)] = black
315 |
316 | api = google.GoogleApi(working_directory)
317 | #print(api.refresh_token,api.access_token,api.is_permissioned())
318 | if api.is_permissioned() == False:
319 | show_security(leds);
320 |
321 | if api.get_permission() == False:
322 | #show some indication that it failed?
323 | print('could not get permission')
324 | exit()
325 |
326 | # show some indication that it was successful?
327 | display.set_all(black)
328 |
329 | #display_test(leds)
330 |
331 | thermostats = api.fetch_thermostats()
332 | if len(thermostats) == 0:
333 | print('no thermostats found!')
334 | exit()
335 |
336 | last = ''
337 | heat = True
338 | while True:
339 | background = black
340 | font = white
341 | set_temp = 0
342 | for d in thermostats:
343 | t = api.fetch_thermostat(d)
344 | if t == None:
345 | print('couldn''t get thermostat - did credentials expire?')
346 | if api.is_permissioned() == False:
347 | print('restart the program and re-permission the application')
348 | show_security(leds);
349 | exit()
350 | time.sleep(10)
351 | else:
352 | #print(t)
353 | setTemp = get_set_temperature(t,heat)
354 | ambientTemp = t.ambient_temperature
355 | font = get_font_color(t)
356 | mode = get_mode(t)
357 |
358 | set_display_mode(leds, mode)
359 |
360 | text = f'{t}'
361 | if last != text:
362 | print(text)
363 | last = text
364 |
365 | if setTemp is not None:
366 | setTemp = int(round(setTemp,0))
367 | helpers.set_digits(leds,'left',setTemp,font,background)
368 | humidity_count +=1
369 | if humidity_count > settings.humidity_every:
370 | humidity_count = 0
371 | helpers.set_digits(leds,'right',int(round(t.humidity,0)),yellow,background)
372 | else:
373 | helpers.set_digits(leds,'right',int(round(ambientTemp,0)),green,background)
374 |
375 | if (t.eco_mode or t.mode == 'HEATCOOL') and t.status == 'OFF':
376 | if heat:
377 | leds[(4,0,8)] = orange
378 | else:
379 | leds[(4,0,8)] = blue
380 |
381 | display.set_3d(leds)
382 |
383 | action = wait_on_buttons(settings.delay)
384 | if action != None:
385 | if handle_action(leds,action,t,heat):
386 | #toggling heat because if they changed temp during HEATCOOL cycle I want to show the same cycle again
387 | heat = not heat
388 |
389 | heat = not heat
--------------------------------------------------------------------------------
/community-scripts/pacman.py:
--------------------------------------------------------------------------------
1 | c = cyan
2 | y = yellow
3 | pacman_and_ghost = [
4 | [0,c,c,c,c,c,0,0, 0,0,y,y,y,y,0,0,],
5 | [c,c,c,c,c,c,c,0, 0,y,y,y,y,y,y,0,],
6 | [c,0,c,c,0,c,c,0, y,y,0,y,y,y,y,0,],
7 | [c,0,0,c,0,0,c,0, y,y,y,y,y,y,0,0,],
8 | [c,c,c,c,c,c,c,0, y,y,y,y,y,0,0,0,],
9 | [c,c,c,c,c,c,c,0, y,y,y,y,y,y,0,0,],
10 | [c,c,c,c,c,c,c,0, 0,y,y,y,y,y,y,0,],
11 | [c,0,c,0,c,0,c,0, 0,0,y,y,y,y,0,0,],
12 | ]
13 |
14 | while True:
15 | display.set_all(black)
16 | leds = {}
17 | for frame in range(0,32):
18 | for y in range(0, 8):
19 | for x in range(0, 16):
20 | x1 = x - frame + 15
21 | if x1 >= 0 and x1 < 16:
22 | leds[x, y] = pacman_and_ghost[7 - y][x1]
23 | else:
24 | leds[x, y] = black
25 | display.set_leds(leds)
26 | time.sleep(1/10)
27 |
28 |
--------------------------------------------------------------------------------
/community-scripts/vesuvius.py:
--------------------------------------------------------------------------------
1 | # Volcano simulation
2 | # Bubbling magma, random lava flows, random projectiles, earthquakes...
3 | # Author : Kevin Haney
4 | # Date : 10/21/2022
5 | # v1.0
6 |
7 | import time
8 |
9 | display.set_all(black)
10 |
11 | b0 = hsv_colour(.0, .0, .750)
12 | b1 = hsv_colour(.0, .0, .650)
13 | b2 = hsv_colour(.0, .0, .550)
14 | b3 = hsv_colour(.0, .0, .450)
15 | b4 = hsv_colour(.0, .0, .350)
16 | b5 = hsv_colour(.0, .0, .250)
17 | b6 = hsv_colour(.0, .0, .150)
18 |
19 | cone = {
20 | #top
21 | (1,8,5) : b6, (1,8,4) : b5, (1,8,3) : b6, (2,8,2) : b6,
22 | (2,8,6) : b5, (2,8,5) : b3, (2,8,4) : b3, (2,8,3) : b4, (2,8,2) : b6,
23 | (3,8,7) : b5, (3,8,6) : b5, (3,8,5) : b5, (3,8,3) : b5, (3,8,2) : b4, (3,8,1) : b6,
24 | (4,8,7) : b5, (4,8,6) : b4, (4,8,2) : b3, (4,8,1) : b5,
25 | (5,8,7) : b3, (5,8,6) : b3, (5,8,5) : b5, (5,8,3) : b5, (5,8,2) : b3, (5,8,1) : b6,
26 | (6,8,7) : b2, (6,8,6) : b2, (6,8,5) : b3, (6,8,4) : b4, (6,8,3) : b3, (6,8,2) : b5,
27 | (7,8,7) : b3, (7,8,6) : b2, (7,8,5) : b2, (7,8,4) : b5, (7,8,3) : b6,
28 | #left
29 | (7,7,8) : b2, (7,6,8) : b1, (7,5,8) : b1, (7,4,8) : b1, (7,3,8) : b2, (7,2,8) : b3, (7,1,8) : b0, (7,0,8) : b0,
30 | (6,7,8) : b5, (6,6,8) : b2, (6,5,8) : b1, (6,4,8) : b1, (6,3,8) : b1, (6,2,8) : b0, (6,1,8) : b2, (6,0,8) : b3,
31 | (5,7,8) : b4, (5,6,8) : b5, (5,5,8) : b4, (5,4,8) : b1, (5,3,8) : b1, (5,2,8) : b1, (5,1,8) : b0, (5,0,8) : b0,
32 | (4,7,8) : b5, (4,6,8) : b4, (4,5,8) : b3, (4,4,8) : b4, (4,3,8) : b4, (4,2,8) : b3, (4,1,8) : b1, (4,0,8) : b0,
33 | (3,7,8) : b6, (3,6,8) : b5, (3,5,8) : b4, (3,4,8) : b3, (3,3,8) : b2, (3,2,8) : b1, (3,1,8) : b3, (3,0,8) : b0,
34 | (2,5,8) : b6, (2,4,8) : b5, (2,3,8) : b4, (2,2,8) : b2, (2,1,8) : b1, (2,0,8) : b3,
35 | (1,4,8) : b6, (1,3,8) : b5, (1,2,8) : b3, (1,1,8) : b2, (1,0,8) : b1,
36 | (0,3,8) : b6, (0,2,8) : b5, (0,1,8) : b3, (0,0,8) : b2,
37 | #right
38 | (8,7,7) : b2, (8,6,7) : b1, (8,5,7) : b1, (8,4,7) : b1, (8,3,7) : b2, (8,2,7) : b3, (8,1,7) : b0, (8,0,7) : b0,
39 | (8,7,6) : b5, (8,6,6) : b2, (8,5,6) : b1, (8,4,6) : b1, (8,3,6) : b1, (8,2,6) : b0, (8,1,6) : b2, (8,0,6) : b3,
40 | (8,7,5) : b4, (8,6,5) : b5, (8,5,5) : b4, (8,4,5) : b1, (8,3,5) : b1, (8,2,5) : b1, (8,1,5) : b0, (8,0,5) : b0,
41 | (8,7,4) : b5, (8,6,4) : b4, (8,5,4) : b3, (8,4,4) : b4, (8,3,4) : b4, (8,2,4) : b3, (8,1,4) : b1, (8,0,4) : b0,
42 | (8,7,3) : b6, (8,6,3) : b5, (8,5,3) : b4, (8,4,3) : b3, (8,3,3) : b2, (8,2,3) : b1, (8,1,3) : b3, (8,0,3) : b0,
43 | (8,5,2) : b6, (8,4,2) : b5, (8,3,2) : b4, (8,2,2) : b2, (8,1,2) : b1, (8,0,2) : b3,
44 | (8,4,1) : b6, (8,3,1) : b5, (8,2,1) : b3, (8,1,1) : b2, (8,0,1) : b1,
45 | (8,3,0) : b6, (8,2,0) : b5, (8,1,0) : b3, (8,0,0) : b2
46 | }
47 |
48 | lava0 = [
49 | (3,8,5), (2,8,6), (2,8,7) , (2,7,8), (2,6,8), (2,5,8), (1,5,8), (2,5,8), (1,5,8), (1,4,8), (1,4,8), (0,4,8), (0,4,8), (0,3,8)
50 | ]
51 |
52 | lava1 = [
53 | (4,8,6), (5,8,6), (4,8,7) , (5,8,7), (4,7,8), (5,7,8), (4,6,8), (4,5,8), (3,4,8), (5,4,8),
54 | (3,3,8), (5,3,8), (3,2,8) , (5,2,8), (2,1,8), (5,1,8), (1,0,8), (5,0,8), (0,0,8), (4,0,8), (4,0,8), (4,1,8), (3,0,8), (4,0,8)
55 | ]
56 |
57 | lava2 = [
58 | (5,8,5), (6,8,6), (5,8,6), (6,8,5), (6,8,6), (6,8,7), (7,8,6), (7,8,7), (7,7,8), (7,7,8), (7,6,8), (8,7,7), (7,5,8), (8,6,7),
59 | (7,4,8), (8,5,7), (7,3,8), (8,4,7), (7,3,8), (8,3,7), (6,3,8), (8,3,7), (6,3,8), (8,3,6), (6,2,8), (8,2,6), (5,2,8), (8,2,6),
60 | (5,1,8), (8,2,5), (6,1,8), (8,1,5), (5,0,8), (8,1,6), (4,0,8), (8,0,6), (4,1,8), (8,0,5), (4,0,8), (8,0,4), (3,0,8), (8,0,7)
61 | ]
62 |
63 | lava3 = [
64 | (6,8,5), (6,8,4), (7,8,5) , (7,8,4), (8,7,5), (8,7,4), (8,6,5), (8,6,4), (8,5,4), (8,5,3),
65 | (8,4,4), (8,4,3), (8,3,3) , (8,3,2), (8,2,3), (8,2,2), (8,1,2), (8,1,1), (8,0,3), (8,0,1), (8,0,2), (8,0,0)
66 | ]
67 |
68 | lava4 = [
69 | (5,8,3), (6,8,2), (7,8,8) , (8,7,2), (8,6,2), (8,5,2), (8,5,1), (8,5,2), (8,5,1), (8,4,1), (8,4,1), (8,4,0), (8,4,0), (8,3,0)
70 | ]
71 |
72 | flows = [
73 | lava0,
74 | lava1,
75 | lava2,
76 | lava3,
77 | lava4
78 | ]
79 |
80 | fireball0 = [
81 | (4,8,5), (4,8,5), (3,8,4), (2,8,3), (1,8,2), (1,8,2), (0,8,3), (1,8,4), (2,8,5), (3,8,6), (4,8,7), (5,7,8), (5,6,8), (5,5,8),
82 | (5,5,8), (4,6,8), (3,5,8), (3,4,8), (3,3,8), (3,2,8), (3,2,8), (2,3,8), (1,2,8), (1,1,8), (1,0,8)
83 | ]
84 |
85 | fireball1 = [
86 | (4,8,5), (4,8,5), (3,8,4), (2,8,3), (1,8,2), (1,8,2), (0,8,3), (1,8,4), (2,8,5), (3,8,6), (4,8,7), (5,7,8), (5,6,8), (5,5,8),
87 | (5,5,8), (6,6,8), (7,5,8), (7,4,8), (7,3,8), (7,2,8), (7,2,8), (5,3,8), (4,2,8), (4,1,8), (4,0,8)
88 | ]
89 |
90 | fireball2 = [
91 | (5,8,4), (5,8,4), (4,8,3), (3,8,2), (2,8,1), (2,8,1), (3,8,0), (4,8,1), (5,8,2), (6,8,3), (7,8,4), (8,7,5), (8,6,5), (8,5,5),
92 | (8,5,5), (8,6,4), (8,5,3), (8,4,3), (8,3,3), (8,2,3), (8,2,3), (8,3,2), (8,2,1), (8,1,1), (8,0,1)
93 | ]
94 |
95 | fireball3 = [
96 | (5,8,4), (5,8,4), (4,8,3), (3,8,2), (2,8,1), (2,8,1), (3,8,0), (4,8,1), (5,8,2), (6,8,3), (7,8,4), (8,7,5), (8,6,5), (8,5,5),
97 | (8,5,5), (8,6,4), (8,5,3), (8,4,3), (8,3,3), (8,2,3), (8,2,3), (8,3,4), (8,2,5), (8,1,5), (8,0,5)
98 | ]
99 |
100 | fireballs = [
101 | fireball0,
102 | fireball1,
103 | fireball2,
104 | fireball3
105 | ]
106 |
107 |
108 | def LedDictionary():
109 | leds = {}
110 | for x in range(9):
111 | for y in range(9):
112 | for z in range(9):
113 | leds[x,y,z] = blue
114 | return leds
115 |
116 | def Shift(direction, leds):
117 | if direction == "left":
118 | #left side
119 | for x in range(0,7):
120 | for y in range(8):
121 | leds[x,y,8] = leds[x+1,y,8]
122 | #right side
123 | for y in range(8):
124 | for z in reversed(range(1,8)):
125 | leds[8,y,z] = leds[8,y,z-1]
126 | #fill in on right
127 | leds[8,0,0] = b5
128 | leds[8,1,0] = b5
129 | leds[8,2,0] = blue
130 | leds[8,3,0] = blue
131 | #top
132 | for x in range(8):
133 | for z in reversed(range(1,7)):
134 | leds[x,8,z] = leds[x,8,z-1]
135 | else:
136 | #left side
137 | for x in reversed(range(1,8)):
138 | for y in range(8):
139 | leds[x,y,8] = leds[x-1,y,8]
140 | #right side
141 | for y in range(8):
142 | for z in (range(0,7)):
143 | leds[8,y,z] = leds[8,y,z+1]
144 | #fill in on the left
145 | leds[0,0,8] = b5
146 | leds[0,1,8] = b5
147 | leds[0,2,8] = blue
148 | leds[0,3,8] = blue
149 | #top
150 | for x in reversed(range(1,8)):
151 | for z in range(8):
152 | leds[x,8,z] = leds[x-1,8,z]
153 |
154 | return leds
155 |
156 | def Shake(leds):
157 | left = Shift("left",leds.copy())
158 | right = Shift("right",leds.copy())
159 |
160 | for i in range(7):
161 | display.set_3d(left)
162 | time.sleep(random.randrange(1,3)/10)
163 | display.set_3d(leds)
164 | time.sleep(random.randrange(1,3)/10)
165 | display.set_3d(right)
166 | time.sleep(random.randrange(1,3)/10)
167 |
168 | display.set_3d(leds)
169 |
170 | def RandomBubbleColor():
171 | return hsv_colour(random.randrange(0,5)/100, random.randrange(50,100)/100, random.randrange(60,100)/100)
172 |
173 | def RandomFlowColor():
174 | return hsv_colour(random.randrange(0,5)/100, random.randrange(60,100)/100, random.randrange(80,100)/100)
175 |
176 | def Bubble(count, leds, sleep):
177 | for c in range(count):
178 | for x in range(3,6):
179 | for z in range(3,6):
180 | leds[x,8,z] = RandomBubbleColor()
181 | display.set_3d(leds)
182 | if sleep:
183 | time.sleep(random.randrange(5,7)/10)
184 |
185 | def Flow(leds, lava):
186 | copy = leds.copy()
187 | Bubble(1,copy,False)
188 | pairs = int(len(lava)/2)
189 | # flow them on
190 | for p in range(pairs+1):
191 | # draw 2 at a time
192 | for l in range(p*2):
193 | copy[lava[l]] = RandomFlowColor()
194 | Bubble(1,copy,False)
195 | display.set_3d(copy)
196 | time.sleep(.9)
197 | # refresh all colors a few times
198 | for i in range(6):
199 | for l in range(len(lava)):
200 | copy[lava[l]] = RandomFlowColor()
201 | Bubble(1,copy,False)
202 | display.set_3d(copy)
203 | time.sleep(.9)
204 | # flow them off
205 | for p in range(1,pairs+1):
206 | copy = leds.copy()
207 | for l in range(p*2,pairs*2):
208 | copy[lava[l]] = RandomFlowColor()
209 | Bubble(1,copy,False)
210 | display.set_3d(copy)
211 | time.sleep(.9)
212 |
213 | def Fireball(leds, fb):
214 | count = len(fb)
215 | for i in range(count):
216 | copy = leds.copy()
217 | Bubble(1,copy,False)
218 | if i > 0:
219 | copy[fb[i-1]] = hsv_colour(.05, .7, .7)
220 | copy[fb[i]] = hsv_colour(.05, 1, 1)
221 |
222 | display.set_3d(copy);
223 | time.sleep(.1)
224 |
225 | def main():
226 | leds = LedDictionary()
227 |
228 | for key, value in cone.items():
229 | leds[key] = value
230 |
231 | display.set_3d(leds)
232 |
233 | shakeEvery = 8
234 | fireballEvery = 2
235 | shake = 0
236 | fireball = 0
237 |
238 | while True:
239 | fireball += 1
240 | if fireball >= fireballEvery:
241 | fireball = 0
242 | fb = fireballs[random.randrange(0,len(fireballs))]
243 | Fireball(leds,fb)
244 |
245 | Bubble(random.randrange(5,8), leds, True)
246 | shake += 1
247 | if shake >= shakeEvery:
248 | shake = 0
249 | Shake(leds)
250 | flow = random.randrange(0,len(flows))
251 | Flow(leds, flows[flow])
252 |
253 | if __name__ == "__main__":
254 | main()
--------------------------------------------------------------------------------
/community-scripts/weather.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 |
3 | from foundry_api.standard_library import *
4 |
5 |
6 | # Predefined colors
7 |
8 | D = 0x5A5A5A # dark grey
9 | G = grey
10 | w = white
11 | r = red
12 | o = orange
13 | y = yellow
14 | g = green
15 | c = cyan
16 | b = blue
17 | m = magenta
18 | p = pink
19 | P = purple
20 |
21 | icon_sun = [ [0,o,0,o,o,0,o,0], [o,0,y,y,y,y,0,o], [0,y,y,y,y,y,y,0], [o,y,y,y,y,y,y,o], [o,y,y,y,y,y,y,o], [0,y,y,y,y,y,y,0], [o,0,y,y,y,y,0,o], [0,o,0,o,o,0,o,0] ]
22 | ##icon_clear_day = [ [0,0,y,0,0,y,0,0], [0,0,0,0,0,0,0,0], [y,0,y,y,y,y,0,y], [0,0,y,y,y,y,0,0], [0,0,y,y,y,y,0,0], [y,0,y,y,y,y,0,y], [0,0,0,0,0,0,0,0], [0,0,y,0,0,y,0,0] ]
23 | icon_moon = [ [0,0,G,w,w,w,0,0], [0,G,w,G,0,0,G,0], [G,w,w,0,0,0,0,0], [w,w,w,0,0,0,0,0], [w,w,w,0,0,0,0,0], [G,w,w,G,0,0,0,G], [0,G,w,w,w,w,G,0], [0,0,G,G,G,G,0,0] ]
24 | ##icon_clear_night = [ [0,0,b,0,0,b,0,0], [0,0,0,0,0,0,0,0], [b,0,b,b,b,b,0,b], [0,0,b,b,b,b,0,0], [0,0,b,b,b,b,0,0], [b,0,b,b,b,b,0,b], [0,0,0,0,0,0,0,0], [0,0,b,0,0,b,0,0] ]
25 | icon_clouds = [ [0,0,o,o,o,0,0,0], [0,o,y,y,y,o,0,0], [o,y,y,y,D,D,D,0], [o,y,y,D,G,G,G,D], [o,y,y,D,G,G,G,G], [o,o,D,G,G,G,G,G], [0,D,G,G,G,G,G,G], [D,G,G,G,G,G,G,G] ]
26 | icon_rain = [ [0,0,0,G,G,G,0,0], [0,0,G,G,G,G,G,0], [0,0,G,G,G,G,G,0], [G,G,G,G,G,G,G,G], [0,0,0,0,0,0,0,0], [b,0,0,b,0,0,b,0], [0,0,0,0,0,0,0,0], [0,b,0,0,b,0,0,b] ]
27 | icon_raindrop = [ [0,0,0,b,0,0,0,0], [0,0,0,b,0,0,0,0], [0,0,w,b,b,0,0,0], [0,w,w,b,b,b,0,0], [b,w,b,b,b,b,b,0], [b,b,b,b,b,b,b,0], [b,w,b,b,b,b,b,0], [0,b,b,b,b,b,0,0] ]
28 | icon_thunder = [ [0,0,o,y,y,o,0,0], [0,0,y,y,o,0,0,0], [0,o,y,o,0,0,0,0], [0,y,y,0,0,0,0,0], [o,y,y,y,y,y,o,0], [0,0,0,0,y,o,0,0], [0,0,0,y,o,0,0,0], [0,0,0,o,0,0,0,0] ]
29 | icon_snow = [ [0,0,0,w,0,0,0,0], [0,0,w,w,w,0,0,0], [0,0,0,w,0,0,w,0], [0,w,0,w,w,w,w,w], [w,w,w,w,w,0,w,0], [0,w,0,0,w,0,0,0], [0,0,0,w,w,w,0,0], [0,0,0,0,w,0,0,0] ]
30 | icon_question_mark = [ [0,r,r,r,r,r,r,0], [r,r,r,r,r,r,r,r], [r,r,0,0,0,0,r,r], [0,0,0,0,0,0,r,r], [0,0,0,r,r,r,r,0], [0,0,0,r,r,0,0,0], [0,0,0,0,0,0,0,0], [0,0,0,r,r,0,0,0] ]
31 |
32 |
33 | # import required modules
34 | import requests, json
35 |
36 | # Enter your API key here
37 | api_key = "" # Create an account and put the API key here
38 |
39 | # base_url variable to store url
40 | base_url = "http://api.openweathermap.org/data/2.5/weather?"
41 |
42 | # Give city id
43 | city_id = # Look up your city on openweathermap.org
44 |
45 | # complete_url variable to store
46 | # complete url address
47 | complete_url = base_url + "appid=" + api_key + "&id=" + city_id
48 |
49 | # get method of requests module
50 | # return response object
51 | response = requests.get(complete_url)
52 |
53 | # json method of response object
54 | # convert json format data into
55 | # python format data
56 | x = response.json()
57 |
58 | # Now x contains list of nested dictionaries
59 | # Check the value of "cod" key is equal to
60 | # "404", means city is found otherwise,
61 | # city is not found
62 | if x["cod"] != "404":
63 |
64 | # store the value of "main"
65 | # key in variable y
66 | y = x["main"]
67 |
68 | # store the value corresponding
69 | # to the "temp" key of y
70 | current_temperature = y["temp"]
71 | celsius_temperature = int(current_temperature - 273.15)
72 |
73 | # store the value corresponding
74 | # to the "pressure" key of y
75 | current_pressure = y["pressure"]
76 |
77 | # store the value corresponding
78 | # to the "humidity" key of y
79 | current_humidity = y["humidity"]
80 |
81 | # store the value of "weather"
82 | # key in variable w
83 | w = x["weather"]
84 |
85 | # store the value corresponding
86 | # to the "description" key at
87 | # the 0th index of z
88 | weather_description = w[0]["description"]
89 |
90 | # colour of the text based on temperature
91 | if celsius_temperature >= 40: colour = red
92 | elif celsius_temperature >= 30 and celsius_temperature < 40: colour = orange
93 | elif celsius_temperature >= 20 and celsius_temperature < 30: colour = yellow
94 | elif celsius_temperature >= 10 and celsius_temperature < 20: colour = green
95 | elif celsius_temperature >= 0 and celsius_temperature < 10: colour = cyan
96 | elif celsius_temperature >= -10 and celsius_temperature < 0: colour = blue
97 | else: colour = white
98 |
99 | z = colour # because i use 'z' in the number icons
100 |
101 | # number icons
102 |
103 | number_one = [ [0,0,0,0,0,0,0,0], [z,z,z,z,0,0,0,0], [0,0,0,z,0,0,0,0], [0,0,0,z,0,0,0,0], [0,0,0,z,0,0,0,0], [0,0,0,z,0,0,0,0], [0,0,0,z,0,0,0,0], [z,z,z,z,z,z,z,0] ]
104 | number_two = [ [0,0,0,0,0,0,0,0], [z,z,z,z,z,z,z,0], [0,0,0,0,0,0,z,0], [0,0,0,0,0,0,z,0], [z,z,z,z,z,z,z,0], [z,0,0,0,0,0,0,0], [z,0,0,0,0,0,0,0], [z,z,z,z,z,z,z,0] ]
105 | number_three = [ [0,0,0,0,0,0,0,0], [z,z,z,z,z,z,z,0], [0,0,0,0,0,0,z,0], [0,0,0,0,0,0,z,0], [z,z,z,z,z,z,z,0], [0,0,0,0,0,0,z,0], [0,0,0,0,0,0,z,0], [z,z,z,z,z,z,z,0] ]
106 | number_four = [ [0,0,0,0,0,0,0,0], [z,0,0,0,0,0,z,0], [z,0,0,0,0,0,z,0], [z,0,0,0,0,0,z,0], [z,z,z,z,z,z,z,0], [0,0,0,0,0,0,z,0], [0,0,0,0,0,0,z,0], [0,0,0,0,0,0,z,0] ]
107 | number_five = [ [0,0,0,0,0,0,0,0], [z,z,z,z,z,z,z,0], [z,0,0,0,0,0,0,0], [z,0,0,0,0,0,0,0], [z,z,z,z,z,z,z,0], [0,0,0,0,0,0,z,0], [0,0,0,0,0,0,z,0], [z,z,z,z,z,z,z,0] ]
108 | number_six = [ [0,0,0,0,0,0,0,0], [z,z,z,z,z,z,z,0], [z,0,0,0,0,0,0,0], [z,0,0,0,0,0,0,0], [z,z,z,z,z,z,z,0], [z,0,0,0,0,0,z,0], [z,0,0,0,0,0,z,0], [z,z,z,z,z,z,z,0] ]
109 | number_seven = [ [0,0,0,0,0,0,0,0], [z,z,z,z,z,z,z,0], [0,0,0,0,0,0,z,0], [0,0,0,0,0,0,z,0], [0,0,0,0,0,0,z,0], [0,0,0,0,0,0,z,0], [0,0,0,0,0,0,z,0], [0,0,0,0,0,0,z,0] ]
110 | number_eight = [ [0,0,0,0,0,0,0,0], [z,z,z,z,z,z,z,0], [z,0,0,0,0,0,z,0], [z,0,0,0,0,0,z,0], [z,z,z,z,z,z,z,0], [z,0,0,0,0,0,z,0], [z,0,0,0,0,0,z,0], [z,z,z,z,z,z,z,0] ]
111 | number_nine = [ [0,0,0,0,0,0,0,0], [z,z,z,z,z,z,z,0], [z,0,0,0,0,0,z,0], [z,0,0,0,0,0,z,0], [z,z,z,z,z,z,z,0], [0,0,0,0,0,0,z,0], [0,0,0,0,0,0,z,0], [z,z,z,z,z,z,z,0] ]
112 | number_zero = [ [0,0,0,0,0,0,0,0], [z,z,z,z,z,z,z,0], [z,0,0,0,0,0,z,0], [z,0,0,0,0,0,z,0], [z,0,0,0,0,0,z,0], [z,0,0,0,0,0,z,0], [z,0,0,0,0,0,z,0], [z,z,z,z,z,z,z,0] ]
113 |
114 | # text based on celsius temperature
115 | strtemp = str(celsius_temperature)
116 | firstnumber = strtemp[:1]
117 | secondnumber = strtemp[1:2]
118 |
119 | if firstnumber == "1": left_number = number_one
120 | elif firstnumber == "2": left_number = number_two
121 | elif firstnumber == "3": left_number = number_three
122 | elif firstnumber == "4": left_number = number_four
123 | elif firstnumber == "5": left_number = number_five
124 | elif firstnumber == "6": left_number = number_six
125 | elif firstnumber == "7": left_number = number_seven
126 | elif firstnumber == "8": left_number = number_eight
127 | elif firstnumber == "9": left_number = number_nine
128 | elif firstnumber == "0": left_number = number_zero
129 | else: left_number = icon_question_mark
130 |
131 | if secondnumber == "1": right_number = number_one
132 | elif secondnumber == "2": right_number = number_two
133 | elif secondnumber == "3": right_number = number_three
134 | elif secondnumber == "4": right_number = number_four
135 | elif secondnumber == "5": right_number = number_five
136 | elif secondnumber == "6": right_number = number_six
137 | elif secondnumber == "7": right_number = number_seven
138 | elif secondnumber == "8": right_number = number_eight
139 | elif secondnumber == "9": right_number = number_nine
140 | elif secondnumber == "0": right_number = number_zero
141 | else: right_number = icon_question_mark
142 |
143 |
144 | # weather icon based on openweathermap icon
145 |
146 | weather_icon = w[0]["icon"]
147 |
148 | if weather_icon == "01d": icon = icon_sun
149 | elif weather_icon == "01n": icon = icon_moon
150 | elif weather_icon == "02d": icon = icon_clouds
151 | elif weather_icon == "02n": icon = icon_clouds
152 | elif weather_icon == "03d": icon = icon_clouds
153 | elif weather_icon == "03n": icon = icon_clouds
154 | elif weather_icon == "04d": icon = icon_clouds
155 | elif weather_icon == "04n": icon = icon_clouds
156 | elif weather_icon == "10d": icon = icon_rain
157 | elif weather_icon == "10n": icon = icon_rain
158 | elif weather_icon == "09d": icon = icon_raindrop
159 | elif weather_icon == "09n": icon = icon_raindrop
160 |
161 | else: icon = icon_question_mark
162 |
163 | # first set lumicube display to black
164 | display.set_all(black)
165 | # lumicube display
166 | display.set_panel("top", icon)
167 | display.set_panel("left", left_number)
168 | display.set_panel("right", right_number)
169 |
170 | else:
171 | print(" Wrong city ID ")
172 |
--------------------------------------------------------------------------------
/community-scripts/yagol.py:
--------------------------------------------------------------------------------
1 | # Yet Another Game of Life variant.
2 | # Conway's Game of Life (https://en.wikipedia.org/wiki/Conway's_Game_of_Life).
3 | # modified to show new cells as green, old cells as blue, new dead cell as red
4 | # will run for as long as 5 minutes, automatically checking for stopped or loop states, when it will pause for a few seconds then restart
5 | # Author : Kevin Haney
6 | # Date : 10/9/2022
7 | # Version : 1.0
8 |
9 | import random
10 |
11 | fresh = green
12 | old = blue
13 | dead = red
14 |
15 | while True:
16 | max_turns = 300
17 | starting_live_ratio = 0.4
18 | # colour = random_colour()
19 | colour = fresh
20 | num_turns = 0
21 | alive_cells = []
22 | for x in range(0,16):
23 | for y in range(0,16):
24 | if x < 8 or y < 8:
25 | if random.random() < starting_live_ratio:
26 | alive_cells.append((x,y))
27 |
28 | def num_alive_neighbours(x, y):
29 | num_neighbours = 0
30 | for x2 in [x-1, x, x+1]:
31 | for y2 in [y-1, y, y+1]:
32 | if (x2, y2) != (x, y):
33 | neighbour = (x2, y2)
34 | # Account for 3D nature of panels
35 | if x2 == 8 and y2 >= 8:
36 | neighbour = (y2, 7)
37 | elif y2 == 8 and x2 >= 8:
38 | neighbour = (7, x2)
39 | if neighbour in alive_cells:
40 | num_neighbours += 1
41 | return num_neighbours
42 |
43 | leds = {}
44 | last_cells = [] # keep track of last state for when things stop changing
45 | prev_cells = [] # keep track of two states ago go catch when we're stuck in a repeating loop
46 | loop_waits = 0
47 |
48 | while (num_turns < max_turns):
49 | next_cells = []
50 | prev_cells = last_cells
51 | last_cells = alive_cells
52 | prev_leds = leds
53 | leds = {}
54 | for x in range(0,16):
55 | for y in range(0,16):
56 | if x < 8 or y < 8:
57 | alive = (x, y) in alive_cells
58 | neighbours = num_alive_neighbours(x, y)
59 | # Remains alive
60 | if alive and (neighbours == 2
61 | or neighbours == 3):
62 | next_cells.append((x, y))
63 | leds[x, y] = old #colour
64 | # Becomes alive
65 | elif not alive and neighbours == 3:
66 | next_cells.append((x, y))
67 | leds[x, y] = fresh #colour
68 | # Else dead
69 | else:
70 | if alive:
71 | k = x, y
72 | if prev_leds.get((x,y)) == old:
73 | leds[x,y] = dead
74 | else:
75 | leds[x,y] = orange
76 | else:
77 | leds[x, y] = black
78 | display.set_leds(leds)
79 | alive_cells = next_cells
80 | if (alive_cells==last_cells):
81 | time.sleep(5.0)
82 | break
83 | if (alive_cells==prev_cells):
84 | loop_waits += 1
85 | if (loop_waits > 20):
86 | loop_waits = 0
87 | break
88 | time.sleep(1.0)
89 | num_turns += 1
90 |
91 |
92 |
--------------------------------------------------------------------------------
/examples/autumn_scene.py:
--------------------------------------------------------------------------------
1 | # Autumn animation (tree, moon, and falling leaves).
2 |
3 | y = 0xffc000
4 | o = 0xff9000
5 | r = hsv_colour(0.04, 1, 1)
6 | b = hsv_colour(0.04, 0.7, 0.3)
7 | w = hsv_colour(0, 0, 1)
8 | top = [
9 | [0,w,0,0,0,0,0,0],
10 | [w,0,0,0,0,0,0,0],
11 | [w,0,0,0,0,0,o,r],
12 | [0,w,0,0,o,o,o,o],
13 | [0,0,0,o,o,r,o,o],
14 | [0,0,0,o,o,o,o,o],
15 | [0,0,o,o,r,o,y,o],
16 | [0,0,o,o,o,o,o,o],
17 | ]
18 | left = [
19 | [0,0,o,o,y,o,r,o],
20 | [0,0,0,r,o,o,o,o],
21 | [0,0,0,0,0,o,o,y],
22 | [0,0,0,0,0,0,0,b],
23 | [0,0,0,0,0,0,0,b],
24 | [0,0,0,0,0,0,0,b],
25 | [0,0,0,0,0,0,0,b],
26 | [0,0,0,0,0,0,b,b],
27 | ]
28 | right = [
29 | [o,r,o,y,o,o,0,0],
30 | [o,o,o,o,r,0,0,0],
31 | [y,o,o,0,0,0,0,0],
32 | [b,0,0,0,0,0,0,0],
33 | [b,0,0,0,0,0,0,0],
34 | [b,0,0,0,0,0,0,0],
35 | [b,0,0,0,0,0,0,0],
36 | [b,b,0,0,0,0,0,0],
37 | ]
38 | display.set_panel('left', left)
39 | display.set_panel('right', right)
40 | display.set_panel('top', top)
41 |
42 | # Animation of leaves falling to the floor
43 | leaves = {}
44 | while True:
45 | # 30% chance of creating a new falling leaf
46 | if random.random() < 0.3:
47 | # Start at a random point on the tree
48 | y = 7
49 | x = random.randint(2,6)
50 | if (random.random() < 0.5):
51 | x += 8
52 | leaves[(x,y)] = hsv_colour(0.04 + 0.12*random.random(), 1, 1)
53 |
54 | # Move all the falling leaves
55 | leds = {}
56 | new_leaves = {}
57 | for (x,y), colour in leaves.items():
58 | if y > 0:
59 | # If the leaf has moved, set the LED back to the original image
60 | if x < 8:
61 | leds[(x,y)] = left[7-y][x]
62 | else:
63 | leds[(x,y)] = right[7-y][x-8]
64 | # Move the leaf side to side as it falls
65 | if y % 2 == 0:
66 | x = x + 1
67 | else:
68 | x = x - 1
69 | # Move the leaf down
70 | new_leaves[(x,y-1)] = colour
71 |
72 | leaves = new_leaves
73 | leds.update(leaves)
74 | display.set_leds(leds)
75 | time.sleep(0.2)
76 |
--------------------------------------------------------------------------------
/examples/binary_clock.py:
--------------------------------------------------------------------------------
1 | # Display a binary clock (https://en.wikipedia.org/wiki/Binary_clock).
2 |
3 | def draw_column(decimal_digit, x, colour):
4 | # Convert digit to four digit binary
5 | binary = list(format(decimal_digit, '04b'))
6 | # Start at the bottom with the least significant digit
7 | binary.reverse()
8 | leds = {}
9 | for i, value in enumerate(binary):
10 | # Set all the leds in a 2x2 square
11 | pixel = colour if value == '1' else black
12 | y = i * 2
13 | leds[x, y ] = pixel
14 | leds[x, y+1] = pixel
15 | leds[x+1, y ] = pixel
16 | leds[x+1, y+1] = pixel
17 | display.set_leds(leds)
18 |
19 | import datetime
20 | display.set_all(black)
21 | while True:
22 | time_now = datetime.datetime.now()
23 | seconds = format(time_now.second, '02')
24 | minutes = format(time_now.minute, '02')
25 | hours = format(time_now.hour, '02')
26 | draw_column(int(hours[0]), 0, pink)
27 | draw_column(int(hours[1]), 2, pink)
28 | draw_column(int(minutes[0]), 4, purple)
29 | draw_column(int(minutes[1]), 6, purple)
30 | draw_column(int(seconds[0]), 8, cyan)
31 | draw_column(int(seconds[1]), 10, cyan)
32 | time.sleep(1/10)
33 |
--------------------------------------------------------------------------------
/examples/button.py:
--------------------------------------------------------------------------------
1 | # If the top button has been pressed an even number of times set the cube red,
2 | # otherwise set it blue.
3 |
4 | while True:
5 | if buttons.top_pressed_count % 2 == 0:
6 | display.set_all(red)
7 | else:
8 | display.set_all(blue)
9 | time.sleep(1 / 20)
10 |
--------------------------------------------------------------------------------
/examples/chiptune.py:
--------------------------------------------------------------------------------
1 | # Play a randomly generated tune.
2 |
3 | while True:
4 | # Play a rising piece
5 | for frequency in range(500, 2000, 100):
6 | speaker.tone(frequency, 0.01)
7 | # Play a beat and then another beat
8 | time.sleep(0.02)
9 | speaker.tone(500, 0.1, 0.1, function=white_noise)
10 | time.sleep(0.05)
11 | speaker.tone(500, 0.1, 0.1, function=white_noise)
12 | # Play 3 different tones
13 | speaker.tone(500 + 500 * random.random(), 0.1)
14 | speaker.tone(500 + 500 * random.random(), 0.1)
15 | speaker.tone(500 + 500 * random.random(), 0.1)
16 | # Play another rising piece
17 | for frequency in range(200, 1000, 10):
18 | speaker.tone(frequency, 0.003)
19 |
--------------------------------------------------------------------------------
/examples/conways_game_of_life.py:
--------------------------------------------------------------------------------
1 | # Run Conway's Game of Life (https://en.wikipedia.org/wiki/Conway's_Game_of_Life).
2 |
3 | import random
4 | max_turns = 50
5 | starting_live_ratio = 0.4
6 | colour = random_colour()
7 | num_turns = 0
8 | alive_cells = []
9 | for x in range(0,16):
10 | for y in range(0,16):
11 | if x < 8 or y < 8:
12 | if random.random() < starting_live_ratio:
13 | alive_cells.append((x,y))
14 |
15 | def num_alive_neighbours(x, y):
16 | num_neighbours = 0
17 | for x2 in [x-1, x, x+1]:
18 | for y2 in [y-1, y, y+1]:
19 | if (x2, y2) != (x, y):
20 | neighbour = (x2, y2)
21 | # Account for 3D nature of panels
22 | if x2 == 8 and y2 >= 8:
23 | neighbour = (y2, 7)
24 | elif y2 == 8 and x2 >= 8:
25 | neighbour = (7, x2)
26 | if neighbour in alive_cells:
27 | num_neighbours += 1
28 | return num_neighbours
29 |
30 | while (num_turns < max_turns):
31 | next_cells = []
32 | leds = {}
33 | for x in range(0,16):
34 | for y in range(0,16):
35 | if x < 8 or y < 8:
36 | alive = (x, y) in alive_cells
37 | neighbours = num_alive_neighbours(x, y)
38 | # Remains alive
39 | if alive and (neighbours == 2
40 | or neighbours == 3):
41 | next_cells.append((x, y))
42 | leds[x, y] = colour
43 | # Becomes alive
44 | elif not alive and neighbours == 3:
45 | next_cells.append((x, y))
46 | leds[x, y] = colour
47 | # Else dead
48 | else:
49 | leds[x, y] = black
50 | alive_cells = next_cells
51 | display.set_leds(leds)
52 | time.sleep(0.3)
53 | num_turns += 1
54 |
--------------------------------------------------------------------------------
/examples/equaliser.py:
--------------------------------------------------------------------------------
1 | # Records microphone audio, processes it into frequency buckets,
2 | # and then displays the levels of each bucket on the LEDs.
3 |
4 | num_buckets = 8
5 |
6 | display.set_all(black)
7 | pixels_per_bucket = max(1, 16/num_buckets)
8 | buckets = [0 for i in range(num_buckets)]
9 | last_time = time.time()
10 | max_magnitude = 1 # Dynamically adjust the max as we go along
11 |
12 | # Start recording audio samples
13 | microphone.start_recording_for_frequency_analysis()
14 |
15 | while 1:
16 | # Get new bucket levels, converting the audio samples into 8 frequency buckets between 0Hz and 1000Hz
17 | new_buckets = list(microphone.get_frequency_buckets(num_buckets, 0, 800).values())
18 |
19 | # Process data, keeping track of max level
20 | for bi, magnitude in enumerate(new_buckets):
21 | buckets[bi] = max(buckets[bi], magnitude)
22 | max_magnitude = max(max_magnitude, magnitude)
23 |
24 | # Every 10th of a second update the LEDs
25 | # Note: This is done separately from the data processing because we don't want the LEDs to change too fast
26 | if time.time() - last_time > 1/10:
27 | last_time = time.time()
28 | colours = {}
29 | for y in range(0,8):
30 | for x in range(0,16):
31 | bi = int(x/pixels_per_bucket)
32 | if y/8.0 <= buckets[bi] / max_magnitude or y == 0:
33 | colours[(x,y)] = hsv_colour(1 - bi/num_buckets, 1, 1)
34 | else:
35 | colours[(x,y)] = 0
36 | display.set_leds(colours)
37 | # Reset the bucket levels
38 | buckets = [0 for i in range(num_buckets)]
39 | # Over time gradually decay the max
40 | max_magnitude *= 0.99
41 |
--------------------------------------------------------------------------------
/examples/land_grab.py:
--------------------------------------------------------------------------------
1 | # Several automated players mark neighbouring LEDs with their colour,
2 | # until they get stuck. Can you make the players more intelligent?
3 |
4 | def adjacent_positions(x, y, include_diagonal=False):
5 | positions = [(x-1, y), (x+1, y), (x, y-1), (x, y+1)]
6 | if include_diagonal:
7 | positions = positions + [(x-1, y-1), (x-1, y+1), (x+1, y-1), (x+1, y+1)]
8 | mapped_positions = []
9 | for (x,y) in positions:
10 | # Account for the 3D nature of the panels
11 | if x > 7 and y > 7:
12 | if x > 7 and y == 8:
13 | mapped_positions.append((7, x))
14 | elif x == 8 and y > 7:
15 | mapped_positions.append((y, 7))
16 | # Make sure x and y are valid positions
17 | elif (x < 16 and x >= 0) and (y < 16 and y >= 0):
18 | mapped_positions.append((x,y))
19 | return mapped_positions
20 |
21 | class Player:
22 | def __init__(self, pos, colour):
23 | self.pos = pos
24 | self.colour = colour
25 |
26 | def move(self, leds):
27 | (x,y) = self.pos
28 | # Get a list of all valid positions to move to
29 | valid_positions = []
30 | for neighbour in adjacent_positions(x, y):
31 | if neighbour not in leds:
32 | valid_positions.append(neighbour)
33 | # If there are no valid positions the player is finished, so return False
34 | if len(valid_positions) == 0:
35 | return False
36 | # Choose a random valid position
37 | pos = valid_positions[random.randrange(len(valid_positions))]
38 | (x,y) = pos
39 | self.pos = pos
40 | leds[x, y] = self.colour
41 | display.set_led(x, y, self.colour)
42 | return True
43 |
44 | num_players = 4
45 | while True:
46 | # Start a new game
47 | players = []
48 | leds = {}
49 | display.set_all(black)
50 | # Generate all the players with random starting positions and colours
51 | for i in range(0, num_players):
52 | (x,y) = (random.randrange(16), random.randrange(16))
53 | if x >= 8 and y >= 8:
54 | y -= 8
55 | players.append(Player((x,y), random_colour()))
56 | # Keep allowing players to move until there are no players left
57 | while len(players) > 0:
58 | died = []
59 | # Move all the players
60 | for player in players:
61 | if player.move(leds) == False:
62 | died.append(player)
63 | # Remove finished players
64 | for player in died:
65 | players.remove(player)
66 | time.sleep(0.1)
67 | # Wait before starting a new game
68 | time.sleep(1)
69 |
--------------------------------------------------------------------------------
/examples/lava_lamp.py:
--------------------------------------------------------------------------------
1 | # Generate a lava lamp effect using OpenSimplex noise.
2 |
3 | def lava_colour(x, y, z, t):
4 | scale = 0.10
5 | speed = 0.05
6 | hue = noise_4d(scale * x, scale * y, scale * z, speed * t)
7 | return hsv_colour(hue, 1, 1)
8 |
9 | def paint_cube(t):
10 | colours = {}
11 | for x in range(9):
12 | for y in range(9):
13 | for z in range(9):
14 | if x == 8 or y == 8 or z == 8:
15 | colour = lava_colour(x, y, z, t)
16 | colours[x,y,z] = colour
17 | display.set_3d(colours)
18 |
19 | t = 0
20 | while True:
21 | paint_cube(t)
22 | time.sleep(1/30)
23 | t += 1
24 |
--------------------------------------------------------------------------------
/examples/pi_status_screen.py:
--------------------------------------------------------------------------------
1 | # Write some statistics about the Pi to the the screen.
2 |
3 | def to_text(value):
4 | return str("{:.1f}".format(value))
5 |
6 | screen.draw_rectangle(0, 0, 320, 240, black)
7 | height = 36
8 | while True:
9 | text = ("IP address: " + pi.ip_address() + "\n"
10 | + "CPU temp : " + to_text(pi.cpu_temp()) + "\n"
11 | + "CPU usage : " + to_text(pi.cpu_percent()) + "\n"
12 | + "RAM usage : " + to_text(pi.ram_percent_used()) +"\n"
13 | + "Disk usage: " + to_text(pi.disk_percent()) + "\n")
14 | screen.write_text(10, 18, text, 1, white, black)
15 | time.sleep(5)
16 |
--------------------------------------------------------------------------------
/examples/rain.py:
--------------------------------------------------------------------------------
1 | # Rain animation.
2 |
3 | display.set_all(black)
4 | rows = [[0 for x in range(16)] for y in range(8)]
5 | while True:
6 | # Shift all rows down
7 | rows.pop(0)
8 | # Create a new row
9 | top_row = rows[-1]
10 | new_top_row = []
11 | for prev_pixel in top_row:
12 | new_pixel = 0
13 | # If the previous pixel was the start of a drop, create the
14 | # droplet tail by reducing the brightness for the new pixel
15 | if prev_pixel > 0:
16 | new_pixel = prev_pixel - 0.4
17 | new_pixel = max(new_pixel, 0.0)
18 | # Sometimes generate a new droplet
19 | elif random.random() < 0.1:
20 | new_pixel = 1
21 | new_top_row.append(new_pixel)
22 | rows.append(new_top_row)
23 | # Convert the brightness values to LED colours
24 | leds = {}
25 | for y in range(0,8):
26 | for x in range(0,16):
27 | leds[(x, y)] = hsv_colour(0.6, 1, rows[y][x])
28 | display.set_leds(leds)
29 | time.sleep(1/15)
30 |
--------------------------------------------------------------------------------
/examples/rainbow.py:
--------------------------------------------------------------------------------
1 | # Continually change the cube's colour.
2 |
3 | hue = 0
4 | while True:
5 | hue += 0.01
6 | if hue > 1:
7 | hue = 0
8 | display.set_all(hsv_colour(hue, 1, 1))
9 | time.sleep(1 / 30)
10 |
--------------------------------------------------------------------------------
/examples/ripples.py:
--------------------------------------------------------------------------------
1 | # Animate ripples across the cube's LEDs.
2 |
3 | class Ripple:
4 | def __init__(self, start_x, start_y, start_z, hsv, velocity=10, wavefront_size=1.3):
5 | self.start_x = start_x
6 | self.start_y = start_y
7 | self.start_z = start_z
8 | self.hsv = hsv
9 | self.velocity = velocity
10 | self.wavefront_size = wavefront_size
11 | self.start_time = time.time()
12 | self.radius = 0
13 |
14 | def draw(self, leds):
15 | self.radius = self.velocity * (time.time() - self.start_time)
16 | for x in range(0,9):
17 | for y in range(0,9):
18 | for z in range(0,9):
19 | if x == 8 or y == 8 or z == 8:
20 | # Calculate how far this pixel is from the ripple line
21 | distance = (abs(self.start_x - x) ** 2 + abs(self.start_y - y) ** 2 + abs(self.start_z - z) ** 2) ** (1/2)
22 | dist_diff = abs(self.radius - distance)
23 | if (dist_diff < self.wavefront_size):
24 | # Set the brightness based on how close the pixel is to the ripple line
25 | pos_brightness = math.cos(math.pi*dist_diff/(2*self.wavefront_size))
26 | hue, sat, value = self.hsv
27 | brightness = value*pos_brightness
28 | if brightness > 0.05: # Set any low brightness LEDs black
29 | leds[(x,y,z)] = hsv_colour(hue, sat, brightness)
30 |
31 | def finished(self):
32 | if (self.radius > 16):
33 | return True
34 | return False
35 |
36 | display.set_all(black)
37 | ripples = []
38 | count = 0
39 |
40 | while True:
41 |
42 | # Every 30 iterations create a new ripple
43 | if count % 30 == 0:
44 | # Pick a random 3D coordinate to start from
45 | (x, y, z) = [random.randint(0,7) for i in range(0,3)]
46 | # Make it start on one of the faces
47 | panel = random.randint(0,3)
48 | if panel == 0:
49 | z = 0
50 | elif panel == 1:
51 | x = 8
52 | else:
53 | y = 8
54 | hsv = (random.random(), 1, 1)
55 | ripples.append(Ripple(x, y, z, hsv))
56 |
57 | # Initialise the LEDs to black
58 | leds = {}
59 | for x in range(0,9):
60 | for y in range(0,9):
61 | for z in range(0,9):
62 | if x == 8 or y == 8 or z == 8:
63 | leds[(x,y,z)] = 0;
64 |
65 | # Draw all the ripples
66 | for r in ripples:
67 | r.draw(leds)
68 |
69 | # Remove any ripples that have finished
70 | to_remove = []
71 | for r in ripples:
72 | if r.finished():
73 | to_remove.append(r)
74 | for r in to_remove:
75 | ripples.remove(r)
76 |
77 | display.set_3d(leds, True)
78 | time.sleep(1/20)
79 | count += 1
80 |
--------------------------------------------------------------------------------
/examples/scrolling_clock.py:
--------------------------------------------------------------------------------
1 | # Every 20 seconds scroll the time across the cube.
2 |
3 | import datetime
4 | display.set_all(black)
5 | while True:
6 | time_text = datetime.datetime.now().strftime("%H:%M")
7 | display.scroll_text(time_text, orange)
8 | time.sleep(20)
9 |
--------------------------------------------------------------------------------
/examples/tapping_ripples.py:
--------------------------------------------------------------------------------
1 | # Animate ripples across the cube's LEDs, which respond
2 | # to tapping the cube (or accelerating it in some way).
3 | # Note: Requires the advanced kit IMU add-on.
4 |
5 | import threading
6 |
7 | class Ripple:
8 | def __init__(self, start_x, start_y, start_z, hsv, velocity=10, wavefront_size=1.3):
9 | self.start_x = start_x
10 | self.start_y = start_y
11 | self.start_z = start_z
12 | self.hsv = hsv
13 | self.velocity = velocity
14 | self.wavefront_size = wavefront_size
15 | self.start_time = time.time()
16 | self.radius = 0
17 |
18 | def draw(self, leds):
19 | self.radius = self.velocity * (time.time() - self.start_time)
20 | for x in range(0,9):
21 | for y in range(0,9):
22 | for z in range(0,9):
23 | if x == 8 or y == 8 or z == 8:
24 | # Calculate how far this pixel is from the ripple line
25 | distance = (abs(self.start_x - x) ** 2 + abs(self.start_y - y) ** 2 + abs(self.start_z - z) ** 2) ** (1/2)
26 | dist_diff = abs(self.radius - distance)
27 | if (dist_diff < self.wavefront_size):
28 | # Set the brightness based on how close the pixel is to the ripple line
29 | pos_brightness = math.cos(math.pi*dist_diff/(2*self.wavefront_size))
30 | hue, sat, value = self.hsv
31 | brightness = value*pos_brightness
32 | if brightness > 0.05: # Set any low brightness LEDs black
33 | leds[(x,y,z)] = hsv_colour(hue, sat, brightness)
34 |
35 | def finished(self):
36 | if (self.radius > 16):
37 | return True
38 | return False
39 |
40 | # Check the acceleration in a separate thread to ensure we sample it at regular intervals
41 | acc_direction = None
42 | wait_count = 0
43 | def worker():
44 | global acc_direction, wait_count
45 | avg_acc_x, avg_acc_y, avg_acc_z = 0, 0, 0
46 | while True:
47 | acc_x, acc_y, acc_z = (abs(imu.acceleration_x), abs(imu.acceleration_y), abs(imu.acceleration_z))
48 | avg_acc_x = avg_acc_x * 0.95 + acc_x * 0.05
49 | avg_acc_y = avg_acc_y * 0.95 + acc_y * 0.05
50 | avg_acc_z = avg_acc_z * 0.95 + acc_z * 0.05
51 | acc_x -= avg_acc_x
52 | acc_y -= avg_acc_y
53 | acc_z -= avg_acc_z
54 | max_acc = max(acc_x, acc_y, acc_z)
55 | if wait_count > 0:
56 | wait_count -= 1
57 | if max_acc > 0.15 and wait_count == 0:
58 | wait_count = 5
59 | if max_acc == acc_x:
60 | acc_direction = "x"
61 | elif max_acc == acc_y:
62 | acc_direction = "y"
63 | else:
64 | acc_direction = "z"
65 | time.sleep(0.02)
66 | threading.Thread(target=worker, daemon=True).start()
67 |
68 | display.set_all(black)
69 | ripples = []
70 |
71 | while True:
72 | # If an acceleration is detected create new ripple
73 | if acc_direction != None:
74 | # Make it start in the middle of the accelerated face
75 | if acc_direction == "x":
76 | x, y, z = 8, 4, 4
77 | elif acc_direction == "y":
78 | x, y, z = 4, 8, 4
79 | else:
80 | x, y, z = 4, 4, 0
81 | hsv = (random.random(), 1, 1)
82 | ripples.append(Ripple(x, y, z, hsv))
83 | # Reset the acceleration direction
84 | acc_direction = None
85 |
86 | # Initialise the LEDs to black
87 | leds = {}
88 | for x in range(0,9):
89 | for y in range(0,9):
90 | for z in range(0,9):
91 | if x == 8 or y == 8 or z == 8:
92 | leds[(x,y,z)] = 0;
93 |
94 | # Draw all the ripples
95 | for r in ripples:
96 | r.draw(leds)
97 |
98 | # Remove any ripples that have finished
99 | to_remove = []
100 | for r in ripples:
101 | if r.finished():
102 | to_remove.append(r)
103 | for r in to_remove:
104 | ripples.remove(r)
105 |
106 | display.set_3d(leds, True)
107 | time.sleep(1/20)
108 |
--------------------------------------------------------------------------------
/examples/voice_recognition.py:
--------------------------------------------------------------------------------
1 | # Say "Hey Mycroft" to wake up the voice recognition,
2 | # and then say a sentence. Your sentence will then be
3 | # repeated back to you.
4 |
5 | microphone.start_voice_recognition()
6 | while (True):
7 | # Wait up to 1000 seconds for some speech
8 | sentence = microphone.wait_for_sentence(1000.0)
9 | if sentence:
10 | # Convert text back to speech
11 | speaker.say('You said ' + sentence)
12 |
--------------------------------------------------------------------------------
/examples/water_level.py:
--------------------------------------------------------------------------------
1 | # Pick up the cube and rotate it - this project makes it look like there is water inside.
2 | # Note: Requires the advanced kit IMU add-on.
3 |
4 | def dot_product(vector1, vector2):
5 | (x1,y1,z1) = vector1
6 | (x2,y2,z2) = vector2
7 | return (x1 * x2) + (y1 * y2) + (z1 * z2)
8 |
9 | def led_below_water_level(x, y, z, gravity):
10 | mid_point_height = dot_product((4.5, 4.5, 4.5), gravity)
11 | led_bottom = (x, y, z)
12 | led_top = (x+1, y+1, z+1)
13 | max_height = max(dot_product(led_bottom, gravity), \
14 | dot_product(led_top, gravity))
15 | return (max_height < mid_point_height)
16 |
17 | while True:
18 | gravity = (imu.gravity_x, imu.gravity_y, imu.gravity_z)
19 | leds = {}
20 | for x in range(9):
21 | for y in range(9):
22 | for z in range(9):
23 | if x == 8 or y == 8 or z == 8:
24 | if led_below_water_level(x,y,z, gravity):
25 | leds[x, y, z] = cyan
26 | else:
27 | leds[x, y, z] = black
28 | time.sleep(0.05)
29 | display.set_3d(leds)
30 |
--------------------------------------------------------------------------------
/examples/windmill.py:
--------------------------------------------------------------------------------
1 | # Blow on the back of the cube to make the windmill animation turn.
2 |
3 | import threading
4 |
5 | humidity = 100
6 |
7 | def worker():
8 | global humidity
9 | while True:
10 | humidity = env_sensor.humidity
11 | time.sleep(0.05)
12 |
13 | threading.Thread(target=worker, daemon=True).start()
14 |
15 | def windmill_shader(x, y, blade_angle):
16 | theta = 360 * (0.5 + math.atan2(y, x) / (2 * math.pi))
17 | modulo = (3 * theta) % 360
18 | difference = abs(modulo - blade_angle)
19 | return hsv_colour(0, 0, 500 / difference ** 2 if difference > 0 else 1)
20 |
21 | decay = 0.07
22 | sample_period = 0.025
23 | threshold = 0.15
24 | previous_sample = 100
25 | next_sample_time = time.monotonic() + sample_period
26 | accumulator = 0
27 | blade_angle = 0
28 | rotational_velocity = 0
29 | max_velocity = 70
30 | while True:
31 | now = time.monotonic()
32 | if time.monotonic() > next_sample_time:
33 | sample = humidity
34 | if sample > previous_sample + threshold:
35 | accumulator = 3
36 | previous_sample = sample
37 | next_sample_time = now + sample_period
38 | rotational_velocity = max_velocity * min(accumulator, 1)
39 | blade_angle = (blade_angle + rotational_velocity) % 360
40 | canvas = {}
41 | for x in range(9):
42 | for y in range(9):
43 | for z in range(9):
44 | if x == 8 or y == 8 or z == 8:
45 | projected_x = (x - z)
46 | projected_y = (2 * y - x - z) / (3 ** 0.5)
47 | canvas[(x,y,z)] = windmill_shader(projected_x, projected_y, blade_angle)
48 | display.set_3d(canvas, True)
49 | accumulator *= (1 - decay)
50 | time.sleep(1 / 25)
51 |
--------------------------------------------------------------------------------
/official-documentation/api.txt:
--------------------------------------------------------------------------------
1 | ===================================
2 | Pre-defined variables and functions
3 | ===================================
4 |
5 | black = 0x000000
6 | grey = 0x808080
7 | white = 0xFFFFFF
8 | red = 0xFF0000
9 | orange = 0xFF8C00
10 | yellow = 0xFFFF00
11 | green = 0x00FF00
12 | cyan = 0x00FFFF
13 | blue = 0x0000FF
14 | magenta = 0xFF00FF
15 | pink = 0xFF007F
16 | purple = 0x800080
17 |
18 | hsv_colour(hue, saturation, value)
19 | random_colour()
20 | noise_2d(x, y)
21 | noise_3d(x, y, z)
22 | noise_4d(x, y, z, w)
23 | run_async(task, *args, **kwargs)
24 | LumiCube(ip_address)
25 |
26 |
27 | ========================
28 | Default imported modules
29 | ========================
30 |
31 | import time
32 | import math
33 | import random
34 |
35 |
36 | ===========
37 | LED display
38 | ===========
39 |
40 | display.brightness # 0 - 100
41 |
42 | display.set_all(colour)
43 | display.set_led(x, y, colour)
44 | display.set_leds(x_y_to_colour_dict) # { (x1, y1): colour1, (x2, y2): colour2, ... }
45 | display.set_panel(panel, 2d_colour_list) # 2d_colour_list is a list of 8 lists, each containing 8 colours
46 | display.scroll_text(text, colour=white, background_colour=black, speed=1)
47 | display.set_3d(x_y_z_to_colour_dict) # { (x1, y1, z1): colour1, (x2, y2, z2): colour2, ... }
48 |
49 | 3D coordinate system (looking at the 3 LED panels, the origin is at the back corner of the cube):
50 |
51 | y
52 | |
53 | |
54 | _-` `-_
55 | z _-` `-_ x
56 |
57 | - The left panel is described by: x, y when z = 8
58 | - The right panel is described by: y, z when x = 8
59 | - The top panel is described by: x, z when y = 8
60 | - Coordinates outside this range will be ignored
61 |
62 |
63 | ==========
64 | Microphone
65 | ==========
66 |
67 | microphone.enable # 0 or 1
68 |
69 | microphone.start_recording(file)
70 | microphone.stop_recording()
71 | microphone.start_voice_recognition()
72 | microphone.wait_for_sentence(timeout) # Returns the sentence spoken as a string
73 | microphone.stop_voice_recognition()
74 | microphone.start_recording_for_frequency_analysis()
75 | microphone.get_frequency_buckets(num_buckets=8, min_hz=0, max_hz=4000)
76 |
77 |
78 | =======
79 | Speaker
80 | =======
81 |
82 | speaker.volume # 0 - 200
83 |
84 | speaker.play(path)
85 | speaker.stop()
86 | speaker.say(text)
87 | speaker.tone(frequency=261.626, duration=0.5, amplitude=0.25, function=sine_wave)
88 |
89 |
90 | ======
91 | Screen
92 | ======
93 |
94 | screen.set_pixel(x, y, colour)
95 | screen.set_pixels(x, y, width, height, pixels)
96 | screen.draw_rectangle(x, y, width, height, colour)
97 | screen.write_text(x, y, text, size, colour, background_colour)
98 | screen.draw_image(path, x, y, width, height)
99 |
100 |
101 | =======
102 | Buttons
103 | =======
104 |
105 | buttons.top_pressed
106 | buttons.middle_pressed
107 | buttons.bottom_pressed
108 | buttons.top_pressed_count
109 | buttons.middle_pressed_count
110 | buttons.bottom_pressed_count
111 |
112 | buttons.get_next_action(timeout) # Returns either 'top', 'middle', or 'bottom'
113 |
114 |
115 | ============
116 | Light sensor
117 | ============
118 |
119 | buttons.get_next_gesture(timeout) # Returns either 'up', 'down', 'left' or 'right'
120 |
121 | light_sensor.ambient_light
122 | light_sensor.red
123 | light_sensor.green
124 | light_sensor.blue
125 | light_sensor.last_gesture
126 | light_sensor.num_gestures
127 | light_sensor.within_proximity
128 | light_sensor.num_times_within_proximity
129 |
130 |
131 | ========================
132 | Orientation sensor (IMU)
133 | ========================
134 |
135 | imu.pitch
136 | imu.roll
137 | imu.yaw
138 | imu.acceleration_x
139 | imu.acceleration_y
140 | imu.acceleration_z
141 | imu.angular_velocity_x
142 | imu.angular_velocity_y
143 | imu.angular_velocity_z
144 | imu.gravity_x
145 | imu.gravity_y
146 | imu.gravity_z
147 |
148 |
149 | ==================
150 | Environment sensor
151 | ==================
152 |
153 | env_sensor.temperature
154 | env_sensor.pressure
155 | env_sensor.humidity
156 |
157 |
158 | ============
159 | Raspberry Pi
160 | ============
161 |
162 | pi.ip_address()
163 | pi.cpu_temp()
164 | pi.cpu_percent()
165 | pi.ram_percent_used()
166 | pi.disk_percent()
167 |
168 |
169 | ========================
170 | Using multiple LumiCubes
171 | ========================
172 |
173 | Technically the objects above are also available on an object called "cube", so:
174 |
175 | display.set_led(0, 0, red)
176 |
177 | is the same as:
178 |
179 | cube.display.set_led(0, 0, red)
180 |
181 | If you want to interact with a different LumiCube, create another cube object using the IP address of the other cube.
182 | You can then access all the fields and methods of the remote cube in the same way:
183 |
184 | cube2 = Cube("192.168.0.14")
185 | cube2.display.set_led(0, 0, red)
186 |
187 | Note: It will be communicating over a WiFi network, so the responsiveness will depend on your network speed and reliability.
188 |
--------------------------------------------------------------------------------