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.

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