209 lines
8.4 KiB
YAML
209 lines
8.4 KiB
YAML
---
|
|
# Phase 1: Query Omada client table and find the Philips Hue hub by MAC OUI.
|
|
# Phase 2: Probe the discovered IP via the Hue Bridge local API.
|
|
#
|
|
# Philips Hue / Signify known MAC OUI prefixes:
|
|
# 00:17:88 — classic Hue Bridge and bulbs
|
|
# EC:B5:FA — newer Hue Bridge v2 and Hue products
|
|
#
|
|
# Usage:
|
|
# ansible-playbook playbooks/network/omada_find_hue_hub.yml --ask-vault-pass
|
|
|
|
- name: Find and probe Philips Hue hub via Omada client table
|
|
hosts: localhost
|
|
connection: local
|
|
gather_facts: false
|
|
|
|
vars_files:
|
|
- "../../group_vars/all.yml"
|
|
- "../../group_vars/vault/all.yml"
|
|
|
|
vars:
|
|
omada_validate_certs: false
|
|
omada_page_size: 200
|
|
# Signify / Philips Hue MAC OUI prefixes (lowercase, colon-separated)
|
|
hue_mac_ouis:
|
|
- "00:17:88"
|
|
- "ec:b5:fa"
|
|
hue_probe_validate_certs: false
|
|
|
|
tasks:
|
|
# -----------------------------------------------------------------------
|
|
# PHASE 1 — Omada token
|
|
# -----------------------------------------------------------------------
|
|
- name: Request Omada access token (client credentials)
|
|
ansible.builtin.uri:
|
|
url: "{{ omada_base_url }}/openapi/authorize/token?grant_type=client_credentials"
|
|
method: POST
|
|
validate_certs: "{{ omada_validate_certs }}"
|
|
headers:
|
|
Content-Type: application/json
|
|
body_format: json
|
|
body:
|
|
omadacId: "{{ omada_id }}"
|
|
client_id: "{{ omada_client_id }}"
|
|
client_secret: "{{ omada_client_secret }}"
|
|
return_content: true
|
|
status_code: 200
|
|
register: omada_token_response
|
|
no_log: true
|
|
failed_when:
|
|
- omada_token_response.json is not defined
|
|
- omada_token_response.json.errorCode | default(-1) != 0
|
|
|
|
- name: Save access token
|
|
ansible.builtin.set_fact:
|
|
omada_access_token: "{{ omada_token_response.json.result.accessToken }}"
|
|
no_log: true
|
|
|
|
# -----------------------------------------------------------------------
|
|
# PHASE 1 — Get site list
|
|
# -----------------------------------------------------------------------
|
|
- name: Query Omada sites
|
|
ansible.builtin.uri:
|
|
url: "{{ omada_base_url }}/openapi/v1/{{ omada_id }}/sites?page=1&pageSize={{ omada_page_size }}"
|
|
method: GET
|
|
validate_certs: "{{ omada_validate_certs }}"
|
|
headers:
|
|
Content-Type: application/json
|
|
Authorization: "AccessToken={{ omada_access_token }}"
|
|
return_content: true
|
|
status_code: 200
|
|
register: omada_sites_response
|
|
no_log: true
|
|
failed_when:
|
|
- omada_sites_response.json is not defined
|
|
- omada_sites_response.json.errorCode | default(-1) != 0
|
|
|
|
- name: Save site list
|
|
ansible.builtin.set_fact:
|
|
omada_sites: "{{ omada_sites_response.json.result.data | default([]) }}"
|
|
|
|
# -----------------------------------------------------------------------
|
|
# PHASE 1 — Query clients per site
|
|
# -----------------------------------------------------------------------
|
|
- name: Query all clients per site
|
|
ansible.builtin.uri:
|
|
url: "{{ omada_base_url }}/openapi/v1/{{ omada_id }}/sites/{{ item.siteId }}/clients?page=1&pageSize={{ omada_page_size }}"
|
|
method: GET
|
|
validate_certs: "{{ omada_validate_certs }}"
|
|
headers:
|
|
Content-Type: application/json
|
|
Authorization: "AccessToken={{ omada_access_token }}"
|
|
return_content: true
|
|
status_code: 200
|
|
loop: "{{ omada_sites }}"
|
|
loop_control:
|
|
label: "{{ item.name | default(item.siteId) }}"
|
|
register: omada_clients_by_site
|
|
no_log: true
|
|
failed_when: false
|
|
|
|
# -----------------------------------------------------------------------
|
|
# PHASE 1 — Filter for Hue hub by OUI
|
|
# -----------------------------------------------------------------------
|
|
- name: Collect all clients from all sites into flat list
|
|
ansible.builtin.set_fact:
|
|
all_clients: "{{ omada_clients_by_site.results
|
|
| selectattr('json', 'defined')
|
|
| selectattr('json.errorCode', 'equalto', 0)
|
|
| map(attribute='json.result.data')
|
|
| flatten }}"
|
|
|
|
- name: Filter clients by Hue MAC OUI prefixes
|
|
ansible.builtin.set_fact:
|
|
hue_candidates: "{{ all_clients | selectattr('mac', 'defined')
|
|
| selectattr('ip', 'defined')
|
|
| selectattr('mac', 'search', hue_mac_ouis | join('|'), ignorecase=True)
|
|
| list }}"
|
|
|
|
- name: Report Omada client search results
|
|
ansible.builtin.debug:
|
|
msg:
|
|
- "Total clients scanned: {{ all_clients | length }}"
|
|
- "Hue hub candidates found: {{ hue_candidates | length }}"
|
|
- "Candidates: {{ hue_candidates | map(attribute='mac') | list }}"
|
|
|
|
- name: Abort with clear message if no Hue hub found
|
|
ansible.builtin.fail:
|
|
msg: >
|
|
No Philips Hue hub found in Omada client table.
|
|
Verify the hub is powered on and connected to a monitored VLAN.
|
|
Expected MAC OUI prefixes: {{ hue_mac_ouis | join(', ') }}.
|
|
when: hue_candidates | length == 0
|
|
|
|
- name: Save first Hue hub candidate IP and MAC
|
|
ansible.builtin.set_fact:
|
|
hue_ip: "{{ hue_candidates[0].ip }}"
|
|
hue_mac: "{{ hue_candidates[0].mac }}"
|
|
hue_hostname: "{{ hue_candidates[0].hostname | default('unknown') }}"
|
|
|
|
- name: Report discovered Hue hub
|
|
ansible.builtin.debug:
|
|
msg:
|
|
- "Hue hub MAC : {{ hue_mac }}"
|
|
- "Hue hub IP : {{ hue_ip }}"
|
|
- "Hue hub hostname: {{ hue_hostname }}"
|
|
|
|
# -----------------------------------------------------------------------
|
|
# PHASE 2 — Probe Hue Bridge local API
|
|
# -----------------------------------------------------------------------
|
|
- name: Probe Hue Bridge local discovery endpoint
|
|
ansible.builtin.uri:
|
|
url: "http://{{ hue_ip }}/api/config"
|
|
method: GET
|
|
validate_certs: "{{ hue_probe_validate_certs }}"
|
|
return_content: true
|
|
timeout: 5
|
|
status_code: 200
|
|
register: hue_config_response
|
|
failed_when: false
|
|
|
|
- name: Probe Hue Bridge HTTPS clip v2 endpoint (newer firmware)
|
|
ansible.builtin.uri:
|
|
url: "https://{{ hue_ip }}/clip/v2/resource/bridge"
|
|
method: GET
|
|
validate_certs: "{{ hue_probe_validate_certs }}"
|
|
return_content: true
|
|
timeout: 5
|
|
status_code: [200, 401, 403]
|
|
register: hue_clipv2_response
|
|
failed_when: false
|
|
|
|
- name: Build Hue adoption readiness summary
|
|
ansible.builtin.set_fact:
|
|
hue_adoption_summary:
|
|
mac: "{{ hue_mac }}"
|
|
ip: "{{ hue_ip }}"
|
|
hostname: "{{ hue_hostname }}"
|
|
local_api_v1:
|
|
reachable: "{{ hue_config_response.status | default('n/a') == 200 }}"
|
|
http_status: "{{ hue_config_response.status | default('n/a') }}"
|
|
bridge_id: "{{ hue_config_response.json.bridgeid | default('n/a') }}"
|
|
model_id: "{{ hue_config_response.json.modelid | default('n/a') }}"
|
|
sw_version: "{{ hue_config_response.json.swversion | default('n/a') }}"
|
|
api_version: "{{ hue_config_response.json.apiversion | default('n/a') }}"
|
|
name: "{{ hue_config_response.json.name | default('n/a') }}"
|
|
local_api_v2_clip:
|
|
reachable: "{{ hue_clipv2_response.status | default('n/a') in [200, 401, 403] }}"
|
|
http_status: "{{ hue_clipv2_response.status | default('n/a') }}"
|
|
note: >-
|
|
{{
|
|
'CLIP v2 available (needs app_key header for full access)' if hue_clipv2_response.status | default(0) in [401, 403]
|
|
else 'CLIP v2 accessible' if hue_clipv2_response.status | default(0) == 200
|
|
else 'CLIP v2 not reachable'
|
|
}}
|
|
ansible_adoption:
|
|
method: "ansible.builtin.uri (REST API — no SSH required)"
|
|
auth_required: "Hue app_key token (generate by pressing bridge button + POST /api)"
|
|
next_step: >-
|
|
{{
|
|
'Bridge is reachable. Press the physical button on the bridge, then run omada_adopt_hue_hub.yml to generate an app_key.'
|
|
if (hue_config_response.status | default(0) == 200 or hue_clipv2_response.status | default(0) in [200, 401, 403])
|
|
else 'Bridge API not responding. Check firewall rules for VLAN ' ~ hue_hostname ~ ' → Ansible control node.'
|
|
}}
|
|
|
|
- name: Print Hue adoption readiness summary
|
|
ansible.builtin.debug:
|
|
var: hue_adoption_summary
|