Fork of the espurna firmware for `mhsw` switches
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

341 lines
10 KiB

  1. /*
  2. RFM69 MODULE
  3. Copyright (C) 2016-2017 by Xose Pérez <xose dot perez at gmail dot com>
  4. */
  5. #include "rfm69.h"
  6. #if RFM69_SUPPORT
  7. #define RFM69_PACKET_SEPARATOR ':'
  8. #include <RFM69.h>
  9. #include <RFM69_ATC.h>
  10. #include <SPI.h>
  11. #include "mqtt.h"
  12. #include "ws.h"
  13. // -----------------------------------------------------------------------------
  14. // Locals
  15. // -----------------------------------------------------------------------------
  16. struct packet_t {
  17. unsigned long messageID;
  18. unsigned char packetID;
  19. unsigned char senderID;
  20. unsigned char targetID;
  21. char * key;
  22. char * value;
  23. int16_t rssi;
  24. };
  25. struct _node_t {
  26. unsigned long count = 0;
  27. unsigned long missing = 0;
  28. unsigned long duplicates = 0;
  29. unsigned char lastPacketID = 0;
  30. };
  31. _node_t _rfm69_node_info[RFM69_MAX_NODES];
  32. unsigned char _rfm69_node_count;
  33. unsigned long _rfm69_packet_count;
  34. void _rfm69Clear() {
  35. for(unsigned int i=0; i<RFM69_MAX_NODES; i++) {
  36. _rfm69_node_info[i].duplicates = 0;
  37. _rfm69_node_info[i].missing = 0;
  38. _rfm69_node_info[i].count = 0;
  39. }
  40. _rfm69_node_count = 0;
  41. _rfm69_packet_count = 0;
  42. }
  43. // -----------------------------------------------------------------------------
  44. class RFM69Wrap: public RFM69_ATC {
  45. public:
  46. RFM69Wrap(uint8_t slaveSelectPin=RF69_SPI_CS, uint8_t interruptPin=RF69_IRQ_PIN, bool isRFM69HW=false, uint8_t interruptNum=0):
  47. RFM69_ATC(slaveSelectPin, interruptPin, isRFM69HW, interruptNum) {};
  48. protected:
  49. // overriding SPI_CLOCK for ESP8266
  50. void select() {
  51. noInterrupts();
  52. #if defined (SPCR) && defined (SPSR)
  53. // save current SPI settings
  54. _SPCR = SPCR;
  55. _SPSR = SPSR;
  56. #endif
  57. // set RFM69 SPI settings
  58. SPI.setDataMode(SPI_MODE0);
  59. SPI.setBitOrder(MSBFIRST);
  60. #if defined(__arm__)
  61. SPI.setClockDivider(SPI_CLOCK_DIV16);
  62. #elif defined(ARDUINO_ARCH_ESP8266)
  63. SPI.setClockDivider(SPI_CLOCK_DIV2); // speeding it up for the ESP8266
  64. #else
  65. SPI.setClockDivider(SPI_CLOCK_DIV4);
  66. #endif
  67. digitalWrite(_slaveSelectPin, LOW);
  68. }
  69. };
  70. RFM69Wrap * _rfm69_radio;
  71. // -----------------------------------------------------------------------------
  72. // WEB
  73. // -----------------------------------------------------------------------------
  74. #if WEB_SUPPORT
  75. void _rfm69WebSocketOnConnected(JsonObject& root) {
  76. root["rfm69Visible"] = 1;
  77. root["rfm69Topic"] = getSetting("rfm69Topic", RFM69_DEFAULT_TOPIC);
  78. root["packetCount"] = _rfm69_packet_count;
  79. root["nodeCount"] = _rfm69_node_count;
  80. JsonArray& mappings = root.createNestedArray("mapping");
  81. for (unsigned char i=0; i<RFM69_MAX_TOPICS; i++) {
  82. auto node = getSetting({"node", i}, 0);
  83. if (0 == node) break;
  84. JsonObject& mapping = mappings.createNestedObject();
  85. mapping["node"] = node;
  86. mapping["key"] = getSetting({"key", i});
  87. mapping["topic"] = getSetting({"topic", i});
  88. }
  89. }
  90. bool _rfm69WebSocketOnKeyCheck(const char * key, JsonVariant& value) {
  91. if (strncmp(key, "rfm69", 5) == 0) return true;
  92. if (strncmp(key, "node", 4) == 0) return true;
  93. if (strncmp(key, "key", 3) == 0) return true;
  94. if (strncmp(key, "topic", 5) == 0) return true;
  95. return false;
  96. }
  97. void _rfm69WebSocketOnAction(uint32_t client_id, const char * action, JsonObject& data) {
  98. if (strcmp(action, "clear-counts") == 0) _rfm69Clear();
  99. }
  100. #endif // WEB_SUPPORT
  101. void _rfm69CleanNodes(unsigned char num) {
  102. // Look for the last defined node
  103. unsigned char id = 0;
  104. while (id < num) {
  105. if (0 == getSetting({"node", id}, 0)) break;
  106. if (!getSetting({"key", id}).length()) break;
  107. if (!getSetting({"topic", id}).length()) break;
  108. ++id;
  109. }
  110. // Delete all other settings
  111. while (id < SETTINGS_MAX_LIST_COUNT) {
  112. delSetting({"node", id});
  113. delSetting({"key", id});
  114. delSetting({"topic", id});
  115. ++id;
  116. }
  117. }
  118. void _rfm69Configure() {
  119. _rfm69CleanNodes(RFM69_MAX_TOPICS);
  120. }
  121. // -----------------------------------------------------------------------------
  122. // Radio
  123. // -----------------------------------------------------------------------------
  124. void _rfm69Debug(const char * level, packet_t * data) {
  125. DEBUG_MSG_P(
  126. PSTR("[RFM69] %s: messageID:%05d senderID:%03d targetID:%03d packetID:%03d rssi:%-04d key:%s value:%s\n"),
  127. level,
  128. data->messageID,
  129. data->senderID,
  130. data->targetID,
  131. data->packetID,
  132. data->rssi,
  133. data->key,
  134. data->value
  135. );
  136. }
  137. void _rfm69Process(packet_t * data) {
  138. // Is node beyond RFM69_MAX_NODES?
  139. if (data->senderID >= RFM69_MAX_NODES) return;
  140. // Count seen nodes
  141. if (_rfm69_node_info[data->senderID].count == 0) ++_rfm69_node_count;
  142. // Detect duplicates and missing packets
  143. // packetID==0 means device is not sending packetID info
  144. if (data->packetID > 0) {
  145. if (_rfm69_node_info[data->senderID].count > 0) {
  146. unsigned char gap = data->packetID - _rfm69_node_info[data->senderID].lastPacketID;
  147. if (gap == 0) {
  148. _rfm69_node_info[data->senderID].duplicates = _rfm69_node_info[data->senderID].duplicates + 1;
  149. //_rfm69Debug("DUP", data);
  150. return;
  151. }
  152. if ((gap > 1) && (data->packetID > 1)) {
  153. _rfm69_node_info[data->senderID].missing = _rfm69_node_info[data->senderID].missing + gap - 1;
  154. DEBUG_MSG_P(PSTR("[RFM69] %u missing packets detected\n"), gap - 1);
  155. }
  156. }
  157. }
  158. _rfm69Debug("OK ", data);
  159. _rfm69_node_info[data->senderID].lastPacketID = data->packetID;
  160. _rfm69_node_info[data->senderID].count = _rfm69_node_info[data->senderID].count + 1;
  161. // Send info to websocket clients
  162. {
  163. char buffer[200];
  164. snprintf_P(
  165. buffer,
  166. sizeof(buffer) - 1,
  167. PSTR("{\"nodeCount\": %d, \"packetCount\": %lu, \"packet\": {\"senderID\": %u, \"targetID\": %u, \"packetID\": %u, \"key\": \"%s\", \"value\": \"%s\", \"rssi\": %d, \"duplicates\": %d, \"missing\": %d}}"),
  168. _rfm69_node_count, _rfm69_packet_count,
  169. data->senderID, data->targetID, data->packetID, data->key, data->value, data->rssi,
  170. _rfm69_node_info[data->senderID].duplicates , _rfm69_node_info[data->senderID].missing);
  171. wsSend(buffer);
  172. }
  173. // If we are the target of the message, forward it via MQTT, otherwise quit
  174. if (!RFM69_PROMISCUOUS_SENDS && (RFM69_GATEWAY_ID != data->targetID)) return;
  175. // Try to find a matching mapping
  176. for (unsigned char i=0; i<RFM69_MAX_TOPICS; i++) {
  177. auto node = getSetting({"node", i}, 0);
  178. if (0 == node) break;
  179. if ((node == data->senderID) && (getSetting({"key", i}).equals(data->key))) {
  180. mqttSendRaw((char *) getSetting({"topic", i}).c_str(), (char *) String(data->value).c_str());
  181. return;
  182. }
  183. }
  184. // Mapping not found, use default topic
  185. String topic = getSetting("rfm69Topic", RFM69_DEFAULT_TOPIC);
  186. if (topic.length() > 0) {
  187. topic.replace("{node}", String(data->senderID));
  188. topic.replace("{key}", String(data->key));
  189. mqttSendRaw((char *) topic.c_str(), (char *) String(data->value).c_str());
  190. }
  191. }
  192. void _rfm69Loop() {
  193. if (_rfm69_radio->receiveDone()) {
  194. uint8_t senderID = _rfm69_radio->SENDERID;
  195. uint8_t targetID = _rfm69_radio->TARGETID;
  196. int16_t rssi = _rfm69_radio->RSSI;
  197. uint8_t length = _rfm69_radio->DATALEN;
  198. char buffer[length + 1];
  199. strncpy(buffer, (const char *) _rfm69_radio->DATA, length);
  200. buffer[length] = 0;
  201. // Do not send ACKs in promiscuous mode,
  202. // we want to listen without being heard
  203. if (!RFM69_PROMISCUOUS) {
  204. if (_rfm69_radio->ACKRequested()) _rfm69_radio->sendACK();
  205. }
  206. uint8_t parts = 1;
  207. for (uint8_t i=0; i<length; i++) {
  208. if (buffer[i] == RFM69_PACKET_SEPARATOR) ++parts;
  209. }
  210. if (parts > 1) {
  211. char sep[2] = {RFM69_PACKET_SEPARATOR, 0};
  212. uint8_t packetID = 0;
  213. char * key = strtok(buffer, sep);
  214. char * value = strtok(NULL, sep);
  215. if (parts > 2) {
  216. char * packet = strtok(NULL, sep);
  217. packetID = atoi(packet);
  218. }
  219. packet_t message;
  220. message.messageID = ++_rfm69_packet_count;
  221. message.packetID = packetID;
  222. message.senderID = senderID;
  223. message.targetID = targetID;
  224. message.key = key;
  225. message.value = value;
  226. message.rssi = rssi;
  227. _rfm69Process(&message);
  228. }
  229. }
  230. }
  231. // -----------------------------------------------------------------------------
  232. // RFM69
  233. // -----------------------------------------------------------------------------
  234. void rfm69Setup() {
  235. delay(10);
  236. _rfm69Configure();
  237. _rfm69_radio = new RFM69Wrap(RFM69_CS_PIN, RFM69_IRQ_PIN, RFM69_IS_RFM69HW, digitalPinToInterrupt(RFM69_IRQ_PIN));
  238. _rfm69_radio->initialize(RFM69_FREQUENCY, RFM69_NODE_ID, RFM69_NETWORK_ID);
  239. _rfm69_radio->encrypt(RFM69_ENCRYPTKEY);
  240. _rfm69_radio->promiscuous(RFM69_PROMISCUOUS);
  241. _rfm69_radio->enableAutoPower(0);
  242. if (RFM69_IS_RFM69HW) _rfm69_radio->setHighPower();
  243. DEBUG_MSG_P(PSTR("[RFM69] Worning at %u MHz\n"), RFM69_FREQUENCY == RF69_433MHZ ? 433 : RFM69_FREQUENCY == RF69_868MHZ ? 868 : 915);
  244. DEBUG_MSG_P(PSTR("[RFM69] Node %u\n"), RFM69_NODE_ID);
  245. DEBUG_MSG_P(PSTR("[RFM69] Network %u\n"), RFM69_NETWORK_ID);
  246. DEBUG_MSG_P(PSTR("[RFM69] Promiscuous mode %s\n"), RFM69_PROMISCUOUS ? "ON" : "OFF");
  247. #if WEB_SUPPORT
  248. wsRegister()
  249. .onConnected(_rfm69WebSocketOnConnected)
  250. .onAction(_rfm69WebSocketOnAction)
  251. .onKeyCheck(_rfm69WebSocketOnKeyCheck);
  252. #endif
  253. // Main callbacks
  254. espurnaRegisterLoop(_rfm69Loop);
  255. espurnaRegisterReload(_rfm69Configure);
  256. }
  257. #endif // RFM69_SUPPORT