Lottery Monitor (Hong Kong Mark Six Lottery) - Cloud Pixel
Cloud Pixel : Record the lottery results of previous periods and make statistics to see which number appears the most often.
Software Apps and online services
The lottery industry in Hong Kong is relatively prosperous, Mark Six Lottery is the most famous lottery in Hongkong. Many people buy Mark Six lottery and study the probability of the lottery numbers appearing in each lottery and other issues. So I want to develop a lottery-related application based on the developed Cloud Pixel hardware.
Displays the drawing time and winning numbers of the latest lottery, and can make statistics on historical winning numbers, the number of occurrences of each number, and which number appears most and least.
Test Video:
Display the Hongkong mark six Lottery.
Cloud Pixel uses PCB as the material of the casing.
Cloud Pixel uses 4 layers of PCB to form a complete casing.
Cloud Pixel use Raspberry Pi RP2040 as the MCU, wizfi360 as the wireless LAN module, and has a 4-inch 480*320 pixel screen onboard to display data, and a TF card interface to store data, onboard An MPU6050 is used to determine the screen placement direction.
These are the Pin Maps of the IO used by Cloud Pixel.
Cloud Pixel is mainly used to develop applications related to network communications and need to display information.
Each Mark Six lottery draws seven numbers out of 49 numbers. The first six numbers are called "pulled numbers" and the seventh number is called "special number".
Mark Six Lottery does not provide an official API. I use an API provided by a third party. This API cannot provide filtering conditions, but can only provide the latest lottery data. A maximum of 200 sets of data can be provided at a time.
This is the result of a single data return. "issue":"24032" is the 24032nd lottery draw, and "drawResult":"20,06,34,39,42,19,44" is the result of this lottery draw"20 ,06,34,39,42,19,44", "drawTime": "2024-03-21 21:35:20" is the lottery time, "code": "hk6" represents the result of Hong Kong Mark Six Lottery .
{"issue":"24032","drawResult":"20,06,34,39,42,19,44","drawTime":"2024-03-21 21:35:20","code":"hk6"}
This application I developed using Arduino, first in the setup() part, I initialized the serial port as debugging output, initialized eeprom to store the latest information; initialized the TF interface and file system to store historical lottery data; initialized the ILI9488 screen , and display the first screen interface of the screen; initialize the Wizfi360 module and connect to the wireless LAN.
void setup() {
// initialize serial for debugging
Serial.begin(115200);
// initialize the pushbutton pin as an input:
pinMode(buttonPin, INPUT);
pinMode(ledPin, OUTPUT);
digitalWrite(ledPin, LOW);
EEPROM.begin(64);
sync_with_eeprom(0);
SD.begin(17);
#ifdef debug_msg
myFile = SD.open("/");
printDirectory(myFile, 0);
#endif
tft->begin();
tft->fillScreen(WHITE);
pinMode(backLightPin, OUTPUT);
digitalWrite(backLightPin, HIGH);
display_logo(240,80,BLACK);
image_x = 129;
image_y = 125;
display_MarkSix_icon(1);
display_wifi_status(240,250);
// initialize serial for WizFi360 module
Serial2.setFIFOSize(4096);
Serial2.begin(2000000);
WiFi.init(&Serial2);
// attempt to connect to WiFi network
while ( status != WL_CONNECTED) {
// Connect to WPA/WPA2 network
status = WiFi.begin(ssid, pass);
}
display_wifi_status(240,250);
// print your WiFi shield's IP address
ip = WiFi.localIP();
currentState = send_marksix_request;
}
This is the first screen interface displayed on the screen:
After WiFi is connected to the wireless LAN, the signal indicator icon will turn green:
In Loop(), the communication with the API is processed. In this part, we put the entire processing process in a switch state machine. There are the following steps:
typedef enum
{
send_marksix_request = 0,
get_marksix_content,
analyze_marksix_content,
display_marksix_content,
display_marksix_result,
check_display_status,
}STATE_;
STATE_ currentState;
I'll walk through each step individually:
Case1. send_marksix_request:
char Lottery_server[] = "kclm.site";
In this step, establish a socket of SSL port 443 of "kclm.site" and send our request message with the following format:
https://kclm.site/api/trial/drawResult?code=hk6&format=json&rows=200
case send_marksix_request:
{
// if you get a connection, report back via serial
if (client.connectSSL(Lottery_server,443)) {
delay(3000);
// Make a HTTP request
client.println(String("GET /api/trial/drawResult?code=hk6&format=json&rows=300 HTTP/1.1"));
client.println(String("Host: ") + String(Lottery_server));
client.println(String("Content-Type: application/json"));
client.println("Connection: close");
client.println();
json_String= "";
currentState = get_marksix_content;
}
else
{
client.stop();
delay(1000);
}
}
break;
Case2. get_marksix_content:
In this step we will receive the response from the server, which is returned in JSON format. In order to make the interface of this step less boring, I designed a rotating loading bar.
The first data packet received will contain the header file of the HTML data. You can see that the data is sent in the "Transfer-Encoding: chunked" method, so what we have after the header file is all the data we need to receive. The length, according to observation, is 20040 bytes of data almost every time.
In order to ensure that all data can be received completely, we need to receive complete data according to the "chunked" sending method, calculate the length of each data packet, and perform integrity checks.
case get_marksix_content:
{
while (client.available()) {
json_String += (char)client.read();
data_now =1;
frame_cnt ++;
if(frame_cnt > 256)
{
tft->fillRect(225,230,32,32,WHITE);
tft->drawBitmap(225, 230, frames[frame], FRAME_WIDTH, FRAME_HEIGHT,BLACK);
frame = (frame + 1) % FRAME_COUNT;
if(frame_cnt > 256)
{
frame_cnt = 0;
}
}
}
if(data_now)
{
dataStart = json_String.indexOf("\r\n\r\n") + strlen("\r\n\r\n");
dataEnd = json_String.indexOf("\r\n{", dataStart);
html_len = strtol(json_String.substring(dataStart, dataEnd).c_str(), NULL, 16);
Serial.println(html_len);
html_len -= (json_String.length()- dataEnd -2);
html_len = html_len + 2 + 5;
#ifdef debug_msg
Serial.println(html_len);
#endif
dataStart = json_String.indexOf("\"data\":[") + strlen("\"data\":[");
Serial.println(json_String.length() - dataStart);
json_String = json_String.substring(dataStart, json_String.length());
while(html_len)
{
while (client.available()) {
json_String += (char)client.read();
html_len -- ;
frame_cnt ++;
if(frame_cnt > 256)
{
tft->fillRect(225,230,32,32,WHITE);
tft->drawBitmap(225, 230, frames[frame], FRAME_WIDTH, FRAME_HEIGHT,BLACK);
frame = (frame + 1) % FRAME_COUNT;
if(frame_cnt > 256)
{
frame_cnt = 0;
}
}
}
}
currentState = analyze_marksix_content;
}
}
break;
Case3. analyze_marksix_content:
In this step, we will analyze, classify and store Mark Six data;
I create a structure array to store this parameter:
struct _marksix_project{
boolean Exist = false;
String Issue;
String DrawResult;
String DrawTime;
} ;
_marksix_project marksix_project[200];
By performing String processing on “Json_String”, information such as "issue", "drawResult", and "drawTime" can be analyzed. And judge the files in the current TF. If there is a file "MarkSix.txt", write the compiled information into the TF. If not, create the "MarkSix.txt" file and then write it.
In order to facilitate subsequent data processing, I also counted the winning times of each winning number, as follows:
for(uint8_t m=0; m<7; m++)
{
dataEnd = marksix_project[199-i].DrawResult.indexOf(",",dataStart);
ball_num = (marksix_project[199-i].DrawResult.substring(dataStart, dataEnd)).toInt();
dataStart = dataEnd+1;
ball_total[ball_num-1].ball_cnt++;
}
Code:
case analyze_marksix_content:
{
html_len = json_String.length();
dataStart = 0 ;
dataEnd = 0 ;
while(marksix_project_num < 200)
{
dataStart = json_String.indexOf("issue\":\"",dataEnd) + strlen("issue\":\"");
dataEnd = json_String.indexOf("\",\"", dataStart);
marksix_project[marksix_project_num].Issue = json_String.substring(dataStart, dataEnd);
#ifdef debug_msg
Serial.println(marksix_project[marksix_project_num].Issue);
#endif
dataStart = json_String.indexOf("drawResult\":\"", dataEnd)+ strlen("drawResult\":\"");
dataEnd = json_String.indexOf("\",\"", dataStart);
marksix_project[marksix_project_num].DrawResult = json_String.substring(dataStart, dataEnd);
#ifdef debug_msg
Serial.println(marksix_project[marksix_project_num].DrawResult);
#endif
dataStart = json_String.indexOf("drawTime\":\"", dataEnd)+ strlen("drawTime\":\"");
dataEnd = json_String.indexOf("\",\"", dataStart);
marksix_project[marksix_project_num].DrawTime = json_String.substring(dataStart, dataEnd);
#ifdef debug_msg
Serial.println(marksix_project[marksix_project_num].DrawTime);
#endif
marksix_project_num ++;
}
marksix_project_cnt = marksix_project_num;
#ifdef debug_msg
for(int i=0;i<marksix_project_cnt;i++)
{
Serial.println(i);
Serial.println(marksix_project[i].Issue);
Serial.println(marksix_project[i].DrawResult);
Serial.println(marksix_project[i].DrawTime);
}
#endif
myFile = SD.open(file_name);
if (myFile) {
// Serial.println("myFile:");
// while (myFile.available()) {
// //Serial.write(myFile.read());
// tf_String += (char)myFile.read();
// }
// Serial.println(tf_String);
myFile = SD.open(file_name, FILE_WRITE);
if (myFile)
{
for(int i=199;i>=0;i--)
{
if((marksix_project[i].Issue).toInt() > Marksix_Round)
{
myFile.print("Num:");
myFile.print(Marksix_num);
myFile.print(" Issue:");
myFile.print(marksix_project[i].Issue);
myFile.print(" DrawResult:");
myFile.print(marksix_project[i].DrawResult);
myFile.print(" DrawTime:");
myFile.println(marksix_project[i].DrawTime);
for(uint8_t m=0; m<7; m++)
{
dataEnd = marksix_project[i].DrawResult.indexOf(",",dataStart);
ball_num = (marksix_project[i].DrawResult.substring(dataStart, dataEnd)).toInt();
dataStart = dataEnd+1;
ball_total[ball_num-1].ball_cnt++;
}
Marksix_num ++;
}
}
}
myFile.close();
}
else
{
Serial.println("Creat new file");
myFile = SD.open(file_name, FILE_WRITE);
if (myFile) {
for(int i=0;i<marksix_project_cnt;i++)
{
myFile.print("Num:");
myFile.print(i);
myFile.print(" Issue:");
myFile.print(marksix_project[199-i].Issue);
myFile.print(" DrawResult:");
myFile.print(marksix_project[199-i].DrawResult);
myFile.print(" DrawTime:");
myFile.println(marksix_project[199-i].DrawTime);
for(uint8_t m=0; m<7; m++)
{
dataEnd = marksix_project[199-i].DrawResult.indexOf(",",dataStart);
ball_num = (marksix_project[199-i].DrawResult.substring(dataStart, dataEnd)).toInt();
dataStart = dataEnd+1;
ball_total[ball_num-1].ball_cnt++;
}
}
}
myFile.close();
Marksix_num = marksix_project_cnt;
}
sync_with_eeprom(1);
currentState = display_marksix_content;
}
break;
case4. display_marksix_content:
Displays the drawing time and winning numbers of the latest lottery, and can make statistics on historical winning numbers, the number of occurrences of each number, and which number appears most and least.
The small number under each number is the number of times this number appears in the "Record Round".
case display_marksix_content:
{
x = 241;
y = 303;
dataStart = 0;
dataEnd = 0;
display_dashboard();
tft->setTextColor(WHITE);
tft->setTextSize(1);
tft->setCursor(3, y-15);
tft->print("Round");
tft->setCursor(72, y-15);
tft->print("Time");
tft->setCursor(202, y-15);
tft->print("Draw");
tft->setTextSize(2);
tft->setCursor(3, y-2);
tft->print(marksix_project[0].Issue);
tft->setTextSize(2);
tft->setCursor(72, y-2);
tft->print(marksix_project[0].DrawTime.substring(0,10));
for(uint8_t i; i<7; i++)
{
dataEnd = marksix_project[0].DrawResult.indexOf(",",dataStart);
ball_num = (marksix_project[0].DrawResult.substring(dataStart, dataEnd)).toInt();
dataStart = dataEnd+1;
ball_color = get_ball_color(ball_num);
tft->fillCircle(x,y,16,ball_color);
tft->drawCircle(x,y,15,WHITE);
//tft->fillArc(x,y,15,15,0,360,WHITE);
tft->setTextColor(WHITE);
tft->setTextSize(2);
if(ball_num < 10)
{
tft->setCursor(x-4, y-7);
tft->print(ball_num);
Serial.println(ball_num);
}
else
{
tft->setCursor(x-11, y-7);
tft->print(ball_num);
Serial.println(ball_num);
}
x += (32+4);
if(i == 5)
{
x += 3;
}
}
for(uint8_t m = 1; m<=49; m++)
{
x = (m%10)*48 + 24;
y = (m/10)*48 + 57;
ball_color = get_ball_color(m);
tft->fillCircle(x,y,17,ball_color);
tft->fillArc(x,y,15,16,0,360,WHITE);
tft->setTextColor(WHITE);
tft->setTextSize(2);
if(m < 10)
{
tft->setCursor(x-4, y-7);
tft->print(m);
}
else
{
tft->setCursor(x-11, y-7);
tft->print(m);
}
tft->setTextColor(ball_color);
tft->setTextSize(1);
tft->setCursor(x-5, y+21);
tft->print(ball_total[m-1].ball_cnt);
}
bubbleSort(ball_total,49);
currentState = display_marksix_result;
}
break;
case5. display_marksix_result:
The small number under each number is the number of times this number appears in the "Record Round".The sorting results are before the small numbers below. There are now two sorting methods, the number that appears most often among the winning numbers and the number that appears the least often among the winning numbers.
Through the buttons on the back, you can switch the display state to display the statistical results of the number that appears most frequently or the number that appears least frequently.
case display_marksix_result:
{
if(buttonState)
{
for (int i = 0; i < 7; i++) {
x = (ball_total[48-i].ball_No%10)*48 + 24;
y = (ball_total[48-i].ball_No/10)*48 + 57;
tft->fillRoundRect(x-13,y+19,28,11,5, WHITE);
ball_color = get_ball_color(ball_total[48-i].ball_No);
tft->setTextColor(ball_color);
tft->setTextSize(1);
tft->setCursor(x-5, y+21);
tft->print(ball_total[48-i].ball_cnt);
x = (ball_total[i].ball_No%10)*48 + 24;
y = (ball_total[i].ball_No/10)*48 + 57;
tft->fillRoundRect(x-13,y+19,28,11,5, WHITE);
ball_color = get_ball_color(ball_total[i].ball_No);
tft->drawRoundRect(x-12,y+19,27,11,5, ball_color);
tft->fillCircle(x-8,y+24,5,ball_color);
tft->setTextColor(WHITE);
tft->setTextSize(1);
tft->setCursor(x-10, y+21);
tft->print(i+1);
tft->setTextColor(ball_color);
tft->setTextSize(1);
tft->setCursor(x, y+21);
tft->print(ball_total[i].ball_cnt);
}
}
else
{
for (int i = 0; i <7; i++) {
x = (ball_total[i].ball_No%10)*48 + 24;
y = (ball_total[i].ball_No/10)*48 + 57;
tft->fillRoundRect(x-13,y+19,28,11,5, WHITE);
ball_color = get_ball_color(ball_total[i].ball_No);
tft->setTextColor(ball_color);
tft->setTextSize(1);
tft->setCursor(x-5, y+21);
tft->print(ball_total[i].ball_cnt);
x = (ball_total[48-i].ball_No%10)*48 + 24;
y = (ball_total[48-i].ball_No/10)*48 + 57;
tft->fillRoundRect(x-13,y+19,28,11,5, WHITE);
ball_color = get_ball_color(ball_total[48-i].ball_No);
tft->drawRoundRect(x-12,y+19,27,11,5, ball_color);
tft->fillCircle(x-8,y+24,5,ball_color);
tft->setTextColor(WHITE);
tft->setCursor(x-10, y+21);
tft->print(i+1);
tft->setTextColor(ball_color);
tft->setCursor(x, y+21);
tft->print(ball_total[48-i].ball_cnt);
}
}
digitalWrite(ledPin, LOW);
delay(500);
currentState = check_display_status;
}
break;
Done!