Skip to content
Merged
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
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
![format badge](https://github.com/jhoff/Split-Flap-Display/actions/workflows/format-check.yml/badge.svg?branch=main)
[![](https://dcbadge.limes.pink/api/server/https://discord.gg/RCvks4XXXH?style=flat)](https://discord.gg/RCvks4XXXH)


Looking for support? Find help on discord ☝️

Firmware for the modular Split Flap Display created by [Morgan Manly](https://github.com/ManlyMorgan/Split-Flap-Display)
Expand Down
16 changes: 15 additions & 1 deletion src/SplitFlapDisplay.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ void SplitFlapDisplay::init() {
displayOffset = settings.getInt("displayOffset");
magnetPosition = settings.getInt("magnetPosition");
maxVel = settings.getFloat("maxVel");
charSetSize = settings.getInt("charset");

std::vector<int> settingAddresses = settings.getIntVector("moduleAddresses");
for (int i = 0; i < numModules; i++) {
Expand All @@ -31,7 +32,9 @@ void SplitFlapDisplay::init() {
Serial.println();

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

SDAPin = settings.getInt("sdaPin");
Expand Down Expand Up @@ -157,7 +160,18 @@ void SplitFlapDisplay::writeChar(char inputChar, float speed) {
moveTo(targetPositions, speed);
}

String sanitizeInput(const String &input) {
String sanitized = input;

// Replace problematic characters
sanitized.replace("'", "'\\'");
sanitized.replace("%", "%%");

return sanitized;
}

void SplitFlapDisplay::writeString(String inputString, float speed, bool centering) {
inputString = sanitizeInput(inputString);
String displayString = inputString.substring(0, numModules);

if (centering) {
Expand Down
8 changes: 5 additions & 3 deletions src/SplitFlapDisplay.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ class SplitFlapDisplay {
void testCount();
void testRandom(float speed = MAX_RPM);
int getNumModules() { return numModules; }
int getCharsetSize() const { return charSetSize; }
void setMqtt(SplitFlapMqtt *mqttHandler);

private:
Expand All @@ -48,9 +49,10 @@ class SplitFlapDisplay {
int moduleOffsets[MAX_MODULES];
int displayOffset;

float maxVel; // Max Velocity In RPM
int stepsPerRot; // number of motor steps per full rotation of character
// drum
float maxVel; // Max Velocity In RPM
int charSetSize; // 37 for standard, 48 for extended
int stepsPerRot; // number of motor steps per full rotation of character
// drum
int magnetPosition; // position of drum wheel when magnet is detected
int SDAPin; // SDA pin
int SCLPin; // SCL pin
Expand Down
1 change: 1 addition & 0 deletions src/SplitFlapDisplay.ino
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ JsonSettings settings = JsonSettings("config", {
{"sclPin", JsonSetting(9)},
{"stepsPerRot", JsonSetting(2048)},
{"maxVel", JsonSetting(15.0f)},
{"charset", JsonSetting(37)},
// Operational States
{"mode", JsonSetting(0)}
});
Expand Down
50 changes: 27 additions & 23 deletions src/SplitFlapModule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,33 @@

// Array of characters, in order, the first item is located on the magnet on the
// character drum
const char SplitFlapModule::chars[37] = {' ', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L',
'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y',
'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
const int SplitFlapModule::numChars = sizeof(SplitFlapModule::chars) / sizeof(SplitFlapModule::chars[0]);
int SplitFlapModule::charPositions[37]; // to be written in init function
const char SplitFlapModule::StandardChars[37] = {' ', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L',
'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y',
'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};

bool hasErrored = false;
const char SplitFlapModule::ExtendedChars[48] = {
' ', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '0', '1', '2', '3', '4',
'5', '6', '7', '8', '9', '\'', ':', '?', '!', '.', '-', '/', '$', '@', '#', '%',
};

// Not used but useful to have
// const int SplitFlapModule::motorPins[] = {P06, P05, P04, P03}; // List of
// pins to set as OUTPUT const int SplitFlapModule::HallEffectPIN = P17; //Input
// pin for Hall effect sensor
bool hasErrored = false;

// Default Constructor
SplitFlapModule::SplitFlapModule() : address(0), position(0), stepNumber(0), stepsPerRot(0) { // default values
magnetPosition = 710; // sort of guessing
SplitFlapModule::SplitFlapModule()
: address(0), position(0), stepNumber(0), stepsPerRot(0), chars(StandardChars), numChars(37), charSetSize(37) {
magnetPosition = 710;
}

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

chars = (charsetSize == 48) ? ExtendedChars : StandardChars;
numChars = (charsetSize == 48) ? 48 : 37;
}

void SplitFlapModule::writeIO(uint16_t data) {
Expand All @@ -49,6 +54,13 @@ void SplitFlapModule::writeIO(uint16_t data) {

// Init Module, Setup IO Board
void SplitFlapModule::init() {
float stepSize = (float) stepsPerRot / (float) numChars;
float currentPosition = 0;
for (int i = 0; i < numChars; i++) {
charPositions[i] = (int) currentPosition;
currentPosition += stepSize;
}

uint16_t initState = 0b1111111111100001; // Pin 15 (17) as INPUT, Pins 1-4 as OUTPUT
writeIO(initState);

Expand All @@ -67,19 +79,11 @@ void SplitFlapModule::init() {
delay(initDelay);

stop();

// Generate Character Position Array
float stepSize = (float) stepsPerRot / (float) numChars;
float currentPosition = 0;
for (int i = 0; i < numChars; i++) {
charPositions[i] = (int) currentPosition;
currentPosition += stepSize;
}
}

int SplitFlapModule::getCharPosition(char inputChar) {
inputChar = toupper(inputChar);
for (int i = 0; i < numChars; i++) {
for (int i = 0; i < charSetSize; i++) {
if (chars[i] == inputChar) {
return charPositions[i];
}
Expand Down
14 changes: 9 additions & 5 deletions src/SplitFlapModule.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ class SplitFlapModule {
// Constructor declarationS
SplitFlapModule(); // default constructor required to allocate memory for
// SplitFlapDisplay class
SplitFlapModule(uint8_t I2Caddress, int stepsPerFullRotation, int stepOffset, int magnetPos);
SplitFlapModule(uint8_t I2Caddress, int stepsPerFullRotation, int stepOffset, int magnetPos, int charSetSize);

void init();

Expand All @@ -19,6 +19,7 @@ class SplitFlapModule {
int getMagnetPosition() const { return magnetPosition; } // position where magnet is detected
int getCharPosition(char inputChar); // get integer position given single character
int getPosition() const { return position; } // get integer position
int getCharsetSize() const { return numChars; } // getter for charset size

bool readHallEffectSensor(); // return the value read by the hall effect
// sensor
Expand All @@ -41,10 +42,13 @@ class SplitFlapModule {
static const int motorPins[]; // Array of motor pins
static const int HallEffectPIN; // Hall Effect Sensor Pin (On PCF8575)

static const char chars[37]; // all characters in order
static int charPositions[37]; // will be generated based on the characters and
// the magnetPosition variable
static const int numChars; // number of characters in module
const char *chars; // pointer to active character set
int charPositions[48]; // support up to 48 characters
int numChars; // current number of characters
int charSetSize;

static const char StandardChars[37];
static const char ExtendedChars[48];
};

// //PINs on the PCF8575 Board
Expand Down
26 changes: 26 additions & 0 deletions src/web/settings.html
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ <h1 class="text-2xl font-bold mx-auto" x-text="header"></h1>
<input
class="w-full p-3 mt-2 text-lg border border-gray-600 rounded-md text-center bg-gray-700 text-gray-100"
type="text"
id="name"
x-model="settings.name"
placeholder="Enter a name for this display"
/>
Expand All @@ -58,6 +59,7 @@ <h1 class="text-2xl font-bold mx-auto" x-text="header"></h1>
<input
class="w-full p-3 mt-2 text-lg border border-gray-600 rounded-md text-center bg-gray-700 text-gray-100"
type="text"
id="mdns"
x-model="settings.mdns"
placeholder="Enter mDNS hostname"
/>
Expand All @@ -74,6 +76,7 @@ <h1 class="text-2xl font-bold mx-auto" x-text="header"></h1>
<input
class="w-full p-3 mt-2 text-lg border border-gray-600 rounded-md text-center bg-gray-700 text-gray-100"
type="text"
id="otaPass"
x-model="settings.otaPass"
placeholder="Enter OTA Password"
/>
Expand All @@ -91,6 +94,7 @@ <h1 class="text-2xl font-bold mx-auto" x-text="header"></h1>
<select
class="w-full p-3 text-lg border border-gray-600 rounded-md text-center bg-gray-700 text-gray-100"
x-model="settings.timezone"
id="timezone"
>
<template x-for="(name, zone) in timezones" :key="zone">
<option
Expand Down Expand Up @@ -217,6 +221,7 @@ <h1 class="text-2xl font-bold mx-auto" x-text="header"></h1>
<input
class="w-full p-3 mt-2 text-lg border border-gray-600 rounded-md text-center bg-gray-700 text-gray-100"
type="number"
id="moduleCount"
x-model="settings.moduleCount"
min="1"
max="8"
Expand All @@ -229,12 +234,25 @@ <h1 class="text-2xl font-bold mx-auto" x-text="header"></h1>
x-text="errors.message"
></div>

<label for="charset" class="block text-left text-lg mt-4"
>Character Set</label
>
<select
class="w-full p-3 mt-2 text-lg border border-gray-600 rounded-md text-center bg-gray-700 text-white"
x-model.number="settings.charset"
id="charset"
>
<option value="37">Standard (37)</option>
<option value="48">Extended (48)</option>
</select>

<label for="moduleAddresses" class="block text-left text-lg mt-4"
>Module Addresses (comma-separated)</label
>
<input
class="w-full p-3 mt-2 text-lg border border-gray-600 rounded-md text-center bg-gray-700 text-gray-100"
type="text"
id="moduleAddresses"
x-model="settings.moduleAddresses"
placeholder="Enter addresses"
/>
Expand All @@ -251,6 +269,7 @@ <h1 class="text-2xl font-bold mx-auto" x-text="header"></h1>
<input
class="w-full p-3 mt-2 text-lg border border-gray-600 rounded-md text-center bg-gray-700 text-gray-100"
type="number"
id="magnetPosition"
x-model="settings.magnetPosition"
placeholder="Enter magnet position"
/>
Expand All @@ -267,6 +286,7 @@ <h1 class="text-2xl font-bold mx-auto" x-text="header"></h1>
<input
class="w-full p-3 mt-2 text-lg border border-gray-600 rounded-md text-center bg-gray-700 text-gray-100"
type="text"
id="moduleOffsets"
x-model="settings.moduleOffsets"
placeholder="Enter module offsets"
/>
Expand All @@ -283,6 +303,7 @@ <h1 class="text-2xl font-bold mx-auto" x-text="header"></h1>
<input
class="w-full p-3 mt-2 text-lg border border-gray-600 rounded-md text-center bg-gray-700 text-gray-100"
type="number"
id="displayOffset"
x-model="settings.displayOffset"
placeholder="Enter display offset"
/>
Expand All @@ -299,6 +320,7 @@ <h1 class="text-2xl font-bold mx-auto" x-text="header"></h1>
<input
class="w-full p-3 mt-2 text-lg border border-gray-600 rounded-md text-center bg-gray-700 text-gray-100"
type="number"
id="sdaPin"
x-model="settings.sdaPin"
placeholder="Enter SDA pin"
/>
Expand All @@ -315,6 +337,7 @@ <h1 class="text-2xl font-bold mx-auto" x-text="header"></h1>
<input
class="w-full p-3 mt-2 text-lg border border-gray-600 rounded-md text-center bg-gray-700 text-gray-100"
type="number"
id="sclPin"
x-model="settings.sclPin"
placeholder="Enter SCL pin"
/>
Expand All @@ -331,6 +354,7 @@ <h1 class="text-2xl font-bold mx-auto" x-text="header"></h1>
<input
class="w-full p-3 mt-2 text-lg border border-gray-600 rounded-md text-center bg-gray-700 text-gray-100"
type="number"
id="stepsPerRot"
x-model="settings.stepsPerRot"
placeholder="Enter steps per rotation"
/>
Expand All @@ -347,6 +371,7 @@ <h1 class="text-2xl font-bold mx-auto" x-text="header"></h1>
<input
class="w-full p-3 mt-2 text-lg border border-gray-600 rounded-md text-center bg-gray-700 text-gray-100"
type="number"
id="maxVel"
step="0.1"
x-model="settings.maxVel"
placeholder="Enter max velocity"
Expand Down Expand Up @@ -437,6 +462,7 @@ <h1 class="text-2xl font-bold mx-auto" x-text="header"></h1>
save() {
this.saving = true;
this.errors = {};

fetch("/settings", {
method: "POST",
headers: { "Content-Type": "application/json" },
Expand Down