diff --git a/README.md b/README.md index 421756c..d743fe9 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,8 @@ sudo apt-get update sudo apt-get upgrade +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 + sudo apt-get install libatlas-base-dev libsdl2-dev libopenjp2-7-dev libtiff5 python3-pandas python3-psutil pip3 install --upgrade colour luma.lcd luma.emulator adafruit-circuitpython-bme680 sense-hat adafruit-circuitpython-mpr121 pygame==2.0.0 diff --git a/assets/lcarsburgerframefull.png b/assets/lcarsburgerframefull.png index 7091c85..34dbfbc 100644 Binary files a/assets/lcarsburgerframefull.png and b/assets/lcarsburgerframefull.png differ diff --git a/assets/videobg.png b/assets/videobg.png new file mode 100644 index 0000000..3ba234a Binary files /dev/null and b/assets/videobg.png differ diff --git a/display.py b/display.py index 77661fc..55a8848 100644 --- a/display.py +++ b/display.py @@ -1,70 +1,63 @@ - -# -*- coding: utf-8 -*- -# Originally found: https://github.com/rm-hull/luma.examples/blob/master/examples/demo_opts.py -# Modified for PicorderOS -# Copyright (c) 2014-18 Richard Hull and contributors -# See LICENSE.rst for details. - print("Unified Display Module loading") import sys import logging - -from luma.core import cmdline, error - - -# logging -logging.basicConfig( - level=logging.DEBUG, - format='%(asctime)-15s - %(message)s' -) -# ignore PIL debug messages -logging.getLogger('PIL').setLevel(logging.ERROR) - - -def display_settings(device, args): - """ - Display a short summary of the settings. - :rtype: str - """ - iface = '' - display_types = cmdline.get_display_types() - if args.display not in display_types['emulator']: - iface = 'Interface: {}\n'.format(args.interface) - - lib_name = cmdline.get_library_for_display_type(args.display) - if lib_name is not None: - lib_version = cmdline.get_library_version(lib_name) - else: - lib_name = lib_version = 'unknown' - - import luma.core - version = 'luma.{} {} (luma.core {})'.format( - lib_name, lib_version, luma.core.__version__) - - return 'Version: {}\nDisplay: {}\n{}Dimensions: {} x {}\n{}'.format( - version, args.display, iface, device.width, device.height, '-' * 60) - - -def get_device(actual_args=None): - """ - Create device from command-line arguments and return it. - """ - if actual_args is None: - actual_args = sys.argv[1:] - parser = cmdline.create_parser(description='luma.examples arguments') - args = parser.parse_args(actual_args) - - if args.config: - # load config from file - config = cmdline.load_config(args.config) - args = parser.parse_args(config + actual_args) - - # create device - try: - device = cmdline.create_device(args) - print(display_settings(device, args)) - return device - - except error.Error as e: - parser.error(e) - return None +from objects import * + +if configure.display == 1: + from luma.core.interface.serial import spi + from luma.core.render import canvas + from luma.lcd.device import st7735 + from luma.emulator.device import pygame + + # Raspberry Pi hardware SPI config: + DC = 23 + RST = 24 + SPI_PORT = 0 + SPI_DEVICE = 0 + + if not configure.pc: + serial = spi(port = SPI_PORT, device = SPI_DEVICE, gpio_DC = DC, gpio_RST = RST) + device = st7735(serial, width = 160, height = 128, mode = "RGB") + else: + device = pygame(width = 160, height = 128) + +# for TFT24T screens +elif configure.display == 2: + # Details pulled from https://github.com/BehindTheSciences/ili9341_SPI_TouchScreen_LCD_Raspberry-Pi/blob/master/BTS-ili9341-touch-calibration.py + from lib_tft24T import TFT24T + import RPi.GPIO as GPIO + GPIO.setmode(GPIO.BCM) + GPIO.setwarnings(False) + import spidev + DC = 24 + RST = 25 + LED = 15 + PEN = 26 + device = TFT24T(spidev.SpiDev(), GPIO) + # Initialize display and touch. + TFT.initLCD(DC, RST, LED) + +class GenericDisplay(object): + + def __init__(self): + + # lib_tft24 screens require us to create a drawing surface for the screen + # and add to it. + if configure.display == 2: + self.surface = device.draw() + + + # Display takes a PILlow based drawobject and pushes it to screen. + def display(self,frame): + + # the following is only for screens that use Luma.LCD + if configure.display == 1: + device.display(frame) + + # the following is only for TFT24T screens + elif configure.display == 2: + # Resize the image and rotate it so it's 240x320 pixels. + frame = frame.rotate(90,0,1).resize((240, 320)) + # Draw the image on the display hardware. + self.surface.pasteimage(frame,(0,0)) + device.display() diff --git a/input.py b/input.py index ffb27ca..f4d90f7 100644 --- a/input.py +++ b/input.py @@ -119,10 +119,11 @@ if configure.input_pcf8575: from pcf8575 import PCF8575 - i2c_port_num = 0 + i2c_port_num = 1 pcf_address = 0x20 pcf = PCF8575(i2c_port_num, pcf_address) + button_table = [-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,1,0,3,2,4] # the input class receives and relays control events for user interaction class Inputs(object): @@ -190,7 +191,7 @@ def read(self): # if the alert pin is brought LOW if GPIO.input(configure.ALERTPIN) == 0 and configure.eventready[0] == False: - print("touch received") + #print("touch received") # collect the event list from the chip reading = cap1208.get_input_status() @@ -401,8 +402,10 @@ def read(self): if configure.input_pcf8575: if not configure.eventready[0]: - print("button state = ", pcf.port) - for this, button in enumerate(pcf.port): + this_frame = list(pcf.port) + + for this, button in enumerate(this_frame): + # if an item is pressed if not button: @@ -410,15 +413,18 @@ def read(self): if not self.pressed[this]: # mark it in the pressed list - print("pad press registered") - self.pressed[this] = True + print("pad press registered at ", this) + print("raising an event at address ", button_table[this]) + + self.pressed[button_table[this]] = True configure.eventready[0] = True configure.beep_ready[0] = True else: - self.pressed[this] = False + self.pressed[button_table[this]] = False configure.eventlist[0] = self.pressed + def keypress(self): pygame.event.get() #pygame.time.wait(50) diff --git a/lcars_clr.py b/lcars_clr.py index fb6485c..da99cc8 100644 --- a/lcars_clr.py +++ b/lcars_clr.py @@ -1,18 +1,18 @@ #!/usr/bin/env python # This module controls the st7735 type screens print("Loading 160x128 LCARS Interface") +from objects import * import math import time from operator import itemgetter -# remove this part and replace with display -from luma.core.interface.serial import spi -from luma.core.render import canvas -from luma.lcd.device import st7735 -from luma.emulator.device import pygame +from display import GenericDisplay + +device = GenericDisplay() # Load up the image library stuff to help draw bitmaps to push to the screen +import numpy import PIL.ImageOps from PIL import Image from PIL import ImageFont @@ -22,7 +22,7 @@ from pilgraph import * from amg8833_pil import * from plars import * -from objects import * + from modulated_em import * @@ -36,20 +36,10 @@ # Load assets logo = Image.open('assets/picorderOS_logo.png') -# Raspberry Pi hardware SPI config: -DC = 23 -RST = 24 -SPI_PORT = 0 -SPI_DEVICE = 0 -TRANSITION = [False] -if not configure.pc: - serial = spi(port = SPI_PORT, device = SPI_DEVICE, gpio_DC = DC, gpio_RST = RST)# ,bus_speed_hz=24000000) - device = st7735(serial, width = 160, height = 128, mode = "RGB") -else: - device = pygame(width = 160, height = 128) + # Standard LCARS colours @@ -525,13 +515,18 @@ def __init__(self): self.title = LabelObj("Modulated EM Scan",titlefont, colour = lcars_orange) self.signal_name = LabelObj("SSID",bigfont, colour = lcars_peach) + self.signal_name_sm = LabelObj("SSID",font, colour = lcars_peach) + self.signal_strength = LabelObj("ST",giantfont, colour = lcars_peach) + self.signal_strength_sm = LabelObj("ST",littlefont, colour = lcars_peach) + self.signal_frequency = LabelObj("FQ",titlefont, colour = lcars_orpeach) + self.signal_frequency_sm = LabelObj("FQ",littlefont, colour = lcars_peach) self.signal_mac = LabelObj("MAC",font, colour = lcars_orpeach) self.list = Label_List(22,35, colour = lcars_peach) - + self.burgerfull = Image.open('assets/lcarsburgerframefull.png') def push(self, draw): @@ -551,7 +546,7 @@ def push(self, draw): if keys[1]: self.selection += 1 - if self.selection >= 2: + if self.selection >= 3: self.selection = 0 pass @@ -565,6 +560,7 @@ def push(self, draw): self.wifi.update_plars() + # details on strongest wifi network. if self.selection == 0: # grab EM data from plars @@ -574,12 +570,15 @@ def push(self, draw): self.Signal_Graph.render(draw) self.title.string = "Dominant Transciever" self.title.r_align(self.labelxr,self.titley,draw) + self.signal_name.push(20,35,draw, string = info[0]) + self.signal_strength.string = str(info[1]) + " DB" self.signal_strength.r_align(self.labelxr,92,draw) self.signal_frequency.push(20,92,draw, string = info[3]) self.signal_mac.push(20,111, draw, string = info[6]) + #list of all wifi ssids if self.selection == 1: # list to hold the data labels @@ -588,6 +587,7 @@ def push(self, draw): # grab EM list em_list = plars.get_recent_em_list() + #sort it so strongest is first sorted_em_list = sorted(em_list, key=itemgetter(1), reverse = True) # prepare a list of the data received for display @@ -604,8 +604,94 @@ def push(self, draw): # assign each list element and its + # frequency intensity map + if self.selection == 2: + # returns the data necessary for freq_intensity map with EM. + # displays each SSID as a line segment. Its position along the x is + # determined by frequency. Its height by its signal strength. + + # change Background + + #draw.rectangle((0,0,320,240),(0,0,0)) + draw._image = self.burgerfull + #draw.bitmap((0,0), ) + + #draw round rect background + draw.rectangle((18,49,158,126), outline = lcars_blue) + + #draw labels + self.title.string = "EM Channel Analysis" + self.title.r_align(self.labelxr,self.titley,draw) + + #grab EM list + unsorted_em_list = plars.get_recent_em_list() + + # sort it so strongest is first. + em_list = sorted(unsorted_em_list, key=itemgetter(1), reverse = True) + + # create a list to hold just the info we need for the screen. + items_list = [] + + #filter info into items_list + for ssid in em_list: + name = str(ssid[0]) + strength = ssid[1] + frequency = ssid[3] + frequency = float(frequency.replace(' GHz', '')) + + # determing x coordinate + screenpos = numpy.interp(frequency,(2.412, 2.462),(25, 151)) + + # determine y coordinate + lineheight = numpy.interp(strength, (-100, 0), (126, 55)) + + # package into list + this_ssid = (name,screenpos,lineheight,strength,frequency) + items_list.append(this_ssid) + + + #for each item in item_list + for index, item in enumerate(items_list): + + # determine dot coordinates. + cords = ((item[1],126),(item[1],item[2])) + x1 = cords[1][0] - (6/2) + y1 = cords[1][1] - (6/2) + x2 = cords[1][0] + (6/2) + y2 = cords[1][1] + (6/2) + + # if this is the strongest singal draw labels and change colour. + if index == 0: + draw.line(cords,lcars_peach,1) + draw.ellipse([x1,y1,x2,y2],lcars_peach) + + + name = item[0] + trunc_name = name[:16] + (name[16:] and '..') + # draw the strongest signals name, top center + self.signal_name_sm.push(19,34,draw,string = trunc_name) + + # put strength at lower left + strength_string = str(item[3]) + " DB" + #self.signal_strength_sm.push(19,114,draw,string = strength_string) + + # put frequency at lower right + self.signal_frequency_sm.string = str(item[4]) + " GHZ" + ", " + strength_string + self.signal_frequency_sm.r_align(157,37,draw) + + # otherwise just draw the line and dot in the usual color + else: + draw.line(cords,lcars_bluer,1) + draw.ellipse([x1,y1,x2,y2],lcars_bluer) + + + + return status + + + # Controls the LCARS frame, measures the label and makes sure the top frame bar has the right spacing. class MultiFrame(object): @@ -963,8 +1049,11 @@ class ColourScreen(object): def __init__(self): + if configure.display == 2: + self.surface = TFT.draw() + # instantiates an image and uses it in a draw object. - self.image = Image.open('assets/lcarsframe.png')#.convert('1') + self.image = Image.open('assets/lcarsframe.png') self.blankimage = Image.open('assets/lcarsframeblank.png') self.tbar = Image.open('assets/lcarssplitframe.png') self.burger = Image.open('assets/lcarsburgerframe.png') diff --git a/lcars_clr_ili9341.py b/lcars_clr_ili9341.py deleted file mode 100644 index 9e03959..0000000 --- a/lcars_clr_ili9341.py +++ /dev/null @@ -1,796 +0,0 @@ -#!/usr/bin/env python -# This module controls the st7735 type screens -print("Loading Secondary Screen Module ili9341") -import math -import time - -# Load up the image library stuff to help draw bitmaps to push to the screen -import PIL.ImageOps -from PIL import Image -from PIL import ImageFont -from PIL import ImageDraw - -# load the module that draws graphs -from pilgraph import * -from amg8833_pil import * -from plars import * -from objects import * - - -# Details pulled from https://github.com/BehindTheSciences/ili9341_SPI_TouchScreen_LCD_Raspberry-Pi/blob/master/BTS-ili9341-touch-calibration.py -from lib_tft24T import TFT24T -import RPi.GPIO as GPIO -GPIO.setmode(GPIO.BCM) -GPIO.setwarnings(False) - -import spidev -from time import sleep - -DC = 24 -RST = 25 -LED = 15 -PEN = 26 - -TFT = TFT24T(spidev.SpiDev(), GPIO) - -# Initialize display and touch. -TFT.initLCD(DC, RST, LED) - - - - - -# Load default font. -font = ImageFont.truetype("assets/babs.otf",13) -titlefont = ImageFont.truetype("assets/babs.otf",16) -bigfont = ImageFont.truetype("assets/babs.otf",20) - -# Raspberry Pi hardware SPI config: -DC = 23 -RST = 24 -SPI_PORT = 0 -SPI_DEVICE = 0 - -TRANSITION = [False] - - - -# The following are for LCARS colours from LCARScom.net -lcars_orange = (255,153,0) -lcars_pink = (204,153,204) -lcars_blue = (153,153,204) -lcars_red = (204,102,102) -lcars_peach = (255,204,153) -lcars_bluer = (153,153,255) -lcars_orpeach = (255,153,102) -lcars_pinker = (204,102,153) - -theme1 = [lcars_orange,lcars_blue,lcars_pinker] - -fore_col = 0 -back_col = 1 - -# Controls text objects drawn to the LCD -class LabelObj(object): - def __init__(self,string,font, colour = lcars_blue): - self.font = font - #self.draw = draw - self.string = string - self.colour = colour - - def center(self,y,x,w,draw): - size = self.font.getsize(self.string) - xmid = x + w/2 - #ymid = y + h/2 - textposx = xmid - (size[0]/2) - #textposy = ymid - (size[1]/2) + self.scaler - self.push(textposx,y,draw) - - def r_align(self,x,y,draw): - size = self.font.getsize(self.string) - self.push(x-size[0],y,draw) - - # Draws the label onto the provided draw buffer. - def push(self,locx,locy,draw): - self.draw = draw - self.draw.text((locx, locy), self.string, font = self.font, fill= self.colour) - - def getsize(self): - size = self.draw.textsize(self.string, font=self.font) - return size - -class SelectableLabel(LabelObj): - - - def __init__(self,font,draw,oper,colour = lcars_blue, special = 0): - self.font = font - self.draw = draw - self.colour = colour - - # special determines the behaviour of the label for each type of oper - # the class is supplied. There may be multiple types of int or boolean based - # configuration parameters so this variable helps make new options - self.special = special - - # coordinates - self.x = 0 - self.y = 0 - - # basic graphical parameters - self.fontSize = 33 - - # self.myfont = pygame.font.Font(titleFont, self.fontSize) - # text = "Basic Item" - # self.size = self.myfont.size(text) - - self.scaler = 3 - self.selected = False - self.indicator = Image() - self.content = "default" - - # this variable is a reference to a list stored in "objects.py" - # containing either a boolean or an integer - self.oper = oper - - def update(self, content, fontSize, nx, ny, fontType, color): - self.x = nx - self.y = ny - self.content = content - self.fontSize = fontSize - self.myfont = pygame.font.Font(fontType, self.fontSize) - self.color = color - self.indicator.update(sliderb, nx - 23, ny+1) - - def toggle(self): - - # if the parameter supplied is a boolean - if isinstance(self.oper[0], bool): - #toggle its state - self.oper[0] = not self.oper[0] - - #if the parameter supplied is an integer - elif isinstance(self.oper[0], int): - - # increment the integer. - self.oper[0] += 1 - - # if the integer is larger than the pool - if self.special == 1 and self.oper[0] > configure.max_sensors[0]-1: - self.oper[0] = 0 - - if self.special == 2 and self.oper[0] > (len(themes) - 1): - self.oper[0] = 0 - - return self.oper[0] - - def draw(self, surface): - if self.selected: - self.indicator.draw(surface) - - label = self.myfont.render(self.content, 1, self.color) - - - status_text = "dummy" - if self.special == 0: - status_text = str(self.oper[0]) - elif self.special == 1: - status_text = configure.sensor_info[self.oper[0]][3] - elif self.special == 2: - status_text = themenames[self.oper[0]] - - pos = resolution[0] - (self.get_size(status_text) + 37) - state = self.myfont.render(status_text, 1, self.color) - - - surface.blit(label, (self.x, self.y)) - surface.blit(state, (pos, self.y)) - - -class SettingsFrame(object): - def __init__(self): - - self.pages = [["Sensor 1",configure.sensor1], ["Sensor 2", configure.sensor2], ["Sensor 3",configure.sensor3], ["Auto Range",configure.auto], ["LEDs", configure.sleep],["Power Off","poweroff"]] - - # Sets the topleft origin of the graph - self.graphx = 23 - self.graphy = 24 - - self.status_raised = False - - # Sets the x and y span of the graph - self.gspanx = 133 - self.gspany = 71 - - self.selection = 0 - - self.auto = configure.auto[0] - self.interval = timer() - self.interval.logtime() - #self.draw = draw - self.titlex = 25 - self.titley = 6 - self.labely = 102 - - self.graphcycle = 0 - self.decimal = 1 - - self.divider = 47 - self.labely = 102 - - - self.title = LabelObj("Settings",titlefont) - self.itemlabel = LabelObj("Item Label",titlefont,colour = lcars_peach) - self.A_Label = LabelObj("Next",font,colour = lcars_blue) - self.B_Label = LabelObj("Enter",font, colour = lcars_blue) - self.C_Label = LabelObj("Exit",font, colour = lcars_blue) - - self.item = LabelObj("No Data",bigfont,colour = lcars_pink) - - - # device needs to show multiple settings - # first the sensor palette configuration - - def toggle(self,oper): - - # if the parameter supplied is a boolean - if isinstance(oper[0], bool): - #toggle its state - oper[0] = not oper[0] - - #if the parameter supplied is an integer - elif isinstance(oper[0], int): - - # increment the integer. - oper[0] += 1 - - # if the integer is larger than the pool - if oper[0] > configure.max_sensors[0]-1: - oper[0] = 0 - - elif isinstance(oper, str): - #configure.last_status[0] = configure.status[0] - self.status_raised = True - configure.status[0] = oper - - - return oper - - - def push(self, draw): - - #draw the frame heading - self.title.push(self.titlex,self.titley,draw) - - - #draw the option item heading - self.itemlabel.string = str(self.pages[self.selection][0]) - self.itemlabel.push(self.titlex,self.titley+20,draw) - - - - self.A_Label.push(23,self.labely,draw) - self.B_Label.center(self.labely,23,135,draw) - self.C_Label.r_align(156,self.labely,draw) - - - #draw the 3 graph parameter items - if self.selection == 0 or self.selection == 1 or self.selection == 2: - - self.item.string = str(configure.sensor_info[self.pages[self.selection][1][0]][3]) - self.item.push(self.titlex,self.titley+40,draw) - else: - - if isinstance(self.pages[self.selection][1][0], bool): - self.item.string = str(self.pages[self.selection][1][0]) - self.item.push(self.titlex,self.titley+40,draw) - - - status = "settings" - - if configure.eventready[0]: - keys = configure.eventlist[0] - - if keys[0]: - self.selection = self.selection + 1 - if self.selection > (len(self.pages) - 1): - self.selection = 0 - - if keys[1]: - state = self.toggle(self.pages[self.selection][1]) - - if self.status_raised: - status = state - self.status_raised = False - - if keys[2]: - status = configure.last_status[0] - - configure.eventready[0] = False - - return status - -class PowerDown(object): - def __init__(self): - - # Sets the topleft origin of the graph - self.graphx = 23 - self.graphy = 24 - - self.status_raised = False - - # Sets the x and y span of the graph - self.gspanx = 133 - self.gspany = 71 - - self.selection = 0 - - - self.auto = configure.auto[0] - self.interval = timer() - self.interval.logtime() - #self.draw = draw - self.titlex = 25 - self.titley = 6 - self.labely = 102 - - self.graphcycle = 0 - self.decimal = 1 - - self.divider = 47 - self.labely = 102 - - - self.title = LabelObj("CAUTION",bigfont, colour = lcars_red) - self.itemlabel = LabelObj("Item Label",titlefont,colour = lcars_orange) - self.A_Label = LabelObj("Yes",font,colour = lcars_blue) - self.B_Label = LabelObj("Enter",font, colour = lcars_blue) - self.C_Label = LabelObj("No",font, colour = lcars_blue) - - self.item = LabelObj("No Data",bigfont,colour = lcars_orpeach) - # device needs to show multiple settings - # first the sensor palette configuration - - - def push(self, draw): - - #draw the frame heading - - self.title.center(self.titley,self.titlex,135,draw) - - - #draw the option item heading - #self.itemlabel.string = str(self.pages[self.selection][0]) - # self.itemlabel.push(self.titlex,self.titley+20,draw) - - - - self.A_Label.push(23,self.labely,draw) - - - #self.B_Label.center(self.labely,23,135,draw) - - - self.C_Label.r_align(156,self.labely,draw) - - - #draw the 3 graph parameter items - - self.item.string = "Power Down?" - self.item.center(self.titley+40, self.titlex, 135,draw) - - - status = "poweroff" - - - if configure.eventready[0]: - - keys = configure.eventlist[0] - - if keys[0]: - status = "shutdown" - - if keys[1]: - pass - - if keys[2]: - status = "settings" - - configure.eventready[0] = False - - - return status - - - -# Controls the LCARS frame, measures the label and makes sure the top frame bar has the right spacing. -class MultiFrame(object): - - def __init__(self): - - - - # Sets the topleft origin of the graph - self.graphx = 23 - self.graphy = 24 - - # Sets the x and y span of the graph - self.gspanx = 133 - self.gspany = 71 - - - self.marginleft = 23 - self.marginright= 133 - - # sets the background image for the display - self.back = Image.open('assets/lcarsframe.png') - - # sets the currently selected sensor to focus on - self.selection = 0 - - # ties the auto state to the global object - self.auto = configure.auto[0] - - # creates and interval timer for screen refresh. - self.interval = timer() - self.interval.logtime() - - # Sets the coordinates of onscreen labels. - self.titlex = 23 - self.titley = 6 - self.labely = 102 - - self.graphcycle = 0 - - self.decimal = 1 - - self.divider = 47 - - # create our graph_screen - self.A_Graph = graph_area(0,(self.graphx,self.graphy),(self.gspanx,self.gspany),self.graphcycle, theme1[0], width = 1) - - self.B_Graph = graph_area(1,(self.graphx,self.graphy),(self.gspanx,self.gspany),self.graphcycle, theme1[1], width = 1) - - self.C_Graph = graph_area(2,(self.graphx,self.graphy),(self.gspanx,self.gspany),self.graphcycle, theme1[2], width = 1) - - self.Graphs = [self.A_Graph, self.B_Graph, self.C_Graph] - - self.A_Label = LabelObj("a_string",font,colour = lcars_orange) - - self.B_Label = LabelObj("b_string",font, colour = lcars_blue) - - self.C_Label = LabelObj("c_string",font, colour = lcars_pinker) - - self.focus_Label = LabelObj("test",bigfont, colour = lcars_orpeach) - self.focus_high_Label = LabelObj("test",font, colour = lcars_peach) - self.focus_low_Label = LabelObj("test",font, colour = lcars_bluer) - self.focus_mean_Label = LabelObj("test",font, colour = lcars_pinker) - - self.title = LabelObj("Multi-Graph",titlefont, colour = lcars_peach) - - def get_x(self): - return self.gspanx - self.graphx - - # takes a value and sheds the second digit after the decimal place - def arrangelabel(self,data,range = ".0f"): - datareturn = format(float(data), range) - return datareturn - - # defines the labels for the screen - def labels(self): - - # depending on which number the "selection" variable takes on. - if self.selection == 0: - raw_a = str(self.A_Data) - adjusted_a = self.arrangelabel(raw_a) - a_string = adjusted_a + " " + configure.sensor_info[configure.sensor1[0]][4] - - raw_b = str(self.B_Data) - adjusted_b = self.arrangelabel(raw_b) - b_string = adjusted_b + " " + configure.sensor_info[configure.sensor2[0]][4] - - raw_c = str(self.C_Data) - adjusted_c = self.arrangelabel(raw_c) - c_string = adjusted_c + " " + configure.sensor_info[configure.sensor3[0]][4] - - self.A_Label.string = a_string - self.A_Label.push(23,self.labely,self.draw) - - self.B_Label.string = b_string - self.B_Label.center(self.labely,23,135,self.draw) - - self.C_Label.string = c_string - self.C_Label.r_align(156,self.labely,self.draw) - - # displays more details for whatever sensor is in focus - if self.selection != 0: - - carousel = [self.A_Data,self.B_Data,self.C_Data] - - this = self.selection - 1 - - this_bundle = self.Graphs[this] - - raw = str(carousel[this]) - - adjusted = self.arrangelabel(raw, '.2f') - self.focus_Label.string = adjusted - self.focus_Label.r_align(156,self.titley,self.draw) - - self.focus_high_Label.string = "max " + self.arrangelabel(str(this_bundle.get_high()), '.1f') - self.focus_high_Label.push(23,self.labely,self.draw) - - self.focus_low_Label.string = "min " + self.arrangelabel(str(this_bundle.get_low()), '.1f') - self.focus_low_Label.center(self.labely,23,135,self.draw) - - self.focus_mean_Label.string = "x- " + self.arrangelabel(str(this_bundle.get_average()), '.1f') - self.focus_mean_Label.r_align(156,self.labely,self.draw) - - - # push the image frame and contents to the draw object. - def push(self,draw): - - - - # returns mode_a to the main loop unless something causes state change - status = "mode_a" - - - if configure.eventready[0]: - keys = configure.eventlist[0] - - # if a key is registering as pressed increment or rollover the selection variable. - if keys[0]: - self.selection += 1 - if self.selection > 3: - self.selection = 0 - - if keys[1]: - status = "mode_b" - configure.eventready[0] = False - return status - - if keys[2]: - configure.last_status[0] = "mode_a" - status = "settings" - configure.eventready[0] = False - return status - - configure.eventready[0] = False - - - # passes the current bitmap buffer to the object incase someone else needs it. - self.draw = draw - - senseslice =[0,0,0] - - for i in range(3): - - dsc = configure.sensor_info[configure.sensors[i][0]][3] - dev = configure.sensor_info[configure.sensors[i][0]][5] - - item = plars.get_recent(dsc,dev,num = 1) - - senseslice[i] = item[0] - - - # Grabs the current sensor reading - self.A_Data = senseslice[0]#configure.sensor_data[configure.sensor1[0]][0] - self.B_Data = senseslice[1]#configure.sensor_data[configure.sensor2[0]][0] - self.C_Data = senseslice[2]#configure.sensor_data[configure.sensor3[0]][0] - - - - - # Draws the Title - if self.selection != 0: - this = self.selection - 1 - self.title.string = configure.sensor_info[configure.sensors[this][0]][3] - else: - self.title.string = "Multi-Graph" - - self.title.push(self.titlex,self.titley,draw) - - - - - # Updates the graphs with the new data. - self.A_Graph.update(self.A_Data) - self.B_Graph.update(self.B_Data) - self.C_Graph.update(self.C_Data) - - # turns each channel on individually - if self.selection == 0: - - self.C_Graph.render(self.draw) - self.B_Graph.render(self.draw) - self.A_Graph.render(self.draw) - - - - if self.selection == 1: - self.A_Graph.render(self.draw) - - if self.selection == 2: - self.B_Graph.render(self.draw) - - if self.selection == 3: - self.C_Graph.render(self.draw) - - - - self.labels() - - - - return status -# governs the screen drawing of the entire program. Everything flows through Screen. -# Screen instantiates a draw object and passes it the image background. -# Screen monitors button presses and passes flags for interface updates to the draw object. - -class ThermalFrame(object): - def __init__(self): - # Sets the topleft origin of the graph - self.graphx = 23 - self.graphy = 24 - - # Sets the x and y span of the graph - self.gspanx = 133 - self.gspany = 71 - self.t_grid = ThermalGrid(23,24,133,71) - self.t_grid_full = ThermalGrid(23,8,133,109) - self.titlex = 23 - self.titley = 6 - - self.high = 0 - self.low = 0 - self.average = 0 - self.labely = 102 - - self.selection = 0 - - self.timed = timer() - self.timed.logtime() - - self.title = LabelObj("Thermal Array",titlefont) - - self.A_Label = LabelObj("No Data",font,colour = lcars_blue) - self.B_Label = LabelObj("No Data",font, colour = lcars_pinker) - self.C_Label = LabelObj("No Data",font, colour = lcars_orange) - - - # this function takes a value and sheds the second digit after the decimal place - def arrangelabel(self,data): - datareturn = format(float(data), '.0f') - return datareturn - - def labels(self): - - if self.selection == 0 or self.selection == 1: - raw_a = str(self.low) - adjusted_a = self.arrangelabel(raw_a) - a_string = "Low: " + adjusted_a - self.A_Label.string = a_string - self.A_Label.push(23,self.labely,self.draw) - - if self.selection == 0 or self.selection == 2: - raw_b = str(self.high) - adjusted_b = self.arrangelabel(raw_b) - self.B_Label.string = "High: " + adjusted_b - self.B_Label.center(self.labely,23,135, self.draw) - - if self.selection == 0 or self.selection == 3: - raw_c = str(self.average) - adjusted_c = self.arrangelabel(raw_c) - self.C_Label.string = "Avg: " + adjusted_c - self.C_Label.r_align(156,self.labely,self.draw) - - def push(self, draw): - - status = "mode_b" - - if configure.eventready[0]: - keys = configure.eventlist[0] - - - # ------------- Input handling -------------- # - if keys[0]: - status = "mode_a" - configure.eventready[0] = False - return status - - if keys[1]: - self.selection += 1 - if self.selection > 1: - self.selection = 0 - - if keys[2]: - status = "settings" - configure.last_status[0] = "mode_b" - configure.eventready[0] = False - return status - - configure.eventready[0] = False - - - self.draw = draw - self.labels() - - # Draw title - - self.title.push(self.titlex,self.titley, self.draw) - - - if self.selection == 0: - self.average,self.high,self.low = self.t_grid.update() - self.timed.logtime() - if self.selection == 1: - self.average,self.high,self.low = self.t_grid_full.update() - self.timed.logtime() - - if self.selection == 0: - self.t_grid.push(draw) - elif self.selection ==1: - self.t_grid_full.push(draw) - - return status - -class ColourScreen(object): - - def __init__(self): - self.surface = TFT.draw() - - # instantiates an image and uses it in a draw object. - self.image = Image.open('assets/lcarsframe.png') - self.blankimage = Image.open('assets/lcarsframeblank.png') - - self.status = "mode_a" - - # initializes all the modules - self.multi_frame = MultiFrame() - self.settings_frame = SettingsFrame() - self.thermal_frame = ThermalFrame() - self.powerdown_frame = PowerDown() - - - def get_size(self): - return self.multi_frame.get_x() - - def graph_screen(self): - - self.newimage = self.image.copy() - self.draw = ImageDraw.Draw(self.newimage) - self.status = self.multi_frame.push(self.draw) - - self.pixdrw() - - return self.status - - - def thermal_screen(self): - self.newimage = self.image.copy() - self.draw = ImageDraw.Draw(self.newimage) - self.status = self.thermal_frame.push(self.draw) - self.pixdrw() - return self.status - - def settings(self): - self.newimage = self.blankimage.copy() - self.draw = ImageDraw.Draw(self.newimage) - self.status = self.settings_frame.push(self.draw) - self.pixdrw() - return self.status - - def powerdown(self): - self.newimage = self.blankimage.copy() - self.draw = ImageDraw.Draw(self.newimage) - self.status = self.powerdown_frame.push(self.draw) - self.pixdrw() - return self.status - - def pixdrw(self): - thisimage = self.newimage.convert(mode = "RGB") - # Resize the image and rotate it so it's 240x320 pixels. - thisimage = self.newimage.rotate(90,0,1).resize((240, 320)) - # Draw the image on the display hardware. - self.surface.pasteimage(thisimage,(0,0)) - TFT.display() diff --git a/leds.py b/leds.py index a46386e..ca395a9 100644 --- a/leds.py +++ b/leds.py @@ -128,7 +128,8 @@ def resetleds(): # # This way you can just manually set them as you please. def screen_on(): if configure.tr109: - GPIO.output(sc_led, GPIO.HIGH) + if configure.display == 1: + GPIO.output(sc_led, GPIO.HIGH) def leda_on(): GPIO.output(led1, GPIO.HIGH) diff --git a/main.py b/main.py index 27bc8ba..0b18700 100644 --- a/main.py +++ b/main.py @@ -40,11 +40,8 @@ # for the TR-109 there are two display modes supported. if configure.tr109: - if configure.display == 2: - from lcars_clr import * - # 1.8" TFT colour LCD - if configure.display == 1: + if configure.display == 1 or configure.display == 2: from lcars_clr import * # Nokia 5110 black and white dot matrix screen. @@ -66,7 +63,7 @@ def Main(): if configure.display == 0: dotscreen = NokiaScreen() - if configure.display == 1: + if configure.display == 1 or configure.display == 2: colourscreen = ColourScreen() colourscreen.start_up() @@ -115,7 +112,7 @@ def Main(): # The rest of these loops all handle a different mode, switched by buttons within the functions. - if (configure.status[0] == "mode_a"): + if configure.status[0] == "mode_a": # the following is only run if the tr108 flag is set if configure.tr108: @@ -131,7 +128,7 @@ def Main(): if configure.display == 0: configure.status[0] = dotscreen.push(data) - if configure.display == 1: + if configure.display == 1 or configure.display == 2: configure.status[0] = colourscreen.graph_screen() if configure.status[0] == "mode_b": @@ -147,7 +144,7 @@ def Main(): if configure.tr109: if configure.display == 0: configure.status[0] = dotscreen.push(data) - if configure.display == 1: + if configure.display == 1 or configure.display == 2: configure.status[0] = colourscreen.em_screen() if configure.status[0] == "mode_c": @@ -155,7 +152,14 @@ def Main(): if configure.display == 1: configure.status[0] = colourscreen.thermal_screen() - if (configure.status[0] == "settings"): + if configure.tr108: + configure.status[0] = PyScreen.video_screen() + if not configure.pc: + leda_on() + ledb_on() + ledc_off() + + if configure.status[0] == "settings": if configure.tr108: configure.status[0] = PyScreen.settings() @@ -167,16 +171,16 @@ def Main(): if configure.tr109: if configure.display == 0: configure.status[0] = dotscreen.push() - if configure.display == 1: + if configure.display == 1 or configure.display == 2: configure.status[0] = colourscreen.settings() # Handles the poweroff screen - if (configure.status[0] == "poweroff"): + if configure.status[0] == "poweroff": if configure.tr109: if configure.display == 0: configure.status[0] = dotscreen.push() - if configure.display == 1: + if configure.display == 1 or configure.display == 2: configure.status[0] = colourscreen.powerdown() if configure.status[0] == "shutdown": diff --git a/objects.py b/objects.py index eb57301..cbd3a9d 100644 --- a/objects.py +++ b/objects.py @@ -29,7 +29,10 @@ def createMissingINI(self,filepath): config['SENSORS'] = {'# Only TR-108 uses SenseHat':None, 'sensehat':'yes', # Only TR-108 uses this 'system_vitals':'yes', + '# BME680 Raw Values':None, 'bme':'no', + '# BME680 VOC BSEC':None, + 'bme_bsec':'no', 'amg8833':'no', 'pocket_geiger':'no', '# IR Infrared Imaging':None, @@ -75,7 +78,8 @@ def createMissingINI(self,filepath): 'PG_NS':'21'} config['OUTPUT'] = {'display':'1', - 'LED_timer':'0.2'} + 'LED_timer':'0.2', + } config['GLOBALS'] = {'# Controls whether LEDs are active':None, 'leds':'yes', @@ -88,6 +92,10 @@ def createMissingINI(self,filepath): 'sleep':'yes', # If sleep is True the lights will respond to hall effect sensors '# Autoranging of graphs':None, 'autoranging':'yes', # Auto ranging of graphs + 'mode_a_graph_width':'280', # graph width for TR108 mode_a + 'mode_a_graph_height':'160', # graph height for TR108 mode_a\ + 'mode_a_x_offset':18, # x offset for TR108 mode_a + 'mode_a_y_offset':31, # y offset for TR108 mode_a '# Interpolate Temperature':None, 'interpolate':'yes', # Interpolate temperature 'samplerate':'0', @@ -231,6 +239,11 @@ def __init__(self): # controls auto ranging of graphs self.auto = [self.str2bool(config['GLOBALS']['autoranging'])] + # controls sizes and offsets for mode_a on TR108 + self.mode_a_graph_width = int(config['GLOBALS']['mode_a_graph_width']) + self.mode_a_graph_height = int(config['GLOBALS']['mode_a_graph_height']) + self.mode_a_x_offset = int(config['GLOBALS']['mode_a_x_offset']) + self.mode_a_y_offset = int(config['GLOBALS']['mode_a_y_offset']) # holds theme state for UI self.theme = [0] diff --git a/pilgraph.py b/pilgraph.py index a71d5f4..feec098 100644 --- a/pilgraph.py +++ b/pilgraph.py @@ -2,8 +2,8 @@ # PILgraph provides an object (graphlist) that will draw a new graph each frame. -# It was written to contain memory of the previous sensor readings, but this -# feature is no longer necessary. +# It was written to contain all the previous sensor readings, but this +# feature is no longer necessary as PLARS now handles all data history. # To do: # - request from PLARS the N most recent values for the sensor assigned to this identifier @@ -11,7 +11,10 @@ # it is initialized with: -# - a graph identifier so it knows which sensor to grab data for +# - ident: a graph identifier so it knows which sensor to grab data for +# - graphcoords: list containing the top left x,y coordinates +# - graphspan: list containing the x and y span in pixels +# - cycle: time per division of the graph (not working) # - from objects import * @@ -28,20 +31,26 @@ class graph_area(object): def __init__(self, ident, graphcoords, graphspan, cycle = 0, colour = 0, width = 1, type = 0, samples = False): + # if a samplesize is provided use it, otherwise grab global setting. if not samples: self.samples = configure.samples else: self.samples = samples - self.new = True self.cycle = cycle - self.tock = timer() - self.tock.logtime() + + self.glist = array('f', []) self.dlist = array('f', []) + self.colour = colour + + #controls auto scaling (set by global variable at render) self.auto = True + + # controls width self.width = width + self.dotw = 6 self.doth = 6 self.buff = array('f', []) @@ -186,6 +195,8 @@ def graphprep(self, datalist, ranger = None): return self.newlist + + def render(self, draw, auto = True, dot = True, ranger = None): self.auto = configure.auto[0] @@ -201,8 +212,9 @@ def render(self, draw, auto = True, dot = True, ranger = None): # standard pilgraph: takes DSC,DEV keypairs from screen drawer, asks # plars for data, graphs it. - if self.type == 0: + # Standard graph + if self.type == 0: index = configure.sensors[self.ident][0] dsc,dev,sym,maxi,mini = configure.sensor_info[index] recent = plars.get_recent(dsc,dev,num = self.samples) @@ -211,12 +223,12 @@ def render(self, draw, auto = True, dot = True, ranger = None): elif self.type == 1: recent = plars.get_top_em_history(no = self.samples) - # Testing a stream graph + # Testing a new graph elif self.type == 2: recent = plars.get_recent(dsc,dev,num = self.samples) - cords = self.graphprep(recent) + cords = self.graphprep(recent) self.buff = recent # draws the line graph diff --git a/pyvidplayer.py b/pyvidplayer.py new file mode 100644 index 0000000..dad07dc --- /dev/null +++ b/pyvidplayer.py @@ -0,0 +1,87 @@ +import pygame +from pymediainfo import MediaInfo +from ffpyplayer.player import MediaPlayer +from os.path import exists, basename, splitext +from os import strerror +from errno import ENOENT + + +class Video: + def __init__(self, path): + self.path = path + + if exists(path): + self.video = MediaPlayer(path) + info = self.get_file_data() + + self.duration = info["duration"] + self.frames = 0 + self.frame_delay = 1 / info["frame rate"] + self.size = info["original size"] + self.image = pygame.Surface((0, 0)) + + self.active = True + else: + raise FileNotFoundError(ENOENT, strerror(ENOENT), path) + + def get_file_data(self): + info = MediaInfo.parse(self.path).video_tracks[0] + return {"path":self.path, + "name":splitext(basename(self.path))[0], + "frame rate":float(info.frame_rate), + "frame count":info.frame_count, + "duration":info.duration / 1000, + "original size":(info.width, info.height), + "original aspect ratio":info.other_display_aspect_ratio[0]} + + def get_playback_data(self): + return {"active":self.active, + "time":self.video.get_pts(), + "volume":self.video.get_volume(), + "paused":self.video.get_pause(), + "size":self.size} + + def restart(self): + self.video.seek(0, relative=False, accurate=False) + self.frames = 0 + self.active = True + + def close(self): + self.video.close_player() + self.active = False + + def set_size(self, size): + self.video.set_size(size[0], size[1]) + self.size = size + + def set_volume(self, volume): + self.video.set_volume(volume) + + def seek(self, seek_time, accurate=False): + vid_time = self.video.get_pts() + if vid_time + seek_time < self.duration and self.active: + self.video.seek(seek_time) + if seek_time < 0: + while (vid_time + seek_time < self.frames * self.frame_delay): + self.frames -= 1 + + def toggle_pause(self): + self.video.toggle_pause() + + def update(self): + updated = False + while self.video.get_pts() > self.frames * self.frame_delay: + frame, val = self.video.get_frame() + self.frames += 1 + updated = True + if updated: + if val == "eof": + self.active = False + elif frame != None: + self.image = pygame.image.frombuffer(frame[0].to_bytearray()[0], frame[0].get_size(), "RGB") + return updated + + def draw(self, surf, pos, force_draw=True): + if self.active: + if self.update() or force_draw: + surf.blit(self.image, pos) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 9dd3974..468fb54 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,9 +7,10 @@ board==1.0 Cap1xxx==0.1.4 colour==0.1.5 envirophat==1.0.1 +ffpyplayer==4.3.2 luma.core==2.3.1 -luma.lcd==2.9.0 luma.emulator==1.4.0 +luma.lcd==2.9.0 numpy==1.21.4 pandas==1.3.4 Pillow==9.0.0 diff --git a/sensors.py b/sensors.py index 620d38a..5a63be9 100644 --- a/sensors.py +++ b/sensors.py @@ -180,6 +180,10 @@ def __init__(self): self.bme_humi = Fragment(0,100,"Hygrometer", "%", "BME680") self.bme_press = Fragment(300,1100,"Barometer","hPa", "BME680") self.bme_voc = Fragment(300000,1100000,"VOC","KOhm", "BME680") + #self.voc_procc = subprocess.Popen(['./bsec_bme680'], stdout=subprocess.PIPE) + + #if configure.bme_bsec: + # self.bme_bsec = Fragment(-40,85,"Quality",self.deg_sym + "Q", "BME680") if configure.pocket_geiger: self.radiat = Fragment(0.0, 10000.0, "Radiation", "urem/hr", "pocketgeiger") @@ -266,7 +270,7 @@ def get(self): # times 100 to convert to urem/h self.radiat.set(rad_data*100, timestamp) - + sensorlist.append(self.radiat) if configure.amg8833: diff --git a/tos_display.py b/tos_display.py index 4471120..07d5788 100644 --- a/tos_display.py +++ b/tos_display.py @@ -5,15 +5,19 @@ # styled tricorders. print("Loading 320x240 Duotronic Interface") # The following are some necessary modules for the Picorder. -import pygame -import time -import os +from asyncio import sleep +from pickle import FALSE +import pygame, time, os +from pyvidplayer import Video +os.environ["SDL_VIDEO_CENTERED"] = "1" +from pathlib import Path from plars import * from objects import * from input import * + if not configure.pc: if configure.tr108: import os @@ -21,10 +25,10 @@ #os.system('xset -display :0 -dpms') SAMPLE_SIZE = configure.samples -GRAPH_WIDTH = 280 -GRAPH_HEIGHT = 182 -GRAPH_X = 18 -GRAPH_Y = 20 +GRAPH_WIDTH = configure.mode_a_graph_width +GRAPH_HEIGHT = configure.mode_a_graph_height +GRAPH_X = configure.mode_a_x_offset +GRAPH_Y = configure.mode_a_y_offset GRAPH_X2 = GRAPH_X + GRAPH_WIDTH GRAPH_Y2 = GRAPH_Y + GRAPH_HEIGHT @@ -71,6 +75,7 @@ backgraph = pygame.image.load('assets/backgraph.png') slidera = pygame.image.load('assets/slider.png') sliderb = pygame.image.load('assets/slider2.png') +videobg = pygame.image.load('assets/videobg.png') status = "startup" last_status = "startup" @@ -581,6 +586,7 @@ def frame(self): if keys[1]: status = "mode_b" + configure.eventready[0] = False return status if keys[2]: @@ -750,6 +756,76 @@ def frame(self): def visible(self,item,option): self.visibility[item] = option +#experimental video screen written by scifi.radio from the mycorder discord +class Video_Screen(object): + def __init__(self,surface): + self.status = "mode_c" + self.surface = surface + self.videobg = Image() + self.videobg.update(videobg, 0,0) + self.running = False + self.paused = False + self.clock = pygame.time.Clock() + + def frame(self): + self.status = "mode_c" + if not self.running: + self.running = True + self.clip = Video('assets/ekmd.mov') + self.clip.set_size(resolution) + pygame.mixer.quit() + + self.clock.tick(60) + + if configure.eventready[0]: + + # The following code handles inputs and button presses. + keys = configure.eventlist[0] + + # if a key is registering as pressed. + if keys[0]: + print("Button 1") + self.status = "mode_b" + configure.eventready[0] = False + self.running = False + self.clip.close() + return self.status + + if keys[1]: + self.status = "mode_c" + self.clip.toggle_pause() + if self.paused: + self.paused = False + print("Resume") + else: + self.paused = True + print("Paused") + # We would use this to set mode_d instead of + # toggling a pause were we to get a mode_d + configure.eventready[0] = False + return self.status + + + if keys[2]: + configure.last_status[0] = "mode_c" + print("Button 3") + self.status = "settings" + self.running = False + configure.eventready[0] = False + + return self.status + + configure.eventready[0] = False + + #draws Background gridplane + self.videobg.draw(self.surface) + self.clip.draw(self.surface,(0,0), force_draw=FALSE) + #draws UI to frame buffer + pygame.display.update() + if not self.clip.active: + self.clip.restart() + + return self.status class Slider_Screen(object): @@ -795,7 +871,9 @@ def frame(self): return status if keys[1]: - status = "mode_b" + status = "mode_c" + configure.eventready[0] = False + return status if keys[2]: configure.last_status[0] = "mode_b" @@ -896,6 +974,7 @@ def __init__(self): self.timed = time.time() self.graphscreen = Graph_Screen(self.surface) + self.videoscreen = Video_Screen(self.surface) self.slidescreen = Slider_Screen(self.surface) self.settings_screen = Settings_Panel(self.surface) @@ -918,6 +997,10 @@ def graph_screen(self): status = self.graphscreen.frame() return status + def video_screen(self): + status = self.videoscreen.frame() + return status + def settings(self): status = self.settings_screen.frame() return status