Skip to content

Instantly share code, notes, and snippets.

@gabonator
Last active April 14, 2020 08:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save gabonator/64d2ff5f26b725ed4f79f127e071fd4d to your computer and use it in GitHub Desktop.
Save gabonator/64d2ff5f26b725ed4f79f127e071fd4d to your computer and use it in GitHub Desktop.
Sygic navigation external HUD display with ESP32 TTGO board with 128x64 pixel oled display and Wifi
// Sygic navigation external HUD display with ESP32 TTGO board with 128x64 pixel oled display and Wifi
//
// Board description: 16 Mt Bytes (128 Mt bit) Pro ESP32 OLED V2.0 TTGO For Arduino ESP32 OLED WiFi Mo
// Board info: https://gist.github.com/gabonator/402e09eb1b5afce1e7be228483138071
//
// Board installation for arduino:
// https://github.com/espressif/arduino-esp32/blob/master/docs/arduino-ide/windows.md
//
// Used libraries:
// Websocket protocol: https://gist.github.com/gabonator/1fb37f2478b053336b01b0c7e5842bca
// SSD1306 Oled display: https://github.com/ThingPulse/esp8266-oled-ssd1306
//
// Video:
// https://youtu.be/Kb0fgH8GiYA
//
// To enable websocket service on your iPhone:
// Sygic -> Menu (left top corner) -> Settings -> Info -> About -> 3x tap anywhere -> enable Websocket server
//
#include <WiFi.h>
#include <WebSocketClient.h>
#include <OLEDDisplayFonts.h>
#include "SSD1306Wire.h"
class CIcons
{
typedef unsigned long icon_t[32];
public:
static icon_t iconLeft;
static icon_t iconRight;
static icon_t iconRoundabout;
static icon_t iconFinish;
static icon_t iconClean;
static icon_t iconStraight;
};
class CInternetClient
{
const char* ssid = "SyLoc";
const char* password = "??????????????????";
public:
void Connect()
{
Serial.println();
Serial.println();
Serial.print("Connecting to ");
Serial.println(ssid);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi connected");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
Serial.println("wait...");
delay(1000);
}
};
class CInternetAccessPoint
{
const char* ssid = "SygicHud";
const char* password = "11111111";
public:
void Connect()
{
WiFi.softAP(ssid, password);
Serial.print("IP address: ");
Serial.println(WiFi.softAPIP());
}
};
class CHud
{
public:
enum {
DirectionStart = 20,
DirectionEasyLeft = 6,
DirectionEasyRight = 4,
DirectionEnd = 19,
DirectionVia = 15,
DirectionKeepLeft = 2,
DirectionKeepRight = 0,
DirectionLeft = 10,
DirectionOutOfRoute = 21,
DirectionRight = 8,
DirectionSharpLeft = 14,
DirectionSharpRight = 12,
DirectionStraight = 16,
DirectionUTurnLeft = 18,
DirectionUTurnRight = 17,
DirectionRoundaboutSE = 23,
DirectionRoundaboutE = 24,
DirectionRoundaboutNE = 25,
DirectionRoundaboutN = 26,
DirectionRoundaboutNW = 27,
DirectionRoundaboutW = 28,
DirectionRoundaboutSW = 29,
DirectionRoundaboutS = 30,
DirectionTunnel = 50,
};
public:
int mDistanceToInstruction{0};
int mSpeedLimit{0};
int mInstruction{-1};
int mSpeed{0};
int mChecksum{0};
public:
bool IsValid()
{
return mInstruction != -1 || mSpeed > 0;
}
};
class CSdk
{
WebSocketClient webSocketClient;
WiFiClient client;
const char path[32] = "/";
//const char host[32] = "10.0.8.193"; // lan-iphone
//const char host[32] = "10.0.9.116"; // lan-pc
const char host[32] = "192.168.4.2"; // AP mode
long mGotDataTimestamp;
long mGotDataTimestamp2;
public:
bool mConnected{false};
CHud mHud;
public:
bool HasData()
{
return (millis() - mGotDataTimestamp2) < 10000;
}
void Connect()
{
mConnected = false;
Serial.println("Connecting websocket...");
// Connect to the websocket server
if (client.connect(host, 38950)) {
Serial.println("TCP Connected");
} else {
Serial.println("TCP Connection failed.");
client.stop();
delay(3000);
return;
}
// Handshake with the server
webSocketClient.path = (char*)path;
webSocketClient.host = (char*)host;
if (webSocketClient.handshake(client)) {
Serial.println("Handshake successful");
} else {
Serial.println("Handshake failed.");
client.stop();
return;
}
Serial.print("Websocket init ok\n");
mConnected = true;
}
bool Changed()
{
static int lastChecksum = 0;
if (lastChecksum != mHud.mChecksum)
{
lastChecksum = mHud.mChecksum;
return true;
}
return false;
}
void Do()
{
static long lTick = 0;
long lNow = millis();
if ((mGotDataTimestamp && lNow-mGotDataTimestamp > 200) || lNow > lTick + 5000)
{
mGotDataTimestamp = 0;
lTick = lNow;
RequestData();
}
if (client.connected())
{
String data;
webSocketClient.getData(data);
if (data.length() > 0)
{
//Serial.print("Received data: ");
//Serial.println(data);
ParseData(data);
}
} else
{
Serial.println("Client disconnected.");
delay(500);
Connect();
}
}
void RequestData()
{
if (mConnected)
{
webSocketClient.sendData("Hud.Get('distance','speedLimit','directionRaw','speed');\n", WS_OPCODE_TEXT);
}
}
void ParseData(String& data)
{
Serial.print("Got data: ");
Serial.print(data);
mGotDataTimestamp = mGotDataTimestamp2 = millis();
//$JS:"200m","50km/h","8,0,1","40km/h";
int nIndex = 0;
int nBegin = -1;
char buffer[256];
data.toCharArray(buffer, sizeof(buffer));
int nLength = strlen(buffer);
mHud.mChecksum = 0;
for (int i=0; i<nLength; i++)
{
mHud.mChecksum += (int)buffer[i];
if (buffer[i] == '"')
{
if (nBegin == -1)
nBegin = i;
else
{
char *value = buffer + nBegin + 1;
buffer[i] = 0;
switch (nIndex)
{
case 0: mHud.mDistanceToInstruction = atoi(value); break;
case 1: mHud.mSpeedLimit = atoi(value); break;
case 2: if (strncmp(value, "0,0,0", 5) == 0)
mHud.mInstruction = -1;
else
mHud.mInstruction = atoi(value);
break;
case 3: mHud.mSpeed = atoi(value); break;
}
if (0)
{
Serial.print("data[");
Serial.print(nIndex);
Serial.print("]=");
Serial.print(buffer+nBegin+1);
Serial.print("\n");
}
nIndex++;
nBegin = -1;
}
}
}
}
} Sdk;
class CDisplay
{
public:
SSD1306Wire& display()
{
static SSD1306Wire disp(0x3c, 4, 15);
return disp;
}
void Init()
{
pinMode(16, OUTPUT);
digitalWrite(16, 1);
delay(10);
display().init();
display().flipScreenVertically();
display().setFont(ArialMT_Plain_10);
}
void Welcome(bool bConnected)
{
display().clear();
display().setFont(ArialMT_Plain_24);
display().drawString(12, 12, "Sygic hud");
display().setFont(ArialMT_Plain_16);
if (bConnected)
display().drawString(30-5, 46, "Connected!");
else
display().drawString(30, 46, "AP ready");
display().display();
}
void Update(CHud& hud)
{
int screen0 = 0;
char buf[128];
display().clear();
if (hud.mSpeed > 0)
{
display().setFont(ArialMT_Plain_16);
sprintf(buf, "%d kmh", hud.mSpeed);
display().drawString(screen0+0, 8, buf);
}
if (hud.mSpeedLimit > 0)
{
if (hud.mSpeed < hud.mSpeedLimit)
{
display().setColor(WHITE);
display().fillCircle(screen0+80+10, 8+10, 14);
display().setColor(BLACK);
display().fillCircle(screen0+80+10, 8+10, 12);
display().setColor(WHITE);
} else
{
display().setColor(WHITE);
display().fillCircle(screen0+80+10, 8+10, 14);
display().setColor(BLACK);
}
display().setFont(ArialMT_Plain_16);
sprintf(buf, "%d", hud.mSpeedLimit);
display().drawString(screen0+81, 10, buf);
display().drawString(screen0+82, 10, buf);
display().setColor(WHITE);
display().setFont(ArialMT_Plain_24);
sprintf(buf, "%dm", hud.mDistanceToInstruction);
display().drawString(screen0+68-4, 34, buf);
display().drawString(screen0+69-4, 34, buf);
}
drawIcon(screen0+0, 64-32, getIcon(hud));
display().display();
}
unsigned long* getIcon(CHud& hud)
{
switch(hud.mInstruction)
{
case CHud::DirectionSharpLeft:
case CHud::DirectionEasyLeft:
case CHud::DirectionKeepLeft:
case CHud::DirectionLeft:
case CHud::DirectionUTurnLeft:
return CIcons::iconLeft;
case CHud::DirectionSharpRight:
case CHud::DirectionEasyRight:
case CHud::DirectionKeepRight:
case CHud::DirectionRight:
case CHud::DirectionUTurnRight:
return CIcons::iconRight;
case CHud::DirectionRoundaboutSE:
case CHud::DirectionRoundaboutE:
case CHud::DirectionRoundaboutNE:
case CHud::DirectionRoundaboutN:
case CHud::DirectionRoundaboutNW:
case CHud::DirectionRoundaboutW:
case CHud::DirectionRoundaboutSW:
case CHud::DirectionRoundaboutS:
return CIcons::iconRoundabout;
case CHud::DirectionVia:
case CHud::DirectionEnd:
return CIcons::iconFinish;
case CHud::DirectionStraight:
case CHud::DirectionTunnel:
return CIcons::iconStraight;
default:
return CIcons::iconClean;
}
}
void drawIcon(int x, int y, unsigned long* buffer)
{
SSD1306Wire& d = display();
for (int _y=0; _y<32; _y++)
for (int _x=0; _x<32; _x++)
if (buffer[_y] & (1<<(31-_x)))
d.setPixel(x+_x, y+_y);
}
} Display;
CInternetAccessPoint Internet;
void setup()
{
Serial.begin(115200);
delay(10);
Serial.print("Sygic ESP HUD Built " __DATE__ " " __TIME__ "\n");
Display.Init();
Display.Welcome(false);
Internet.Connect();
Sdk.Connect();
Display.Welcome(Sdk.mConnected);
}
void loop()
{
Sdk.Do();
static long lLastChange = 0;
long lNow = millis();
if (lNow - lLastChange > 20)
{
if (!Sdk.mConnected)
Display.Welcome(false);
else if (Sdk.mConnected && Sdk.HasData() && Sdk.mHud.IsValid())
Display.Update(Sdk.mHud);
else
Display.Welcome(true);
lLastChange = lNow;
}
}
CIcons::icon_t CIcons::iconLeft = {
0b00000000100000000000000000000000,
0b00000001100000000000000000000000,
0b00000011100000000000000000000000,
0b00000111100000000000000000000000,
0b00001111100000000000000000000000,
0b00011111100000000000000000000000,
0b00111111111111100000000000000000,
0b01111111111111111000000000000000,
0b11111111111111111100000000000000,
0b01111111111111111100000000000000,
0b00111111111111111110000000000000,
0b00011111100000111110000000000000,
0b00001111100000111110000000000000,
0b00000111100000111110000000000000,
0b00000011100000111110000000000000,
0b00000001100000111110000000000000,
0b00000000100000111110000000000000,
0b00000000000000111110000000000000,
0b00000000000000111110000000000000,
0b00000000000000111110000000000000,
0b00000000000000111110000000000000,
0b00000000000000111110000000000000,
0b00000000000000111110000000000000,
0b00000000000000111110000000000000,
0b00000000000000111110000000000000,
0b00000000000000111110000000000000,
0b00000000000000111110000000000000,
0b00000000000000111110000000000000,
0b00000000000000111110000000000000,
0b00000000000000111110000000000000,
0b00000000000000111110000000000000,
0b00000000000000111110000000000000};
CIcons::icon_t CIcons::iconRight = {
0b00000000000000000000000100000000,
0b00000000000000000000000110000000,
0b00000000000000000000000111000000,
0b00000000000000000000000111100000,
0b00000000000000000000000111110000,
0b00000000000000000000000111111000,
0b00000000000000000111111111111100,
0b00000000000000001111111111111110,
0b00000000000000011111111111111111,
0b00000000000000111111111111111110,
0b00000000000000111111111111111100,
0b00000000000000111110000111111000,
0b00000000000000111110000111110000,
0b00000000000000111110000111100000,
0b00000000000000111110000111000000,
0b00000000000000111110000110000000,
0b00000000000000111110000100000000,
0b00000000000000111110000000000000,
0b00000000000000111110000000000000,
0b00000000000000111110000000000000,
0b00000000000000111110000000000000,
0b00000000000000111110000000000000,
0b00000000000000111110000000000000,
0b00000000000000111110000000000000,
0b00000000000000111110000000000000,
0b00000000000000111110000000000000,
0b00000000000000111110000000000000,
0b00000000000000111110000000000000,
0b00000000000000111110000000000000,
0b00000000000000111110000000000000,
0b00000000000000111110000000000000,
0b00000000000000111110000000000000};
CIcons::icon_t CIcons::iconStraight = {
0b00000000000000001000000000000000,
0b00000000000000011100000000000000,
0b00000000000000111110000000000000,
0b00000000000001111111000000000000,
0b00000000000011111111100000000000,
0b00000000000111111111110000000000,
0b00000000001111111111111000000000,
0b00000000011111111111111100000000,
0b00000000111111111111111110000000,
0b00000001111111111111111111000000,
0b00000011111111111111111111100000,
0b00000111111111111111111111110000,
0b00000000000000111110000000000000,
0b00000000000000111110000000000000,
0b00000000000000111110000000000000,
0b00000000000000111110000000000000,
0b00000000000000111110000000000000,
0b00000000000000111110000000000000,
0b00000000000000111110000000000000,
0b00000000000000111110000000000000,
0b00000000000000111110000000000000,
0b00000000000000111110000000000000,
0b00000000000000111110000000000000,
0b00000000000000111110000000000000,
0b00000000000000111110000000000000,
0b00000000000000111110000000000000,
0b00000000000000111110000000000000,
0b00000000000000111110000000000000,
0b00000000000000111110000000000000,
0b00000000000000111110000000000000,
0b00000000000000111110000000000000,
0b00000000000000111110000000000000};
CIcons::icon_t CIcons::iconRoundabout = {
0b00000000000001111100000000000000,
0b00000000000001111100000000000000,
0b00000000000001111100000000000000,
0b00000000000001111100000000000000,
0b00000000000001111100000000000000,
0b00000000001111111110000000000000,
0b00000000111111111111110000000000,
0b00000001111110000011111000000000,
0b00000011110000000001111100000000,
0b00000011110000000000011110000000,
0b00000111000000000000001111000000,
0b00001111000000000000000111100000,
0b00001110000000000000000011100000,
0b00011110000000000000000011100000,
0b11111100000000000000000011111111,
0b11111000000000000000000011111111,
0b11111000000000000000000011111111,
0b11111000000000000000000011111111,
0b11111000000000000000000011111111,
0b00000000000000000000000011100000,
0b00000000000000000000000111100000,
0b00000000000000000000001111100000,
0b00000000000000000000011110000000,
0b00000000000000000001111100000000,
0b00000000000000000011111000000000,
0b00000000000000011111100000000000,
0b00000000000000111111100000000000,
0b00000000000001111100000000000000,
0b00000000000001111100000000000000,
0b00000000000001111100000000000000,
0b00000000000001111100000000000000,
0b00000000000001111100000000000000};
CIcons::icon_t CIcons::iconFinish = {
0b00000000000000000000000000000000,
0b01111111111111111111111111111110,
0b01111111111111111111111111111100,
0b01111000000000000000000000011000,
0b01111000000111111111100000110000,
0b01111000000111111111000000110000,
0b01111000000111000000000000011000,
0b01111000000111111100000000011000,
0b01111000000111111100000000011000,
0b01111000000111000000000000001100,
0b01111000000111000000000000001100,
0b01111000000111000000000000001100,
0b01111000000111000000000000011000,
0b01111000000000000000000000111000,
0b01111111111111111111111111110000,
0b01111111111111111111111111100000,
0b01111000000000000000000000000000,
0b01111000000000000000000000000000,
0b01111000000000000000000000000000,
0b01111000000000000000000000000000,
0b01111000000000000000000000000000,
0b01111000000000000000000000000000,
0b01111000000000000000000000000000,
0b01111000000000000000000000000000,
0b01111000000000000000000000000000,
0b01111000000000000000000000000000,
0b01111000000000000000000000000000,
0b01111000000000000000000000000000,
0b01111000000000000000000000000000,
0b01111000000000000000000000000000,
0b01111000000000000000000000000000,
0b01111000000000000000000000000000};
CIcons::icon_t CIcons::iconClean = {
0b00000000000000000000000000000000,
0b00000000000000000000000000000000,
0b00000000000000000000000000000000,
0b00000000000000000000000000000000,
0b00000000000000000000000000000000,
0b00000000000000000000000000000000,
0b00000000000000000000000000000000,
0b00000000000000000000000000000000,
0b00000000000000000000000000000000,
0b00000000000000000000000000000000,
0b00000000000000000000000000000000,
0b00000000000000000000000000000000,
0b00000000000000000000000000000000,
0b00000000000000000000000000000000,
0b00000000000000000000000000000000,
0b00000000000000000000000000000000,
0b00000000000000000000000000000000,
0b00000000000000000000000000000000,
0b00000000000000000000000000000000,
0b00000000000000000000000000000000,
0b00000000000000000000000000000000,
0b00000000000000000000000000000000,
0b00000000000000000000000000000000,
0b00000000000000000000000000000000,
0b00000000000000000000000000000000,
0b00000000000000000000000000000000,
0b00000000000000000000000000000000,
0b00000000000000000000000000000000,
0b00000000000000000000000000000000,
0b00000000000000000000000000000000,
0b00000000000000000000000000000000,
0b00000000000000000000000000000000};
@gabonator
Copy link
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment