Add your description here
Project description
edifier_es300
Control an Edifier ES300 speaker over Wi-Fi, without the Edifier Home app.
Ships an asyncio library and a click-based command-line interface.
- Library (
edifier_es300): stdlib-only, fully async. - CLI (
python -m edifier_es300): thin wrapper over the library; needsclick.
Requirements
- Python 3.13+
click(CLI only — the library has no third-party dependencies)
uv sync # or: pip install click
Library usage
Everything is async. The ES300 connection is an async context manager — reuse a
single connection for a burst of commands (the device drops an idle socket after a
few seconds).
import asyncio
from edifier_es300 import ES300, Source, EqPreset, LightEffect, LightColor
async def main():
async with ES300("192.168.1.123", 8080) as device:
print(await device.status()) # parsed Status (see below)
await device.volume(20) # 0..30
await device.play() # resume
await device.pause() # pause
await device.play_pause_toggle() # toggle
await device.next_track()
await device.previous_track()
await device.input_source(Source.AIRPLAY)
await device.light_switch(True)
await device.brightness(60) # 0..100
await device.light_effect(LightEffect.BREATHING)
await device.light_color(LightColor.YELLOW)
await device.eq_preset(EqPreset.VOCAL)
await device.eq_custom((10, 5, 0, 0, 0, -5)) # 6 gains, tenths of a dB (-30..30)
await device.timer_shutdown(30) # sleep timer in minutes (0 = off)
await device.shutdown() # power off (no remote power-on; physical button to wake)
asyncio.run(main())
Internally the context manager holds the socket open and runs a background task that
reads every inbound frame. Each command carries a generated id; the device echoes
it in its reply, and the background task hands the reply to the awaiting call — so
await device.volume(20) returns only once that command's ack comes back.
Discovery
ES300.discover() broadcasts on the LAN and returns a list of ready-to-use
ES300 objects (host, port, and name filled in):
speakers = await ES300.discover(seconds=3.0)
for speaker in speakers:
print(speaker) # "EDIFIER ES300 192.168.1.123:8080"
async with speakers[0] as device:
await device.volume(15)
Return values & errors
- Command methods (
volume,play,input_source,eq_custom, …) return the device's raw ack frame (aFrameDatadict). They raiseCommandFailedif the device acks with a non-successmessage, orEndOfStreamif the socket drops before the ack arrives. status()returns aStatus | None(Noneonly if the device stays silent).
Live callbacks
Register callbacks to react to unsolicited frames the device pushes while a connection is held open. Both are usable as decorators and hold the callback by weak reference (drop your reference and it stops firing):
async with ES300("192.168.1.123") as device:
@device.status_callback
async def on_status(status: Status):
print("state changed:", status.volume)
@device.heartbeat_callback
async def on_heartbeat(frame):
print("heartbeat")
await asyncio.sleep(60) # callbacks fire as frames arrive
Status
str(status) renders a human-readable dump (this is what the CLI status prints):
playing: - / - (status <PlayerStatus.PLAYING: 1>)
volume : 6 / 30
source : <Source.USB: 2>
effect : <LightEffect.STATIC: 1>
color : <LightColor.YELLOW: {'r': 255, 'g': 170, 'b': 60}>
eq : <EqPreset.CLASSIC: 0> gains=[20, 10, 0, -5, 5, 10]
battery: 40% (<BatteryStatus.DISCONNECTED: 2>)
timer : off
Fields: volume, max_volume, song, lyric, player_status, input_source,
light_effect, sound_index, eq_selected_index, eq_gains, battery, and
raw (the full status frame for anything not surfaced).
Enums
| Enum | Values | Notes |
|---|---|---|
Source |
BLUETOOTH=0, AUX=1, USB=2, AIRPLAY=3 |
input source |
EqPreset |
CLASSIC=0, MONITOR=1, GAME=2, VOCAL=3, CUSTOMIZED=4 |
CUSTOMIZED is the editable slot |
LightEffect |
STATIC=1, BREATHING=2, WATERFLOW=3 |
ambient LED effect |
LightColor |
YELLOW, WHITE |
value is the RGB dict; hardware only does these two |
BatteryStatus |
CONNECTED=1, DISCONNECTED=2 |
external power state (read-only) |
Source, EqPreset, and LightEffect are IntEnums, so methods also accept a
plain int. Setting eq_custom gains automatically selects EqPreset.CUSTOMIZED.
CLI usage
python -m edifier_es300 [--host IP] [--port N] COMMAND [ARGS]
--host— speaker IP. Omit it to auto-discover the first speaker on the LAN.--port— control-channel TCP port (default8080).
| Command | Args | Description |
|---|---|---|
discover |
— | list speakers on the LAN (name ip:port) |
status |
— | dump volume / source / light / EQ / battery |
volume |
LEVEL (0..30) |
set volume |
play / pause |
— | resume / pause playback |
play-pause |
— | toggle play/pause |
next-track / previous-track |
— | skip track |
shutdown |
— | power the speaker off (no remote power-on) |
timer-shutdown |
MINUTES (0..1440) |
sleep timer (0 = off; app presets 5/15/30/60/180) |
light |
on | off |
LED strip on/off |
light-brightness |
LEVEL (0..100) |
LED brightness |
light-effect |
static | breathing | waterflow |
LED effect |
light-color |
yellow | white |
LED color |
source |
bluetooth | aux | usb | airplay |
input source |
eq-preset |
classic | monitor | game | vocal | customized |
EQ preset |
eq |
G1..G6 |
exactly 6 custom gains, tenths of a dB (-30..30 = -3.0..+3.0 dB) |
Examples
python -m edifier_es300 discover
python -m edifier_es300 status # auto-discover, then dump state
python -m edifier_es300 --host 192.168.1.123 volume 22
python -m edifier_es300 source airplay
python -m edifier_es300 light-effect breathing
python -m edifier_es300 light-color yellow
python -m edifier_es300 eq-preset vocal
python -m edifier_es300 eq -- 10 5 0 0 0 -5 # use -- so negatives aren't read as options
Note: negative EQ gains look like CLI options, so prefix the gain list with
--.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file edifier_es300-1.0.2.tar.gz.
File metadata
- Download URL: edifier_es300-1.0.2.tar.gz
- Upload date:
- Size: 16.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.27 {"installer":{"name":"uv","version":"0.11.27","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
83dde084c4714f11f6668d58b265df3f061f0de5418317de97bb82bc8fd52799
|
|
| MD5 |
0c74ba5b6da658267ea1e121e040458e
|
|
| BLAKE2b-256 |
30a5d1592397519461ac7d5fcff4b16257acf44fee5136aacb364e71b433dc4f
|
File details
Details for the file edifier_es300-1.0.2-py3-none-any.whl.
File metadata
- Download URL: edifier_es300-1.0.2-py3-none-any.whl
- Upload date:
- Size: 14.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.27 {"installer":{"name":"uv","version":"0.11.27","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c5bf1b1677598350203f5cba4fcd6ccf1b0be51002a91e7384d92fd99b1d3405
|
|
| MD5 |
c021c125f704e1be31a3b60b2209da3f
|
|
| BLAKE2b-256 |
dbd8658b8d1040a9b706ee3178e0922122f277d3689203d33b9e554200c5cbcb
|