Hackster is hosting Hackster Holidays, Ep. 6: Livestream & Giveaway Drawing. Watch previous episodes or stream live on Monday!Stream Hackster Holidays, Ep. 6 on Monday!
Evan Rust
Published © GPL3+

Positive Habit Enforcement Device

Stick with your New Year's resolution this time by setting goals and getting a reward upon completing them.

IntermediateFull instructions provided5 hours2,108
Positive Habit Enforcement Device

Things used in this project

Hardware components

Photon
Particle Photon
×1
Driver DRV8825 for Stepper Motors for Theremino System
Driver DRV8825 for Stepper Motors for Theremino System
×1
Stepper Motor, Mini Step
Stepper Motor, Mini Step
×1
2828 OLED
×1
Grove - Micro Switch
Seeed Studio Grove - Micro Switch
×1
Pushbutton Switch, Momentary
Pushbutton Switch, Momentary
×1

Software apps and online services

Fusion
Autodesk Fusion
Particle Build Web IDE
Particle Build Web IDE
VS Code
Microsoft VS Code

Hand tools and fabrication machines

3D Printer (generic)
3D Printer (generic)

Story

Read more

Schematics

Components

Code

Remote HTML File

HTML
<!DOCTYPE html>
<html>
<head>
<title>Positive Reinforcement Device</title>

<meta name="viewport" content="width=device-width,initial-scale=1">
<meta charset="UTF-8">

<style type="text/css">
html,
body {
	font-family: sans-serif;
}

fieldset {
	margin-left: auto;
	margin-right: auto;
	max-width: 480px;
	border-radius: 8px;
}

p {
	clear: both;
	width: 100%;
	line-height: 1.5em;
}

label {
	width: 49%;
	text-align: right;
	display: inline-block;
	line-height: 1.5em;
	float: left;
	margin-left: -1em;
}
</style>

<!-- Download microajax.minified.js from https://code.google.com/archive/p/microajax/ -->
<script type="text/javascript" src="microajax.minified.js"></script>

<script type="text/javascript">
var $goal
var mA = microAjax;

window.onload = function() {
	$goal = document.querySelector('#goal');

	// Load our data
	get_settings();
}

// Get the settings from mydevice.local
function get_settings() {
	mA('http://mydevice.local/settings.json', function(res) {
		var d = JSON.parse(res);

		console.log('goal', d);

		document.querySelector('#loading').style.display = 'none';

		$goal.value = d.b;

		document.querySelector('#goal_form').style.display = 'block';
	});
}
</script>
</head>
<body>
<p id="loading">Please wait.  Loading settings . . .</p>
<form id="goal_form" method="POST" action="http://mydevice.local/save.html" style="display:none">
	<fieldset>
		<legend>Enter a Goal</legend>
		<p>
			<label for="b">Goal: </label>
			<input type="text" id="goal" name="goal">
		</p>
		<p>
			<label for="s"> </label>
			<input type="submit" id="s" value="Save">
		</p>
	</fieldset>
</form>
</body>
</html>

Device Code

C/C++
// This #include statement was automatically added by the Particle IDE.
#include <Adafruit_mfGFX.h>

// This #include statement was automatically added by the Particle IDE.
#include <WebServer.h>

// This #include statement was automatically added by the Particle IDE.
#include <MDNS.h>

// This #include statement was automatically added by the Particle IDE.
#include <Adafruit_SSD1351_Photon.h>

// This #include statement was automatically added by the Particle IDE.
#include <AccelStepper.h>

#define STEPSTOTAKE 200 //Go this many steps to the end to open

#define cs   A2
#define sclk A3
#define mosi A5
#define rst  D5
#define dc   D6

#define	BLACK           0x0000
#define WHITE           0xFFFF

#define homing_pin D4
#define goalCompleteBtn D7

AccelStepper stepper(1,3,2);
Adafruit_SSD1351 tft = Adafruit_SSD1351(cs, dc, rst);

MDNS mdns;
#define REMOTE_URL "http://localwebserver/device_page.html"

#define POST_NAME_LENGTH 32
#define POST_VALUE_LENGTH 120

WebServer webserver("", 80);

String goalString = "Enter a new goal";
bool goalActive = false;

void web_index(WebServer &server, WebServer::ConnectionType type, char *, bool) {
    server.httpSuccess();

    server.print("<!DOCTYPE html><html><head><title>Positive Reinforcement Machine</title><style type=\"text/css\">html,body,iframe{position:absolute;top:0;right:0;bottom:0;left:0;border:0;width:99%;height:99%;}</style></head><body><iframe src=\""+String(REMOTE_URL)+"/index.html\"></iframe></body></html>");
}

void web_settings(WebServer &server, WebServer::ConnectionType type, char *, bool) {
    server.httpSuccess("application/json");

    server.print("{\"goal\":"+goalString+"}");

}

void web_save(WebServer &server, WebServer::ConnectionType type, char *, bool) {
    URLPARAM_RESULT rc;
    char name[POST_NAME_LENGTH];
    char value[POST_VALUE_LENGTH];

    server.httpSeeOther(String(SETTINGS_URL)+"/index.html");

    // Loop through POSTed data
    while(server.readPOSTparam(name, POST_NAME_LENGTH, value, POST_VALUE_LENGTH)) {
        // Because strings are easier to test/manipulate
        String _name = String(name);
        String _value = String(value);

        if(_name.equals("goal") && !goalActive){
            goalString = _value;
            setNewGoal(goalString);
            goalActive = true;
        }
    }
}

void setup()
{
    stepper.setMaxSpeed(500);
    stepper.setSpeed(200);
    stepper.setAcceleration(150);
    tft.begin();
    pinMode(homing_pin, INPUT_PULLUP);
    pinMode(goalCompleteBtn, INPUT_PULLUP);
    //Home door position
    while(!digitalRead(homing_pin)) stepper.move(-1);
    bool mdns_success = mdns.setHostname("habitFormer");

    if(mdns_success) {
        mdns.addService("tcp", "http", 80, "Positive Reinforcement");
        mdns.begin();
    }
    webserver.setDefaultCommand(&web_index);
    webserver.addCommand("save.html", &web_save);
    webserver.addCommand("settings.json", &web_settings);
    webserver.begin();
}

void loop()
{
    mdns.processQueries();

    char web_buff[64];
    int web_len = 64;
    webserver.processConnection(web_buff, &web_len);
    if(!digitalRead(goalCompleteBtn))
    {
        goalActive = false;
        openDoor();
        goalString = "Enter a new goal";
    }
}

void displayGoal(String goal)
{
    tft.fillScreen(BLACK);
    tft.setCursor(0, 5);
    tft.setTextColor(WHITE);
    tft.setTextSize(2);
    tft.print(goal);
}

void setNewGoal(String newGoal)
{
    displayGoal(newGoal);
    stepper.move(STEPSTOTAKE);
}

void openDoor()
{
    if(goalActive) return;
    tft.fillScreen(BLACK);
    stepper.move(-STEPSTOTAKE);
}

Credits

Evan Rust

Evan Rust

122 projects • 1093 followers
IoT, web, and embedded systems enthusiast. Contact me for product reviews or custom project requests.

Comments