// // MAX7219 demo program // // Copyright (c) 2018 BitBank Software, Inc. // Written by Larry Bank // email: bitbank@pobox.com // Project started 3/10/2018 // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . // // #include #include #include #include #include #include #include #include #include #include "max7219.h" #include "helper.h" #include "types.h" #define ADDRESS "tcp://localhost:1883" #define CLIENTID "max7219_c" #define QOS 1 #define TIMEOUT 10000L volatile MQTTClient_deliveryToken deliveredtoken; struct scrolltext_t stWeather, stDisplay, stTime, stDate; int drawString(struct scrolltext_t *st, char *cTemp) { if(st->width+6 >= SCROLL_BUF_SIZE) { printf("ERROR: no space for scrolltext\n"); return 0; } char buf[64]; snprintf(buf, sizeof(buf), "%s", cTemp); int width = strlen(buf)*6; if(width + st->width > SCROLL_BUF_SIZE) { width = (SCROLL_BUF_SIZE - st->width)/6*6; buf[width/6] = '\0'; printf("WARNING: scrolltext cropped\n"); } maxScrollBitmap(st->bImg, SCROLL_BUF_SIZE/8, (st->width % 8)); maxDrawString(buf, (st->bImg) + st->width/8, SCROLL_BUF_SIZE/8, 1); // draw narrow digits maxScrollBitmap(st->bImg, SCROLL_BUF_SIZE/8, -(st->width % 8)); printf("new scroll text, offset: %d:%d, width: %d, text: %s\n", st->width/8, st->width%8, width, buf); st->width = st->width + width; return width; } int drawGraph(struct scrolltext_t *st, struct weather_t data[], size_t field_offset, int count, float min, float max) { int width = st->width; for(int i=0; iwidth + sizeof(EASE_IN) < SCROLL_BUF_SIZE) { memcpy(st->frameCount + st->width-sizeof(EASE_OUT)+2, EASE_OUT, sizeof(EASE_OUT)-1); memcpy(st->frameCount + st->width+1, EASE_IN, sizeof(EASE_IN)-1); st->frameCount[st->width+1] = 0xFF; } int col = st->width++; float field = *((float*)(&data[i].valid+field_offset/sizeof(float))); int val = fmax(fmin((field-min) / (max-min) * 8, 7), 0); st->bImg[col/8 + (7-val)*SCROLL_BUF_SIZE/8] |= 0x80 >> (col%8); } } return st->width - width; } void stReset(struct scrolltext_t *st) { memset(st->bImg, 0, SCROLL_BUF_SIZE); st->width = 0; st->frame = 0; memset(st->frameCount, 2, sizeof(st->frameCount)); memcpy(st->frameCount, EASE_IN, sizeof(EASE_IN)-1); } void delivered(void *context, MQTTClient_deliveryToken dt) { printf("Message with token value %d delivery confirmed\n", dt); deliveredtoken = dt; } int msgarrvd(void *context, char *topicName, int topicLen, MQTTClient_message *message) { int i; char* payloadptr; printf("Message arrived\n"); printf(" topic: %s\n", topicName); printf(" message: "); umlauts(message->payload, message->payloadlen); payloadptr = message->payload; for(i=0; ipayloadlen; i++) { putchar(*payloadptr); if(*payloadptr > 0x7F) printf("[%X]", *payloadptr); payloadptr++; if (i >= 300) break; } putchar('\n'); if(strcmp(topicName, "Room/display") == 0) { char displayMsg[128]; snprintf(displayMsg, sizeof(displayMsg), "%s", (char*)message->payload); stReset(&stDisplay); drawString(&stDisplay, displayMsg); } else if(strcmp(topicName, "Timer/weather") == 0) { struct json_object *parsed_json, *detail, *tempc, *humidity, *clouds, *windspeed; parsed_json = json_tokener_parse(message->payload); json_object_object_get_ex(parsed_json, "detail", &detail); json_object_object_get_ex(parsed_json, "tempc", &tempc); json_object_object_get_ex(parsed_json, "humidity", &humidity); json_object_object_get_ex(parsed_json, "clouds", &clouds); json_object_object_get_ex(parsed_json, "windspeed", &windspeed); char weatherMsg[128]; snprintf( weatherMsg, sizeof(weatherMsg), "%s %.1f'C LF %d%% WLK %d%% %.1f m/s", json_object_get_string(detail), json_object_get_double(tempc), json_object_get_int(humidity), json_object_get_int(clouds), json_object_get_double(windspeed) ); } else if(strcmp(topicName, "Timer/forecast") == 0) { time_t rawtime; time(&rawtime); struct json_object *parsed_json, *forecast, *tmp, *fmain, *weather, *clouds, *rain, *snow, *wind; parsed_json = json_tokener_parse(message->payload); int count = json_object_array_length(parsed_json); if(count > 40) count = 40; struct weather_t data[40], maxData; memset(data, 0, sizeof(data)); memset(&maxData, 0, sizeof(maxData)); int dataPoints = 0; float temp_min = 100, rain_sum = 0, snow_sum = 0; for(int i=0; i rawtime) { data[i].valid = 1; dataPoints++; data[i].dt = timestamp; maxData.dt = timestamp; } else { continue; } float val; json_object_object_get_ex(forecast, "main", &fmain); if(json_object_object_get_ex(fmain, "temp", &tmp)) { val = json_object_get_double(tmp); data[i].temp = val; if(val > maxData.temp) maxData.temp = val; if(val < temp_min) temp_min = val; } json_object_object_get_ex(forecast, "weather", &weather); if(json_object_object_get_ex(json_object_array_get_idx(weather, 0), "description", &tmp)) { //const char *desc = json_object_get_string(tmp); } json_object_object_get_ex(forecast, "clouds", &clouds); if(json_object_object_get_ex(clouds, "all", &tmp)) { val = json_object_get_double(tmp); data[i].clouds = val; if(val > maxData.clouds) maxData.clouds = val; } json_object_object_get_ex(forecast, "wind", &wind); if(json_object_object_get_ex(wind, "speed", &tmp)) { val = json_object_get_double(tmp); data[i].wind = val; if(val > maxData.wind) maxData.wind = val; } json_object_object_get_ex(forecast, "rain", &rain); if(json_object_object_get_ex(rain, "3h", &tmp)) { val = json_object_get_double(tmp); data[i].rain = val; if(val > maxData.rain) maxData.rain = val; rain_sum += val; } json_object_object_get_ex(forecast, "snow", &snow); if(json_object_object_get_ex(snow, "3h", &tmp)) { val = json_object_get_double(tmp); data[i].snow = val; if(val > maxData.snow) maxData.snow = val; snow_sum += val; } } printf( "dataPoints: %d, maxTemp: %.1f°C, maxClouds: %.1f%%, maxWind: %.1fm/s, rain: %.1fmm, snow: %.1fmm\n", dataPoints, maxData.temp, maxData.clouds, maxData.wind, rain_sum, snow_sum ); char tmpStr[32]; struct tm *maxTm = localtime(&maxData.dt); stReset(&stWeather); sprintf(tmpStr, "Wetter bis %d.%d.: ", maxTm->tm_mday, maxTm->tm_mon+1); drawString(&stWeather, tmpStr); sprintf(tmpStr, "%.0f'C - %.0f'C ", temp_min, maxData.temp); drawString(&stWeather, tmpStr); drawGraph(&stWeather, data, offsetof(struct weather_t, temp), count, temp_min, maxData.temp); if(maxData.wind > 3.0) { sprintf(tmpStr, " Wind: %.1fm/s ", maxData.wind); drawString(&stWeather, tmpStr); drawGraph(&stWeather, data, offsetof(struct weather_t, wind), count, 0, maxData.wind); } if(maxData.clouds > 10.0) { sprintf(tmpStr, " Wlk: %.1f%% ", maxData.clouds); drawString(&stWeather, tmpStr); drawGraph(&stWeather, data, offsetof(struct weather_t, clouds), count, 0, maxData.clouds); } if(rain_sum > 1.0) { sprintf(tmpStr, " Regen: %.1fmm ", rain_sum); drawString(&stWeather, tmpStr); drawGraph(&stWeather, data, offsetof(struct weather_t, rain), count, 0, maxData.rain); } if(snow_sum > 1.0) { sprintf(tmpStr, " Schnee: %.1fmm ", snow_sum); drawString(&stWeather, tmpStr); drawGraph(&stWeather, data, offsetof(struct weather_t, snow), count, 0, maxData.snow); } json_object_put(parsed_json); } MQTTClient_freeMessage(&message); MQTTClient_free(topicName); return 1; } void connlost(void *context, char *cause) { printf("\nConnection lost\n"); printf(" cause: %s\n", cause); } void drawTime(uint8_t *bImg, time_t *t) { struct tm *now_tm = localtime(t); char cTemp[6]; snprintf( cTemp, sizeof(cTemp), "%2d%c%02d", now_tm->tm_hour, now_tm->tm_sec%2 ? ':' : ' ', now_tm->tm_min ); memset(bImg, 0, 4*8); maxDrawString(cTemp, bImg, 4, 1); } void drawDate(uint8_t *bImg, time_t *t) { struct tm *now_tm = localtime(t); char cTemp[10]; snprintf( cTemp, sizeof(cTemp), "%hhu.%hhu.", now_tm->tm_mday, now_tm->tm_mon+1 ); memset(bImg, 0, 4*8); cTemp[6] = '\0'; maxDrawString(cTemp, bImg, 4, 1); } int drawScroll(uint8_t *bImg, struct scrolltext_t *st) { for(int i=0; i<8; i++) { if(st->offset < st->width && (st->bImg[i*SCROLL_BUF_SIZE/8 + st->offset/8] & (0x80 >> st->offset%8))) { bImg[i*4+3] |= 0x01; } else { bImg[i*4+3] &= 0xFE; } } return st->offset != 0; } int moveScroll(uint8_t *bImgCurrent, uint8_t *bImgTarget, struct scrolltext_t *st) { int busy = 0; int frames = (st->offset == 0) ? 0 : (st->frameCount)[st->offset-1]; if(st->width > 32 && ++(st->frame) >= frames) { st->frame = 0; maxScrollBitmap(bImgCurrent, 4, 1); maxScrollBitmap(bImgTarget, 4, 1); for(int i=0; i<8; i++) bImgCurrent[i*4+3] &= 0xFE; busy = drawScroll(bImgTarget, st); if(++(st->offset) >= st->width+32 || st->offset >= SCROLL_BUF_SIZE) { st->offset = 0; } } else if(st->width > 32) { st->frame += 1; busy = 2; } else { st->offset = 0; for(int i=0; i<8; i++) { memcpy(&bImgTarget[i*4], &(st->bImg)[i*SCROLL_BUF_SIZE/8], 4); } } return busy; } int main(int argc, char* argv[]) { int rc; time_t rawtime, oldTime = 0; uint8_t bImgTarget[4*8], bImgCurrent[4*8]; uint8_t pixelPositions[4*8*8]; srand((unsigned) time(&rawtime)); // Initialize the library // num controllers, BCD mode, SPI channel, GPIO pin number for CS rc = maxInit(4, 0, 0, 22); if (rc != 0) { printf("Problem initializing max7219\n"); return 0; } maxSetIntensity(0); MQTTClient client; MQTTClient_connectOptions conn_opts = MQTTClient_connectOptions_initializer; MQTTClient_create(&client, ADDRESS, CLIENTID, MQTTCLIENT_PERSISTENCE_NONE, NULL); conn_opts.keepAliveInterval = 20; conn_opts.cleansession = 1; MQTTClient_setCallbacks(client, NULL, connlost, msgarrvd, delivered); if ((rc = MQTTClient_connect(client, &conn_opts)) != MQTTCLIENT_SUCCESS) { printf("Failed to connect, return code %d\n", rc); exit(EXIT_FAILURE); } MQTTClient_subscribe(client, "Timer/#", QOS); MQTTClient_subscribe(client, "Room/display", QOS); uint32_t busy = 0; while(1) { if(!busy) time(&rawtime); if(rawtime - oldTime < 10) { drawTime(bImgTarget, &rawtime); } else if(rawtime - oldTime < 40) { drawDate(bImgTarget, &rawtime); } else if(rawtime - oldTime < 50) { busy = moveScroll(bImgCurrent, bImgTarget, &stDisplay); } else if(rawtime - oldTime < 60) { busy = moveScroll(bImgCurrent, bImgTarget, &stWeather); } else { busy = 0; oldTime = rawtime; } // find pixels to change int pixelsToChange = 0; for(int i=0; i<4*8; i++) { if(bImgCurrent[i] != bImgTarget[i]) { uint8_t diff = bImgCurrent[i] ^ bImgTarget[i]; // iterate bits int p = 0; while(diff) { if(diff & 1) { pixelPositions[pixelsToChange++] = i*8 + p; } p++; diff >>= 1; } } } if(pixelsToChange) { // shuffle list // if(pixelsToChange > 1) { // for(int i=0; i 1) debugPrint(bImgCurrent, 4); maxSendImage(bImgCurrent, 4, 1); } usleep(1000000 / 50); } // Quit library and free resources maxShutdown(); MQTTClient_disconnect(client, 10000); MQTTClient_destroy(&client); return rc; } /* main() */