Skip to content
Snippets Groups Projects
__init__.py 4.94 KiB
from st3m.application import Application, ApplicationContext
from st3m.ui.colours import PUSH_RED, GO_GREEN, BLACK
from st3m.goose import Dict, Any, Tuple
from st3m.input import InputState
from ctx import Context
import leds
import json
import math


class Configuration:
    def __init__(self) -> None:
        self.name = "flow3r"
        self.size: int = 75
        self.font: int = 5
        self.pronouns: list[str] = []
        self.pronouns_size: int = 25
        self.color = "0x40ff22"
        self.mode = 0

    @classmethod
    def load(cls, path: str) -> "Configuration":
        res = cls()
        try:
            with open(path) as f:
                jsondata = f.read()
            data = json.loads(jsondata)
        except OSError:
            data = {}
        if "name" in data and type(data["name"]) == str:
            res.name = data["name"]
        if "size" in data:
            if type(data["size"]) == float:
                res.size = int(data["size"])
            if type(data["size"]) == int:
                res.size = data["size"]
        if "font" in data and type(data["font"]) == int:
            res.font = data["font"]
        # type checks don't look inside collections
        if (
            "pronouns" in data
            and type(data["pronouns"]) == list
            and set([type(x) for x in data["pronouns"]]) == {str}
        ):
            res.pronouns = data["pronouns"]
        if "pronouns_size" in data:
            if type(data["pronouns_size"]) == float:
                res.pronouns_size = int(data["pronouns_size"])
            if type(data["pronouns_size"]) == int:
                res.pronouns_size = data["pronouns_size"]
        if (
            "color" in data
            and type(data["color"]) == str
            and data["color"][0:2] == "0x"
            and len(data["color"]) == 8
        ):
            res.color = data["color"]
        if "mode" in data:
            if type(data["mode"]) == float:
                res.mode = int(data["mode"])
            if type(data["mode"]) == int:
                res.mode = data["mode"]
        return res

    def save(self, path: str) -> None:
        d = {
            "name": self.name,
            "size": self.size,
            "font": self.font,
            "pronouns": self.pronouns,
            "pronouns_size": self.pronouns_size,
            "color": self.color,
        }
        jsondata = json.dumps(d)
        with open(path, "w") as f:
            f.write(jsondata)
            f.close()

    def to_normalized_tuple(self) -> Tuple[float, float, float]:
        return (
            int(self.color[2:4], 16) / 255.0,
            int(self.color[4:6], 16) / 255.0,
            int(self.color[6:8], 16) / 255.0,
        )


class NickApp(Application):
    def __init__(self, app_ctx: ApplicationContext) -> None:
        super().__init__(app_ctx)
        self._scale_name = 1.0
        self._scale_pronouns = 1.0
        self._led = 0.0
        self._phase = 0.0
        self._filename = "/flash/nick.json"
        self._config = Configuration.load(self._filename)
        self._pronouns_serialized = " ".join(self._config.pronouns)
        self._angle = 0.0

    def draw(self, ctx: Context) -> None:
        ctx.text_align = ctx.CENTER
        ctx.text_baseline = ctx.MIDDLE
        ctx.font_size = self._config.size
        ctx.font = ctx.get_font_name(self._config.font)

        ctx.rgb(0, 0, 0).rectangle(-120, -120, 240, 240).fill()
        ctx.rgb(*self._config.to_normalized_tuple())

        ctx.move_to(0, 0)
        ctx.save()
        if self._config.mode == 0:
            ctx.scale(self._scale_name, 1)
        elif self._config.mode == 1:
            ctx.rotate(self._angle)
        ctx.text(self._config.name)
        ctx.restore()

        if self._pronouns_serialized:
            ctx.move_to(0, -60)
            ctx.font_size = self._config.pronouns_size
            ctx.save()
            if self._config.mode == 0:
                ctx.scale(self._scale_pronouns, 1)
            elif self._config.mode == 1:
                ctx.rotate(self._angle)
            ctx.text(self._pronouns_serialized)
            ctx.restore()

        leds.set_hsv(int(self._led), abs(self._scale_name) * 360, 1, 0.2)

        leds.update()
        # ctx.fill()

    def on_exit(self) -> None:
        self._config.save(self._filename)

    def think(self, ins: InputState, delta_ms: int) -> None:
        super().think(ins, delta_ms)

        self._phase += delta_ms / 1000
        self._scale_name = math.sin(self._phase)
        self._scale_pronouns = math.cos(self._phase)

        iy = ins.imu.acc[0] * delta_ms / 10.0
        ix = ins.imu.acc[1] * delta_ms / 10.0
        ang = math.atan2(ix, iy)
        d_ang = self._angle + (ang + math.pi / 8 * math.sin(self._phase))
        self._angle -= d_ang / 20

        self._led += delta_ms / 45
        if self._led >= 40:
            self._led = 0


# For running with `mpremote run`:
if __name__ == "__main__":
    import st3m.run

    st3m.run.run_view(NickApp(ApplicationContext()))