There’s a vaporent community and a vaporists community. Neither have been active for almost a year. It would be nice to have one of those going to see what everybody’s using. I also miss the old old cult/ Franklin/thca groups from r/. Anyone interested in starting one of those?
Ask and ye shall receive! I’m tacking a GPL 3 license to it. The plugs that play a sound after a delay won’t work on Windows because I use fork(), but I don’t use Windows because fuck Windows. It uses mpv to play sounds, but that is easily changed. AMA.
#!/usr/bin/env python "Control my various tasmota devices. Licensed under GPL v3: https://www.gnu.org/licenses/gpl-3.0.en.html#license-text" import os from time import sleep import urllib.request from json import loads from subprocess import Popen __all__ = ["Tasmota", "TasmotaWarmup", "TasmotaOffFirst", "DEVICENAMES"] class Tasmota(): "A tasmota device." def __init__(self, ipaddress: str, name: str=None): self.ipaddress = ipaddress self.name = name def __repr__(self): return f"<{type(self).__name__} {self.name if self.name else self.ipaddress}>" def _request(self, cmd: str) -> str: "make an http request to the device" return urllib.request.urlopen(f"http://{self.ipaddress}/cm?cmnd={cmd.replace(' ', '%20')}").read() def on(self) -> bool: "Turn the device on. return True if successful" return b"ON" in self._request("Power On") def off(self) -> bool: "Turn the device off. return True if successful" return b"OFF" in self._request("Power Off") def toggle(self) -> bool: "Toggle the device power. return True if power is now on, False if it's off" return b"ON" in self._request("Power Toggle") def status(self) -> bool: "return True if the device is on, False if it is off" return bool(loads(self._request("Status"))["Status"]["Power"]) class TasmotaWarmup(Tasmota): "Plays a sound when started, plays a sound after a waiting period." def __init__(self, ipaddress: str, name: str=None, warmup_time: int=None, on_sound: str=None, ready_sound: str=None): "warmup_time is seconds, on/ready_sound is the path to the audio file to play" super().__init__(ipaddress, name) self.warmup_time = warmup_time self.on_sound = on_sound self.ready_sound = ready_sound def _playSound(self, sound: str) -> None: "play a sound" Popen(["mpv", "--no-terminal", "--volume=60", sound]) def _beginPowerOnSounds(self) -> None: "Play a sound when turning on and another sound when ready" if self.on_sound: self._playSound(self.on_sound) if self.warmup_time and self.ready_sound: if __name__ == "__main__": # using this as a script, fork to background and return terminal if os.fork() == 0: # wait in the background for the warmup_time self._sleepAndPlay() raise SystemExit else: Thread(target=self._sleepAndPlay).start() def _sleepAndPlay(self) -> None: "The actual sleeping and playing, to be run in a thread if needed." sleep(self.warmup_time) if self.status(): # if device is still on self._playSound(self.ready_sound) def on(self) -> bool: "Turn the device on and play sounds" if super().on(): self._beginPowerOnSounds() return True return False def toggle(self) -> bool: "toggle the status and play sounds if we're turning it on" if super().toggle(): self._beginPowerOnSounds() return True return False class TasmotaOffFirst(TasmotaWarmup): "A Tasmota object that turns the device off first before turning it on" def _turn_off_before_on(self) -> bool: "Turn this device off first if it's already on when it's switched on" if not super().toggle(): # if toggling turned it off super().on() return True def on(self) -> bool: return self._turn_off_before_on() class TasmotaAlwaysOn(TasmotaOffFirst): "This Tasmota class is always on; toggling it will turn it off briefly and then back on" def toggle(self) -> bool: "toggle this device off and then back on again" return self._turn_off_before_on() DEVICENAMES = {"volcano": TasmotaWarmup("192.168.1.152", "Volcano", 355, "/home/jt/.sounds/hold up hey.ogg", "/home/jt/.sounds/fill that bag up right now2.flac"), "towel": TasmotaOffFirst("192.168.1.153", "Towel Warmer", warmup_time=(20*60)+30, ready_sound="/home/jt/.sounds/yayeah.ogg"), "radiator": Tasmota("192.168.1.166", "Radiator"), "taco": TasmotaAlwaysOn("192.168.1.156", "Taco") } if __name__ != "__main__": from threading import Thread # only needed when importing this module else: import sys, argparse parser = argparse.ArgumentParser(description="Control Tasmota wifi power plugs") parser.add_argument("devices", help="device(s)", action="store", nargs="*") operation_group = parser.add_mutually_exclusive_group() operation_group.add_argument('--on', '-n', help="power on device", action="store_true") operation_group.add_argument('--off', '-f', help="power off device", action="store_true") operation_group.add_argument('--toggle', '-t', help="toggle device power", action="store_true") operation_group.add_argument('--status', '-s', help="get status of device", action="store_true") args = parser.parse_args() # Sanity checks if not args.devices: print(f"No device specified. Available devices are: {' '.join(DEVICENAMES.keys())}", file=sys.stderr) parser.print_help() sys.exit(1) invalid = [] for d in args.devices: if not DEVICENAMES.get(d): invalid.append(d) if invalid: print(f"Invalid device{'s' if len(invalid) > 1 else ''}: {' '.join(invalid)}", file=sys.stderr) print(f"Available devices are: {' '.join(DEVICENAMES.keys())}", file=sys.stderr) sys.exit(3) for d in args.devices: # gogo t = DEVICENAMES[d] if args.on: if t.on(): print(f"{t.name} turned on") else: print(f"Failed to turn on {t.name}", file=sys.stderr) elif args.off: if t.off(): print(f"{t.name} turned off") else: print(f"Failed to turn off {t.name}", file=sys.stderr) elif args.toggle: print(f"{t.name} turned {'on' if t.toggle() else 'off'}") elif args.status: print(f"{t.name} is {'on' if t.status() else 'off'}")