Thanks for a great solution 
I struggled to come up with a solution for interfacing from Home Assistant to micropython on XBee3 until I stumbled on this.
I used a similar configuration to control XBee3 micropython which activates Seeed SPDT relays over i2c.
The configuration looks like this - I have just shown one of the four relays:
input_boolean:
xbee_aaca92_i2c_channel17_relay1_state:
name: "XBee i2c17 Relay 1 State"
switch:
- platform: template
switches:
xbee_aaca92_i2c_channel17_relay1:
friendly_name: Pool pump relay
value_template: "{{ is_state('input_boolean.xbee_aaca92_i2c_channel17_relay1_state', 'on') }}"
turn_on:
- service: zha.issue_zigbee_cluster_command
data:
ieee: 00:13:a2:00:41:aa:ca:92
endpoint_id: 232
cluster_id: 17
cluster_type: in
command: 0
command_type: server
args: '{"relay": 1, "status": true}'
- service: input_boolean.turn_on
data:
entity_id: input_boolean.xbee_aaca92_i2c_channel17_relay1_state
turn_off:
- service: zha.issue_zigbee_cluster_command
data:
ieee: 00:13:a2:00:41:aa:ca:92
endpoint_id: 232
cluster_id: 17
cluster_type: in
command: 0
command_type: server
args: '{"relay": 1, "status": false}'
- service: input_boolean.turn_off
data:
entity_id: input_boolean.xbee_aaca92_i2c_channel17_relay1_state
The micropython on the XBee3 looks as follows (The XBee3 needs to be configured in API mode):
import time
import xbee
from machine import I2C
import ujson
import struct
i2c = I2C(1)
CMD_CHANNEL_CTRL = 0x10
relayState = 0x0
while True:
# Check if the XBee has any message in the queue.
received_msg = xbee.receive()
if received_msg:
# Get the sender's 64-bit address and payload from the received message.
sender = received_msg['sender_eui64']
payload = received_msg['payload']
try:
obj = ujson.loads(payload.decode())
if obj['relay'] < 1 | obj['relay'] > 8:
print("relay must be an integer from 1 - 8")
else:
if obj['status']:
relayState |= (1 << obj['relay'] - 1)
else:
relayState &= ~(1 << obj['relay'] - 1)
cmdBytes = struct.pack("BB", CMD_CHANNEL_CTRL, relayState)
print(relayState)
print(cmdBytes)
i2c.writeto(17, cmdBytes)
# Send back the same payload to the sender.
print("Sending back a response...\n")
# xbee.transmit(sender, payload)
except ValueError:
print("Failed to parse json for payload:")
print(payload)
else:
# Wait 100 ms before checking for data again.
time.sleep(0.2)
I would like to look at using more standard Zigbee protocol message to drive the micropython, but for now I am pleased with the progress.