main.c 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424
  1. //
  2. // MAX7219 demo program
  3. //
  4. // Copyright (c) 2018 BitBank Software, Inc.
  5. // Written by Larry Bank
  6. // email: bitbank@pobox.com
  7. // Project started 3/10/2018
  8. //
  9. // This program is free software: you can redistribute it and/or modify
  10. // it under the terms of the GNU General Public License as published by
  11. // the Free Software Foundation, either version 3 of the License, or
  12. // (at your option) any later version.
  13. //
  14. // This program is distributed in the hope that it will be useful,
  15. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17. // GNU General Public License for more details.
  18. //
  19. // You should have received a copy of the GNU General Public License
  20. // along with this program. If not, see <http://www.gnu.org/licenses/>.
  21. //
  22. //
  23. #include <stdio.h>
  24. #include <stdlib.h>
  25. #include <stdint.h>
  26. #include <string.h>
  27. #include <unistd.h>
  28. #include <time.h>
  29. #include <math.h>
  30. #include <MQTTClient.h>
  31. #include<json-c/json.h>
  32. #include "max7219.h"
  33. #include "helper.h"
  34. #include "types.h"
  35. #define ADDRESS "tcp://localhost:1883"
  36. #define CLIENTID "max7219_c"
  37. #define QOS 1
  38. #define TIMEOUT 10000L
  39. volatile MQTTClient_deliveryToken deliveredtoken;
  40. struct scrolltext_t stWeather, stDisplay, stTime, stDate;
  41. int drawString(struct scrolltext_t *st, char *cTemp) {
  42. if(st->width+6 >= SCROLL_BUF_SIZE) {
  43. printf("ERROR: no space for scrolltext\n");
  44. return 0;
  45. }
  46. char buf[64];
  47. snprintf(buf, sizeof(buf), "%s", cTemp);
  48. int width = strlen(buf)*6;
  49. if(width + st->width > SCROLL_BUF_SIZE) {
  50. width = (SCROLL_BUF_SIZE - st->width)/6*6;
  51. buf[width/6] = '\0';
  52. printf("WARNING: scrolltext cropped\n");
  53. }
  54. maxScrollBitmap(st->bImg, SCROLL_BUF_SIZE/8, (st->width % 8));
  55. maxDrawString(buf, (st->bImg) + st->width/8, SCROLL_BUF_SIZE/8, 1); // draw narrow digits
  56. maxScrollBitmap(st->bImg, SCROLL_BUF_SIZE/8, -(st->width % 8));
  57. printf("new scroll text, offset: %d:%d, width: %d, text: %s\n", st->width/8, st->width%8, width, buf);
  58. st->width = st->width + width;
  59. return width;
  60. }
  61. int drawGraph(struct scrolltext_t *st, struct weather_t data[], size_t field_offset, int count, float min, float max) {
  62. int width = st->width;
  63. for(int i=0; i<count; i++) {
  64. if(data[i].valid) {
  65. if(i == 31 && st->width + sizeof(EASE_IN) < SCROLL_BUF_SIZE) {
  66. memcpy(st->frameCount + st->width-sizeof(EASE_OUT)+2, EASE_OUT, sizeof(EASE_OUT)-1);
  67. memcpy(st->frameCount + st->width+1, EASE_IN, sizeof(EASE_IN)-1);
  68. st->frameCount[st->width+1] = 0xFF;
  69. }
  70. int col = st->width++;
  71. float field = *((float*)(&data[i].valid+field_offset/sizeof(float)));
  72. int val = fmax(fmin((field-min) / (max-min) * 8, 7), 0);
  73. st->bImg[col/8 + (7-val)*SCROLL_BUF_SIZE/8] |= 0x80 >> (col%8);
  74. }
  75. }
  76. return st->width - width;
  77. }
  78. void stReset(struct scrolltext_t *st) {
  79. memset(st->bImg, 0, SCROLL_BUF_SIZE);
  80. st->width = 0;
  81. st->frame = 0;
  82. memset(st->frameCount, 2, sizeof(st->frameCount));
  83. memcpy(st->frameCount, EASE_IN, sizeof(EASE_IN)-1);
  84. }
  85. void delivered(void *context, MQTTClient_deliveryToken dt)
  86. {
  87. printf("Message with token value %d delivery confirmed\n", dt);
  88. deliveredtoken = dt;
  89. }
  90. int msgarrvd(void *context, char *topicName, int topicLen, MQTTClient_message *message)
  91. {
  92. int i;
  93. char* payloadptr;
  94. printf("Message arrived\n");
  95. printf(" topic: %s\n", topicName);
  96. printf(" message: ");
  97. umlauts(message->payload, message->payloadlen);
  98. payloadptr = message->payload;
  99. for(i=0; i<message->payloadlen; i++)
  100. {
  101. putchar(*payloadptr);
  102. if(*payloadptr > 0x7F)
  103. printf("[%X]", *payloadptr);
  104. payloadptr++;
  105. if (i >= 300) break;
  106. }
  107. putchar('\n');
  108. if(strcmp(topicName, "Room/display") == 0) {
  109. char displayMsg[128];
  110. snprintf(displayMsg, sizeof(displayMsg), "%s", (char*)message->payload);
  111. stReset(&stDisplay);
  112. drawString(&stDisplay, displayMsg);
  113. } else if(strcmp(topicName, "Timer/weather") == 0) {
  114. struct json_object *parsed_json, *detail, *tempc, *humidity, *clouds, *windspeed;
  115. parsed_json = json_tokener_parse(message->payload);
  116. json_object_object_get_ex(parsed_json, "detail", &detail);
  117. json_object_object_get_ex(parsed_json, "tempc", &tempc);
  118. json_object_object_get_ex(parsed_json, "humidity", &humidity);
  119. json_object_object_get_ex(parsed_json, "clouds", &clouds);
  120. json_object_object_get_ex(parsed_json, "windspeed", &windspeed);
  121. char weatherMsg[128];
  122. snprintf(
  123. weatherMsg, sizeof(weatherMsg),
  124. "%s %.1f'C LF %d%% WLK %d%% %.1f m/s",
  125. json_object_get_string(detail),
  126. json_object_get_double(tempc),
  127. json_object_get_int(humidity),
  128. json_object_get_int(clouds),
  129. json_object_get_double(windspeed)
  130. );
  131. } else if(strcmp(topicName, "Timer/forecast") == 0) {
  132. time_t rawtime;
  133. time(&rawtime);
  134. struct json_object *parsed_json, *forecast, *tmp, *fmain, *weather, *clouds, *rain, *snow, *wind;
  135. parsed_json = json_tokener_parse(message->payload);
  136. int count = json_object_array_length(parsed_json);
  137. if(count > 40)
  138. count = 40;
  139. struct weather_t data[40], maxData;
  140. memset(data, 0, sizeof(data));
  141. memset(&maxData, 0, sizeof(maxData));
  142. int dataPoints = 0;
  143. float temp_min = 100, rain_sum = 0, snow_sum = 0;
  144. for(int i=0; i<count; i++) {
  145. forecast = json_object_array_get_idx(parsed_json, i);
  146. json_object_object_get_ex(forecast, "dt", &tmp);
  147. time_t timestamp = json_object_get_uint64(tmp);
  148. if(timestamp > rawtime) {
  149. data[i].valid = 1;
  150. dataPoints++;
  151. data[i].dt = timestamp;
  152. maxData.dt = timestamp;
  153. } else {
  154. continue;
  155. }
  156. float val;
  157. json_object_object_get_ex(forecast, "main", &fmain);
  158. if(json_object_object_get_ex(fmain, "temp", &tmp)) {
  159. val = json_object_get_double(tmp);
  160. data[i].temp = val;
  161. if(val > maxData.temp) maxData.temp = val;
  162. if(val < temp_min) temp_min = val;
  163. }
  164. json_object_object_get_ex(forecast, "weather", &weather);
  165. if(json_object_object_get_ex(json_object_array_get_idx(weather, 0), "description", &tmp)) {
  166. //const char *desc = json_object_get_string(tmp);
  167. }
  168. json_object_object_get_ex(forecast, "clouds", &clouds);
  169. if(json_object_object_get_ex(clouds, "all", &tmp)) {
  170. val = json_object_get_double(tmp);
  171. data[i].clouds = val;
  172. if(val > maxData.clouds) maxData.clouds = val;
  173. }
  174. json_object_object_get_ex(forecast, "wind", &wind);
  175. if(json_object_object_get_ex(wind, "speed", &tmp)) {
  176. val = json_object_get_double(tmp);
  177. data[i].wind = val;
  178. if(val > maxData.wind) maxData.wind = val;
  179. }
  180. json_object_object_get_ex(forecast, "rain", &rain);
  181. if(json_object_object_get_ex(rain, "3h", &tmp)) {
  182. val = json_object_get_double(tmp);
  183. data[i].rain = val;
  184. if(val > maxData.rain) maxData.rain = val;
  185. rain_sum += val;
  186. }
  187. json_object_object_get_ex(forecast, "snow", &snow);
  188. if(json_object_object_get_ex(snow, "3h", &tmp)) {
  189. val = json_object_get_double(tmp);
  190. data[i].snow = val;
  191. if(val > maxData.snow) maxData.snow = val;
  192. snow_sum += val;
  193. }
  194. }
  195. printf(
  196. "dataPoints: %d, maxTemp: %.1f°C, maxClouds: %.1f%%, maxWind: %.1fm/s, rain: %.1fmm, snow: %.1fmm\n",
  197. dataPoints,
  198. maxData.temp,
  199. maxData.clouds,
  200. maxData.wind,
  201. rain_sum,
  202. snow_sum
  203. );
  204. char tmpStr[32];
  205. struct tm *maxTm = localtime(&maxData.dt);
  206. stReset(&stWeather);
  207. sprintf(tmpStr, "Wetter bis %d.%d.: ", maxTm->tm_mday, maxTm->tm_mon+1);
  208. drawString(&stWeather, tmpStr);
  209. sprintf(tmpStr, "%.0f'C - %.0f'C ", temp_min, maxData.temp);
  210. drawString(&stWeather, tmpStr);
  211. drawGraph(&stWeather, data, offsetof(struct weather_t, temp), count, temp_min, maxData.temp);
  212. if(maxData.wind > 3.0) {
  213. sprintf(tmpStr, " Wind: %.1fm/s ", maxData.wind);
  214. drawString(&stWeather, tmpStr);
  215. drawGraph(&stWeather, data, offsetof(struct weather_t, wind), count, 0, maxData.wind);
  216. }
  217. if(maxData.clouds > 10.0) {
  218. sprintf(tmpStr, " Wlk: %.1f%% ", maxData.clouds);
  219. drawString(&stWeather, tmpStr);
  220. drawGraph(&stWeather, data, offsetof(struct weather_t, clouds), count, 0, maxData.clouds);
  221. }
  222. if(rain_sum > 1.0) {
  223. sprintf(tmpStr, " Regen: %.1fmm ", rain_sum);
  224. drawString(&stWeather, tmpStr);
  225. drawGraph(&stWeather, data, offsetof(struct weather_t, rain), count, 0, maxData.rain);
  226. }
  227. if(snow_sum > 1.0) {
  228. sprintf(tmpStr, " Schnee: %.1fmm ", snow_sum);
  229. drawString(&stWeather, tmpStr);
  230. drawGraph(&stWeather, data, offsetof(struct weather_t, snow), count, 0, maxData.snow);
  231. }
  232. json_object_put(parsed_json);
  233. }
  234. MQTTClient_freeMessage(&message);
  235. MQTTClient_free(topicName);
  236. return 1;
  237. }
  238. void connlost(void *context, char *cause)
  239. {
  240. printf("\nConnection lost\n");
  241. printf(" cause: %s\n", cause);
  242. }
  243. void drawTime(uint8_t *bImg, time_t *t) {
  244. struct tm *now_tm = localtime(t);
  245. char cTemp[6];
  246. snprintf(
  247. cTemp, sizeof(cTemp),
  248. "%2d%c%02d",
  249. now_tm->tm_hour,
  250. now_tm->tm_sec%2 ? ':' : ' ',
  251. now_tm->tm_min
  252. );
  253. memset(bImg, 0, 4*8);
  254. maxDrawString(cTemp, bImg, 4, 1);
  255. }
  256. void drawDate(uint8_t *bImg, time_t *t) {
  257. struct tm *now_tm = localtime(t);
  258. char cTemp[10];
  259. snprintf(
  260. cTemp, sizeof(cTemp),
  261. "%hhu.%hhu.",
  262. now_tm->tm_mday,
  263. now_tm->tm_mon+1
  264. );
  265. memset(bImg, 0, 4*8);
  266. cTemp[6] = '\0';
  267. maxDrawString(cTemp, bImg, 4, 1);
  268. }
  269. int drawScroll(uint8_t *bImg, struct scrolltext_t *st) {
  270. for(int i=0; i<8; i++) {
  271. if(st->offset < st->width && (st->bImg[i*SCROLL_BUF_SIZE/8 + st->offset/8] & (0x80 >> st->offset%8))) {
  272. bImg[i*4+3] |= 0x01;
  273. } else {
  274. bImg[i*4+3] &= 0xFE;
  275. }
  276. }
  277. return st->offset != 0;
  278. }
  279. int moveScroll(uint8_t *bImgCurrent, uint8_t *bImgTarget, struct scrolltext_t *st) {
  280. int busy = 0;
  281. int frames = (st->offset == 0) ? 0 : (st->frameCount)[st->offset-1];
  282. if(st->width > 32 && ++(st->frame) >= frames) {
  283. st->frame = 0;
  284. maxScrollBitmap(bImgCurrent, 4, 1);
  285. maxScrollBitmap(bImgTarget, 4, 1);
  286. for(int i=0; i<8; i++) bImgCurrent[i*4+3] &= 0xFE;
  287. busy = drawScroll(bImgTarget, st);
  288. if(++(st->offset) >= st->width+32 || st->offset >= SCROLL_BUF_SIZE) {
  289. st->offset = 0;
  290. }
  291. } else if(st->width > 32) {
  292. st->frame += 1;
  293. busy = 2;
  294. } else {
  295. st->offset = 0;
  296. for(int i=0; i<8; i++) {
  297. memcpy(&bImgTarget[i*4], &(st->bImg)[i*SCROLL_BUF_SIZE/8], 4);
  298. }
  299. }
  300. return busy;
  301. }
  302. int main(int argc, char* argv[]) {
  303. int rc;
  304. time_t rawtime, oldTime = 0;
  305. uint8_t bImgTarget[4*8], bImgCurrent[4*8];
  306. uint8_t pixelPositions[4*8*8];
  307. srand((unsigned) time(&rawtime));
  308. // Initialize the library
  309. // num controllers, BCD mode, SPI channel, GPIO pin number for CS
  310. rc = maxInit(4, 0, 0, 22);
  311. if (rc != 0)
  312. {
  313. printf("Problem initializing max7219\n");
  314. return 0;
  315. }
  316. maxSetIntensity(0);
  317. MQTTClient client;
  318. MQTTClient_connectOptions conn_opts = MQTTClient_connectOptions_initializer;
  319. MQTTClient_create(&client, ADDRESS, CLIENTID,
  320. MQTTCLIENT_PERSISTENCE_NONE, NULL);
  321. conn_opts.keepAliveInterval = 20;
  322. conn_opts.cleansession = 1;
  323. MQTTClient_setCallbacks(client, NULL, connlost, msgarrvd, delivered);
  324. if ((rc = MQTTClient_connect(client, &conn_opts)) != MQTTCLIENT_SUCCESS)
  325. {
  326. printf("Failed to connect, return code %d\n", rc);
  327. exit(EXIT_FAILURE);
  328. }
  329. MQTTClient_subscribe(client, "Timer/#", QOS);
  330. MQTTClient_subscribe(client, "Room/display", QOS);
  331. uint32_t busy = 0;
  332. while(1) {
  333. if(!busy)
  334. time(&rawtime);
  335. if(rawtime - oldTime < 10) {
  336. drawTime(bImgTarget, &rawtime);
  337. } else if(rawtime - oldTime < 40) {
  338. drawDate(bImgTarget, &rawtime);
  339. } else if(rawtime - oldTime < 50) {
  340. busy = moveScroll(bImgCurrent, bImgTarget, &stDisplay);
  341. } else if(rawtime - oldTime < 60) {
  342. busy = moveScroll(bImgCurrent, bImgTarget, &stWeather);
  343. } else {
  344. busy = 0;
  345. oldTime = rawtime;
  346. }
  347. // find pixels to change
  348. int pixelsToChange = 0;
  349. for(int i=0; i<4*8; i++) {
  350. if(bImgCurrent[i] != bImgTarget[i]) {
  351. uint8_t diff = bImgCurrent[i] ^ bImgTarget[i];
  352. // iterate bits
  353. int p = 0;
  354. while(diff) {
  355. if(diff & 1) {
  356. pixelPositions[pixelsToChange++] = i*8 + p;
  357. }
  358. p++;
  359. diff >>= 1;
  360. }
  361. }
  362. }
  363. if(pixelsToChange) {
  364. // shuffle list
  365. // if(pixelsToChange > 1) {
  366. // for(int i=0; i<pixelsToChange-1; i++) {
  367. // int j = i + rand() / (RAND_MAX / (pixelsToChange - i) + 1);
  368. // uint8_t t = pixelPositions[j];
  369. // pixelPositions[j] = pixelPositions[i];
  370. // pixelPositions[i] = t;
  371. // }
  372. // }
  373. // animate change
  374. for(int i=0; i<(busy?2:1); i++) {
  375. int pos = pixelPositions[rand() % pixelsToChange];
  376. uint8_t bit = bImgTarget[pos/8] & (1 << (pos%8));
  377. if(bit)
  378. bImgCurrent[pos/8] |= bit;
  379. else
  380. bImgCurrent[pos/8] &= ~(1 << (pos%8));
  381. }
  382. }
  383. if(pixelsToChange || busy==1) {
  384. if(argc > 1)
  385. debugPrint(bImgCurrent, 4);
  386. maxSendImage(bImgCurrent, 4, 1);
  387. }
  388. usleep(1000000 / 50);
  389. }
  390. // Quit library and free resources
  391. maxShutdown();
  392. MQTTClient_disconnect(client, 10000);
  393. MQTTClient_destroy(&client);
  394. return rc;
  395. } /* main() */