├── .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 | --------------------------------------------------------------------------------