1
0
mirror of https://github.com/natekspencer/hacs-oasis_mini.git synced 2025-11-08 05:03:52 -05:00

12 Commits

Author SHA1 Message Date
Nathan Spencer
21fd8a63ba Merge pull request #80 from natekspencer/led-effects
Add additional led effects
2025-07-22 18:09:16 -06:00
Nathan Spencer
552339665f Add additional led effects 2025-07-23 00:06:10 +00:00
Nathan Spencer
85449a5363 Merge pull request #79 from natekspencer/add-sleep-button
Add sleep button
2025-07-22 17:37:52 -06:00
Nathan Spencer
d2bc89bdd7 Add sleep button 2025-07-22 23:36:33 +00:00
Nathan Spencer
06008e8f4c Merge pull request #78 from natekspencer/firmware-2.02-temp-fix
Add fix for firmware 2.02 led issue
2025-07-22 17:34:36 -06:00
Nathan Spencer
9fdfd8129f Merge pull request #76 from natekspencer/update-tracks
Update tracks
2025-07-22 17:31:03 -06:00
natekspencer
f9237927d9 Update tracks 2025-07-22 19:20:38 +00:00
Nathan Spencer
dcd8db52f5 Merge pull request #75 from natekspencer/update-tracks
Update tracks
2025-07-21 13:21:11 -06:00
natekspencer
86cf060af0 Update tracks 2025-07-21 19:20:16 +00:00
Nathan Spencer
d7a803abc7 Merge pull request #74 from natekspencer/update-tracks
Update tracks
2025-07-21 09:20:31 -06:00
natekspencer
a1bb4c78fb Update tracks 2025-07-18 19:19:31 +00:00
Nathan Spencer
50f7b270f2 Add temp fix for firmware 2.02 led issue 2025-03-26 17:40:26 +00:00
6 changed files with 278 additions and 18 deletions

View File

@@ -61,6 +61,11 @@ DESCRIPTORS = (
translation_key="random_track",
press_fn=play_random_track,
),
OasisMiniButtonEntityDescription(
key="sleep",
translation_key="sleep",
press_fn=lambda device: device.async_sleep(),
),
)

View File

@@ -10,7 +10,7 @@ from urllib.parse import urljoin
from aiohttp import ClientResponseError, ClientSession
from .const import TRACKS
from .utils import _bit_to_bool, decrypt_svg_content
from .utils import _bit_to_bool, _parse_int, decrypt_svg_content
_LOGGER = logging.getLogger(__name__)
@@ -20,9 +20,11 @@ STATUS_CODE_MAP = {
3: "centering",
4: "playing",
5: "paused",
6: "sleeping",
9: "error",
11: "updating",
13: "downloading",
14: "busy",
15: "live",
}
@@ -51,12 +53,39 @@ LED_EFFECTS: Final[dict[str, str]] = {
"12": "Follow Rainbow",
"13": "Chasing Comet",
"14": "Gradient Follow",
"15": "Cumulative Fill",
"16": "Multi Comets A",
"17": "Rainbow Chaser",
"18": "Twinkle Lights",
"19": "Tennis Game",
"20": "Breathing Exercise 4-7-8",
"21": "Cylon Scanner",
"22": "Palette Mode",
"23": "Aurora Flow",
"24": "Colorful Drops",
"25": "Color Snake",
"26": "Flickering Candles",
"27": "Digital Rain",
"28": "Center Explosion",
"29": "Rainbow Plasma",
"30": "Comet Race",
"31": "Color Waves",
"32": "Meteor Storm",
"33": "Firefly Flicker",
"34": "Ripple",
"35": "Jelly Bean",
"36": "Forest Rain",
"37": "Multi Comets",
"38": "Multi Comets with Background",
"39": "Rainbow Fill",
"40": "White Red Comet",
"41": "Color Comets",
}
CLOUD_BASE_URL = "https://app.grounded.so"
BALL_SPEED_MAX: Final = 1000
BALL_SPEED_MIN: Final = 200
BALL_SPEED_MAX: Final = 400
BALL_SPEED_MIN: Final = 100
LED_SPEED_MAX: Final = 90
LED_SPEED_MIN: Final = -90
@@ -210,25 +239,29 @@ class OasisMini:
raw_status = await self._async_get(params={"GETSTATUS": ""})
_LOGGER.debug("Status: %s", raw_status)
values = raw_status.split(";")
playlist = [int(track) for track in values[3].split(",") if track]
playlist = [_parse_int(track) for track in values[3].split(",") if track]
shift = len(values) - 18 if len(values) > 17 else 0
status = {
"status_code": int(values[0]), # see status code map
"error": int(values[1]), # noqa: E501; error, 0 = none, and 10 = ?, 18 = can't download?
"ball_speed": int(values[2]), # 200 - 1000
"status_code": _parse_int(values[0]), # see status code map
"error": _parse_int(values[1]), # noqa: E501; error, 0 = none, and 10 = ?, 18 = can't download?
"ball_speed": _parse_int(values[2]), # 200 - 1000
"playlist": playlist,
"playlist_index": min(int(values[4]), len(playlist)), # index of above
"progress": int(values[5]), # 0 - max svg path
"playlist_index": min(_parse_int(values[4]), len(playlist)), # noqa: E501; index of above
"progress": _parse_int(values[5]), # 0 - max svg path
"led_effect": values[6], # led effect (code lookup)
"led_color_id": values[7], # led color id?
"led_speed": int(values[8]), # -90 - 90
"brightness": int(values[9]) if values[10] else 0, # noqa: E501; 0 - 200 in app, but seems to be 0 (off) to 304 (max), then repeats
"color": values[10] or None, # hex color code
"busy": _bit_to_bool(values[11]), # noqa: E501; device is busy (downloading track, centering, software update)?
"download_progress": int(values[12]),
"max_brightness": int(values[13]),
"wifi_connected": _bit_to_bool(values[14]),
"repeat_playlist": _bit_to_bool(values[15]),
"autoplay": AUTOPLAY_MAP.get(value := values[16], value),
"led_speed": _parse_int(values[8]), # -90 - 90
"brightness": _parse_int(values[9]), # noqa: E501; 0 - 200 in app, but seems to be 0 (off) to 304 (max), then repeats
"color": values[10] if "#" in values[10] else None, # hex color code
"busy": _bit_to_bool(values[11 + shift]), # noqa: E501; device is busy (downloading track, centering, software update)?
"download_progress": _parse_int(values[12 + shift]),
"max_brightness": _parse_int(values[13 + shift]),
"wifi_connected": _bit_to_bool(values[14 + shift]),
"repeat_playlist": _bit_to_bool(values[15 + shift]),
"autoplay": AUTOPLAY_MAP.get(value := values[16 + shift], value),
"autoclean": _bit_to_bool(values[17 + shift])
if len(values) > 17
else False,
}
for key, value in status.items():
if (old_value := getattr(self, key, None)) != value:
@@ -327,6 +360,10 @@ class OasisMini:
"""Set repeat playlist."""
await self._async_command(params={"WRIREPEATJOB": 1 if repeat else 0})
async def async_sleep(self) -> None:
"""Send sleep command."""
await self._async_command(params={"CMDSLEEP": ""})
async def async_stop(self) -> None:
"""Send stop command."""
await self._async_command(params={"CMDSTOP": ""})

View File

@@ -269,6 +269,14 @@
"1": 18767
}
},
"8503": {
"id": 8503,
"name": "Angry Badger",
"author": "Otávio Bittencourt",
"image": "2025/07/e322d3ade30f0a0e9af497a13257efab.svg",
"clean_pattern": null,
"reduced_svg_content": []
},
"8341": {
"id": 8341,
"name": "Angry Cat",
@@ -749,6 +757,14 @@
"1": 3920
}
},
"8485": {
"id": 8485,
"name": "Bodhi tree",
"author": "Otávio Bittencourt",
"image": "2025/06/637f117735a86aa56a9eb590d7d805ac.svg",
"clean_pattern": null,
"reduced_svg_content": []
},
"7915": {
"id": 7915,
"name": "Bonsai tree",
@@ -759,6 +775,14 @@
"1": 55010
}
},
"8511": {
"id": 8511,
"name": "Botanic Spiral",
"author": "Otávio Bittencourt",
"image": "2025/07/3a2c85f1216662881ae2ee783382734f.svg",
"clean_pattern": null,
"reduced_svg_content": []
},
"56": {
"id": 56,
"name": "Branch",
@@ -1040,6 +1064,14 @@
"1": 28146
}
},
"8441": {
"id": 8441,
"name": "Cherry Blossom Bonsai",
"author": "Otávio Bittencourt",
"image": "2025/06/f1a6e7624e05a330c1d439001f3f09f5.svg",
"clean_pattern": null,
"reduced_svg_content": []
},
"7478": {
"id": 7478,
"name": "ChevronWipe OO",
@@ -1273,6 +1305,14 @@
},
"updated_at": "2024-08-04T16:01:33.000000Z"
},
"8586": {
"id": 8586,
"name": "Clover Star",
"author": "Walter Melanson",
"image": "2025/07/a7bbfe0d796ccd16fbe1cb20ee865bb9.svg",
"clean_pattern": null,
"reduced_svg_content": []
},
"505": {
"id": 505,
"name": "Coarse Hilbert Wiper",
@@ -1370,6 +1410,22 @@
"1": 2550
}
},
"8573": {
"id": 8573,
"name": "Coffee Table Spiral In to Out",
"author": "Oasis Mini",
"image": "2025/07/3ab8079a9fcd56febcfcb0caa8a033e7.svg",
"clean_pattern": null,
"reduced_svg_content": []
},
"8574": {
"id": 8574,
"name": "Coffee Table Spiral Out to In",
"author": "Oasis Mini",
"image": "2025/07/6dc91c7431d03817425010d2beffde8e.svg",
"clean_pattern": null,
"reduced_svg_content": []
},
"1300": {
"id": 1300,
"name": "Collapse",
@@ -3158,6 +3214,14 @@
"1": 22478
}
},
"8510": {
"id": 8510,
"name": "Heartleaf Philodendron",
"author": "Otávio Bittencourt",
"image": "2025/07/c5256b608155eb20cf4a26bc78a8a62c.svg",
"clean_pattern": null,
"reduced_svg_content": []
},
"6414": {
"id": 6414,
"name": "Hearts",
@@ -3209,6 +3273,14 @@
"1": 67309
}
},
"8498": {
"id": 8498,
"name": "Hibiscus Flower",
"author": "Otávio Bittencourt",
"image": "2025/06/111f43d73c3dfe1cd836ee70592b7673.svg",
"clean_pattern": null,
"reduced_svg_content": []
},
"147": {
"id": 147,
"name": "Hilbert",
@@ -3417,6 +3489,14 @@
"clean_pattern": null,
"reduced_svg_content": []
},
"8403": {
"id": 8403,
"name": "Imaginative Sand Drawning",
"author": "Otávio Bittencourt",
"image": "2025/06/180e2727632e3e92791e8160f993c550.svg",
"clean_pattern": null,
"reduced_svg_content": []
},
"2686": {
"id": 2686,
"name": "Infinite Rose",
@@ -3992,6 +4072,14 @@
"1": 44947
}
},
"8425": {
"id": 8425,
"name": "Mammoth",
"author": "Otávio Bittencourt",
"image": "2025/06/b8a285c6ad730abaa0d89690be5abb38.svg",
"clean_pattern": null,
"reduced_svg_content": []
},
"64": {
"id": 64,
"name": "Mandala",
@@ -4268,6 +4356,14 @@
},
"updated_at": "2024-08-04T15:58:53.000000Z"
},
"8508": {
"id": 8508,
"name": "Monstera Deliciosa",
"author": "Otávio Bittencourt",
"image": "2025/07/af2e6ac2a5c92705eb8f2fcfea5ac928.svg",
"clean_pattern": null,
"reduced_svg_content": []
},
"1778": {
"id": 1778,
"name": "Monstera leaves",
@@ -4577,6 +4673,22 @@
"1": 29643
}
},
"8491": {
"id": 8491,
"name": "Passion Flower",
"author": "Otávio Bittencourt",
"image": "2025/06/f743035a2c869d16267e18feba47371b.svg",
"clean_pattern": null,
"reduced_svg_content": []
},
"8502": {
"id": 8502,
"name": "Passion Flower power",
"author": "Otávio Bittencourt",
"image": "2025/06/e87580e434cc8cec9c4c28bcf1c0acd2.svg",
"clean_pattern": null,
"reduced_svg_content": []
},
"221": {
"id": 221,
"name": "Pattern 3",
@@ -4832,6 +4944,14 @@
"1": 24258
}
},
"8483": {
"id": 8483,
"name": "Poison Ivy",
"author": "Otávio Bittencourt",
"image": "2025/06/d33ab742dfe9ce327a7a9746e1a8a096.svg",
"clean_pattern": null,
"reduced_svg_content": []
},
"1049": {
"id": 1049,
"name": "PolarValentine",
@@ -4842,6 +4962,14 @@
"1": 29754
}
},
"8464": {
"id": 8464,
"name": "Polka Dot Begonia",
"author": "Otávio Bittencourt",
"image": "2025/06/46cd2ffdf53ab439af8edd4ccecad3be.svg",
"clean_pattern": null,
"reduced_svg_content": []
},
"995": {
"id": 995,
"name": "Polygon Fisheye",
@@ -4852,6 +4980,14 @@
"1": 7020
}
},
"8588": {
"id": 8588,
"name": "Polygons",
"author": "Walter Melanson",
"image": "2025/07/eee6096038e9b6a70808fdddcbe1fef7.svg",
"clean_pattern": null,
"reduced_svg_content": []
},
"559": {
"id": 559,
"name": "Polymath",
@@ -4904,6 +5040,14 @@
"1": 33688
}
},
"8431": {
"id": 8431,
"name": "Pufferfish",
"author": "Otávio Bittencourt",
"image": "2025/06/b97dcd4158361728d4908fcff652ead7.svg",
"clean_pattern": null,
"reduced_svg_content": []
},
"5184": {
"id": 5184,
"name": "Pyrohedron",
@@ -5503,6 +5647,22 @@
"1": 4665
}
},
"8575": {
"id": 8575,
"name": "Side Table Spiral In to Out",
"author": "Oasis Mini",
"image": "2025/07/f3956248c0f7f5c281c52d85b7654ec9.svg",
"clean_pattern": null,
"reduced_svg_content": []
},
"8576": {
"id": 8576,
"name": "Side Table Spiral Out to In",
"author": "Oasis Mini",
"image": "2025/07/ffb61a26562e26efb34f8706c7ba52ec.svg",
"clean_pattern": null,
"reduced_svg_content": []
},
"149": {
"id": 149,
"name": "Sierpenski",
@@ -6270,6 +6430,14 @@
"1": 3187
}
},
"8459": {
"id": 8459,
"name": "Spoted Rex Begonia",
"author": "Otávio Bittencourt",
"image": "2025/06/5a16cae9f3a6b5d870a6e2a6fd4506fe.svg",
"clean_pattern": null,
"reduced_svg_content": []
},
"3047": {
"id": 3047,
"name": "Sprioneering",
@@ -6914,6 +7082,14 @@
},
"updated_at": "2024-08-04T15:58:54.000000Z"
},
"8497": {
"id": 8497,
"name": "T-Rex Skull",
"author": "Otávio Bittencourt",
"image": "2025/06/8f6e693fa071f8be3b084032d78580a1.svg",
"clean_pattern": null,
"reduced_svg_content": []
},
"7276": {
"id": 7276,
"name": "Takeshi Ren",
@@ -7032,6 +7208,14 @@
"1": 58681
}
},
"8428": {
"id": 8428,
"name": "The Buddahs Head 2",
"author": "Otávio Bittencourt",
"image": "2025/06/71bad0197fc33b677b29bdd063d50b59.svg",
"clean_pattern": null,
"reduced_svg_content": []
},
"6998": {
"id": 6998,
"name": "The Camel",
@@ -8232,6 +8416,14 @@
},
"updated_at": "2024-08-04T16:01:31.000000Z"
},
"8488": {
"id": 8488,
"name": "Zen Bambu and Stones",
"author": "Otávio Bittencourt",
"image": "2025/06/21cdeb1f40571b68578afa6844326936.svg",
"clean_pattern": null,
"reduced_svg_content": []
},
"4014": {
"id": 4014,
"name": "Zombie",
@@ -8241,5 +8433,13 @@
"reduced_svg_content": {
"1": 21411
}
},
"8593": {
"id": 8593,
"name": "ZSC Lions Stern",
"author": "adi",
"image": "2025/07/de61ce519b7e6946ed777d54f521a29c.svg",
"clean_pattern": null,
"reduced_svg_content": []
}
}

View File

@@ -26,6 +26,14 @@ def _bit_to_bool(val: str) -> bool:
return val == "1"
def _parse_int(val: str) -> int:
"""Convert an int string to int."""
try:
return int(val)
except Exception:
return 0
def draw_svg(track: dict, progress: int, model_id: str) -> str | None:
"""Draw SVG."""
if track and (svg_content := track.get("svg_content")):

View File

@@ -41,6 +41,9 @@
"button": {
"random_track": {
"name": "Play random track"
},
"sleep": {
"name": "Sleep"
}
},
"binary_sensor": {
@@ -93,9 +96,11 @@
"centering": "Centering",
"playing": "Playing",
"paused": "Paused",
"sleeping": "Sleeping",
"error": "Error",
"updating": "Updating",
"downloading": "Downloading",
"busy": "Busy",
"live": "Live drawing"
}
}

View File

@@ -41,6 +41,9 @@
"button": {
"random_track": {
"name": "Play random track"
},
"sleep": {
"name": "Sleep"
}
},
"binary_sensor": {
@@ -93,9 +96,11 @@
"centering": "Centering",
"playing": "Playing",
"paused": "Paused",
"sleeping": "Sleeping",
"error": "Error",
"updating": "Updating",
"downloading": "Downloading",
"busy": "Busy",
"live": "Live drawing"
}
}