Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow to use mac address for connection to coco #1

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 19 additions & 3 deletions nhc2_coco/coco.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import json
import logging
import os
import re

import paho.mqtt.client as mqtt

from .coco_ip_by_mac import CoCoIpByMac
from .coco_light import CoCoLight
from .coco_switch import CoCoSwitch
from .const import MQTT_PROTOCOL, MQTT_TRANSPORT, MQTT_TOPIC_PUBLIC_RSP, MQTT_TOPIC_SUFFIX_RSP, \
Expand All @@ -18,6 +20,7 @@

class CoCo:
def __init__(self, address, username, password, port=8883, ca_path=None, switches_as_lights=False):
self._address_is_mac = re.match("[0-9a-f]{2}([-:]?)[0-9a-f]{2}(\\1[0-9a-f]{2}){4}$", address.lower())
self._valid_switches = ['socket', 'switched-generic']
self._valid_lights = ['light', 'dimmer']
if ca_path is None:
Expand Down Expand Up @@ -124,6 +127,7 @@ def _on_message(client, userdata, message):

def _on_connect(client, userdata, flags, rc):
if rc == 0:
print("connetec %d" % rc)
_LOGGER.info('Connected!')
client.subscribe(self._profile_creation_id + MQTT_TOPIC_SUFFIX_RSP, qos=1)
client.subscribe(self._profile_creation_id + MQTT_TOPIC_PUBLIC_RSP, qos=1)
Expand All @@ -137,17 +141,29 @@ def _on_connect(client, userdata, flags, rc):
raise Exception('Unknown error')

def _on_disconnect(client, userdata, rc):
def update_ip(ip):
if self._client._host != ip:
self._client.connect_async(ip, self._port, keepalive=5, clean_start=True)
self._client.reconnect()

_LOGGER.warning('Disconnected')
if self._address_is_mac:
CoCoIpByMac(self._address, update_ip)
for uuid, device_callback in self._device_callbacks.items():
offline = {'Online': 'False', KEY_UUID: uuid}
device_callback[INTERNAL_KEY_CALLBACK](offline)

self._client.on_message = _on_message
self._client.on_connect = _on_connect
self._client.on_disconnect = _on_disconnect

self._client.connect_async(self._address, self._port)
self._client.loop_start()
if self._address_is_mac:
def connect_with_ip(ip):
self._client.connect_async(ip, self._port, keepalive=5, clean_start=True)
self._client.loop_start()
CoCoIpByMac(self._address, connect_with_ip)
else:
self._client.connect_async(self._address, self._port, clean_start=True)
self._client.loop_start()

def disconnect(self):
self._client.loop_stop()
Expand Down
1 change: 0 additions & 1 deletion nhc2_coco/coco_discover.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ class CoCoDiscover:
"""

def __init__(self, on_discover, on_done):
self._get_broadcast_ips()

self._thread = threading.Thread(target=self._scan_for_nhc)
self._on_discover = on_discover
Expand Down
63 changes: 63 additions & 0 deletions nhc2_coco/coco_ip_by_mac.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import binascii
import select
import threading
import socket
import netifaces
from getmac import get_mac_address


class CoCoIpByMac:
"""CoCoDiscover will help you discover NHC2.
It will also tell you about NHC1, but the result will differ.

You create CoCoDiscover, passing along a callback an the time you max want to wait.
By default we wait 3 seconds.

For every result with matching header the callback is called,
with the address, mac-address and a boolean if it's a NHC2.
"""

def __init__(self, mac_to_match, on_found_ip):
self._thread = threading.Thread(target=self._scan_for_nhc)
self._on_found_ip = on_found_ip
self._mac_to_match = mac_to_match
self._thread.start()

def _get_broadcast_ips(self):
interfaces = netifaces.interfaces()
return filter(lambda x: x,
map(lambda x: netifaces.ifaddresses(x).get(netifaces.AF_INET)[0].get('broadcast') if (
(netifaces.AF_INET in netifaces.ifaddresses(x))
and ('broadcast' in netifaces.ifaddresses(x).get(netifaces.AF_INET)[0])

) else None, interfaces))

def _scan_for_nhc(self):
server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
server.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
""" We search for all broadcast ip4s, so that we don't only search the main interface """
self._broadcast_ping(server)
searching = True

while searching:
ready = select.select([server], [], [], 1)
if ready[0]:
data, addr = server.recvfrom(4096)
raw_ip = list(map(lambda x: data[x], (6, 7, 8, 9)))
raw_mac = ''.join('{:02X}'.format(a) for a in data[2:6])
ip = "{}.{}.{}.{}".format(*raw_ip)
mac = raw_mac[-4:]
mac_to_match = self._mac_to_match.replace(':', '').upper()[-4:]
if mac_to_match == mac and callable(self._on_found_ip):
print("found ip %s" % ip)
self._on_found_ip(ip)
searching = False
self._broadcast_ping(server)
server.close()
print('Done searching')

def _broadcast_ping(self, server):
broadcast_ips = self._get_broadcast_ips()
for broadcast_ip in broadcast_ips:
server.sendto(bytes([0x44]), (broadcast_ip, 10000))
server.setblocking(0)
11 changes: 10 additions & 1 deletion nhc2_coco/coco_login_validation.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import asyncio
import os
import re

import paho.mqtt.client as mqtt

from nhc2_coco.coco_ip_by_mac import CoCoIpByMac
from nhc2_coco.const import MQTT_PROTOCOL, MQTT_TRANSPORT

loop = asyncio.get_event_loop()
Expand All @@ -13,6 +15,8 @@ class CoCoLoginValidation:
"""

def __init__(self, address, username, password, port=8883, ca_path=None):

self._address_is_mac = re.match("[0-9a-f]{2}([-:]?)[0-9a-f]{2}(\\1[0-9a-f]{2}){4}$", address.lower())
self._address = address
self._username = username
self._password = password
Expand Down Expand Up @@ -47,7 +51,12 @@ def on_connect(x, xx, xxx, reason_code):

client.on_connect = on_connect
client.loop_start()
client.connect_async(self._address, self._port, keepalive=timeout)
if self._address_is_mac:
def connect_callback(ip):
client.connect_async(ip, self._port, keepalive=timeout)
CoCoIpByMac(self._address, connect_callback)
else:
client.connect_async(self._address, self._port, keepalive=timeout)

try:
await asyncio.wait_for(done_testing.wait(), timeout + 2)
Expand Down