├── .gitignore
├── README.md
├── amg8833_pil.py
├── assets
├── Med_Picorder_Logo.png
├── Picorder_Logo.png
├── alarm.wav
├── babs.otf
├── backgraph.bmp
├── backgraph.png
├── background.bmp
├── background.png
├── beep.wav
├── clicking.wav
├── ekmd.mov
├── font.ttf
├── font2.ttf
├── frame.ppm
├── frame.xcf
├── icon.bmp
├── icon.png
├── insigniablue.bmp
├── insigniablue.png
├── lcarsburgerframe.png
├── lcarsburgerframefull.png
├── lcarsframe.png
├── lcarsframeblank.png
├── lcarssplitframe.png
├── lozenge.png
├── picorderOS_logo.png
├── scanning.wav
├── slider.png
├── slider2.png
└── videobg.png
├── audio.py
├── display.py
├── gpiodummy.py
├── input.py
├── lcars_bw.py
├── lcars_clr.py
├── leds.py
├── lib_tft24T.py
├── main.py
├── modulated_em.py
├── objects.py
├── pilgraph.py
├── plars.py
├── pyvidplayer.py
├── requirements.txt
├── sensors.py
└── tos_display.py
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | *.DS_Store
3 | *.pyc
4 | *.csv
5 | /__pycache__/*
6 | data/
7 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | # PicorderOS
6 |
7 | A set of python components that provide functionality for a Raspberry Pi based [Tricorder](https://en.wikipedia.org/wiki/Tricorder) replica called a "picorder". A picorder is made of a Raspberry Pi, one or more sensor packages, a battery, a display, and supplemental components. The purpose of this project is to provide a simple, extensible method for quickly getting a Raspberry Pi to be a handheld sensor data collection and display device like the fictional device it is inspired by.
8 |
9 | PicorderOS is experimental and in development. A lot of user configuration is required and you will probably need to "roll your own" solutions to some problems. PicorderOS is offered as a framework solution that can be built upon if desired or used as is.
10 |
11 | For further information on hardware please visit the [Wiki](https://squaredwave.com/wiki/index.php?title=PicorderOS)
12 |
13 | ## Requirements:
14 | Depending on hardware configuration picorderOS can use a number of existing third party libraries to communicate with i2c sensors. At present only a few sensors are currently officially supported like
15 |
16 | - The [Sensehat](https://projects.raspberrypi.org/en/projects/getting-started-with-the-sense-hat/2) using the [sensehat library](https://pythonhosted.org/sense-hat/)
17 | - The Bosch [BME680](https://www.bosch-sensortec.com/products/environmental-sensors/gas-sensors/bme680/) with an [Adafruit library](https://github.com/adafruit/Adafruit_CircuitPython_BME680)
18 | - The Panasonic [AMG8833](https://www.digikey.ca/en/products/detail/panasonic-electronic-components/AMG8833/5825302) with an [Adafruit library](https://github.com/adafruit/Adafruit_CircuitPython_AMG88xx)
19 |
20 | There is mostly stable support for the PocketGeiger and envirophat but detailed instructions are not available for those yet.
21 |
22 | For testing purposes picorderOS does not need to be run with real sensors, or even on a Raspberry Pi when put in "PC Mode".
23 |
24 | Most installs will require:
25 | - [RPi.GPIO](https://pypi.org/project/RPi.GPIO/) (For various I/O functions)
26 | - [os](https://pythonprogramming.net/python-3-os-module/) (For machine vitals)
27 | - [psutil](https://psutil.readthedocs.io/en/latest/) (For simulating sensors when testing or demonstrating)
28 | - [Adafruit Blinka](https://learn.adafruit.com/circuitpython-on-raspberrypi-linux/installing-circuitpython-on-raspberry-pi) (Adafruit graciously offers a number of great python sensor libraries through their circuitpython platform)
29 |
30 |
31 | For capacitive touch sensing the [Pimoroni cap1xxx library](https://github.com/pimoroni/cap1xxx) can be used for CAP1208 based capacitive touch buttons, and an [adafruit MPR121 Capacitive Sensor](https://github.com/adafruit/Adafruit_CircuitPython_MPR121) for MPR121 ones.
32 |
33 | PicorderOS can run using an HDMI or Composite screen in some cases, and can also use TFT LCD displays. Depending on your choice of screen you may need:
34 | - [Pygame](https://www.pygame.org/wiki/GettingStarted) (For framebuffer screens)
35 | - [Luma.lcd](https://pypi.org/project/luma.lcd/) (For a range of LCD options, thought not all are supported)
36 |
37 |
38 | ## Installation:
39 |
40 | A requirements file is included, it can be used to install all the necessary python modules through pip.
41 |
42 | ```
43 | python3 -m pip -r requirements.txt
44 | ```
45 |
46 | A fresh Raspberry Pi OS image can usually be initialized to work with picorderOS with the following installation commands:
47 |
48 | ```
49 | sudo apt-get update
50 |
51 | sudo apt-get upgrade
52 |
53 | sudo apt install libsdl2-2.0-0 libsdl2-gfx-1.0-0 libsdl2-image-2.0-0 libsdl2-mixer-2.0-0 libsdl2-net-2.0-0 libsdl2-ttf-2.0-0
54 |
55 | sudo apt-get install libatlas-base-dev libsdl2-dev libopenjp2-7-dev libtiff5 python3-pandas python3-psutil
56 |
57 | pip3 install --upgrade colour luma.lcd luma.emulator pygame==2.0.0
58 | ```
59 | Depending on your sensors, you will need to install a package that supports it:
60 | ```
61 | pip3 install adafruit-circuitpython-bme680 sense-hat adafruit-circuitpython-mpr121
62 |
63 | ```
64 |
65 | ## Usage:
66 | At present picorderOS supports a number of displays, sensors, and inputs. The user can mix and match their desired picorder load out. PicorderOS will generate an INI file on creation called ```config.ini```. By editing this file certain flags can be set for specific Picorder builds. Any sensors or peripherals not physically present must be turned off before picorderOS will run properly.
67 |
68 | The software is started with ```python3 main.py``` from the project folder (run with sudo for full wifi features on 109 style builds), which starts the main loop. The main loop handles display directly, various ancillary elements like input detection, LED sequencing, and audio playback run as separate threads and communicate via flags set in the ```objects.py``` module.
69 |
70 |
71 |
72 |
73 |
74 |
75 | Some experimentation may be necessary. PicorderOS is provided as free software and comes with no guarantee or warranty. This software is not to be used outside federation space, and is not suitable for use in environments that do not obey established laws of physics. Using this software could cause timeline damage, rocks exploding out of consoles, or blue barrels. Do not use PicorderOS if you have recently tested positive for Andorian Flu.
76 |
--------------------------------------------------------------------------------
/amg8833_pil.py:
--------------------------------------------------------------------------------
1 | print("Loading AMG8833 Thermal Camera Module")
2 | #import pygame
3 | import random
4 | import math
5 | import numpy
6 |
7 | # Load up the image library stuff to help draw bitmaps to push to the screen
8 | import PIL.ImageOps
9 |
10 |
11 | # from https://learn.adafruit.com/adafruit-amg8833-8x8-thermal-camera-sensor/raspberry-pi-thermal-camera
12 | # interpolates the data into a smoothed screen res
13 | import numpy as np
14 | from scipy.interpolate import griddata
15 | from colour import Color
16 |
17 |
18 | # some utility functions
19 | def constrain(val, min_val, max_val):
20 | return min(max_val, max(min_val, val))
21 |
22 |
23 | def map_value(x, in_min, in_max, out_min, out_max):
24 | return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min
25 |
26 | from PIL import Image
27 | from PIL import ImageFont
28 | from PIL import ImageDraw
29 | from colour import Color
30 | # The following are for LCARS colours from LCARScom.net
31 | lcars_orange = (255,153,0)
32 | lcars_pink = (204,153,204)
33 | lcars_blue = (153,152,208)
34 | lcars_red = (204,102,102)
35 | lcars_peach = (255,204,153)
36 | lcars_bluer = (153,153,255)
37 | lcars_orpeach = (255,153,102)
38 | lcars_pinker = (204,102,153)
39 |
40 | standard_blue = (0,0,255)
41 | standard_red = (255,0,0)
42 |
43 | # low range of the sensor (this will be blue on the screen)
44 | MINTEMP = -2.0
45 |
46 | # high range of the sensor (this will be red on the screen)
47 | MAXTEMP = 150.0
48 |
49 | # how many color values we can have
50 | COLORDEPTH = 1024
51 |
52 | # pylint: disable=invalid-slice-index
53 | points = [(math.floor(ix / 8), (ix % 8)) for ix in range(0, 64)]
54 | grid_x, grid_y = np.mgrid[0:7:32j, 0:7:32j]
55 | # pylint: enable=invalid-slice-index
56 | # sensor is an 8x8 grid so lets do a square
57 | height = 133
58 | width = 71
59 |
60 | # the list of colors we can choose from
61 | cool = Color(rgb=(0.0, 0.0, 0.0)) #Color("blue")
62 | hot = Color(rgb=(0.8, 0.4, 0.6))#"red")
63 | #blue = Color("indigo")
64 | colors = list(cool.range_to(hot, COLORDEPTH))
65 |
66 | # create the array of colors
67 | colors = [(int(c.red * 255), int(c.green * 255), int(c.blue * 255)) for c in colors]
68 |
69 | displayPixelWidth = width / 30
70 | displayPixelHeight = height / 30
71 |
72 |
73 | colrange = list(cool.range_to(hot, 256))
74 |
75 | rotate = False
76 | fliplr = False
77 | flipud = True
78 |
79 | from objects import *
80 |
81 | import sensors
82 |
83 | if configure.amg8833:
84 | import adafruit_amg88xx
85 | import busio
86 | import board
87 | i2c = busio.I2C(board.SCL, board.SDA)
88 | amg = adafruit_amg88xx.AMG88XX(i2c)
89 |
90 |
91 |
92 | # create an 8x8 array for testing purposes. Displays random 'sensor data'.
93 | def makegrid(random = True):
94 | dummyvalue = []
95 | #
96 | for i in range(8):
97 | dummyrow = []
98 | for r in range(8):
99 | if random:
100 | dummyrow.append(random.uniform(1.0,81.0))
101 | else:
102 | dummyrow.append(0)
103 | dummyvalue.append(dummyrow)
104 |
105 | return dummyvalue
106 |
107 | # a single pixel of temperature information
108 | class ThermalPixel(object):
109 |
110 | def __init__(self,x,y,w,h):
111 | self.x = x
112 | self.y = y
113 | self.w = w
114 | self.h = h
115 | self.colour = (255,255,255)
116 | self.temp = 0
117 | self.count = 0
118 |
119 |
120 | def update(self,value,high,low,surface):
121 |
122 | if configure.auto[0]:
123 |
124 | color = numpy.interp(value,(low,high),(0,254))
125 | else:
126 | color = numpy.interp(value,(0,80),(0,254))
127 |
128 | if color > 255:
129 | color = 255
130 | if color < 0:
131 | color = 0
132 |
133 | colorindex = int(color)
134 |
135 | temp = colrange[colorindex].rgb
136 |
137 | red = int(temp[0] * 255.0)
138 | green = int(temp[1] * 255.0)
139 | blue = int(temp[2] * 255.0)
140 |
141 | self.count += 1
142 |
143 | if self.count > 255:
144 | self.count = 0
145 |
146 | surface.rectangle([(self.x, self.y), (self.x + self.w, self.y + self.h)], fill = (red,green,blue), outline=None)
147 |
148 | class ThermalColumns(object):
149 |
150 | def __init__(self,x,y,w,h):
151 | self.x = x
152 | self.y = y
153 | self.w = w
154 | self.h = h
155 |
156 | self.pixels = []
157 |
158 | for i in range(8):
159 | self.pixels.append(ThermalPixel(self.x, self.y + (i * (h/8)), self.w, self.h / 8))
160 |
161 | #[10.0,10.0,10.0,10.0,10.0,10.0,10.0,10.0]
162 | def update(self,data,high,low,surface):
163 | for i in range(8):
164 | self.pixels[i].update(data[i],high, low, surface)
165 |
166 | class ThermalRows(object):
167 |
168 | def __init__(self,x,y,w,h):
169 | self.x = x
170 | self.y = y
171 | self.w = w
172 | self.h = h
173 |
174 | self.pixels = []
175 |
176 | for i in range(8):
177 | self.pixels.append(ThermalPixel(self.x + (i * (w/8)), self.y, self.w / 8, self.h))
178 |
179 | #[10.0,10.0,10.0,10.0,10.0,10.0,10.0,10.0]
180 | def update(self,data,high,low,surface):
181 | for i in range(8):
182 | self.pixels[i].update(data[i],high, low, surface)
183 |
184 | class ThermalGrid(object):
185 |
186 | def __init__(self,x,y,w,h):
187 | self.x = x
188 | self.y = y
189 | self.h = h
190 | self.w = w
191 | self.data = []
192 |
193 | self.rows = []
194 | self.high = 0.0
195 | self.low = 0.0
196 | self.average = 0
197 | self.ticks = 0
198 |
199 | for i in range(8):
200 | self.rows.append(ThermalRows(self.x, self.y + (i * (h/8)), self.w, self.h / 8))
201 |
202 | self.update()
203 |
204 | def push(self,surface):
205 | if not configure.interpolate[0]:
206 | for i in range(8):
207 | self.rows[i].update(self.data[i],self.high,self.low,surface)
208 | else:
209 | self.interpolate(surface)
210 | # Function to draw a pretty pattern to the display for demonstration.
211 | def animate(self):
212 |
213 | self.dummy = makegrid(random = False)
214 |
215 | for x in range(8):
216 | for y in range(8):
217 | cx = x + 0.5*math.sin(self.ticks/5.0)
218 | cy = y + 0.5*math.cos(self.ticks/3.0)
219 | a = math.sin(math.sqrt(1.0*(math.pow(cx, 2.0)+math.pow(cy, 2.0))+1.0)+self.ticks)
220 | b = math.sin(10*(x * math.sin(self.ticks/2) + y * math.cos(self.ticks/3))+self.ticks)
221 | v = (a + 1.0)/2.0
222 |
223 | v = int(v*256.0)
224 | self.dummy[x][y] = v
225 | #dsense.set_pixel(x,y,v,v,v)
226 | self.ticks = self.ticks+1
227 | return self.dummy
228 |
229 | def interpolate(self, surface):
230 |
231 | height = self.w
232 | width = self.h
233 | displayPixelWidth = width / 30
234 | displayPixelHeight = height / 30
235 |
236 | if configure.auto[0]:
237 | # low range of the sensor (this will be blue on the screen)
238 | mintemp = self.low
239 | # high range of the sensor (this will be red on the screen)
240 | maxtemp = self.high
241 | else:
242 | mintemp = MINTEMP
243 | maxtemp = MAXTEMP
244 |
245 | pixels = []
246 |
247 | for row in self.data:
248 | pixels = pixels + list(row)
249 | pixels = [numpy.interp(p,(mintemp,maxtemp),(0,COLORDEPTH - 1)) for p in pixels]
250 |
251 | # perform interpolation
252 | bicubic = griddata(points, pixels, (grid_x, grid_y), method="cubic")
253 |
254 | # draw everything
255 | for ix, row in enumerate(bicubic):
256 | for jx, pixel in enumerate(row):
257 | x = self.x + (displayPixelHeight * ix)
258 | y = self.y + (displayPixelWidth * jx)
259 | x2 = x + displayPixelHeight
260 | y2 = y + displayPixelWidth
261 | surface.rectangle([(x, y), (x2, y2)], fill = colors[constrain(int(pixel), 0, COLORDEPTH - 1)], outline=None)
262 |
263 | def update(self):
264 |
265 | if configure.amg8833:
266 | self.data = amg.pixels
267 | else:
268 | self.data = self.animate()
269 |
270 | if rotate:
271 | self.data = np.transpose(self.data).tolist()
272 |
273 | if fliplr:
274 | self.data = np.fliplr(self.data).tolist()
275 |
276 | if flipud:
277 | self.data = np.flipud(self.data).tolist()
278 |
279 | thisaverage = 0
280 | rangemax = []
281 | rangemin = []
282 |
283 | for i in range(8):
284 |
285 | for j in range(8):
286 | thisaverage += self.data[i][j]
287 |
288 | thismax = max(self.data[i])
289 | thismin = min(self.data[i])
290 | rangemin.append(thismin)
291 | rangemax.append(thismax)
292 |
293 | self.average = thisaverage / (8*8)
294 |
295 | self.high = max(rangemax)
296 | self.low = min(rangemin)
297 |
298 | return self.average, self.high, self.low
299 |
--------------------------------------------------------------------------------
/assets/Med_Picorder_Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/directive0/picorderOS/0bab63407135091e2c05514a238abf86cdaa700c/assets/Med_Picorder_Logo.png
--------------------------------------------------------------------------------
/assets/Picorder_Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/directive0/picorderOS/0bab63407135091e2c05514a238abf86cdaa700c/assets/Picorder_Logo.png
--------------------------------------------------------------------------------
/assets/alarm.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/directive0/picorderOS/0bab63407135091e2c05514a238abf86cdaa700c/assets/alarm.wav
--------------------------------------------------------------------------------
/assets/babs.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/directive0/picorderOS/0bab63407135091e2c05514a238abf86cdaa700c/assets/babs.otf
--------------------------------------------------------------------------------
/assets/backgraph.bmp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/directive0/picorderOS/0bab63407135091e2c05514a238abf86cdaa700c/assets/backgraph.bmp
--------------------------------------------------------------------------------
/assets/backgraph.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/directive0/picorderOS/0bab63407135091e2c05514a238abf86cdaa700c/assets/backgraph.png
--------------------------------------------------------------------------------
/assets/background.bmp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/directive0/picorderOS/0bab63407135091e2c05514a238abf86cdaa700c/assets/background.bmp
--------------------------------------------------------------------------------
/assets/background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/directive0/picorderOS/0bab63407135091e2c05514a238abf86cdaa700c/assets/background.png
--------------------------------------------------------------------------------
/assets/beep.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/directive0/picorderOS/0bab63407135091e2c05514a238abf86cdaa700c/assets/beep.wav
--------------------------------------------------------------------------------
/assets/clicking.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/directive0/picorderOS/0bab63407135091e2c05514a238abf86cdaa700c/assets/clicking.wav
--------------------------------------------------------------------------------
/assets/ekmd.mov:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/directive0/picorderOS/0bab63407135091e2c05514a238abf86cdaa700c/assets/ekmd.mov
--------------------------------------------------------------------------------
/assets/font.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/directive0/picorderOS/0bab63407135091e2c05514a238abf86cdaa700c/assets/font.ttf
--------------------------------------------------------------------------------
/assets/font2.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/directive0/picorderOS/0bab63407135091e2c05514a238abf86cdaa700c/assets/font2.ttf
--------------------------------------------------------------------------------
/assets/frame.ppm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/directive0/picorderOS/0bab63407135091e2c05514a238abf86cdaa700c/assets/frame.ppm
--------------------------------------------------------------------------------
/assets/frame.xcf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/directive0/picorderOS/0bab63407135091e2c05514a238abf86cdaa700c/assets/frame.xcf
--------------------------------------------------------------------------------
/assets/icon.bmp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/directive0/picorderOS/0bab63407135091e2c05514a238abf86cdaa700c/assets/icon.bmp
--------------------------------------------------------------------------------
/assets/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/directive0/picorderOS/0bab63407135091e2c05514a238abf86cdaa700c/assets/icon.png
--------------------------------------------------------------------------------
/assets/insigniablue.bmp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/directive0/picorderOS/0bab63407135091e2c05514a238abf86cdaa700c/assets/insigniablue.bmp
--------------------------------------------------------------------------------
/assets/insigniablue.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/directive0/picorderOS/0bab63407135091e2c05514a238abf86cdaa700c/assets/insigniablue.png
--------------------------------------------------------------------------------
/assets/lcarsburgerframe.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/directive0/picorderOS/0bab63407135091e2c05514a238abf86cdaa700c/assets/lcarsburgerframe.png
--------------------------------------------------------------------------------
/assets/lcarsburgerframefull.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/directive0/picorderOS/0bab63407135091e2c05514a238abf86cdaa700c/assets/lcarsburgerframefull.png
--------------------------------------------------------------------------------
/assets/lcarsframe.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/directive0/picorderOS/0bab63407135091e2c05514a238abf86cdaa700c/assets/lcarsframe.png
--------------------------------------------------------------------------------
/assets/lcarsframeblank.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/directive0/picorderOS/0bab63407135091e2c05514a238abf86cdaa700c/assets/lcarsframeblank.png
--------------------------------------------------------------------------------
/assets/lcarssplitframe.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/directive0/picorderOS/0bab63407135091e2c05514a238abf86cdaa700c/assets/lcarssplitframe.png
--------------------------------------------------------------------------------
/assets/lozenge.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/directive0/picorderOS/0bab63407135091e2c05514a238abf86cdaa700c/assets/lozenge.png
--------------------------------------------------------------------------------
/assets/picorderOS_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/directive0/picorderOS/0bab63407135091e2c05514a238abf86cdaa700c/assets/picorderOS_logo.png
--------------------------------------------------------------------------------
/assets/scanning.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/directive0/picorderOS/0bab63407135091e2c05514a238abf86cdaa700c/assets/scanning.wav
--------------------------------------------------------------------------------
/assets/slider.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/directive0/picorderOS/0bab63407135091e2c05514a238abf86cdaa700c/assets/slider.png
--------------------------------------------------------------------------------
/assets/slider2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/directive0/picorderOS/0bab63407135091e2c05514a238abf86cdaa700c/assets/slider2.png
--------------------------------------------------------------------------------
/assets/videobg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/directive0/picorderOS/0bab63407135091e2c05514a238abf86cdaa700c/assets/videobg.png
--------------------------------------------------------------------------------
/audio.py:
--------------------------------------------------------------------------------
1 | from objects import *
2 | import simpleaudio as sa
3 | print("Loading Audio Thread")
4 |
5 | scansound = sa.WaveObject.from_wave_file("assets/scanning.wav")
6 | clicksound = sa.WaveObject.from_wave_file("assets/clicking.wav")
7 | beepsound = sa.WaveObject.from_wave_file("assets/beep.wav")
8 | alarmsound = sa.WaveObject.from_wave_file("assets/alarm.wav")
9 |
10 |
11 | sounds = [scansound, clicksound]
12 | # the audio object will serve as the primary mechanism by which all sounds
13 | # are loaded and deployed.
14 |
15 |
16 |
17 | def threaded_audio():
18 | timed = timer()
19 | start = True
20 | was_open = False
21 | warble = scansound.play()
22 | click = clicksound.play()
23 | alarm = alarmsound.play()
24 |
25 | click.stop()
26 | warble.stop()
27 | alarm.stop()
28 |
29 | while not configure.status[0] == "quit":
30 | if configure.audio[0]:
31 |
32 | if configure.dr_opening[0]:
33 | click = clicksound.play()
34 | configure.dr_opening[0] = False
35 |
36 | if configure.dr_closing[0]:
37 | click = clicksound.play()
38 | configure.dr_closing[0] = False
39 |
40 | if configure.beep_ready[0]:
41 | beep = beepsound.play()
42 | configure.beep_ready[0] = False
43 |
44 |
45 | # controls the main tricorder sound loop
46 | if configure.dr_open[0]:
47 | if not warble.is_playing():
48 | warble = scansound.play()
49 | else:
50 | if warble.is_playing():
51 | warble.stop()
52 |
53 | if configure.alarm_ready[0]:
54 | if not alarm.is_playing():
55 | alarm = alarmsound.play()
56 | configure.alarm_ready[0] = False
57 | else:
58 | warble.stop()
59 |
--------------------------------------------------------------------------------
/display.py:
--------------------------------------------------------------------------------
1 | print("Unified Display Module loading")
2 | import sys
3 | import logging
4 | from objects import *
5 | from multiprocessing import Process,Queue,Pipe
6 | import signal
7 |
8 |
9 | if configure.display == 1:
10 | from luma.core.interface.serial import spi
11 | from luma.core.render import canvas
12 | from luma.lcd.device import st7735
13 | from luma.emulator.device import pygame
14 |
15 | # Raspberry Pi hardware SPI config:
16 | DC = 23
17 | RST = 24
18 | SPI_PORT = 0
19 | SPI_DEVICE = 0
20 |
21 | if not configure.pc:
22 | serial = spi(port = SPI_PORT, device = SPI_DEVICE, gpio_DC = DC, gpio_RST = RST)
23 | device = st7735(serial, width = 160, height = 128, mode = "RGB")
24 | else:
25 | device = pygame(width = 160, height = 128)
26 |
27 | # for TFT24T screens
28 | elif configure.display == 2:
29 | # Details pulled from https://github.com/BehindTheSciences/ili9341_SPI_TouchScreen_LCD_Raspberry-Pi/blob/master/BTS-ili9341-touch-calibration.py
30 | from lib_tft24T import TFT24T
31 | import RPi.GPIO as GPIO
32 | GPIO.setmode(GPIO.BCM)
33 | GPIO.setwarnings(False)
34 | import spidev
35 | DC = 24
36 | RST = 25
37 | LED = 15
38 | PEN = 26
39 | device = TFT24T(spidev.SpiDev(), GPIO)
40 | # Initialize display and touch.
41 | TFT.initLCD(DC, RST, LED)
42 |
43 | # a function intended to be run as a process so as to offload the computation
44 | # of the screen rendering from the GIL.
45 | def DisplayFunction(q):
46 | # lib_tft24 screens require us to create a drawing surface for the screen
47 | # and add to it.
48 | if configure.display == 2:
49 | surface = device.draw()
50 |
51 | while True:
52 |
53 | payload = q.get()
54 |
55 | # add an event to handle shutdown.
56 | if payload == "quit":
57 | pass
58 |
59 | # the following is only for screens that use Luma.LCD
60 | if configure.display == 1:
61 | device.display(payload)
62 |
63 | # the following is only for TFT24T screens
64 | elif configure.display == 2:
65 | # Resize the image and rotate it so it's 240x320 pixels.
66 | frame = frame.rotate(90,0,1).resize((240, 320))
67 | # Draw the image on the display hardware.
68 | surface.pasteimage(payload,(0,0))
69 | device.display()
70 |
71 | # a class to control the connected display. It serves as a transmission between
72 | # the main drawing program and the possible connected screen. A range of screens
73 | # and libraries can be used in this way with small modifications to the base
74 | # class.
75 | class GenericDisplay(object):
76 |
77 | def __init__(self):
78 |
79 | # lib_tft24 screens require us to create a drawing surface for the screen
80 | # and add to it.
81 | if configure.display == 2:
82 | self.surface = device.draw()
83 |
84 | self.q = Queue()
85 | self.display_process = Process(target=DisplayFunction, args=(self.q,))
86 | self.display_process.start()
87 |
88 |
89 | # Display takes a PILlow based drawobject and pushes it to screen.
90 | def display(self,frame):
91 | self.q.put(frame)
92 |
--------------------------------------------------------------------------------
/gpiodummy.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | print("Loading GPIO Dummy")
3 |
4 | # This entire module just returns dummy results so as to avoid errors in the program. It has no useful purpose beyond that.
5 |
6 | # External module imports
7 | #import RPi.GPIO as GPIO
8 | import pygame
9 | import sys
10 | import time
11 |
12 | # Pin Definitons:
13 | led1 = 4 # Broadcom pin 4 (Pi0 pin 7)
14 | led2 = 17 # Broadcom pin 17 (Pi0 pin 11)
15 | led3 = 27 # Broadcom pin 27 (P1 pin 13)
16 |
17 | # The following pins are my three buttons.
18 | buta = 5
19 | butb = 6
20 | butc = 13
21 |
22 |
23 | # a function to clear the gpio
24 | def cleangpio():
25 | pass
26 |
27 | def resetleds():
28 | pass
29 |
30 | def leda_on():
31 | pass
32 |
33 | def ledb_on():
34 | pass
35 |
36 | def ledc_on():
37 | pas
38 |
39 | def leda_off():
40 | pass
41 |
42 | def ledb_off():
43 | pass
44 |
45 | def ledc_off():
46 | pass
47 |
48 |
49 | # The following function returns the instantanious state of each button.
50 | def buttonget():
51 |
52 | buttondict = {'buta':False, 'butb':False, 'butc':False}
53 |
54 | return buttondict
55 |
56 | # The following function returns the debounced activation for each button.
57 | class debounce(object):
58 |
59 | def __init__(self):
60 | self.awaspressed = 0
61 | self.bwaspressed = 0
62 | self.cwaspressed = 0
63 | self.afire = False
64 | self.bfire = False
65 | self.cfire = False
66 |
67 | def read(self):
68 | self.afire = False
69 | self.bfire = False
70 | self.cfire = False
71 | buttondict = {'buta':False, 'butb':False, 'butc':False}
72 | buttondict['buta'] = self.afire
73 | buttondict['butb'] = self.bfire
74 | buttondict['butc'] = self.cfire
75 |
76 | return buttondict
77 |
78 |
79 | # The following function is merely a hardware tool so I can ensure my LED's were wired correctly.
80 | def cycleloop():
81 | pass
82 |
--------------------------------------------------------------------------------
/input.py:
--------------------------------------------------------------------------------
1 | print("Loading Unified Input Module")
2 | # This script retrieves and packages all input events that might be useful to the program
3 | # The input object checks the configuration object and returns an array of button inputs.
4 |
5 |
6 | # This script needs to be able to accept 3 different kinds of inputs
7 | # 1) inputs handed directly from the RPI gpio
8 | # 2) inputs handed from pygame event checkers
9 | # 3) inputs handed from the capacitive touch device on i2c
10 |
11 | # array needs:
12 |
13 | # geo, met and bio are going to be standard across all trics.
14 |
15 |
16 | # array holds the pins for each hard coded button on the tric
17 | # The TR-108 only has 3 buttons
18 |
19 | # Max number of buttons
20 | # 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14
21 | # geo, met, bio, pwr, f1/f2, I, E, accpt/pool, intrship/tricrder, EMRG, fwd/input, rvs/erase, Ib, Eb, ID
22 |
23 |
24 | import time
25 | from objects import *
26 |
27 |
28 | # stores the number of buttons to be queried
29 | buttons = 16
30 |
31 | threshold = 3
32 | release_threshold = 2
33 |
34 |
35 | # if tr108 set up pins for gpio buttons
36 | if configure.tr108:
37 | pins = [5,6,13]
38 |
39 | # tr109 by default uses cap1208. This will require modifying for other inputs
40 | if configure.tr109:
41 |
42 | import RPi.GPIO as GPIO
43 |
44 |
45 | hallpin1 = configure.HALLPIN1
46 | hallpin2 = configure.HALLPIN2
47 |
48 |
49 | alertpin = configure.ALERTPIN
50 |
51 | GPIO.setmode(GPIO.BCM)
52 | GPIO.setup(hallpin1, GPIO.IN, pull_up_down=GPIO.PUD_UP)
53 | GPIO.setup(hallpin2, GPIO.IN, pull_up_down=GPIO.PUD_UP)
54 |
55 | if configure.sensehat:
56 | if configure.input_joystick:
57 | from sense_hat import SenseHat
58 | sense = SenseHat()
59 |
60 | # set up requirements for USB keyboard
61 | if configure.input_kb:
62 | import pygame
63 |
64 | # set up requirements for GPIO based inputs
65 | if configure.input_gpio:
66 |
67 | # setup for ugeek test rig.
68 | import RPi.GPIO as GPIO
69 |
70 | GPIO.setmode(GPIO.BCM)
71 |
72 | if configure.tr108:
73 | # setup our 3 control buttons
74 | GPIO.setup(pins[0], GPIO.IN, pull_up_down=GPIO.PUD_UP)
75 | GPIO.setup(pins[1], GPIO.IN, pull_up_down=GPIO.PUD_UP)
76 | GPIO.setup(pins[2], GPIO.IN, pull_up_down=GPIO.PUD_UP)
77 |
78 | if configure.tr109:
79 | # setup our 3 control buttons
80 | GPIO.setup(pins[0], GPIO.IN, pull_up_down=GPIO.PUD_UP)
81 | GPIO.setup(pins[1], GPIO.IN, pull_up_down=GPIO.PUD_UP)
82 | GPIO.setup(pins[2], GPIO.IN, pull_up_down=GPIO.PUD_UP)
83 |
84 |
85 | # set up requirements for capacitive buttons using an mpr121
86 | if configure.input_cap_mpr121:
87 | # if using the capacitive touch board from adafruit we import that library
88 | import adafruit_mpr121
89 | import busio
90 |
91 | # initiate I2C bus.
92 | i2c = busio.I2C(configure.PIN_SCL, configure.PIN_SDA)
93 |
94 | # Create MPR121 object. Address can be 5A or 5B (proto uses 5A)
95 | mpr121 = adafruit_mpr121.MPR121(i2c, address = 0x5A)
96 |
97 | # initializes each input
98 | for i in range(3):
99 | test = adafruit_mpr121.MPR121_Channel(mpr121,i)
100 | test.threshold = threshold
101 | test.release_threshold = release_threshold
102 |
103 | if configure.input_cap1208:
104 |
105 |
106 | import RPi.GPIO as GPIO
107 |
108 | GPIO.setmode(GPIO.BCM)
109 |
110 |
111 | GPIO.setup(configure.ALERTPIN, GPIO.IN, pull_up_down=GPIO.PUD_UP)
112 | GPIO.add_event_detect(configure.ALERTPIN, GPIO.BOTH)
113 |
114 |
115 | import cap1xxx
116 | cap1208 = cap1xxx.Cap1208(alert_pin = 0)
117 | cap1208._write_byte(0x1F, configure.CAPSENSITIVITY)
118 |
119 | if configure.input_pcf8575:
120 | from pcf8575 import PCF8575
121 | i2c_port_num = 1
122 | pcf_address = 0x20
123 | pcf = PCF8575(i2c_port_num, pcf_address)
124 |
125 | button_table = [-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,1,0,3,2,4]
126 |
127 | # the input class receives and relays control events for user interaction
128 | class Inputs(object):
129 |
130 | def __init__(self):
131 |
132 |
133 | # lists for hold behaviour tracking
134 | self.holding = []
135 | self.holdtimers = []
136 | # holding timer interval
137 | self.thresh_hold = 1.500
138 |
139 | # list stores each inputs previous state for comparison with now.
140 | self.pressed = []
141 |
142 | # a fresh eventlist to initialize.
143 | self.clear = []
144 |
145 | # this list stores the final state of all buttons to allow the program to check for multiple button presses for hidden features
146 | self.buttonlist = []
147 | self.door_was_open = False
148 | self.door_was_closed = False
149 |
150 | # seeds each list with a spot for each input
151 | for i in range(buttons):
152 | self.pressed.append(False)
153 | self.buttonlist.append(False)
154 | self.holding.append(False)
155 | self.clear.append(False)
156 |
157 | thistimer = timer()
158 | self.holdtimers.append(thistimer)
159 |
160 | configure.eventlist[0] = self.clear
161 |
162 | def getlist(self):
163 | pass
164 |
165 | def read(self):
166 |
167 | # looks for door open/close.
168 | if configure.tr109 and configure.dr[0]:
169 | # top hall sensor, 1 = door open
170 | if GPIO.input(hallpin1) == 1:
171 | if self.door_was_closed == True:
172 | self.door_was_closed = False
173 | configure.dr_opening[0] = True
174 |
175 | configure.dr_open[0] = True
176 | else:
177 | self.door_was_closed = True
178 | configure.dr_open[0] = False
179 |
180 | # lower hall, 0 = door open
181 | if GPIO.input(hallpin2) == 1:
182 | if self.door_was_open == True:
183 | self.door_was_open = False
184 | configure.dr_closing[0] = True
185 | else:
186 | self.door_was_open = True
187 |
188 | if configure.input_cap1208:
189 |
190 | # if the alert pin is brought LOW
191 | if GPIO.input(configure.ALERTPIN) == 0 and configure.eventready[0] == False:
192 |
193 | #print("touch received")
194 |
195 | # collect the event list from the chip
196 | reading = cap1208.get_input_status()
197 |
198 | # for each item in that event list
199 | for iteration, input in enumerate(reading):
200 |
201 | # if an item is pressed
202 | if input == "press":
203 | # mark it in the pressed list
204 | self.pressed[iteration] = True
205 | # raise the eventready flag
206 | configure.eventready[0] = True
207 | # raise the sound effect flag
208 | configure.beep_ready[0] = True
209 | else:
210 | # if an item is marked "released"
211 | if input == "release":
212 | # if it was previously marked pressed
213 | if self.pressed[iteration] == True:
214 | # ignore it
215 | self.pressed[iteration] = False
216 | else:
217 | # if it wasn't marked pressed last time (was missed)
218 | configure.eventready[0] = True
219 | self.pressed[iteration] = True
220 | configure.beep_ready[0] = True
221 | # else mark it not pressed
222 | else:
223 | self.pressed[iteration] = False
224 |
225 |
226 |
227 | #clear Alert pin
228 | cap1208.clear_interrupt()
229 |
230 | configure.eventlist[0] = self.pressed
231 |
232 | # return the pressed data
233 | return self.pressed
234 |
235 | else:
236 | # otherwise just return a line of negatives.
237 | return self.clear
238 |
239 | if configure.input_kb:
240 |
241 | key = self.keypress()
242 |
243 | # key was pressed
244 | if configure.eventready[0] == False:
245 | if key[pygame.K_LEFT]:
246 | if not self.pressed[0]:
247 | self.pressed[0] = True
248 | configure.eventready[0] = True
249 | self.holdtimers[0].logtime()
250 | else:
251 | if self.holdtimers[0].timelapsed() > self.thresh_hold:
252 | self.holding[0] = True
253 |
254 | if not key[pygame.K_LEFT]:
255 | self.holding[0] = False
256 | if self.pressed[0]:
257 | self.buttonlist[0] = True
258 | self.pressed[0] = False
259 | else:
260 | self.buttonlist[0] = False
261 |
262 |
263 | if key[pygame.K_DOWN]:
264 | if not self.pressed[1]:
265 | self.pressed[1] = True
266 | configure.eventready[0] = True
267 | self.holdtimers[1].logtime()
268 | else:
269 |
270 | if self.holdtimers[1].timelapsed() > self.thresh_hold:
271 | self.holding[1] = True
272 |
273 | if not key[pygame.K_DOWN]:
274 | self.holding[1] = False
275 | if self.pressed[1]:
276 | self.buttonlist[1] = True
277 | self.pressed[1] = False
278 | else:
279 | self.buttonlist[1] = False
280 |
281 |
282 | if key[pygame.K_RIGHT]:
283 | if not self.pressed[2]:
284 | self.pressed[2] = True
285 | configure.eventready[0] = True
286 | self.holdtimers[2].logtime()
287 | else:
288 |
289 | if self.holdtimers[2].timelapsed() > self.thresh_hold:
290 | self.holding[2] = True
291 |
292 | if not key[pygame.K_RIGHT]:
293 | self.holding[2] = False
294 | if self.pressed[2]:
295 | self.buttonlist[2] = True
296 | self.pressed[2] = False
297 | else:
298 | self.buttonlist[2] = False
299 |
300 | if configure.input_gpio:
301 |
302 | for i in range(3):
303 |
304 | # if the button has not been registered as pressed
305 | if GPIO.input(pins[i]) == 0: # button pressed
306 | if not self.pressed[i]:
307 | self.pressed[i] = True
308 | configure.eventlist[0] = True
309 |
310 |
311 | if GPIO.input(pins[i]) == 1:
312 |
313 | if self.pressed[i]:
314 | self.buttonlist[i] = True
315 | self.pressed[i] = False
316 | else:
317 | self.buttonlist[i] = False
318 |
319 | if configure.sensehat and configure.input_joystick:
320 |
321 | if configure.eventready[0] == False:
322 |
323 | for event in sense.stick.get_events():
324 |
325 | if (event.direction == 'left' and event.action == 'pressed'):
326 | if not self.pressed[0]:
327 | self.pressed[0] = True
328 | configure.eventready[0] = True
329 | self.holdtimers[0].logtime()
330 | else:
331 | if self.holdtimers[0].timelapsed() > self.thresh_hold:
332 | self.holding[0] = True
333 |
334 | if (event.direction == 'left' and event.action == 'released'):
335 | self.holding[0] = False
336 | if self.pressed[0]:
337 | self.buttonlist[0] = True
338 | self.pressed[0] = False
339 | else:
340 | self.buttonlist[0] = False
341 |
342 | if (event.direction == 'down' and event.action == 'pressed'):
343 | if not self.pressed[1]:
344 | self.pressed[1] = True
345 | configure.eventready[0] = True
346 | self.holdtimers[1].logtime()
347 | else:
348 | if self.holdtimers[1].timelapsed() > self.thresh_hold:
349 | self.holding[1] = True
350 |
351 | if (event.direction == 'down' and event.action == 'released'):
352 | self.holding[1] = False
353 | if self.pressed[1]:
354 | self.buttonlist[1] = True
355 | self.pressed[1] = False
356 | else:
357 | self.buttonlist[1] = False
358 |
359 | if (event.direction == 'right' and event.action == 'pressed'):
360 | if not self.pressed[2]:
361 | self.pressed[2] = True
362 | configure.eventready[0] = True
363 | self.holdtimers[2].logtime()
364 | else:
365 | if self.holdtimers[2].timelapsed() > self.thresh_hold:
366 | self.holding[2] = True
367 |
368 | if (event.direction == 'right' and event.action == 'released'):
369 | self.holding[2] = False
370 | if self.pressed[2]:
371 | self.buttonlist[2] = True
372 | self.pressed[2] = False
373 | else:
374 | self.buttonlist[2] = False
375 |
376 | if configure.input_cap_mpr121:
377 | # Reads the touched capacitive elements
378 | touched = mpr121.touched_pins
379 |
380 | # runs a loop to check each possible button
381 | for i in range(len(touched)):
382 |
383 | # if the button has not been registered as pressed
384 | if touched[i]: # button pressed
385 | if not self.pressed[i]:
386 | self.pressed[i] = True
387 | self.holdtimers[i].logtime()
388 | else:
389 |
390 | if self.holdtimers[i].timelapsed() > self.thresh_hold:
391 | self.holding[i] = True
392 |
393 | if not touched[i]:
394 | self.holding[i] = False
395 | if self.pressed[i]:
396 | self.buttonlist[i] = True
397 | self.pressed[i] = False
398 | else:
399 | self.buttonlist[i] = False
400 |
401 | if configure.input_pcf8575:
402 |
403 | if not configure.eventready[0]:
404 | this_frame = list(pcf.port)
405 |
406 | for this, button in enumerate(this_frame):
407 |
408 | # if an item is pressed
409 | if not button:
410 |
411 | #if it wasn't pressed last time
412 | if not self.pressed[this]:
413 |
414 | # mark it in the pressed list
415 | print("pad press registered at ", this)
416 | print("raising an event at address ", button_table[this])
417 |
418 | self.pressed[button_table[this]] = True
419 | configure.eventready[0] = True
420 | configure.beep_ready[0] = True
421 | else:
422 | self.pressed[button_table[this]] = False
423 |
424 | configure.eventlist[0] = self.pressed
425 |
426 |
427 | def keypress(self):
428 | pygame.event.get()
429 | #pygame.time.wait(50)
430 | key = pygame.key.get_pressed()
431 |
432 | return key
433 |
434 |
435 |
436 | def threaded_input():
437 |
438 |
439 | timed = timer()
440 | input = Inputs()
441 | timeit = timer()
442 |
443 | while not configure.status == "quit":
444 |
445 | if configure.samplerate[0] < timed.timelapsed():
446 | input.read()
447 |
--------------------------------------------------------------------------------
/lcars_bw.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | print("Loading Luma.LCD Nokia 5110 Screen")
4 | import math
5 | import time
6 | from input import *
7 |
8 |
9 | from luma.core.interface.serial import spi
10 | from luma.core.render import canvas
11 | from luma.lcd.device import pcd8544
12 | from luma.emulator.device import pygame
13 |
14 | # Load up the image library stuff to help draw bitmaps to push to the screen
15 | import PIL.ImageOps
16 | from PIL import Image
17 | from PIL import ImageFont
18 | from PIL import ImageDraw
19 |
20 | # load the module that draws graphs
21 | from pilgraph import *
22 |
23 |
24 | # Load default font.
25 | font = ImageFont.truetype("assets/font2.ttf",10)
26 | titlefont = ImageFont.truetype("assets/font.ttf",8)
27 |
28 | # Raspberry Pi hardware SPI config:
29 | DC = 23
30 | RST = 24
31 | SPI_PORT = 0
32 | SPI_DEVICE = 0
33 |
34 | # Beaglebone Black hardware SPI config:
35 | # DC = 'P9_15'
36 | # RST = 'P9_12'
37 | # SPI_PORT = 1
38 | # SPI_DEVICE = 0
39 |
40 | # Hardware SPI usage:
41 | if configure.pc:
42 | device = pygame(width = 84, height = 48, mode = "1")
43 | else:
44 | serial = spi(port = SPI_PORT, device = SPI_DEVICE, gpio_DC = DC, gpio_RST = RST)
45 | device = pcd8544(serial)
46 | device.contrast(50)
47 |
48 | fore_col = 0
49 | back_col = 1
50 |
51 | # Controls text objects drawn to the LCD
52 | class LabelObj(object):
53 | def __init__(self,string,font,draw):
54 | self.font = font
55 | self.draw = draw
56 | self.string = string
57 |
58 | def push(self,locx,locy):
59 | self.draw.text((locx, locy), self.string, font = self.font, fill= fore_col)
60 |
61 | def getsize(self):
62 | size = self.draw.textsize(self.string, font=self.font)
63 | return size
64 |
65 |
66 | # Controls the LCARS frame, measures the label and makes sure the top frame bar has the right spacing.
67 | class MultiFrame(object):
68 |
69 | def __init__(self,draw, input):
70 | self.input = input
71 | self.auto = configure.auto
72 | self.interval = timer()
73 | self.interval.logtime()
74 | self.draw = draw
75 | self.definetitle()
76 | self.layout()
77 | self.graphcycle = 0
78 | self.decimal = 1
79 | self.selection = 0
80 |
81 | self.divider = 47
82 |
83 | self.A_Graph = graphlist((-40,85),(4,8),(self.divider,11),self.graphcycle)
84 |
85 | self.B_Graph = graphlist((300,1100),(4,21),(self.divider,11),self.graphcycle)
86 |
87 | self.C_Graph = graphlist((0,100),(4,34),(self.divider,11),self.graphcycle)
88 |
89 | def definetitle(self):
90 | self.string = "MULTISCAN"
91 |
92 | # draws the title and sets the appropriate top bar length to fill the gap.
93 | def layout(self):
94 |
95 | self.title = LabelObj(self.string,titlefont,self.draw)
96 | self.titlesizex, self.titlesizey = self.title.getsize()
97 | self.barlength = (79 - (4+ self.titlesizex)) + 2
98 |
99 | # this function updates the graph for the screen
100 | def graphs(self):
101 |
102 | if self.selection == 0 or self.selection == 1:
103 | self.C_Graph.update(self.C_Data)
104 | self.C_Graph.render(self.draw)
105 |
106 | if self.selection == 0 or self.selection == 2:
107 | self.A_Graph.update(self.A_Data)
108 | self.A_Graph.render(self.draw)
109 |
110 | if self.selection == 0 or self.selection == 3:
111 | self.B_Graph.update(self.B_Data)
112 | self.B_Graph.render(self.draw)
113 |
114 | # this function takes a value and sheds the second digit after the decimal place
115 | def arrangelabel(self,data):
116 | data2 = data.split(".")
117 | dataa = data2[0]
118 | datab = data2[1]
119 |
120 | datadecimal = datab[0:self.decimal]
121 |
122 | datareturn = dataa + "." + datadecimal
123 |
124 | return datareturn
125 |
126 | # this function defines the labels for the screen
127 | def labels(self):
128 |
129 | self.titlex = self.divider + 7
130 |
131 | raw_a = str(self.A_Data)
132 | print("raw a is ", raw_a)
133 | adjusted_a = self.arrangelabel(raw_a)
134 | a_string = adjusted_a + self.sensors[configure.sensor1[0]][4]
135 |
136 | self.temLabel = LabelObj(a_string,font,self.draw)
137 | self.temLabel.push(self.titlex,7)
138 |
139 |
140 | raw_b = str(self.B_Data)
141 | adjusted_b = self.arrangelabel(raw_b)
142 | b_string = adjusted_b + " " + self.sensors[configure.sensor2[0]][4]
143 |
144 | self.baroLabel = LabelObj(b_string,font,self.draw)
145 | self.baroLabel.push(self.titlex,22)
146 |
147 | rawhumi = str(self.humi)
148 | adjustedhumi = self.arrangelabel(rawhumi)
149 | humistring = adjustedhumi + "%"
150 |
151 | self.humiLabel = LabelObj(humistring,font,self.draw)
152 | self.humiLabel.push(self.titlex,35)
153 |
154 |
155 | #push the image frame and contents to the draw object.
156 | def push(self,sensors):
157 | self.A_Data = sensors[configure.sensor1[0]][0]
158 | self.B_Data = sensors[configure.sensor2[0]][0]
159 | self.C_Data = sensors[configure.sensor3[0]][0]
160 | self.pres = sensors[1][0]
161 | self.humi = sensors[2][0]
162 |
163 | #Draw the background
164 | self.draw.rectangle((3,7,83,45),fill="white")
165 |
166 | #Top Bar - Needs to be scaled based on title string size.
167 | self.draw.rectangle((2,0,self.barlength,6), fill="black")
168 |
169 | self.title.push(self.barlength + 2,-1)
170 | self.sensors = sensors
171 | #self.sense()
172 | self.graphs()
173 | self.labels()
174 |
175 | status = "mode_a"
176 |
177 | keys = self.input.read()
178 |
179 | if keys[0]:
180 | if self.input.is_down(0):
181 | print("Input1")
182 | self.selection = self.selection + 1
183 | if self.selection > 3:
184 | self.selection = 0
185 |
186 | if keys[1]:
187 | if self.input.is_down(1):
188 | print("Input2")
189 | status = "mode_b"
190 |
191 |
192 | if keys[2]:
193 | if self.input.is_down(2):
194 | print("Input3")
195 | status = "settings"
196 | configure.last_status[0] = "mode_a"
197 |
198 | return status
199 |
200 | # governs the screen drawing of the entire program. Everything flows through Screen.
201 | # Screen instantiates a draw object and passes it the image background.
202 | # Screen monitors button presses and passes flags for interface updates to the draw object.
203 |
204 | class NokiaScreen(object):
205 |
206 | def __init__(self):
207 |
208 |
209 | self.input = Inputs()
210 | #---------------------------IMAGE LIBRARY STUFF------------------------------#
211 | # Create image buffer.
212 | # Load the background LCARS frame
213 | #image = Image.open('frame.ppm')#.convert('1')
214 |
215 |
216 |
217 | # instantiates an image and uses it in a draw object.
218 | self.image = Image.open('assets/frame.ppm').convert('1')
219 | self.draw = ImageDraw.Draw(self.image)
220 |
221 |
222 | self.frame = MultiFrame(self.draw,self.input)
223 | #self.graph = graphlist
224 |
225 |
226 | def push(self,sensors):
227 |
228 | self.frame.push(sensors)
229 | self.input.read()
230 | self.pixdrw()
231 | return "mode_a"
232 |
233 | def pixdrw(self):
234 |
235 | im = self.image.convert("L")
236 | im = PIL.ImageOps.invert(im)
237 | im = im.convert("1")
238 |
239 | if configure.pc:
240 | device.display(self.image)
241 | else:
242 | device.display(im)
243 |
--------------------------------------------------------------------------------
/lcars_clr.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # This module controls the st7735 type screens
3 | print("Loading 160x128 LCARS Interface")
4 | from objects import *
5 | import math
6 | import time
7 |
8 | from operator import itemgetter
9 |
10 | from display import GenericDisplay
11 |
12 | device = GenericDisplay()
13 |
14 | # Load up the image library stuff to help draw bitmaps to push to the screen
15 | import random
16 | import numpy
17 | import PIL.ImageOps
18 | from PIL import Image
19 | from PIL import ImageFont
20 | from PIL import ImageDraw
21 |
22 | # load the module that draws graphs
23 | from pilgraph import *
24 | from amg8833_pil import *
25 | from plars import *
26 |
27 |
28 | from modulated_em import *
29 |
30 | # Load default font.
31 | littlefont = ImageFont.truetype("assets/babs.otf",10)
32 | font = ImageFont.truetype("assets/babs.otf",13)
33 | titlefont = ImageFont.truetype("assets/babs.otf",16)
34 | bigfont = ImageFont.truetype("assets/babs.otf",20)
35 | giantfont = ImageFont.truetype("assets/babs.otf",30)
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 | # Standard LCARS colours
46 | lcars_orange = (255,153,0)
47 | lcars_pink = (204,153,204)
48 | lcars_blue = (153,153,204)
49 | lcars_red = (204,102,102)
50 | lcars_peach = (255,204,153)
51 | lcars_bluer = (153,153,255)
52 | lcars_orpeach = (255,153,102)
53 | lcars_pinker = (204,102,153)
54 |
55 | theme1 = [lcars_orange,lcars_blue,lcars_pinker]
56 |
57 | fore_col = 0
58 | back_col = 1
59 |
60 | # Controls text objects drawn to the LCD
61 | class LabelObj(object):
62 | def __init__(self,string, font, colour = lcars_blue):
63 | self.font = font
64 | #self.draw = draw
65 | self.string = string
66 | self.colour = colour
67 |
68 |
69 | def center(self,y,x,w,draw):
70 | size = self.font.getsize(self.string)
71 | xmid = x + w/2
72 |
73 | textposx = xmid - (size[0]/2)
74 |
75 | self.push(textposx,y,draw)
76 |
77 | def r_align(self,x,y,draw):
78 | size = self.font.getsize(self.string)
79 | self.push(x-size[0],y,draw)
80 |
81 | # Draws the label onto the provided draw buffer.
82 | def push(self,locx,locy,draw, string = "None"):
83 | if string == "None":
84 | drawstring = self.string
85 | else:
86 | drawstring = str(string)
87 | self.draw = draw
88 | self.draw.text((locx, locy), drawstring, font = self.font, fill= self.colour)
89 |
90 | def getsize(self):
91 | size = self.font.getsize(self.string)
92 | return size
93 |
94 | # a class to create a simple text list.
95 | # initialize with x/y coordinates
96 | # on update provide list of items to display, and draw object to draw to.
97 | class Label_List(object):
98 |
99 | def __init__(self, x, y, colour = None):
100 |
101 | #initial coordinates
102 | self.x = x
103 | self.y = y
104 |
105 | # used in the loop to offset y location of items.
106 | self.jump = 0
107 |
108 | #adjusts the increase in seperation
109 | self.spacer = 1
110 |
111 | # holds the items to display
112 | self.labels = []
113 |
114 | if colour == None:
115 | self.colour = lcars_orpeach
116 | else:
117 | self.colour = colour
118 |
119 |
120 | # draws the list of items as a text list.
121 | def update(self, items, draw):
122 | # clears label buffer.
123 | self.labels = []
124 |
125 | # for each item in the list of items to draw
126 | for index, item in enumerate(items):
127 |
128 | string = str(item)
129 | # create a text item with the string.
130 | thislabel = LabelObj(string, font, colour = self.colour)
131 | thislabel.push(self.x, self.y + self.jump,draw)
132 |
133 | # increase the y position by the height of the last item, plus spacer
134 | self.jump += (thislabel.getsize()[1] + self.spacer)
135 |
136 | # when loop is over reset jump counter.
137 | self.jump = 0
138 |
139 |
140 |
141 | class SelectableLabel(LabelObj):
142 |
143 |
144 | def __init__(self,font,draw,oper,colour = lcars_blue, special = 0):
145 | self.font = font
146 | self.draw = draw
147 | self.colour = colour
148 |
149 | # special determines the behaviour of the label for each type of oper
150 | # the class is supplied. There may be multiple types of int or boolean based
151 | # configuration parameters so this variable helps make new options
152 | self.special = special
153 |
154 | # coordinates
155 | self.x = 0
156 | self.y = 0
157 |
158 | # basic graphical parameters
159 | self.fontSize = 33
160 |
161 | # self.myfont = pygame.font.Font(titleFont, self.fontSize)
162 | # text = "Basic Item"
163 | # self.size = self.myfont.size(text)
164 |
165 | self.scaler = 3
166 | self.selected = False
167 | self.indicator = Image()
168 | self.content = "default"
169 |
170 | # this variable is a reference to a list stored in "objects.py"
171 | # containing either a boolean or an integer
172 | self.oper = oper
173 |
174 | def update(self, content, fontSize, nx, ny, fontType, color):
175 | self.x = nx
176 | self.y = ny
177 | self.content = content
178 | self.fontSize = fontSize
179 | self.myfont = pygame.font.Font(fontType, self.fontSize)
180 | self.color = color
181 | self.indicator.update(sliderb, nx - 23, ny+1)
182 |
183 | def toggle(self):
184 |
185 | # if the parameter supplied is a boolean just flip it.
186 | if isinstance(self.oper[0], bool):
187 | #toggle its state
188 | self.oper[0] = not self.oper[0]
189 |
190 | # if the parameter supplied is an integer its to change
191 | # one of the graphed sensors.
192 |
193 | elif isinstance(self.oper[0], int):
194 |
195 | # increment the integer.
196 | self.oper[0] += 1
197 |
198 | # if the integer is larger than the pool reset it
199 | if self.special == 1 and self.oper[0] > configure.max_sensors[0]-1:
200 | self.oper[0] = 0
201 |
202 |
203 | if self.special == 2 and self.oper[0] > (len(themes) - 1):
204 | self.oper[0] = 0
205 |
206 |
207 | return self.oper[0]
208 |
209 | def draw(self, surface):
210 | if self.selected:
211 | self.indicator.draw(surface)
212 |
213 | label = self.myfont.render(self.content, 1, self.color)
214 |
215 |
216 | status_text = "dummy"
217 | if self.special == 0:
218 | status_text = str(self.oper[0])
219 | elif self.special == 1:
220 | status_text = configure.sensor_info[self.oper[0]][3]
221 | elif self.special == 2:
222 | status_text = themenames[self.oper[0]]
223 |
224 | pos = resolution[0] - (self.get_size(status_text) + 37)
225 | state = self.myfont.render(status_text, 1, self.color)
226 |
227 |
228 | surface.blit(label, (self.x, self.y))
229 | surface.blit(state, (pos, self.y))
230 |
231 |
232 | class SettingsFrame(object):
233 | def __init__(self):
234 |
235 | self.pages = [["Sensor 1",configure.sensor1], ["Sensor 2", configure.sensor2], ["Sensor 3",configure.sensor3], ["Audio",configure.audio],["Alarm",configure.alarm], ["Auto Range",configure.auto], ["LEDs", configure.sleep],["Power Off","poweroff"]]
236 |
237 | # Sets the topleft origin of the graph
238 | self.graphx = 23
239 | self.graphy = 24
240 |
241 | self.status_raised = False
242 |
243 | # Sets the x and y span of the graph
244 | self.gspanx = 133
245 | self.gspany = 71
246 |
247 | self.selection = 0
248 |
249 | self.auto = configure.auto[0]
250 | self.interval = timer()
251 | self.interval.logtime()
252 | #self.draw = draw
253 | self.titlex = 2
254 | self.titley = 11
255 | self.labely = 114
256 |
257 | self.graphcycle = 0
258 | self.decimal = 1
259 |
260 | self.divider = 47
261 |
262 |
263 | # the set labels for the screen
264 | self.title = LabelObj("Settings",bigfont)
265 | self.itemlabel = LabelObj("Item Label",bigfont,colour = lcars_peach)
266 | self.item = LabelObj("No Data",titlefont,colour = lcars_pink)
267 |
268 | # three input cue labels
269 | self.A_Label = LabelObj("Next",font,colour = lcars_orpeach)
270 | self.B_Label = LabelObj("Enter",font, colour = lcars_orpeach)
271 | self.C_Label = LabelObj("Exit",font, colour = lcars_orpeach)
272 |
273 |
274 |
275 |
276 | # device needs to show multiple settings
277 | # first the sensor palette configuration
278 |
279 | def toggle(self,oper):
280 |
281 | # if the parameter supplied is a boolean
282 | if isinstance(oper[0], bool):
283 | #toggle its state
284 | oper[0] = not oper[0]
285 |
286 | #if the parameter supplied is an integer
287 | elif isinstance(oper[0], int):
288 |
289 | # increment the integer.
290 | oper[0] += 1
291 |
292 | # if the integer is larger than the pool
293 | if oper[0] > configure.max_sensors[0]-1:
294 | oper[0] = 0
295 |
296 | elif isinstance(oper, str):
297 | #configure.last_status[0] = configure.status[0]
298 | self.status_raised = True
299 | configure.status[0] = oper
300 |
301 |
302 | return oper
303 |
304 |
305 | def push(self, draw):
306 |
307 | status = "settings"
308 |
309 | if configure.eventready[0]:
310 | keys = configure.eventlist[0]
311 |
312 | if keys[0]:
313 | self.selection = self.selection + 1
314 | if self.selection > (len(self.pages) - 1):
315 | self.selection = 0
316 |
317 | if keys[1]:
318 | state = self.toggle(self.pages[self.selection][1])
319 |
320 | if self.status_raised:
321 | status = state
322 | self.status_raised = False
323 |
324 | if keys[2]:
325 | status = configure.last_status[0]
326 |
327 | configure.eventready[0] = False
328 |
329 |
330 |
331 | #draw the frame heading
332 | self.title.push(self.titlex,self.titley,draw)
333 |
334 |
335 | #draw the option item heading
336 | self.itemlabel.string = str(self.pages[self.selection][0])
337 | self.itemlabel.push(self.titlex+23,self.titley+25,draw)
338 |
339 | self.A_Label.push(2,self.labely,draw)
340 | self.B_Label.center(self.labely,23,114,draw)
341 | self.C_Label.r_align(156,self.labely,draw)
342 |
343 |
344 | #draw the 3 graph parameter items
345 | if self.selection == 0 or self.selection == 1 or self.selection == 2:
346 | self.item.string = str(configure.sensor_info[self.pages[self.selection][1][0]][0])
347 | self.item.push(self.titlex+23,self.titley+53,draw)
348 | else:
349 | if isinstance(self.pages[self.selection][1][0], bool):
350 | self.item.string = str(self.pages[self.selection][1][0])
351 | self.item.push(self.titlex+23,self.titley+53,draw)
352 |
353 |
354 | return status
355 |
356 | # a simple frame that tells the user that the picorder is loading another screen.
357 | class LoadingFrame(object):
358 |
359 | captions = ["working", "accessing", "initializing", "computing", "calculating"]
360 |
361 | def __init__(self):
362 | self.annunciator = LabelObj("Stand By",giantfont,colour = lcars_peach)
363 | self.caption = LabelObj("47",bigfont,colour = lcars_peach)
364 | self.titley = 20
365 | self.captiony = 65
366 |
367 | def push(self, draw, status):
368 |
369 | self.caption.string = random.choice(self.captions)
370 | self.annunciator.center(self.titley,0,160,draw)
371 | self.caption.center(self.captiony,0,160,draw)
372 |
373 | return status
374 |
375 | class StartUp(object):
376 | def __init__(self):
377 | self.titlex = 0
378 | self.titley = 77
379 | self.labely = 102
380 | self.jump = 22
381 |
382 | self.graphcycle = 0
383 | self.decimal = 1
384 |
385 | self.divider = 47
386 | self.labely = 102
387 |
388 |
389 | self.title = LabelObj("PicorderOS " + configure.version,bigfont, colour = lcars_peach)
390 | self.item = LabelObj(configure.boot_message,font,colour = lcars_peach)
391 |
392 | # creates and interval timer for screen refresh.
393 | self.interval = timer()
394 | self.interval.logtime()
395 |
396 | def push(self, draw):
397 |
398 |
399 | #draw the frame heading
400 | self.title.center(self.titley,0,160,draw)
401 |
402 | #draw the title and version
403 | self.item.center(self.titley+self.jump,0, 160,draw)
404 |
405 |
406 | if self.interval.timelapsed() > configure.boot_delay and configure.sensor_ready[0]:
407 | status = "mode_a"
408 | else:
409 | status = "startup"
410 |
411 |
412 | return status
413 |
414 | class PowerDown(object):
415 | def __init__(self):
416 |
417 | # Sets the topleft origin of the graph
418 | self.graphx = 23
419 | self.graphy = 24
420 |
421 | self.status_raised = False
422 |
423 | # Sets the x and y span of the graph
424 | self.gspanx = 133
425 | self.gspany = 71
426 |
427 | self.selection = 0
428 |
429 |
430 | self.auto = configure.auto[0]
431 | self.interval = timer()
432 | self.interval.logtime()
433 | #self.draw = draw
434 | self.titlex = 25
435 | self.titley = 6
436 | self.labely = 102
437 |
438 | self.graphcycle = 0
439 | self.decimal = 1
440 |
441 | self.divider = 47
442 | self.labely = 102
443 |
444 |
445 | self.title = LabelObj("CAUTION",bigfont, colour = lcars_red)
446 | self.itemlabel = LabelObj("Item Label",titlefont,colour = lcars_orange)
447 | self.A_Label = LabelObj("Yes",font,colour = lcars_blue)
448 | self.B_Label = LabelObj("Enter",font, colour = lcars_blue)
449 | self.C_Label = LabelObj("No",font, colour = lcars_blue)
450 |
451 | self.item = LabelObj("No Data",bigfont,colour = lcars_orpeach)
452 | # device needs to show multiple settings
453 | # first the sensor palette configuration
454 |
455 |
456 | def push(self, draw):
457 |
458 | #draw the frame heading
459 |
460 | self.title.center(self.titley,self.titlex,135,draw)
461 |
462 |
463 | #draw the option item heading
464 | #self.itemlabel.string = str(self.pages[self.selection][0])
465 | # self.itemlabel.push(self.titlex,self.titley+20,draw)
466 |
467 |
468 |
469 | self.A_Label.push(23,self.labely,draw)
470 |
471 |
472 | #self.B_Label.center(self.labely,23,135,draw)
473 |
474 |
475 | self.C_Label.r_align(156,self.labely,draw)
476 |
477 |
478 | #draw the 3 graph parameter items
479 |
480 | self.item.string = "Power Down?"
481 | self.item.center(self.titley+40, self.titlex, 135,draw)
482 |
483 |
484 | status = "poweroff"
485 |
486 |
487 | if configure.eventready[0]:
488 |
489 | keys = configure.eventlist[0]
490 |
491 | if keys[0]:
492 | status = "shutdown"
493 |
494 | if keys[1]:
495 | pass
496 |
497 | if keys[2]:
498 | status = "settings"
499 |
500 | configure.eventready[0] = False
501 |
502 |
503 | return status
504 |
505 | class EMFrame(object):
506 | def __init__(self):
507 |
508 | self.wifi = Wifi_Scan()
509 |
510 | self.graphcycle = 0
511 |
512 | # Sets the topleft origin of the graph
513 | self.graphx = 20
514 | self.graphy = 58
515 |
516 | # Sets the x and y span of the graph
517 | self.gspanx = 135
518 | self.gspany = 29
519 | self.titlex = 23
520 | self.titley = 2
521 |
522 | self.high = 0
523 | self.low = 0
524 | self.average = 0
525 | self.labely = 4
526 | self.labelxr = 156
527 |
528 |
529 | self.selection = 0
530 |
531 | # create our graph_screen
532 | self.Signal_Graph = graph_area(0,(self.graphx,self.graphy),(self.gspanx,self.gspany),self.graphcycle, lcars_pink, width = 2, type = 1, samples = 45)
533 |
534 | self.title = LabelObj("Modulated EM Scan",titlefont, colour = lcars_orange)
535 |
536 | self.signal_name = LabelObj("SSID",bigfont, colour = lcars_peach)
537 | self.signal_name_sm = LabelObj("SSID",font, colour = lcars_peach)
538 |
539 | self.signal_strength = LabelObj("ST",giantfont, colour = lcars_peach)
540 | self.signal_strength_sm = LabelObj("ST",littlefont, colour = lcars_peach)
541 |
542 | self.signal_frequency = LabelObj("FQ",titlefont, colour = lcars_orpeach)
543 | self.signal_frequency_sm = LabelObj("FQ",littlefont, colour = lcars_peach)
544 | self.signal_mac = LabelObj("MAC",font, colour = lcars_orpeach)
545 |
546 | self.list = Label_List(22,35, colour = lcars_peach)
547 |
548 | self.burgerfull = Image.open('assets/lcarsburgerframefull.png')
549 |
550 | def push(self, draw):
551 |
552 | status = "mode_b"
553 |
554 | # input handling
555 | if configure.eventready[0]:
556 | keys = configure.eventlist[0]
557 |
558 |
559 | # ------------- Input handling -------------- #
560 | if keys[0]:
561 | status = "mode_a"
562 | configure.eventready[0] = False
563 | return status
564 |
565 | if keys[1]:
566 | self.selection += 1
567 |
568 | if self.selection >= 3:
569 | self.selection = 0
570 |
571 |
572 | if keys[2]:
573 | status = "settings"
574 | configure.last_status[0] = "mode_b"
575 | configure.eventready[0] = False
576 | return status
577 |
578 | configure.eventready[0] = False
579 |
580 | self.wifi.update_plars()
581 |
582 | # details on strongest wifi network.
583 | if self.selection == 0:
584 |
585 | # grab EM data from plars
586 | info = plars.get_top_em_info()[0]
587 |
588 | # draw screen elements
589 | self.Signal_Graph.render(draw)
590 | self.title.string = "Dominant Transciever"
591 | self.title.r_align(self.labelxr,self.titley,draw)
592 |
593 | self.signal_name.push(20,35,draw, string = info[0])
594 |
595 | self.signal_strength.string = str(info[1]) + " DB"
596 | self.signal_strength.r_align(self.labelxr,92,draw)
597 | self.signal_frequency.push(20,92,draw, string = info[3])
598 | self.signal_mac.push(20,111, draw, string = info[6])
599 |
600 | #list of all wifi ssids
601 | if self.selection == 1:
602 |
603 | # list to hold the data labels
604 | list_for_labels = []
605 |
606 | # grab EM list
607 | em_list = plars.get_recent_em_list()
608 |
609 | #sort it so strongest is first
610 | sorted_em_list = sorted(em_list, key=itemgetter(1), reverse = True)
611 |
612 | # prepare a list of the data received for display
613 | for ssid in sorted_em_list:
614 | name = str(ssid[0])
615 | strength = str(ssid[1])
616 |
617 | label = strength + " dB • " + name
618 |
619 | list_for_labels.append(label)
620 | self.title.string = "Modulated EM Scan"
621 | self.title.r_align(self.labelxr,self.titley,draw)
622 | self.list.update(list_for_labels,draw)
623 |
624 | # assign each list element and its
625 |
626 | # frequency intensity map
627 | if self.selection == 2:
628 | # returns the data necessary for freq_intensity map with EM.
629 | # displays each SSID as a line segment. Its position along the x is
630 | # determined by frequency. Its height by its signal strength.
631 |
632 | # change Background
633 |
634 | #draw.rectangle((0,0,320,240),(0,0,0))
635 | draw._image = self.burgerfull
636 | #draw.bitmap((0,0), )
637 |
638 | #draw round rect background
639 | draw.rectangle((18,49,158,126), outline = lcars_blue)
640 |
641 | #draw labels
642 | self.title.string = "EM Channel Analysis"
643 | self.title.r_align(self.labelxr,self.titley,draw)
644 |
645 | #grab EM list
646 | unsorted_em_list = plars.get_recent_em_list()
647 |
648 | # sort it so strongest is first.
649 | em_list = sorted(unsorted_em_list, key=itemgetter(1), reverse = True)
650 |
651 | # create a list to hold just the info we need for the screen.
652 | items_list = []
653 |
654 | #filter info into items_list
655 | for ssid in em_list:
656 | name = str(ssid[0])
657 | strength = ssid[1]
658 | frequency = ssid[3]
659 | frequency = float(frequency.replace(' GHz', ''))
660 |
661 | # determing x coordinate
662 | screenpos = numpy.interp(frequency,(2.412, 2.462),(25, 151))
663 |
664 | # determine y coordinate
665 | lineheight = numpy.interp(strength, (-100, 0), (126, 55))
666 |
667 | # package into list
668 | this_ssid = (name,screenpos,lineheight,strength,frequency)
669 | items_list.append(this_ssid)
670 |
671 |
672 | #for each item in item_list
673 | for index, item in enumerate(items_list):
674 |
675 | # determine dot coordinates.
676 | cords = ((item[1],126),(item[1],item[2]))
677 | x1 = cords[1][0] - (6/2)
678 | y1 = cords[1][1] - (6/2)
679 | x2 = cords[1][0] + (6/2)
680 | y2 = cords[1][1] + (6/2)
681 |
682 | # if this is the strongest singal draw labels and change colour.
683 | if index == 0:
684 | draw.line(cords,lcars_peach,1)
685 | draw.ellipse([x1,y1,x2,y2],lcars_peach)
686 |
687 |
688 | name = item[0]
689 | trunc_name = name[:16] + (name[16:] and '..')
690 | # draw the strongest signals name, top center
691 | self.signal_name_sm.push(19,34,draw,string = trunc_name)
692 |
693 | # put strength at lower left
694 | strength_string = str(item[3]) + " DB"
695 | #self.signal_strength_sm.push(19,114,draw,string = strength_string)
696 |
697 | # put frequency at lower right
698 | self.signal_frequency_sm.string = str(item[4]) + " GHZ" + ", " + strength_string
699 | self.signal_frequency_sm.r_align(157,37,draw)
700 |
701 | # otherwise just draw the line and dot in the usual color
702 | else:
703 | draw.line(cords,lcars_bluer,1)
704 | draw.ellipse([x1,y1,x2,y2],lcars_bluer)
705 |
706 |
707 |
708 |
709 | return status
710 |
711 |
712 |
713 |
714 | # Controls the LCARS frame, measures the label and makes sure the top frame bar has the right spacing.
715 | class MultiFrame(object):
716 |
717 | def __init__(self):
718 |
719 | # Sets the topleft origin of the graph
720 | self.graphx = 21
721 | self.graphy = 25
722 | self.samples = configure.samples
723 |
724 | # Sets the x and y span of the graph
725 | self.gspanx = 133
726 | self.gspany = 69
727 |
728 | self.graphcycle = 0
729 |
730 | self.marginleft = 23
731 | self.marginright= 132
732 |
733 | # sets the background image for the display
734 | self.back = Image.open('assets/lcarsframe.png')
735 |
736 | # sets the currently selected sensor to focus on
737 | self.selection = 0
738 |
739 | # ties the auto state to the global object
740 | self.auto = configure.auto[0]
741 |
742 | # creates and interval timer for screen refresh.
743 | self.interval = timer()
744 | self.interval.logtime()
745 |
746 | # Sets the coordinates of onscreen labels.
747 | self.titlex = 23
748 | self.titley = 6
749 | self.labely = 102
750 |
751 |
752 |
753 | self.decimal = 1
754 |
755 | self.divider = 47
756 |
757 | # create our graph_screen
758 | self.A_Graph = graph_area(0,(self.graphx,self.graphy),(self.gspanx,self.gspany),self.graphcycle, theme1[0], width = 1)
759 |
760 | self.B_Graph = graph_area(1,(self.graphx,self.graphy),(self.gspanx,self.gspany),self.graphcycle, theme1[1], width = 1)
761 |
762 | self.C_Graph = graph_area(2,(self.graphx,self.graphy),(self.gspanx,self.gspany),self.graphcycle, theme1[2], width = 1)
763 |
764 | self.Graphs = [self.A_Graph, self.B_Graph, self.C_Graph]
765 |
766 | self.A_Label = LabelObj("a_string",font,colour = lcars_orange)
767 |
768 | self.B_Label = LabelObj("b_string",font, colour = lcars_blue)
769 |
770 | self.C_Label = LabelObj("c_string",font, colour = lcars_pinker)
771 |
772 | self.focus_Label = LabelObj("test",bigfont, colour = lcars_orpeach)
773 | self.focus_high_Label = LabelObj("test",font, colour = lcars_peach)
774 | self.focus_low_Label = LabelObj("test",font, colour = lcars_bluer)
775 | self.focus_mean_Label = LabelObj("test",font, colour = lcars_pinker)
776 |
777 | self.title = LabelObj("Multi-Graph",titlefont, colour = lcars_peach)
778 |
779 | def get_x(self):
780 | return self.gspanx - self.graphx
781 |
782 | # takes a value and sheds the second digit after the decimal place
783 | def arrangelabel(self,data,range = ".1f"):
784 | datareturn = format(float(data), range)
785 | return datareturn
786 |
787 | # defines the labels for the screen
788 | def labels(self):
789 |
790 | # depending on which number the "selection" variable takes on.
791 |
792 | if self.selection == 0:
793 | raw_a = str(self.A_Data)
794 | adjusted_a = self.arrangelabel(raw_a)
795 | a_string = adjusted_a + " " + configure.sensor_info[configure.sensor1[0]][2]
796 |
797 | raw_b = str(self.B_Data)
798 | adjusted_b = self.arrangelabel(raw_b)
799 | b_string = adjusted_b + " " + configure.sensor_info[configure.sensor2[0]][2]
800 |
801 | raw_c = str(self.C_Data)
802 | adjusted_c = self.arrangelabel(raw_c)
803 | c_string = adjusted_c + " " + configure.sensor_info[configure.sensor3[0]][2]
804 |
805 | self.A_Label.string = a_string
806 | self.A_Label.push(23,self.labely,self.draw)
807 |
808 | self.B_Label.string = b_string
809 | self.B_Label.center(self.labely,23,135,self.draw)
810 |
811 | self.C_Label.string = c_string
812 | self.C_Label.r_align(156,self.labely,self.draw)
813 |
814 | # displays more details for whatever sensor is in focus
815 | if self.selection != 0:
816 |
817 | carousel = [self.A_Data,self.B_Data,self.C_Data]
818 |
819 | this = self.selection - 1
820 |
821 | this_bundle = self.Graphs[this]
822 |
823 | raw = str(carousel[this])
824 |
825 | adjusted = self.arrangelabel(raw, '.2f')
826 | self.focus_Label.string = adjusted
827 | self.focus_Label.r_align(156,self.titley,self.draw)
828 |
829 | self.focus_high_Label.string = "max " + self.arrangelabel(str(this_bundle.get_high()), '.1f')
830 | self.focus_high_Label.push(23,self.labely,self.draw)
831 |
832 | self.focus_low_Label.string = "min " + self.arrangelabel(str(this_bundle.get_low()), '.1f')
833 | self.focus_low_Label.center(self.labely,23,135,self.draw)
834 |
835 | self.focus_mean_Label.string = "x- " + self.arrangelabel(str(this_bundle.get_average()), '.1f')
836 | self.focus_mean_Label.r_align(156,self.labely,self.draw)
837 |
838 |
839 | # push the image frame and contents to the draw object.
840 | def push(self,draw):
841 |
842 |
843 |
844 | # returns mode_a to the main loop unless something causes state change
845 | status = "mode_a"
846 |
847 |
848 | if configure.eventready[0]:
849 | keys = configure.eventlist[0]
850 |
851 | # if a key is registering as pressed increment or rollover the selection variable.
852 | if keys[0]:
853 |
854 | configure.eventready[0] = False
855 |
856 | self.selection += 1
857 | if self.selection > 3:
858 | self.selection = 0
859 | status = "mode_c"
860 | return status
861 |
862 | if keys[1]:
863 | status = "mode_b"
864 | configure.eventready[0] = False
865 | return status
866 |
867 | if keys[2]:
868 | configure.last_status[0] = "mode_a"
869 | status = "settings"
870 | configure.eventready[0] = False
871 | return status
872 |
873 | configure.eventready[0] = False
874 |
875 |
876 | # passes the current bitmap buffer to the object incase someone else needs it.
877 | self.draw = draw
878 |
879 | senseslice = []
880 | data_a = []
881 | data_b = []
882 | data_c = []
883 | datas = [data_a,data_b,data_c]
884 |
885 | for i in range(3):
886 |
887 | # determines the sensor keys for each of the three main sensors
888 | this_index = int(configure.sensors[i][0])
889 |
890 | dsc,dev,sym,maxi,mini = configure.sensor_info[this_index]
891 |
892 | datas[i] = plars.get_recent(dsc,dev,num = 1)
893 |
894 |
895 |
896 | if len(datas[i]) == 0:
897 | datas[i] = [47]
898 |
899 | item = datas[i]
900 |
901 | senseslice.append([item[-1], dsc, dev, sym, mini, maxi])
902 |
903 |
904 |
905 | # Grabs the current sensor reading
906 | self.A_Data = senseslice[0][0]#configure.sensor_data[configure.sensor1[0]][0]
907 | self.B_Data = senseslice[1][0]#configure.sensor_data[configure.sensor2[0]][0]
908 | self.C_Data = senseslice[2][0]#configure.sensor_data[configure.sensor3[0]][0]
909 |
910 |
911 |
912 |
913 | # Draws the Title
914 | if self.selection != 0:
915 | this = self.selection - 1
916 | self.title.string = senseslice[this][1]
917 | else:
918 | self.title.string = "Multi-Graph"
919 |
920 | self.title.push(self.titlex,self.titley,draw)
921 |
922 |
923 | # turns each channel on individually
924 | if self.selection == 0:
925 |
926 | self.C_Graph.render(self.draw)
927 | self.B_Graph.render(self.draw)
928 | self.A_Graph.render(self.draw)
929 |
930 |
931 |
932 | if self.selection == 1:
933 | self.A_Graph.render(self.draw)
934 |
935 | if self.selection == 2:
936 | self.B_Graph.render(self.draw)
937 |
938 | if self.selection == 3:
939 | self.C_Graph.render(self.draw)
940 |
941 |
942 | self.labels()
943 |
944 |
945 |
946 |
947 | return status
948 | # governs the screen drawing of the entire program. Everything flows through Screen.
949 | # Screen instantiates a draw object and passes it the image background.
950 | # Screen monitors button presses and passes flags for interface updates to the draw object.
951 |
952 | class ThermalFrame(object):
953 | def __init__(self):
954 | # Sets the topleft origin of the graph
955 | self.graphx = 23
956 | self.graphy = 24
957 |
958 | # Sets the x and y span of the graph
959 | self.gspanx = 133
960 | self.gspany = 71
961 | self.t_grid = ThermalGrid(23,24,133,71)
962 | self.t_grid_full = ThermalGrid(23,8,133,103)
963 | self.titlex = 23
964 | self.titley = 6
965 |
966 | self.high = 0
967 | self.low = 0
968 | self.average = 0
969 | self.labely = 102
970 |
971 | self.selection = 0
972 |
973 | self.title = LabelObj("Thermal Array",titlefont)
974 |
975 | self.A_Label = LabelObj("No Data",font,colour = lcars_blue)
976 | self.B_Label = LabelObj("No Data",font, colour = lcars_pinker)
977 | self.C_Label = LabelObj("No Data",font, colour = lcars_orange)
978 |
979 |
980 | # this function takes a value and sheds the second digit after the decimal place
981 | def arrangelabel(self,data):
982 | datareturn = format(float(data), '.0f')
983 | return datareturn
984 |
985 | def labels(self):
986 |
987 | if self.selection == 0:
988 | raw_a = str(self.low)
989 | adjusted_a = self.arrangelabel(raw_a)
990 | a_string = "Low: " + adjusted_a
991 | self.A_Label.string = a_string
992 | self.A_Label.push(23,self.labely,self.draw)
993 |
994 | if self.selection == 0:
995 | raw_b = str(self.high)
996 | adjusted_b = self.arrangelabel(raw_b)
997 | self.B_Label.string = "High: " + adjusted_b
998 | self.B_Label.center(self.labely,23,135, self.draw)
999 |
1000 | if self.selection == 0:
1001 | raw_c = str(self.average)
1002 | adjusted_c = self.arrangelabel(raw_c)
1003 | self.C_Label.string = "Avg: " + adjusted_c
1004 | self.C_Label.r_align(156,self.labely,self.draw)
1005 |
1006 | def push(self, draw):
1007 |
1008 | status = "mode_c"
1009 |
1010 | if configure.eventready[0]:
1011 | keys = configure.eventlist[0]
1012 |
1013 |
1014 | # ------------- Input handling -------------- #
1015 | if keys[0]:
1016 | self.selection += 1
1017 | if self.selection > 2:
1018 | self.selection = 0
1019 | status = "mode_a"
1020 | return status
1021 | configure.eventready[0] = False
1022 |
1023 |
1024 | if keys[1]:
1025 | configure.eventready[0] = False
1026 | status = "mode_b"
1027 | return status
1028 |
1029 | if keys[2]:
1030 | status = "settings"
1031 | configure.last_status[0] = "mode_c"
1032 | configure.eventready[0] = False
1033 | return status
1034 |
1035 | configure.eventready[0] = False
1036 |
1037 |
1038 | self.draw = draw
1039 | self.labels()
1040 |
1041 | # Draw title
1042 |
1043 | self.title.push(self.titlex,self.titley, self.draw)
1044 |
1045 |
1046 | if self.selection == 0:
1047 | self.average,self.high,self.low = self.t_grid.update()
1048 | if self.selection == 1:
1049 | self.average,self.high,self.low = self.t_grid_full.update()
1050 |
1051 | if not configure.alarm_ready[0]:
1052 | if self.high >= configure.TEMP_ALERT[1]:
1053 | configure.alarm_ready[0] = True
1054 | if self.low <= configure.TEMP_ALERT[0]:
1055 | configure.alarm_ready[0] = True
1056 |
1057 | if self.selection == 0:
1058 | self.t_grid.push(draw)
1059 | elif self.selection == 1:
1060 | self.t_grid_full.push(draw)
1061 | elif self.selection == 2:
1062 | self.selection = 0
1063 | status = "mode_a"
1064 |
1065 | return status
1066 |
1067 | class ColourScreen(object):
1068 |
1069 | def __init__(self):
1070 |
1071 | if configure.display == 2:
1072 | self.surface = TFT.draw()
1073 |
1074 | # instantiates an image and uses it in a draw object.
1075 | self.image = Image.open('assets/lcarsframe.png')
1076 | self.blankimage = Image.open('assets/lcarsframeblank.png')
1077 | self.tbar = Image.open('assets/lcarssplitframe.png')
1078 | self.burger = Image.open('assets/lcarsburgerframe.png')
1079 | self.burgerfull = Image.open('assets/lcarsburgerframefull.png')
1080 | # Load assets
1081 | self.logo = Image.open('assets/picorderOS_logo.png')
1082 |
1083 | self.status = "mode_a"
1084 |
1085 | self.multi_frame = MultiFrame()
1086 | self.settings_frame = SettingsFrame()
1087 | self.thermal_frame = ThermalFrame()
1088 | self.powerdown_frame = PowerDown()
1089 | self.em_frame = EMFrame()
1090 | self.startup_frame = StartUp()
1091 | self.loading_frame = LoadingFrame()
1092 |
1093 |
1094 | def get_size(self):
1095 | return self.multi_frame.samples
1096 |
1097 | def start_up(self):
1098 | self.newimage = self.burgerfull.copy()
1099 | self.newimage.paste(self.logo,(59,15))
1100 | self.draw = ImageDraw.Draw(self.newimage)
1101 | self.status = self.startup_frame.push(self.draw)
1102 | self.pixdrw()
1103 |
1104 | return self.status
1105 |
1106 | # simple frame to let user know new info is loading while waiting.
1107 | def loading(self):
1108 | self.newimage = self.burgerfull.copy()
1109 | self.draw = ImageDraw.Draw(self.newimage)
1110 | self.status = self.loading_frame.push(self.draw,self.status)
1111 |
1112 | self.pixdrw()
1113 | return self.status
1114 |
1115 | def graph_screen(self):
1116 | self.newimage = self.image.copy()
1117 | self.draw = ImageDraw.Draw(self.newimage)
1118 |
1119 | last_status = self.status
1120 | self.status = self.multi_frame.push(self.draw)
1121 |
1122 | if self.status == last_status:
1123 | self.pixdrw()
1124 | else:
1125 | self.loading()
1126 |
1127 | return self.status
1128 |
1129 | def em_screen(self):
1130 | self.newimage = self.tbar.copy()
1131 | self.draw = ImageDraw.Draw(self.newimage)
1132 | last_status = self.status
1133 | self.status = self.em_frame.push(self.draw)
1134 |
1135 | if self.status == last_status:
1136 | self.pixdrw()
1137 | else:
1138 | self.loading()
1139 | return self.status
1140 |
1141 | def thermal_screen(self):
1142 | self.newimage = self.image.copy()
1143 | self.draw = ImageDraw.Draw(self.newimage)
1144 |
1145 | last_status = self.status
1146 |
1147 | self.status = self.thermal_frame.push(self.draw)
1148 |
1149 | if self.status == last_status:
1150 | self.pixdrw()
1151 | else:
1152 | self.loading()
1153 |
1154 | return self.status
1155 |
1156 | def settings(self):
1157 | self.newimage = self.burger.copy()
1158 | self.draw = ImageDraw.Draw(self.newimage)
1159 | last_status = self.status
1160 | self.status = self.settings_frame.push(self.draw)
1161 |
1162 | if self.status == last_status:
1163 | self.pixdrw()
1164 | else:
1165 | self.loading()
1166 | return self.status
1167 |
1168 | def powerdown(self):
1169 | self.newimage = self.blankimage.copy()
1170 | self.draw = ImageDraw.Draw(self.newimage)
1171 | self.status = self.powerdown_frame.push(self.draw)
1172 | self.pixdrw()
1173 | return self.status
1174 |
1175 | def pixdrw(self):
1176 | thisimage = self.newimage.convert(mode = "RGB")
1177 | device.display(thisimage)
1178 |
--------------------------------------------------------------------------------
/leds.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | print("Loading Unified Indicator Module")
3 | # Provides a surface for control over the LEDs connected via GPIO. For the tr-tr108
4 | # LEDs are controlled directly from GPIO, for the tr109 a shift register is used
5 |
6 | from objects import *
7 | import time
8 | import math
9 |
10 | #loads parameters for configurations
11 | interval = configure.LED_TIMER
12 | timer = timer()
13 |
14 |
15 |
16 | # External module import
17 | if not configure.pc:
18 | import RPi.GPIO as GPIO
19 |
20 | if configure.sensehat:
21 | # instantiates and defines paramaters for the sensehat
22 |
23 | from sense_hat import SenseHat
24 |
25 | # instantiate a sensehat object,
26 | sensehat = SenseHat()
27 |
28 | # Initially clears the LEDs once loaded
29 | sensehat.clear()
30 |
31 | # Prepares an array of 64 pixel triplets for the Sensehat moire display
32 | moire=[[0 for x in range(3)] for x in range(64)]
33 |
34 |
35 | # a list of the shift register pin data, for loop purposes (main board, sensor board).
36 | # Pulls the pin assignments from the objects.py configuration object.
37 | PINS = [[configure.PIN_DATA,configure.PIN_LATCH,configure.PIN_CLOCK],[configure.PIN_DATA2,configure.PIN_LATCH2,configure.PIN_CLOCK2]]
38 |
39 | # set the mode of the shift register pins
40 | GPIO.setmode(GPIO.BCM)
41 | GPIO.setup(configure.PIN_DATA, GPIO.OUT)
42 | GPIO.setup(configure.PIN_LATCH, GPIO.OUT)
43 | GPIO.setup(configure.PIN_CLOCK, GPIO.OUT)
44 | GPIO.setup(configure.PIN_DATA2, GPIO.OUT)
45 | GPIO.setup(configure.PIN_LATCH2, GPIO.OUT)
46 | GPIO.setup(configure.PIN_CLOCK2, GPIO.OUT)
47 |
48 | # loads the pin configurations and modes for the tr-108 (3 leds)
49 | if configure.tr108:
50 |
51 | led1 = 4
52 | led2 = 17
53 | led3 = 27
54 |
55 | GPIO.setmode(GPIO.BCM) # Broadcom pin-numbering scheme
56 | GPIO.setup(led1, GPIO.OUT) # LED pin set as output
57 | GPIO.setup(led2, GPIO.OUT) # LED pin set as output
58 | GPIO.setup(led3, GPIO.OUT) # LED pin set as output
59 |
60 | # loads the pin configurations and modes for the tr-109 (shift register based)
61 | if configure.tr109:
62 | # Pin Definitons:
63 | led1 = 16 #19 # Broadcom pin 19
64 | led2 = 20 #6 # Broadcom pin 13
65 | led3 = 6 #20
66 | led4 = 19 #16
67 | sc_led = 15
68 |
69 | # Pin Setup:
70 | # Set broadcom pin mode
71 | GPIO.setmode(GPIO.BCM)
72 |
73 | # Assign the
74 | GPIO.setup(configure.PIN_DATA, GPIO.OUT)
75 | GPIO.setup(configure.PIN_LATCH, GPIO.OUT)
76 | GPIO.setup(configure.PIN_CLOCK, GPIO.OUT)
77 |
78 | GPIO.setup(configure.PIN_DATA2, GPIO.OUT)
79 | GPIO.setup(configure.PIN_LATCH2, GPIO.OUT)
80 | GPIO.setup(configure.PIN_CLOCK2, GPIO.OUT)
81 |
82 | GPIO.setup(sc_led, GPIO.OUT)
83 |
84 |
85 | # delivers data to the shift register
86 | # can use alternate set of shift pins using the "board" argument
87 | def shiftout(byte,board = 0):
88 |
89 | # brings the latch low
90 | GPIO.output(PINS[board][1], 0)
91 |
92 | for x in range(8):
93 | # Assigns logic to the pin based on the bit x
94 | GPIO.output(PINS[board][0], (byte >> x) & 1)
95 |
96 | # toggle the clock, first set the pin because it fails if I don't
97 | GPIO.setup(PINS[board][2], GPIO.OUT)
98 | GPIO.output(PINS[board][2], 1)
99 | GPIO.output(PINS[board][2], 0)
100 |
101 | GPIO.output(PINS[board][1], 1)
102 |
103 |
104 |
105 |
106 | # a function to clear the gpio
107 | def cleangpio():
108 | resetleds()
109 | GPIO.cleanup() # cleanup all GPIO
110 |
111 | # a function to clear the LEDs
112 | def resetleds():
113 |
114 | if configure.tr109:
115 | shiftout(0)
116 | shiftout(0, board = 1)
117 |
118 | if configure.tr108:
119 | GPIO.output(led1, GPIO.LOW)
120 | GPIO.output(led2, GPIO.LOW)
121 | GPIO.output(led3, GPIO.LOW)
122 |
123 |
124 |
125 |
126 | # # The following set of functions are for activating each LED individually.
127 | # # I figured it was easier than having different functions for different combinations.
128 | # # This way you can just manually set them as you please.
129 | def screen_on():
130 | if configure.tr109:
131 | if configure.display == 1:
132 | GPIO.output(sc_led, GPIO.HIGH)
133 |
134 | def leda_on():
135 | GPIO.output(led1, GPIO.HIGH)
136 |
137 | def ledb_on():
138 | GPIO.output(led2, GPIO.HIGH)
139 |
140 | def ledc_on():
141 | GPIO.output(led3, GPIO.HIGH)
142 |
143 | def ledd_on():
144 | GPIO.output(led4, GPIO.HIGH)
145 |
146 | def leda_off():
147 | GPIO.output(led1, GPIO.LOW)
148 |
149 | def ledb_off():
150 | GPIO.output(led2, GPIO.LOW)
151 |
152 | def ledc_off():
153 | GPIO.output(led3, GPIO.LOW)
154 |
155 | def ledd_off():
156 | GPIO.output(led4, GPIO.LOW)
157 | #
158 | def screen_off():
159 | if configure.tr109:
160 | GPIO.output(sc_led, GPIO.LOW)
161 |
162 |
163 | # The following class drives the ABGD ripple animation for the tr-109.
164 | class ripple(object):
165 |
166 | def __init__(self):
167 | self.beat = 0
168 | self.disabled = False
169 | self.statuswas = False
170 | self.lights = True
171 | self.ticks = 0
172 |
173 |
174 | def cycle(self):
175 |
176 | # because the tr-109 uses a shift register to drive its indicator LEDs
177 | # each frame of LED animations is represented by a byte, with the LEDs
178 | # being arranged as follows:
179 |
180 | # 0 0 0 0 0 0 0 0
181 | # a b d g pwr a1 b1 d1
182 |
183 | # the basic ripple animation is as follows.
184 | #140
185 | #74
186 | #41
187 | #26
188 |
189 | # if sleep detection is active:
190 | if configure.sleep[0]:
191 | #check if the door is open.
192 | if configure.dr_open[0]:
193 | # if it wasn't open last time.
194 | if self.statuswas != configure.dr_open[0]:
195 | # turn on our screen
196 | screen_on()
197 | # engage the lights
198 | self.lights = True
199 | self.statuswas = configure.dr_open[0]
200 | else:
201 | screen_off()
202 | self.lights = False
203 | self.statuswas = configure.dr_open[0]
204 | else:
205 | screen_on()
206 |
207 |
208 | # if lights are engaged this block of code will run the animation, or else
209 | # turn them off.
210 | if self.lights:
211 |
212 | if configure.tr109:
213 | if self.beat > 3:
214 | self.beat = 0
215 |
216 | if self.beat == 0:
217 | shiftout(140)
218 | shiftout(140,board = 1)
219 |
220 | if self.beat == 1:
221 | shiftout(74)
222 | shiftout(74,board = 1)
223 |
224 | if self.beat == 2:
225 | shiftout(41)
226 | shiftout(41, board = 1)
227 |
228 | if self.beat == 3:
229 | shiftout(26)
230 | shiftout(26, board = 1)
231 |
232 | self.beat += 1
233 |
234 | else:
235 | if configure.tr109:
236 | shiftout(0)
237 | shiftout(0,board =1)
238 |
239 |
240 | if configure.sensehat and configure.moire[0]:
241 | cxtick = 0.5 * math.sin(self.ticks/15.0) # change this line
242 | cytick = 0.5 * math.cos(self.ticks/8.0) #change this line
243 |
244 | for x in range(8):
245 | for y in range(8):
246 | # it's this cool plasma effect from demoscene I stole from
247 | # somewhere.
248 | cx = x + cxtick #change this line
249 | cy = y + cytick #change this line
250 | v = math.sin(math.sqrt(1.0*(math.pow(cy, 2.0)+math.pow(cx, 2.0))+1.0)+self.ticks)
251 | v = (v + 1.0)/2.0
252 | v = int(v*255.0)
253 |
254 |
255 | # Pack the computed pixel into the moire pixel list
256 | moire[(x*8)+y]=[v,v,v]
257 |
258 | sensehat.set_pixels(moire)
259 | self.ticks += 1
260 |
261 |
262 |
263 | # function to handle lights as a seperate thread.
264 | def ripple_async():
265 |
266 | thread_rip = ripple()
267 |
268 | while not configure.status[0] == "quit":
269 |
270 | if timer.timelapsed() > interval:
271 | thread_rip.cycle()
272 | timer.logtime()
273 |
274 | # start the ripple routine
275 | # have a state variables
276 | # received commands asynchronously "start" "stop"
277 | # quit when asked.
278 |
--------------------------------------------------------------------------------
/lib_tft24T.py:
--------------------------------------------------------------------------------
1 | # tft24T V0.3 April 2015 Brian Lavery TJCTM24024-SPI 2.4 inch Touch 320x240 SPI LCD
2 | #
3 | # Permission is hereby granted, free of charge, to any person obtaining a copy
4 | # of this software and associated documentation files (the "Software"), to deal
5 | # in the Software without restriction, including without limitation the rights
6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | # copies of the Software, and to permit persons to whom the Software is
8 | # furnished to do so.
9 |
10 | # Calibration scaling values from the calibration utility:
11 | # Makes touchscreen coordinates agree with TFT coordinates (240x320) for YOUR device
12 | # You may need to adjust these values for YOUR device
13 | calib_scale240 = 272
14 | calib_scale320 = -364
15 | calib_offset240 = 16
16 | calib_offset320 = -354
17 |
18 | margin = 13
19 | # "margin" is a no-go perimeter (in pixels). [Stylus at very edge of touchscreen is rather jitter-prone.]
20 |
21 |
22 | import numbers
23 | import time
24 |
25 | from PIL import Image
26 | from PIL import ImageDraw
27 | import textwrap
28 |
29 | from types import MethodType
30 |
31 | # Constants for interacting with display registers.
32 | ILI9341_TFTWIDTH = 240
33 | ILI9341_TFTHEIGHT = 320
34 |
35 | ILI9341_SWRESET = 0x01
36 | ILI9341_SLPOUT = 0x11
37 | ILI9341_INVOFF = 0x20
38 | ILI9341_INVON = 0x21
39 | ILI9341_GAMMASET = 0x26
40 | ILI9341_DISPON = 0x29
41 | ILI9341_CASET = 0x2A
42 | ILI9341_PASET = 0x2B
43 | ILI9341_RAMWR = 0x2C
44 | ILI9341_RAMRD = 0x2E
45 | ILI9341_MADCTL = 0x36
46 | ILI9341_PIXFMT = 0x3A
47 | ILI9341_FRMCTR1 = 0xB1
48 | ILI9341_DFUNCTR = 0xB6
49 |
50 | ILI9341_PWCTR1 = 0xC0
51 | ILI9341_PWCTR2 = 0xC1
52 | ILI9341_VMCTR1 = 0xC5
53 | ILI9341_VMCTR2 = 0xC7
54 | ILI9341_GMCTRP1 = 0xE0
55 | ILI9341_GMCTRN1 = 0xE1
56 |
57 |
58 | Buffer = None
59 | # textrotated custom method for our "draw" cannot find TFT's canvas buffer if it is not global.
60 | # This method obviously precludes multiple instances of TFT running independently,
61 | # but we use both CE0/CE1 of the Raspberry Pi anyway, so how could we have another display?
62 | # (Want the second SPI of the RPI2? - Not considered in this library)
63 |
64 | class TFT24T():
65 | def __init__(self, spi, gpio, landscape=False):
66 |
67 |
68 | self.is_landscape = landscape
69 | self._spi = spi
70 | self._gpio = gpio
71 |
72 | # TOUCHSCREEN HARDWARE PART
73 |
74 | # ads7843 max spi speed 2 MHz?
75 | X = 0xD0
76 | Y = 0x90
77 | Z1 = 0xB0
78 | Z2 = 0xC0
79 |
80 | def initTOUCH(self, pen, ce=0, spi_speed=100000):
81 | self._ce_tch = ce
82 | self._spi_speed_tch=spi_speed
83 | self._pen = pen
84 | self._gpio.setup(pen, self._gpio.IN)
85 |
86 | def penDown(self):
87 | # reads True when stylus is in contact
88 | return not self._gpio.input(self._pen)
89 |
90 | def readValue(self, channel):
91 | # self._spi.open(0, self._ce_tch)
92 | self._spi.open(1,self._ce_tch)
93 | self._spi.max_speed_hz=self._spi_speed_tch
94 |
95 | responseData = self._spi.xfer([channel , 0, 0])
96 | self._spi.close()
97 | return (responseData[1] << 5) | (responseData[2] >> 3)
98 | # Pick off the 12-bit reply
99 |
100 | def penPosition(self):
101 | self.readValue(self.X)
102 | self.readValue(self.Y)
103 | self.readValue(self.X)
104 | self.readValue(self.Y)
105 | # burn those
106 | x=0
107 | y=0
108 | for k in range(10):
109 | x += self.readValue(self.X)
110 | y += self.readValue(self.Y)
111 | # average those
112 | x = x / 10
113 | y = y / 10
114 | # empirically set calibration factors:
115 | x2 = (4096 -x) * calib_scale240 / 4096 -calib_offset240
116 | y2 = y * calib_scale320 / 4096 - calib_offset320
117 | # So far, these co-ordinates are the native portrait mode
118 |
119 | if y2(319-margin) or x2 (239-margin):
120 | x2=0
121 | y2=0
122 | # The fringes of touchscreen give a lot of erratic/spurious results
123 | # Don't allow fringes to return anything.
124 | # (Also, user should not program hotspots/icons out in the margin, to discourage pointing pen there)
125 | # a return of (0,0) is considered a nul return
126 |
127 | if self.is_landscape:
128 | # rotate the coordinates
129 | x3 = x2
130 | x2 = 319-y2
131 | y2 = x3
132 | return [x2, y2]
133 |
134 |
135 | # TFT/LCD part
136 |
137 | def send2lcd(self, data, is_data=True, chunk_size=4096):
138 |
139 | # Set DC low for command, high for data.
140 | self._gpio.output(self._dc, is_data)
141 | self._spi.open(0, self._ce_lcd)
142 | self._spi.max_speed_hz=self._spi_speed_lcd
143 |
144 | # Convert scalar argument to list so either can be passed as parameter.
145 | if isinstance(data, numbers.Number):
146 | data = [data & 0xFF]
147 | # Write data a chunk at a time.
148 | for start in range(0, len(data), chunk_size):
149 | end = min(start+chunk_size, len(data))
150 | self._spi.writebytes(data[start:end])
151 | self._spi.close()
152 |
153 | def command(self, data):
154 | """Write a byte or array of bytes to the display as command data."""
155 | self.send2lcd(data, False)
156 |
157 | def data(self, data):
158 | """Write a byte or array of bytes to the display as display data."""
159 | self.send2lcd(data, True)
160 |
161 | def resetlcd(self):
162 | if self._rst is not None:
163 | self._gpio.output(self._rst, self._gpio.HIGH)
164 | time.sleep(0.005)
165 | self._gpio.output(self._rst, self._gpio.LOW)
166 | time.sleep(0.02)
167 | self._gpio.output(self._rst, self._gpio.HIGH)
168 | time.sleep(0.150)
169 | else:
170 | self.command(ILI9341_SWRESET)
171 | sleep(1)
172 |
173 | def _init9341(self):
174 | self.command(ILI9341_PWCTR1)
175 | self.data(0x23)
176 | self.command(ILI9341_PWCTR2)
177 | self.data(0x10)
178 | self.command(ILI9341_VMCTR1)
179 | self.data([0x3e, 0x28])
180 | self.command(ILI9341_VMCTR2)
181 | self.data(0x86)
182 | self.command(ILI9341_MADCTL)
183 | self.data(0x48)
184 | self.command(ILI9341_PIXFMT)
185 | self.data(0x55)
186 | self.command(ILI9341_FRMCTR1)
187 | self.data([0x00, 0x18])
188 | self.command(ILI9341_DFUNCTR)
189 | self.data([0x08, 0x82, 0x27])
190 | self.command(0xF2)
191 | self.data(0x00)
192 | self.command(ILI9341_GAMMASET)
193 | self.data(0x01)
194 | self.command(ILI9341_GMCTRP1)
195 | self.data([0x0F, 0x31, 0x2b, 0x0c, 0x0e, 0x08, 0x4e, 0xf1, 0x37, 0x07, 0x10, 0x03, 0x0e, 0x09, 0x00])
196 | self.command(ILI9341_GMCTRN1)
197 | self.data([0x00, 0x0e, 0x14, 0x03, 0x11, 0x07, 0x31, 0xc1, 0x48, 0x08, 0x0f, 0x0c, 0x31, 0x36, 0x0f])
198 | self.command(ILI9341_SLPOUT)
199 | time.sleep(0.120)
200 | self.command(ILI9341_DISPON)
201 |
202 | def initLCD(self, dc=None, rst=None, led=None, ce=0, spi_speed=32000000):
203 | global Buffer
204 | self._dc = dc
205 | self._rst = rst
206 | self._led = led
207 | self._ce_lcd = ce
208 | self._spi_speed_lcd=spi_speed
209 | # Set DC as output.
210 | self._gpio.setup(dc, self._gpio.OUT)
211 | # Setup reset as output (if provided).
212 | if rst is not None:
213 | self._gpio.setup(rst, self._gpio.OUT)
214 | if led is not None:
215 | self._gpio.setup(led, self._gpio.OUT)
216 | self._gpio.output(led, self._gpio.HIGH)
217 |
218 | # Create an image buffer.
219 | if self.is_landscape:
220 | Buffer = Image.new('RGB', (ILI9341_TFTHEIGHT, ILI9341_TFTWIDTH))
221 | else:
222 | Buffer = Image.new('RGB', (ILI9341_TFTWIDTH, ILI9341_TFTHEIGHT))
223 | # and a backup buffer for backup/restore
224 | self.buffer2 = Buffer.copy()
225 | self.resetlcd()
226 | self._init9341()
227 |
228 | def set_frame(self, x0=0, y0=0, x1=None, y1=None):
229 |
230 | if x1 is None:
231 | x1 = ILI9341_TFTWIDTH-1
232 | if y1 is None:
233 | y1 = ILI9341_TFTHEIGHT-1
234 | self.command(ILI9341_CASET) # Column addr
235 | self.data([x0 >> 8, x0, x1 >> 8, x1])
236 | self.command(ILI9341_PASET) # Row addr
237 | self.data([y0 >> 8, y0, y1 >> 8, y1])
238 | self.command(ILI9341_RAMWR)
239 |
240 | def display(self, image=None):
241 | """Write the display buffer or provided image to the hardware. If no
242 | image parameter is provided the display buffer will be written to the
243 | hardware. If an image is provided, it should be RGB format and the
244 | same dimensions as the display hardware.
245 | """
246 | # By default write the internal buffer to the display.
247 | if image is None:
248 | image = Buffer
249 | if image.size[0] == 320:
250 | image = image.rotate(90)
251 |
252 | # Set address bounds to entire display.
253 | self.set_frame()
254 | # Convert image to array of 16bit 565 RGB data bytes.
255 | pixelbytes = list(self.image_to_data(image))
256 | # Write data to hardware.
257 | self.data(pixelbytes)
258 |
259 | def penprint(self, position, size, color=(0,0,0) ):
260 | x=position[0]
261 | y=position[1]
262 | if self.is_landscape:
263 | # rotate the coordinates. The intrinsic hardware is portrait
264 | x3 = x
265 | x = y
266 | y = 319-x3
267 | self.set_frame(x, y-size, x+size, y+size)
268 | pixelbytes=[0]*(size*size*8)
269 | self.data(pixelbytes)
270 |
271 |
272 | def clear(self, color=(0,0,0)):
273 | """
274 | Clear the image buffer to the specified RGB color (default black).
275 | USE (r, g, b) NOTATION FOR THE COLOUR !!
276 | """
277 |
278 | if type(color) != type((0,0,0)):
279 | print("clear() function colours must be in (255,255,0) form")
280 | exit()
281 | width, height = Buffer.size
282 | Buffer.putdata([color]*(width*height))
283 | self.display()
284 |
285 | def draw(self):
286 | """Return a PIL ImageDraw instance for drawing on the image buffer."""
287 | d = ImageDraw.Draw(Buffer)
288 | # Add custom methods to the draw object:
289 | d.textrotated = MethodType(_textrotated, d)
290 | d.pasteimage = MethodType(_pasteimage, d)
291 | d.textwrapped = MethodType(_textwrapped, d)
292 | return d
293 |
294 | def load_wallpaper(self, filename):
295 | # The image should be 320x240 or 240x320 only (full wallpaper!). Errors otherwise.
296 | # We need to cope with whatever orientations file image and TFT canvas are.
297 | image = Image.open(filename)
298 | if image.size[0] > Buffer.size[0]:
299 | Buffer.paste(image.rotate(90))
300 | elif image.size[0] < Buffer.size[0]:
301 | Buffer.paste(image.rotate(-90))
302 | else:
303 | Buffer.paste(image)
304 |
305 | def backup_buffer(self):
306 | self.buffer2.paste(Buffer)
307 |
308 | def restore_buffer(self):
309 | Buffer.paste(self.buffer2)
310 |
311 | def invert(self, onoff):
312 | if onoff:
313 | self.command(ILI9341_INVON)
314 | else:
315 | self.command(ILI9341_INVOFF)
316 |
317 | def backlite(self, onoff):
318 | if self._led is not None:
319 | self._gpio.output(self._led, onoff)
320 |
321 | def image_to_data(self, image):
322 | """Generator function to convert a PIL image to 16-bit 565 RGB bytes."""
323 | # Source of this code: Adafruit ILI9341 python project
324 | pixels = image.convert('RGB').load()
325 | width, height = image.size
326 | for y in range(height):
327 | for x in range(width):
328 | r,g,b = pixels[(x,y)]
329 | color = ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3)
330 | yield (color >> 8) & 0xFF
331 | yield color & 0xFF
332 |
333 |
334 | def textdirect(self, pos, text, font, fill="white"):
335 |
336 | width, height = self.draw().textsize(text, font=font)
337 | # Create a new image with transparent background to store the text.
338 | textimage = Image.new('RGBA', (width, height), (255,255,255,0))
339 | # Render the text.
340 | textdraw = ImageDraw.Draw(textimage)
341 | textdraw.text((0,0), text, font=font, fill=fill)
342 | self.set_frame(pos[0], pos[1], pos[0]+width-1, pos[1]+height-1)
343 | # Convert image to array of 16bit 565 RGB data bytes.
344 | pixelbytes = list(self.image_to_data(textimage))
345 | # Write data to hardware.
346 | self.data(pixelbytes)
347 |
348 | def penOnHotspot(self, HSlist, pos):
349 | # HotSpot list of "hotspots" - of form [(x0,y0,x1,y1,returnvalue)]*numOfSpots
350 | # if cursor position "pos" is within a hotspot box x0y0:x1y1, then "returnvalue" is returned
351 | x=pos[0]
352 | y=pos[1]
353 | for hs in HSlist:
354 | if x >= hs[0] and x<= hs[2] and y>= hs[1] and y<=hs[3]:
355 | return hs[4]
356 | return None
357 |
358 | # CUSTOM FUNCTIONS FOR draw() IN LCD CANVAS SYSTEM
359 |
360 | # We import these extra functions below as new custom methods of the PIL "draw" function:
361 | # Hints on this custom method technique:
362 | # http://www.ianlewis.org/en/dynamically-adding-method-classes-or-class-instanc
363 |
364 | def _textrotated(self, position, text, angle, font, fill="white"):
365 | # Define a function to create rotated text.
366 | # Source of this rotation coding: Adafruit ILI9341 python project
367 | # "Unfortunately PIL doesn't have good
368 | # native support for rotated fonts, but this function can be used to make a
369 | # text image and rotate it so it's easy to paste in the buffer."
370 | width, height = self.textsize(text, font=font)
371 | # Create a new image with transparent background to store the text.
372 | textimage = Image.new('RGBA', (width, height), (0,0,0,0))
373 | # Render the text.
374 | textdraw = ImageDraw.Draw(textimage)
375 | textdraw.text((0,0), text, font=font, fill=fill)
376 | # Rotate the text image.
377 | rotated = textimage.rotate(angle, expand=1)
378 | # Paste the text into the TFT canvas image, using text itself as a mask for transparency.
379 | Buffer.paste(rotated, position, rotated) # into the global Buffer
380 | # example: draw.textrotated(position, text, angle, font, fill)
381 |
382 | def _pasteimage(self, image, position):
383 | Buffer.paste(image, position)
384 | #Buffer.paste(Image.open(filename), position)
385 | # example: draw.pasteimage('bl.jpg', (30,80))
386 |
387 | def _textwrapped(self, position, text1, length, height, font, fill="white"):
388 | text2=textwrap.wrap(text1, length)
389 | y=position[1]
390 | for t in text2:
391 | self.text((position[0],y), t, font=font, fill=fill)
392 | y += height
393 | # example: draw.textwrapped((2,0), "but a lot longer", 50, 18, myFont, "black")
394 |
395 | # All colours may be any notation:
396 | # (255,0,0) =red (R, G, B)
397 | # 0x0000FF =red BBGGRR
398 | # "#FF0000" =red RRGGBB
399 | # "red" =red html colour names, insensitive
400 |
--------------------------------------------------------------------------------
/main.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 |
3 | # PicorderOS Alpha --------------------------------- December 2021
4 | # Created by Chris Barrett ------------------------- directive0
5 | # For my sister, a real life Beverly Crusher.
6 |
7 | print("PicorderOS - Alpha")
8 | print("Loading Components")
9 |
10 | import os
11 | from threading import Thread
12 |
13 |
14 | os.environ['SDL_AUDIODRIVER'] = 'alsa'
15 |
16 | from objects import *
17 | from sensors import *
18 | from plars import *
19 | from input import *
20 |
21 | if configure.audio[0]:
22 | from audio import *
23 |
24 | # This part loads the appropriate modules depending on which preference flags are set.
25 |
26 | # If we are NOT just running on a computer for development or demo purposes.
27 | if not configure.pc:
28 | # load up the LED indicator module
29 | from leds import *
30 | else:
31 | # otherwise load up the demonstration and dummy modules that emulate sensors and pass GPIO signals without requiring any real GPIO.
32 | from gpiodummy import *
33 |
34 | # The following are only loaded in TR-108 mode
35 | if configure.tr108:
36 | # Load the TR-108 display modules
37 | from tos_display import *
38 |
39 |
40 | # for the TR-109 there are two display modes supported.
41 | if configure.tr109:
42 |
43 | # 1.8" TFT colour LCD
44 | if configure.display == 1 or configure.display == 2:
45 | from lcars_clr import *
46 |
47 | # Nokia 5110 black and white dot matrix screen.
48 | if configure.display == 0:
49 | from lcars_bw import *
50 |
51 |
52 | # the following function is our main loop, it contains all the flow for our program.
53 | def Main():
54 |
55 | # Instantiate a screen object to draw data to screen. Right now for testing
56 | # they all have different names but each display object should use the same
57 | # named methods for simplicity sake.
58 | if configure.tr108:
59 | PyScreen = Screen()
60 | configure.graph_size[0] = PyScreen.get_size()
61 |
62 | if configure.tr109:
63 |
64 | if configure.display == 0:
65 | dotscreen = NokiaScreen()
66 | if configure.display == 1 or configure.display == 2:
67 | colourscreen = ColourScreen()
68 | colourscreen.start_up()
69 |
70 | configure.graph_size[0] = colourscreen.get_size()
71 |
72 | start_time = time.time()
73 | #start the sensor loop
74 | sensor_thread = Thread(target = threaded_sensor, args = ())
75 | sensor_thread.start()
76 |
77 |
78 | if configure.leds[0]:
79 | # seperate thread for LED lighting.
80 | led_thread = Thread(target = ripple_async, args = ())
81 | led_thread.start()
82 |
83 |
84 | #start the event monitor
85 | input_thread = Thread(target = threaded_input, args = ())
86 | input_thread.start()
87 |
88 | #start the audio service
89 | if configure.audio[0]:
90 | audio_thread = Thread(target = threaded_audio, args = ())
91 | audio_thread.start()
92 |
93 |
94 |
95 | print("Main Loop Starting")
96 |
97 | # Main loop. Break when status is "quit".
98 | while configure.status[0] != "quit":
99 |
100 |
101 | # try allows us to capture a keyboard interrupt and assign behaviours.
102 | try:
103 |
104 | # Runs the startup animation played when you first boot the program.
105 | if configure.status[0] == "startup":
106 |
107 | if configure.tr109:
108 | configure.status[0] = colourscreen.start_up()
109 |
110 |
111 | if configure.tr108:
112 | configure.status[0] = PyScreen.startup_screen(start_time)
113 |
114 |
115 | # The rest of these loops all handle a different mode, switched by buttons within the functions.
116 | if configure.status[0] == "mode_a":
117 |
118 | # the following is only run if the tr108 flag is set
119 | if configure.tr108:
120 |
121 | configure.status[0] = PyScreen.graph_screen()
122 |
123 | if not configure.pc:
124 | leda_on()
125 | ledb_off()
126 | ledc_off()
127 |
128 | if configure.tr109:
129 |
130 | if configure.display == 0:
131 | configure.status[0] = dotscreen.push(data)
132 | if configure.display == 1 or configure.display == 2:
133 | configure.status[0] = colourscreen.graph_screen()
134 |
135 | if configure.status[0] == "mode_b":
136 |
137 | if configure.tr108:
138 |
139 | configure.status[0] = PyScreen.slider_screen()
140 | if not configure.pc:
141 | leda_off()
142 | ledb_on()
143 | ledc_off()
144 |
145 | if configure.tr109:
146 | if configure.display == 0:
147 | configure.status[0] = dotscreen.push(data)
148 | if configure.display == 1 or configure.display == 2:
149 | configure.status[0] = colourscreen.em_screen()
150 |
151 | if configure.status[0] == "mode_c":
152 | if configure.tr109:
153 | if configure.display == 1:
154 | configure.status[0] = colourscreen.thermal_screen()
155 |
156 | if configure.tr108:
157 | configure.status[0] = PyScreen.video_screen()
158 | if not configure.pc:
159 | leda_on()
160 | ledb_on()
161 | ledc_off()
162 |
163 | if configure.status[0] == "settings":
164 |
165 | if configure.tr108:
166 | configure.status[0] = PyScreen.settings()
167 | if not configure.pc:
168 | leda_off()
169 | ledb_off()
170 | ledc_on()
171 |
172 | if configure.tr109:
173 | if configure.display == 0:
174 | configure.status[0] = dotscreen.push()
175 | if configure.display == 1 or configure.display == 2:
176 | configure.status[0] = colourscreen.settings()
177 |
178 | # Handles the poweroff screen
179 | if configure.status[0] == "poweroff":
180 |
181 | if configure.tr109:
182 | if configure.display == 0:
183 | configure.status[0] = dotscreen.push()
184 | if configure.display == 1 or configure.display == 2:
185 | configure.status[0] = colourscreen.powerdown()
186 |
187 | if configure.status[0] == "shutdown":
188 | print("Shut Down!")
189 | configure.status[0] = "quit"
190 | resetleds()
191 | cleangpio()
192 | os.system("sudo shutdown -h now")
193 |
194 | # If CTRL-C is received the program gracefully turns off the LEDs and resets the GPIO.
195 | except KeyboardInterrupt:
196 | configure.status[0] = "quit"
197 |
198 | print("Quit Encountered")
199 | print("Main Loop Shutting Down")
200 |
201 | # The following calls are for cleanup and just turn "off" any GPIO
202 | resetleds()
203 | cleangpio()
204 | plars.shutdown()
205 | #print("Quit reached")
206 |
207 |
208 | # the following call starts our program and begins the loop.
209 | Main()
210 |
--------------------------------------------------------------------------------
/modulated_em.py:
--------------------------------------------------------------------------------
1 | # PicorderOS Wifi Module Proto
2 | print("Loading Modulated EM Network Analysis")
3 |
4 | from wifi import Cell, Scheme
5 | import time
6 | from plars import *
7 |
8 | class Wifi_Scan(object):
9 |
10 | def __init__(self):
11 | pass
12 |
13 | def get_list(self):
14 | try:
15 | ap_list = list(Cell.all('wlan0'))
16 | except Exception as e:
17 | print("Wifi failed: ", e)
18 | ap_list = []
19 | return ap_list
20 |
21 | def get_info(self,selection):
22 | ap_list = self.update()
23 |
24 | if selection <= (len(ap_list)-1):
25 | return (ap_list[selection].ssid, int(ap_list[selection].signal), ap_list[selection].quality, ap_list[selection].frequency, ap_list[selection].bitrates, ap_list[selection].encrypted, ap_list[selection].channel, ap_list[selection].address, ap_list[selection].mode)
26 |
27 | def dump_data(self):
28 | ap_list = self.get_list()
29 | return self.plars_package(ap_list)
30 |
31 | def plars_package(self, ap_list):
32 | timestamp = time.time()
33 | ap_fragments = []
34 | #'ssid','signal','quality','frequency','encrypted','channel','address','mode','dsc','timestamp']
35 | for ap in ap_list:
36 | details = [ap.ssid, ap.signal, ap.quality, ap.frequency, ap.encrypted, ap.channel, ap.address, ap.mode, 'wifi', timestamp]
37 | ap_fragments.append(details)
38 |
39 | return ap_fragments
40 |
41 | def get_strongest_ssid(self):
42 |
43 | list = self.get_list()
44 | strengths = []
45 |
46 | for cell in list:
47 | strengths.append(cell.signal)
48 |
49 | max_value = max(strengths)
50 | max_index = strengths.index(max_value)
51 |
52 | strongest = list[max_index]
53 |
54 | details = [strongest.ssid, strongest.signal, strongest.quality, strongest.frequency, strongest.encrypted, strongest.channel, strongest.address, strongest.mode]
55 |
56 | return details
57 |
58 | def update_plars(self):
59 | plars.update_em(self.dump_data())
60 |
61 | def get_ssid_list(self):
62 |
63 | title_list = []
64 |
65 | ap_list = self.get_list()
66 | for ap in ap_list:
67 | name = ap.ssid
68 | title_list.append(name)
69 |
70 | return title_list
71 |
72 |
73 |
74 | class BT_Scan(object):
75 |
76 | def __init__(self):
77 | pass
78 |
--------------------------------------------------------------------------------
/objects.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # This module holds the initialization and global variables for the program.
3 | # Special thanks to SCIFI.radio for their work on the INI loader!
4 |
5 | import time, configparser
6 | from os.path import exists
7 |
8 |
9 | class preferences(object):
10 | def str2bool(self,v):
11 | return v.lower() in ("yes", "true", "t", "1")
12 |
13 | def createMissingINI(self,filepath):
14 | print("config.ini file is missing - making a new one. You'll want to edit to taste and restart.")
15 | config = configparser.ConfigParser(allow_no_value=True)
16 |
17 | config['SYSTEM'] = {'# Text displayed after "picorderOS" on title screen':None,
18 | 'version':'v2', # version number
19 | '# Greeting displayed on title screen':None,
20 | 'boot_message':'Initializing Sensor Array', # Initialization Message
21 | '# Boot Delay is the time allowed to display the banner screen':None,
22 | 'boot_delay':'2', # boot delay
23 | '# Emulating the hardware on a PC?':None,
24 | 'pc':'no', # emulating the hardware on a PC?
25 | '# Select either TR-108, or TR-109. You must choose only one.':None,
26 | 'tr108':'yes', # Running a TR-108 simulation - mutually exclusive with tr109
27 | 'tr109':'no'} # Running a TR-109 simulation - mutually exclusive with tr108
28 |
29 | config['SENSORS'] = {'# Only TR-108 uses SenseHat':None,
30 | 'sensehat':'yes', # Only TR-108 uses this
31 | 'system_vitals':'yes',
32 | '# BME680 Raw Values':None,
33 | 'bme':'no',
34 | '# BME680 VOC BSEC':None,
35 | 'bme_bsec':'no',
36 | 'amg8833':'no',
37 | 'pocket_geiger':'no',
38 | '# IR Infrared Imaging':None,
39 | 'ir_thermo':'no', # IR infrared imaging
40 | 'envirophat':'no',
41 | '# Only TR-109 uses this':None,
42 | 'EM':'no'} # Only TR-109 uses this
43 |
44 | config['INPUT'] = {'# Controls which input method is active (Choose only one)':None,
45 | 'kb':'no',
46 | 'gpio':'no',
47 | 'cap_mpr121':'no',
48 | 'pcf8575':'no',
49 | 'cap1208':'no',
50 | 'sensehat_joystick':'no',
51 | 'capsensitivity':'50'} # Used only if cap1208 is 'yes'
52 |
53 | config['PIN ASSIGNMENTS'] = {'#I2C pins':None,
54 | 'PIN_SDA':'2', # I2C pins
55 | 'PIN_SCL':'3',
56 | '# Main board shift register pins':None,
57 | '# The TR-109 supports two shift registers, and two sets of pin addresses':None,
58 | '# Prototype units 00 and 01 have different pin assignments,':None,
59 | '# so these may need to be swapped.':None,
60 |
61 | 'PIN_DATA':'16', # Main board shift register pins - The TR109 supports two shift registers,
62 | 'PIN_LATCH':'6', # and so two sets of pin addresses; prototype unit 00 and 01 have different pin
63 | 'PIN_CLOCK':'20', # assignments, so these values may need to be swapped
64 |
65 | 'PIN_DATA2':'19', # Sensor board shift register pins
66 | 'PIN_LATCH2':'21',
67 | 'PIN_CLOCK2':'26',
68 | '# Hall effect sensor pins, for door open/close detection':None,
69 |
70 | 'HALLPIN1':'12', # Hall effect sensor pins, for door open and close
71 | 'HALLPIN2':'4',
72 |
73 | '# Cap1208 Alert Pin':None,
74 | 'ALERTPIN':'0', # Cap1208 Alert Pin
75 |
76 | '# Pocket-Geiger Sensor Pins':None,
77 | 'PG_SIG':'20', # PocketGeiger Pins
78 | 'PG_NS':'21'}
79 |
80 | config['OUTPUT'] = {'display':'1',
81 | 'LED_timer':'0.2'}
82 |
83 | config['GLOBALS'] = {'# Controls whether LEDs are active':None,
84 | 'leds':'yes',
85 | '# Enables the moire pattern on the SenseHat LED matrix - TR-108 only':None,
86 | 'moire':'no',
87 | '# Enables audio playback (videos will not play without this)':None,
88 | 'audio':'no', # Enables audio playback
89 | 'alarm':'no',
90 | '# If sleep is "yes" then lights will respond to Hall Effect sensors':None,
91 | 'sleep':'yes', # If sleep is True the lights will respond to hall effect sensors
92 | '# Autoranging of graphs':None,
93 | 'autoranging':'yes', # Auto ranging of graphs
94 | '# Interpolate Temperature':None,
95 | 'interpolate':'yes', # Interpolate temperature
96 | 'samplerate':'0',
97 | '# Affects graphing density':None,
98 | 'samples':'16',
99 | '# Currently not used':None,
100 | 'displayinterval':'0',
101 | '# Turns data logging on - data is written to data/datacore.csv':None,
102 | 'datalog':'no',
103 | 'doordetection':'yes',
104 | '# Settings for mode_a Graph Screen on TR-108':None,
105 | 'graph_width':'280',
106 | 'graph_height':'182',
107 | 'graph_x':'18',
108 | 'graph_y':'20'}
109 |
110 | with open('config.ini','w') as configfile:
111 | config.write(configfile)
112 | print("New INI file is ready.")
113 |
114 | # Initializes the parameters for the program.
115 | def __init__(self):
116 | print("Loading Global Objects")
117 | if not exists("config.ini"):
118 | self.createMissingINI('config.ini')
119 |
120 | config=configparser.ConfigParser()
121 | config.read('config.ini')
122 |
123 | # Sets the variables for boot up
124 | self.version = config['SYSTEM']['version']
125 | self.boot_message = config['SYSTEM']['boot_message']
126 |
127 | self.boot_delay = int(config['SYSTEM']['boot_delay'])
128 |
129 | # enables "PC Mode": sensors and GPIO calls are disabled.
130 | # Machine vitals are substituted and Luma screens use emulator
131 | self.pc = self.str2bool(config['SYSTEM']['pc'])
132 |
133 | # These two bits determine the target device (Original picorder or new version)
134 | # If both true the screens will fight for control!
135 | self.tr108 = self.str2bool(config['SYSTEM']['tr108'])
136 | self.tr109 = self.str2bool(config['SYSTEM']['tr109'])
137 | # SENSORS----------------------------------------------------------------------#
138 | # TR108 uses this sensehat
139 | self.sensehat = self.str2bool(config['SENSORS']['sensehat'])
140 |
141 | # Toggles individual sensor support
142 | self.system_vitals = self.str2bool(config['SENSORS']['system_vitals'])
143 | self.bme = self.str2bool(config['SENSORS']['bme'])
144 | self.amg8833 = self.str2bool(config['SENSORS']['amg8833'])
145 |
146 | # Experimental sensors
147 | self.pocket_geiger = self.str2bool(config['SENSORS']['pocket_geiger'])
148 | self.ir_thermo = self.str2bool(config['SENSORS']['ir_thermo'])
149 | self.envirophat = self.str2bool(config['SENSORS']['envirophat'])
150 |
151 | # toggles wifi/bt scanning
152 | self.EM = self.str2bool(config['SENSORS']['EM'])
153 |
154 |
155 | # INPUT MODULE-----------------------------------------------------------------#
156 |
157 | # testing this setting to switch between Pygame controls and gpio ones
158 | self.input_kb = self.str2bool(config['INPUT']['kb']) # also enables Sensehat joystick if present
159 | self.input_gpio = self.str2bool(config['INPUT']['gpio'])
160 | self.input_cap_mpr121 = self.str2bool(config['INPUT']['cap_mpr121'])
161 | self.input_pcf8575 = self.str2bool(config['INPUT']['pcf8575'])
162 | self.input_joystick = self.str2bool(config['INPUT']['sensehat_joystick'])
163 |
164 | # CAP1208 and sensitivity settings
165 | self.input_cap1208 = self.str2bool(config['INPUT']['cap1208'])
166 | self.CAPSENSITIVITY = int(config['INPUT']['capsensitivity'])
167 |
168 |
169 | # PIN ASSIGNMENTS--------------------------------------------------------------#]
170 |
171 | # GPIO Pin Assignments (BCM)
172 |
173 | # i2c Pins
174 | self.PIN_SDA = int(config['PIN ASSIGNMENTS']['pin_sda'])
175 | self.PIN_SCL = int(config['PIN ASSIGNMENTS']['pin_scl'])
176 |
177 | # the tr109 supports two shift registers, and so two sets of pin addresses
178 | # prototype unit 00 and 01 have different pin assignments for latch and clock
179 | # so these values may need to be swapped
180 |
181 | # Main board shift register pins
182 | self.PIN_DATA = int(config['PIN ASSIGNMENTS']['pin_data'])
183 | self.PIN_LATCH = int(config['PIN ASSIGNMENTS']['pin_latch'])
184 | self.PIN_CLOCK = int(config['PIN ASSIGNMENTS']['pin_clock'])
185 |
186 | # Sensor board shift register pins
187 | self.PIN_DATA2 = int(config['PIN ASSIGNMENTS']['pin_data2'])
188 | self.PIN_LATCH2 = int(config['PIN ASSIGNMENTS']['pin_latch2'])
189 | self.PIN_CLOCK2 = int(config['PIN ASSIGNMENTS']['pin_clock2'])
190 |
191 |
192 | # Hall effect sensors pins, for door open/close.
193 | self.HALLPIN1 = int(config['PIN ASSIGNMENTS']['hallpin1'])
194 | self.HALLPIN2 = int(config['PIN ASSIGNMENTS']['hallpin2'])
195 |
196 | # CAP1208 alert pin
197 | self.ALERTPIN = int(config['PIN ASSIGNMENTS']['alertpin'])
198 |
199 | # PocketGeiger Pins
200 | self.PG_SIG = int(config['PIN ASSIGNMENTS']['pg_sig'])
201 | self.PG_NS = int(config['PIN ASSIGNMENTS']['pg_ns'])
202 |
203 | # OUTPUT SETTINGS--------------------------------------------------------------#
204 |
205 | # chooses SPI display (0 for nokia 5110, 1 for st7735)
206 | self.display = int(config['OUTPUT']['display'])
207 |
208 | # led refresh rate.
209 | self.LED_TIMER = float(config['OUTPUT']['led_timer'])
210 |
211 | # GLOBAL VARIABLES-------------------------------------------------------------#
212 |
213 | # Controls for global event list
214 | self.eventlist = [[]]
215 | self.eventready = [False]
216 |
217 | # contains the current button state (0 is unpressed, 1 is pressed)
218 | self.events = [0,0,0,0,0,0,0,0]
219 |
220 | # holds state for beep input feedback
221 | self.beep_ready = [False]
222 | self.alarm_ready = [False]
223 |
224 |
225 | # flags control the onboard LEDS. Easy to turn them off if need be.
226 | self.leds = [self.str2bool(config['GLOBALS']['leds'])] # was True
227 |
228 | # controls Moire pattern on tr-108
229 | self.moire = [self.str2bool(config['GLOBALS']['moire'])] # was True
230 |
231 | # enables sound effect playback
232 | self.audio = [self.str2bool(config['GLOBALS']['audio'])]
233 |
234 | # turns alarms on/off
235 | self.alarm = [self.str2bool(config['GLOBALS']['alarm'])]
236 |
237 | # If sleep is True the lights will respond to hall effect sensors
238 | self.sleep = [self.str2bool(config['GLOBALS']['sleep'])]
239 |
240 | # controls auto ranging of graphs
241 | self.auto = [self.str2bool(config['GLOBALS']['autoranging'])]
242 |
243 | # holds theme state for UI
244 | self.theme = [0]
245 |
246 | # sets the number of max sensors for user configuration
247 | # (is automatically set by the sensor module at startup)
248 | self.max_sensors = [0]
249 |
250 | #sets the upper and lower threshold for the alert
251 | self.TEMP_ALERT = (0,100)
252 | self.interpolate = [True]
253 |
254 | # flag to command the main loop
255 | self.sensor_ready = [False]
256 | self.screen_halt = [False]
257 | self.sensor_halt = [False]
258 |
259 | # An integer determines which sensor in the dataset to plot
260 | self.sensor1 = [0]
261 | self.sensor2 = [1]
262 | self.sensor3 = [2]
263 | self.sensors = [self.sensor1, self.sensor2, self.sensor3]
264 |
265 | # sets data logging mode.
266 | self.datalog = [self.str2bool(config['GLOBALS']['datalog'])]
267 | self.trim_buffer = [True]
268 | self.buffer_size = [0]
269 | self.graph_size = [0]
270 | self.logtime = [60]
271 | self.recall = [False]
272 |
273 | # used to control refresh speed.
274 | self.samples = int(config['GLOBALS']['samples'])
275 |
276 | self.samplerate=[float(config['GLOBALS']['samplerate'])]
277 | self.displayinterval=[0]
278 |
279 | # holds sensor data (issued by the sensor module at init)
280 | self.sensor_info = []
281 |
282 | self.sensor_data = []
283 |
284 | # holds the global state of the program (allows secondary modules to quit the program should we require it)
285 | self.status = ["startup"]
286 | self.last_status = ["startup"]
287 |
288 | # Enables/disables door detection
289 | self.dr = [self.str2bool(config['GLOBALS']['doordetection'])]
290 |
291 | # holds the physical status of the devices
292 | self.dr_open = [False]
293 | self.dr_closed = [False]
294 | self.dr_opening = [False]
295 | self.dr_closing = [False]
296 |
297 | # Settings for mode_a Graph_Screen for TR108
298 | self.graph_width = int(config['GLOBALS']['graph_width'])
299 | self.graph_height = int(config['GLOBALS']['graph_height'])
300 | self.graph_x = int(config['GLOBALS']['graph_x'])
301 | self.graph_y = int(config['GLOBALS']['graph_y'])
302 |
303 |
304 | # create a shared object for global variables and settings.
305 | configure = preferences()
306 |
307 | # the following function maps a value from the target range onto the desination range
308 | def translate(value, leftMin, leftMax, rightMin, rightMax):
309 | # Figure out how 'wide' each range is
310 | leftSpan = leftMax - leftMin
311 | if leftSpan == 0:
312 | leftSpan = 1
313 | rightSpan = rightMax - rightMin
314 |
315 | # Convert the left range into a 0-1 range (float)
316 | valueScaled = float(value - leftMin) / float(leftSpan)
317 |
318 | # Convert the 0-1 range into a value in the right range.
319 | return rightMin + (valueScaled * rightSpan)
320 |
321 | # The following class is to handle interval timers.
322 | # its used to handle concurrent program flow, but also for diagnostic.
323 | class timer(object):
324 |
325 | # Constructor code logs the time it was instantiated.
326 | def __init__(self):
327 | self.timeInit = time.time()
328 | self.logtime()
329 |
330 | # The following funtion returns the first logged value. When the timer was first started.
331 | def timestart(self):
332 | return self.timeInit
333 |
334 | # the following function updates the time log with the current time.
335 | def logtime(self):
336 | self.lastTime = time.time()
337 |
338 | def event(self,caption):
339 | print(caption)
340 | self.logtime()
341 |
342 | # the following function returns the interval that has elapsed since the last log.
343 | def timelapsed(self):
344 | #print("comparing times")
345 | self.timeLapse = time.time() - self.lastTime
346 | #print("timelapse passed")
347 | #print(self.timeLapse)
348 | return self.timeLapse
349 |
350 | def stoplapsed(self):
351 | self.timelapsed()
352 |
353 | def post(self, caption):
354 | print(caption, "took: ", self.timelapsed(), "Seconds")
355 | self.logtime()
356 |
--------------------------------------------------------------------------------
/pilgraph.py:
--------------------------------------------------------------------------------
1 | print("Loading Python IL Module")
2 |
3 |
4 | # PILgraph provides an object (graphlist) that will draw a new graph each frame.
5 | # It was written to contain all the previous sensor readings, but this
6 | # feature is no longer necessary as PLARS now handles all data history.
7 |
8 | # To do:
9 | # - request from PLARS the N most recent values for the sensor assigned to this identifier
10 |
11 |
12 |
13 | # it is initialized with:
14 | # - ident: a graph identifier so it knows which sensor to grab data for
15 | # - graphcoords: list containing the top left x,y coordinates
16 | # - graphspan: list containing the x and y span in pixels
17 | # - cycle: time per division of the graph (not working)
18 | # -
19 |
20 | from objects import *
21 | from PIL import Image
22 | from PIL import ImageFont
23 | from PIL import ImageDraw
24 |
25 | import numpy
26 | from array import *
27 | from plars import *
28 | from multiprocessing import Process,Queue,Pipe
29 |
30 | # function to calculate onscreen coordinates of graph pixels as a process.
31 | def graph_prep_process(conn,samples,datalist,auto,newrange,targetrange,sourcerange,linepoint,jump,sourcelow):
32 | newlist = []
33 | # for each vertical bar in the graph size
34 | for i in range(samples):
35 |
36 |
37 |
38 | # if the cursor has data to write
39 | if i < len(datalist):
40 |
41 | # gives me an index within the current length of the datalist
42 | # goes from the most recent data backwards
43 | # so the graph prints from left-right: oldest-newest data.
44 | indexer = (len(datalist) - i) - 1
45 |
46 | # if auto scaling is on
47 | if auto == True:
48 | # take the sensor value received and map it against the on screen limits
49 | scaledata = abs(numpy.interp(datalist[indexer],newrange,targetrange))
50 | else:
51 | # use the sensors stated limits as the range.
52 | scaledata = abs(numpy.interp(datalist[indexer],sourcerange,targetrange))
53 |
54 | # append the current x position, with this new scaled data as the y positioning into the buffer
55 | newlist.append((linepoint,scaledata))
56 | else:
57 | # If no data just write intensity as scaled zero
58 | scaledata = abs(numpy.interp(sourcelow,sourcerange,targetrange))
59 | newlist.append((linepoint,scaledata))
60 |
61 | # increment the cursor
62 | linepoint = linepoint + jump
63 |
64 | conn.put(newlist)
65 |
66 |
67 | class graph_area(object):
68 |
69 |
70 | def __init__(self, ident, graphcoords, graphspan, cycle = 0, colour = 0, width = 1, type = 0, samples = False):
71 |
72 | # if a samplesize is provided use it, otherwise grab global setting.
73 | if not samples:
74 | self.samples = configure.samples
75 | else:
76 | self.samples = samples
77 |
78 | self.cycle = cycle
79 |
80 |
81 | self.glist = array('f', [])
82 | self.dlist = array('f', [])
83 |
84 | self.colour = colour
85 |
86 | #controls auto scaling (set by global variable at render)
87 | self.auto = True
88 |
89 | # controls width
90 | self.width = width
91 |
92 | self.dotw = 6
93 | self.doth = 6
94 | self.buff = array('f', [])
95 | self.type = type
96 |
97 | self.timeit = timer()
98 |
99 | self.datahigh = 0
100 | self.datalow = 0
101 | self.newrange = (self.datalow,self.datahigh)
102 |
103 | # stores the graph identifier, there are three on the multiframe
104 | self.ident = ident
105 |
106 | # collect data for where the graph should be drawn to screen.
107 | self.x, self.y = graphcoords
108 | self.spanx,self.spany = graphspan
109 |
110 | # defines the vertical limits of the screen based on the area provided
111 | self.targetrange = ((self.y + self.spany), self.y)
112 |
113 | # seeds a list with the coordinates for 0 to give us a list that we
114 | # can put our scaled graph values in
115 | for i in range(self.spanx):
116 | self.glist.append(self.y + self.spany)
117 |
118 | # seeds a list with sourcerange zero so we can put our sensor readings into it.
119 | # dlist is the list where we store the raw sensor values with no scaling
120 | for i in range(self.spanx):
121 | self.dlist.append(self.datalow)
122 | self.buff.append(self.datalow)
123 |
124 |
125 | # the following function returns the graph list.
126 | def grabglist(self):
127 | return self.glist
128 |
129 | # the following function returns the data list.
130 | def grabdlist(self):
131 | return self.dlist
132 |
133 | # Returns the average of the current dataset
134 | def get_average(self):
135 | average = sum(self.buff) / len(self.buff)
136 | return average
137 |
138 | # returns the highest
139 | def get_high(self):
140 | return max(self.buff)
141 |
142 | def get_low(self):
143 | return min(self.buff)
144 |
145 | # this function calculates the approximate time scale of the graph
146 | def giveperiod(self):
147 | self.period = (self.spanx * self.cycle) / 60
148 |
149 | return self.period
150 |
151 |
152 |
153 | # the following pairs the list of values with coordinates on the X axis.
154 |
155 | # if the auto flag is set then the class will autoscale the graph so that
156 | # the highest and lowest currently displayed values are presented.
157 | # takes in a list/array with length => span
158 | def graphprep(self, datalist, ranger = None):
159 |
160 | index = configure.sensors[self.ident][0]
161 |
162 | dsc,dev,sym,maxi,mini = configure.sensor_info[index]
163 |
164 | # The starting X coordinate, the graph draws from right to left (new to old).
165 | self.linepoint = self.spanx + self.x
166 |
167 | # calculate how many pixels per sample the graph will take.
168 | spacing = self.spanx / self.samples
169 |
170 | # Spacing between each point.
171 | self.jump = -spacing
172 |
173 |
174 |
175 | # if this graph ISNT for WIFI.
176 | if self.type == 0:
177 | # grabs the currently selected sensors range data.
178 | sourcelow = mini
179 | sourcehigh = maxi
180 |
181 | self.sourcerange = [sourcelow,sourcehigh]
182 | #otherwise assume its for wifi.
183 | else:
184 | sourcelow = -90
185 |
186 | sourcehigh = -5
187 |
188 | self.sourcerange = [sourcelow,sourcehigh]
189 |
190 | # get the range of the data.
191 | if len(datalist) > 0:
192 | self.datahigh = max(datalist)
193 | self.datalow = min(datalist)
194 | else:
195 | self.datahigh = sourcehigh
196 | self.datalow = sourcelow
197 |
198 | self.newrange = (self.datalow,self.datahigh)
199 |
200 |
201 |
202 | q = Queue()
203 | prep_process = Process(target=graph_prep_process, args=(q,self.samples,datalist,self.auto,self.newrange,self.targetrange,self.sourcerange,self.linepoint,self.jump,sourcelow,))
204 | prep_process.start()
205 |
206 | prep_process.join()
207 | result = q.get()
208 |
209 | return result
210 |
211 |
212 |
213 |
214 | def render(self, draw, auto = True, dot = True, ranger = None):
215 |
216 | self.auto = configure.auto[0]
217 |
218 | # for PLARS we reduce the common identifier of our currently selected sensor
219 | # by using its description (dsc) and device (dev): eg
220 | # BME680 has a thermometer and hygrometer,
221 | # therefore the thermometer's dsc is "thermometer" and the device is 'BME680'
222 | # the hygrometer's dsc is "hygrometer" and the the device is "BME680"
223 |
224 | # so every time through the loop PILgraph will pull the latest sensor
225 | # settings.
226 |
227 | # standard pilgraph: takes DSC,DEV keypairs from screen drawer, asks
228 | # plars for data, graphs it.
229 |
230 | # Standard graph
231 | if self.type == 0:
232 | index = configure.sensors[self.ident][0]
233 | dsc,dev,sym,maxi,mini = configure.sensor_info[index]
234 | recent = plars.get_recent(dsc,dev,num = self.samples)
235 |
236 | # EM pilgraph: pulls wifi data only.
237 | elif self.type == 1:
238 | recent = plars.get_top_em_history(no = self.samples)
239 |
240 | # Testing a new graph
241 | elif self.type == 2:
242 | recent = plars.get_recent(dsc,dev,num = self.samples)
243 |
244 |
245 |
246 | cords = self.graphprep(recent)
247 | self.buff = recent
248 |
249 | # draws the line graph
250 | draw.line(cords,self.colour,self.width)
251 |
252 | # draws the line dot.
253 | if dot:
254 | x1 = cords[0][0] - (self.dotw/2)
255 | y1 = cords[0][1] - (self.doth/2)
256 | x2 = cords[0][0] + (self.dotw/2)
257 | y2 = cords[0][1] + (self.doth/2)
258 | draw.ellipse([x1,y1,x2,y2],self.colour)
259 |
--------------------------------------------------------------------------------
/plars.py:
--------------------------------------------------------------------------------
1 | print("Loading Picorder Library Access and Retrieval System Module")
2 | from objects import *
3 | from multiprocessing import Process,Queue,Pipe
4 |
5 |
6 | import json
7 |
8 | # PLARS (Picorder Library Access and Retrieval System) aims to provide a
9 | # single surface for storing and retrieving data for display in any of the
10 | # different Picorder screen modes.
11 |
12 |
13 | import os
14 | import numpy
15 | import datetime
16 | from array import *
17 | import pandas as pd
18 | import json
19 |
20 | import threading
21 |
22 | # Broken out functions for use with processing:
23 |
24 | # organizes and returns a list of data as a multiprocess.
25 | def get_recent_proc(conn,buffer,dsc,dev,num):
26 |
27 | result = buffer[buffer["dsc"] == dsc]
28 |
29 | untrimmed_data = result.loc[result['dev'] == dev]
30 |
31 | # trim it to length (num).
32 | trimmed_data = untrimmed_data.tail(num)
33 |
34 | # return a list of the values
35 | result = trimmed_data['value'].tolist()
36 |
37 | conn.put(result)
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 | class PLARS(object):
46 |
47 | def __init__(self):
48 |
49 | # add a lock to avoid race conditions
50 | self.lock = threading.Lock()
51 |
52 | # PLARS opens a data frame at initialization.
53 | # If the csv file exists it opens it, otherwise creates it.
54 | # self.core is used to refer to the archive on disk
55 | # self.buffer is created as a truncated dataframe for drawing to screen.
56 |
57 | # create buffer
58 | self.file_path = "data/datacore.csv"
59 |
60 | if configure.recall[0]:
61 | if os.path.exists(self.file_path):
62 | if configure.datalog:
63 | self.core = pd.read_csv(self.file_path)
64 | else:
65 | if not os.path.exists("data"):
66 | os.mkdir("data")
67 | self.core = pd.DataFrame(columns=['value','min','max','dsc','sym','dev','timestamp'])
68 | self.core.to_csv(self.file_path)
69 |
70 |
71 | # Set floating point display to raw, instead of exponent
72 | pd.set_option('display.float_format', '{:.7f}'.format)
73 |
74 | #create a buffer object to hold screen data
75 | self.buffer = pd.DataFrame(columns=['value','min','max','dsc','sym','dev','timestamp'])
76 |
77 | #create a buffer for wifi/bt data
78 | self.buffer_em = pd.DataFrame(columns=['ssid','signal','quality','frequency','encrypted','channel','dev','mode','dsc','timestamp'])
79 |
80 |
81 | self.timer = timer()
82 |
83 |
84 | def shutdown(self):
85 | self.append_to_core(self.buffer)
86 |
87 | # gets the latest CSV file
88 | def get_core(self):
89 | datacore = pd.read_csv(self.file_path)
90 | return datacore
91 |
92 | def merge_with_core(self):
93 | print("PLARS - merging to core")
94 | # open the csv
95 | core = self.get_core()
96 | copydf = self.buffer.copy()
97 | newcore = pd.concat([core,copydf]).drop_duplicates().reset_index(drop=True)
98 | newcore = self.index_by_time(newcore)
99 | newcore.to_csv(self.file_path,index=False)
100 |
101 | #pends a new set of data to the CSV file.
102 | def append_to_core(self, data):
103 | data.to_csv(self.file_path, mode='a', header=False)
104 |
105 | # returns a list of every EM transciever that was discovered last scan.
106 | def get_recent_em_list(self):
107 |
108 | # set the thread lock so other threads are unable to add data
109 | self.lock.acquire()
110 |
111 | # get the most recent ssids discovered
112 | recent_em = self.get_em_recent()
113 |
114 | # sort it by signal strength
115 | recent_em.sort_values(by=['signal'], ascending = False)
116 |
117 | # release the thread lock.
118 | self.lock.release()
119 |
120 | return recent_em.values.tolist()
121 |
122 | def get_top_em_info(self):
123 |
124 | #find the most recent timestamp to limit focus
125 | focus = self.get_em_recent()
126 |
127 | # find most powerful signal of the most recent transciever data
128 | db_column = focus["signal"]
129 | strongest = db_column.astype(int).max()
130 |
131 | # Identify the SSID of the strongest signal.
132 | self.identity = focus.loc[focus['signal'] == strongest]
133 |
134 | # Return the SSID of the strongest signal as a list.
135 | return self.identity.values.tolist()
136 |
137 | def get_em_recent(self):
138 | # find the most recent timestamp
139 | time_column = self.buffer_em["timestamp"]
140 | most_recent = time_column.max()
141 |
142 | #limit focus to data from that timestamp
143 | return self.buffer_em.loc[self.buffer_em['timestamp'] == most_recent]
144 |
145 | def get_top_em_history(self, no = 5):
146 | # returns a list of Db values for whatever SSID is currently the strongest.
147 | # suitable to be fed into pilgraph for graphing.
148 |
149 | # set the thread lock so other threads are unable to add data
150 | self.lock.acquire()
151 |
152 | #limit focus to data from that timestamp
153 | focus = self.get_em_recent()
154 |
155 | # find most powerful signal
156 | db_column = focus["signal"]
157 | strongest = db_column.astype(int).max()
158 |
159 | # Identify the SSID of the strongest signal.
160 | self.identity = focus.loc[focus['signal'] == strongest]
161 |
162 |
163 | # prepare markers to pull data
164 | # Wifi APs can have the same name and different paramaters
165 | # I use MAC and frequency to individualize a signal
166 | dev = self.identity["dev"].iloc[0]
167 | frq = self.identity["frequency"].iloc[0]
168 |
169 |
170 | # release the thread lock.
171 | self.lock.release()
172 |
173 | return self.get_recent_em(dev,frq, num = no)
174 |
175 |
176 | def update_em(self,data):
177 | #print("Updating EM Dataframe:")
178 |
179 | newdata = pd.DataFrame(data, columns=['ssid','signal','quality','frequency','encrypted','channel','dev','mode','dsc','timestamp'])
180 |
181 |
182 | # sets/requests the thread lock to prevent other threads reading data.
183 | self.lock.acquire()
184 |
185 |
186 | # appends the new data to the buffer
187 | self.buffer_em = self.buffer_em.append(newdata, ignore_index=True)
188 |
189 |
190 | self.lock.release()
191 |
192 |
193 | # updates the data storage file with the most recent sensor values from each
194 | # initialized sensor.
195 | # Sensor data is taken in as Fragment() instance objects. Each one contains
196 | # the a sensor value and context for it (scale, symbol, unit, etc).
197 | def update(self,data):
198 |
199 | # sets/requests the thread lock to prevent other threads reading data.
200 | self.lock.acquire()
201 |
202 | #listbuilder:
203 | fragdata = []
204 |
205 | for fragment in data:
206 | #
207 | item = fragment.get()
208 | fragdata.append(item)
209 |
210 |
211 | # creates a new dataframe to add new data to
212 | newdata = pd.DataFrame(fragdata, columns=['value','min','max','dsc','sym','dev','timestamp'])
213 |
214 |
215 | # appends the new data to the buffer
216 | self.buffer = self.buffer.append(newdata, ignore_index=True)
217 |
218 | # get buffer size to determine how many rows to remove from the end
219 | currentsize = len(self.buffer)
220 |
221 | targetsize = configure.buffer_size[0]
222 |
223 | # determine difference between buffer and target size
224 | length = currentsize - targetsize
225 |
226 |
227 | if configure.trim_buffer[0]:
228 | # if buffer is larger than double the buffer size
229 | if length >= configure.buffer_size[0] * 2:
230 | self.trimbuffer()
231 |
232 | # release the thread lock for other threads
233 | self.lock.release()
234 |
235 | def get_em(self,dev,frequency):
236 | result = self.buffer_em.loc[self.buffer_em['dev'] == dev]
237 | result2 = result.loc[result["frequency"] == frequency]
238 |
239 | return result2
240 |
241 | # returns all sensor data in the buffer for the specific sensor (dsc,dev)
242 | def get_sensor(self,dsc,dev):
243 |
244 | result = self.buffer[self.buffer["dsc"] == dsc]
245 |
246 | result2 = result.loc[result['dev'] == dev]
247 |
248 | return result2
249 |
250 |
251 | def index_by_time(self,df, ascending = False):
252 | df.sort_values(by=['timestamp'], ascending = ascending)
253 | return df
254 |
255 |
256 | # return a list of n most recent data from specific ssid defined by keys
257 | def get_recent_em(self, dev, frequency, num = 5):
258 |
259 | # get a dataframe of just the requested sensor
260 | untrimmed_data = self.get_em(dev,frequency)
261 |
262 | # trim it to length (num).
263 | trimmed_data = untrimmed_data.tail(num)
264 |
265 | # return a list of the values
266 | return trimmed_data['signal'].tolist()
267 |
268 |
269 | # return a list of n most recent data from specific sensor defined by keys
270 | def get_recent(self, dsc, dev, num = 5):
271 |
272 | # set the thread lock so other threads are unable to add sensor data
273 | self.lock.acquire()
274 |
275 | q = Queue()
276 | get_process = Process(target=get_recent_proc, args=(q,self.buffer,dsc,dev,num,))
277 | get_process.start()
278 |
279 | # return a list of the values
280 | result = q.get()
281 | get_process.join()
282 |
283 | # release the thread lock.
284 | self.lock.release()
285 |
286 | return result
287 |
288 |
289 | def trimbuffer(self, save = True):
290 | # should take the buffer in memory and trim some of it
291 |
292 | # get buffer size to determine how many rows to remove from the end
293 | currentsize = len(self.buffer)
294 | targetsize = configure.buffer_size[0]
295 |
296 | # determine difference between buffer and target size
297 | length = currentsize - targetsize
298 |
299 | # make a new dataframe of the most recent data to keep using
300 | newbuffer = self.buffer.tail(targetsize)
301 |
302 | # slice off the rows outside the buffer and backup to disk
303 | tocore = self.buffer.head(length)
304 |
305 | if configure.recall[0]:
306 | self.append_to_core(tocore)
307 |
308 | # replace existing buffer with new trimmed buffer
309 | self.buffer = newbuffer
310 |
311 |
312 | def emrg(self):
313 | self.get_core()
314 | return self.df
315 |
316 | def convert_epoch(self, time):
317 | return datetime.datetime.fromtimestamp(time)
318 |
319 | # Creates a plars database object as soon as it is loaded.
320 | plars = PLARS()
321 |
--------------------------------------------------------------------------------
/pyvidplayer.py:
--------------------------------------------------------------------------------
1 | import pygame
2 | from pymediainfo import MediaInfo
3 | from ffpyplayer.player import MediaPlayer
4 | from os.path import exists, basename, splitext
5 | from os import strerror
6 | from errno import ENOENT
7 |
8 |
9 | class Video:
10 | def __init__(self, path):
11 | self.path = path
12 |
13 | if exists(path):
14 | self.video = MediaPlayer(path)
15 | info = self.get_file_data()
16 |
17 | self.duration = info["duration"]
18 | self.frames = 0
19 | self.frame_delay = 1 / info["frame rate"]
20 | self.size = info["original size"]
21 | self.image = pygame.Surface((0, 0))
22 |
23 | self.active = True
24 | else:
25 | raise FileNotFoundError(ENOENT, strerror(ENOENT), path)
26 |
27 | def get_file_data(self):
28 | info = MediaInfo.parse(self.path).video_tracks[0]
29 | return {"path":self.path,
30 | "name":splitext(basename(self.path))[0],
31 | "frame rate":float(info.frame_rate),
32 | "frame count":info.frame_count,
33 | "duration":info.duration / 1000,
34 | "original size":(info.width, info.height),
35 | "original aspect ratio":info.other_display_aspect_ratio[0]}
36 |
37 | def get_playback_data(self):
38 | return {"active":self.active,
39 | "time":self.video.get_pts(),
40 | "volume":self.video.get_volume(),
41 | "paused":self.video.get_pause(),
42 | "size":self.size}
43 |
44 | def restart(self):
45 | self.video.seek(0, relative=False, accurate=False)
46 | self.frames = 0
47 | self.active = True
48 |
49 | def close(self):
50 | self.video.close_player()
51 | self.active = False
52 |
53 | def set_size(self, size):
54 | self.video.set_size(size[0], size[1])
55 | self.size = size
56 |
57 | def set_volume(self, volume):
58 | self.video.set_volume(volume)
59 |
60 | def seek(self, seek_time, accurate=False):
61 | vid_time = self.video.get_pts()
62 | if vid_time + seek_time < self.duration and self.active:
63 | self.video.seek(seek_time)
64 | if seek_time < 0:
65 | while (vid_time + seek_time < self.frames * self.frame_delay):
66 | self.frames -= 1
67 |
68 | def toggle_pause(self):
69 | self.video.toggle_pause()
70 |
71 | def update(self):
72 | updated = False
73 | while self.video.get_pts() > self.frames * self.frame_delay:
74 | frame, val = self.video.get_frame()
75 | self.frames += 1
76 | updated = True
77 | if updated:
78 | if val == "eof":
79 | self.active = False
80 | elif frame != None:
81 | self.image = pygame.image.frombuffer(frame[0].to_bytearray()[0], frame[0].get_size(), "RGB")
82 | return updated
83 |
84 | def draw(self, surf, pos, force_draw=True):
85 | if self.active:
86 | if self.update() or force_draw:
87 | surf.blit(self.image, pos)
88 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | adafruit_amg88xx==1.4.3
2 | Adafruit_Blinka==6.15.0
3 | adafruit_circuitpython_bme680==3.3.4
4 | adafruit_circuitpython_mpr121==2.1.9
5 | adafruit_mpr121==1.1.2
6 | board==1.0
7 | Cap1xxx==0.1.4
8 | colour==0.1.5
9 | envirophat==1.0.1
10 | luma.core==2.3.1
11 | luma.lcd==2.9.0
12 | luma.emulator==1.4.0
13 | numpy==1.21.4
14 | pandas==1.3.4
15 | Pillow==10.0.1
16 | psutil==5.8.0
17 | pygame==2.1.0
18 | scipy==1.7.2
19 | sense_hat==2.2.0
20 | simpleaudio==1.0.4
21 | wifi==0.3.8
22 |
--------------------------------------------------------------------------------
/sensors.py:
--------------------------------------------------------------------------------
1 | from objects import *
2 | import time
3 | from plars import *
4 | import math
5 | import numpy
6 | from multiprocessing import Process,Queue,Pipe
7 | # the following is a sensor module for use with the PicorderOS
8 | print("Loading Unified Sensor Module")
9 |
10 |
11 | if not configure.pc:
12 | import os
13 |
14 | if configure.bme:
15 | import adafruit_bme680
16 | import busio as io
17 |
18 |
19 |
20 | if configure.sensehat:
21 | # instantiates and defines paramaters for the sensehat
22 |
23 | from sense_hat import SenseHat
24 |
25 | # instantiate a sensehat object,
26 | sense = SenseHat()
27 |
28 | # Initially clears the LEDs once loaded
29 | sense.clear()
30 |
31 | # Sets the IMU Configuration.
32 | sense.set_imu_config(True,False,False)
33 |
34 | # Prepares an array of 64 pixel triplets for the Sensehat moire display
35 | moire=[[0 for x in range(3)] for x in range(64)]
36 |
37 |
38 | if configure.envirophat:
39 | from envirophat import light, weather, motion, analog
40 |
41 | # support for the MLX90614 IR Thermo
42 | if configure.ir_thermo:
43 | import busio as io
44 | import adafruit_mlx90614
45 |
46 | # These imports are for the Sin and Tan waveform generators
47 | if configure.system_vitals:
48 | import psutil
49 | import math
50 |
51 | if configure.pocket_geiger:
52 | from PiPocketGeiger import RadiationWatch
53 |
54 | if configure.amg8833:
55 | import adafruit_amg88xx
56 | import busio
57 | import board
58 | i2c = busio.I2C(board.SCL, board.SDA)
59 | amg = adafruit_amg88xx.AMG88XX(i2c)
60 |
61 |
62 | # An object to store each sensor value and context.
63 | class Fragment(object):
64 |
65 | __slots__ = ('value','mini','maxi','dsc','sym','dev','timestamp')
66 |
67 | def __init__(self,mini,maxi,dsc,sym,dev):
68 | self.mini = mini
69 | self.maxi = maxi
70 | self.dsc = dsc
71 | self.dev = dev
72 | self.sym = sym
73 | self.value = 47
74 |
75 | # Sets the value and timestamp for the fragment.
76 | def set(self,value, timestamp):
77 |
78 | self.value = value
79 |
80 | self.timestamp = timestamp
81 |
82 | # Returns all the data for the fragment.
83 | def get(self):
84 | return [self.value, self.mini, self.maxi, self.dsc, self.sym, self.dev, self.timestamp]
85 |
86 | # Returns only the info constants for this fragment
87 | def get_info(self):
88 | return [self.mini, self.maxi, self.dsc, self.sym, self.dev]
89 |
90 | class Sensor(object):
91 | # sensors should check the configuration flags to see which sensors are
92 | # selected and then if active should poll the sensor and append it to the
93 | # sensor array.
94 |
95 | def __init__(self):
96 |
97 | #set up the necessary info for the sensors that are active.
98 |
99 | # create a simple reference for the degree symbol since we use it a lot
100 | self.deg_sym = '\xB0'
101 |
102 | self.generators = False
103 |
104 | # add individual sensor module parameters below.
105 | #0 1 2 3 4
106 | #info = (lower range, upper range, unit, symbol)
107 | #'value','min','max','dsc','sym','dev','timestamp'
108 |
109 |
110 | # testing:
111 | # data fragments (objects that contain the most recent sensor value,
112 | # plus its context) are objects called Fragment().
113 | if configure.system_vitals:
114 |
115 | self.step = 0.0
116 | self.step2 = 0.0
117 | self.steptan = 0.0
118 | totalmem = float(psutil.virtual_memory().total) / 1024
119 |
120 | self.cputemp = Fragment(0, 100, "CpuTemp", self.deg_sym + "c", "RaspberryPi")
121 | self.cpuperc = Fragment(0,100,"CpuPercent","%","Raspberry Pi")
122 | self.virtmem = Fragment(0,totalmem,"VirtualMemory","b","RaspberryPi")
123 | self.bytsent = Fragment(0,100000,"BytesSent","b","RaspberryPi")
124 | self.bytrece = Fragment(0, 100000,"BytesReceived","b","RaspberryPi")
125 |
126 | if self.generators:
127 | self.sinewav = Fragment(-100,100,"SineWave", "","RaspberryPi")
128 | self.tanwave = Fragment(-500,500,"TangentWave", "","RaspberryPi")
129 | self.coswave = Fragment(-100,100,"CosWave", "","RaspberryPi")
130 | self.sinwav2 = Fragment(-100,100,"SineWave2", "","RaspberryPi")
131 |
132 | if configure.sensehat:
133 | self.ticks = 0
134 | self.onoff = 1
135 |
136 | # instantiate a sensehat object,
137 | self.sense = SenseHat()
138 | # Initially clears the LEDs once loaded
139 | self.sense.clear()
140 | # Sets the IMU Configuration.
141 | self.sense.set_imu_config(True,False,False)
142 | # activates low light conditions to not blind the user.
143 | self.sense.low_light = True
144 |
145 | self.sh_temp = Fragment(0,65,"Thermometer",self.deg_sym + "c", "sensehat")
146 | self.sh_humi = Fragment(20,80,"Hygrometer", "%", "sensehat")
147 | self.sh_baro = Fragment(260,1260,"Barometer","hPa", "sensehat")
148 | self.sh_magx = Fragment(-500,500,"MagnetX","G", "sensehat")
149 | self.sh_magy = Fragment(-500,500,"MagnetY","G", "sensehat")
150 | self.sh_magz = Fragment(-500,500,"MagnetZ","G", "sensehat")
151 | self.sh_accx = Fragment(-500,500,"AccelX","g", "sensehat")
152 | self.sh_accy = Fragment(-500,500,"AccelY","g", "sensehat")
153 | self.sh_accz = Fragment(-500,500,"AccelZ","g", "sensehat")
154 |
155 | if configure.ir_thermo:
156 | i2c = io.I2C(configure.PIN_SCL, configure.PIN_SDA, frequency=100000)
157 | self.mlx = adafruit_mlx90614.MLX90614(i2c)
158 |
159 | self.irt_ambi = Fragment(0,80,"IR ambient [mlx]",self.deg_sym + "c")
160 | self.irt_obje = Fragment(0,80,"IR object [mlx]",self.deg_sym + "c")
161 |
162 | if configure.envirophat: # and not configure.simulate:
163 |
164 | self.ep_temp = Fragment(0,65,"Thermometer",self.deg_sym + "c","Envirophat")
165 | self.ep_colo = Fragment(20,80,"Colour", "RGB","Envirophat")
166 | self.ep_baro = Fragment(260,1260,"Barometer","hPa","Envirophat")
167 | self.ep_magx = Fragment(-500,500,"Magnetomer X","G","Envirophat")
168 | self.ep_magy = Fragment(-500,500,"Magnetomer Y","G","Envirophat")
169 | self.ep_magz = Fragment(-500,500,"Magnetomer Z","G","Envirophat")
170 | self.ep_accx = Fragment(-500,500,"Accelerometer X (EP)","g","Envirophat")
171 | self.ep_accy = Fragment(-500,500,"Accelerometer Y (EP)","g","Envirophat")
172 | self.ep_accz = Fragment(-500,500,"Accelerometer Z (EP)","g","Envirophat")
173 |
174 | if configure.bme:
175 | # Create library object using our Bus I2C port
176 | i2c = io.I2C(configure.PIN_SCL, configure.PIN_SDA)
177 | self.bme = adafruit_bme680.Adafruit_BME680_I2C(i2c, address=0x76, debug=False)
178 |
179 | self.bme_temp = Fragment(-40,85,"Thermometer",self.deg_sym + "c", "BME680")
180 | self.bme_humi = Fragment(0,100,"Hygrometer", "%", "BME680")
181 | self.bme_press = Fragment(300,1100,"Barometer","hPa", "BME680")
182 | self.bme_voc = Fragment(300000,1100000,"VOC","KOhm", "BME680")
183 |
184 |
185 | #if configure.bme_bsec:
186 | #self.voc_procc = subprocess.Popen(['./bsec_bme680'], stdout=subprocess.PIPE)
187 | # self.bme_bsec = Fragment(-40,85,"Quality",self.deg_sym + "Q", "BME680")
188 |
189 | if configure.pocket_geiger:
190 | self.radiat = Fragment(0.0, 10000.0, "Radiation", "urem/hr", "pocketgeiger")
191 | self.radiation = RadiationWatch(configure.PG_SIG,configure.PG_NS)
192 | self.radiation.setup()
193 |
194 | if configure.amg8833:
195 | self.amg_high = Fragment(0.0, 80.0, "IRHigh", self.deg_sym + "c", "amg8833")
196 | self.amg_low = Fragment(0.0, 80.0, "IRLow", self.deg_sym + "c", "amg8833")
197 |
198 | configure.sensor_info = self.get_all_info()
199 |
200 |
201 | def get_all_info(self):
202 | info = self.get()
203 |
204 | allinfo = []
205 | for fragment in info:
206 | thisfrag = [fragment.dsc,fragment.dev,fragment.sym, fragment.mini, fragment.maxi]
207 | allinfo.append(thisfrag)
208 | return allinfo
209 |
210 | def sin_gen(self):
211 | wavestep = math.sin(self.step)
212 | self.step += .1
213 | return wavestep
214 |
215 | def tan_gen(self):
216 | wavestep = math.tan(self.steptan)
217 | self.steptan += .1
218 | return wavestep
219 |
220 | def sin2_gen(self, offset = 0):
221 | wavestep = math.sin(self.step2)
222 | self.step2 += .05
223 | return wavestep
224 |
225 | def cos_gen(self, offset = 0):
226 | wavestep = math.cos(self.step)
227 | self.step += .1
228 | return wavestep
229 |
230 |
231 | def get(self):
232 |
233 | #sensorlist holds all the data fragments to be handed to plars.
234 | sensorlist = []
235 |
236 | #timestamp for this sensor get.
237 | timestamp = time.time()
238 |
239 |
240 |
241 | if configure.bme:
242 |
243 | self.bme_temp.set(self.bme.temperature,timestamp)
244 | self.bme_humi.set(self.bme.humidity,timestamp)
245 | self.bme_press.set(self.bme.pressure,timestamp)
246 | self.bme_voc.set(self.bme.gas / 1000,timestamp)
247 |
248 | sensorlist.extend((self.bme_temp,self.bme_humi,self.bme_press, self.bme_voc))
249 |
250 | if configure.sensehat:
251 |
252 | magdata = sense.get_compass_raw()
253 | acceldata = sense.get_accelerometer_raw()
254 |
255 | self.sh_temp.set(sense.get_temperature(),timestamp)
256 | self.sh_humi.set(sense.get_humidity(),timestamp)
257 | self.sh_baro.set(sense.get_pressure(),timestamp)
258 | self.sh_magx.set(magdata["x"],timestamp)
259 | self.sh_magy.set(magdata["y"],timestamp)
260 | self.sh_magz.set(magdata["z"],timestamp)
261 | self.sh_accx.set(acceldata['x'],timestamp)
262 | self.sh_accy.set(acceldata['y'],timestamp)
263 | self.sh_accz.set(acceldata['z'],timestamp)
264 |
265 | sensorlist.extend((self.sh_temp, self.sh_baro, self.sh_humi, self.sh_magx, self.sh_magy, self.sh_magz, self.sh_accx, self.sh_accy, self.sh_accz))
266 |
267 | if configure.pocket_geiger:
268 |
269 | data = self.radiation.status()
270 | rad_data = float(data["uSvh"])
271 |
272 | # times 100 to convert to urem/h
273 | self.radiat.set(rad_data*100, timestamp)
274 |
275 | sensorlist.append(self.radiat)
276 |
277 | if configure.amg8833:
278 | data = numpy.array(amg.pixels)
279 |
280 | high = numpy.max(data)
281 | low = numpy.min(data)
282 |
283 | self.amg_high.set(high,timestamp)
284 | self.amg_low.set(low,timestamp)
285 |
286 | sensorlist.extend((self.amg_high, self.amg_low))
287 |
288 | if configure.envirophat:
289 | self.rgb = light.rgb()
290 | self.analog_values = analog.read_all()
291 | self.mag_values = motion.magnetometer()
292 | self.acc_values = [round(x, 2) for x in motion.accelerometer()]
293 |
294 | self.ep_temp.set(weather.temperature(),timestamp)
295 | self.ep_colo.set(light.light(),timestamp)
296 | self.ep_baro.set(weather.pressure(unit='hpa'), timestamp)
297 | self.ep_magx.set(self.mag_values[0],timestamp)
298 | self.ep_magy.set(self.mag_values[1],timestamp)
299 | self.ep_magz.set(self.mag_values[2],timestamp)
300 | self.ep_accx.set(self.acc_values[0],timestamp)
301 | self.ep_accy.set(self.acc_values[1],timestamp)
302 | self.ep_accz.set(self.acc_values[2],timestamp)
303 |
304 | sensorlist.extend((self.ep_temp, self.ep_baro, self.ep_colo, self.ep_magx, self.ep_magy, self.ep_magz, self.ep_accx, self.ep_accy, self.ep_accz))
305 |
306 | # provides the basic definitions for the system vitals sensor readouts
307 | if configure.system_vitals:
308 |
309 | if not configure.pc:
310 | f = os.popen("cat /sys/class/thermal/thermal_zone0/temp").readline()
311 | t = float(f[0:2] + "." + f[2:])
312 | else:
313 | t = float(47)
314 |
315 | # update each fragment with new data and mark the time.
316 | self.cputemp.set(t,timestamp)
317 | self.cpuperc.set(float(psutil.cpu_percent()),timestamp)
318 | self.virtmem.set(float(psutil.virtual_memory().available * 0.0000001),timestamp)
319 | self.bytsent.set(float(psutil.net_io_counters().bytes_recv * 0.00001),timestamp)
320 | self.bytrece.set(float(psutil.net_io_counters().bytes_recv * 0.00001),timestamp)
321 |
322 | if self.generators:
323 | self.sinewav.set(float(self.sin_gen()*100),timestamp)
324 | self.tanwave.set(float(self.tan_gen()*100),timestamp)
325 | self.coswave.set(float(self.cos_gen()*100),timestamp)
326 | self.sinwav2.set(float(self.sin2_gen()*100),timestamp)
327 |
328 | # load the fragments into the sensorlist
329 | sensorlist.extend((self.cputemp, self.cpuperc, self.virtmem, self.bytsent, self.bytrece))
330 |
331 | if self.generators:
332 | sensorlist.extend((self.sinewav, self.tanwave, self.coswave, self.sinwav2))
333 |
334 |
335 |
336 | configure.max_sensors[0] = len(sensorlist)
337 |
338 | if len(sensorlist) < 1:
339 | print("NO SENSORS LOADED")
340 |
341 | return sensorlist
342 |
343 | def end(self):
344 | if configure.pocket_geiger:
345 | self.radiation.close()
346 |
347 |
348 | class MLX90614():
349 |
350 | MLX90614_RAWIR1=0x04
351 | MLX90614_RAWIR2=0x05
352 | MLX90614_TA=0x06
353 | MLX90614_TOBJ1=0x07
354 | MLX90614_TOBJ2=0x08
355 |
356 | MLX90614_TOMAX=0x20
357 | MLX90614_TOMIN=0x21
358 | MLX90614_PWMCTRL=0x22
359 | MLX90614_TARANGE=0x23
360 | MLX90614_EMISS=0x24
361 | MLX90614_CONFIG=0x25
362 | MLX90614_ADDR=0x0E
363 | MLX90614_ID1=0x3C
364 | MLX90614_ID2=0x3D
365 | MLX90614_ID3=0x3E
366 | MLX90614_ID4=0x3F
367 |
368 | comm_retries = 5
369 | comm_sleep_amount = 0.1
370 |
371 | def __init__(self, address=0x5a, bus_num=1):
372 | self.bus_num = bus_num
373 | self.address = address
374 | self.bus = smbus.SMBus(bus=bus_num)
375 |
376 | def read_reg(self, reg_addr):
377 | err = None
378 | for i in range(self.comm_retries):
379 | try:
380 | return self.bus.read_word_data(self.address, reg_addr)
381 | except IOError as e:
382 | err = e
383 | #"Rate limiting" - sleeping to prevent problems with sensor
384 | #when requesting data too quickly
385 | sleep(self.comm_sleep_amount)
386 |
387 | #By this time, we made a couple requests and the sensor didn't respond
388 | #(judging by the fact we haven't returned from this function yet)
389 | #So let's just re-raise the last IOError we got
390 | raise err
391 |
392 | def data_to_temp(self, data):
393 | temp = (data*0.02) - 273.15
394 | return temp
395 |
396 | def get_amb_temp(self):
397 | data = self.read_reg(self.MLX90614_TA)
398 | return self.data_to_temp(data)
399 |
400 | def get_obj_temp(self):
401 | data = self.read_reg(self.MLX90614_TOBJ1)
402 | return self.data_to_temp(data)
403 |
404 | # function to use the sensor class as a process.
405 | def sensor_process(conn):
406 | #init sensors
407 | sensors = Sensor()
408 |
409 | while True:
410 | #constantly grab sensors.
411 | conn.send(sensors.get())
412 |
413 | def threaded_sensor():
414 |
415 | sensors = Sensor()
416 | sensors.get()
417 | configure.buffer_size[0] = configure.graph_size[0]*len(configure.sensor_info)
418 | configure.sensor_ready[0] = True
419 |
420 | timed = timer()
421 | sensors.end()
422 | parent_conn,child_conn = Pipe()
423 | sense_process = Process(target=sensor_process, args=(child_conn,))
424 | sense_process.start()
425 |
426 | while not configure.status == "quit":
427 |
428 | if timed.timelapsed() > configure.samplerate[0]:
429 |
430 | timed.logtime()
431 | data = parent_conn.recv()
432 | #print(data)
433 | plars.update(data)
434 |
435 | sense_process.terminate()
436 |
--------------------------------------------------------------------------------
/tos_display.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 |
3 | # This display module uses Pygame to draw picorder routines to the screen.
4 | # It is built upon the original Picorder UI and is intended to be used for TOS
5 | # styled tricorders.
6 | print("Loading 320x240 Duotronic Interface")
7 | # The following are some necessary modules for the Picorder.
8 | from asyncio import sleep
9 | from pickle import FALSE
10 | import pygame, time, os
11 | from pyvidplayer import Video
12 |
13 |
14 | os.environ["SDL_VIDEO_CENTERED"] = "1"
15 | from pathlib import Path
16 | from plars import *
17 | from objects import *
18 | from input import *
19 |
20 | from modulated_em import *
21 |
22 | if not configure.pc:
23 | if configure.tr108:
24 | import os
25 |
26 | SAMPLE_SIZE = configure.samples
27 | GRAPH_WIDTH = configure.graph_width #280
28 | GRAPH_HEIGHT = configure.graph_height #182
29 | GRAPH_X = configure.graph_x #18
30 | GRAPH_Y = configure.graph_y #20
31 | GRAPH_X2 = GRAPH_X + GRAPH_WIDTH
32 | GRAPH_Y2 = GRAPH_Y + GRAPH_HEIGHT
33 |
34 | os.environ['PYGAME_BLEND_ALPHA_SDL2'] = '1'
35 | # The following commands initiate a pygame environment.
36 | pygame.init()
37 | pygame.font.init()
38 | pygame.display.set_caption('PicorderOS')
39 |
40 | # The following commands disable the mouse and cursor.
41 | #pygame.event.set_blocked(pygame.MOUSEMOTION)
42 | #pygame.mouse.set_visible(0)
43 |
44 | # set the screen configuration
45 | resolution = (320,240)
46 | def_modes = 16
47 | refreshrate = 0
48 |
49 | # controls the margins to adjust for adjusting text positioning.
50 | marginright = 16
51 | marginleft = 16
52 |
53 | # The following lists are for my colour standards.
54 | red = (255,0,0)
55 | green = (106,255,69)
56 | blue = (99,157,255)
57 | yellow = (255,221,5)
58 | orange = (255,192,2)
59 | black = (0,0,0)
60 | white = (255,255,255)
61 |
62 | theme1 = [red,green,yellow]
63 | theme2 = [blue,green,white]
64 | theme3 = [blue, white, red]
65 | themes = [theme1,theme2, theme3]
66 | themenames = ["alpha", "beta", "delta"]
67 |
68 |
69 | # The following lists/objects are for UI elements.
70 | titleFont = "assets/babs.otf"
71 | blueInsignia = pygame.image.load('assets/icon.png')
72 | pioslogo = pygame.image.load('assets/Med_Picorder_Logo.png')
73 | backplane = pygame.image.load('assets/background.png')
74 | backgraph = pygame.image.load('assets/backgraph.png')
75 | slidera = pygame.image.load('assets/slider.png')
76 | sliderb = pygame.image.load('assets/slider2.png')
77 | videobg = pygame.image.load('assets/videobg.png')
78 | status = "startup"
79 | last_status = "startup"
80 |
81 | # sets the icon for the program (will show up in docks/taskbars on PCs)
82 | #pygame.display.set_icon(blueInsignia)
83 |
84 |
85 |
86 | # The following function defines button behaviours and allows the program to
87 | # query the button events and act accordingly. This function is deprecated.
88 | def butswitch():
89 | pygame.event.get()
90 | key = pygame.key.get_pressed()
91 | message = []
92 |
93 | if key[pygame.K_RIGHT]:
94 | message += ["right"]
95 |
96 | if key[pygame.K_LEFT]:
97 | message += ["left"]
98 |
99 | if key[pygame.K_UP]:
100 | message += ["up"]
101 |
102 | if key[pygame.K_a]:
103 | configure.auto[0] = not configure.auto[0]
104 |
105 | return message
106 |
107 | # the following class defines simple text labels
108 |
109 | class Label(object):
110 | def __init__(self):
111 | self.x = 0
112 | self.y = 0
113 | self.color = white
114 | self.fontSize = 30
115 | self.myfont = pygame.font.Font(titleFont, self.fontSize)
116 | text = "hello"
117 | self.size = self.myfont.size(text)
118 | self.scaler = 3
119 |
120 | # Sets the paramaters of the text, used when ready to push the text.
121 | def update(self, content, fontSize, nx, ny, fontType, color):
122 | self.x = nx
123 | self.y = ny
124 | self.content = content
125 | self.fontSize = fontSize
126 | self.myfont = pygame.font.Font(fontType, self.fontSize)
127 | self.color = color
128 |
129 | def r_align(self,x,y):
130 | size = self.getrect()
131 | textposx = x-size[0]
132 | textposy = y
133 | self.update(self.content,self.fontSize,textposx,textposy,titleFont,self.color)
134 |
135 | # centers the text within an envelope
136 | def center(self,w,h,x,y):
137 | size = self.getrect()
138 | xmid = x + w/2
139 | ymid = y + h/2
140 | textposx = xmid - (size[0]/2)
141 | textposy = ymid - (size[1]/2) + self.scaler
142 | self.update(self.content,self.fontSize,textposx,textposy,titleFont,self.color)
143 |
144 | # returns the width and height of the desired text (useful for justifying)
145 | def getrect(self):
146 | label = self.myfont.render(self.content, 1, self.color)
147 | textw = label.get_width()
148 | texth = label.get_height()
149 | return textw,texth
150 |
151 | # returns only the width of the text.
152 | def get_size(self, content):
153 | width, height = self.myfont.size(content)
154 | return width
155 |
156 | # finally draws the text to screen
157 | def draw(self, surface):
158 | label = self.myfont.render(self.content, 1, self.color)
159 | surface.blit(label, (self.x, self.y))
160 |
161 | # this class provides functionality for interactive text labels.
162 | class SelectableLabel(Label):
163 |
164 | def __init__(self, oper, special = 0):
165 |
166 | # special determines the behaviour of the label for each type of
167 | # operator the class is supplied. There may be multiple types of int or
168 | # boolean based configuration parameters so this variable helps make
169 | # new options
170 |
171 | self.special = special
172 |
173 | # coordinates
174 | self.x = 0
175 | self.y = 0
176 |
177 | # basic graphical parameters
178 | self.color = white
179 | self.fontSize = 33
180 | self.myfont = pygame.font.Font(titleFont, self.fontSize)
181 | text = "Basic Item"
182 | self.size = self.myfont.size(text)
183 | self.scaler = 3
184 | self.selected = False
185 | self.indicator = Image()
186 | self.content = "default"
187 |
188 | # this variable is a reference to a list stored in "objects.py"
189 | # containing either a boolean or an integer. This allows the Selectable
190 | # Lable to actually change settings on the fly.
191 | self.oper = oper
192 |
193 | def update(self, content, fontSize, nx, ny, fontType, color):
194 | self.x = nx
195 | self.y = ny
196 | self.content = content
197 | self.fontSize = fontSize
198 | self.myfont = pygame.font.Font(fontType, self.fontSize)
199 | self.color = color
200 | self.indicator.update(sliderb, nx - 23, ny+1)
201 |
202 | # this function is called when the user decides to change a setting.
203 | def toggle(self):
204 |
205 | # if the parameter supplied is a boolean
206 | if isinstance(self.oper[0], bool):
207 | #toggle its state
208 | self.oper[0] = not self.oper[0]
209 |
210 | #if the parameter supplied is an integer
211 | elif isinstance(self.oper[0], int):
212 |
213 | # increment the integer.
214 | self.oper[0] += 1
215 |
216 | # if the integer is larger than the pool of available sensors.
217 | # this assumes this selectable label is being used to control
218 | # sensor selection.
219 | if self.special == 1 and self.oper[0] > configure.max_sensors[0]-1:
220 | self.oper[0] = 0
221 |
222 | if self.special == 2 and self.oper[0] > (len(themes) - 1):
223 | self.oper[0] = 0
224 |
225 | return self.oper[0]
226 |
227 | def draw(self, surface):
228 | if self.selected:
229 | self.indicator.draw(surface)
230 |
231 | label = self.myfont.render(self.content, 1, self.color)
232 |
233 | dsc,dev,sym,maxi,mini = configure.sensor_info[self.oper[0]]
234 |
235 | status_text = "dummy"
236 | if self.special == 0:
237 | status_text = str(self.oper[0])
238 | elif self.special == 1:
239 | status_text = dsc
240 | elif self.special == 2:
241 | status_text = themenames[self.oper[0]]
242 |
243 | pos = resolution[0] - (self.get_size(status_text) + 37)
244 | state = self.myfont.render(status_text, 1, self.color)
245 |
246 |
247 | surface.blit(label, (self.x, self.y))
248 | surface.blit(state, (pos, self.y))
249 |
250 | # the following class is used to display images
251 | class Image(object):
252 | def __init__(self):
253 | self.x = 258
254 | self.y = 66
255 | self.Img = blueInsignia
256 |
257 | def update(self, image, nx, ny):
258 | self.x = nx
259 | self.y = ny
260 | self.Img = image
261 |
262 |
263 | def draw(self, surface):
264 | surface.blit(self.Img, (self.x,self.y))
265 |
266 |
267 | # The following class is used to prepare sensordata for display on the graph.
268 | class graphlist(object):
269 |
270 | # the following is constructor code to give each object a list suitable for storing all our graph data, in this case it is 145 spaces.
271 | def __init__(self):
272 | self.glist = []
273 | for i in range(145):
274 | self.glist.append(110.5)
275 |
276 | # the following function returns the list.
277 | def grablist(self):
278 | return self.glist
279 |
280 | # the following appends data to the list.
281 | def updatelist(self, data):
282 |
283 | #puts a new sensor value at the end
284 | self.glist.append(data)
285 | #pop the oldest value off
286 | self.glist.pop(0)
287 |
288 | # the following pairs the list of values with coordinates on the X axis. The supplied variables are the starting X coordinates and spacing between each point.
289 | def graphprep(list):
290 | linepoint = GRAPH_X
291 | jump = GRAPH_WIDTH / SAMPLE_SIZE
292 | newlist = []
293 |
294 | for i in range(SAMPLE_SIZE):
295 | # if not enough data
296 | if i > (len(list) - 1):
297 | # make the data show as 0 scale. (y coord 110)
298 | item = 110
299 | else:
300 | # otherwise just keep plotting the data provided.
301 | item = list[i]
302 |
303 | newlist.append((linepoint,item))
304 | linepoint = linepoint + jump
305 |
306 | return newlist
307 |
308 | # graphit is a quick tool to help prepare graphs by changing their data from
309 | # true values into scaled values for their pixel position on screen.
310 | def graphit(data, auto = True):
311 |
312 | #grabs our databuffer object.
313 | buffer = data
314 |
315 |
316 | prep = []
317 |
318 | for i in data:
319 |
320 |
321 | if configure.auto[0]:
322 | # autoscales the data.
323 | data_high = max(buffer)
324 | data_low = min(buffer)
325 | # scales the data on the y axis.
326 | prep.append(numpy.interp(i, (data_low, data_high), (GRAPH_Y2, GRAPH_Y)))
327 | else:
328 | prep.append(numpy.interp(i, (data_low, data_high), (GRAPH_Y2, GRAPH_Y))) # <----need to fix total scale.
329 |
330 |
331 | return graphprep(prep)
332 |
333 |
334 | # the following function runs the startup animation
335 | def startUp(surface,timeSinceStart):
336 | #This function draws the opening splash screen for the program that provides the user with basic information.
337 |
338 | #Sets a black screen ready for our UI elements
339 | surface.fill(black)
340 |
341 | #Instantiates the components of the scene
342 | insignia = Image()
343 | mainTitle = Label()
344 | secTitle = Label()
345 | secblurb = Label()
346 |
347 | logoposx = (resolution[0]/2) - (98/2)
348 |
349 | #sets out UI objects with the appropriate data
350 | insignia.update(pioslogo, logoposx, 37)
351 |
352 | secTitle.update("PicorderOS " + configure.version,19,37,210,titleFont,blue)
353 | secTitle.center(resolution[0],20,0,190)
354 | secblurb.update(configure.boot_message,15,37,210,titleFont,blue)
355 | secblurb.center(resolution[0],20,0,210)
356 |
357 | #writes our objects to the buffer
358 | insignia.draw(surface)
359 |
360 | #checks time
361 | timenow = time.time()
362 |
363 | #compares time just taken with time of start to animate the apperance of text
364 | #if (timenow - timeSinceStart) > .5:
365 | #mainTitle.draw(surface)
366 |
367 | #if (timenow - timeSinceStart) > 1:
368 | secTitle.draw(surface)
369 | secblurb.draw(surface)
370 |
371 | pygame.display.flip()
372 | elapsed = timenow - timeSinceStart
373 |
374 | #waits for x seconds to elapse before returning the state that will take us to the sensor readout
375 | if elapsed > configure.boot_delay and configure.sensor_ready[0]:
376 | return "mode_a"
377 | else:
378 | return "startup"
379 |
380 | # the following function displays version information about the program
381 | def about(surface):
382 |
383 | #Sets a black screen ready for our UI elements
384 | surface.fill(black)
385 |
386 | #Instantiates the components of the scene
387 | insignia = Image()
388 | mainTitle = Label()
389 | version = Label()
390 | secTitle = Label()
391 | secblurb = Label()
392 |
393 | logoposx = (resolution[0]/2) - (226/2)
394 |
395 | insignia.update(pioslogo, logoposx, 30)
396 | version.update(configure.version,17,37,210,titleFont,blue)
397 | version.center(resolution[0],20,0,90)
398 |
399 | secTitle.update(configure.author,17,37,210,titleFont,blue)
400 | secTitle.center(resolution[0],20,0,110)
401 |
402 | mainTitle.update("Written in Python",17,37,210,titleFont,blue)
403 | mainTitle.center(resolution[0],20,0,125)
404 | secblurb.update("Developed By Chris Barrett",15,37,210,titleFont,blue)
405 | secblurb.center(resolution[0],20,0,210)
406 |
407 | #writes our objects to the buffer
408 | insignia.draw(surface)
409 |
410 |
411 | version.draw(surface)
412 | mainTitle.draw(surface)
413 | secTitle.draw(surface)
414 | secblurb.draw(surface)
415 |
416 | pygame.display.flip()
417 |
418 |
419 |
420 | class Settings_Panel(object):
421 |
422 | def __init__(self,surface):
423 |
424 | self.left_margin = 37
425 |
426 | self.index = 0
427 | self.surface = surface
428 | self.labelstart = 47
429 | self.labeljump = 21
430 |
431 | self.titlelabel = Label()
432 | self.titlelabel.update("Control Panel",25,17,15,titleFont,orange)
433 |
434 | self.option1 = SelectableLabel(configure.sensor1, special = 1)
435 | self.option1.update("Graph 1: ",20,self.left_margin,47,titleFont,themes[configure.theme[0]][0])
436 |
437 | self.option2 = SelectableLabel(configure.sensor2, special = 1)
438 | self.option2.update("Graph 2: ", 20, self.left_margin, self.labelstart + self.labeljump, titleFont, themes[configure.theme[0]][1])
439 |
440 | self.option3 = SelectableLabel(configure.sensor3, special = 1)
441 | self.option3.update("Graph 3: ", 20, self.left_margin, self.labelstart + (self.labeljump*2), titleFont, themes[configure.theme[0]][2])
442 |
443 | self.option4 = SelectableLabel(configure.theme, special = 2)
444 | self.option4.update("Theme: ", 20, self.left_margin, self.labelstart + (self.labeljump*3), titleFont, orange)
445 |
446 | self.option5 = SelectableLabel(configure.auto)
447 | self.option5.update("Auto Range: ", 20, self.left_margin, self.labelstart + (self.labeljump*4), titleFont, orange)
448 |
449 | self.option6 = SelectableLabel(configure.leds)
450 | self.option6.update("LEDs: ", 20, self.left_margin, self.labelstart + (self.labeljump*5), titleFont, orange)
451 |
452 | self.option7 = SelectableLabel(configure.moire)
453 | self.option7.update("Moire: ", 20, self.left_margin, self.labelstart + (self.labeljump*6), titleFont, orange)
454 |
455 | self.options = [self.option1,self.option2, self.option3, self.option4, self.option5, self.option6, self.option7]
456 |
457 | def colour_update(self):
458 | self.option1.update("Graph 1: ",20,self.left_margin,47,titleFont,themes[configure.theme[0]][0])
459 | self.option2.update("Graph 2: ", 20, self.left_margin, 68, titleFont, themes[configure.theme[0]][1])
460 | self.option3.update("Graph 3: ", 20, self.left_margin, 90, titleFont, themes[configure.theme[0]][2])
461 | self.option4.update("Theme: ", 20, self.left_margin, 111, titleFont, orange)
462 | self.option5.update("Auto Range: ", 20, self.left_margin, 132, titleFont, orange)
463 | self.option6.update("LEDs: ", 20, self.left_margin, 154, titleFont, orange)
464 | self.option7.update("Moire: ", 20, self.left_margin, 176, titleFont, orange)
465 |
466 |
467 | def frame(self):
468 |
469 | self.surface.fill(black)
470 |
471 | self.titlelabel.draw(self.surface)
472 |
473 | for i in range(len(self.options)):
474 | if i == self.index:
475 | self.options[i].selected = True
476 | else:
477 | self.options[i].selected = False
478 |
479 | self.options[i].draw(self.surface)
480 |
481 | self.colour_update()
482 | # draws UI to frame buffer
483 | pygame.display.flip()
484 |
485 | result = "settings"
486 |
487 | if configure.eventready[0]:
488 |
489 | # The following code handles inputs and button presses.
490 | keys = configure.eventlist[0]
491 |
492 | # if a key is registering as pressed.
493 | if keys[0]:
494 | self.index += 1
495 |
496 | if self.index > (len(self.options) - 1):
497 | self.index = 0
498 |
499 | if keys[1]:
500 | self.options[self.index].toggle()
501 |
502 | if keys[2]:
503 | result = configure.last_status[0]
504 | configure.eventready[0] = False
505 |
506 | configure.eventready[0] = False
507 |
508 |
509 | return result
510 |
511 |
512 | class EMFrame(object):
513 | def __init__(self, surface):
514 |
515 | self.wifi = Wifi_Scan()
516 |
517 | self.graphcycle = 0
518 |
519 | # Sets the topleft origin of the graph
520 | self.graphx = 20
521 | self.graphy = 58
522 |
523 | # Pygame drawing surface.
524 | self.surface = surface
525 |
526 |
527 | # Sets the x and y span of the graph
528 | self.gspanx = 135
529 | self.gspany = 29
530 | self.titlex = 23
531 | self.titley = 2
532 |
533 | self.high = 0
534 | self.low = 0
535 | self.average = 0
536 | self.labely = 4
537 | self.labelxr = 156
538 |
539 |
540 | self.selection = 0
541 |
542 | # create our graph_screen
543 | self.Signal_Graph = graph_area(0,(self.graphx,self.graphy),(self.gspanx,self.gspany),self.graphcycle, lcars_pink, width = 2, type = 1, samples = 45)
544 |
545 | self.title = LabelObj("Modulated EM Scan",titlefont, colour = lcars_orange)
546 |
547 | self.signal_name = LabelObj("SSID",bigfont, colour = lcars_peach)
548 | self.signal_name_sm = LabelObj("SSID",font, colour = lcars_peach)
549 |
550 | self.signal_strength = LabelObj("ST",giantfont, colour = lcars_peach)
551 | self.signal_strength_sm = LabelObj("ST",littlefont, colour = lcars_peach)
552 |
553 | self.signal_frequency = LabelObj("FQ",titlefont, colour = lcars_orpeach)
554 | self.signal_frequency_sm = LabelObj("FQ",littlefont, colour = lcars_peach)
555 | self.signal_mac = LabelObj("MAC",font, colour = lcars_orpeach)
556 |
557 | self.list = Label_List(22,35, colour = lcars_peach)
558 |
559 | self.burgerfull = Image.open('assets/lcarsburgerframefull.png')
560 |
561 | def push(self, draw):
562 |
563 | status = "mode_b"
564 |
565 | # input handling
566 | if configure.eventready[0]:
567 | keys = configure.eventlist[0]
568 |
569 |
570 | # ------------- Input handling -------------- #
571 | if keys[0]:
572 | status = "mode_a"
573 | configure.eventready[0] = False
574 | return status
575 |
576 | if keys[1]:
577 | self.selection += 1
578 |
579 | if self.selection >= 3:
580 | self.selection = 0
581 | pass
582 |
583 | if keys[2]:
584 | status = "settings"
585 | configure.last_status[0] = "mode_b"
586 | configure.eventready[0] = False
587 | return status
588 |
589 | configure.eventready[0] = False
590 |
591 | self.wifi.update_plars()
592 |
593 | # details on strongest wifi network.
594 | if self.selection == 0:
595 |
596 | # grab EM data from plars
597 | info = plars.get_top_em_info()[0]
598 |
599 | # draw screen elements
600 | self.Signal_Graph.render(draw)
601 | self.title.string = "Dominant Transciever"
602 | self.title.r_align(self.labelxr,self.titley,draw)
603 |
604 | self.signal_name.push(20,35,draw, string = info[0])
605 |
606 | self.signal_strength.string = str(info[1]) + " DB"
607 | self.signal_strength.r_align(self.labelxr,92,draw)
608 | self.signal_frequency.push(20,92,draw, string = info[3])
609 | self.signal_mac.push(20,111, draw, string = info[6])
610 |
611 | #list of all wifi ssids
612 | if self.selection == 1:
613 |
614 | # list to hold the data labels
615 | list_for_labels = []
616 |
617 | # grab EM list
618 | em_list = plars.get_recent_em_list()
619 |
620 | #sort it so strongest is first
621 | sorted_em_list = sorted(em_list, key=itemgetter(1), reverse = True)
622 |
623 | # prepare a list of the data received for display
624 | for ssid in sorted_em_list:
625 | name = str(ssid[0])
626 | strength = str(ssid[1])
627 |
628 | label = strength + " dB • " + name
629 |
630 | list_for_labels.append(label)
631 | self.title.string = "Modulated EM Scan"
632 | self.title.r_align(self.labelxr,self.titley,draw)
633 | self.list.update(list_for_labels,draw)
634 |
635 | # assign each list element and its
636 |
637 | # frequency intensity map
638 | if self.selection == 2:
639 | # returns the data necessary for freq_intensity map with EM.
640 | # displays each SSID as a line segment. Its position along the x is
641 | # determined by frequency. Its height by its signal strength.
642 |
643 | # change Background
644 |
645 | #draw.rectangle((0,0,320,240),(0,0,0))
646 | draw._image = self.burgerfull
647 | #draw.bitmap((0,0), )
648 |
649 | #draw round rect background
650 | draw.rectangle((18,49,158,126), outline = lcars_blue)
651 |
652 | #draw labels
653 | self.title.string = "EM Channel Analysis"
654 | self.title.r_align(self.labelxr,self.titley,draw)
655 |
656 | #grab EM list
657 | unsorted_em_list = plars.get_recent_em_list()
658 |
659 | # sort it so strongest is first.
660 | em_list = sorted(unsorted_em_list, key=itemgetter(1), reverse = True)
661 |
662 | # create a list to hold just the info we need for the screen.
663 | items_list = []
664 |
665 | #filter info into items_list
666 | for ssid in em_list:
667 | name = str(ssid[0])
668 | strength = ssid[1]
669 | frequency = ssid[3]
670 | frequency = float(frequency.replace(' GHz', ''))
671 |
672 | # determing x coordinate
673 | screenpos = numpy.interp(frequency,(2.412, 2.462),(25, 151))
674 |
675 | # determine y coordinate
676 | lineheight = numpy.interp(strength, (-100, 0), (126, 55))
677 |
678 | # package into list
679 | this_ssid = (name,screenpos,lineheight,strength,frequency)
680 | items_list.append(this_ssid)
681 |
682 |
683 | #for each item in item_list
684 | for index, item in enumerate(items_list):
685 |
686 | # determine dot coordinates.
687 | cords = ((item[1],126),(item[1],item[2]))
688 | x1 = cords[1][0] - (6/2)
689 | y1 = cords[1][1] - (6/2)
690 | x2 = cords[1][0] + (6/2)
691 | y2 = cords[1][1] + (6/2)
692 |
693 | # if this is the strongest singal draw labels and change colour.
694 | if index == 0:
695 | draw.line(cords,lcars_peach,1)
696 | draw.ellipse([x1,y1,x2,y2],lcars_peach)
697 |
698 |
699 | name = item[0]
700 | trunc_name = name[:16] + (name[16:] and '..')
701 | # draw the strongest signals name, top center
702 | self.signal_name_sm.push(19,34,draw,string = trunc_name)
703 |
704 | # put strength at lower left
705 | strength_string = str(item[3]) + " DB"
706 | #self.signal_strength_sm.push(19,114,draw,string = strength_string)
707 |
708 | # put frequency at lower right
709 | self.signal_frequency_sm.string = str(item[4]) + " GHZ" + ", " + strength_string
710 | self.signal_frequency_sm.r_align(157,37,draw)
711 |
712 | # otherwise just draw the line and dot in the usual color
713 | else:
714 | draw.line(cords,lcars_bluer,1)
715 | draw.ellipse([x1,y1,x2,y2],lcars_bluer)
716 |
717 |
718 |
719 |
720 | return status
721 |
722 |
723 |
724 |
725 |
726 | # The graph screen object is a self contained screen that is fed the surface
727 | # and the sensor at the current moment and draws a frame when called.
728 | class Graph_Screen(object):
729 |
730 | # Draws three graphs in a grid and three corresponding labels.
731 |
732 | def __init__(self,surface):
733 |
734 |
735 | # for long presses
736 | self.input_timer = timer()
737 | self.presstime = 5
738 | self.longpressed = [False, False, False]
739 |
740 | # State variable
741 | self.status = "mode_a"
742 | self.selection = 0
743 |
744 | # An fps controller
745 | self.drawinterval = timer()
746 |
747 | # Sample rate controller
748 | self.senseinterval = 0
749 |
750 | # Pygame drawing surface.
751 | self.surface = surface
752 |
753 | # Draws Background gridplane
754 | self.graphback = Image()
755 | self.graphback.update(backgraph, 0, 0)
756 |
757 |
758 | # Instantiates 3 labels for our readout
759 | self.a_label = Label()
760 | self.b_label = Label()
761 | self.c_label = Label()
762 | self.intervallabel = Label()
763 | self.intervallabelshadow = Label()
764 |
765 | self.focus_label = Label()
766 |
767 | self.slider1 = Image()
768 | self.slider2 = Image()
769 | self.slider3 = Image()
770 |
771 | self.data_a = graphlist()
772 | self.data_b = graphlist()
773 | self.data_c = graphlist()
774 |
775 | self.graphon1 = True
776 | self.graphon2 = True
777 | self.graphon3 = True
778 |
779 | self.visibility = [self.graphon1,self.graphon2,self.graphon3]
780 |
781 | self.margin = 16
782 |
783 |
784 |
785 |
786 | def frame(self):
787 | status = "mode_a"
788 |
789 | if configure.eventready[0]:
790 |
791 | # The following code handles inputs and button presses.
792 | keys = configure.eventlist[0]
793 |
794 | # if a key is registering as pressed.
795 | if keys[0]:
796 | self.selection += 1
797 | if self.selection > 3:
798 | self.selection = 0
799 |
800 | if keys[1]:
801 | status = "mode_b"
802 | configure.eventready[0] = False
803 | return status
804 |
805 | if keys[2]:
806 | configure.last_status[0] = "mode_a"
807 | status = "settings"
808 | configure.eventready[0] = False
809 | return status
810 |
811 | configure.eventready[0] = False
812 |
813 |
814 | # grabs sensor info from settings for quick reference and display
815 | sense_info_a = configure.sensor_info[configure.sensors[0][0]]
816 | sense_info_b = configure.sensor_info[configure.sensors[1][0]]
817 | sense_info_c = configure.sensor_info[configure.sensors[2][0]]
818 |
819 |
820 | #Sets a black screen ready for our UI elements
821 | self.surface.fill(black)
822 |
823 | #draws Background gridplane
824 | self.graphback.draw(self.surface)
825 |
826 | # resolves the key items (dsc and dev) for the targeted sensor, for plars to use.
827 | # creates a "senseslice"; an up to date data fragment for each configured sensor
828 |
829 | senseslice = []
830 | data_a = []
831 | data_b = []
832 | data_c = []
833 | datas = [data_a,data_b,data_c]
834 |
835 | #gathers the data for all three sensors currently selected for each slot.
836 | for i in range(3):
837 |
838 | # determines the sensor keys for each of the three main sensors
839 | this_index = int(configure.sensors[i][0])
840 |
841 | # grabs the sensor metadata for display
842 | dsc,dev,sym,maxi,mini = configure.sensor_info[this_index]
843 |
844 | # grabs sensor data
845 | datas[i] = plars.get_recent(dsc,dev,num = SAMPLE_SIZE)
846 |
847 |
848 | # if data capture has failed, replace with 47 for diagnostic
849 | if len(datas[i]) == 0:
850 | datas[i] = [47]
851 |
852 | item = datas[i]
853 |
854 | senseslice.append([item[-1], dsc, dev, sym, mini, maxi])
855 |
856 |
857 | #converts data to float
858 |
859 | a_newest = float(senseslice[0][0])
860 | b_newest = float(senseslice[1][0])
861 | c_newest = float(senseslice[2][0])
862 | newests = [a_newest,b_newest,c_newest]
863 |
864 |
865 |
866 |
867 |
868 | a_content = "{:.2f}".format(a_newest)
869 | a_color = themes[configure.theme[0]][0]
870 | self.a_label.update(a_content + senseslice[0][3],27,marginleft,205,titleFont,a_color)
871 |
872 |
873 | b_content = "{:.2f}".format(b_newest)
874 | b_color = themes[configure.theme[0]][1]
875 | self.b_label.update( b_content + senseslice[1][3],27,114,205,titleFont,b_color)
876 | self.b_label.center(resolution[0],27,0,205)
877 |
878 |
879 | c_content = "{:.2f}".format(c_newest)
880 | c_color = themes[configure.theme[0]][2]
881 |
882 | self.c_label.update(c_content + senseslice[2][3],27,marginright,205,titleFont,c_color)
883 | self.c_label.r_align(320 - marginright ,205)
884 | contents = [a_content,b_content,c_content]
885 |
886 | labels = [self.a_label,self.b_label,self.c_label]
887 |
888 |
889 |
890 | interx= (22)
891 | intery= (21)
892 |
893 |
894 | # updates the data storage object and retrieves a fresh graph ready to store the positions of each segment for the line drawing
895 | a_cords = graphit(datas[0])
896 | b_cords = graphit(datas[1])
897 | c_cords = graphit(datas[2])
898 | cords = [a_cords,b_cords,c_cords]
899 |
900 | if not configure.auto[0]:
901 |
902 | a_slide = translate(a_newest, senseslice[0][4], senseslice[0][5], GRAPH_Y2, GRAPH_Y)
903 |
904 | b_slide = translate(b_newest, senseslice[1][2], senseslice[1][5], GRAPH_Y2, GRAPH_Y)
905 |
906 | c_slide = translate(c_newest, senseslice[2][2], senseslice[2][5], GRAPH_Y2, GRAPH_Y)
907 |
908 | self.slider1.update(sliderb, 283, a_slide)
909 | self.slider2.update(sliderb, 283, b_slide)
910 | self.slider3.update(sliderb, 283, c_slide)
911 | else:
912 | self.slider1.update(sliderb, 283, a_cords[-1][1]-10)
913 | self.slider2.update(sliderb, 283, b_cords[-1][1]-10)
914 | self.slider3.update(sliderb, 283, c_cords[-1][1]-10)
915 |
916 | sliders = [self.slider1,self.slider2,self.slider3]
917 |
918 | if self.selection == 0:
919 |
920 | #draw the lines
921 | pygame.draw.lines(self.surface, c_color, False, c_cords, 2)
922 | self.slider3.draw(self.surface)
923 |
924 | pygame.draw.lines(self.surface, b_color, False, b_cords, 2)
925 | self.slider2.draw(self.surface)
926 |
927 | pygame.draw.lines(self.surface, a_color, False, a_cords, 2)
928 | self.slider1.draw(self.surface)
929 |
930 |
931 | # draws the labels
932 | self.a_label.draw(self.surface)
933 | self.b_label.draw(self.surface)
934 | self.c_label.draw(self.surface)
935 |
936 |
937 |
938 | # this checks if we are viewing a sensor individually and graphing it alone.
939 | # if individually:
940 | if self.selection != 0:
941 | # we make a variable carrying the index of the currently selected item.
942 | this = self.selection - 1
943 |
944 | # we grab information for it.
945 | sym, dsc = senseslice[this][3],senseslice[this][1]
946 |
947 | # we collect its default colour based off our theme
948 | this_color = themes[configure.theme[0]][this]
949 |
950 | focus_cords = cords[this]
951 | focus_slider = sliders[this]
952 | pygame.draw.lines(self.surface, this_color, False, focus_cords, 2)
953 | focus_slider.draw(self.surface)
954 |
955 | self.focus_label.update(dsc,30,283,205,titleFont,this_color)
956 | self.focus_label.r_align(320 - marginright ,205)
957 | self.focus_label.draw(self.surface)
958 |
959 | self.a_label.update(contents[this] + sym,30,15,205,titleFont,this_color)
960 | self.a_label.draw(self.surface)
961 |
962 | # draws the interval label (indicates refresh rate)
963 | #self.intervallabelshadow.draw(self.surface)
964 | #self.intervallabel.draw(self.surface)
965 |
966 | #draws UI to frame buffer
967 | pygame.display.update()
968 |
969 |
970 | return status
971 |
972 | def visible(self,item,option):
973 | self.visibility[item] = option
974 |
975 | #experimental video screen written by scifi.radio from the mycorder discord
976 | class EdithKeeler_Screen(object):
977 | def __init__(self,surface):
978 | self.status = "mode_c"
979 | self.surface = surface
980 | self.videobg = Image()
981 | self.videobg.update(videobg, 0,0)
982 | self.running = False
983 | self.paused = False
984 | self.clock = pygame.time.Clock()
985 |
986 | def frame(self):
987 | self.status = "mode_c"
988 | if not self.running:
989 | self.running = True
990 | self.clip = Video('assets/ekmd.mov')
991 | self.clip.set_size(resolution)
992 | self.clip.restart()
993 |
994 |
995 | self.clock.tick(60)
996 |
997 | if configure.eventready[0]:
998 |
999 | # The following code handles inputs and button presses.
1000 | keys = configure.eventlist[0]
1001 |
1002 | # if a key is registering as pressed.
1003 | if keys[0]:
1004 | print("Button 1")
1005 | self.status = "mode_b"
1006 | configure.eventready[0] = False
1007 | self.running = False
1008 | self.clip.close()
1009 | return self.status
1010 |
1011 | if keys[1]:
1012 | self.status = "mode_c"
1013 | self.clip.toggle_pause()
1014 | if self.paused:
1015 | self.paused = False
1016 | print("Resume")
1017 | else:
1018 | self.paused = True
1019 | print("Paused")
1020 | configure.eventready[0] = False
1021 | return self.status
1022 |
1023 |
1024 | if keys[2]:
1025 | configure.last_status[0] = "mode_c"
1026 | print("Button 3")
1027 | self.status = "settings"
1028 | self.running = False
1029 | configure.eventready[0] = False
1030 |
1031 | return self.status
1032 |
1033 | configure.eventready[0] = False
1034 |
1035 | #draws Background gridplane
1036 | self.videobg.draw(self.surface)
1037 | self.clip.draw(self.surface,(0,0), force_draw=FALSE)
1038 | #draws UI to frame buffer
1039 | pygame.display.update()
1040 | if not self.clip.active:
1041 | self.clip.restart()
1042 |
1043 | return self.status
1044 |
1045 |
1046 | class Slider_Screen(object):
1047 | def __init__(self, surface):
1048 | # This function draws the main 3-slider interface, modelled after McCoy's tricorder in "Plato's Stepchildren". It displays temperature, humidity and pressure.
1049 | self.surface = surface
1050 |
1051 | #checks time
1052 | self.timenow = time.time()
1053 |
1054 | #compares time just taken with time of start to animate the apperance of text
1055 |
1056 | # Sets a black screen ready for our UI elements
1057 | self.surface.fill(black)
1058 |
1059 | # Instantiates the components of the scene
1060 | self.a_label = Label()
1061 | self.b_label = Label()
1062 | self.c_label = Label()
1063 | self.backPlane = Image()
1064 |
1065 | self.slider1 = Image()
1066 | self.slider2 = Image()
1067 | self.slider3 = Image()
1068 | self.status = "mode_b"
1069 | self.input = input
1070 |
1071 |
1072 |
1073 | def frame(self):
1074 | #set status for return to main
1075 | status = "mode_b"
1076 |
1077 |
1078 | if configure.eventready[0]:
1079 |
1080 | # The following code handles inputs and button presses.
1081 | keys = configure.eventlist[0]
1082 |
1083 | # if a key is registering as pressed.
1084 | if keys[0]:
1085 | status = "mode_a"
1086 | return status
1087 |
1088 | if keys[1]:
1089 | status = "mode_c"
1090 | configure.eventready[0] = False
1091 | return status
1092 |
1093 | if keys[2]:
1094 | configure.last_status[0] = "mode_b"
1095 | status = "settings"
1096 | configure.eventready[0] = False
1097 | return status
1098 |
1099 | configure.eventready[0] = False
1100 |
1101 | senseslice = []
1102 |
1103 | for i in range(3):
1104 |
1105 | # determines the sensor keys for each of the three main sensors
1106 | this_index = int(configure.sensors[i][0])
1107 |
1108 | dsc,dev,sym,maxi,mini = configure.sensor_info[this_index]
1109 |
1110 |
1111 | item = plars.get_recent(dsc,dev,num = 1)
1112 |
1113 | if len(item) > 0:
1114 | senseslice.append([item[0], sym, mini, maxi])
1115 | else:
1116 | senseslice.append([47, sym, mini, maxi])
1117 |
1118 | #converts data to float
1119 | a_newest = float(senseslice[0][0])
1120 | b_newest = float(senseslice[1][0])
1121 | c_newest = float(senseslice[2][0])
1122 | newests = [a_newest,b_newest,c_newest]
1123 |
1124 |
1125 | # data labels
1126 | a_content = str(int(a_newest))
1127 | self.a_label.update(a_content + senseslice[0][1],19,47,215,titleFont,yellow)
1128 | b_content = str(int(b_newest))
1129 | self.b_label.update(b_content + senseslice[1][1],19,152,215,titleFont,yellow)
1130 | c_content = str(int(c_newest))
1131 | self.c_label.update(c_content + senseslice[2][1],19,254,215,titleFont,yellow)
1132 |
1133 | # slider data adjustment
1134 | # the routine takes the raw sensor data and converts it to screen coordinates to move the sliders
1135 | # determines the sensor keys for each of the three main sensors
1136 |
1137 |
1138 |
1139 | a_slide = translate(senseslice[0][0], senseslice[0][2], senseslice[0][3], 204, 15)
1140 | b_slide = translate(senseslice[1][0], senseslice[1][2], senseslice[1][3], 204, 15)
1141 | c_slide = translate(senseslice[2][0], senseslice[2][2], senseslice[2][3], 204, 15)
1142 |
1143 | # Updates our UI objects with data parsed from sensor/weather
1144 | self.backPlane.update(backplane, 0, 0)
1145 | self.slider1.update(slidera, 70, a_slide)
1146 | self.slider2.update(slidera, 172, b_slide)
1147 | self.slider3.update(slidera, 276, c_slide)
1148 |
1149 | # draws the graphic UI to the buffer
1150 | self.backPlane.draw(self.surface)
1151 | self.slider1.draw(self.surface)
1152 | self.slider2.draw(self.surface)
1153 | self.slider3.draw(self.surface)
1154 |
1155 | # draws the labels to the buffer
1156 | self.a_label.draw(self.surface)
1157 | self.b_label.draw(self.surface)
1158 | self.c_label.draw(self.surface)
1159 |
1160 | pygame.display.update()
1161 | # draws UI to frame buffer
1162 | return status
1163 | # A basic screen object. Is given parameters and displays them on a number of preset panels
1164 | class Screen(object):
1165 |
1166 | def __init__(self):
1167 | screenSize = resolution
1168 |
1169 | # The following commands initiate a pygame environment.
1170 | pygame.init()
1171 | pygame.font.init()
1172 | pygame.display.set_caption('PicorderOS')
1173 |
1174 | # I forget, probably colour depth?
1175 | smodes = pygame.display.list_modes(def_modes)
1176 |
1177 | # instantiate a pygame display with the name "surface",
1178 |
1179 | if configure.pc:
1180 | #for development use this one (windowed mode)
1181 | self.surface = pygame.display.set_mode(screenSize)
1182 | else:
1183 | # on the picorder use this option (Fullscreen).
1184 | flags = pygame.FULLSCREEN | pygame.SCALED
1185 | self.surface = pygame.display.set_mode(screenSize, flags, display=0)
1186 | pygame.event.set_blocked(pygame.MOUSEMOTION)
1187 | pygame.mouse.set_visible(False)
1188 |
1189 |
1190 | self.timed = time.time()
1191 | self.graphscreen = Graph_Screen(self.surface)
1192 | self.edithkeelerscreen = EdithKeeler_Screen(self.surface)
1193 | self.slidescreen = Slider_Screen(self.surface)
1194 | self.settings_screen = Settings_Panel(self.surface)
1195 |
1196 | def get_size(self):
1197 | return SAMPLE_SIZE
1198 |
1199 | def startup_screen(self,start_time):
1200 | status = startUp(self.surface,start_time)
1201 | return status
1202 |
1203 | def about_screen(self):
1204 | status = about()
1205 | return status
1206 |
1207 | def slider_screen(self):
1208 | status = self.slidescreen.frame()
1209 | return status
1210 |
1211 | def graph_screen(self):
1212 | status = self.graphscreen.frame()
1213 | return status
1214 |
1215 | def edithkeeler_screen(self):
1216 | status = self.edithkeelerscreen.frame()
1217 | return status
1218 |
1219 | def settings(self):
1220 | status = self.settings_screen.frame()
1221 | return status
1222 |
--------------------------------------------------------------------------------