Skip to content

Commit 5b9ca6a

Browse files
committed
headless update
1 parent f939fee commit 5b9ca6a

9 files changed

Lines changed: 291 additions & 25 deletions

File tree

platformio.ini

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,30 @@
1+
; PlatformIO Project Configuration File
2+
;
3+
; Build options: build flags, source filter
4+
; Upload options: custom upload port, speed and extra flags
5+
; Library options: dependencies, extra library storages
6+
; Advanced options: extra scripting
7+
;
8+
; Please visit documentation for the other options and examples
9+
; https://docs.platformio.org/page/projectconf.html
10+
111
[env:esp32-c3-devkitm-1]
212
platform = espressif32
313
board = esp32-c3-devkitm-1
414
framework = arduino
515
board_build.partitions = no_ota.csv
6-
lib_deps =
7-
crankyoldgit/IRremoteESP8266@^2.8.6
8-
adafruit/Adafruit SSD1306@^2.5.15
9-
jgromes/RadioLib@^7.3.0
16+
lib_deps =
17+
crankyoldgit/IRremoteESP8266@^2.8.6
18+
adafruit/Adafruit SSD1306@^2.5.15
19+
jgromes/RadioLib@^7.3.0
20+
esphome/AsyncTCP-esphome@^2.0.0
21+
esp32async/ESPAsyncWebServer@3.6.0
1022
monitor_speed = 115200
11-
upload_port = COM12
12-
monitor_port = COM12
13-
build_flags =
14-
-DARDUINO_USB_CDC_ON_BOOT=1
15-
-DARDUINO_USB_MODE=1
16-
-Os
17-
-ffunction-sections
18-
-fdata-sections
19-
-Wl,--gc-sections
23+
build_flags =
24+
-DARDUINO_USB_CDC_ON_BOOT=1
25+
-DARDUINO_USB_MODE=1
26+
-Os
27+
-ffunction-sections
28+
-fdata-sections
29+
-Wl,--gc-sections
30+
upload_port = COM3

src/bluetooth/spam.hpp

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,19 @@ void BLE_SpamSendPayload(SPAM_PAYLOAD_TYPE type)
172172

173173
void BLE_Spam()
174174
{
175+
wifi_mode_t prev_mode = WIFI_MODE_NULL;
176+
esp_err_t err = esp_wifi_get_mode(&prev_mode);
177+
178+
bool had_ap = (WiFi.getMode() == WIFI_AP || WiFi.getMode() == WIFI_AP_STA);
179+
180+
if (err == ESP_OK) {
181+
esp_wifi_stop();
182+
WiFi.mode(WIFI_MODE_NULL);
183+
delay(200);
184+
}
185+
186+
delay(200);
187+
175188
BLE_Setup();
176189

177190
String selected = SelectionMenu(SPAM_PAYLOAD_TYPE_NAMES, sizeof(SPAM_PAYLOAD_TYPE_NAMES) / sizeof(String));
@@ -208,6 +221,22 @@ void BLE_Spam()
208221
}
209222
advertising->stop();
210223

224+
BLEDevice::deinit(true);
225+
delay(200);
226+
227+
if (err == ESP_OK) {
228+
if (had_ap) {
229+
WiFi.mode(WIFI_AP_STA);
230+
WiFi.softAP("Pocket Puter", "deveclipse");
231+
} else {
232+
WiFi.mode(prev_mode);
233+
if (prev_mode != WIFI_MODE_NULL) {
234+
esp_wifi_start();
235+
}
236+
}
237+
delay(200);
238+
}
239+
211240
HaltTillRelease(BUTTON_CENTER);
212241
display.clearDisplay();
213242
display.display();

src/global.cpp

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,25 @@
33
Adafruit_SSD1306 display(128, 64, &Wire, -1);
44
IRsend irtx(IR_TX);
55

6+
VirtualButtons virtualButtons = { false, false, false };
7+
68
bool ReadButton(int P)
79
{
8-
return digitalRead(P) == LOW;
10+
bool v = false;
11+
if (P == BUTTON_LEFT) v = virtualButtons.left;
12+
else if (P == BUTTON_CENTER) v = virtualButtons.center;
13+
else if (P == BUTTON_RIGHT) v = virtualButtons.right;
14+
return (digitalRead(P) == LOW) || v;
915
}
1016

1117
bool ReadButtonWait(int P)
1218
{
1319
bool pressed = false;
14-
if (ReadButton(P)) {
15-
pressed = true;
16-
}
17-
20+
if (ReadButton(P)) pressed = true;
1821
if (pressed) {
1922
while (ReadButton(P)) delay(10);
2023
return true;
2124
}
22-
2325
return false;
2426
}
2527

src/global.hpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include <Adafruit_SSD1306.h>
88
#include <IRremoteESP8266.h>
99
#include <IRsend.h>
10+
#include "freertos/semphr.h"
1011

1112
#define BUTTON_LEFT 1
1213
#define BUTTON_CENTER 2
@@ -17,6 +18,18 @@
1718
#define CC1101_CS 10
1819
#define CC1101_GDO0 7
1920

21+
struct VirtualButtons {
22+
bool left;
23+
bool center;
24+
bool right;
25+
};
26+
27+
extern VirtualButtons virtualButtons;
28+
29+
#define VIRTUAL_BUTTON_LEFT 0
30+
#define VIRTUAL_BUTTON_CENTER 1
31+
#define VIRTUAL_BUTTON_RIGHT 2
32+
2033
extern Adafruit_SSD1306 display;
2134
extern IRsend irtx;
2235

src/headless.h

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
#pragma once
2+
#include "global.hpp"
3+
4+
#include <WiFi.h>
5+
#include <AsyncTCP.h>
6+
#include <ESPAsyncWebServer.h>
7+
#include <ESPmDNS.h>
8+
#include <Adafruit_SSD1306.h>
9+
10+
AsyncWebServer server(80);
11+
static uint8_t display_copy[(128 * 64) / 8];
12+
13+
void Headless_Thread(void *pvParameters)
14+
{
15+
while (true)
16+
{
17+
memcpy(display_copy, display.getBuffer(), sizeof(display_copy));
18+
vTaskDelay(33 / portTICK_PERIOD_MS);
19+
}
20+
}
21+
22+
void Headless_Setup(bool on)
23+
{
24+
if (!on) return;
25+
26+
WiFi.mode(WIFI_AP_STA);
27+
WiFi.softAP("Pocket Puter", "deveclipse");
28+
MDNS.begin("pocketputer");
29+
30+
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
31+
request->send_P(200, "text/html", R"rawliteral(
32+
<!DOCTYPE html>
33+
<html>
34+
<head>
35+
<meta charset="utf-8">
36+
<title>Pocket Puter Display</title>
37+
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no, maximum-scale=1.0">
38+
<style>
39+
html, body {
40+
margin: 0;
41+
padding: 0;
42+
background: black;
43+
overflow: hidden;
44+
height: 100%;
45+
touch-action: none;
46+
-webkit-user-select: none;
47+
user-select: none;
48+
}
49+
body {
50+
display: flex;
51+
flex-direction: column;
52+
align-items: center;
53+
justify-content: center;
54+
}
55+
canvas {
56+
background: black;
57+
image-rendering: pixelated;
58+
width: 100vw;
59+
height: calc(100vw * 0.5);
60+
max-height: 90vh;
61+
max-width: calc(90vh * 2);
62+
border: 2px solid #444;
63+
border-radius: 10px;
64+
touch-action: none;
65+
}
66+
.buttons {
67+
display: flex;
68+
justify-content: space-around;
69+
width: 100%;
70+
max-width: 400px;
71+
margin-top: 15px;
72+
}
73+
button {
74+
flex: 1;
75+
margin: 5px;
76+
padding: 15px;
77+
font-size: 20px;
78+
background: #222;
79+
color: white;
80+
border: 2px solid #555;
81+
border-radius: 10px;
82+
outline: none;
83+
touch-action: manipulation;
84+
-webkit-user-select: none;
85+
user-select: none;
86+
}
87+
button:active {
88+
background: #0f0;
89+
color: black;
90+
}
91+
#refreshBtn {
92+
position: fixed;
93+
top: 10px;
94+
right: 10px;
95+
width: 40px;
96+
height: 40px;
97+
background: #222;
98+
border: 1px solid #666;
99+
border-radius: 50%;
100+
color: white;
101+
font-size: 22px;
102+
display: flex;
103+
align-items: center;
104+
justify-content: center;
105+
line-height: 1;
106+
opacity: 0.6;
107+
transition: opacity 0.2s, background 0.2s;
108+
}
109+
#refreshBtn:hover {
110+
opacity: 1;
111+
background: #0ff;
112+
color: black;
113+
}
114+
</style>
115+
</head>
116+
<body>
117+
<canvas id="disp" width="128" height="64"></canvas>
118+
<div class="buttons">
119+
<button ontouchstart="press(0)" ontouchend="release(0)" onmousedown="press(0)" onmouseup="release(0)">Left</button>
120+
<button ontouchstart="press(1)" ontouchend="release(1)" onmousedown="press(1)" onmouseup="release(1)">Center</button>
121+
<button ontouchstart="press(2)" ontouchend="release(2)" onmousedown="press(2)" onmouseup="release(2)">Right</button>
122+
</div>
123+
<button id="refreshBtn" onclick="location.reload()">⟳</button>
124+
<script>
125+
document.addEventListener('gesturestart', e => e.preventDefault());
126+
document.addEventListener('gesturechange', e => e.preventDefault());
127+
document.addEventListener('gestureend', e => e.preventDefault());
128+
document.addEventListener('dblclick', e => e.preventDefault());
129+
130+
const c = document.getElementById('disp');
131+
const ctx = c.getContext('2d', {alpha:false});
132+
const imgData = new ImageData(128,64);
133+
let failCount = 0;
134+
135+
async function update() {
136+
try {
137+
const res = await fetch('/buffer');
138+
if (!res.ok) throw new Error("Bad response");
139+
const buf = new Uint8Array(await res.arrayBuffer());
140+
failCount = 0;
141+
142+
for (let page = 0; page < 8; page++) {
143+
for (let x = 0; x < 128; x++) {
144+
const byte = buf[page * 128 + x];
145+
for (let bit = 0; bit < 8; bit++) {
146+
const y = page * 8 + bit;
147+
const v = (byte >> bit) & 1 ? 255 : 0;
148+
const i = (y * 128 + x) * 4;
149+
imgData.data[i] = v;
150+
imgData.data[i+1] = v;
151+
imgData.data[i+2] = v;
152+
imgData.data[i+3] = 255;
153+
}
154+
}
155+
}
156+
157+
// Create bitmap and draw once — prevents flicker
158+
const bmp = await createImageBitmap(imgData);
159+
ctx.drawImage(bmp, 0, 0);
160+
} catch(e) {
161+
failCount++;
162+
if (failCount > 30) location.reload();
163+
}
164+
}
165+
setInterval(update, 33); // ~30fps
166+
167+
function press(id){ fetch(`/btn?id=${id}&state=1`); }
168+
function release(id){ fetch(`/btn?id=${id}&state=0`); }
169+
</script>
170+
</body>
171+
</html>
172+
)rawliteral");
173+
});
174+
175+
server.on("/buffer", HTTP_GET, [](AsyncWebServerRequest *request) {
176+
AsyncWebServerResponse *response = request->beginResponse_P(
177+
200, "application/octet-stream", display_copy, sizeof(display_copy));
178+
request->send(response);
179+
});
180+
181+
server.on("/btn", HTTP_GET, [](AsyncWebServerRequest *request) {
182+
if (!request->hasParam("id") || !request->hasParam("state")) {
183+
request->send(400, "text/plain", "Missing params");
184+
return;
185+
}
186+
int id = request->getParam("id")->value().toInt();
187+
int state = request->getParam("state")->value().toInt();
188+
bool pressed = (state == 1);
189+
190+
if (id == 0) virtualButtons.left = pressed;
191+
else if (id == 1) virtualButtons.center = pressed;
192+
else if (id == 2) virtualButtons.right = pressed;
193+
194+
request->send(200, "text/plain", "OK");
195+
});
196+
197+
server.begin();
198+
199+
xTaskCreatePinnedToCore(
200+
Headless_Thread,
201+
"Headless",
202+
4096,
203+
NULL,
204+
1,
205+
NULL,
206+
1
207+
);
208+
}

src/main.cpp

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
#include "rf/scan.hpp"
2424
#include "rf/send.hpp"
2525

26+
#include "headless.h"
27+
2628
#include <LittleFS.h>
2729

2830
Menu menu;
@@ -45,10 +47,9 @@ Menu* active_menu = nullptr;
4547

4648
void setup() {
4749
Serial.begin(115200);
48-
50+
4951
if(!display.begin(SSD1306_SWITCHCAPVCC, 0X3C)) {
50-
Serial.println("SSD1306 allocation failed");
51-
for(;;);
52+
Serial.println("SSD1306 allocation failed, force starting headless...");
5253
}
5354

5455
pinMode(BUZZER_PIN, OUTPUT);
@@ -101,6 +102,8 @@ void setup() {
101102
}
102103
}
103104

105+
Headless_Setup(true);
106+
104107
menu.AddItem(MenuItem("WiFi", &menu_wifi));
105108
menu.AddItem(MenuItem("Bluetooth", &menu_bluetooth));
106109
menu.AddItem(MenuItem("Infrared", &menu_infrared));

0 commit comments

Comments
 (0)