stock.cpp 5.22 KB
#include <cJSON.h>

#include "display.h"
#include "https_request.h"
#include "stock.h"

static SemaphoreHandle_t mutex = NULL;

static unsigned int current_symbol = 0;
static stock_data symbols[] = {
  {
    .name = "META",
    .current = 0.0,
    .delta = 0.0,
    .color = {
      .r = 0x00,
      .g = 0x81,
      .b = 0xFB
    }
  },
  {
    .name = "AAPL",
    .current = 0.0,
    .delta = 0.0,
    .color = {
      .r = 0xA2,
      .g = 0xAA,
      .b = 0xAD
    }
  },
  {
    .name = "MSFT",
    .current = 0.0,
    .delta = 0.0,
    .color = {
      .r = 0x00,
      .g = 0xA3,
      .b = 0xEE
    }
  },
  {
    .name = "GOOGL",
    .current = 0.0,
    .delta = 0.0,
    .color = {
      .r = 0x42,
      .g = 0x85,
      .b = 0xF4
    }
  },
  {
    .name = "NVDA",
    .current = 0.0,
    .delta = 0.0,
    .color = {
      .r = 0x76,
      .g = 0xB9,
      .b = 0x00
    }
  }
};

esp_err_t init_stock() {
  mutex = xSemaphoreCreateMutex();
  return ESP_OK;
}

esp_err_t update_stock_price(unsigned int index, float current, float delta) {
  const char* TAG = "update_stock_price";

  if(xSemaphoreTake(mutex, 5000 / portTICK_PERIOD_MS ) == pdTRUE) {
    symbols[index].current = current;
    symbols[index].delta = delta;
    xSemaphoreGive(mutex);
  } else {
    ESP_LOGE(TAG, "Failed to acquire stock update mutex");
    return ESP_ERR_NOT_FINISHED;
  }
  return ESP_OK;
}

esp_err_t parse_JSON(const char* json, float* current, float* delta) {
  cJSON *root = cJSON_Parse(json);
  if (cJSON_GetObjectItem(root, "c")) {
    *current = (float)cJSON_GetObjectItem(root,"c")->valuedouble;
  } else {
    return ESP_ERR_NOT_FOUND;
  }

  if (cJSON_GetObjectItem(root, "d")) {
    *delta = (float)cJSON_GetObjectItem(root,"d")->valuedouble;
  } else {
    return ESP_ERR_NOT_FOUND;
  }

  return ESP_OK;
}

void display_stock_task(void* arg) {
  uint8_t display_percentage = 0;
  uint8_t alt_count = 0;
  char stock[16];
  char delta_perc[16];
  char delta[16];

  const char* TAG = "display_stock_task";

  ESP_LOGI(TAG, "display_stock_task start");

  for(;;) {
    float c = 0.0;
    float d = 0.0;

    if(xSemaphoreTake(mutex, portMAX_DELAY ) == pdTRUE) {
      c = symbols[current_symbol].current;
      d = symbols[current_symbol].delta;
      xSemaphoreGive(mutex);
    }

    drawText(19, 0, symbols[current_symbol].name, strlen(symbols[current_symbol].name), symbols[current_symbol].color.r, symbols[current_symbol].color.g, symbols[current_symbol].color.b);
    sprintf(stock, "$%.02f", c);
    sprintf(delta, "%+.02f", d);
    sprintf(delta_perc, "%+0.04f%%", -100* (1 - (c / (c-d))));

    // META
    drawText((PANEL_RES_X - strlen(stock))/4, 8, stock, strlen(stock), d < 0 ? 0xFF : 0x00, d < 0 ? 0x00 : 0xFF, 0x00);

    // Arrow
    if(d < 0) {
      dma_display->drawIcon(arrow_down_icon, 1, 8, 6, 8);
    } else {
      dma_display->drawIcon(arrow_up_icon, 1, 8, 6, 8);
    }

    // Delta
    if(display_percentage) {
      drawText((PANEL_RES_X - strlen(delta_perc))/4, 16, delta_perc, strlen(delta_perc), d < 0 ? 0xFF : 0x00, d < 0 ? 0x00 : 0xFF, 0x00);
    } else {
      drawText((PANEL_RES_X - strlen(delta))/4, 16, delta, strlen(delta), d < 0 ? 0xFF : 0x00, d < 0 ? 0x00 : 0xFF, 0x00);
    }
    vTaskDelay(ALTERNATE_TIME * 1000 / portTICK_PERIOD_MS);
    display_percentage ^= 1;
    dma_display->clearScreen();
    if(++alt_count == ALTERNATIONS_PER_SYMBOL) {
      current_symbol = (current_symbol + 1) % (sizeof(symbols) / sizeof(stock_data));
      alt_count = 0;
    }
  }
}

esp_err_t fetch_stock_data(const char* symbol, float* current, float* delta) {
  const char* TAG = "fetch_stock_data";

  int code = 0;
  char** response_headers = nullptr;
  char* response = nullptr;
  char* headers[2] = {(char*)("X-Finnhub-Token=" FINNHUB_API_KEY), NULL};
  size_t url_len = strlen(BASE_URL) + strlen(symbol) + 1;
  char* url = (char*)malloc(url_len);
  if(url == NULL) {
    ESP_LOGE(TAG, "Unable to alloc memory for URL");
    return ESP_FAIL;
  }
  snprintf(url, url_len, "%s%s", BASE_URL, symbol);

  esp_err_t err = do_https_request(url, HTTP_METHOD_GET, (const char**)headers, &response_headers, &response, &code);
  free(url);
  if(err != ESP_OK) {
    ESP_LOGE(TAG, "Unable to obtain stock data: err=%d", err);
    return err;
  } else if(code != 200) {
    ESP_LOGE(TAG, "API returned code: code=%d", code);
    free_https_response(&response_headers, &response);
    return ESP_FAIL;
  } else {
    err = parse_JSON(response, current, delta);
    if(err != ESP_OK) {
      ESP_LOGE(TAG, "Error parsing JSON: err=%d", err);
    }
  }
  free_https_response(&response_headers, &response);
  return ESP_OK;
}

void fetch_stock_task(void* arg) {
  const char* TAG = "fetch_stock_task";

  ESP_LOGI(TAG, "fetch_stock_task start");

  for(;;) {
    for(unsigned int i = 0; i < (sizeof(symbols)/sizeof(stock_data)); ++i) {
      float current;
      float delta;

      esp_err_t err = fetch_stock_data(symbols[i].name, &current, &delta);
      if(err != ESP_OK) {
        ESP_LOGE(TAG, "Error parsing JSON: err=%d", err);
      } else {
        int retries = 0;
        while(retries++ != MAX_UPDATE_RETRIES) {
          update_stock_price(i, current, delta);
        }
      }
      vTaskDelay(API_DELAY * 1000 / portTICK_PERIOD_MS);
    }
    vTaskDelay(FETCH_TIME * 1000 / portTICK_PERIOD_MS);
  }
}