Created
June 1, 2020 02:08
-
-
Save prairiesnpr/7a40b78e765044252a4799d328327f0a to your computer and use it in GitHub Desktop.
Water Heater Control
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <SoftwareSerial.h> | |
#include <XBee.h> | |
#include <OneWire.h> | |
#include <DallasTemperature.h> | |
#include <timer.h> | |
#include <EEPROM.h> | |
#include <EmonLib.h> | |
#define WATER_TEMP_BUS 7 | |
#define SSR_PIN 5 | |
#define AMP_PIN 16 | |
auto timer = timer_create_default(); // create a timer with default settings | |
EnergyMonitor emon1; | |
//One wire temp sensors | |
OneWire oneWire(WATER_TEMP_BUS); | |
DallasTemperature sensors(&oneWire); | |
//Water Input Temp | |
DeviceAddress inputThermometer = {0x28, 0xDB, 0x8F, 0x77, 0x91, 0x15, 0x02, 0x84}; | |
//Water Output Temp | |
DeviceAddress outputThermometer = {0x28, 0x7D, 0xA3, 0x77, 0x91, 0x0D, 0x02, 0x29}; | |
#define UKN_NET_ADDR 0xFFFE | |
#define HA_PROFILE_ID 0x0104 | |
#define MATCH_DESC_RQST 0x0006 | |
#define MATCH_DESC_RSP 0x8006 | |
#define SIMPLE_DESC_RSP 0x8004 | |
#define ACTIVE_EP_RSP 0x8005 | |
#define ACTIVE_EP_RQST 0x0005 | |
#define SIMPLE_DESC_RQST 0x0004 | |
#define READ_ATTRIBUTES 0x0000 | |
//Input | |
#define BASIC_CLUSTER_ID 0x0000 | |
#define IDENTIFY_CLUSTER_ID 0x0003 | |
#define GROUPS_CLUSTER_ID 0x0004 | |
#define SCENES_CLUSTER_ID 0x0005 | |
#define ON_OFF_CLUSTER_ID 0x0006 | |
#define LEVEL_CONTROL_CLUSTER_ID 0x0008 | |
#define LIGHT_LINK_CLUSTER_ID 0x1000 | |
#define TEMP_CLUSTER_ID 0x0402 | |
#define HUMIDITY_CLUSTER_ID 0x405 | |
#define BINARY_INPUT_CLUSTER_ID 0x000f | |
#define IAS_ZONE_CLUSTER_ID 0x0500 | |
#define METERING_CLUSTER_ID 0x0702 | |
//Attr id | |
#define INSTANTANEOUS_DEMAND 0x0400 | |
//Output | |
#define OTA_CLUSTER_ID 0x0019 //Upgrade | |
//Data Type | |
#define ZCL_CHAR_STR 0x42 | |
#define ZCL_UINT8_T 0x20 | |
#define ZCL_UINT16_T 0x21 | |
#define ZCL_BOOL 0x10 | |
//Device | |
#define ON_OFF_LIGHT 0x0100 | |
#define DIMMABLE_LIGHT 0x0101 | |
#define TEMPERATURE_SENSOR 0x0302 | |
#define ON_OFF_OUTPUT 0x0002 | |
#define IAS_ZONE 0x0402 | |
uint8_t t_payload[25] = {}; | |
/* | |
typedef struct { | |
uint16_t id; | |
uint8_t* value; | |
uint8_t val_len; | |
uint8_t type; | |
} attribute; | |
*/ | |
class attribute | |
{ | |
public: | |
uint16_t id; | |
uint8_t* value; | |
uint8_t val_len; | |
uint8_t type; | |
attribute(uint16_t a_id, uint8_t* a_value, uint8_t a_val_len, uint8_t a_type) | |
{ | |
id = a_id; | |
value = new uint8_t[a_val_len]; | |
memcpy(value, a_value, a_val_len); | |
//value = a_value; | |
val_len = a_val_len; | |
type = a_type; | |
} | |
}; | |
class Cluster | |
{ | |
private: | |
attribute* attributes; | |
uint8_t num_attr; | |
public: | |
uint16_t id; | |
Cluster(uint16_t cl_id, attribute* attr, uint8_t num) | |
{ | |
id = cl_id; | |
attributes = attr; | |
num_attr = num; | |
} | |
attribute* GetAttr(uint16_t attr_id) | |
{ | |
for (uint8_t i = 0; i < num_attr; i++) | |
{ | |
//Serial.println(num_attr); | |
if (attributes[i].id == attr_id) { | |
//attribute res = attributes[i]; | |
return &attributes[i]; | |
} | |
else { | |
//Serial.print("T: "); | |
//Serial.println(attributes[i].id); | |
} | |
} | |
Serial.print(F("Attr Not Found: ")); | |
Serial.println(attr_id, HEX); | |
return &attribute{0, 0, 0, 0}; | |
} | |
}; | |
class LocalMac | |
{ | |
private: | |
uint8_t addr; | |
public: | |
void Set(XBeeAddress64 mac) { | |
EEPROM.put(addr, mac); | |
} | |
XBeeAddress64 Get() | |
{ | |
XBeeAddress64 mac; | |
EEPROM.get(addr, mac); | |
return mac; | |
} | |
LocalMac(uint8_t mem_loc = 0) { | |
addr = mem_loc; | |
} | |
}; | |
class Endpoint | |
{ | |
private: | |
uint8_t num_in_clusters; | |
uint8_t num_out_clusters; | |
Cluster* out_clusters; | |
uint16_t dev_type; | |
public: | |
uint8_t id; | |
Cluster* in_clusters; | |
public: | |
Endpoint(uint8_t ep_id = 0, uint16_t type_dev = 0, Cluster* in_cls = {}, Cluster* out_cls = {}, uint8_t num_in_cls = 0, uint8_t num_out_cls = 0) | |
{ | |
id = ep_id; | |
dev_type = type_dev; | |
num_in_clusters = num_in_cls; | |
num_out_clusters = num_out_cls; | |
in_clusters = in_cls; | |
out_clusters = out_cls; | |
} | |
Cluster GetCluster(uint16_t cl_id) | |
{ | |
for (uint8_t i = 0; i < num_in_clusters; i++) | |
{ | |
if (cl_id == in_clusters[i].id) { | |
return in_clusters[i]; | |
} | |
else | |
{ | |
Serial.println(in_clusters[i].id); | |
} | |
} | |
Serial.print(F("No Cl ")); | |
Serial.println(cl_id); | |
} | |
void GetInClusters(uint16_t* in_cl) | |
{ | |
for (uint8_t i = 0; i < num_in_clusters; i++) | |
{ | |
*(in_cl + i) = in_clusters[i].id; | |
} | |
} | |
void GetOutClusters(uint16_t* out_cl) | |
{ | |
for (uint8_t i = 0; i < num_out_clusters; i++) | |
{ | |
*(out_cl + i) = out_clusters[i].id; | |
} | |
} | |
uint8_t GetNumInClusters() { | |
return num_in_clusters; | |
} | |
uint8_t GetNumOutClusters() { | |
return num_out_clusters; | |
} | |
uint16_t GetDevType() { | |
return dev_type; | |
} | |
}; | |
LocalMac macAddr = LocalMac(0); | |
#define NUM_ENDPOINTS 3 | |
static uint8_t* manuf = (uint8_t*)"iSilentLLC"; | |
static attribute dev_basic_attr[] {{0x0004, manuf, 10, ZCL_CHAR_STR}, {0x0005, (uint8_t*)"Water Heater", 12, ZCL_CHAR_STR}}; | |
static attribute in_temp_basic_attr[] {{0x0004, manuf, 10, ZCL_CHAR_STR}, {0x0005, (uint8_t*)"In Temp", 7, ZCL_CHAR_STR}}; | |
static attribute out_temp_basic_attr[] {{0x0004, manuf, 10, ZCL_CHAR_STR}, {0x0005, (uint8_t*)"Out Temp", 8, ZCL_CHAR_STR}}; | |
static attribute ssr_attr[] {{0x0000, 0x00, 1, ZCL_BOOL}}; | |
static attribute metering_attr[] = {{INSTANTANEOUS_DEMAND, 0x00, 1, ZCL_UINT16_T}}; | |
static attribute in_temp_attr[] = {{0x0000, 0x00, 1, ZCL_UINT16_T}}; | |
static attribute out_temp_attr[] = {{0x0000, 0x00, 1, ZCL_UINT16_T}}; | |
//dev_basic_attr | |
static Cluster s_in_clusters[] = {Cluster(BASIC_CLUSTER_ID, dev_basic_attr, 2), Cluster(ON_OFF_CLUSTER_ID, ssr_attr, 1), Cluster(METERING_CLUSTER_ID, metering_attr, 1)}; | |
static Cluster i_in_clusters[] = {Cluster(BASIC_CLUSTER_ID, in_temp_basic_attr, 2), Cluster(TEMP_CLUSTER_ID, in_temp_attr, 1)}; | |
static Cluster o_in_clusters[] = {Cluster(BASIC_CLUSTER_ID, out_temp_basic_attr, 2), Cluster(TEMP_CLUSTER_ID, out_temp_attr, 1)}; | |
static Cluster out_clusters[] = {}; | |
static Endpoint ENDPOINTS[NUM_ENDPOINTS] = { | |
Endpoint(1, ON_OFF_OUTPUT, s_in_clusters, out_clusters, 3, 0), | |
Endpoint(2, TEMPERATURE_SENSOR, i_in_clusters, out_clusters, 2, 0), | |
Endpoint(3, TEMPERATURE_SENSOR, o_in_clusters, out_clusters, 2, 0), | |
}; | |
XBeeAddress64 COORDINATOR64 = XBeeAddress64(0, 0); | |
uint16_t COORDINATOR_NWK = 0x0000; | |
bool is_joined = 0; | |
bool start = 0; | |
uint8_t associated = 1; | |
bool setup_complete = 0; | |
bool nwk_pending = 0; | |
bool assc_pending = 0; | |
uint8_t cmd_frame_id; | |
typedef void (*cmd_ptr)(); | |
cmd_ptr last_command; | |
uint8_t cmd_seq_id; | |
#define SWAP_UINT16(x) (((x) >> 8) | ((x) << 8)) | |
#define SWAP_UINT32(x) (((x) >> 24) | (((x) & 0x00FF0000) >> 8) | (((x) & 0x0000FF00) << 8) | ((x) << 24)) | |
uint64_t SWAP_UINT64(uint64_t num) | |
{ | |
uint64_t byte0, byte1, byte2, byte3, byte4, byte5, byte6, byte7; | |
byte0 = (num & 0x00000000000000FF) >> 0 ; | |
byte1 = (num & 0x000000000000FF00) >> 8 ; | |
byte2 = (num & 0x0000000000FF0000) >> 16 ; | |
byte3 = (num & 0x00000000FF000000) >> 24 ; | |
byte4 = (num & 0x000000FF00000000) >> 32 ; | |
byte5 = (num & 0x0000FF0000000000) >> 40 ; | |
byte6 = (num & 0x00FF000000000000) >> 48 ; | |
byte7 = (num & 0xFF00000000000000) >> 56 ; | |
return ((byte0 << 56) | (byte1 << 48) | (byte2 << 40) | (byte3 << 32) | (byte4 << 24) | (byte5 << 16) | (byte6 << 8) | (byte7 << 0)); | |
} | |
Endpoint GetEndpoint(uint8_t ep_id) | |
{ | |
for (uint8_t i = 0; i < NUM_ENDPOINTS; i++) { | |
if (ENDPOINTS[i].id == ep_id) { | |
return ENDPOINTS[i]; | |
} | |
} | |
} | |
// Define SoftSerial TX/RX pins | |
// Connect Arduino pin 10 to TX of usb-serial device | |
#define ssRX 10 | |
// Connect Arduino pin 11 to RX of usb-serial device | |
#define ssTX 11 | |
//XBeeAddress64 macAddr; | |
uint8_t netAddr[2]; | |
//uint8_t frameID; | |
uint8_t seqID; | |
SoftwareSerial nss(ssRX, ssTX); | |
XBeeWithCallbacks xbee; | |
// ieee high | |
static const uint8_t shCmd[] = {'S', 'H'}; | |
// ieee low | |
static const uint8_t slCmd[] = {'S', 'L'}; | |
// association status | |
static const uint8_t assocCmd[] = {'A', 'I'}; | |
// panID | |
static const uint8_t netCmd[] = {'M', 'Y'}; | |
ZBExplicitTxRequest exp_tx = ZBExplicitTxRequest(); | |
void setup() { | |
digitalWrite(SSR_PIN, HIGH); | |
Serial.begin(9600); | |
Serial.println(F("Startup")); | |
sensors.begin(); | |
nss.begin(9600); | |
xbee.setSerial(nss); | |
Serial.print(F("Found ")); | |
Serial.print(sensors.getDeviceCount(), DEC); | |
Serial.println(F(" devices.")); | |
// start soft serial | |
// Startup delay to wait for XBee radio to initialize. | |
// you may need to increase this value if you are not getting a response | |
//Serial.println(basic_cluster.GetAttr(0x0004).c_str()); | |
sensors.setResolution(inputThermometer, 9); | |
sensors.setResolution(outputThermometer, 9); | |
pinMode(SSR_PIN, OUTPUT); | |
delay(5000); | |
//emon init | |
// emon1.current(AMP_PIN, 111.1); // Current: input pin, calibration. | |
emon1.current(AMP_PIN, 0); | |
//Log errors to Serial | |
//xbee.onPacketError(printErrorCb, (uintptr_t)(Print*)&Serial); | |
//Set up callbacks | |
xbee.onZBExplicitRxResponse(zdoReceive); | |
xbee.onZBTxStatusResponse(zbTxStatusResp); | |
xbee.onAtCommandResponse(atCmdResp); | |
xbee.onOtherResponse(otherResp); | |
//Serial.println("Setup Complete"); | |
getMAC(); | |
Serial.print(F("LCL Add: ")); | |
printAddr(macAddr.Get()); | |
timer.every(30000, update_temp); | |
//Update our current state | |
uint8_t ep_id = 1; | |
Endpoint end_point = GetEndpoint(ep_id); | |
Cluster cluster = end_point.GetCluster(ON_OFF_CLUSTER_ID); | |
attribute* attr = cluster.GetAttr(0x0000); | |
*attr->value = 0x01; | |
} | |
//Update temp, serial for now | |
bool update_temp(void *) { | |
uint8_t in_ep_id = 2; | |
uint8_t out_ep_id = 3; | |
uint8_t amp_ep_id = 1; | |
sensors.requestTemperatures(); | |
float TempC = sensors.getTempC(inputThermometer); | |
Serial.print(F("In Temp: ")); | |
Serial.print(TempC); | |
Serial.println(F("°C")); | |
if (TempC < 200 && TempC > -100){ | |
Endpoint end_point = GetEndpoint(in_ep_id); | |
Cluster cluster = end_point.GetCluster(TEMP_CLUSTER_ID); | |
attribute* attr = cluster.GetAttr(0x0000); | |
attr->val_len = 2; | |
uint16_t cor_t = (uint16_t)(TempC * 100.0); | |
uint8_t t_value[2] = {(uint8_t)cor_t, | |
(uint8_t)(cor_t >> 8) | |
}; | |
attr->value = t_value; | |
sendAttributeRpt(cluster.id, attr, end_point.id, end_point.id); | |
} | |
TempC = sensors.getTempC(outputThermometer); | |
Serial.print(F("Out Temp: ")); | |
Serial.print(TempC); | |
Serial.println(F("°C")); | |
if (TempC < 200 && TempC > -100){ | |
Endpoint end_point = GetEndpoint(out_ep_id); | |
Cluster cluster = end_point.GetCluster(TEMP_CLUSTER_ID); | |
attribute* attr = cluster.GetAttr(0x0000); | |
attr->val_len = 2; | |
uint16_t cor_t = (uint16_t)(TempC * 100.0); | |
uint8_t t2_value[2] = {(uint8_t)cor_t, | |
(uint8_t)(cor_t >> 8) | |
}; | |
attr->value = t2_value; | |
sendAttributeRpt(cluster.id, attr, end_point.id, end_point.id); | |
} | |
//Update amps | |
// | |
double Irms = emon1.calcIrms(1480); // Calculate Irms only | |
Serial.print(Irms * 240.0); // Apparent power | |
Serial.print(" "); | |
Serial.println(Irms); // Irms | |
Endpoint end_point = GetEndpoint(amp_ep_id); | |
Cluster cluster = end_point.GetCluster(METERING_CLUSTER_ID); | |
attribute* attr = cluster.GetAttr(INSTANTANEOUS_DEMAND); | |
attr->val_len = 2; | |
uint16_t cor_t = (uint16_t)(Irms * 240.0); | |
uint8_t a_value[2] = {(uint8_t)cor_t, | |
(uint8_t)(cor_t >> 8) | |
}; | |
attr->value = a_value; | |
sendAttributeRpt(cluster.id, attr, end_point.id, end_point.id); | |
//Update our current switch state | |
cluster = end_point.GetCluster(ON_OFF_CLUSTER_ID); | |
attr = cluster.GetAttr(0x0000); | |
Serial.print(F("Cur St ")); | |
Serial.println(*attr->value); | |
sendAttributeRpt(cluster.id, attr, end_point.id, end_point.id); | |
return true; // repeat? true | |
} | |
void printAddr(uint64_t val) { | |
uint32_t msb = val >> 32; | |
uint32_t lsb = val; | |
Serial.print(msb, HEX); | |
Serial.println(lsb, HEX); | |
} | |
void SetAttr(uint8_t ep_id, uint16_t cluster_id, uint16_t attr_id, uint8_t value) | |
{ | |
Endpoint end_point = GetEndpoint(ep_id); | |
Cluster cluster = end_point.GetCluster(cluster_id); | |
attribute* attr = cluster.GetAttr(attr_id); | |
Serial.println(cluster_id); | |
if (cluster_id == ON_OFF_CLUSTER_ID) { | |
*attr->value = value; //breaking | |
if (value == 0x00) { | |
Serial.print(F("Turn Off: ")); | |
Serial.println(end_point.id); | |
digitalWrite(SSR_PIN, LOW); | |
} | |
else if (value == 0x01) { | |
Serial.print(F("Turn On: ")); | |
Serial.println(end_point.id); | |
digitalWrite(SSR_PIN, HIGH); | |
} | |
} | |
sendAttributeWriteRsp(cluster_id, attr, ep_id, ep_id, value); | |
} | |
void loop() { | |
xbee.loop(); | |
if (associated != 0 && !assc_pending) { | |
assc_pending = 1; | |
getAssociation(); | |
} | |
if (netAddr[0] == 0 && netAddr[1] == 0 && !nwk_pending && !assc_pending) { | |
nwk_pending = 1; | |
getNetAddr(); | |
} | |
if (!nwk_pending && !assc_pending) { | |
//Serial.println("Config Cmp"); | |
setup_complete = 1; | |
} | |
if (setup_complete && !start) { | |
sendDevAnnounce(); | |
start = 1; | |
} | |
if (start) { | |
} | |
timer.tick(); | |
} | |
void zbTxStatusResp(ZBTxStatusResponse& resp, uintptr_t) { | |
if (resp.isSuccess()) { | |
Serial.println(F("TX OK")); | |
} | |
else { | |
Serial.println(F("TX FAIL")); | |
Serial.println(resp.getDeliveryStatus(), HEX); | |
if (resp.getFrameId() == cmd_frame_id) { | |
last_command(); | |
} | |
} | |
} | |
void otherResp(XBeeResponse& resp, uintptr_t) { | |
Serial.println(F("Other Response")); | |
} | |
void atCmdResp(AtCommandResponse& resp, uintptr_t) { | |
Serial.println(F("At resp")); | |
if (resp.getStatus() == AT_OK) { | |
if (resp.getCommand()[0] == assocCmd[0] && | |
resp.getCommand()[1] == assocCmd[1]) { | |
//Association Status | |
associated = resp.getValue()[0]; | |
assc_pending = 0; | |
Serial.print(F("Asc St: ")); | |
Serial.println(associated); | |
} | |
else if (resp.getCommand()[0] == netCmd[0] && | |
resp.getCommand()[1] == netCmd[1]) { | |
//NWK | |
for (int i = 0; i < resp.getValueLength(); i++) { | |
Serial.print(resp.getValue()[i], HEX); | |
Serial.print(F(" ")); | |
} | |
Serial.println(); | |
netAddr[0] = resp.getValue()[0]; | |
netAddr[1] = resp.getValue()[1]; | |
nwk_pending = 0; | |
Serial.print(F("NWK: ")); | |
Serial.print(netAddr[0], HEX); | |
Serial.println(netAddr[1], HEX); | |
} | |
else { | |
Serial.println(F("Ukn Cmd")); | |
} | |
} | |
else { | |
Serial.println(F("AT Fail")); | |
} | |
} | |
void zdoReceive(ZBExplicitRxResponse& erx, uintptr_t) { | |
// Create a reply packet containing the same data | |
// This directly reuses the rx data array, which is ok since the tx | |
// packet is sent before any new response is received | |
//tx.setAddress64(erx.getRemoteAddress64()); | |
//tx.setAddress16(erx.getRemoteAddress16()); | |
//tx.setPayload(erx.getFrameData() + rx.getDataOffset(), rx.getDataLength()); | |
if (erx.getRemoteAddress16() == 0 ) { | |
Serial.println(F("ZDO")); | |
Serial.println(erx.getClusterId(), HEX); | |
if (erx.getClusterId() == ACTIVE_EP_RQST) { | |
//Have to match sequence number in response | |
cmd_seq_id = erx.getFrameData()[erx.getDataOffset()]; | |
sendActiveEpResp(); | |
} | |
else if (erx.getClusterId() == SIMPLE_DESC_RQST) { | |
Serial.print("Actv Ep Rqst: "); | |
//Have to match sequence number in response | |
cmd_seq_id = erx.getFrameData()[erx.getDataOffset()]; | |
//Payload is EndPoint | |
uint8_t ep = erx.getFrameData()[erx.getDataOffset() + 3]; | |
Serial.println(ep, HEX); | |
sendSimpleDescRpt(ep); | |
} | |
else if (erx.getClusterId() == ON_OFF_CLUSTER_ID) { | |
Serial.println(F("ON/OFF Cl")); | |
uint8_t len_data = erx.getDataLength() - 3; | |
uint16_t attr_rqst[len_data / 2]; | |
for (uint8_t i = erx.getDataOffset(); i < (erx.getDataLength() + erx.getDataOffset() + 3); i ++) { | |
Serial.print(erx.getFrameData()[i]); | |
} | |
Serial.println(); | |
cmd_seq_id = erx.getFrameData()[erx.getDataOffset() + 1]; | |
uint8_t ep = erx.getDstEndpoint(); | |
uint8_t cmd_id = erx.getFrameData()[erx.getDataOffset() + 2]; | |
Endpoint end_point = GetEndpoint(ep); | |
if (cmd_id == 0x00) { | |
Serial.println(F("Cmd Off")); | |
SetAttr(ep, erx.getClusterId(), 0x0000, 0x00); | |
} | |
else if (cmd_id == 0x01) { | |
Serial.println(F("Cmd On")); | |
SetAttr(ep, erx.getClusterId(), 0x0000, 0x01); | |
} | |
else { | |
Serial.print(F("Cmd Id: ")); | |
Serial.println(cmd_id, HEX); | |
} | |
} | |
else if (erx.getClusterId() == READ_ATTRIBUTES) { //SHould be basic cluster id | |
Serial.println(F("Clstr Rd Att:")); | |
cmd_seq_id = erx.getFrameData()[erx.getDataOffset() + 1]; | |
uint8_t ep = erx.getDstEndpoint(); | |
//cmd_seq_id = erx.getFrameData()[erx.getDataOffset()]; | |
Serial.print(F("Cmd Seq: ")); | |
Serial.println(cmd_seq_id); | |
uint8_t len_data = erx.getDataLength() - 3; | |
uint16_t attr_rqst[len_data / 2]; | |
Endpoint end_point = GetEndpoint(ep); | |
for (uint8_t i = erx.getDataOffset() + 3; i < (len_data + erx.getDataOffset() + 3); i += 2) { | |
attr_rqst[i / 2] = (erx.getFrameData()[i + 1] << 8) | | |
(erx.getFrameData()[i] & 0xff); | |
attribute* attr = end_point.GetCluster(erx.getClusterId()).GetAttr(attr_rqst[i / 2]); | |
sendAttributeRsp(erx.getClusterId(), attr, ep, ep, 0x01); | |
} | |
} | |
} | |
} | |
void sendAttributeWriteRsp(uint16_t cluster_id, attribute* attr, uint8_t src_ep, uint8_t dst_ep, uint8_t result) { | |
/* | |
payload | |
byte 0: frame control | |
byte 1 Seq | |
byte 2 cmd id | |
byte 3-4: Attr Id | |
byte 5: type | |
bytes6: Success 0x01 | |
----------------------------- | |
CMDS: 0x0A Report Attr | |
0x01 Read Attr Response | |
0x0D Discover Attributes Response | |
0x04 Write Attr Response | |
*/ | |
uint8_t payload_len = 6 + attr->val_len; | |
uint8_t pre[payload_len] = {0x00, | |
cmd_seq_id, | |
0x0A, //0x04, //Write attr resp | |
static_cast<uint8_t>((attr->id & 0x00FF) >> 0), | |
static_cast<uint8_t>((attr->id & 0xFF00) >> 8), | |
attr->type, | |
result | |
}; | |
memcpy(t_payload, pre, sizeof(pre)); | |
cmd_frame_id = xbee.getNextFrameId(); | |
exp_tx = ZBExplicitTxRequest(COORDINATOR64, | |
COORDINATOR_NWK, | |
0x00, //broadcast radius | |
0x00, //option | |
t_payload, //payload | |
payload_len, //payload length | |
cmd_frame_id, // frame ID | |
src_ep, //src endpoint | |
dst_ep, //dest endpoint | |
cluster_id, //cluster ID | |
HA_PROFILE_ID //profile ID | |
); | |
if (attr->type != 0) { | |
xbee.send(exp_tx); | |
Serial.println(F("Sent Attr Write Rsp")); | |
} | |
} | |
void sendAttributeRpt(uint16_t cluster_id, attribute* attr, uint8_t src_ep, uint8_t dst_ep) { | |
/* | |
payload | |
byte 0: frame control | |
byte 1 Seq | |
byte 2 cmd id | |
byte 3-4: Attr Id | |
byte 5: type | |
bytes[] value in little endian | |
----------------------------- | |
CMDS: 0x0A Report Attr | |
0x01 Read Attr Response | |
0x0D Discover Attributes Response | |
0x04 Write Attr Response | |
*/ | |
uint8_t payload_len; | |
if (attr->type == ZCL_CHAR_STR){ | |
payload_len = 7 + attr->val_len; | |
uint8_t pre[] = {0x00, | |
cmd_seq_id, | |
0x0A, //Read attr resp | |
static_cast<uint8_t>((attr->id & 0x00FF) >> 0), | |
static_cast<uint8_t>((attr->id & 0xFF00) >> 8), | |
attr->type, | |
attr->val_len, | |
}; | |
memcpy(t_payload, pre, sizeof(pre)); | |
memcpy(t_payload + 7, attr->value, attr->val_len); | |
} | |
else { | |
payload_len = 6 + attr->val_len; | |
uint8_t pre[] = {0x00, | |
cmd_seq_id, | |
0x0A, //Read attr resp | |
static_cast<uint8_t>((attr->id & 0x00FF) >> 0), | |
static_cast<uint8_t>((attr->id & 0xFF00) >> 8), | |
attr->type, | |
}; | |
memcpy(t_payload, pre, sizeof(pre)); | |
memcpy(t_payload + 6, attr->value, attr->val_len); | |
} | |
// } | |
cmd_frame_id = xbee.getNextFrameId(); | |
//last_command = &sendAttributeRsp; | |
exp_tx = ZBExplicitTxRequest(COORDINATOR64, | |
COORDINATOR_NWK, | |
0x00, //broadcast radius | |
0x00, //option | |
t_payload, //payload | |
payload_len, //payload length | |
cmd_frame_id, // frame ID | |
src_ep, //src endpoint | |
dst_ep, //dest endpoint | |
cluster_id, //cluster ID | |
HA_PROFILE_ID //profile ID | |
); | |
if (attr->type != 0) { | |
xbee.send(exp_tx); | |
Serial.println(F("Sent Attr Rpt")); | |
} | |
} | |
void sendAttributeRsp(uint16_t cluster_id, attribute* attr, uint8_t src_ep, uint8_t dst_ep, uint8_t cmd) { | |
/* | |
payload | |
byte 0: frame control | |
byte 1 Seq | |
byte 2 cmd id | |
byte 3-4: Attr Id | |
byte 5: type | |
bytes[] value in little endian | |
----------------------------- | |
CMDS: 0x0A Report Attr | |
0x01 Read Attr Response | |
0x0D Discover Attributes Response | |
0x04 Write Attr Response | |
*/ | |
//uint8_t res_success[] = {0x01}; | |
// if (attr->type == ZCL_BOOL) | |
//{ | |
// memcpy(t_payload + 6, attr->value[0], attr->val_len); | |
/// } | |
// else | |
// { | |
uint8_t payload_len; | |
if (attr->type == ZCL_CHAR_STR){ | |
payload_len = 8 + attr->val_len; | |
uint8_t pre[] = {0x00, | |
cmd_seq_id, | |
cmd, //Read attr resp | |
static_cast<uint8_t>((attr->id & 0x00FF) >> 0), | |
static_cast<uint8_t>((attr->id & 0xFF00) >> 8), | |
0x00,//status | |
attr->type, | |
attr->val_len, | |
}; | |
memcpy(t_payload, pre, sizeof(pre)); | |
memcpy(t_payload + 8, attr->value, attr->val_len); | |
} | |
else { | |
payload_len = 7 + attr->val_len; | |
uint8_t pre[] = {0x00, | |
cmd_seq_id, | |
cmd, //Read attr resp | |
static_cast<uint8_t>((attr->id & 0x00FF) >> 0), | |
static_cast<uint8_t>((attr->id & 0xFF00) >> 8), | |
0x00, | |
attr->type, | |
}; | |
memcpy(t_payload, pre, sizeof(pre)); | |
memcpy(t_payload + 7, attr->value, attr->val_len); | |
} | |
// } | |
cmd_frame_id = xbee.getNextFrameId(); | |
//last_command = &sendAttributeRsp; | |
exp_tx = ZBExplicitTxRequest(COORDINATOR64, | |
COORDINATOR_NWK, | |
0x00, //broadcast radius | |
0x00, //option | |
t_payload, //payload | |
payload_len, //payload length | |
cmd_frame_id, // frame ID | |
src_ep, //src endpoint | |
dst_ep, //dest endpoint | |
cluster_id, //cluster ID | |
HA_PROFILE_ID //profile ID | |
); | |
if (attr->type != 0) { | |
xbee.send(exp_tx); | |
Serial.println(F("Sent Attr Rsp")); | |
} | |
} | |
void sendDevAnnounce() { | |
/* | |
12 byte data payload | |
byte 0: Sequence Number | |
byte 1-2: Net Addr in Little Endian | |
byte 3-10: Mac Addr in Little Endian | |
byte 11: Mac Capability Flag, 0x8C = Mains powered device; receiver on when idle; address not self-assigned. | |
*/ | |
uint64_t mac = SWAP_UINT64(macAddr.Get()); | |
uint8_t payload[] = {seqID++, | |
netAddr[1], | |
netAddr[0], | |
static_cast<uint8_t>((mac & 0xFF00000000000000) >> 56), | |
static_cast<uint8_t>((mac & 0x00FF000000000000) >> 48), | |
static_cast<uint8_t>((mac & 0x0000FF0000000000) >> 40), | |
static_cast<uint8_t>((mac & 0x000000FF00000000) >> 32), | |
static_cast<uint8_t>((mac & 0x00000000FF000000) >> 24), | |
static_cast<uint8_t>((mac & 0x0000000000FF0000) >> 16), | |
static_cast<uint8_t>((mac & 0x000000000000FF00) >> 8), | |
static_cast<uint8_t>((mac & 0x00000000000000FF) >> 0), | |
0x8C | |
}; | |
cmd_frame_id = xbee.getNextFrameId(); | |
last_command = &sendDevAnnounce; | |
exp_tx = ZBExplicitTxRequest(COORDINATOR64, | |
0x00FD, | |
0x00, //broadcast radius | |
0x00, //option | |
payload, //payload | |
sizeof(payload), //payload length | |
cmd_frame_id, // frame ID | |
0x00, //src endpoint | |
0x00, //dest endpoint | |
0x0013, //cluster ID | |
0x0000 //profile ID | |
); | |
xbee.send(exp_tx); | |
Serial.println(F("Send Dev Ann")); | |
} | |
void build_payload_list(const uint16_t *values, const uint8_t v_size, uint8_t* res) { | |
//Build byte Payload in little endian order | |
*res = v_size / 2; | |
uint8_t c = 0; | |
for (uint8_t i = 1; i < (2 * v_size + 1); i += 2) { | |
*(res + i) = static_cast<uint8_t>((values[c] & 0x00FF) >> 0); | |
*(res + i + 1) = static_cast<uint8_t>((values[c] & 0xFF00) >> 8); | |
c++; | |
} | |
} | |
void print_payload(uint8_t* payload, uint8_t len) { | |
Serial.print(F("PLD: ")); | |
for (uint8_t i = 0; i < len; i++) { | |
if (i == 0) { | |
Serial.print(*payload, HEX); | |
} | |
else { | |
Serial.print(*(payload + i), HEX); | |
} | |
} | |
Serial.println(); | |
} | |
void sendActiveEpResp() { | |
/* | |
byte 0 sequence number | |
byte 1 status 00 success | |
byte 2-3 NWK little endian | |
byte 4 Number of active endpoints | |
List of active endpoints | |
*/ | |
cmd_frame_id = xbee.getNextFrameId(); | |
last_command = &sendActiveEpResp; | |
uint8_t len_payload = 5 + NUM_ENDPOINTS; | |
uint8_t payload[len_payload] = {cmd_seq_id, //Has to match requesting packet | |
0x00, | |
netAddr[1], | |
netAddr[0], | |
NUM_ENDPOINTS, | |
}; | |
uint8_t i = 5; | |
uint8_t cl_i = 0; | |
for (i; i < len_payload; i++) { | |
payload[i] = ENDPOINTS[cl_i].id; | |
cl_i++; | |
} | |
Serial.print(F("Actv Ep Rsp Pld: ")); | |
for (int j = 0; j < len_payload; j++) { | |
Serial.print(payload[j], HEX); | |
Serial.print(" "); | |
} | |
Serial.println(""); | |
exp_tx = ZBExplicitTxRequest(COORDINATOR64, | |
UKN_NET_ADDR, | |
0x00, //broadcast radius | |
0x00, //option | |
payload, //payload | |
len_payload, //payload length | |
cmd_frame_id, // frame ID | |
0x00, //src endpoint | |
0x00, //dest endpoint | |
ACTIVE_EP_RSP, //cluster ID | |
0x0000 //profile ID | |
); | |
xbee.send(exp_tx); | |
Serial.println(F("Send Actv Ep Resp")); | |
} | |
void sendSimpleDescRpt(uint8_t ep) { | |
/* | |
byte 0: Sequence number, match requesting packet | |
byte 1: status 00= Success | |
byte 2-3: NWK in little endian | |
byte 4: Length of Simple Descriptor Report | |
byte 5: End point report, which endpoint is being reported | |
byte 6-7: profile id in little endian 0x0104 | |
byte 7-8: Device Type in little endian, 0x0007 is combined interface see page 51 of Zigbee HA profile | |
byte 9: version number (App Dev) | |
byte 10: Input Cluster Count | |
byte [] List of output clusters in little endian format | |
byte n+1: Output Cluster Count | |
byte [] List of output clusters in little endian format | |
*/ | |
cmd_frame_id = xbee.getNextFrameId(); | |
uint8_t num_out = ENDPOINTS[(ep - 1)].GetNumOutClusters(); | |
uint8_t out_len = 0; | |
if (num_out > 0) | |
{ | |
out_len = 2 *num_out + 1; | |
} | |
uint8_t num_in = ENDPOINTS[(ep - 1)].GetNumInClusters(); | |
uint8_t in_len = 0; | |
if (num_in > 0) | |
{ | |
in_len = 2 * num_in + 1; | |
} | |
uint8_t pre_len = 11; | |
uint8_t payload_len = pre_len + out_len + in_len; | |
uint8_t pre[] = {cmd_seq_id, | |
0x00, | |
netAddr[1], | |
netAddr[0], | |
static_cast<uint8_t>(out_len + in_len + 6), //Length of simple descriptor | |
ep, | |
static_cast<uint8_t>((HA_PROFILE_ID & 0x00FF) >> 0), | |
static_cast<uint8_t>((HA_PROFILE_ID & 0xFF00) >> 8), | |
static_cast<uint8_t>((ENDPOINTS[(ep - 1)].GetDevType() & 0x00FF) >> 0), //Fix me | |
static_cast<uint8_t>((ENDPOINTS[(ep - 1)].GetDevType() & 0xFF00) >> 8), | |
0x01, //Don't Care (App Version) | |
}; | |
uint8_t in_clusters[in_len]; | |
/* | |
for (int j = 0; j < payload_len; j++) { | |
Serial.print(payload[j], HEX); | |
Serial.print(F(" ")); | |
} | |
Serial.println("");*/ | |
memcpy(t_payload, pre, pre_len); | |
//free(pre); | |
/* | |
for (int j = 0; j < payload_len; j++) { | |
Serial.print(t_payload[j], HEX); | |
Serial.print(F(" ")); | |
} | |
Serial.println(""); | |
*/ | |
//Serial.println(F("In Pld Strt")); | |
uint16_t in_cl[num_in]; | |
ENDPOINTS[(ep - 1)].GetInClusters(in_cl); | |
build_payload_list(in_cl, 2 * num_in, in_clusters); | |
//Serial.println(F("In Pld Fin")); | |
//free(in_cl); | |
//free(num_in); | |
/* for (int j = 0; j < in_len; j++) { | |
Serial.print(in_clusters[j], HEX); | |
Serial.print(" "); | |
} | |
Serial.println("");*/ | |
memcpy(t_payload + pre_len , in_clusters, sizeof(in_clusters)); | |
/* | |
for (int j = 0; j < payload_len; j++) { | |
Serial.print(t_payload[j], HEX); | |
Serial.print(" "); | |
} | |
Serial.println(""); | |
*/ | |
//Serial.println(F("Out Pld Strt")); | |
uint8_t out_clusters[out_len]; | |
uint16_t out_cl[num_out]; | |
ENDPOINTS[(ep - 1)].GetOutClusters(out_cl); | |
build_payload_list(out_cl, 2 * num_out, out_clusters); | |
//Serial.println(F("Out Pld Fin")); | |
//free(out_cl); | |
/* | |
for (int j = 0; j < out_len; j++) { | |
Serial.print(out_clusters[j], HEX); | |
Serial.print(" "); | |
} | |
Serial.println(""); | |
*/ | |
//Serial.println(F("Mem Cpy Fin Strt")); | |
memcpy(t_payload + pre_len + sizeof(in_clusters) , out_clusters, sizeof(out_clusters)); | |
//Serial.println(F("Mem Cpy Fin")); | |
/* | |
Serial.print(F("Smpl Desc Rsp Pld: ")); | |
for (int j = 0; j < payload_len; j++) { | |
Serial.print(t_payload[j], HEX); | |
Serial.print(F(" ")); | |
} | |
Serial.println(); | |
*/ | |
exp_tx = ZBExplicitTxRequest(COORDINATOR64, | |
UKN_NET_ADDR, | |
0x00, //broadcast radius | |
0x00, //option | |
t_payload, //payload | |
payload_len, //payload length | |
cmd_frame_id, // frame ID | |
0x00, //src endpoint | |
0x00, //dest endpoint | |
SIMPLE_DESC_RSP, //cluster ID | |
0x0000 //profile ID | |
); | |
xbee.send(exp_tx); | |
Serial.print(F("Send Smpl Desc Rpt: ")); | |
Serial.println(ep, HEX); | |
} | |
void sendBasicClusterResp() { | |
/*Not sure exactly on this one, | |
0x0004: ('manufacturer', t.LVBytes), | |
0x0005: ('model', t.LVBytes), | |
*/ | |
} | |
void getNetAddr() { | |
AtCommandRequest atRequest = AtCommandRequest(); | |
atRequest.setCommand((uint8_t*)netCmd); //breaking(uint8_t*)at | |
xbee.send(atRequest); | |
} | |
uint32_t packArray(uint8_t *val) { | |
uint32_t res = 0; | |
for (int i = 0; i < 4; i++) { | |
res = res << 8 | val[i]; | |
} | |
return res; | |
} | |
void getAssociation() { | |
AtCommandRequest atRequest = AtCommandRequest(); | |
atRequest.setCommand((uint8_t*)assocCmd); //breaking | |
xbee.send(atRequest); | |
} | |
void getMAC() { | |
uint8_t msb[4]; | |
uint8_t lsb[4]; | |
AtCommandResponse atResponse = AtCommandResponse(); | |
AtCommandRequest atRequest = AtCommandRequest(); | |
bool success = 0; | |
while (success == 0) { | |
atRequest.setCommand((uint8_t*)shCmd); //breaking | |
xbee.send(atRequest); | |
success = waitforResponse(msb); | |
} | |
success = 0; | |
while (success == 0) { | |
atRequest.setCommand((uint8_t*)slCmd); //breaking | |
xbee.send(atRequest); | |
success = waitforResponse(lsb); | |
} | |
macAddr.Set(XBeeAddress64(packArray(msb), packArray(lsb))); | |
} | |
bool waitforResponse(uint8_t *val) { | |
AtCommandResponse atResponse = AtCommandResponse(); | |
if (xbee.readPacket(5000)) { | |
if (xbee.getResponse().getApiId() == AT_COMMAND_RESPONSE) { | |
xbee.getResponse().getAtCommandResponse(atResponse); | |
if (atResponse.isOk()) { | |
if (atResponse.getValueLength() > 0) { | |
//Serial.print("Size of: "); | |
//Serial.println(atResponse.getValueLength()); | |
for (int i = 0; i < atResponse.getValueLength(); i++) { | |
val[i] = atResponse.getValue()[i]; | |
} | |
return 1; | |
} | |
} | |
else { | |
Serial.print(F("Cmd rtrn err: ")); | |
Serial.println(atResponse.getStatus(), HEX); | |
} | |
} else { | |
Serial.print(F("Exp AT got ")); | |
Serial.print(xbee.getResponse().getApiId(), HEX); | |
} | |
} else { | |
// at command failed | |
if (xbee.getResponse().isError()) { | |
Serial.print(F("Er rd pkt. EC: ")); | |
Serial.println(xbee.getResponse().getErrorCode()); | |
} | |
else { | |
Serial.print(F("No rsp")); | |
} | |
} | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment