The simplicity of a tactile gadget can be incredibly refreshing. I decided to merge the nostalgic allure of retro tech with the practicality of modern tools, creating a fidget toy that's not just fun but genuinely useful. This isn't your average desk toy; it's a retro, future dystopian-inspired gadget designed to make life just a bit easier, especially for those of us constantly converting measurements.
3D File attached
Parts:
NOTE:
I didn't expect to post a blog about this, but since the video had such a great response I wanted to make what I had available. HOWEVER, this project is not 100% complete, so please be understanding.
At the heart of this device is a rotary encoder with a clickable button, all powered by a creatively repurposed battery from a disposable vape, orchestrated by a D1 Mini. But the innovation doesn't stop at its functionality. Part of the charm and appeal of this project lies in the customization of its enclosure, particularly the custom paint job. On my version, I opted for a nuclear-retro-industrial look, adding a layer of personalization and fun to the gadget. This unique aesthetic not only accentuates its physical appeal but also enhances the overall user experience, making it a standout piece on any desk.
Whether you're a maker, a thinker, or simply someone who appreciates the beauty of a well-crafted tool, this retro, future dystopian fidget toy is a reminder that sometimes, the most innovative creations come from addressing the simplest needs.
3D File attached
Parts:
- 3D Printed housing (right now the back is open)
- D1 Mini
- Rotary encoder
- Standard rocker switch
- OLED I2C IIC Display 128x64
- 10K Pullup resistor
- Plastic, Polycarbonate or Lexan 54x25x2mm
NOTE:
I didn't expect to post a blog about this, but since the video had such a great response I wanted to make what I had available. HOWEVER, this project is not 100% complete, so please be understanding.
The Birth of a Practical Fidget Toy
The idea came from a simple need: the frequent conversion of units like inches to millimeters and ounces to grams. Sure, there are countless apps and online tools available, but they come with distractions, ads, or unnecessary complexity. What if there was a way to have these conversions at your fingertips, without the wait or hassle? Enter the concept of a fidget toy that serves a dual purpose: entertainment and utility.Functionality
The process is straightforward:- Power Up: A simple flip of the switch brings the device to life.
- Select and Convert: Select your category, click the dial to your desired unit, and twist to convert. The tactile satisfaction of clicking and twisting the dial, combined with the practical output of real-time conversions, makes for a uniquely satisfying experience.
The Build
The housing was designed in Fusion 360 and brought to life through the precision of SLA 3D printing. The heart of the operation is a rotary encoder with a clickable button, powered by a repurposed battery from a disposable vape, all orchestrated by a D1 Mini. It's a testament to the power of creative repurposing and DIY ingenuity.At the heart of this device is a rotary encoder with a clickable button, all powered by a creatively repurposed battery from a disposable vape, orchestrated by a D1 Mini. But the innovation doesn't stop at its functionality. Part of the charm and appeal of this project lies in the customization of its enclosure, particularly the custom paint job. On my version, I opted for a nuclear-retro-industrial look, adding a layer of personalization and fun to the gadget. This unique aesthetic not only accentuates its physical appeal but also enhances the overall user experience, making it a standout piece on any desk.
More Than Just a Toy
This project isn't just about creating another gadget; it's about reimagining what a fidget toy can be. It's for the tinkerers, the curious minds, and anyone who's ever found themselves lost in thought, flipping a pen or clicking a button. It combines the joy of manual interaction with the utility of a tool you'll find yourself using more often than you'd think.The Takeaway
In a world where digital devices dominate our attention, there's something profoundly satisfying about interacting with a physical object designed to both focus the mind and serve a practical purpose. This gadget is more than just a toy; it's a bridge between the tactile satisfaction of the past and the technological needs of the present.Whether you're a maker, a thinker, or simply someone who appreciates the beauty of a well-crafted tool, this retro, future dystopian fidget toy is a reminder that sometimes, the most innovative creations come from addressing the simplest needs.
Closing Thoughts
I hope this project inspires you to look at everyday annoyances as opportunities for creativity and innovation. The fusion of fun and function is a powerful combination, capable of producing objects that not only entertain but also enrich our lives in small, meaningful ways. So, the next time you catch yourself reaching for your phone to perform a simple task, remember: there might just be a more satisfying, analog solution waiting to be created.
Arduino Code:
//https://hackmakemod.com/
#include <EEPROM.h>
#include <Adafruit_SSD1306.h>
/*
* Definitions and Macros
*/
#define MODE_RELEASE
/* Pins settings */
#define RE_PIN_CLK D4
#define RE_PIN_DT D3
#define RE_PIN_SW D0
//#define RE_PIN_CLK D0
//#define RE_PIN_DT D5
//#define RE_PIN_SW D4
#define RE_DEBOUNCE_TIME 50 //ms
/* OLED settings */
#define OLED_SSD1306_I2C_ADDR 0x3C
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
#define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin)
#define CURSOR_ARROW_WIDTH 10
#define OLED_SUBMENU_START_Y 18
#define OLED_TEXT_LINE_GAP 9 //pixel
#define MENU_TEXT_LEN_MAX 16
#define MENU_LIST_ITEM_MAX 16
#define MAX_ITEM_DISPLAY 5
#define INCH_FRAC_ARRAY_MAX 32
#define DECIMAL_PLACES 2
/* Enum */
typedef enum
{
/* Main menu */
idLength = (0), idMass, idSpeed, idTemperature,
/* Length */
idKilometer, idMeter, idCentimeter, idMillimeter, idMicrometer, idNanometer, idMile, idYard, idFoot, idFootAndInch, idInch, idInchFractional, idNauticalMile,
/* MASS */
idMetricTon, idKilogram, idGram, idMilligram, idMicrogram, idImperialTon, idUSTon, idStone, idPound, idOunce,
/* Speed */
idMPH, idFPS, idMPS, idKPH, idKnots,
/* Temperature */
idCelsius, idFahrenheit, idKelvin,
idMenuMax
} e_Menu;
typedef enum {
IDLE, FOCUSED,
} e_MenuState;
typedef enum {
RE_SLOW, RE_FAST
} e_ReSpeed;
typedef enum {
KEY_UP = (0), KEY_DOWN, KEY_OK, KEY_NONE
} e_Key;
typedef enum {
E_CURSOR_ARROW, E_CURSOR_HIGHLIGH
} e_CursorType;
typedef enum {
E_MENU_MAIN = (0), E_MENU_FROM, E_MENU_TO, E_MENU_CONVERTER, E_MENU_MAX
} e_MenuType;
typedef struct {
uint8_t type; uint8_t mainId; uint8_t fromId; uint8_t toId;
} st_MenuState;
typedef struct
{
uint8_t whole; uint8_t numerator; uint8_t denominator;
} st_MixedNumber;
/*
* Local variables
*/
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
int counter = 0;
int currentStateCLK;
int lastStateCLK;
String currentDir ="";
unsigned long lastButtonPress = 0;
static unsigned long g_re_debounce = 0;
uint8_t g_lastRotate = KEY_DOWN;
String MenuShortText[idMenuMax] = {
/* Main menu */
String("Length"), String("Mass"), String("Speed"), String("Temperature"),
/* Length */
String("km"), String("Meter"), String("cm"), String("Mm"), String("um"), String("nm"), String("Mile"), String("Yard"), String("Foot"), String("FootInch"), String("Inch"), String("Inch(F)"), String("NMile"),
/* Mass */
String("Ton"), String("kg"), String("Gram"), String("mgram"), String("ugram"), String("ImpTon"), String("USTon"), String("Stone"), String("Pound"), String("Ounce"),
/* Speed */
String("MPH"), String("FPS"), String("MPS"), String("KPH"), String("Knots"),
/* Temperature */
String("C"), String("F"), String("K")
};
String MenuLongText[idMenuMax] = {
/* Main menu */
String("Length"), String("Mass"), String("Speed"), String("Temperature"),
/* Length */
String("Kilometer"), String("Meter"), String("Centimeter"), String("Millimeter"), String("Micrometer"), String("Nanometer"), String("Mile"), String("Yard"), String("Foot"), String("FootInch"),String("Inch"), String("Inch Fractional"), String("NauticalMile"),
/* Mass */
String("MetricTon"), String("Kilogram"), String("Gram"), String("Milligram"), String("Microgram"), String("ImperialTon"), String("USTon"), String("Stone"), String("Pound"), String("Ounce"),
/* Speed */
String("MPH"), String("FPS"), String("MPS"), String("KPH"), String("Knots"),
/* Temperature */
String("C (Celsius)"), String("F (Fahrenheit)"), String("K (Kelvin)")
};
/* Main menu List */
uint8_t MainMenu_List[] = {
idLength, idMass, idSpeed, idTemperature
};
/* Length List */
uint8_t Length_List[] = {
idKilometer,idMeter,idCentimeter,idMillimeter,idMile,idYard,idFoot,idFootAndInch,idInch,idInchFractional
};
/* Mass List */
uint8_t Mass_List[] = {
idMetricTon,idKilogram,idGram,idMilligram,idImperialTon,idUSTon,idPound,idOunce
};
/* Speed List */
uint8_t Speed_List[] = {
idMPH,idFPS,idMPS,idKPH,idKnots
};
/* Temperature List */
uint8_t Temperature_List[] = {
idCelsius,idFahrenheit,idKelvin
};
/* Menu manager */
uint8_t* p_crtMenu = NULL;
uint8_t g_menuList[MENU_LIST_ITEM_MAX];
uint8_t g_menuCount = 0;
uint8_t g_cursorType = E_CURSOR_ARROW;
uint8_t g_focusIndex = 0;
uint8_t g_topIndex = 0;
st_MenuState g_menuState = { E_MENU_MAIN, idLength, 0, 0 };
/* Converter */
struct {
char input_buffer[19];
char result[19];
float inputInt = 0.0;
float inputFloat = 0.0;
float output = 0.0;
} g_converter;
uint8_t m_inchFractionIdx = 0;
st_MixedNumber m_inchFractionArray[INCH_FRAC_ARRAY_MAX] = {
{ 0, 1, 32}, { 0, 1, 16}, { 0, 3, 32}, { 0, 1, 8}, { 0, 5, 32}, { 0, 3, 16}, { 0, 7, 32}, { 0, 1, 4},
{ 0, 9, 32}, { 0, 5, 16}, { 0, 11, 32}, { 0, 3, 8}, { 0, 13, 32}, { 0, 7, 16}, { 0, 15, 32}, { 0, 1, 2},
{ 0, 17, 32}, { 0, 9, 16}, { 0, 19, 32}, { 0, 5, 8}, { 0, 21, 32}, { 0, 11, 16}, { 0, 23, 32}, { 0, 3, 4},
{ 0, 25, 32}, { 0, 3, 16}, { 0, 27, 32}, { 0, 7, 8}, { 0, 29, 32}, { 0, 15, 16}, { 0, 31, 32}, { 1, 0, 1},
};
/* Bitmap */
static const uint8_t cursor_arrow_bm[] = {
0x00, 0x00, 0x04, 0x00, 0x06, 0x00, 0x3f, 0x00, 0x3f, 0x00, 0x06, 0x00, 0x04, 0x00, 0x00, 0x00
};
/*
* Local functions
*/
bool LocalInitDatabase(void);
void dectofrac(float input);
void LocalHandleRotaryButton(void);
void LocalDisplayMenu(void);
void LocalPrepareMenuItems(void);
void LocalHandleKey(uint8_t key, uint8_t speed = RE_SLOW);
void LocalHandleKeyUp(void);
void LocalHandleKeyDown(void);
void LocalHandleKeyOK(void);
void LocalConverterHandleKey( uint8_t key, uint8_t speed );
void LocalConvert( uint8_t from, uint8_t to, float input, float &output );
void setup()
{
Serial.begin( 115200 );
/* OLED Settings */
display.begin( SSD1306_SWITCHCAPVCC, OLED_SSD1306_I2C_ADDR );
display.clearDisplay();
display.setTextColor( WHITE );
display.setTextSize( 1 );
display.setRotation( 0 );
display.setTextWrap( false );
display.dim(0); //Set brightness (0 is maximun and 1 is a little dim)
p_crtMenu = MainMenu_List;
g_menuCount = sizeof(MainMenu_List);
g_menuState = { E_MENU_MAIN, idLength, 0, 0 };
/* Screen initiation */
if ( LocalInitDatabase() == false )
{
LocalPrepareMenuItems();
LocalDisplayMenu();
}
else {
LocalDisplayConverter();
}
/* Rotary encoder button settings */
pinMode(RE_PIN_CLK,INPUT);
pinMode(RE_PIN_DT, INPUT);
pinMode(RE_PIN_SW, INPUT_PULLUP);
lastStateCLK = digitalRead(RE_PIN_CLK);
}
void loop()
{
LocalHandleRotaryButton();
}
void LocalPrepareMenuItems(void)
{
if ( p_crtMenu && g_menuCount > 0 )
{
if ( g_menuState.type == E_MENU_MAIN || g_menuState.type == E_MENU_FROM )
{
for ( uint8_t i = 0; i < g_menuCount; i++ )
{
g_menuList[i] = p_crtMenu[i];
}
}
else if ( g_menuState.type == E_MENU_TO )
{
for ( uint8_t i = 0, j = 0; i < g_menuCount; i++ )
{
if ( p_crtMenu[i] != g_menuState.fromId )
{
g_menuList[j] = p_crtMenu[i];
j++;
}
}
g_menuCount--;
}
else {
// Nothing to do with Converter
}
}
}
void LocalDrawLineText( uint8_t line, String text, uint8_t state )
{
if ( E_CURSOR_ARROW == g_cursorType ) {
display.setCursor( CURSOR_ARROW_WIDTH, line * OLED_TEXT_LINE_GAP + OLED_SUBMENU_START_Y );
}
display.setTextSize( 1 );
if ( FOCUSED == state )
{
if ( E_CURSOR_ARROW == g_cursorType )
{
// Draw arrow
display.drawBitmap( 0, line * OLED_TEXT_LINE_GAP + OLED_SUBMENU_START_Y,
cursor_arrow_bm, 16, 8, WHITE);
}
}
else
{
if ( E_CURSOR_ARROW == g_cursorType )
{
// Clear arrow
display.fillRect( 0, line * OLED_TEXT_LINE_GAP + OLED_SUBMENU_START_Y, CURSOR_ARROW_WIDTH, 8, BLACK);
}
}
display.print( text );
}
String LocalGetTitle()
{
String typeStr = "";
switch ( g_menuState.mainId )
{
case idLength: typeStr = String("LENGTH"); break;
case idMass : typeStr = String("MASS"); break;
case idSpeed : typeStr = String("SPEED"); break;
case idTemperature: typeStr = String("Temp"); break;
default: typeStr = String("Unknown"); break;
}
if ( g_menuState.type == E_MENU_FROM ) {
return String("Convert ") + typeStr + String(" from");
}
else if ( g_menuState.type == E_MENU_TO ) {
return String("Convert ") + typeStr + String(" to");
}
else return String("Main Menu");
}
void LocalUpdateConverter(void)
{
if ( idTemperature == g_menuState.mainId )
{
LocalConvert( g_menuState.fromId, g_menuState.toId, g_converter.inputFloat, g_converter.output );
}
else
{
if ( idLength == g_menuState.mainId && idInchFractional == g_menuState.toId )
{
LocalConvert( g_menuState.fromId, idInch, g_converter.inputInt, g_converter.output );
strcpy( g_converter.input_buffer, String(g_converter.output, 1).c_str() );
dectofrac( g_converter.output );
}
else if ( idLength == g_menuState.mainId && idInchFractional == g_menuState.fromId )
{
g_converter.inputFloat = (float)m_inchFractionArray[m_inchFractionIdx].whole +
(float)m_inchFractionArray[m_inchFractionIdx].numerator / (float)m_inchFractionArray[m_inchFractionIdx].denominator;
LocalConvert( idInch, g_menuState.toId, g_converter.inputFloat, g_converter.output );
}
else
{
LocalConvert( g_menuState.fromId, g_menuState.toId, g_converter.inputInt, g_converter.output );
}
}
display.fillRect( 5, 25, 47, 15, WHITE );
display.setTextSize( 1 );
display.setTextColor( BLACK, WHITE );
display.setCursor( 10, 30 );
if ( idTemperature == g_menuState.mainId )
{
display.print( String(g_converter.inputFloat, 1) );
}
else if ( idLength == g_menuState.mainId && idInchFractional == g_menuState.fromId )
{
char fraction[20];
if ( m_inchFractionArray[m_inchFractionIdx].whole != 0 )
{
sprintf(fraction, "%d %d/%d", m_inchFractionArray[m_inchFractionIdx].whole,
m_inchFractionArray[m_inchFractionIdx].numerator,
m_inchFractionArray[m_inchFractionIdx].denominator);
}
else
{
sprintf(fraction, "%d/%d", m_inchFractionArray[m_inchFractionIdx].numerator,
m_inchFractionArray[m_inchFractionIdx].denominator);
}
display.print( String(fraction) );
}
else
{
display.print( String(g_converter.inputInt, 0) );
}
display.setTextColor( WHITE );
display.fillRect( 80, 25, 48, 15, BLACK );
display.setCursor( 80, 30 );
if ( idLength == g_menuState.mainId && idInchFractional == g_menuState.toId )
{
display.print( String(g_converter.result) );
}
else
{
display.print( String(g_converter.output, DECIMAL_PLACES) );
}
display.display();
}
void LocalDisplayConverter(void)
{
display.clearDisplay();
/* Draw title */
String title = MenuLongText[g_menuState.mainId];
display.setCursor( 0, 0 );
if ( title.length() <= 11 ) {
display.setTextSize( 2 );
} else {
display.setTextSize( 1 );
}
display.print( title );
display.fillRect( 5, 25, 47, 39, WHITE );
display.setTextSize( 1 );
/* Draw Unit name */
display.setTextColor( BLACK, WHITE );
display.setCursor( 10, 45 );
display.print( MenuShortText[g_menuState.fromId] );
/* */
display.setTextColor( WHITE );
display.setCursor( 60, 30 );
display.print( String("=") );
display.setCursor( 80, 45 );
display.print( MenuShortText[g_menuState.toId] );
LocalUpdateConverter();
display.display();
EEPROM.write( 0, 1 );
EEPROM.write( 1, g_menuState.mainId );
EEPROM.write( 2, g_menuState.fromId );
EEPROM.write( 3, g_menuState.toId );
EEPROM.commit();
}
void LocalDisplayMenu(void)
{
String title = LocalGetTitle();
display.clearDisplay();
if ( g_menuState.type == E_MENU_CONVERTER ) {
LocalDisplayConverter();
return;
}
if ( p_crtMenu && g_menuCount > 0 )
{
/* Display title */
display.setCursor( 0, 0 );
if ( title.length() <= 11 ) {
display.setTextSize( 2 );
} else {
display.setTextSize( 1 );
}
display.print( title );
/* Display menu items */
for ( uint8_t i = 0; i < MAX_ITEM_DISPLAY; i++ )
{
if ( g_topIndex + i < g_menuCount )
{
LocalDrawLineText( i, MenuLongText[g_menuList[g_topIndex + i]],
( (g_topIndex + i) == g_focusIndex )? FOCUSED : IDLE );
}
}
display.display();
}
}
void LocalHandleKeyDown()
{
uint8_t prev_top = g_topIndex;
uint8_t prev_focus = g_focusIndex;
g_focusIndex++;
if ( g_focusIndex >= g_menuCount )
{
g_focusIndex = 0;
}
if ( g_focusIndex > g_topIndex && (g_focusIndex - g_topIndex) > (MAX_ITEM_DISPLAY - 1) )
{
//new page
g_topIndex++;
}
else if ( g_focusIndex <= g_topIndex )
{
// Back to first item
g_topIndex = 0;
}
if ( prev_top != g_topIndex )
{
LocalDisplayMenu();
}
else
{
LocalDrawLineText( prev_focus - g_topIndex, MenuLongText[g_menuList[prev_focus]], IDLE );
LocalDrawLineText( g_focusIndex - g_topIndex, MenuLongText[g_menuList[g_focusIndex]], FOCUSED );
display.display();
}
}
void LocalHandleKeyUp()
{
uint8_t prev_top = g_topIndex;
uint8_t prev_focus = g_focusIndex;
if ( g_focusIndex > 0 )
{
g_focusIndex--;
}
else if ( g_focusIndex == 0 )
{
g_focusIndex = g_menuCount - 1;
}
if ( g_focusIndex > g_topIndex && (g_focusIndex - g_topIndex) > (MAX_ITEM_DISPLAY - 1) )
{
g_topIndex = g_focusIndex - (MAX_ITEM_DISPLAY - 1);
}
if ( g_focusIndex < g_topIndex )
{
g_topIndex = g_focusIndex;
}
if ( prev_top != g_topIndex )
{
LocalDisplayMenu();
}
else
{
LocalDrawLineText( prev_focus - g_topIndex, MenuLongText[g_menuList[prev_focus]], IDLE );
LocalDrawLineText( g_focusIndex - g_topIndex, MenuLongText[g_menuList[g_focusIndex]], FOCUSED );
display.display();
}
}
void LocalHandleKeyOK(void)
{
if ( E_MENU_MAIN == g_menuState.type )
{
g_menuState.mainId = g_menuList[g_topIndex + g_focusIndex];
if ( idLength == g_menuState.mainId ) {
p_crtMenu = Length_List;
g_menuCount = sizeof(Length_List);
}
else if ( idMass == g_menuState.mainId ) {
p_crtMenu = Mass_List;
g_menuCount = sizeof(Mass_List);
}
else if ( idSpeed == g_menuState.mainId ) {
p_crtMenu = Speed_List;
g_menuCount = sizeof(Speed_List);
}
else if ( idTemperature == g_menuState.mainId ) {
p_crtMenu = Temperature_List;
g_menuCount = sizeof(Temperature_List);
}
g_menuState.type = E_MENU_FROM;
}
else if ( E_MENU_FROM == g_menuState.type )
{
g_menuState.fromId = g_menuList[g_focusIndex];
g_menuState.type = E_MENU_TO;
}
else if ( E_MENU_TO == g_menuState.type )
{
g_menuState.toId = g_menuList[g_focusIndex];
g_menuState.type = E_MENU_CONVERTER;
LocalDisplayConverter();
Serial.print(MenuLongText[g_menuState.mainId]);Serial.print(" - ");
Serial.print(" from ");Serial.print(MenuLongText[g_menuState.fromId]);Serial.print(" to ");
Serial.println(MenuLongText[g_menuState.toId]);
}
else {
g_menuState.type = E_MENU_MAIN;
p_crtMenu = MainMenu_List;
g_menuCount = sizeof(MainMenu_List);
EEPROM.write( 0, 0 );
EEPROM.commit();
}
g_focusIndex = 0;
g_topIndex = 0;
LocalPrepareMenuItems();
LocalDisplayMenu();
}
void LocalConverterHandleKey( uint8_t key, uint8_t speed )
{
uint8_t step = ( RE_SLOW == speed )? 1 : 10;
switch ( key )
{
case KEY_DOWN:
if ( idTemperature == g_menuState.mainId )
{
if ( g_converter.inputFloat < 100.0 )
{
g_converter.inputFloat += 0.1 * step;
}
}
else if ( idLength == g_menuState.mainId && idInchFractional == g_menuState.fromId )
{
m_inchFractionIdx++;
if ( m_inchFractionIdx >= INCH_FRAC_ARRAY_MAX )
{
m_inchFractionIdx = 0;
}
}
else if ( g_converter.inputInt < 1000 )
{
g_converter.inputInt += step;
}
LocalUpdateConverter();
break;
case KEY_UP:
if ( idTemperature == g_menuState.mainId )
{
if ( g_converter.inputFloat >= -100 )
{
g_converter.inputFloat -= 0.1 * step;
}
}
else if ( idLength == g_menuState.mainId && idInchFractional == g_menuState.fromId )
{
if ( m_inchFractionIdx > 0 )
{
m_inchFractionIdx--;
}
else
{
m_inchFractionIdx = INCH_FRAC_ARRAY_MAX - 1;
}
}
else if ( g_converter.inputInt > 0 )
{
if ( g_converter.inputInt > 10 ) g_converter.inputInt -= 1 * step;
else g_converter.inputInt = ( 1 == step )? g_converter.inputInt - 1 : 0;
}
LocalUpdateConverter();
break;
case KEY_OK:
g_menuState.type = E_MENU_MAIN;
p_crtMenu = MainMenu_List;
g_menuCount = sizeof(MainMenu_List);
g_focusIndex = 0;
g_topIndex = 0;
LocalPrepareMenuItems();
LocalDisplayMenu();
EEPROM.write( 0, 0 );
EEPROM.commit();
break;
default:
break;
}
}
void LocalHandleKey(uint8_t key, uint8_t speed)
{
if ( g_menuState.type < E_MENU_CONVERTER )
{
if ( KEY_DOWN == key ) {
LocalHandleKeyDown();
} else if ( KEY_UP == key ) {
LocalHandleKeyUp();
} else if ( KEY_OK == key ) {
LocalHandleKeyOK();
}
}
else
{
//Converter
LocalConverterHandleKey( key, speed );
}
}
void LocalHandleRotaryButton(void)
{
// Read the current state of CLK
currentStateCLK = digitalRead(RE_PIN_CLK);
// If last and current state of CLK are different, then pulse occurred
// React to only 1 state change to avoid double count
#if defined (MODE_RELEASE)
if (currentStateCLK != lastStateCLK && currentStateCLK == 1){
#else
if (currentStateCLK != lastStateCLK /*&& currentStateCLK == 1*/){
#endif
if ( millis() - g_re_debounce > RE_DEBOUNCE_TIME )
{
// If the DT state is different than the CLK state then
// the encoder is rotating CCW so decrement
if (digitalRead(RE_PIN_DT) != currentStateCLK) {
#if !defined (MODE_RELEASE)
counter ++;
currentDir ="CW";
#endif
LocalHandleKey(KEY_DOWN);
g_lastRotate = KEY_DOWN;
} else {
// Encoder is rotating CW so increment
#if !defined (MODE_RELEASE)
counter --;
currentDir ="CCW";
#endif
LocalHandleKey(KEY_UP);
g_lastRotate = KEY_UP;
}
#if !defined (MODE_RELEASE)
Serial.print("Direction: ");
Serial.print(currentDir);
Serial.print(" | Counter: ");
Serial.println(counter);
#endif
}
else
{
LocalHandleKey(g_lastRotate, RE_FAST);
}
g_re_debounce = millis();
}
// Remember last CLK state
lastStateCLK = currentStateCLK;
// Read the button state
int btnState = digitalRead(RE_PIN_SW);
//If we detect LOW signal, button is pressed
if (btnState == LOW) {
//if 50ms have passed since last LOW pulse, it means that the
//button has been pressed, released and pressed again
if (millis() - lastButtonPress > 50) {
Serial.println("Button pressed!");
LocalHandleKey(KEY_OK);
}
// Remember last button press event
lastButtonPress = millis();
}
// Put in a slight delay to help debounce the reading
delay(1);
}
bool LocalInitDatabase(void)
{
EEPROM.begin(128);
uint8_t lastScreenType = EEPROM.read( 0 );
uint8_t lastMainId = 0;
uint8_t lastFrom = 0;
uint8_t lastTo = 0;
Serial.println("Last screen: " + String(lastScreenType));
if ( lastScreenType == 1 )
{
lastMainId = EEPROM.read( 1 );
lastFrom = EEPROM.read( 2 );
lastTo = EEPROM.read( 3 );
g_menuState.mainId = lastMainId;
g_menuState.fromId = lastFrom;
g_menuState.toId = lastTo;
g_menuState.type = E_MENU_CONVERTER;
return true;
// m_currentMenu = reinterpret_cast<CMainMenu*>(m_mainMenu)->GotoConvertScreen( lastMainId, lastFrom, lastTo );
}
return false;
}
long getMultiplier(char *input) {
char* charDec = strtok(input, ".");
charDec = strtok(NULL, ".");
int i = strlen(charDec);
long multiplier = pow(10, (i + 0.000005));
return multiplier;
}
int gcd(int a, int b) {
int R;
while((a % b) > 0) {
R = a % b;
a = b;
b = R;
}
return b;
}
void dectofrac(float input) {
static char res[19];
// Split whole number and decimal
int whole_number = (int)input;
float decimal = input - whole_number;
long multiplier = getMultiplier(g_converter.input_buffer);
long numerator = ((decimal + 0.000001) * multiplier);
long denominator = (1 * multiplier);
int gcd_val = gcd(numerator, denominator);
// Reduce!
numerator = numerator / gcd_val;
denominator = denominator / gcd_val;
if ( whole_number != 0 )
{
sprintf(res, "%d %ld/%ld", whole_number, numerator, denominator);
}
else
{
sprintf(res, "%ld/%ld", numerator, denominator);
}
Serial.print("result: "); Serial.println(res);
strcpy(g_converter.result, res);
}
void LocalConvert( uint8_t from, uint8_t to, float input, float &output )
{
if ( from == to )
{
output = input;
return;
}
if ( idKilometer == from )
{
switch ( to )
{
case idKilometer: break;
case idMeter: output = input * 1000; break;
case idCentimeter: output = input * 100000; break;
case idMillimeter: output = input * 1000000; break;
case idMicrometer: output = input * 1000000000; break;
case idNanometer: output = input * 1000000000000;break;
case idMile: output = input * 0.621371; break;
case idYard: output = input * 1093.61; break;
case idFoot: case idFootAndInch: output = input * 3280.84; break;
case idInch: output = input * 39370.1; break;
case idInchFractional: break;
case idNauticalMile: output = input * 0.539957; break;
}
}
else if ( idMeter == from )
{
switch ( to )
{
case idKilometer: output = input * 0.001; break;
case idCentimeter: output = input * 100; break;
case idMillimeter: output = input * 1000; break;
case idMicrometer: break;
case idNanometer: break;
case idMile: output = input * 0.000621371; break;
case idYard: output = input * 1.09361; break;
case idFoot: case idFootAndInch: output = input * 3.28084; break;
case idInch: output = input * 39.3701; break;
case idInchFractional: break;
case idNauticalMile: output = input * 0.000539957; break;
}
}
else if ( idCentimeter == from )
{
switch ( to )
{
case idKilometer: output = input / 100000; break;
case idMeter: output = input / 100; break;
case idCentimeter: ; break;
case idMillimeter: output = input * 10; break;
case idMicrometer: output = input * 10000; break;
case idNanometer: output = input * 10000000;break;
case idMile: output = input / 160900; break;
case idYard: output = input / 91.44; break;
case idFoot: case idFootAndInch: output = input / 30.48; break;
case idInch: output = input / 2.54; break;
case idInchFractional: break;
case idNauticalMile: output = input / 185200; break;
}
}
else if ( idMillimeter == from )
{
switch ( to )
{
case idKilometer: output = input / 1000000; break;
case idMeter: output = input / 1000; break;
case idCentimeter: output = input / 10; break;
case idMillimeter: ; break;
case idMicrometer: output = input * 1000 ; break;
case idNanometer: output = input * 1000000;break;
case idMile: output = input / 1609000; break;
case idYard: output = input / 914.4; break;
case idFoot: case idFootAndInch: output = input / 304.8; break;
case idInch: output = input / 25.4; break;
case idInchFractional: break;
case idNauticalMile: output = input / 1852000; break;
}
}
else if ( idMicrometer == from )
{
switch ( to )
{
case idKilometer: output = input / 100000000; break;
case idMeter: output = input / 1000000; break;
case idCentimeter: output = input / 10000; break;
case idMillimeter: output = input / 1000; break;
case idMicrometer: ; break;
case idNanometer: output = input * 1000;break;
case idMile: output = input / 1609000000; break;
case idYard: output = input / 914400; break;
case idFoot: case idFootAndInch: output = input / 304800; break;
case idInch: output = input / 25400; break;
case idInchFractional: break;
case idNauticalMile: output = input / 1852000000; break;
}
}
else if ( idNanometer == from )
{
switch ( to )
{
case idKilometer: output = input / 1000000000000; break;
case idMeter: output = input / 1000000000; break;
case idCentimeter: output = input / 10000000; break;
case idMillimeter: output = input / 1000000 ; break;
case idMicrometer: output = input / 1000 ; break;
case idNanometer: ;break;
case idMile: output = input / 1609000000000; break;
case idYard: output = input / 914400000; break;
case idFoot: case idFootAndInch: output = input / 304800000; break;
case idInch: output = input / 25400000; break;
case idInchFractional: break;
case idNauticalMile: output = input / 1852000000000; break;
}
}
else if ( idMile == from )
{
switch ( to )
{
case idKilometer: output = input * 1.609; break;
case idMeter: output = input * 1609.34; break;
case idCentimeter: output = input * 160934; break;
case idMillimeter: output = input * 1609340;break;
case idMicrometer: output = input * 1609340000;break;
case idNanometer: output = input * 1609340000000;break;
case idYard: output = input * 1760; break;
case idFoot: case idFootAndInch: output = input * 5280; break;
case idInch: output = input * 63360; break;
case idInchFractional: break;
case idNauticalMile: output = input * 0.868976; break;
}
}
else if ( idYard == from )
{
switch ( to )
{
case idKilometer: output = input / 1094;break;
case idMeter: output = input * 0.9144; break;
case idCentimeter: output = input * 91.44; break;
case idMillimeter: output = input * 914.4; break;
case idMicrometer: output = input * 914400; break;
case idNanometer: output = input * 914400000; break;
case idMile: output = input * 0.000568182; break;
case idFoot: case idFootAndInch: output = input * 3; break;
case idInch: output = input * 36; break;
case idInchFractional: break;
case idNauticalMile: output = input / 2025; break;
}
}
else if ( idFoot == from )
{
switch ( to )
{
case idKilometer: output = input * 0.0003048; break;
case idMeter: output = input * 0.3048; break;
case idCentimeter: output = input * 30.48; break;
case idMillimeter: output = input * 304.8; break;
case idMicrometer: output = input * 304800; break;
case idNanometer: output = input * 304800000; break;
case idMile: output = input * 0.000189394; break;
case idYard: output = input * 0.333333; break;
case idInch: output = input * 12; break;
case idInchFractional: break;
case idNauticalMile: output = input * 0.000164579; break;
}
}
else if ( idInch == from )
{
switch ( to )
{
case idKilometer: output = input / 39370; break;
case idMeter: output = input * 0.0254; break;
case idCentimeter: output = input * 2.54; break;
case idMillimeter: output = input * 25.4; break;
case idMicrometer: output = input * 25400; break;
case idNanometer: output = input * 25400000; break;
case idMile: output = input / 63360; break;
case idYard: output = input * 0.0277778; break;
case idFoot: case idFootAndInch: output = input * 0.0833333; break;
case idInchFractional: break;
case idNauticalMile: output = input / 72910 ; break;
}
}
else if ( idNauticalMile == from )
{
switch ( to )
{
case idKilometer: output = input * 1.852; break;
case idMeter: output = input * 1852; break;
case idCentimeter: output = input * 185200; break;
case idMillimeter: output = input * 1852000; break;
case idMicrometer: output = input * 0; break;
case idNanometer: output = input * 0; break;
case idMile: output = input * 1.15078; break;
case idYard: output = input * 2025.37; break;
case idFoot: case idFootAndInch: output = input * 6076.12; break;
case idInchFractional: break;
case idInch: output = input * 72913.4; break;
}
}
/*
* SPEED
*/
else if ( idMPH == from )
{
switch ( to )
{
case idFPS: output = input * 1.46667; break;
case idMPS: output = input * 0.44704; break;
case idKPH: output = input * 1.60934; break;
case idKnots: output = input * 0.868976; break;
}
}
else if ( idFPS == from )
{
switch ( to )
{
case idMPH: output = input / 1.467; break;
case idMPS: output = input / 3.281; break;
case idKPH: output = input * 1.097; break;
case idKnots: output = input / 1.688; break;
}
}
else if ( idMPS == from )
{
switch ( to )
{
case idMPH: output = input * 2.237; break;
case idFPS: output = input * 3.281; break;
case idKPH: output = input * 3.6; break;
case idKnots: output = input * 1.944 ; break;
}
}
else if ( idKPH == from )
{
switch ( to )
{
case idMPH: output = input / 1.609; break;
case idFPS: output = input / 1.097; break;
case idMPS: output = input / 3.6; break;
case idKnots: output = input / 1.852; break;
}
}
else if ( idKnots == from )
{
switch ( to )
{
case idMPH: output = input * 1.151; break;
case idFPS: output = input * 1.688; break;
case idMPS: output = input / 1.944; break;
case idKPH: output = input * 1.852; break;
}
}
/*
* Temperature
*/
else if ( idCelsius == from )
{
switch ( to )
{
case idFahrenheit: output = (input * 9/5) + 32; break;
case idKelvin: output = input + 273.15; break;
}
}
else if ( idFahrenheit == from )
{
switch ( to )
{
case idCelsius: output = (input - 32) * 5/9; break;
case idKelvin: output = (input - 32) * 5/9 + 273.15; break;
}
}
else if ( idKelvin == from )
{
switch ( to )
{
case idCelsius: output = (input - 273.15); break;
case idFahrenheit: output = (input - 273.15) * 9/5 + 32; break;
}
}
/*
* MASS
*/
else if ( idMetricTon == from )
{
switch ( to )
{
case idMetricTon: break;
case idKilogram: output = input * 1000; break;
case idGram: output = input * 1000000; break;
case idMilligram: output = input * 1000000000; break;
case idMicrogram: output = input * 1000000000000; break;
case idImperialTon: output = input / 1.016; break;
case idUSTon: output = input * 1.102; break;
case idStone: output = input * 157.5; break;
case idPound: output = input * 2205; break;
case idOunce: output = input * 35270; break;
}
}
else if ( idKilogram == from )
{
switch ( to )
{
case idMetricTon: output = input / 1000; break;
case idKilogram: ; break;
case idGram: output = input * 1000; break;
case idMilligram: output = input * 1000000; break;
case idMicrogram: output = input * 1000000000; break;
case idImperialTon: output = input / 1016; break;
case idUSTon: output = input / 907.2; break;
case idStone: output = input / 6.35; break;
case idPound: output = input * 2.205; break;
case idOunce: output = input * 35.274; break;
}
}
else if ( idGram == from )
{
switch ( to )
{
case idMetricTon: output = input / 1000000; break;
case idKilogram: output = input / 1000; break;
case idGram: ; break;
case idMilligram: output = input * 1000; break;
case idMicrogram: output = input * 1000000; break;
case idImperialTon: output = input / 1016000; break;
case idUSTon: output = input / 907200; break;
case idStone: output = input / 6350; break;
case idPound: output = input / 453.6 ; break;
case idOunce: output = input / 28.35 ; break;
}
}
else if ( idMilligram == from )
{
switch ( to )
{
case idMetricTon: output = input / 1000000000; break;
case idKilogram: output = input / 1000000; break;
case idGram: output = input / 1000; break;
case idMilligram: ; break;
case idMicrogram: output = input * 1000; break;
case idImperialTon: output = input / 1016000000; break;
case idUSTon: output = input / 907200000; break;
case idStone: output = input / 6350000; break;
case idPound: output = input / 453600 ; break;
case idOunce: output = input / 28350 ; break;
}
}
else if ( idMicrogram == from )
{
switch ( to )
{
case idMetricTon: output = input / 1000000000000; break;
case idKilogram: output = input / 1000000000; break;
case idGram: output = input / 1000000; break;
case idMilligram: output = input * 1000; break;
case idMicrogram: ; break;
case idImperialTon: output = input / 1016000000000; break;
case idUSTon: output = input / 907200000000; break;
case idStone: output = input / 6350000000; break;
case idPound: output = input / 453600000 ; break;
case idOunce: output = input / 28350000 ; break;
}
}
else if ( idImperialTon == from )
{
switch ( to )
{
case idMetricTon: output = input * 1.016 ; break;
case idKilogram: output = input * 1016; break;
case idGram: output = input * 1016000; break;
case idMilligram: output = input * 1016000000; break;
case idMicrogram: output = input * 1016000000000 ; break;
case idImperialTon: ; break;
case idUSTon: output = input * 1.12; break;
case idStone: output = input * 160 ; break;
case idPound: output = input * 2240; break;
case idOunce: output = input * 35840; break;
}
}
else if ( idUSTon == from )
{
switch ( to )
{
case idMetricTon: output = input / 1.102; break;
case idKilogram: output = input * 907.2; break;
case idGram: output = input * 907200; break;
case idMilligram: output = input * 907200000; break;
case idMicrogram: output = input * 907200000000; break;
case idImperialTon: output = input / 1.12 ; break;
case idUSTon: ; break;
case idStone: output = input * 142.9; break;
case idPound: output = input * 2000; break;
case idOunce: output = input * 32000; break;
}
}
else if ( idStone == from )
{
switch ( to )
{
case idMetricTon: output = input / 157.5; break;
case idKilogram: output = input * 6.35; break;
case idGram: output = input * 6350; break;
case idMilligram: output = input * 6350000; break;
case idMicrogram: output = input * 6350000000; break;
case idImperialTon: output = input / 160 ; break;
case idUSTon: output = input / 142.9 ; break;
case idStone: ; break;
case idPound: output = input * 14 ; break;
case idOunce: output = input * 224 ; break;
}
}
else if ( idPound == from )
{
switch ( to )
{
case idMetricTon: output = input / 2205; break;
case idKilogram: output = input / 2.205 ; break;
case idGram: output = input * 453.6; break;
case idMilligram: output = input * 453600; break;
case idMicrogram: output = input * 453600000 ; break;
case idImperialTon: output = input / 2240 ; break;
case idUSTon: output = input / 2000 ; break;
case idStone: output = input / 14 ; break;
case idPound: ; break;
case idOunce: output = input * 16 ; break;
}
}
else if ( idOunce == from )
{
switch ( to )
{
case idMetricTon: output = input / 35270; break;
case idKilogram: output = input / 35.274 ; break;
case idGram: output = input * 28.35; break;
case idMilligram: output = input * 28350; break;
case idMicrogram: output = input * 28350000; break;
case idImperialTon: output = input / 35840 ; break;
case idUSTon: output = input / 32000 ; break;
case idStone: output = input / 224 ; break;
case idPound: output = input / 16 ; break;
case idOunce: ; break;
}
}
}