xsns_29_mcp230xx.ino 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845
  1. /*
  2. xsns_29_mcp230xx.ino - Support for I2C MCP23008/MCP23017 GPIO Expander
  3. Copyright (C) 2018 Andre Thomas and Theo Arends
  4. This program is free software: you can redistribute it and/or modify
  5. it under the terms of the GNU General Public License as published by
  6. the Free Software Foundation, either version 3 of the License, or
  7. (at your option) any later version.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. GNU General Public License for more details.
  12. You should have received a copy of the GNU General Public License
  13. along with this program. If not, see <http://www.gnu.org/licenses/>.
  14. */
  15. #ifdef USE_I2C
  16. #ifdef USE_MCP230xx
  17. /*********************************************************************************************\
  18. MCP23008/17 - I2C GPIO EXPANDER
  19. Docs at https://www.microchip.com/wwwproducts/en/MCP23008
  20. https://www.microchip.com/wwwproducts/en/MCP23017
  21. I2C Address: 0x20 - 0x27
  22. \*********************************************************************************************/
  23. #define XSNS_29 29
  24. /*
  25. Default register locations for MCP23008 - They change for MCP23017 in default bank mode
  26. */
  27. uint8_t MCP230xx_IODIR = 0x00;
  28. uint8_t MCP230xx_GPINTEN = 0x02;
  29. uint8_t MCP230xx_IOCON = 0x05;
  30. uint8_t MCP230xx_GPPU = 0x06;
  31. uint8_t MCP230xx_INTF = 0x07;
  32. uint8_t MCP230xx_INTCAP = 0x08;
  33. uint8_t MCP230xx_GPIO = 0x09;
  34. uint8_t mcp230xx_type = 0;
  35. uint8_t mcp230xx_pincount = 0;
  36. uint8_t mcp230xx_int_en = 0;
  37. uint8_t mcp230xx_int_prio_counter = 0;
  38. uint8_t mcp230xx_int_counter_en = 0;
  39. uint8_t mcp230xx_int_retainer_en = 0;
  40. uint8_t mcp230xx_int_sec_counter = 0;
  41. uint8_t mcp230xx_int_report_defer_counter[16] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
  42. uint16_t mcp230xx_int_counter[16] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
  43. uint8_t mcp230xx_int_retainer[16] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; // Used to store if an interrupt occured that needs to be retained until teleperiod
  44. unsigned long int_millis[16]; // To keep track of millis() since last interrupt
  45. const char MCP230XX_SENSOR_RESPONSE[] PROGMEM = "{\"Sensor29_D%i\":{\"MODE\":%i,\"PULL_UP\":\"%s\",\"INT_MODE\":\"%s\",\"STATE\":\"%s\"}}";
  46. const char MCP230XX_INTCFG_RESPONSE[] PROGMEM = "{\"MCP230xx_INT%s\":{\"D_%i\":%i}}";
  47. #ifdef USE_MCP230xx_OUTPUT
  48. const char MCP230XX_CMND_RESPONSE[] PROGMEM = "{\"S29cmnd_D%i\":{\"COMMAND\":\"%s\",\"STATE\":\"%s\"}}";
  49. #endif // USE_MCP230xx_OUTPUT
  50. void MCP230xx_CheckForIntCounter(void) {
  51. uint8_t en = 0;
  52. for (uint8_t ca=0;ca<16;ca++) {
  53. if (Settings.mcp230xx_config[ca].int_count_en) {
  54. en=1;
  55. }
  56. }
  57. if (!Settings.mcp230xx_int_timer) en=0;
  58. mcp230xx_int_counter_en=en;
  59. if (!mcp230xx_int_counter_en) { // Interrupt counters are disabled, so we clear all the counters
  60. for (uint8_t ca=0;ca<16;ca++) {
  61. mcp230xx_int_counter[ca] = 0;
  62. }
  63. }
  64. }
  65. void MCP230xx_CheckForIntRetainer(void) {
  66. uint8_t en = 0;
  67. for (uint8_t ca=0;ca<16;ca++) {
  68. if (Settings.mcp230xx_config[ca].int_retain_flag) {
  69. en=1;
  70. }
  71. }
  72. mcp230xx_int_retainer_en=en;
  73. if (!mcp230xx_int_retainer_en) { // Interrupt counters are disabled, so we clear all the counters
  74. for (uint8_t ca=0;ca<16;ca++) {
  75. mcp230xx_int_retainer[ca] = 0;
  76. }
  77. }
  78. }
  79. const char* ConvertNumTxt(uint8_t statu, uint8_t pinmod=0) {
  80. #ifdef USE_MCP230xx_OUTPUT
  81. if ((6 == pinmod) && (statu < 2)) { statu = abs(statu-1); }
  82. #endif // USE_MCP230xx_OUTPUT
  83. switch (statu) {
  84. case 0:
  85. return "OFF";
  86. break;
  87. case 1:
  88. return "ON";
  89. break;
  90. #ifdef USE_MCP230xx_OUTPUT
  91. case 2:
  92. return "TOGGLE";
  93. break;
  94. #endif // USE_MCP230xx_OUTPUT
  95. }
  96. return "";
  97. }
  98. const char* IntModeTxt(uint8_t intmo) {
  99. switch (intmo) {
  100. case 0:
  101. return "ALL";
  102. break;
  103. case 1:
  104. return "EVENT";
  105. break;
  106. case 2:
  107. return "TELE";
  108. break;
  109. case 3:
  110. return "DISABLED";
  111. break;
  112. }
  113. return "";
  114. }
  115. uint8_t MCP230xx_readGPIO(uint8_t port) {
  116. return I2cRead8(USE_MCP230xx_ADDR, MCP230xx_GPIO + port);
  117. }
  118. void MCP230xx_ApplySettings(void) {
  119. uint8_t int_en = 0;
  120. for (uint8_t mcp230xx_port=0;mcp230xx_port<mcp230xx_type;mcp230xx_port++) {
  121. uint8_t reg_gppu = 0;
  122. uint8_t reg_gpinten = 0;
  123. uint8_t reg_iodir = 0xFF;
  124. #ifdef USE_MCP230xx_OUTPUT
  125. uint8_t reg_portpins = 0x00;
  126. #endif // USE_MCP230xx_OUTPUT
  127. for (uint8_t idx = 0; idx < 8; idx++) {
  128. switch (Settings.mcp230xx_config[idx+(mcp230xx_port*8)].pinmode) {
  129. case 0 ... 1:
  130. reg_iodir |= (1 << idx);
  131. break;
  132. case 2 ... 4:
  133. reg_iodir |= (1 << idx);
  134. reg_gpinten |= (1 << idx);
  135. int_en = 1;
  136. break;
  137. #ifdef USE_MCP230xx_OUTPUT
  138. case 5 ... 6:
  139. reg_iodir &= ~(1 << idx);
  140. if (Settings.flag.save_state) { // Firmware configuration wants us to use the last pin state
  141. reg_portpins |= (Settings.mcp230xx_config[idx+(mcp230xx_port*8)].saved_state << idx);
  142. } else {
  143. if (Settings.mcp230xx_config[idx+(mcp230xx_port*8)].pullup) {
  144. reg_portpins |= (1 << idx);
  145. }
  146. }
  147. break;
  148. #endif // USE_MCP230xx_OUTPUT
  149. default:
  150. break;
  151. }
  152. #ifdef USE_MCP230xx_OUTPUT
  153. if ((Settings.mcp230xx_config[idx+(mcp230xx_port*8)].pullup) && (Settings.mcp230xx_config[idx+(mcp230xx_port*8)].pinmode < 5)) {
  154. reg_gppu |= (1 << idx);
  155. }
  156. #else // not USE_MCP230xx_OUTPUT
  157. if (Settings.mcp230xx_config[idx+(mcp230xx_port*8)].pullup) {
  158. reg_gppu |= (1 << idx);
  159. }
  160. #endif // USE_MCP230xx_OUTPUT
  161. }
  162. I2cWrite8(USE_MCP230xx_ADDR, MCP230xx_GPPU+mcp230xx_port, reg_gppu);
  163. I2cWrite8(USE_MCP230xx_ADDR, MCP230xx_GPINTEN+mcp230xx_port, reg_gpinten);
  164. I2cWrite8(USE_MCP230xx_ADDR, MCP230xx_IODIR+mcp230xx_port, reg_iodir);
  165. #ifdef USE_MCP230xx_OUTPUT
  166. I2cWrite8(USE_MCP230xx_ADDR, MCP230xx_GPIO+mcp230xx_port, reg_portpins);
  167. #endif // USE_MCP230xx_OUTPUT
  168. }
  169. for (uint8_t idx=0;idx<mcp230xx_pincount;idx++) {
  170. int_millis[idx]=millis();
  171. }
  172. mcp230xx_int_en = int_en;
  173. MCP230xx_CheckForIntCounter(); // update register on whether or not we should be counting interrupts
  174. MCP230xx_CheckForIntRetainer(); // update register on whether or not we should be retaining interrupt events for teleperiod
  175. }
  176. void MCP230xx_Detect(void)
  177. {
  178. if (mcp230xx_type) {
  179. return;
  180. }
  181. uint8_t buffer;
  182. I2cWrite8(USE_MCP230xx_ADDR, MCP230xx_IOCON, 0x80); // attempt to set bank mode - this will only work on MCP23017, so its the best way to detect the different chips 23008 vs 23017
  183. if (I2cValidRead8(&buffer, USE_MCP230xx_ADDR, MCP230xx_IOCON)) {
  184. if (0x00 == buffer) {
  185. mcp230xx_type = 1; // We have a MCP23008
  186. snprintf_P(log_data, sizeof(log_data), S_LOG_I2C_FOUND_AT, "MCP23008", USE_MCP230xx_ADDR);
  187. AddLog(LOG_LEVEL_DEBUG);
  188. mcp230xx_pincount = 8;
  189. MCP230xx_ApplySettings();
  190. } else {
  191. if (0x80 == buffer) {
  192. mcp230xx_type = 2; // We have a MCP23017
  193. snprintf_P(log_data, sizeof(log_data), S_LOG_I2C_FOUND_AT, "MCP23017", USE_MCP230xx_ADDR);
  194. AddLog(LOG_LEVEL_DEBUG);
  195. mcp230xx_pincount = 16;
  196. // Reset bank mode to 0
  197. I2cWrite8(USE_MCP230xx_ADDR, MCP230xx_IOCON, 0x00);
  198. // Update register locations for MCP23017
  199. MCP230xx_GPINTEN = 0x04;
  200. MCP230xx_GPPU = 0x0C;
  201. MCP230xx_INTF = 0x0E;
  202. MCP230xx_INTCAP = 0x10;
  203. MCP230xx_GPIO = 0x12;
  204. MCP230xx_ApplySettings();
  205. }
  206. }
  207. }
  208. }
  209. void MCP230xx_CheckForInterrupt(void) {
  210. uint8_t intf;
  211. uint8_t mcp230xx_intcap = 0;
  212. uint8_t report_int;
  213. for (uint8_t mcp230xx_port=0;mcp230xx_port<mcp230xx_type;mcp230xx_port++) {
  214. if (I2cValidRead8(&intf,USE_MCP230xx_ADDR,MCP230xx_INTF+mcp230xx_port)) {
  215. if (intf > 0) {
  216. if (I2cValidRead8(&mcp230xx_intcap, USE_MCP230xx_ADDR, MCP230xx_INTCAP+mcp230xx_port)) {
  217. for (uint8_t intp = 0; intp < 8; intp++) {
  218. if ((intf >> intp) & 0x01) { // we know which pin caused interrupt
  219. report_int = 0;
  220. if (Settings.mcp230xx_config[intp+(mcp230xx_port*8)].pinmode > 1) {
  221. switch (Settings.mcp230xx_config[intp+(mcp230xx_port*8)].pinmode) {
  222. case 2:
  223. report_int = 1;
  224. break;
  225. case 3:
  226. if (((mcp230xx_intcap >> intp) & 0x01) == 0) report_int = 1; // Int on LOW
  227. break;
  228. case 4:
  229. if (((mcp230xx_intcap >> intp) & 0x01) == 1) report_int = 1; // Int on HIGH
  230. break;
  231. default:
  232. break;
  233. }
  234. // Check for interrupt counter
  235. if ((mcp230xx_int_counter_en) && (report_int)) { // We may have some counting to do
  236. if (Settings.mcp230xx_config[intp+(mcp230xx_port*8)].int_count_en) { // Indeed, for this pin
  237. mcp230xx_int_counter[intp+(mcp230xx_port*8)]++;
  238. }
  239. }
  240. // check for interrupt defer on this pin
  241. if (report_int) {
  242. if (Settings.mcp230xx_config[intp+(mcp230xx_port*8)].int_report_defer) {
  243. mcp230xx_int_report_defer_counter[intp+(mcp230xx_port*8)]++;
  244. if (mcp230xx_int_report_defer_counter[intp+(mcp230xx_port*8)] >= Settings.mcp230xx_config[intp+(mcp230xx_port*8)].int_report_defer) {
  245. mcp230xx_int_report_defer_counter[intp+(mcp230xx_port*8)]=0;
  246. } else {
  247. report_int = 0; // defer int report for now
  248. }
  249. }
  250. }
  251. // check if interrupt retain is used, if it is for this pin then we do not report immediately as it will be reported in teleperiod
  252. if (report_int) {
  253. if (Settings.mcp230xx_config[intp+(mcp230xx_port*8)].int_retain_flag) {
  254. mcp230xx_int_retainer[intp+(mcp230xx_port*8)] = 1;
  255. report_int = 0; // do not report for now
  256. }
  257. }
  258. if (Settings.mcp230xx_config[intp+(mcp230xx_port*8)].int_count_en) { // We do not want to report via tele or event if counting is enabled
  259. report_int = 0;
  260. }
  261. if (report_int) {
  262. bool int_tele = false;
  263. bool int_event = false;
  264. unsigned long millis_now = millis();
  265. unsigned long millis_since_last_int = millis_now - int_millis[intp+(mcp230xx_port*8)];
  266. int_millis[intp+(mcp230xx_port*8)]=millis_now;
  267. switch (Settings.mcp230xx_config[intp+(mcp230xx_port*8)].int_report_mode) {
  268. case 0:
  269. int_tele=true;
  270. int_event=true;
  271. break;
  272. case 1:
  273. int_event=true;
  274. break;
  275. case 2:
  276. int_tele=true;
  277. break;
  278. }
  279. if (int_tele) {
  280. snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"" D_JSON_TIME "\":\"%s\""), GetDateAndTime(DT_LOCAL).c_str());
  281. snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s,\"MCP230XX_INT\":{\"D%i\":%i,\"MS\":%lu}"), mqtt_data, intp+(mcp230xx_port*8), ((mcp230xx_intcap >> intp) & 0x01),millis_since_last_int);
  282. snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s}"), mqtt_data);
  283. MqttPublishPrefixTopic_P(RESULT_OR_STAT, PSTR("MCP230XX_INT"));
  284. }
  285. if (int_event) {
  286. char command[19]; // Theoretical max = 'event MCPINT_D16=1' so 18 + 1 (for the \n)
  287. sprintf(command,"event MCPINT_D%i=%i",intp+(mcp230xx_port*8),((mcp230xx_intcap >> intp) & 0x01));
  288. ExecuteCommand(command, SRC_RULE);
  289. }
  290. }
  291. }
  292. }
  293. }
  294. }
  295. }
  296. }
  297. }
  298. }
  299. void MCP230xx_Show(boolean json)
  300. {
  301. if (mcp230xx_type) {
  302. if (json) {
  303. if (mcp230xx_type > 0) { // we have at least 8 pins
  304. uint8_t gpio = MCP230xx_readGPIO(0);
  305. snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s,\"MCP230XX\":{\"D0\":%i,\"D1\":%i,\"D2\":%i,\"D3\":%i,\"D4\":%i,\"D5\":%i,\"D6\":%i,\"D7\":%i"),
  306. mqtt_data,(gpio>>0)&1,(gpio>>1)&1,(gpio>>2)&1,(gpio>>3)&1,(gpio>>4)&1,(gpio>>5)&1,(gpio>>6)&1,(gpio>>7)&1);
  307. if (2 == mcp230xx_type) {
  308. gpio = MCP230xx_readGPIO(1);
  309. snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s,\"D8\":%i,\"D9\":%i,\"D10\":%i,\"D11\":%i,\"D12\":%i,\"D13\":%i,\"D14\":%i,\"D15\":%i"),
  310. mqtt_data,(gpio>>0)&1,(gpio>>1)&1,(gpio>>2)&1,(gpio>>3)&1,(gpio>>4)&1,(gpio>>5)&1,(gpio>>6)&1,(gpio>>7)&1);
  311. }
  312. snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s%s"),mqtt_data,"}");
  313. }
  314. }
  315. }
  316. }
  317. #ifdef USE_MCP230xx_OUTPUT
  318. void MCP230xx_SetOutPin(uint8_t pin,uint8_t pinstate) {
  319. uint8_t portpins;
  320. uint8_t port = 0;
  321. uint8_t pinmo = Settings.mcp230xx_config[pin].pinmode;
  322. uint8_t interlock = Settings.flag.interlock;
  323. int pinadd = (pin % 2)+1-(3*(pin % 2)); //check if pin is odd or even and convert to 1 (if even) or -1 (if odd)
  324. char cmnd[7], stt[4];
  325. if (pin > 7) { port = 1; }
  326. portpins = MCP230xx_readGPIO(port);
  327. if (interlock && (pinmo == Settings.mcp230xx_config[pin+pinadd].pinmode)) {
  328. if (pinstate < 2) {
  329. if (6 == pinmo) {
  330. if (pinstate) portpins |= (1 << (pin-(port*8))); else portpins |= (1 << (pin+pinadd-(port*8))),portpins &= ~(1 << (pin-(port*8)));
  331. } else {
  332. if (pinstate) portpins &= ~(1 << (pin+pinadd-(port*8))),portpins |= (1 << (pin-(port*8))); else portpins &= ~(1 << (pin-(port*8)));
  333. }
  334. } else {
  335. if (6 == pinmo) {
  336. portpins |= (1 << (pin+pinadd-(port*8))),portpins ^= (1 << (pin-(port*8)));
  337. } else {
  338. portpins &= ~(1 << (pin+pinadd-(port*8))),portpins ^= (1 << (pin-(port*8)));
  339. }
  340. }
  341. } else {
  342. if (pinstate < 2) {
  343. if (pinstate) portpins |= (1 << (pin-(port*8))); else portpins &= ~(1 << (pin-(port*8)));
  344. } else {
  345. portpins ^= (1 << (pin-(port*8)));
  346. }
  347. }
  348. I2cWrite8(USE_MCP230xx_ADDR, MCP230xx_GPIO + port, portpins);
  349. if (Settings.flag.save_state) { // Firmware configured to save last known state in settings
  350. Settings.mcp230xx_config[pin].saved_state=portpins>>(pin-(port*8))&1;
  351. Settings.mcp230xx_config[pin+pinadd].saved_state=portpins>>(pin+pinadd-(port*8))&1;
  352. }
  353. sprintf(cmnd,ConvertNumTxt(pinstate, pinmo));
  354. sprintf(stt,ConvertNumTxt((portpins >> (pin-(port*8))&1), pinmo));
  355. if (interlock && (pinmo == Settings.mcp230xx_config[pin+pinadd].pinmode)) {
  356. char stt1[4];
  357. sprintf(stt1,ConvertNumTxt((portpins >> (pin+pinadd-(port*8))&1), pinmo));
  358. snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"S29cmnd_D%i\":{\"COMMAND\":\"%s\",\"STATE\":\"%s\"},\"S29cmnd_D%i\":{\"STATE\":\"%s\"}}"),pin, cmnd, stt, pin+pinadd, stt1);
  359. } else {
  360. snprintf_P(mqtt_data, sizeof(mqtt_data), MCP230XX_CMND_RESPONSE, pin, cmnd, stt);
  361. }
  362. }
  363. #endif // USE_MCP230xx_OUTPUT
  364. void MCP230xx_Reset(uint8_t pinmode) {
  365. uint8_t pullup = 0;
  366. if ((pinmode > 1) && (pinmode < 5)) { pullup=1; }
  367. for (uint8_t pinx=0;pinx<16;pinx++) {
  368. Settings.mcp230xx_config[pinx].pinmode=pinmode;
  369. Settings.mcp230xx_config[pinx].pullup=pullup;
  370. Settings.mcp230xx_config[pinx].saved_state=0;
  371. if ((pinmode > 1) && (pinmode < 5)) {
  372. Settings.mcp230xx_config[pinx].int_report_mode=0; // Enabled for ALL by default
  373. } else {
  374. Settings.mcp230xx_config[pinx].int_report_mode=3; // Disabled for pinmode 1, 5 and 6 (No interrupts there)
  375. }
  376. Settings.mcp230xx_config[pinx].int_report_defer=0; // Disabled
  377. Settings.mcp230xx_config[pinx].int_count_en=0; // Disabled by default
  378. Settings.mcp230xx_config[pinx].int_retain_flag=0; // Disabled by default
  379. Settings.mcp230xx_config[pinx].spare13=0;
  380. Settings.mcp230xx_config[pinx].spare14=0;
  381. Settings.mcp230xx_config[pinx].spare15=0;
  382. }
  383. Settings.mcp230xx_int_prio = 0; // Once per FUNC_EVERY_50_MSECOND callback
  384. Settings.mcp230xx_int_timer = 0;
  385. MCP230xx_ApplySettings();
  386. char pulluptxt[7];
  387. char intmodetxt[9];
  388. sprintf(pulluptxt,ConvertNumTxt(pullup));
  389. uint8_t intmode = 3;
  390. if ((pinmode > 1) && (pinmode < 5)) { intmode = 0; }
  391. sprintf(intmodetxt,IntModeTxt(intmode));
  392. snprintf_P(mqtt_data, sizeof(mqtt_data), MCP230XX_SENSOR_RESPONSE,99,pinmode,pulluptxt,intmodetxt,"");
  393. }
  394. bool MCP230xx_Command(void) {
  395. boolean serviced = true;
  396. boolean validpin = false;
  397. uint8_t paramcount = 0;
  398. if (XdrvMailbox.data_len > 0) {
  399. paramcount=1;
  400. } else {
  401. serviced = false;
  402. return serviced;
  403. }
  404. char sub_string[XdrvMailbox.data_len];
  405. for (uint8_t ca=0;ca<XdrvMailbox.data_len;ca++) {
  406. if ((' ' == XdrvMailbox.data[ca]) || ('=' == XdrvMailbox.data[ca])) { XdrvMailbox.data[ca] = ','; }
  407. if (',' == XdrvMailbox.data[ca]) { paramcount++; }
  408. }
  409. UpperCase(XdrvMailbox.data,XdrvMailbox.data);
  410. if (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 1),"RESET")) { MCP230xx_Reset(1); return serviced; }
  411. if (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 1),"RESET1")) { MCP230xx_Reset(1); return serviced; }
  412. if (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 1),"RESET2")) { MCP230xx_Reset(2); return serviced; }
  413. if (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 1),"RESET3")) { MCP230xx_Reset(3); return serviced; }
  414. if (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 1),"RESET4")) { MCP230xx_Reset(4); return serviced; }
  415. #ifdef USE_MCP230xx_OUTPUT
  416. if (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 1),"RESET5")) { MCP230xx_Reset(5); return serviced; }
  417. if (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 1),"RESET6")) { MCP230xx_Reset(6); return serviced; }
  418. #endif // USE_MCP230xx_OUTPUT
  419. if (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 1),"INTPRI")) {
  420. if (paramcount > 1) {
  421. uint8_t intpri = atoi(subStr(sub_string, XdrvMailbox.data, ",", 2));
  422. if ((intpri >= 0) && (intpri <= 20)) {
  423. Settings.mcp230xx_int_prio = intpri;
  424. snprintf_P(mqtt_data, sizeof(mqtt_data), MCP230XX_INTCFG_RESPONSE,"PRI",99,Settings.mcp230xx_int_prio); // "{\"MCP230xx_INT%s\":{\"D_%i\":%i}}";
  425. return serviced;
  426. }
  427. } else { // No parameter was given for INTPRI so we return the current configured value
  428. snprintf_P(mqtt_data, sizeof(mqtt_data), MCP230XX_INTCFG_RESPONSE,"PRI",99,Settings.mcp230xx_int_prio); // "{\"MCP230xx_INT%s\":{\"D_%i\":%i}}";
  429. return serviced;
  430. }
  431. }
  432. if (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 1),"INTTIMER")) {
  433. if (paramcount > 1) {
  434. uint8_t inttim = atoi(subStr(sub_string, XdrvMailbox.data, ",", 2));
  435. if ((inttim >= 0) && (inttim <= 3600)) {
  436. Settings.mcp230xx_int_timer = inttim;
  437. MCP230xx_CheckForIntCounter(); // update register on whether or not we should be counting interrupts
  438. snprintf_P(mqtt_data, sizeof(mqtt_data), MCP230XX_INTCFG_RESPONSE,"TIMER",99,Settings.mcp230xx_int_timer); // "{\"MCP230xx_INT%s\":{\"D_%i\":%i}}";
  439. return serviced;
  440. }
  441. } else { // No parameter was given for INTTIM so we return the current configured value
  442. snprintf_P(mqtt_data, sizeof(mqtt_data), MCP230XX_INTCFG_RESPONSE,"TIMER",99,Settings.mcp230xx_int_timer); // "{\"MCP230xx_INT%s\":{\"D_%i\":%i}}";
  443. return serviced;
  444. }
  445. }
  446. if (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 1),"INTDEF")) {
  447. if (paramcount > 1) {
  448. uint8_t pin = atoi(subStr(sub_string, XdrvMailbox.data, ",", 2));
  449. if (pin < mcp230xx_pincount) {
  450. if (pin == 0) {
  451. if (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 2), "0")) validpin=true;
  452. } else {
  453. validpin = true;
  454. }
  455. }
  456. if (validpin) {
  457. if (paramcount > 2) {
  458. uint8_t intdef = atoi(subStr(sub_string, XdrvMailbox.data, ",", 3));
  459. if ((intdef >= 0) && (intdef <= 15)) {
  460. Settings.mcp230xx_config[pin].int_report_defer=intdef;
  461. if (Settings.mcp230xx_config[pin].int_count_en) {
  462. Settings.mcp230xx_config[pin].int_count_en=0;
  463. MCP230xx_CheckForIntCounter();
  464. snprintf_P(log_data, sizeof(log_data), PSTR("*** WARNING *** - Disabled INTCNT for pin D%i"),pin);
  465. AddLog(LOG_LEVEL_INFO);
  466. }
  467. snprintf_P(mqtt_data, sizeof(mqtt_data), MCP230XX_INTCFG_RESPONSE,"DEF",pin,Settings.mcp230xx_config[pin].int_report_defer); // "{\"MCP230xx_INT%s\":{\"D_%i\":%i}}";
  468. return serviced;
  469. } else {
  470. serviced=false;
  471. return serviced;
  472. }
  473. } else {
  474. snprintf_P(mqtt_data, sizeof(mqtt_data), MCP230XX_INTCFG_RESPONSE,"DEF",pin,Settings.mcp230xx_config[pin].int_report_defer); // "{\"MCP230xx_INT%s\":{\"D_%i\":%i}}";
  475. return serviced;
  476. }
  477. }
  478. serviced = false;
  479. return serviced;
  480. } else {
  481. serviced = false;
  482. return serviced;
  483. }
  484. }
  485. if (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 1),"INTCNT")) {
  486. if (paramcount > 1) {
  487. uint8_t pin = atoi(subStr(sub_string, XdrvMailbox.data, ",", 2));
  488. if (pin < mcp230xx_pincount) {
  489. if (pin == 0) {
  490. if (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 2), "0")) validpin=true;
  491. } else {
  492. validpin = true;
  493. }
  494. }
  495. if (validpin) {
  496. if (paramcount > 2) {
  497. uint8_t intcnt = atoi(subStr(sub_string, XdrvMailbox.data, ",", 3));
  498. if ((intcnt >= 0) && (intcnt <= 1)) {
  499. Settings.mcp230xx_config[pin].int_count_en=intcnt;
  500. if (Settings.mcp230xx_config[pin].int_report_defer) {
  501. Settings.mcp230xx_config[pin].int_report_defer=0;
  502. snprintf_P(log_data, sizeof(log_data), PSTR("*** WARNING *** - Disabled INTDEF for pin D%i"),pin);
  503. AddLog(LOG_LEVEL_INFO);
  504. }
  505. if (Settings.mcp230xx_config[pin].int_report_mode < 3) {
  506. Settings.mcp230xx_config[pin].int_report_mode=3;
  507. snprintf_P(log_data, sizeof(log_data), PSTR("*** WARNING *** - Disabled immediate interrupt/telemetry reporting for pin D%i"),pin);
  508. AddLog(LOG_LEVEL_INFO);
  509. }
  510. if ((Settings.mcp230xx_config[pin].int_count_en) && (!Settings.mcp230xx_int_timer)) {
  511. snprintf_P(log_data, sizeof(log_data), PSTR("*** WARNING *** - INTCNT enabled for pin D%i but global INTTIMER is disabled!"),pin);
  512. AddLog(LOG_LEVEL_INFO);
  513. }
  514. MCP230xx_CheckForIntCounter(); // update register on whether or not we should be counting interrupts
  515. snprintf_P(mqtt_data, sizeof(mqtt_data), MCP230XX_INTCFG_RESPONSE,"CNT",pin,Settings.mcp230xx_config[pin].int_count_en); // "{\"MCP230xx_INT%s\":{\"D_%i\":%i}}";
  516. return serviced;
  517. } else {
  518. serviced=false;
  519. return serviced;
  520. }
  521. } else {
  522. snprintf_P(mqtt_data, sizeof(mqtt_data), MCP230XX_INTCFG_RESPONSE,"CNT",pin,Settings.mcp230xx_config[pin].int_count_en); // "{\"MCP230xx_INT%s\":{\"D_%i\":%i}}";
  523. return serviced;
  524. }
  525. }
  526. serviced = false;
  527. return serviced;
  528. } else {
  529. serviced = false;
  530. return serviced;
  531. }
  532. }
  533. if (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 1),"INTRETAIN")) {
  534. if (paramcount > 1) {
  535. uint8_t pin = atoi(subStr(sub_string, XdrvMailbox.data, ",", 2));
  536. if (pin < mcp230xx_pincount) {
  537. if (pin == 0) {
  538. if (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 2), "0")) validpin=true;
  539. } else {
  540. validpin = true;
  541. }
  542. }
  543. if (validpin) {
  544. if (paramcount > 2) {
  545. uint8_t int_retain = atoi(subStr(sub_string, XdrvMailbox.data, ",", 3));
  546. if ((int_retain >= 0) && (int_retain <= 1)) {
  547. Settings.mcp230xx_config[pin].int_retain_flag=int_retain;
  548. snprintf_P(mqtt_data, sizeof(mqtt_data), MCP230XX_INTCFG_RESPONSE,"INT_RETAIN",pin,Settings.mcp230xx_config[pin].int_retain_flag);
  549. MCP230xx_CheckForIntRetainer();
  550. return serviced;
  551. } else {
  552. serviced=false;
  553. return serviced;
  554. }
  555. } else {
  556. snprintf_P(mqtt_data, sizeof(mqtt_data), MCP230XX_INTCFG_RESPONSE,"INT_RETAIN",pin,Settings.mcp230xx_config[pin].int_retain_flag);
  557. return serviced;
  558. }
  559. }
  560. serviced = false;
  561. return serviced;
  562. } else {
  563. serviced = false;
  564. return serviced;
  565. }
  566. }
  567. uint8_t pin = atoi(subStr(sub_string, XdrvMailbox.data, ",", 1));
  568. if (pin < mcp230xx_pincount) {
  569. if (0 == pin) {
  570. if (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 1), "0")) validpin=true;
  571. } else {
  572. validpin=true;
  573. }
  574. }
  575. if (validpin && (paramcount > 1)) {
  576. if (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 2), "?")) {
  577. uint8_t port = 0;
  578. if (pin > 7) { port = 1; }
  579. uint8_t portdata = MCP230xx_readGPIO(port);
  580. char pulluptxtr[7],pinstatustxtr[7];
  581. char intmodetxt[9];
  582. sprintf(intmodetxt,IntModeTxt(Settings.mcp230xx_config[pin].int_report_mode));
  583. sprintf(pulluptxtr,ConvertNumTxt(Settings.mcp230xx_config[pin].pullup));
  584. #ifdef USE_MCP230xx_OUTPUT
  585. uint8_t pinmod = Settings.mcp230xx_config[pin].pinmode;
  586. sprintf(pinstatustxtr,ConvertNumTxt(portdata>>(pin-(port*8))&1,pinmod));
  587. snprintf_P(mqtt_data, sizeof(mqtt_data), MCP230XX_SENSOR_RESPONSE,pin,pinmod,pulluptxtr,intmodetxt,pinstatustxtr);
  588. #else // not USE_MCP230xx_OUTPUT
  589. sprintf(pinstatustxtr,ConvertNumTxt(portdata>>(pin-(port*8))&1));
  590. snprintf_P(mqtt_data, sizeof(mqtt_data), MCP230XX_SENSOR_RESPONSE,pin,Settings.mcp230xx_config[pin].pinmode,pulluptxtr,intmodetxt,pinstatustxtr);
  591. #endif //USE_MCP230xx_OUTPUT
  592. return serviced;
  593. }
  594. #ifdef USE_MCP230xx_OUTPUT
  595. if (Settings.mcp230xx_config[pin].pinmode >= 5) {
  596. uint8_t pincmd = Settings.mcp230xx_config[pin].pinmode - 5;
  597. if (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 2), "ON")) {
  598. MCP230xx_SetOutPin(pin,abs(pincmd-1));
  599. return serviced;
  600. }
  601. if (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 2), "OFF")) {
  602. MCP230xx_SetOutPin(pin,pincmd);
  603. return serviced;
  604. }
  605. if (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 2), "T")) {
  606. MCP230xx_SetOutPin(pin,2);
  607. return serviced;
  608. }
  609. }
  610. #endif // USE_MCP230xx_OUTPUT
  611. uint8_t pinmode = 0;
  612. uint8_t pullup = 0;
  613. uint8_t intmode = 0;
  614. if (paramcount > 1) {
  615. pinmode = atoi(subStr(sub_string, XdrvMailbox.data, ",", 2));
  616. }
  617. if (paramcount > 2) {
  618. pullup = atoi(subStr(sub_string, XdrvMailbox.data, ",", 3));
  619. }
  620. if (paramcount > 3) {
  621. intmode = atoi(subStr(sub_string, XdrvMailbox.data, ",", 4));
  622. }
  623. #ifdef USE_MCP230xx_OUTPUT
  624. if ((pin < mcp230xx_pincount) && (pinmode > 0) && (pinmode < 7) && (pullup < 2)) {
  625. #else // not use OUTPUT
  626. if ((pin < mcp230xx_pincount) && (pinmode > 0) && (pinmode < 5) && (pullup < 2)) {
  627. #endif // USE_MCP230xx_OUTPUT
  628. Settings.mcp230xx_config[pin].pinmode=pinmode;
  629. Settings.mcp230xx_config[pin].pullup=pullup;
  630. if ((pinmode > 1) && (pinmode < 5)) {
  631. if ((intmode >= 0) && (intmode <= 3)) {
  632. Settings.mcp230xx_config[pin].int_report_mode=intmode;
  633. }
  634. } else {
  635. Settings.mcp230xx_config[pin].int_report_mode=3; // Int mode not valid for pinmodes other than 2 through 4
  636. }
  637. MCP230xx_ApplySettings();
  638. uint8_t port = 0;
  639. if (pin > 7) { port = 1; }
  640. uint8_t portdata = MCP230xx_readGPIO(port);
  641. char pulluptxtc[7], pinstatustxtc[7];
  642. char intmodetxt[9];
  643. sprintf(pulluptxtc,ConvertNumTxt(pullup));
  644. sprintf(intmodetxt,IntModeTxt(Settings.mcp230xx_config[pin].int_report_mode));
  645. #ifdef USE_MCP230xx_OUTPUT
  646. sprintf(pinstatustxtc,ConvertNumTxt(portdata>>(pin-(port*8))&1,Settings.mcp230xx_config[pin].pinmode));
  647. #else // not USE_MCP230xx_OUTPUT
  648. sprintf(pinstatustxtc,ConvertNumTxt(portdata>>(pin-(port*8))&1));
  649. #endif // USE_MCP230xx_OUTPUT
  650. snprintf_P(mqtt_data, sizeof(mqtt_data), MCP230XX_SENSOR_RESPONSE,pin,pinmode,pulluptxtc,intmodetxt,pinstatustxtc);
  651. return serviced;
  652. }
  653. } else {
  654. serviced=false; // no valid pin was used
  655. return serviced;
  656. }
  657. return serviced;
  658. }
  659. #ifdef USE_MCP230xx_DISPLAYOUTPUT
  660. const char HTTP_SNS_MCP230xx_OUTPUT[] PROGMEM = "%s{s}MCP230XX D%d{m}%s{e}"; // {s} = <tr><th>, {m} = </th><td>, {e} = </td></tr>
  661. void MCP230xx_UpdateWebData(void) {
  662. uint8_t gpio1 = MCP230xx_readGPIO(0);
  663. uint8_t gpio2 = 0;
  664. if (2 == mcp230xx_type) {
  665. gpio2 = MCP230xx_readGPIO(1);
  666. }
  667. uint16_t gpio = (gpio2 << 8) + gpio1;
  668. for (uint8_t pin = 0; pin < mcp230xx_pincount; pin++) {
  669. if (Settings.mcp230xx_config[pin].pinmode >= 5) {
  670. char stt[7];
  671. sprintf(stt,ConvertNumTxt((gpio>>pin)&1,Settings.mcp230xx_config[pin].pinmode));
  672. snprintf_P(mqtt_data, sizeof(mqtt_data), HTTP_SNS_MCP230xx_OUTPUT, mqtt_data, pin, stt);
  673. }
  674. }
  675. }
  676. #endif // USE_MCP230xx_DISPLAYOUTPUT
  677. #ifdef USE_MCP230xx_OUTPUT
  678. void MCP230xx_OutputTelemetry(void) {
  679. if (0 == mcp230xx_type) { return; } // We do not do this if the MCP has not been detected
  680. uint8_t outputcount = 0;
  681. uint16_t gpiototal = 0;
  682. uint8_t gpioa = 0;
  683. uint8_t gpiob = 0;
  684. gpioa=MCP230xx_readGPIO(0);
  685. if (2 == mcp230xx_type) { gpiob=MCP230xx_readGPIO(1); }
  686. gpiototal=((uint16_t)gpiob << 8) | gpioa;
  687. for (uint8_t pinx = 0;pinx < mcp230xx_pincount;pinx++) {
  688. if (Settings.mcp230xx_config[pinx].pinmode >= 5) outputcount++;
  689. }
  690. if (outputcount) {
  691. char stt[7];
  692. snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"" D_JSON_TIME "\":\"%s\",\"MCP230_OUT\": {"), GetDateAndTime(DT_LOCAL).c_str());
  693. for (uint8_t pinx = 0;pinx < mcp230xx_pincount;pinx++) {
  694. if (Settings.mcp230xx_config[pinx].pinmode >= 5) {
  695. sprintf(stt,ConvertNumTxt(((gpiototal>>pinx)&1),Settings.mcp230xx_config[pinx].pinmode));
  696. snprintf_P(mqtt_data,sizeof(mqtt_data), PSTR("%s\"OUT_D%i\":\"%s\","),mqtt_data,pinx,stt);
  697. }
  698. }
  699. snprintf_P(mqtt_data,sizeof(mqtt_data),PSTR("%s\"END\":1}}"),mqtt_data);
  700. MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR), Settings.flag.mqtt_sensor_retain);
  701. }
  702. }
  703. #endif // USE_MCP230xx_OUTPUT
  704. void MCP230xx_Interrupt_Counter_Report(void) {
  705. snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"" D_JSON_TIME "\":\"%s\",\"MCP230_INTTIMER\": {"), GetDateAndTime(DT_LOCAL).c_str());
  706. for (uint8_t pinx = 0;pinx < mcp230xx_pincount;pinx++) {
  707. if (Settings.mcp230xx_config[pinx].int_count_en) { // Counting is enabled for this pin so we add to report
  708. snprintf_P(mqtt_data,sizeof(mqtt_data), PSTR("%s\"INTCNT_D%i\":%i,"),mqtt_data,pinx,mcp230xx_int_counter[pinx]);
  709. mcp230xx_int_counter[pinx]=0;
  710. }
  711. }
  712. snprintf_P(mqtt_data,sizeof(mqtt_data),PSTR("%s\"END\":1}}"),mqtt_data);
  713. MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR), Settings.flag.mqtt_sensor_retain);
  714. mcp230xx_int_sec_counter = 0;
  715. }
  716. void MCP230xx_Interrupt_Retain_Report(void) {
  717. uint16_t retainresult = 0;
  718. snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"" D_JSON_TIME "\":\"%s\",\"MCP_INTRETAIN\": {"), GetDateAndTime(DT_LOCAL).c_str());
  719. for (uint8_t pinx = 0;pinx < mcp230xx_pincount;pinx++) {
  720. if (Settings.mcp230xx_config[pinx].int_retain_flag) {
  721. snprintf_P(mqtt_data,sizeof(mqtt_data), PSTR("%s\"D%i\":%i,"),mqtt_data,pinx,mcp230xx_int_retainer[pinx]);
  722. retainresult |= (((mcp230xx_int_retainer[pinx])&1) << pinx);
  723. mcp230xx_int_retainer[pinx]=0;
  724. }
  725. }
  726. snprintf_P(mqtt_data,sizeof(mqtt_data),PSTR("%s\"Value\":%u}}"),mqtt_data,retainresult);
  727. MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR), Settings.flag.mqtt_sensor_retain);
  728. }
  729. /*********************************************************************************************\
  730. Interface
  731. \*********************************************************************************************/
  732. boolean Xsns29(byte function)
  733. {
  734. boolean result = false;
  735. if (i2c_flg) {
  736. switch (function) {
  737. case FUNC_MQTT_DATA:
  738. break;
  739. case FUNC_EVERY_SECOND:
  740. MCP230xx_Detect();
  741. if (mcp230xx_int_counter_en) {
  742. mcp230xx_int_sec_counter++;
  743. if (mcp230xx_int_sec_counter >= Settings.mcp230xx_int_timer) { // Interrupt counter interval reached, lets report
  744. MCP230xx_Interrupt_Counter_Report();
  745. }
  746. }
  747. if (tele_period == 0) {
  748. if (mcp230xx_int_retainer_en) { // We have pins configured for interrupt retain reporting
  749. MCP230xx_Interrupt_Retain_Report();
  750. }
  751. }
  752. #ifdef USE_MCP230xx_OUTPUT
  753. if (tele_period == 0) {
  754. MCP230xx_OutputTelemetry();
  755. }
  756. #endif // USE_MCP230xx_OUTPUT
  757. break;
  758. case FUNC_EVERY_50_MSECOND:
  759. if ((mcp230xx_int_en) && (mcp230xx_type)) { // Only check for interrupts if its enabled on one of the pins
  760. mcp230xx_int_prio_counter++;
  761. if ((mcp230xx_int_prio_counter) >= (Settings.mcp230xx_int_prio)) {
  762. MCP230xx_CheckForInterrupt();
  763. mcp230xx_int_prio_counter=0;
  764. }
  765. }
  766. break;
  767. case FUNC_JSON_APPEND:
  768. MCP230xx_Show(1);
  769. break;
  770. case FUNC_COMMAND:
  771. if (XSNS_29 == XdrvMailbox.index) {
  772. result = MCP230xx_Command();
  773. }
  774. break;
  775. #ifdef USE_WEBSERVER
  776. #ifdef USE_MCP230xx_OUTPUT
  777. #ifdef USE_MCP230xx_DISPLAYOUTPUT
  778. case FUNC_WEB_APPEND:
  779. MCP230xx_UpdateWebData();
  780. break;
  781. #endif // USE_MCP230xx_DISPLAYOUTPUT
  782. #endif // USE_MCP230xx_OUTPUT
  783. #endif // USE_WEBSERVER
  784. default:
  785. break;
  786. }
  787. }
  788. return result;
  789. }
  790. #endif // USE_MCP230xx
  791. #endif // USE_I2C