Ejemplo n.º 1
0
    def set_resolution(self, resolution: Tuple[int, int]) -> None:
        """Sets the current output to the given resolution.
        Also updates the background in production."""
        if not self.initialized:
            return
        subprocess.call([
            "xrandr",
            "--output",
            self.output,
            "--mode",
            util.format_resolution(resolution),
        ])

        if not conf.DEBUG:
            # show a background that is visible when the visualization is not rendering
            # needs to be reset every resolution change
            try:
                subprocess.call([
                    "feh",
                    "--bg-max",
                    os.path.join(conf.BASE_DIR,
                                 "resources/images/background.png"),
                ])
            except FileNotFoundError:
                pass

        redis.put("current_resolution", resolution)
        self.resolution = resolution
        lights.update_state()
Ejemplo n.º 2
0
    def __init__(self, manager) -> None:
        super().__init__(manager, "wled")

        self.led_count = storage.get("wled_led_count")

        self.ip = storage.get("wled_ip")
        if not self.ip:
            try:
                device = util.get_devices()[0]
                broadcast = util.broadcast_of_device(device)
                self.ip = broadcast
            except Exception:  # pylint: disable=broad-except
                # we don't want the startup to fail
                # just because the broadcast address could not be determined
                self.ip = "127.0.0.1"
            storage.put("wled_ip", self.ip)
        self.port = storage.get("wled_port")

        self.header = bytes([
            2,  # DRGB protocol, we update every led every frame
            1,  # wait 1 second after the last packet until resuming normally
        ])

        self.initialized = True
        redis.put("wled_initialized", True)
Ejemplo n.º 3
0
def _scan_bluetooth() -> None:
    bluetoothctl = _start_bluetoothctl()
    redis.put("bluetooth_devices", [])
    assert bluetoothctl and bluetoothctl.stdin

    bluetoothctl.stdin.write(b"devices\n")
    bluetoothctl.stdin.write(b"scan on\n")
    bluetoothctl.stdin.flush()
    while True:
        line = _get_bluetoothctl_line(bluetoothctl)
        if not line:
            break
        # match old devices
        match = re.match(r"Device (\S*) (.*)", line)
        # match newly scanned devices
        # We need the '.*' at the beginning of the line to account for control sequences
        if not match:
            match = re.match(r".*\[NEW\] Device (\S*) (.*)", line)
        if match:
            address = match.group(1)
            name = match.group(2)
            # filter unnamed devices
            # devices named after their address are no speakers
            if re.match("[A-Z0-9][A-Z0-9](-[A-Z0-9][A-Z0-9]){5}", name):
                continue
            bluetooth_devices = redis.get("bluetooth_devices")
            bluetooth_devices.append({"address": address, "name": name})
            redis.put("bluetooth_devices", bluetooth_devices)
            settings.update_state()
Ejemplo n.º 4
0
def trigger_alarm(_request: WSGIRequest) -> None:
    """Manually triggers an alarm."""
    playback.trigger_alarm()
    # because a state update is sent after every control (including this one)
    # a state update with alarm not being set would be sent
    # prevent this by manually setting this redis variable prematurely
    redis.put("alarm_playing", True)
Ejemplo n.º 5
0
def _start_bluetoothctl() -> Optional[subprocess.Popen]:
    if redis.get("bluetoothctl_active"):
        return None
    redis.put("bluetoothctl_active", True)
    bluetoothctl = subprocess.Popen(["bluetoothctl"],
                                    stdin=subprocess.PIPE,
                                    stdout=subprocess.PIPE)
    return bluetoothctl
Ejemplo n.º 6
0
def update_user_count() -> None:
    """Go through all recent requests and delete those that were too long ago."""
    now = time.time()
    last_requests = redis.get("last_requests")
    for key, value in list(last_requests.items()):
        if now - value >= INACTIVITY_PERIOD:
            del last_requests[key]
            redis.put("last_requests", last_requests)
    redis.put("last_user_count_update", now)
Ejemplo n.º 7
0
    def __init__(self) -> None:

        self.loop_active: Optional[Event] = Event()

        self.devices = Devices(Ring(self), Strip(self), WLED(self),
                               Screen(self))

        # these settings are mirrored from the database,
        # because some of them are accessed multiple times per update.
        self.settings: Settings = {
            "ups": storage.get("ups"),
            "dynamic_resolution": storage.get("dynamic_resolution"),
            "program_speed": storage.get("program_speed"),
            "fixed_color": storage.get("fixed_color"),
            "last_fixed_color": storage.get("fixed_color"),
        }

        self.utilities = Utilities(Disabled(self), Cava(self), Alarm(self))
        cava_installed = shutil.which("cava") is not None

        # a dictionary containing all led programs by their name
        led_programs: Dict[str, LedProgram] = {
            self.utilities.disabled.name: self.utilities.disabled
        }
        led_program_classes = [Fixed, Rainbow]
        if cava_installed:
            led_program_classes.append(Adaptive)
        for led_program_class in led_program_classes:
            led_program = led_program_class(self)
            led_programs[led_program.name] = led_program
        # a dictionary containing all screen programs by their name
        screen_programs: Dict[str, ScreenProgram] = {
            self.utilities.disabled.name: self.utilities.disabled
        }
        logo_loop = Video(self, "LogoLoop.mp4", loop=True)
        screen_programs[logo_loop.name] = logo_loop
        if cava_installed:
            for variant in sorted(Visualization.get_variants()):
                screen_programs[variant] = Visualization(self, variant)

        redis.put("led_programs", list(led_programs.keys()))
        redis.put("screen_programs", list(screen_programs.keys()))

        # a dictionary containing *all* programs by their name
        self.programs: Dict[str, LightProgram] = {
            **led_programs,
            **screen_programs
        }

        for device in self.devices:
            device.load_program()

        self.consumers_changed()

        self.listener = Thread(target=self.listen_for_changes)
        self.listener.start()
Ejemplo n.º 8
0
 def consumers_changed(self) -> None:
     """Stops the loop if no led is active, starts it otherwise"""
     if self.utilities.disabled.consumers == 4:
         assert self.loop_active
         self.loop_active.clear()
         redis.put("lights_active", False)
     else:
         assert self.loop_active
         self.loop_active.set()
         redis.put("lights_active", True)
Ejemplo n.º 9
0
            def stop_workers() -> None:
                # wake up the playback thread and stop it
                redis.put("stop_playback_loop", True)
                playback.queue_changed.set()

                # wake the buzzer thread so it exits
                playback.buzzer_stopped.set()

                # wake up the listener thread with an instruction to stop the lights worker
                redis.connection.publish("lights_settings_changed", "stop")
Ejemplo n.º 10
0
 def __init__(self, manager, name) -> None:
     self.manager = manager
     self.name = name
     assert self.name in ["ring", "strip", "wled", "screen"]
     self.brightness = storage.get(
         cast(DeviceBrightness, f"{self.name}_brightness"))
     self.monochrome = storage.get(
         cast(DeviceMonochrome, f"{self.name}_monochrome"))
     self.initialized = False
     redis.put(cast(DeviceInitialized, f"{self.name}_initialized"), False)
     self.program: LightProgram = Disabled(manager)
Ejemplo n.º 11
0
 def adjust(self) -> None:
     """Updates resolutions and resets the current one.
     Needed after changing screens or hotplugging after booting without a connected screen."""
     self.resolution = storage.get("initial_resolution")
     resolutions = list(reversed(sorted(self.list_resolutions())))
     redis.put("resolutions", resolutions)
     # if unset, initialize with the highest resolution
     if self.resolution == (0, 0):
         storage.put("initial_resolution", resolutions[0])
         self.resolution = resolutions[0]
     self.set_resolution(self.resolution)
Ejemplo n.º 12
0
def set_bluetooth_scanning(request: WSGIRequest) -> HttpResponse:
    """Enables scanning of bluetooth devices."""
    enabled = request.POST.get("value") == "true"
    if enabled:
        if redis.get("bluetoothctl_active"):
            return HttpResponseBadRequest("Already Scanning")

        _scan_bluetooth.delay()
        return HttpResponse("Started scanning")

    if not redis.get("bluetoothctl_active"):
        return HttpResponseBadRequest("Currently not scanning")
    # this is another request, so we don't have a handle of the current bluetoothctl process
    # terminate the process by name and release the lock
    subprocess.call("pkill bluetoothctl".split())
    redis.put("bluetoothctl_active", False)
    return HttpResponse("Stopped scanning")
Ejemplo n.º 13
0
 def compute(self) -> None:
     now = time.time()
     if now - self.last_fps_check > Visualization.FPS_MEASURE_WINDOW / 2:
         self.last_fps_check = now
         current_fps = self.controller.get_fps()
         redis.put("current_fps", current_fps)
         if (
             self.manager.settings["dynamic_resolution"]
             and current_fps < 0.9 * self.manager.settings["ups"]
         ):
             self.manager.devices.screen.lower_resolution()
             # restart the program with the new resolution
             self.manager.restart_screen_program(sleep_time=2, has_lock=True)
         else:
             lights.update_state()
     if not self.controller.is_active():
         raise ScreenProgramStopped
     self.controller.set_parameters(
         self.manager.utilities.alarm.factor,
         self.manager.utilities.cava.current_frame,
     )
Ejemplo n.º 14
0
    def __init__(self, manager) -> None:
        super().__init__(manager, "screen")

        # set the DISPLAY environment variable the correct X Display is used
        os.environ["DISPLAY"] = ":0"
        # the visualization needs X to work, so we check if it is running
        try:
            subprocess.check_call("xset q".split(),
                                  stdout=subprocess.DEVNULL,
                                  stderr=subprocess.DEVNULL)
        except (FileNotFoundError, subprocess.CalledProcessError):
            # Cannot connect to X
            return

        # disable blanking and power saving
        subprocess.call("xset s off".split())
        subprocess.call("xset s noblank".split())
        subprocess.call("xset -dpms".split())

        # ignore the scale factor for large displays,
        # we always render fullscreen without scaling
        os.environ["WINIT_X11_SCALE_FACTOR"] = "1"

        # method should additionally check whether hdmi is connected
        # however, I found no way to do that
        # without hdmi_force_hotplug=1:
        # tvservice -M gives attached events, but tvserice -s is always connected
        # hdmi cannot be plugged in after boot
        # with hdmi_force_hotplug=1:
        # tvservice -M records nothing, tvserice -s is always connected
        # /sys/class/drm/card1-HDMI-A-1/status is always connected
        #
        # so we set hotplug and initialize the screen even if none is connected
        self.initialized = True
        redis.put("screen_initialized", True)

        self.output = self.get_primary()
        self.resolution = (0, 0)  # set in adjust
        self.adjust()
Ejemplo n.º 15
0
    def __init__(self, manager) -> None:
        super().__init__(manager, "strip")
        self.monochrome = True

        try:
            # https://github.com/adafruit/Adafruit_CircuitPython_PCA9685/blob/main/examples/pca9685_simpletest.py
            from board import SCL, SDA
            import busio
            from adafruit_pca9685 import PCA9685
        except ModuleNotFoundError:
            return

        try:
            i2c_bus = busio.I2C(SCL, SDA)
            self.controller = PCA9685(i2c_bus)
            self.controller.frequency = 60

            self.initialized = True
            redis.put("strip_initialized", True)
        except ValueError:
            # LED strip is not connected
            return
Ejemplo n.º 16
0
    def __init__(self, manager) -> None:
        super().__init__(manager, "ring")

        try:
            import rpi_ws281x
        except ModuleNotFoundError:
            return

        self.controller = rpi_ws281x.Adafruit_NeoPixel(
            self.LED_COUNT,
            self.LED_PIN,
            self.LED_FREQ_HZ,
            self.LED_DMA,
            self.LED_INVERT,
            self.LED_BRIGHTNESS,
            self.LED_CHANNEL,
        )
        try:
            self.controller.begin()
            self.initialized = True
            redis.put("ring_initialized", True)
        except RuntimeError:
            # could not connect to led ring
            return
Ejemplo n.º 17
0
    def _decorator(request: WSGIRequest) -> HttpResponse:
        # create a sessions if none exists (necessary for anonymous users)
        if not request.session or not request.session.session_key:
            request.session.save()

        request_ip = get_client_ip(request)
        last_requests = redis.get("last_requests")
        last_requests[request_ip] = time.time()
        redis.put("last_requests", last_requests)

        def check():
            active = redis.get("active_requests")
            if active > 0:
                leds.enable_act_led()
            else:
                leds.disable_act_led()

        redis.connection.incr("active_requests")
        check()
        response = func(request)
        redis.connection.decr("active_requests")
        check()

        return response
Ejemplo n.º 18
0
def start() -> None:
    """Initializes this module by checking which platforms are available to use."""

    # local songs are enabled if a library is set
    local_enabled = os.path.islink(library.get_library_path())
    storage.put("local_enabled", local_enabled)

    # in the docker container all dependencies are installed
    youtube_available = conf.DOCKER or importlib.util.find_spec(
        "yt_dlp") is not None
    redis.put("youtube_available", youtube_available)
    if not youtube_available:
        # if youtube is not available, overwrite the database to disable it
        storage.put("youtube_enabled", False)

    # Spotify has no python dependencies we could easily check.
    try:
        spotify_available = (
            conf.DOCKER or "[spotify]" in subprocess.check_output(
                ["mopidy", "config"],
                stderr=subprocess.DEVNULL).decode().splitlines())
    except FileNotFoundError:
        # mopidy is not installed (eg in docker). Since we can't check, enable
        spotify_available = True
    redis.put("spotify_available", spotify_available)
    if not spotify_available:
        storage.put("spotify_enabled", False)

    soundcloud_available = (conf.DOCKER or
                            importlib.util.find_spec("soundcloud") is not None)
    redis.put("soundcloud_available", soundcloud_available)
    if not soundcloud_available:
        storage.put("soundcloud_enabled", False)

    # Jamendo has no python dependencies we could easily check.
    try:
        jamendo_available = (
            conf.DOCKER or "[jamendo]" in subprocess.check_output(
                ["mopidy", "config"],
                stderr=subprocess.DEVNULL).decode().splitlines())
    except FileNotFoundError:
        jamendo_available = True
    redis.put("jamendo_available", jamendo_available)
    if not jamendo_available:
        storage.put("jamendo_enabled", False)
Ejemplo n.º 19
0
def _check_internet() -> None:
    host = storage.get("connectivity_host")
    if not host:
        redis.put("has_internet", False)
        return
    response = subprocess.call(
        ["ping", "-c", "1", "-W", "3", host], stdout=subprocess.DEVNULL
    )
    if response == 0:
        redis.put("has_internet", True)
    else:
        redis.put("has_internet", False)
Ejemplo n.º 20
0
def _set_scan_progress(scan_progress: str) -> None:
    redis.put("library_scan_progress", scan_progress)
    settings.update_state()
Ejemplo n.º 21
0
def _stop_bluetoothctl(bluetoothctl: subprocess.Popen) -> None:
    assert bluetoothctl.stdin
    bluetoothctl.stdin.close()
    bluetoothctl.wait()
    redis.put("bluetoothctl_active", False)
Ejemplo n.º 22
0
def skip(_request: WSGIRequest) -> None:
    """Skips the current song and continues with the next one."""
    with playback.mopidy_command() as allowed:
        if allowed:
            redis.put("backup_playing", False)
            PLAYER.playback.next()