Skip to content

Commit

Permalink
Merge pull request #4 from JohNan/johnan/server-info
Browse files Browse the repository at this point in the history
Fetch server version from the hub + code refactor
  • Loading branch information
JohNan authored Oct 20, 2023
2 parents ca3f42e + a5065cf commit e103a12
Show file tree
Hide file tree
Showing 10 changed files with 188 additions and 140 deletions.
14 changes: 6 additions & 8 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
logging.basicConfig(level=logging.DEBUG)

CLIENT_READY_TIMEOUT = 10.0
HOST = ('192.168.1.249', 8124)
HOST = ('192.168.1.64', 8124)


def event_callback(button: FlicButton, event: Event):
Expand All @@ -27,15 +27,16 @@ def command_callback(cmd: Command):
async def start():
client_ready = asyncio.Event()

def client_connected():
async def client_connected():
print("Connected!")
client_ready.set()
await client.get_server_info()

def client_disconnected():
async def client_disconnected():
print("Disconnected!")

client.on_connected = client_connected
client.on_disconnected = client_disconnected
client.async_on_connected = client_connected
client.async_on_disconnected = client_disconnected

task = asyncio.create_task(client.async_connect())

Expand All @@ -56,9 +57,6 @@ def client_disconnected():
if network.has_ethernet():
print(f"Ethernet IP: {network.ethernet.ip} - Connected: {network.ethernet.connected}")

# for button in buttons:
# print(f"Button name: {button.name} - Battery: {await client.get_battery_status(button.bdaddr)}")


if __name__ == '__main__':
loop = asyncio.new_event_loop()
Expand Down
2 changes: 2 additions & 0 deletions pyflichub/button.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from dataclasses import dataclass
from datetime import datetime


@dataclass
class FlicButton():
def __init__(self, bdaddr: str, serial_number: str, color: str, name: str, active_disconnect: bool, connected: bool,
ready: bool, battery_status: int, uuid: str, flic_version: int, firmware_version: int, key: str,
Expand Down
112 changes: 64 additions & 48 deletions pyflichub/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@
import async_timeout
import humps

from pyflichub.button import FlicButton
from pyflichub.command import Command
from pyflichub.event import Event
from pyflichub.button import FlicButton
from pyflichub.flichub import FlicHubInfo
from pyflichub.server_command import ServerCommand
from pyflichub.server_info import ServerInfo

_LOGGER = logging.getLogger(__name__)

Expand All @@ -35,7 +37,7 @@ class FlicHubTcpClient(asyncio.Protocol):
network: FlicHubInfo

def __init__(self, ip, port, loop, timeout=1.0, reconnect_timeout=10.0, event_callback=None, command_callback=None):
self._data_ready: Union[asyncio.Event, None] = None
self._data_ready: {str: Union[asyncio.Event, None]} = {}
self._transport = None
self._command_callback = command_callback
self._event_callback = event_callback
Expand All @@ -45,16 +47,16 @@ def __init__(self, ip, port, loop, timeout=1.0, reconnect_timeout=10.0, event_ca
self._tcp_disconnect_timer = time.time()
self._reconnect_timeout = reconnect_timeout
self._timeout = timeout
self._data = None
self.on_connected = None
self.on_disconnected = None
self._data: dict = {}
self._connecting = False
self._forced_disconnect = False
self.async_on_connected = None
self.async_on_disconnected = None

async def async_connect(self):
self._connecting = True
async def _async_connect(self):
"""Connect to the socket."""
try:
while self._connecting:
while self._connecting and not self._forced_disconnect:
_LOGGER.info("Trying to connect to %s", self._server_address)
try:
await asyncio.wait_for(
Expand All @@ -79,42 +81,61 @@ async def async_connect(self):
def disconnect(self):
_LOGGER.info("Disconnected")
self._connecting = False
self._forced_disconnect = True

if self._transport is not None:
self._transport.close()

if self.on_disconnected is not None:
self.on_disconnected()
if self.async_on_disconnected is not None:
self._loop.create_task(self.async_on_disconnected())

async def async_connect(self):
self._connecting = True
self._forced_disconnect = False
await self._async_connect()

def send_command(self, cmd: ServerCommand):
return self._async_send_command(cmd)

async def connect(self):
await self._loop.create_connection(lambda: self, *self._server_address)
async def get_buttons(self) -> [FlicButton]:
command: Command = await self._async_send_command_and_wait_for_data(ServerCommand.BUTTONS)
return command.data if command is not None else []

async def get_buttons(self):
return await self._async_send_command('buttons')
async def get_server_info(self) -> ServerInfo | None:
command: Command = await self._async_send_command_and_wait_for_data(ServerCommand.SERVER_INFO)
return command.data

async def get_hubinfo(self):
return await self._async_send_command('network')
async def get_hubinfo(self) -> FlicHubInfo | None:
command: Command = await self._async_send_command_and_wait_for_data(ServerCommand.HUB_INFO)
return command.data

async def get_battery_status(self, bdaddr: str):
return await self._async_send_command(f'battery;{bdaddr}')
def _async_send_command(self, cmd: ServerCommand):
if self._transport is not None:
self._transport.write(f"{cmd}\n".encode())
else:
_LOGGER.error("Connections seems to be closed.")

async def _async_send_command(self, cmd: str):
async def _async_send_command_and_wait_for_data(self, cmd: ServerCommand) -> Command | None:
if self._transport is not None:
self._data_ready = asyncio.Event()
self._transport.write(cmd.encode())
with async_timeout.timeout(DATA_READY_TIMEOUT):
await self._data_ready.wait()
self._data_ready = None
return self._data
self._data_ready[cmd] = asyncio.Event()
self._transport.write(f"{cmd}\n".encode())
try:
with async_timeout.timeout(DATA_READY_TIMEOUT):
await self._data_ready[cmd].wait()
self._data_ready[cmd] = None
return self._data[cmd]
except asyncio.TimeoutError:
_LOGGER.warning(f"Waited for '{cmd}' data for {DATA_READY_TIMEOUT} secs.")
return None
else:
_LOGGER.error("Connections seems to be closed.")

def connection_made(self, transport):
self._transport = transport
_LOGGER.debug("Connection made")

if self.on_connected is not None:
self.on_connected()
if self.async_on_connected is not None:
self._loop.create_task(self.async_on_connected())

def data_received(self, data):
decoded_data = data.decode()
Expand All @@ -130,33 +151,27 @@ def data_received(self, data):
if 'command' in msg:
self._handle_command(Command(**msg))
except Exception as e:
_LOGGER.warning(e, exc_info = True)
_LOGGER.warning(e, exc_info=True)
_LOGGER.warning('Unable to decode received data')


def connection_lost(self, exc):
_LOGGER.info("Connection lost")
self._connecting = True
self._transport = None
self._loop.create_task(self.async_connect())
self._loop.create_task(self._async_connect())

def _handle_command(self, cmd: Command):
command_data = cmd.data
if cmd.command == 'buttons':
if cmd.command == ServerCommand.SERVER_INFO:
cmd.data = ServerInfo(**humps.decamelize(cmd.data))
elif cmd.command == ServerCommand.BUTTONS:
self.buttons = [FlicButton(**button) for button in humps.decamelize(cmd.data)]
command_data = cmd.data = self.buttons
for button in self.buttons:
_LOGGER.debug(f"Button name: {button.name} - Connected: {button.connected}")
if cmd.command == 'network':
self.network = FlicHubInfo(**humps.decamelize(cmd.data))
command_data = cmd.data = self.network
if self.network.has_wifi():
_LOGGER.debug(f"Wifi State: {self.network.wifi.state} - Connected: {self.network.wifi.connected}")
if self.network.has_ethernet():
_LOGGER.debug(f"Ethernet IP: {self.network.ethernet.ip} - Connected: {self.network.ethernet.connected}")

if self._data_ready is not None:
self._data_ready.set()
self._data = command_data
cmd.data = self.buttons
elif cmd.command == ServerCommand.HUB_INFO:
cmd.data = FlicHubInfo(**humps.decamelize(cmd.data))

if self._data_ready[cmd.command] is not None and cmd.data is not None:
self._data_ready[cmd.command].set()
self._data[cmd.command] = cmd

if self._command_callback is not None:
self._command_callback(cmd)
Expand Down Expand Up @@ -193,6 +208,7 @@ def _check_connection(self):
self._transport.write(msg.encode())
self._tcp_check_timer = time.time()


class _JSONDecoder(json.JSONDecoder):
def __init__(self, *args, **kwargs):
json.JSONDecoder.__init__(
Expand All @@ -202,7 +218,7 @@ def object_hook(self, obj):
ret = {}
for key, value in obj.items():
if key in {'batteryTimestamp'}:
ret[key] = datetime.fromtimestamp(value/1000)
ret[key] = datetime.fromtimestamp(value / 1000)
else:
ret[key] = value
return ret
return ret
11 changes: 9 additions & 2 deletions pyflichub/command.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
from dataclasses import dataclass
from typing import Any

from pyflichub.server_command import ServerCommand


@dataclass
class Command:
def __init__(self, command: str, data: str):
self.data = data
def __init__(self, command: ServerCommand, data: Any):
self.command = command
self.data = data
8 changes: 6 additions & 2 deletions pyflichub/event.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
from dataclasses import dataclass


@dataclass
class Event:
def __init__(self, event: str, button: str, action: str):
self.action = action
self.button = button
self.event = event
self.button = button
self.action = action
8 changes: 6 additions & 2 deletions pyflichub/flichub.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from typing import List
from dataclasses import dataclass


@dataclass
class WifiInfo:
state: str
ssid: str
Expand All @@ -11,19 +12,22 @@ def __init__(self, connected, ip, mac):
self.mac = mac


@dataclass
class EthernetInfo:
def __init__(self, connected, ip, mac):
self.connected = connected
self.ip = ip
self.mac = mac


@dataclass
class DhcpInfo:
def __init__(self, wifi=None, ethernet=None):
self.wifi = WifiInfo(**wifi) if wifi else None
self.ethernet = EthernetInfo(**ethernet) if ethernet else None


@dataclass
class _WifiState:
def __init__(self, state, ssid):
self.state = state
Expand All @@ -36,6 +40,7 @@ def _decode_ssid(ssid):
return ''.join(chr(byte) for byte in ssid)


@dataclass
class FlicHubInfo:
def __init__(self, dhcp, wifi_state=None):
self._dhcp = DhcpInfo(**dhcp)
Expand All @@ -55,4 +60,3 @@ def wifi(self) -> WifiInfo:
@property
def ethernet(self) -> EthernetInfo:
return self._dhcp.ethernet

7 changes: 7 additions & 0 deletions pyflichub/server_command.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from enum import StrEnum


class ServerCommand(StrEnum):
BUTTONS = "buttons"
SERVER_INFO = "server"
HUB_INFO = "network"
7 changes: 7 additions & 0 deletions pyflichub/server_info.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from dataclasses import dataclass


@dataclass
class ServerInfo:
def __init__(self, version: str):
self.version = version
1 change: 1 addition & 0 deletions requirements_test.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pytest==7.4.2
Loading

0 comments on commit e103a12

Please sign in to comment.