Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion src/SplitFlapDisplay.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ void SplitFlapDisplay::init() {
magnetPosition = settings.getInt("magnetPosition");
maxVel = settings.getFloat("maxVel");
charSetSize = settings.getInt("charset");
String customCharsetString = settings.getString("custom_charset");

std::vector<int> settingAddresses = settings.getIntVector("moduleAddresses");
for (int i = 0; i < numModules; i++) {
Expand All @@ -33,7 +34,12 @@ void SplitFlapDisplay::init() {

for (uint8_t i = 0; i < numModules; i++) {
modules[i] = SplitFlapModule(
moduleAddresses[i], stepsPerRot, moduleOffsets[i] + displayOffset, magnetPosition, charSetSize
moduleAddresses[i],
stepsPerRot,
moduleOffsets[i] + displayOffset,
magnetPosition,
charSetSize,
customCharsetString
);
}

Expand Down
3 changes: 2 additions & 1 deletion src/SplitFlapDisplay.ino
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ JsonSettings settings = JsonSettings("config", {
{"sclPin", JsonSetting(9)},
{"stepsPerRot", JsonSetting(2048)},
{"maxVel", JsonSetting(15.0f)},
{"charset", JsonSetting(37)},
{"charset", JsonSetting(48)},
{"custom_charset", JsonSetting(" ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789':?!.-/@$#%")},
// Operational States
{"mode", JsonSetting(0)}
});
Expand Down
42 changes: 34 additions & 8 deletions src/SplitFlapModule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,44 @@ const char SplitFlapModule::ExtendedChars[48] = {
bool hasErrored = false;

// Default Constructor
SplitFlapModule::SplitFlapModule()
: address(0), position(0), stepNumber(0), stepsPerRot(0), chars(StandardChars), numChars(37), charSetSize(37) {
SplitFlapModule::SplitFlapModule() : SplitFlapModule(0x20, 2048, 0, 710, 37, String()) {
magnetPosition = 710;
}

// Constructor implementation
SplitFlapModule::SplitFlapModule(
uint8_t I2Caddress, int stepsPerFullRotation, int stepOffset, int magnetPos, int charsetSize
uint8_t I2Caddress, int stepsPerFullRotation, int stepOffset, int magnetPos, int charsetSize,
const String &charsetStr
)
: address(I2Caddress), position(0), stepNumber(0), stepsPerRot(stepsPerFullRotation), charSetSize(charsetSize) {
: address(I2Caddress), position(0), stepNumber(0), stepsPerRot(stepsPerFullRotation) {
magnetPosition = magnetPos + stepOffset;

chars = (charsetSize == 48) ? ExtendedChars : StandardChars;
numChars = (charsetSize == 48) ? 48 : 37;
int len = charsetStr.length();

if (len < 37) {
// Use StandardChars as fallback
Serial.println("Fallback StandardChars");
charSetSize = numChars = 37;
usingCustomChars = false;
for (int i = 0; i < numChars; i++) {
customChars[i] = StandardChars[i];
}
} else if (len >= 37) {
// Use custom charset, but truncate to either 37 or 48
usingCustomChars = true;
charSetSize = numChars = (len >= 48) ? 48 : 37;
for (int i = 0; i < numChars; i++) {
customChars[i] = charsetStr[i];
}
customChars[numChars] = '\0';
} else {
// Fallback if empty
charSetSize = numChars = 37;
usingCustomChars = false;
for (int i = 0; i < numChars; i++) {
customChars[i] = StandardChars[i];
}
}
}

void SplitFlapModule::writeIO(uint16_t data) {
Expand Down Expand Up @@ -83,12 +107,14 @@ void SplitFlapModule::init() {

int SplitFlapModule::getCharPosition(char inputChar) {
inputChar = toupper(inputChar);

for (int i = 0; i < charSetSize; i++) {
if (chars[i] == inputChar) {
if (customChars[i] == inputChar) {
return charPositions[i];
}
}
return 0; // Character not found, return blank

return 0; // fallback
}

void SplitFlapModule::stop() {
Expand Down
8 changes: 6 additions & 2 deletions src/SplitFlapModule.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ class SplitFlapModule {
// Constructor declarationS
SplitFlapModule(); // default constructor required to allocate memory for
// SplitFlapDisplay class
SplitFlapModule(uint8_t I2Caddress, int stepsPerFullRotation, int stepOffset, int magnetPos, int charSetSize);
SplitFlapModule(
uint8_t I2Caddress, int stepsPerFullRotation, int stepOffset, int magnetPos, int charsetSize,
const String &charset
);

void init();

Expand Down Expand Up @@ -42,10 +45,11 @@ class SplitFlapModule {
static const int motorPins[]; // Array of motor pins
static const int HallEffectPIN; // Hall Effect Sensor Pin (On PCF8575)

const char *chars; // pointer to active character set
int charPositions[48]; // support up to 48 characters
int numChars; // current number of characters
int charSetSize;
bool usingCustomChars = false;
char customChars[50];

static const char StandardChars[37];
static const char ExtendedChars[48];
Expand Down
12 changes: 11 additions & 1 deletion src/SplitFlapWebServer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -369,7 +369,13 @@ void SplitFlapWebServer::startWebServer() {

if (json["otaPass"].is<String>() && json["otaPass"].as<String>() != settings.getString("otaPass")) {
rebootRequired = true; // OTA password change can only be applied by rebooting
response["message"] = "Settings updated successfully, OTA Password has changed. Rebooting...";
}

if ((json["moduleCount"].is<int>() && json["moduleCount"].as<int>() != settings.getInt("moduleCount")) ||
(json["charset"].is<int>() && json["charset"].as<int>() != settings.getInt("charset")) ||
(json["custom_charset"].is<String>() &&
json["custom_charset"].as<String>() != settings.getString("custom_charset"))) {
rebootRequired = true;
}

if (json["mdns"].is<String>() && json["mdns"].as<String>() != settings.getString("mdns")) {
Expand Down Expand Up @@ -397,6 +403,10 @@ void SplitFlapWebServer::startWebServer() {
return request->send(400, "application/json", response.as<String>());
}

if (rebootRequired) {
response["message"] = "Settings updated successfully, Rebooting...";
}

response["type"] = "success";
response["persistent"] = reconnect;

Expand Down
120 changes: 101 additions & 19 deletions src/web/settings.html
Original file line number Diff line number Diff line change
Expand Up @@ -148,18 +148,24 @@

<div class="grid grid-cols-2 gap-2 w-full">
<div>
<label class="flex text-lg mt-4 justify-between">
<label
class="flex text-lg mt-4 justify-between"
for="date_format"
>
Date Format
</label>
<input
class="w-full p-3 mt-2 text-lg border border-gray-600 rounded-md text-center bg-neutral-700 text-gray-100"
type="text"
id="date_format"
x-model="settings.dateFormat"
placeholder="e.g. {dd}-{mm}-{yy}"
/>
</div>
<div>
<label class="flex text-lg mt-4 justify-between"
<label
class="flex text-lg mt-4 justify-between"
for="time_format"
>Time Format
<button
type="button"
Expand Down Expand Up @@ -215,6 +221,7 @@
<input
class="w-full p-3 mt-2 text-lg border border-gray-600 rounded-md text-center bg-neutral-700 text-gray-100"
type="text"
id="time_format"
x-model="settings.timeFormat"
placeholder="e.g. {HH}:{mm}"
/>
Expand Down Expand Up @@ -266,22 +273,60 @@
</div>
</div>

<label for="otaPass" class="block text-left text-lg mt-4">
OTA Password (will restart)
</label>
<input
class="w-full p-3 mt-2 text-lg border border-gray-600 rounded-md text-center bg-neutral-700 text-gray-100"
type="password"
id="otaPass"
x-model="settings.otaPass"
placeholder="Enter OTA Password"
/>
<div
class="w-full p-3 mt-2 text-sm text-white bg-red-700 rounded-md"
x-cloak
x-show="errors.key === 'otaPass'"
x-text="errors.message"
></div>
<div class="relative w-full">
<label for="otaPass" class="block text-lg mt-4">
OTA Password
<button
type="button"
class="ml-1 text-sm absolute right-0 text-gray-400 hover:text-white cursor-pointer transition-colors"
@click="$dispatch('open-help', {
title: 'OTA Password Help',
content: `
<p><strong>What is OTA?</strong></p>
<p>OTA (Over-the-Air) allows you to wirelessly upload new firmware or filesystem updates to your Split Flap Display.</p>

<p class='mt-2'><strong>What is the OTA Password?</strong></p>
<p>This password secures your device by requiring authentication before accepting any OTA updates. Without it, anyone on the same network could potentially push firmware to your device.</p>

<p class='mt-2'><strong>How to Use:</strong></p>
<ul class='list-disc list-inside pl-2'>
<li>Set a password in the <code>OTA Password</code> field.</li>
<li>Add the password to your <code>platformio.ini</code> as the <code>--auth</code> flag for OTA uploads.</li>
<li>Use the appropriate OTA environment (e.g. <code>esp32_s3_ota</code>) when uploading firmware or filesystem updates.</li>
</ul>
`
})"
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="size-6"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M9.879 7.519c1.171-1.025 3.071-1.025 4.242 0 1.172 1.025 1.172 2.687 0 3.712-.203.179-.43.326-.67.442-.745.361-1.45.999-1.45 1.827v.75M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9 5.25h.008v.008H12v-.008Z"
/>
</svg>
</button>
</label>
<input
class="w-full p-3 mt-2 text-lg border border-gray-600 rounded-md text-center bg-neutral-700 text-gray-100"
type="password"
id="otaPass"
x-model="settings.otaPass"
placeholder="Enter OTA Password"
/>
<div
class="w-full p-3 mt-2 text-sm text-white bg-red-700 rounded-md"
x-cloak
x-show="errors.key === 'otaPass'"
x-text="errors.message"
></div>
</div>

<h2
class="text-xl font-semibold text-left w-full border-b border-gray-600 pb-2 mt-10 mb-2 flex justify-between"
Expand Down Expand Up @@ -400,7 +445,8 @@
content: `
<ul class='list-disc list-inside pl-2'>
<li><strong>Number of modules:</strong> How many split-flap modules are connected.</li>
<li><strong>Character Set:</strong> 37 = standard (A-Z0-9), 48 = extended (includes symbols).</li>
<li><strong>Character Set:</strong> 37 = standard (A-Z 0-9), 48 = extended (includes symbols).</li>
<li><strong>Customise Characters:</strong> Specify your custom characters and symbols.</li>
<li><strong>Addresses:</strong> I²C address for each module. Typically starts at 32 and increases per module.</li>
<li><strong>Offsets:</strong> Fine-tune each module’s zero position if flaps are misaligned.</li>
</ul>
Expand Down Expand Up @@ -480,6 +526,42 @@
<option value="48">Extended (48)</option>
</select>
</div>

<div class="col-span-2">
<label
for="customCharset"
class="block text-left text-lg mt-4"
>
Customise Characters
</label>
<div class="relative">
<input
id="customCharset"
type="text"
x-model="settings.custom_charset"
class="w-full p-3 pr-16 text-lg border rounded-md text-center bg-neutral-700 text-gray-100"
:class="{
'border-red-600': settings.custom_charset.length !== settings.charset,
'border-green-500': settings.custom_charset.length === settings.charset
}"
placeholder=" ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789':?!.-/@$#%"
/>
<span
class="absolute right-3 top-1/2 transform -translate-y-1/2 text-sm"
:class="{
'text-red-600': settings.custom_charset.length !== settings.charset,
'text-green-500': settings.custom_charset.length === settings.charset
}"
x-text="`${settings.custom_charset.length}/${settings.charset}`"
></span>
</div>
<div
class="w-full p-3 mt-2 text-sm text-white bg-red-700 rounded-md"
x-cloak
x-show="errors.key === 'custom_charset'"
x-text="errors.message"
></div>
</div>
</div>

<label class="block text-left text-lg mt-4">
Expand Down