mirror of
https://github.com/arendst/Tasmota.git
synced 2025-07-24 11:16:34 +00:00
Zigbee add visual map of network
This commit is contained in:
parent
d418d7bdd8
commit
1558f0c731
@ -16,6 +16,7 @@ All notable changes to this project will be documented in this file.
|
||||
- Zigbee better support for Tuya Protocol (#10074)
|
||||
- Support for SPI connected MFRC522 13.56MHz rfid card reader (#9916)
|
||||
- Letsencrypt R3 in addition to X3 CA (#10086)
|
||||
- Zigbee add visual map of network
|
||||
|
||||
### Breaking Changed
|
||||
- KNX DPT9 (16-bit float) to DPT14 (32-bit float) by Adrian Scillato (#9811, #9888)
|
||||
|
@ -1661,6 +1661,18 @@ bool HandleRootStatusRefresh(void)
|
||||
ExecuteWebCommand(svalue, SRC_WEBGUI);
|
||||
}
|
||||
#endif // USE_SONOFF_RF
|
||||
#ifdef USE_ZIGBEE
|
||||
WebGetArg("zbj", tmp, sizeof(tmp));
|
||||
if (strlen(tmp)) {
|
||||
snprintf_P(svalue, sizeof(svalue), PSTR("ZbPermitJoin"));
|
||||
ExecuteWebCommand(svalue, SRC_WEBGUI);
|
||||
}
|
||||
WebGetArg("zbr", tmp, sizeof(tmp));
|
||||
if (strlen(tmp)) {
|
||||
snprintf_P(svalue, sizeof(svalue), PSTR("ZbMap"));
|
||||
ExecuteWebCommand(svalue, SRC_WEBGUI);
|
||||
}
|
||||
#endif // USE_ZIGBEE
|
||||
WSContentBegin(200, CT_HTML);
|
||||
WSContentSend_P(PSTR("{t}"));
|
||||
XsnsCall(FUNC_WEB_SENSOR);
|
||||
|
@ -90,6 +90,10 @@ public:
|
||||
bool recv_until = false; // ignore all messages until the received frame fully matches
|
||||
bool eeprom_present = false; // is the ZBBridge EEPROM present?
|
||||
bool eeprom_ready = false; // is the ZBBridge EEPROM formatted and ready?
|
||||
// Zigbee mapping
|
||||
bool mapping_in_progress = false; // is there a mapping in progress
|
||||
bool mapping_ready = false; // do we have mapping information ready
|
||||
uint32_t mapping_end_time = 0;
|
||||
|
||||
uint8_t on_error_goto = ZIGBEE_LABEL_ABORT; // on error goto label, 99 default to abort
|
||||
uint8_t on_timeout_goto = ZIGBEE_LABEL_ABORT; // on timeout goto label, 99 default to abort
|
||||
|
@ -717,6 +717,7 @@ public:
|
||||
// sequence number for Zigbee frames
|
||||
uint16_t shortaddr; // unique key if not null, or unspecified if null
|
||||
uint8_t seqNumber;
|
||||
bool is_router; // flag used by ZbMap to distibguish routers from end-devices
|
||||
bool hidden;
|
||||
bool reachable;
|
||||
// Light information for Hue integration integration, last known values
|
||||
@ -742,6 +743,7 @@ public:
|
||||
attr_list(),
|
||||
shortaddr(_shortaddr),
|
||||
seqNumber(0),
|
||||
is_router(false),
|
||||
hidden(false),
|
||||
reachable(false),
|
||||
data(),
|
||||
@ -768,6 +770,10 @@ public:
|
||||
inline bool getReachable(void) const { return reachable; }
|
||||
inline bool getPower(uint8_t ep =0) const;
|
||||
|
||||
inline bool isRouter(void) const { return is_router; }
|
||||
inline bool isCoordinator(void) const { return 0x0000 == shortaddr; }
|
||||
inline void setRouter(bool router) { is_router = router; }
|
||||
|
||||
inline void setLQI(uint8_t _lqi) { lqi = _lqi; }
|
||||
inline void setBatteryPercent(uint8_t bp) { batterypercent = bp; }
|
||||
|
||||
|
180
tasmota/xdrv_23_zigbee_7_5_map.ino
Normal file
180
tasmota/xdrv_23_zigbee_7_5_map.ino
Normal file
@ -0,0 +1,180 @@
|
||||
/*
|
||||
xdrv_23_zigbee_7_5_map.ino - zigbee support for Tasmota
|
||||
|
||||
Copyright (C) 2020 Theo Arends and Stephan Hadinger
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifdef USE_ZIGBEE
|
||||
|
||||
class Z_Mapper_Edge {
|
||||
public:
|
||||
|
||||
// enum Edge_Type : uint8_t {
|
||||
// Unknown = 0x00,
|
||||
// Parent = 0x01, // node_1 is parent of node_2
|
||||
// Child = 0x02, // node_1 is child of node_2
|
||||
// Sibling = 0x03, // both nodes are siblings
|
||||
// };
|
||||
|
||||
Z_Mapper_Edge(void) :
|
||||
node_1(BAD_SHORTADDR),
|
||||
node_2(BAD_SHORTADDR),
|
||||
lqi(0x00)
|
||||
// edge_type(Unknown)
|
||||
{}
|
||||
|
||||
// Z_Mapper_Edge(uint16_t node_a, uint16_t node_b, uint8_t _lqi, Edge_Type _type) :
|
||||
Z_Mapper_Edge(uint16_t node_a, uint16_t node_b, uint8_t _lqi) :
|
||||
node_1(BAD_SHORTADDR),
|
||||
node_2(BAD_SHORTADDR),
|
||||
lqi(_lqi)
|
||||
// edge_type(_type)
|
||||
{
|
||||
setEdges(node_a, node_b);
|
||||
}
|
||||
|
||||
void setEdges(uint16_t node_a, uint16_t node_b);
|
||||
|
||||
bool sameEdge(const Z_Mapper_Edge & edge2) const;
|
||||
|
||||
// Edge_Type Z_Mapper_Edge_Type_Reverse(Edge_Type _type) {
|
||||
// switch (_type) {
|
||||
// case Parent: return Child;
|
||||
// case Child: return Parent;
|
||||
// default: return _type;
|
||||
// }
|
||||
// }
|
||||
|
||||
// we always orientate the edge from with shortaddresses in ascending order
|
||||
// invariant: node_1 < node_2
|
||||
uint16_t node_1;
|
||||
uint16_t node_2;
|
||||
|
||||
uint8_t lqi;
|
||||
// Edge_Type edge_type;
|
||||
};
|
||||
|
||||
//
|
||||
// Handles the mapping of Zigbee devices
|
||||
//
|
||||
class Z_Mapper {
|
||||
public:
|
||||
Z_Mapper(void) :
|
||||
edges()
|
||||
{}
|
||||
|
||||
void reset(void) { edges.reset(); }
|
||||
|
||||
Z_Mapper_Edge & findEdge(const Z_Mapper_Edge & edge2);
|
||||
bool addEdge(const Z_Mapper_Edge & edge2);
|
||||
|
||||
void dumpInternals(void) const;
|
||||
|
||||
LList<Z_Mapper_Edge> edges;
|
||||
|
||||
};
|
||||
|
||||
// global
|
||||
Z_Mapper zigbee_mapper;
|
||||
|
||||
/*********************************************************************************************\
|
||||
* Implementation for Z_Mapper_Edge
|
||||
\*********************************************************************************************/
|
||||
void Z_Mapper_Edge::setEdges(uint16_t node_a, uint16_t node_b) {
|
||||
if (node_a < node_b) {
|
||||
node_1 = node_a;
|
||||
node_2 = node_b;
|
||||
} else if (node_a > node_b) {
|
||||
node_1 = node_b;
|
||||
node_2 = node_a;
|
||||
} else {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
|
||||
bool Z_Mapper_Edge::sameEdge(const Z_Mapper_Edge & edge2) const {
|
||||
return (node_1 == edge2.node_1) && (node_2 == edge2.node_2);
|
||||
}
|
||||
|
||||
/*********************************************************************************************\
|
||||
* Implementation for Z_Mapper
|
||||
\*********************************************************************************************/
|
||||
Z_Mapper_Edge & Z_Mapper::findEdge(const Z_Mapper_Edge & edge2) {
|
||||
if ((edge2.node_1 == BAD_SHORTADDR) || (edge2.node_2 == BAD_SHORTADDR)) { return *(Z_Mapper_Edge*)nullptr; }
|
||||
for (auto & edge : edges) {
|
||||
if (edge2.sameEdge(edge)) {
|
||||
return edge;
|
||||
}
|
||||
}
|
||||
return *(Z_Mapper_Edge*)nullptr;
|
||||
}
|
||||
|
||||
bool Z_Mapper::addEdge(const Z_Mapper_Edge & edge2) {
|
||||
if ((edge2.node_1 == BAD_SHORTADDR) || (edge2.node_2 == BAD_SHORTADDR)) { return false; }
|
||||
Z_Mapper_Edge & cur_edge = findEdge(edge2);
|
||||
if (&cur_edge == nullptr) {
|
||||
edges.addHead(edge2);
|
||||
} else {
|
||||
//upgrade fields
|
||||
if (edge2.lqi > cur_edge.lqi) {
|
||||
cur_edge.lqi = edge2.lqi;
|
||||
}
|
||||
// if (cur_edge.edge_type == Z_Mapper_Edge::Unknown) {
|
||||
// cur_edge.edge_type = edge2.edge_type;
|
||||
// } else if ((edge2.edge_type == Z_Mapper_Edge::Parent) || (edge2.edge_type == Z_Mapper_Edge::Child)) {
|
||||
// // Parent or Child always has priority over Sibling
|
||||
// cur_edge.edge_type = edge2.edge_type;
|
||||
// }
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void Z_Mapper::dumpInternals(void) const {
|
||||
WSContentSend_P(PSTR("nodes:[" "{id:\"0x0000\",label:\"Coordinator\",group:\"o\",title:\"0x0000\"}"));
|
||||
for (const auto & device : zigbee_devices.getDevices()) {
|
||||
WSContentSend_P(PSTR(",{id:\"0x%04X\",group:\"%c\",title:\"0x%04X\",label:\""),
|
||||
device.shortaddr, device.isRouter() ? 'r' : 'e', device.shortaddr);
|
||||
|
||||
const char *fname = device.friendlyName;
|
||||
if (fname != nullptr) {
|
||||
WSContentSend_P(PSTR("%s"), fname);
|
||||
} else {
|
||||
WSContentSend_P(PSTR("0x%04X"), device.shortaddr);
|
||||
}
|
||||
WSContentSend_P("\"}");
|
||||
}
|
||||
WSContentSend_P(PSTR("],"));
|
||||
|
||||
WSContentSend_P(PSTR("edges:["));
|
||||
for (auto & edge : edges) {
|
||||
uint32_t lqi_color = 0x000;
|
||||
// if (edge.lqi >= 192) {
|
||||
// lqi_color = 0x364;
|
||||
// } else if (edge.lqi >= 128) {
|
||||
// lqi_color = 0x346;
|
||||
// } else if (edge.lqi > 0) {
|
||||
// lqi_color = 0xd56;
|
||||
// }
|
||||
char hex[8];
|
||||
snprintf(hex, sizeof(hex), PSTR("%d"), edge.lqi);
|
||||
|
||||
WSContentSend_P("{from:\"0x%04X\",to:\"0x%04X\",label:\"%s\",color:\"#%03X\"},",
|
||||
edge.node_1, edge.node_2, (edge.lqi > 0) ? hex : "", lqi_color);
|
||||
}
|
||||
WSContentSend_P(PSTR("],"));
|
||||
}
|
||||
|
||||
#endif // USE_ZIGBEE
|
@ -229,7 +229,7 @@ void Z_Send_State_or_Map(uint16_t shortaddr, uint8_t index, uint16_t zdo_cmd) {
|
||||
// This callback is registered to send ZbMap(s) to each device one at a time
|
||||
void Z_Map(uint16_t shortaddr, uint16_t groupaddr, uint16_t cluster, uint8_t endpoint, uint32_t value) {
|
||||
if (BAD_SHORTADDR != shortaddr) {
|
||||
AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "sending `ZnMap 0x%04X`"), shortaddr);
|
||||
AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "sending `ZbMap 0x%04X`"), shortaddr);
|
||||
#ifdef USE_ZIGBEE_ZNP
|
||||
Z_Send_State_or_Map(shortaddr, value, ZDO_MGMT_LQI_REQ);
|
||||
#endif // USE_ZIGBEE_ZNP
|
||||
@ -238,6 +238,8 @@ void Z_Map(uint16_t shortaddr, uint16_t groupaddr, uint16_t cluster, uint8_t end
|
||||
#endif // USE_ZIGBEE_EZSP
|
||||
} else {
|
||||
AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "ZbMap done"));
|
||||
zigbee.mapping_in_progress = false;
|
||||
zigbee.mapping_ready = true;
|
||||
}
|
||||
}
|
||||
/*********************************************************************************************\
|
||||
@ -1110,6 +1112,27 @@ int32_t Z_Mgmt_Lqi_Bind_Rsp(int32_t res, const class SBuffer &buf, boolean lqi)
|
||||
TrueFalseNull(m_permitjoin & 0x02),
|
||||
m_depth,
|
||||
m_lqi);
|
||||
|
||||
// detect any router
|
||||
Z_Device & device = zigbee_devices.findShortAddr(m_shortaddr);
|
||||
if (device.valid()) {
|
||||
if ((m_dev_type & 0x03) == 1) { // it is a router
|
||||
device.setRouter(true);
|
||||
}
|
||||
}
|
||||
|
||||
// Add information to zigbee mapper
|
||||
// Z_Mapper_Edge::Edge_Type edge_type;
|
||||
// switch ((m_dev_type & 0x70) >> 4) {
|
||||
// case 0: edge_type = Z_Mapper_Edge::Parent; break;
|
||||
// case 1: edge_type = Z_Mapper_Edge::Child; break;
|
||||
// case 2: edge_type = Z_Mapper_Edge::Sibling; break;
|
||||
// default: edge_type = Z_Mapper_Edge::Unknown; break;
|
||||
|
||||
// }
|
||||
// Z_Mapper_Edge edge(m_shortaddr, shortaddr, m_lqi, edge_type);
|
||||
Z_Mapper_Edge edge(m_shortaddr, shortaddr, m_lqi);
|
||||
zigbee_mapper.addEdge(edge);
|
||||
}
|
||||
|
||||
ResponseAppend_P(PSTR("]}}"));
|
||||
|
@ -1052,6 +1052,26 @@ void CmndZbBindState(void) {
|
||||
CmndZbBindState_or_Map(false);
|
||||
}
|
||||
|
||||
void ZigbeeMapAllDevices(void) {
|
||||
// we can't abort a mapping in progress
|
||||
if (zigbee.mapping_in_progress) { return; }
|
||||
// defer sending ZbMap to each device
|
||||
zigbee_mapper.reset(); // clear all data in Zigbee mapper
|
||||
|
||||
const static uint32_t DELAY_ZBMAP = 2000; // wait for 1s between commands
|
||||
uint32_t wait_ms = DELAY_ZBMAP;
|
||||
zigbee.mapping_in_progress = true; // mark mapping in progress
|
||||
|
||||
zigbee_devices.setTimer(0x0000, 0, 0 /*wait_ms*/, 0, 0, Z_CAT_ALWAYS, 0 /* value = index */, &Z_Map);
|
||||
for (const auto & device : zigbee_devices.getDevices()) {
|
||||
zigbee_devices.setTimer(device.shortaddr, 0, wait_ms, 0, 0, Z_CAT_ALWAYS, 0 /* value = index */, &Z_Map);
|
||||
wait_ms += DELAY_ZBMAP;
|
||||
}
|
||||
wait_ms += DELAY_ZBMAP*2;
|
||||
zigbee_devices.setTimer(BAD_SHORTADDR, 0, wait_ms, 0, 0, Z_CAT_ALWAYS, 0 /* value = index */, &Z_Map);
|
||||
zigbee.mapping_end_time = wait_ms + millis();
|
||||
}
|
||||
|
||||
//
|
||||
// Command `ZbMap`
|
||||
// `ZbMap<x>` as index if it does not fit. If default, `1` starts at the beginning
|
||||
@ -1061,15 +1081,7 @@ void CmndZbMap(void) {
|
||||
RemoveSpace(XdrvMailbox.data);
|
||||
|
||||
if (strlen(XdrvMailbox.data) == 0) {
|
||||
// defer sending ZbMap to each device
|
||||
const static uint32_t DELAY_ZBMAP = 2000; // wait for 1s between commands
|
||||
uint32_t wait_ms = DELAY_ZBMAP;
|
||||
zigbee_devices.setTimer(0x0000, 0, 0 /*wait_ms*/, 0, 0, Z_CAT_ALWAYS, 0 /* value = index */, &Z_Map);
|
||||
for (const auto & device : zigbee_devices.getDevices()) {
|
||||
zigbee_devices.setTimer(device.shortaddr, 0, wait_ms, 0, 0, Z_CAT_ALWAYS, 0 /* value = index */, &Z_Map);
|
||||
wait_ms += DELAY_ZBMAP;
|
||||
}
|
||||
zigbee_devices.setTimer(BAD_SHORTADDR, 0, wait_ms, 0, 0, Z_CAT_ALWAYS, 0 /* value = index */, &Z_Map);
|
||||
ZigbeeMapAllDevices();
|
||||
ResponseCmndDone();
|
||||
} else {
|
||||
CmndZbBindState_or_Map(true);
|
||||
@ -1317,8 +1329,8 @@ void CmndZbSave(void) {
|
||||
case -10:
|
||||
{ // reinit EEPROM
|
||||
ZFS::erase();
|
||||
break;
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
saveZigbeeDevices();
|
||||
@ -1698,6 +1710,16 @@ extern "C" {
|
||||
}
|
||||
} // extern "C"
|
||||
|
||||
#define WEB_HANDLE_ZB_MAP "Zigbee Map"
|
||||
#define WEB_HANDLE_ZB_PERMIT_JOIN "Zigbee Permit Join"
|
||||
#define WEB_HANDLE_ZB_MAP_REFRESH "Zigbee Map Refresh"
|
||||
const char HTTP_BTN_ZB_BUTTONS[] PROGMEM =
|
||||
"<button onclick='la(\"&zbj=1\");'>" WEB_HANDLE_ZB_PERMIT_JOIN "</button>"
|
||||
"<p></p>"
|
||||
"<form action='zbm' method='get'><button>" WEB_HANDLE_ZB_MAP "</button></form>";
|
||||
const char HTTP_AUTO_REFRESH_PAGE[] PROGMEM = "<script>setTimeout(function(){location.reload();},1990);</script>";
|
||||
const char HTTP_BTN_ZB_MAP_REFRESH[] PROGMEM = "<p></p><form action='zbr' method='get'><button>" WEB_HANDLE_ZB_MAP_REFRESH "</button></form>";
|
||||
|
||||
void ZigbeeShow(bool json)
|
||||
{
|
||||
if (json) {
|
||||
@ -1879,11 +1901,67 @@ void ZigbeeShow(bool json)
|
||||
}
|
||||
}
|
||||
|
||||
WSContentSend_P(PSTR("</table>{t}")); // Terminate current multi column table and open new table
|
||||
WSContentSend_P(PSTR("</table>{t}<p></p>")); // Terminate current multi column table and open new table
|
||||
if (zigbee.permit_end_time) {
|
||||
// PermitJoin in progress
|
||||
WSContentSend_P(PSTR("<p><b>[ <span style='color:#080;'>Devices allowed to join</span> ]</b></p>")); // Terminate current multi column table and open new table
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
// Web handler to refresh the map, the redirect to show map
|
||||
void ZigbeeMapRefresh(void) {
|
||||
if ((!zigbee.init_phase) && (!zigbee.mapping_in_progress)) {
|
||||
ZigbeeMapAllDevices();
|
||||
}
|
||||
Webserver->sendHeader("Location","/zbm"); // Add a header to respond with a new location for the browser to go to the home page again
|
||||
Webserver->send(302);
|
||||
}
|
||||
|
||||
// Display a graphical representation of the Zigbee map using vis.js network
|
||||
void ZigbeeShowMap(void) {
|
||||
AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_HTTP "Zigbee Mapper"));
|
||||
|
||||
// if no map, then launch a new mapping
|
||||
if ((!zigbee.init_phase) && (!zigbee.mapping_ready) && (!zigbee.mapping_in_progress)) {
|
||||
ZigbeeMapAllDevices();
|
||||
}
|
||||
|
||||
WSContentStart_P(PSTR("Tasmota Zigbee Mapping"));
|
||||
WSContentSendStyle();
|
||||
|
||||
if (zigbee.init_phase) {
|
||||
WSContentSend_P(PSTR("Zigbee not started"));
|
||||
} else if (zigbee.mapping_in_progress) {
|
||||
int32_t mapping_remaining = 1 + (zigbee.mapping_end_time - millis()) / 1000;
|
||||
if (mapping_remaining < 0) { mapping_remaining = 0; }
|
||||
WSContentSend_P(PSTR("Mapping in progress (%d s. remaining)"), mapping_remaining);
|
||||
WSContentSend_P(HTTP_AUTO_REFRESH_PAGE);
|
||||
} else if (!zigbee.mapping_ready) {
|
||||
WSContentSend_P(PSTR("No mapping"));
|
||||
} else {
|
||||
WSContentSend_P(PSTR(
|
||||
"<script type=\"text/javascript\" src=\"https://unpkg.com/vis-network/standalone/umd/vis-network.min.js\"></script>"
|
||||
"<div id=\"mynetwork\" style=\"background-color:#fff;width:800px;height:400px;border:1px solid lightgray;resize:both;\"></div>"
|
||||
"<script type=\"text/javascript\">"
|
||||
"var container=document.getElementById(\"mynetwork\");"
|
||||
"var options={groups:{o:{shape:\"circle\",color:\"#d55\"},r:{shape:\"box\",color:\"#fb7\"},e:{shape:\"ellipse\",color:\"#adf\"}}};"
|
||||
"var data={"
|
||||
));
|
||||
|
||||
zigbee_mapper.dumpInternals();
|
||||
|
||||
WSContentSend_P(PSTR(
|
||||
"};"
|
||||
"var network=new vis.Network(container,data,options);</script>"
|
||||
));
|
||||
WSContentSend_P(HTTP_BTN_ZB_MAP_REFRESH);
|
||||
}
|
||||
WSContentSpaceButton(BUTTON_MAIN);
|
||||
WSContentStop();
|
||||
}
|
||||
|
||||
/*********************************************************************************************\
|
||||
* Interface
|
||||
\*********************************************************************************************/
|
||||
@ -1920,12 +1998,17 @@ bool Xdrv23(uint8_t function)
|
||||
case FUNC_WEB_SENSOR:
|
||||
ZigbeeShow(false);
|
||||
break;
|
||||
#ifdef USE_ZIGBEE_EZSP
|
||||
// GUI xmodem
|
||||
case FUNC_WEB_ADD_HANDLER:
|
||||
#ifdef USE_ZIGBEE_EZSP
|
||||
WebServer_on(PSTR("/" WEB_HANDLE_ZIGBEE_XFER), HandleZigbeeXfer);
|
||||
break;
|
||||
#endif // USE_ZIGBEE_EZSP
|
||||
WebServer_on(PSTR("/zbm"), ZigbeeShowMap, HTTP_GET); // add web handler for Zigbee map
|
||||
WebServer_on(PSTR("/zbr"), ZigbeeMapRefresh, HTTP_GET); // add web handler for Zigbee map refresh
|
||||
break;
|
||||
case FUNC_WEB_ADD_MAIN_BUTTON:
|
||||
WSContentSend_P(HTTP_BTN_ZB_BUTTONS);
|
||||
break;
|
||||
#endif // USE_WEBSERVER
|
||||
case FUNC_PRE_INIT:
|
||||
ZigbeeInit();
|
||||
|
Loading…
x
Reference in New Issue
Block a user