1
0
mirror of https://github.com/mgerb/mywebsite synced 2026-01-10 18:02:51 +00:00

Merge pull request #1 from mgerb/react

Complete overhaul of front end.

Still using go back end with minor if any changes.

Front end switched over to react with a much cleaner build process with webpack.
This commit is contained in:
2016-08-28 23:38:14 -05:00
committed by GitHub
88 changed files with 2726 additions and 1972 deletions

3
.babelrc Normal file
View File

@@ -0,0 +1,3 @@
{
"presets": ["es2015"]
}

6
.gitignore vendored
View File

@@ -1,4 +1,8 @@
\.c9
\.DS_Store
config.json
node_modules
dist
public
npm-debug.log
mywebsite
mywebsite.exe

View File

@@ -0,0 +1 @@
.hljs{display:block;overflow-x:auto;padding:0.5em;background:#282a36}.hljs-keyword,.hljs-selector-tag,.hljs-literal,.hljs-section,.hljs-link{color:#8be9fd}.hljs-function .hljs-keyword{color:#ff79c6}.hljs,.hljs-subst{color:#f8f8f2}.hljs-string,.hljs-title,.hljs-name,.hljs-type,.hljs-attribute,.hljs-symbol,.hljs-bullet,.hljs-addition,.hljs-variable,.hljs-template-tag,.hljs-template-variable{color:#f1fa8c}.hljs-comment,.hljs-quote,.hljs-deletion,.hljs-meta{color:#6272a4}.hljs-keyword,.hljs-selector-tag,.hljs-literal,.hljs-title,.hljs-section,.hljs-doctag,.hljs-type,.hljs-name,.hljs-strong{font-weight:bold}.hljs-emphasis{font-style:italic}

418
client/assets/css/normalize.css vendored Normal file
View File

@@ -0,0 +1,418 @@
/*! normalize.css v4.1.1 | MIT License | github.com/necolas/normalize.css */
/**
* 1. Change the default font family in all browsers (opinionated).
* 2. Prevent adjustments of font size after orientation changes in IE and iOS.
*/
html {
-ms-text-size-adjust: 100%; /* 2 */
-webkit-text-size-adjust: 100%; /* 2 */
}
/**
* Remove the margin in all browsers (opinionated).
*/
body {
margin: 0;
}
/* HTML5 display definitions
========================================================================== */
/**
* Add the correct display in IE 9-.
* 1. Add the correct display in Edge, IE, and Firefox.
* 2. Add the correct display in IE.
*/
article,
aside,
details, /* 1 */
figcaption,
figure,
footer,
header,
main, /* 2 */
menu,
nav,
section,
summary { /* 1 */
display: block;
}
/**
* Add the correct display in IE 9-.
*/
audio,
canvas,
progress,
video {
display: inline-block;
}
/**
* Add the correct display in iOS 4-7.
*/
audio:not([controls]) {
display: none;
height: 0;
}
/**
* Add the correct vertical alignment in Chrome, Firefox, and Opera.
*/
progress {
vertical-align: baseline;
}
/**
* Add the correct display in IE 10-.
* 1. Add the correct display in IE.
*/
template, /* 1 */
[hidden] {
display: none;
}
/* Links
========================================================================== */
/**
* 1. Remove the gray background on active links in IE 10.
* 2. Remove gaps in links underline in iOS 8+ and Safari 8+.
*/
a {
background-color: transparent; /* 1 */
-webkit-text-decoration-skip: objects; /* 2 */
}
/**
* Remove the outline on focused links when they are also active or hovered
* in all browsers (opinionated).
*/
a:active,
a:hover {
outline-width: 0;
}
/* Text-level semantics
========================================================================== */
/**
* 1. Remove the bottom border in Firefox 39-.
* 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
*/
abbr[title] {
border-bottom: none; /* 1 */
text-decoration: underline; /* 2 */
text-decoration: underline dotted; /* 2 */
}
/**
* Prevent the duplicate application of `bolder` by the next rule in Safari 6.
*/
b,
strong {
font-weight: inherit;
}
/**
* Add the correct font weight in Chrome, Edge, and Safari.
*/
b,
strong {
font-weight: bolder;
}
/**
* Add the correct font style in Android 4.3-.
*/
dfn {
font-style: italic;
}
/**
* Correct the font size and margin on `h1` elements within `section` and
* `article` contexts in Chrome, Firefox, and Safari.
*/
h1 {
font-size: 2em;
margin: 0.67em 0;
}
/**
* Add the correct background and color in IE 9-.
*/
mark {
background-color: #ff0;
color: #000;
}
/**
* Add the correct font size in all browsers.
*/
small {
font-size: 80%;
}
/**
* Prevent `sub` and `sup` elements from affecting the line height in
* all browsers.
*/
sub,
sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
/* Embedded content
========================================================================== */
/**
* Remove the border on images inside links in IE 10-.
*/
img {
border-style: none;
}
/**
* Hide the overflow in IE.
*/
svg:not(:root) {
overflow: hidden;
}
/* Grouping content
========================================================================== */
/**
* 1. Correct the inheritance and scaling of font size in all browsers.
* 2. Correct the odd `em` font sizing in all browsers.
*/
code,
kbd,
pre,
samp {
font-family: monospace, monospace; /* 1 */
font-size: 1em; /* 2 */
}
/**
* Add the correct margin in IE 8.
*/
figure {
margin: 1em 40px;
}
/**
* 1. Add the correct box sizing in Firefox.
* 2. Show the overflow in Edge and IE.
*/
hr {
box-sizing: content-box; /* 1 */
height: 0; /* 1 */
overflow: visible; /* 2 */
}
/* Forms
========================================================================== */
/**
* 1. Change font properties to `inherit` in all browsers (opinionated).
* 2. Remove the margin in Firefox and Safari.
*/
button,
input,
select,
textarea {
font: inherit; /* 1 */
margin: 0; /* 2 */
}
/**
* Restore the font weight unset by the previous rule.
*/
optgroup {
font-weight: bold;
}
/**
* Show the overflow in IE.
* 1. Show the overflow in Edge.
*/
button,
input { /* 1 */
overflow: visible;
}
/**
* Remove the inheritance of text transform in Edge, Firefox, and IE.
* 1. Remove the inheritance of text transform in Firefox.
*/
button,
select { /* 1 */
text-transform: none;
}
/**
* 1. Prevent a WebKit bug where (2) destroys native `audio` and `video`
* controls in Android 4.
* 2. Correct the inability to style clickable types in iOS and Safari.
*/
button,
html [type="button"], /* 1 */
[type="reset"],
[type="submit"] {
-webkit-appearance: button; /* 2 */
}
/**
* Remove the inner border and padding in Firefox.
*/
button::-moz-focus-inner,
[type="button"]::-moz-focus-inner,
[type="reset"]::-moz-focus-inner,
[type="submit"]::-moz-focus-inner {
border-style: none;
padding: 0;
}
/**
* Restore the focus styles unset by the previous rule.
*/
button:-moz-focusring,
[type="button"]:-moz-focusring,
[type="reset"]:-moz-focusring,
[type="submit"]:-moz-focusring {
outline: 1px dotted ButtonText;
}
/**
* Change the border, margin, and padding in all browsers (opinionated).
*/
fieldset {
border: 1px solid #c0c0c0;
margin: 0 2px;
padding: 0.35em 0.625em 0.75em;
}
/**
* 1. Correct the text wrapping in Edge and IE.
* 2. Correct the color inheritance from `fieldset` elements in IE.
* 3. Remove the padding so developers are not caught out when they zero out
* `fieldset` elements in all browsers.
*/
legend {
box-sizing: border-box; /* 1 */
color: inherit; /* 2 */
display: table; /* 1 */
max-width: 100%; /* 1 */
padding: 0; /* 3 */
white-space: normal; /* 1 */
}
/**
* Remove the default vertical scrollbar in IE.
*/
textarea {
overflow: auto;
}
/**
* 1. Add the correct box sizing in IE 10-.
* 2. Remove the padding in IE 10-.
*/
[type="checkbox"],
[type="radio"] {
box-sizing: border-box; /* 1 */
padding: 0; /* 2 */
}
/**
* Correct the cursor style of increment and decrement buttons in Chrome.
*/
[type="number"]::-webkit-inner-spin-button,
[type="number"]::-webkit-outer-spin-button {
height: auto;
}
/**
* 1. Correct the odd appearance in Chrome and Safari.
* 2. Correct the outline style in Safari.
*/
[type="search"] {
-webkit-appearance: textfield; /* 1 */
outline-offset: -2px; /* 2 */
}
/**
* Remove the inner padding and cancel buttons in Chrome and Safari on OS X.
*/
[type="search"]::-webkit-search-cancel-button,
[type="search"]::-webkit-search-decoration {
-webkit-appearance: none;
}
/**
* Correct the text style of placeholders in Chrome, Edge, and Safari.
*/
::-webkit-input-placeholder {
color: inherit;
opacity: 0.54;
}
/**
* 1. Correct the inability to style clickable types in iOS and Safari.
* 2. Change font properties to `inherit` in Safari.
*/
::-webkit-file-upload-button {
-webkit-appearance: button; /* 1 */
font: inherit; /* 2 */
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

View File

@@ -0,0 +1,17 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="32" height="32" fill="#3598db">
<path transform="translate(2)" d="M0 12 V20 H4 V12z">
<animate attributeName="d" values="M0 12 V20 H4 V12z; M0 4 V28 H4 V4z; M0 12 V20 H4 V12z; M0 12 V20 H4 V12z" dur="1.2s" repeatCount="indefinite" begin="0" keytimes="0;.2;.5;1" keySplines="0.2 0.2 0.4 0.8;0.2 0.6 0.4 0.8;0.2 0.8 0.4 0.8" calcMode="spline" />
</path>
<path transform="translate(8)" d="M0 12 V20 H4 V12z">
<animate attributeName="d" values="M0 12 V20 H4 V12z; M0 4 V28 H4 V4z; M0 12 V20 H4 V12z; M0 12 V20 H4 V12z" dur="1.2s" repeatCount="indefinite" begin="0.2" keytimes="0;.2;.5;1" keySplines="0.2 0.2 0.4 0.8;0.2 0.6 0.4 0.8;0.2 0.8 0.4 0.8" calcMode="spline" />
</path>
<path transform="translate(14)" d="M0 12 V20 H4 V12z">
<animate attributeName="d" values="M0 12 V20 H4 V12z; M0 4 V28 H4 V4z; M0 12 V20 H4 V12z; M0 12 V20 H4 V12z" dur="1.2s" repeatCount="indefinite" begin="0.4" keytimes="0;.2;.5;1" keySplines="0.2 0.2 0.4 0.8;0.2 0.6 0.4 0.8;0.2 0.8 0.4 0.8" calcMode="spline" />
</path>
<path transform="translate(20)" d="M0 12 V20 H4 V12z">
<animate attributeName="d" values="M0 12 V20 H4 V12z; M0 4 V28 H4 V4z; M0 12 V20 H4 V12z; M0 12 V20 H4 V12z" dur="1.2s" repeatCount="indefinite" begin="0.6" keytimes="0;.2;.5;1" keySplines="0.2 0.2 0.4 0.8;0.2 0.6 0.4 0.8;0.2 0.8 0.4 0.8" calcMode="spline" />
</path>
<path transform="translate(26)" d="M0 12 V20 H4 V12z">
<animate attributeName="d" values="M0 12 V20 H4 V12z; M0 4 V28 H4 V4z; M0 12 V20 H4 V12z; M0 12 V20 H4 V12z" dur="1.2s" repeatCount="indefinite" begin="0.8" keytimes="0;.2;.5;1" keySplines="0.2 0.2 0.4 0.8;0.2 0.6 0.4 0.8;0.2 0.8 0.4 0.8" calcMode="spline" />
</path>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

Before

Width:  |  Height:  |  Size: 109 KiB

After

Width:  |  Height:  |  Size: 109 KiB

View File

@@ -0,0 +1,92 @@
var width, height, largeHeader, canvas, ctx, circles, target, animateHeader = true;
// Main
export function bubble() {
initHeader();
addListeners();
}
function initHeader() {
width = window.innerWidth;
height = 500;
target = {
x: 0,
y: height
};
largeHeader = document.getElementById('header');
largeHeader.style.height = height + 'px';
canvas = document.getElementById('canvas');
canvas.width = width;
canvas.height = height;
ctx = canvas.getContext('2d');
// create particles
circles = [];
for (var x = 0; x < width * 0.5; x++) {
var c = new Circle();
circles.push(c);
}
animate();
}
// Event handling
function addListeners() {
window.addEventListener('scroll', scrollCheck);
window.addEventListener('resize', resize);
}
function scrollCheck() {
if (document.body.scrollTop > height) animateHeader = false;
else animateHeader = true;
}
function resize() {
width = window.innerWidth;
height = 500;
largeHeader.style.height = height + 'px';
canvas.width = width;
canvas.height = height;
}
function animate() {
if (animateHeader) {
ctx.clearRect(0, 0, width, height);
for (var i in circles) {
circles[i].draw();
}
}
requestAnimationFrame(animate);
}
// Canvas manipulation
function Circle() {
var _this = this;
// constructor
(function() {
_this.pos = {};
init();
})();
function init() {
_this.pos.x = Math.random() * width;
_this.pos.y = height + Math.random() * 100;
_this.alpha = 0.1 + Math.random() * 0.3;
_this.scale = 0.1 + Math.random() * 0.3;
_this.velocity = Math.random();
}
this.draw = function() {
if (_this.alpha <= 0) {
init();
}
_this.pos.y -= _this.velocity;
_this.alpha -= 0.0005;
ctx.beginPath();
ctx.arc(_this.pos.x, _this.pos.y, _this.scale * 10, 0, 2 * Math.PI, false);
ctx.fillStyle = 'rgba(255,255,255,' + _this.alpha + ')';
ctx.fill();
};
}

View File

@@ -0,0 +1,12 @@
/*!
* VERSION: beta 1.9.4
* DATE: 2014-07-17
* UPDATES AND DOCS AT: http://www.greensock.com
*
* @license Copyright (c) 2008-2014, GreenSock. All rights reserved.
* This work is subject to the terms at http://www.greensock.com/terms_of_use.html or for
* Club GreenSock members, the software agreement that was issued with your membership.
*
* @author: Jack Doyle, jack@greensock.com
**/
var _gsScope="undefined"!=typeof module&&module.exports&&"undefined"!=typeof global?global:this||window;(_gsScope._gsQueue||(_gsScope._gsQueue=[])).push(function(){"use strict";_gsScope._gsDefine("easing.Back",["easing.Ease"],function(t){var e,i,s,r=_gsScope.GreenSockGlobals||_gsScope,n=r.com.greensock,a=2*Math.PI,o=Math.PI/2,h=n._class,l=function(e,i){var s=h("easing."+e,function(){},!0),r=s.prototype=new t;return r.constructor=s,r.getRatio=i,s},_=t.register||function(){},u=function(t,e,i,s){var r=h("easing."+t,{easeOut:new e,easeIn:new i,easeInOut:new s},!0);return _(r,t),r},c=function(t,e,i){this.t=t,this.v=e,i&&(this.next=i,i.prev=this,this.c=i.v-e,this.gap=i.t-t)},p=function(e,i){var s=h("easing."+e,function(t){this._p1=t||0===t?t:1.70158,this._p2=1.525*this._p1},!0),r=s.prototype=new t;return r.constructor=s,r.getRatio=i,r.config=function(t){return new s(t)},s},f=u("Back",p("BackOut",function(t){return(t-=1)*t*((this._p1+1)*t+this._p1)+1}),p("BackIn",function(t){return t*t*((this._p1+1)*t-this._p1)}),p("BackInOut",function(t){return 1>(t*=2)?.5*t*t*((this._p2+1)*t-this._p2):.5*((t-=2)*t*((this._p2+1)*t+this._p2)+2)})),m=h("easing.SlowMo",function(t,e,i){e=e||0===e?e:.7,null==t?t=.7:t>1&&(t=1),this._p=1!==t?e:0,this._p1=(1-t)/2,this._p2=t,this._p3=this._p1+this._p2,this._calcEnd=i===!0},!0),d=m.prototype=new t;return d.constructor=m,d.getRatio=function(t){var e=t+(.5-t)*this._p;return this._p1>t?this._calcEnd?1-(t=1-t/this._p1)*t:e-(t=1-t/this._p1)*t*t*t*e:t>this._p3?this._calcEnd?1-(t=(t-this._p3)/this._p1)*t:e+(t-e)*(t=(t-this._p3)/this._p1)*t*t*t:this._calcEnd?1:e},m.ease=new m(.7,.7),d.config=m.config=function(t,e,i){return new m(t,e,i)},e=h("easing.SteppedEase",function(t){t=t||1,this._p1=1/t,this._p2=t+1},!0),d=e.prototype=new t,d.constructor=e,d.getRatio=function(t){return 0>t?t=0:t>=1&&(t=.999999999),(this._p2*t>>0)*this._p1},d.config=e.config=function(t){return new e(t)},i=h("easing.RoughEase",function(e){e=e||{};for(var i,s,r,n,a,o,h=e.taper||"none",l=[],_=0,u=0|(e.points||20),p=u,f=e.randomize!==!1,m=e.clamp===!0,d=e.template instanceof t?e.template:null,g="number"==typeof e.strength?.4*e.strength:.4;--p>-1;)i=f?Math.random():1/u*p,s=d?d.getRatio(i):i,"none"===h?r=g:"out"===h?(n=1-i,r=n*n*g):"in"===h?r=i*i*g:.5>i?(n=2*i,r=.5*n*n*g):(n=2*(1-i),r=.5*n*n*g),f?s+=Math.random()*r-.5*r:p%2?s+=.5*r:s-=.5*r,m&&(s>1?s=1:0>s&&(s=0)),l[_++]={x:i,y:s};for(l.sort(function(t,e){return t.x-e.x}),o=new c(1,1,null),p=u;--p>-1;)a=l[p],o=new c(a.x,a.y,o);this._prev=new c(0,0,0!==o.t?o:o.next)},!0),d=i.prototype=new t,d.constructor=i,d.getRatio=function(t){var e=this._prev;if(t>e.t){for(;e.next&&t>=e.t;)e=e.next;e=e.prev}else for(;e.prev&&e.t>=t;)e=e.prev;return this._prev=e,e.v+(t-e.t)/e.gap*e.c},d.config=function(t){return new i(t)},i.ease=new i,u("Bounce",l("BounceOut",function(t){return 1/2.75>t?7.5625*t*t:2/2.75>t?7.5625*(t-=1.5/2.75)*t+.75:2.5/2.75>t?7.5625*(t-=2.25/2.75)*t+.9375:7.5625*(t-=2.625/2.75)*t+.984375}),l("BounceIn",function(t){return 1/2.75>(t=1-t)?1-7.5625*t*t:2/2.75>t?1-(7.5625*(t-=1.5/2.75)*t+.75):2.5/2.75>t?1-(7.5625*(t-=2.25/2.75)*t+.9375):1-(7.5625*(t-=2.625/2.75)*t+.984375)}),l("BounceInOut",function(t){var e=.5>t;return t=e?1-2*t:2*t-1,t=1/2.75>t?7.5625*t*t:2/2.75>t?7.5625*(t-=1.5/2.75)*t+.75:2.5/2.75>t?7.5625*(t-=2.25/2.75)*t+.9375:7.5625*(t-=2.625/2.75)*t+.984375,e?.5*(1-t):.5*t+.5})),u("Circ",l("CircOut",function(t){return Math.sqrt(1-(t-=1)*t)}),l("CircIn",function(t){return-(Math.sqrt(1-t*t)-1)}),l("CircInOut",function(t){return 1>(t*=2)?-.5*(Math.sqrt(1-t*t)-1):.5*(Math.sqrt(1-(t-=2)*t)+1)})),s=function(e,i,s){var r=h("easing."+e,function(t,e){this._p1=t||1,this._p2=e||s,this._p3=this._p2/a*(Math.asin(1/this._p1)||0)},!0),n=r.prototype=new t;return n.constructor=r,n.getRatio=i,n.config=function(t,e){return new r(t,e)},r},u("Elastic",s("ElasticOut",function(t){return this._p1*Math.pow(2,-10*t)*Math.sin((t-this._p3)*a/this._p2)+1},.3),s("ElasticIn",function(t){return-(this._p1*Math.pow(2,10*(t-=1))*Math.sin((t-this._p3)*a/this._p2))},.3),s("ElasticInOut",function(t){return 1>(t*=2)?-.5*this._p1*Math.pow(2,10*(t-=1))*Math.sin((t-this._p3)*a/this._p2):.5*this._p1*Math.pow(2,-10*(t-=1))*Math.sin((t-this._p3)*a/this._p2)+1},.45)),u("Expo",l("ExpoOut",function(t){return 1-Math.pow(2,-10*t)}),l("ExpoIn",function(t){return Math.pow(2,10*(t-1))-.001}),l("ExpoInOut",function(t){return 1>(t*=2)?.5*Math.pow(2,10*(t-1)):.5*(2-Math.pow(2,-10*(t-1)))})),u("Sine",l("SineOut",function(t){return Math.sin(t*o)}),l("SineIn",function(t){return-Math.cos(t*o)+1}),l("SineInOut",function(t){return-.5*(Math.cos(Math.PI*t)-1)})),h("easing.EaseLookup",{find:function(e){return t.map[e]}},!0),_(r.SlowMo,"SlowMo","ease,"),_(i,"RoughEase","ease,"),_(e,"SteppedEase","ease,"),f},!0)}),_gsScope._gsDefine&&_gsScope._gsQueue.pop()();

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,186 @@
import './TweenLite.min';
import './EasePack.min';
var width, height, largeHeader, canvas, ctx, points, target, animateHeader = true;
// Main
export function constellation(){
initHeader();
initAnimation();
addListeners();
}
function initHeader() {
width = window.innerWidth;
height = '400';
target = {x: width/2, y: height/2};
largeHeader = document.getElementById('header');
largeHeader.style.height = height+'px';
canvas = document.getElementById('demo-canvas');
canvas.width = width;
canvas.height = height;
ctx = canvas.getContext('2d');
// create points
points = [];
for(var x = 0; x < width; x = x + width/20) {
for(var y = 0; y < height; y = y + height/20) {
var px = x + Math.random()*width/20;
var py = y + Math.random()*height/20;
var p = {x: px, originX: px, y: py, originY: py };
points.push(p);
}
}
// for each point find the 5 closest points
for(var i = 0; i < points.length; i++) {
var closest = [];
var p1 = points[i];
for(var j = 0; j < points.length; j++) {
var p2 = points[j]
if(!(p1 == p2)) {
var placed = false;
for(var k = 0; k < 5; k++) {
if(!placed) {
if(closest[k] == undefined) {
closest[k] = p2;
placed = true;
}
}
}
for(var k = 0; k < 5; k++) {
if(!placed) {
if(getDistance(p1, p2) < getDistance(p1, closest[k])) {
closest[k] = p2;
placed = true;
}
}
}
}
}
p1.closest = closest;
}
// assign a circle to each point
for(var i in points) {
var c = new Circle(points[i], 2+Math.random()*2, 'rgba(255,255,255,0.3)');
points[i].circle = c;
}
}
// Event handling
function addListeners() {
if(!('ontouchstart' in window)) {
window.addEventListener('mousemove', mouseMove);
}
window.addEventListener('scroll', scrollCheck);
window.addEventListener('resize', resize);
}
function mouseMove(e) {
var posx = 0, posy = 0;
if (e.pageX || e.pageY) {
posx = e.pageX;
posy = e.pageY;
}
else if (e.clientX || e.clientY) {
posx = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
posy = e.clientY + document.body.scrollTop + document.documentElement.scrollTop;
}
target.x = posx;
target.y = posy;
}
function scrollCheck() {
if(document.body.scrollTop > height) animateHeader = false;
else animateHeader = true;
}
function resize() {
width = window.innerWidth;
height = '400';
largeHeader.style.height = height+'px';
canvas.width = width;
canvas.height = height;
}
// animation
function initAnimation() {
animate();
for(var i in points) {
shiftPoint(points[i]);
}
}
function animate() {
if(animateHeader) {
ctx.clearRect(0,0,width,height);
for(var i in points) {
// detect points in range
if(Math.abs(getDistance(target, points[i])) < 4000) {
points[i].active = 0.3;
points[i].circle.active = 0.6;
} else if(Math.abs(getDistance(target, points[i])) < 20000) {
points[i].active = 0.1;
points[i].circle.active = 0.3;
} else if(Math.abs(getDistance(target, points[i])) < 40000) {
points[i].active = 0.02;
points[i].circle.active = 0.1;
} else {
points[i].active = 0;
points[i].circle.active = 0;
}
drawLines(points[i]);
points[i].circle.draw();
}
}
requestAnimationFrame(animate);
}
function shiftPoint(p) {
TweenLite.to(p, 1+1*Math.random(), {x:p.originX-50+Math.random()*100,
y: p.originY-50+Math.random()*100, ease:Circ.easeInOut,
onComplete: function() {
shiftPoint(p);
}});
}
// Canvas manipulation
function drawLines(p) {
if(!p.active) return;
for(var i in p.closest) {
ctx.beginPath();
ctx.moveTo(p.x, p.y);
ctx.lineTo(p.closest[i].x, p.closest[i].y);
ctx.strokeStyle = 'rgba(156,217,249,'+ p.active+')';
ctx.stroke();
}
}
function Circle(pos,rad,color) {
var _this = this;
// constructor
(function() {
_this.pos = pos || null;
_this.radius = rad || null;
_this.color = color || null;
})();
this.draw = function() {
if(!_this.active) return;
ctx.beginPath();
ctx.arc(_this.pos.x, _this.pos.y, _this.radius, 0, 2 * Math.PI, false);
ctx.fillStyle = 'rgba(156,217,249,'+ _this.active+')';
ctx.fill();
};
}
// Util
function getDistance(p1, p2) {
return Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2);
}

View File

@@ -0,0 +1,31 @@
.Content {
flex: 1;
flex-wrap: wrap;
.post + .post {
margin-top: 2em;
}
.date {
display: block;
margin: 0;
opacity: 0.7;
@media (min-width: 1200px){
position: absolute;
width: 7em;
text-align: right;
margin-left: -8em;
}
}
.intro {
margin-bottom: 0.5em;
}
img {
max-width: 100%;
display: block;
margin: 0 auto;
}
code,
pre {
white-space: pre-wrap;
}
}

View File

@@ -0,0 +1,10 @@
.Footer {
height: 5em;
align-items: center;
background-color: #F1F1F1;
border-top: solid;
border-width: 1px;
border-color: #DADADA;
font-weight: 300;
margin-top: 1em;
}

View File

@@ -0,0 +1,64 @@
$transitionDuration: 0.4s;
.Sidebar {
background: #fff;
width: 300px;
padding-right: 1em;
padding-left: 1em;
img {
width: 100%;
height: auto;
border-radius: 0.5em;
}
h2 {
text-align: center;
margin-bottom: 0.5em;
}
a.toggler {
display: none;
}
@media (max-width: 768px) {
position: absolute;
top: 0;
right: 0;
min-height: 100%;
padding-top: 1em;
transform: translateX(300px);
-webkit-transform: translateX(300px);
transition: transform $transitionDuration ease-out;
-webkit-transition: transform $transitionDuration ease-out;
&.open {
transform: translateX(0px);
-webkit-transform: translateX(0px);
border-left: 1px solid #EAEAEA;
&:before {
content: " ";
display: block;
background: rgba(0,0,0,0.4);
position: absolute;
height: 100%;
width: 100vw;
top: 0;
right: 100%;
}
a.toggler {
transform: rotate(450deg);
}
}
a.toggler {
display: block;
width: 2em;
height: 2em;
position: absolute;
left: -3em;
top: 0.5em;
cursor: pointer;
color: white;
transform: rotate(0deg);
transition: transform $transitionDuration ease-out;
-webkit-transition: transform $transitionDuration ease-out;
&:hover {
color: #D1D1D1;
}
}
}
}

View File

@@ -0,0 +1,81 @@
@import './utils.scss';
$linkColor: #3598db;
html {
font-family: 'Roboto Slab', serif;
max-width: 100%;
overflow-x: hidden;
overflow-y: scroll;
}
body {
max-width: 100%;
overflow-x: hidden;
position: relative;
-webkit-font-smoothing: subpixel-antialiased;
font-weight: 300;
}
h1,
h2,
h3,
h4,
h5,
h6 {
margin: 0;
font-weight: 400;
line-height: 1em;
}
p {
font-weight: 300;
}
*,
*:after,
*:before {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
a {
background-color: transparent;
color: $linkColor;
text-decoration: none;
&:hover {
color: $linkColor;
}
}
hr {
margin: 1em;
border: 0;
border-top: 1px solid #eee;
}
.Footer,
.Main {
display: flex;
padding-right: calc(50% - 997px / 2);
padding-left: calc(50% - 997px / 2);
&:after,
&:before {
content: " ";
width: 1em;
}
}
.Main {
padding-top: 1em;
}
.Header {
width: 100%;
background: url("../images/header.jpg");
background-size: cover;
height: 30em;
border-bottom: solid;
border-width: 1px;
border-color: #DADADA;
h1 {
text-align: center;
}
}
.Loading{
display: flex;
flex: 1;
justify-content: center;
}

View File

@@ -0,0 +1,23 @@
/* Effect 1: Brackets */
.link {
position: relative;
&:before {
content: "";
position: absolute;
width: 100%;
height: 1px;
bottom: 0;
left: 0;
background-color: #3598db;
visibility: hidden;
-webkit-transform: scaleX(0);
transform: scaleX(0);
-webkit-transition: all 0.2s ease-in-out 0s;
transition: all 0.2s ease-in-out 0s;
}
&:hover:before {
visibility: visible;
-webkit-transform: scaleX(1);
transform: scaleX(1);
}
}

47
client/js/app.js Normal file
View File

@@ -0,0 +1,47 @@
//react imports
import React from 'react';
import ReactDOM from 'react-dom';
import {Router, Route, IndexRoute} from 'react-router';
//redux imports
import {bindActionCreators} from 'redux';
import {connect, Provider} from 'react-redux';
import store, {history} from './redux/store';
//import actions
import * as actions from './redux/actions';
import Index from './pages/Index';
class Main extends React.Component{
render(){
return(
<div>{React.cloneElement(this.props.children, this.props)}</div>
);
}
}
function mapStateToProps(state){
return {
redux: state.reducer
}
}
function mapDispatchToProps(dispatch){
return{
actions: bindActionCreators(actions, dispatch)
}
}
const App = connect(mapStateToProps, mapDispatchToProps)(Main);
ReactDOM.render((
<Provider store={store}>
<Router history={history}>
<Route path="/" component={App}>
<IndexRoute component={Index}/>
<Route path="/:page(/:category)(/:post)" component={Index}/>
</Route>
</Router>
</Provider>
),document.getElementById('app'));

View File

@@ -0,0 +1,14 @@
import React from 'react';
import '../../assets/scss/Footer.scss';
export default class Footer extends React.Component{
render(){
return(
<div class="Footer">
Site created and maintained by Mitchell Gerber
</div>
);
}
}

View File

@@ -0,0 +1,16 @@
import React from 'react';
import {bubble} from '../../assets/js/bubble';
export default class Header extends React.Component{
componentDidMount(){
bubble();
}
render(){
return(
<header id="header" class="Header">
<canvas id="canvas" width="854" height="709"></canvas>
</header>
)
}
}

View File

@@ -0,0 +1,28 @@
import React from 'react';
import {Link} from 'react-router';
import marked from 'marked';
import hljs from 'highlight.js';
import '../../assets/scss/Content.scss';
const renderer = new marked.Renderer();
marked.setOptions({
langPrefix: 'hljs ',
highlight: (code) => {
return hljs.highlightAuto(code).value;
}
});
export default class Post extends React.Component{
render(){
return(
<div class="Content">
<div dangerouslySetInnerHTML={{__html : marked(this.props.content, {renderer : renderer})}}>
</div>
<Link to="/" class="link"><i class="fa fa-caret-left" aria-hidden="true"></i> Home</Link>
</div>
);
}
}

View File

@@ -0,0 +1,39 @@
import React from 'react';
import {Link} from 'react-router';
import '../../assets/scss/Content.scss';
export default class Preview extends React.Component{
insertPosts(posts){
let elements = [];
for (let i in posts){
elements.push(
<div class="post" key={i}>
<div class="date">
{posts[i].date}
</div>
<h1 class="intro" >{posts[i].title.toString()}</h1>
<p>{posts[i].intro.toString()}</p>
<p>
<Link class="link" to={`/post/${posts[i].category}/${posts[i].filename}`}>
continue reading <i class="fa fa-caret-right" aria-hidden="true"></i>
</Link>
</p>
</div>
);
}
return elements;
}
render(){
const posts = this.props.posts;
return (
<div class="Content">
{posts.length > 0 ? this.insertPosts(posts): ""}
</div>
);
}
}

View File

@@ -0,0 +1,70 @@
import React from 'react';
//components
import SensorList from './sensors/SensorList';
//assets
import me from '../../assets/images/me.jpg';
import '../../assets/scss/Sidebar.scss';
export default class Sidebar extends React.Component{
constructor(){
super();
this.state = {
toggler: ""
};
this.onToggle = this.onToggle.bind(this);
}
onToggle(){
let temp = this.state.toggler;
temp = temp === "open" ? "" : "open";
this.setState({
toggler: temp
});
}
render(){
return(
<div class={"Sidebar " + this.state.toggler}>
<a onClick={this.onToggle} class="toggler">
<i
class="fa fa-2x fa-navicon"
aria-hidden="true" />
</a>
<h2>About Me</h2>
<img src={me}/>
<p>
My name is Mitchell and I have a passion for software development. I am currently a software engineer and enjoy working on personal projects in my free time.
</p>
<p>
<i class="fa fa-envelope" aria-hidden="true"></i>
<a class="link" href="mailto:mgerb42@gmail.com"> eMail</a>
</p>
<p>
<i class="fa fa-linkedin-square" aria-hidden="true"></i>
<a class="link" href="https://www.linkedin.com/in/mitchell-gerber-125391b3" target="_blank"> LinkedIn</a>
</p>
<p>
<i class="fa fa-github" aria-hidden="true"></i>
<a class="link" href="https://github.com/mgerb" target="_blank"> GitHub</a>
</p>
<p>
<i class="fa fa-wpforms" aria-hidden="true"> </i>
<a href="/resume" class="link"> Resume</a>
</p>
<br/>
<h2>Sensors</h2>
<hr/>
<SensorList/>
</div>
);
}
}

View File

@@ -0,0 +1,60 @@
import React from 'react';
import 'whatwg-fetch';
export default class SensorList extends React.Component {
constructor(){
super();
this.state = {
sensors : {},
fetching: false,
fetched: false
}
}
componentDidMount(){
this.loadSensorData();
}
loadSensorData(){
this.setState({
fetching: true
});
fetch('/api/allsensors')
.then((response) => {
return response.json()
})
.then((json) => {
this.setState({
sensors: json,
fetching: false,
fetched: true
});
})
.catch((e) => {
console.log('Loading sensors failed', e)
});
}
insertSensorData = (sensor, index) => {
const date = new Date(sensor.updated);
return (
<div key={index}>
<h3>{sensor.location}</h3>
<p>{sensor.temperature}</p>
<p>{date.toString()}</p>
</div>
);
}
render(){
console.log(this.state);
return (
<div>
{this.state.fetched ? this.state.sensors.map(this.insertSensorData) : null}
</div>
)
}
}

59
client/js/pages/Index.js Normal file
View File

@@ -0,0 +1,59 @@
import React from 'react';
//components
import Header from '../components/Header';
import Preview from '../components/Preview';
import Footer from '../components/Footer';
import Sidebar from '../components/Sidebar';
import Post from '../components/Post';
//css
import '../../assets/css/normalize.css';
import '../../assets/scss/main.scss';
import 'font-awesome/css/font-awesome.min.css';
import '../../assets/css/dracula.css';
//loading icon
import loading from '../../assets/images/loading.svg';
export default class Index extends React.Component {
componentDidMount() {
this.props.actions.fetchPreview();
this.page = this.props.params.page;
this.page === 'post' ? this.props.actions.fetchPost(this.props.params.category, this.props.params.post) : "";
}
componentWillReceiveProps(nextProps){
if(this.props.params !== nextProps.params){
const params = nextProps.params;
this.page = params.page;
if(typeof params.post !== 'undefined' && typeof params.category !== 'undefined'){
this.props.actions.fetchPost(params.category, params.post);
}
}
}
render() {
const fetched = this.props.redux.fetched;
const fetching = this.props.redux.fetching;
return (
<div>
<Header />
<div class="Main">
{typeof this.page === 'undefined' && !fetching ? <Preview posts={this.props.redux.preview.posts} /> : null}
{this.page === 'post' && !fetching ? <Post content={this.props.redux.post}/> : null}
{fetching ? loadingElement : null}
<Sidebar />
</div>
<Footer />
</div>
);
}
}
const loadingElement = <div class="Loading">
<img src={loading} alt="loading..."/>
</div>;

View File

@@ -0,0 +1,53 @@
import * as types from "./constants";
import marked from 'marked';
import 'whatwg-fetch';
function initPreview(posts) {
return {
type: types.INIT_PREVIEW,
posts
}
}
function loadPost(post){
return {
type: types.LOAD_POST,
post
}
}
function fetching(){
return{
type: types.FETCHING
}
}
//using redux-thunk we can modify actions before they get called
//in this case we can send the http request here rather in the react component
export function fetchPreview() {
return (dispatch) => {
dispatch(fetching());
return fetch('/public/metadata.json')
.then(response => response.json())
.then(json => {
dispatch(initPreview(json));
})
.catch(error => {
console.log(error);
});
}
}
export function fetchPost(category, post) {
return (dispatch) => {
dispatch(fetching());
return fetch(`/public/posts/${category}/${post}.md`)
.then(response => response.text())
.then(response => {
dispatch(loadPost(response));
})
.catch(error => {
console.log(error);
});
}
}

View File

@@ -0,0 +1,5 @@
//constants
export const INIT_PREVIEW = 'INIT_PREVIEW';
export const FILTER_PREVIEW = 'FILTER_PREVIEW';
export const LOAD_POST = 'LOAD_POST';
export const FETCHING = 'FETCHING';

View File

@@ -0,0 +1,61 @@
//just using one reducer - use combineReducers from redux to modularize things
import {
combineReducers
} from 'redux';
import {
routerReducer
} from 'react-router-redux';
//import typs
import * as types from './constants';
//defaults -
const defaultState = {
preview: {
posts: []
},
filteredPreview: {
posts: []
},
post: "",
fetched: false,
fetching: false
};
//default reducer
function reducer(state = defaultState, action) {
//every reducer gets called when an action is called - we check for the type to modify our state accordingly
switch (action.type) {
case types.INIT_PREVIEW:
return Object.assign({}, state, {
preview: Object.assign({}, state.preview, action.posts),
fetched: true,
fetching: false
});
case types.FILTER_PREVIEW:
return Object.assign({}, state, {
filteredPreview: Object.assign({}, state.filteredPreview, action.posts)
});
case types.LOAD_POST:
return Object.assign({}, state, {
post: action.post,
fetched: true,
fetching: false
});
case types.FETCHING:
return Object.assign({}, state, {
fetched : false,
fetching: true
});
}
//return present state if no actions get called
return state;
}
const allReducers = combineReducers({
reducer,
routing: routerReducer
});
export default allReducers;

16
client/js/redux/store.js Normal file
View File

@@ -0,0 +1,16 @@
import {applyMiddleware, createStore} from 'redux';
import {syncHistoryWithStore} from 'react-router-redux';
import {browserHistory} from 'react-router';
import thunk from 'redux-thunk';
import logger from 'redux-logger';
import reducers from './reducers';
const middleware = applyMiddleware(thunk, logger());
//create the new store with default state as an empty object
const store = createStore(reducers, {}, middleware);
export const history = syncHistoryWithStore(browserHistory, store);
export default store;

View File

@@ -1,12 +0,0 @@
{
"database": {
"url": "",
"database": ""
},
"api": {
"key": ""
},
"server": {
"port": ""
}
}

12
config.template.json Normal file
View File

@@ -0,0 +1,12 @@
{
"database": {
"url": "",
"database": "",
"username": "",
"password": ""
},
"api": {
"key": ""
},
"port": 8080
}

View File

@@ -1,54 +0,0 @@
package db
import (
"gopkg.in/mgo.v2"
"log"
"time"
)
var Mongo Driver
type Driver struct {
Session *mgo.Session
Info DatabaseInfo
}
type DatabaseInfo struct {
URL string `json:"url"`
Database string `json:"database"`
Username string `json:"username,omitempty"`
Password string `json:"password,omitempty"`
}
func Configure(d DatabaseInfo) {
Mongo.Info = d
}
func (d *Driver) Connect() {
// Connect to MongoDB
s, err := mgo.DialWithTimeout(d.Info.URL, 5*time.Second)
if err != nil {
log.Println("MongoDB Driver Error", err)
return
}
d.Session = s
// Prevents these errors: read tcp 127.0.0.1:27017: i/o timeout
d.Session.SetSocketTimeout(10 * time.Second)
// Check if is alive
if err = d.Session.Ping(); err != nil {
log.Println("Database Error", err)
}
log.Println("Connected to database")
}
func (d *Driver) Connected() bool {
if d.Session != nil {
return true
}
return false
}

31
index.html Normal file
View File

@@ -0,0 +1,31 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="author" content="Mitchell Gerber">
<link href="https://fonts.googleapis.com/css?family=Roboto+Slab:300,400" rel="stylesheet">
<title>mitchel.io</title>
</head>
<body>
<script>
(function(i, s, o, g, r, a, m) {
i['GoogleAnalyticsObject'] = r;
i[r] = i[r] || function() {
(i[r].q = i[r].q || []).push(arguments)
}, i[r].l = 1 * new Date();
a = s.createElement(o), m = s.getElementsByTagName(o)[0];
a.async = 1;
a.src = g;
m.parentNode.insertBefore(a, m)
})
(window, document, 'script', 'https://www.google-analytics.com/analytics.js', 'ga');
</script>
<!--it's happening!!!-->
<div id="app"></div>
</body>
</html>

66
main.go
View File

@@ -1,66 +0,0 @@
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
"github.com/mgerb42/mywebsite/controller/api"
"github.com/mgerb42/mywebsite/db"
"github.com/mgerb42/mywebsite/route"
)
//structure for application configurations
type Config struct {
Database db.DatabaseInfo `json:"database"`
Api api.ApiInfo `json:"api"`
Server ServerConfig `json:"server"`
}
//configuration settings for the web
type ServerConfig struct {
Port string `json:"port"`
}
func main() {
configurations := readConfig()
db.Configure(configurations.Database)
api.Configure(configurations.Api)
db.Mongo.Connect()
log.Println("Starting Server...")
log.Println(http.ListenAndServe(":"+configurations.Server.Port, route.Routes()))
}
//read the config file and return JsonObject struct
func readConfig() Config {
log.Println("Reading config file...")
file, e := ioutil.ReadFile("./config.json")
if e != nil {
fmt.Printf("File error: %v\n", e)
os.Exit(1)
}
fmt.Printf("%s\n", string(file))
//m := new(Dispatch)
//var m interface{}
var result Config
err := json.Unmarshal(file, &result)
if err != nil {
fmt.Println(err)
}
return result
}

71
metadata.js Normal file
View File

@@ -0,0 +1,71 @@
/*
This script runs through each markdown post and scrapes out the title and intro.
folder/files within posts are scanned recursively
each post is contained within category, which is supplied by the direct parent folder
Posts are sorted by date
Stores all metadata in ./public/metadata.json
Client uses metadata to display posts on preview page
*/
import fs from 'fs';
import ncp from 'ncp';
import marked from 'marked';
import highlight from 'highlight.js';
marked.setOptions({
header: true,
highlight: (code) => {
return highlight.highlightAuto(code).value;
}
});
const dir = './posts/';
const json = {
posts: []
};
//do everything synchronously to keep posts ordered
function parse_dir(dir, folder_name){
const posts = fs.readdirSync(dir);
for(let post of posts){
const stats = fs.statSync(dir + post);
if(stats.isDirectory()){
parse_dir(dir + post + '/', post);
} else {
const file = fs.readFileSync(dir+post, 'utf8');
const tokens = marked.lexer(file, null);
const temp = {
filename: post.slice(0, post.length - 3),
category: folder_name,
date: post.slice(0, 10),
title: tokens[0].text,
intro: tokens[1].text
}
json.posts.push(temp);
}
}
}
//recursively parse posts directory for all markdown files
parse_dir(dir, 'posts');
//sort posts by date
json.posts.sort((a, b) => {
return new Date(b.date) - new Date(a.date);
});
//output to public path
fs.writeFile('./public/metadata.json', JSON.stringify(json,null,4), (err) => {
if (err) throw err;
console.log("Saved metadata.json");
})
//copy posts folder to public
ncp('./posts', './public/posts', (err) => {
if (err) {
return console.error(err);
}
console.log('copied');
});

60
package.json Normal file
View File

@@ -0,0 +1,60 @@
{
"name": "mywebsite",
"version": "1.0.0",
"description": "My personal website built with React and Go",
"main": "index.js",
"scripts": {
"build": "webpack && babel-node metadata.js",
"c9": "webpack-dev-server --port $PORT --host $IP --hot --content-base dist --history-api-fallback",
"dev": "webpack-dev-server --content-base public --inline --hot --history-api-fallback",
"prod": "export NODE_ENV=production && webpack -p && babel-node metadata.js",
"prod-win": "set NODE_ENV=production && webpack -p && babel-node metadata.js"
},
"repository": {
"type": "git",
"url": "git+https://github.com/mgerb/mywebsite.git"
},
"author": "Mitchell Gerber",
"license": "ISC",
"bugs": {
"url": "https://github.com/mgerb/mywebsite/issues"
},
"homepage": "https://github.com/mgerb/mywebsite#readme",
"dependencies": {
"babel": "^6.5.2",
"babel-cli": "^6.11.4",
"babel-core": "^6.13.2",
"babel-loader": "^6.2.4",
"babel-plugin-react-html-attrs": "^2.0.0",
"babel-plugin-transform-class-properties": "^6.11.5",
"babel-plugin-transform-decorators-legacy": "^1.3.4",
"babel-polyfill": "^6.13.0",
"babel-preset-es2015": "^6.13.2",
"babel-preset-react": "^6.11.1",
"babel-preset-stage-0": "^6.5.0",
"css-loader": "^0.23.1",
"exports-loader": "^0.6.3",
"file-loader": "^0.9.0",
"font-awesome": "^4.6.3",
"highlight.js": "^9.6.0",
"html-webpack-plugin": "^2.22.0",
"marked": "^0.3.6",
"ncp": "^2.0.0",
"node-sass": "^3.8.0",
"react": "^15.3.0",
"react-dom": "^15.3.0",
"react-redux": "^4.4.5",
"react-router": "^2.6.1",
"react-router-redux": "^4.0.5",
"redux": "^3.5.2",
"redux-logger": "^2.6.1",
"redux-thunk": "^2.1.0",
"sass-loader": "^4.0.0",
"style-loader": "^0.13.1",
"url-loader": "^0.5.7",
"webpack": "^1.13.1",
"webpack-dev-server": "^1.14.1",
"whatwg-fetch": "^1.0.0",
"wowjs": "^1.1.3"
}
}

View File

@@ -0,0 +1,45 @@
# First Blog Post
My first blog post introduces this website and the motives behind it. I discuss setting up a Node.js webserver for the first time and my experiences with it thus far.
I have had this site up for awhile now, but I've decided to turn it into a blog style website, more or less. I first started working on the site because I wanted to learn new web development tools, in this case, Node.js and MongoDB.
I already had previous experience with SQL databases and other server side scripting languages such as PHP and JSP, but I heard good things about Node.js and I wanted to familiarize myself with a NoSQL database. At first, Node.js was a bit confusing to work with and figure everything out. Now that I am starting to get the hang of Node and MongoDB I am enjoying them more and more.
---
## Setting up Node.js for the first time
There are multiple ways to get up and running with Node. Node uses "modules" and has some that are already built in. Others need to be installed with the NPM package manager, which will become your friend. I am going focus on installing Node.js with Express, which is a web framework for Node.js.
```bash
$ sudo apt-get install nodejs
$ sudo apt-get install npm
```
Once you have both Node.js and NPM installed, install Express and MongoDB. We are going to install and use express generator to easily create a skeleton that we can work from.
```bash
$ npm install express-generator -g
```
Based on your system, installing MongoDB may be a bit different. [Here is a guide on how to install.](http://docs.mongodb.org/manual/) Once express generator is installed, it is extremely simple to set up a new Node application.
```bash
$ express myapp
```
This will generate a new folder myapp with prebuild folders and files.
```bash
$ cd myapp
$ npm install
$ npm start
```
Now as easy as that sounds, you have a Node web application running on your machine. You can navigate to your web app at localhost:3000 (It's configured to use port 3000 by default)
Express organizes everything nicely in the [Model View Controller](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller) software pattern. Folders are split up into views, routes, and public files. If you are implementing a database, you would also create a "models" folder. There is also a folder which contains Node modules. When you use NPM to install new modules this is where they will be located.
Node is a very powerful tool that I plan on exploring more in the future. As development of this site continues, I plan to submit more content similar to this.

View File

@@ -0,0 +1,123 @@
# How do I use git?
Git is a very useful file management system that you will find very handy. There are many services that host git repositories and it can also be installed on a personal server if you so choose. I'm going to explain how to set up git and discuss some useful commands and what they do.
---
Throughout this tutorial I am going to be focusing on [Github](http://www.github.com), because I find it easy to use and it is what I use most of the time. Github is just one of the many web-based git repository hosting services. It also has a nice GUI interface that may be useful to beginners, but I will mainly be focusing on git via command line. I have used both in the past and although command line may seem intimidating at first, it becomes easier and more powerful over time.
What exactly does git do? Git is basically a cloud service that you can use for your coding projects. You may be wondering why use git over other cloud hosting services such as dropbox? Git is essentially the same thing, but has many built-in utilities for coding projects specifically. For example, you can see the changes between each commit (or each time you save new changes) displayed in each file. Github has a nice web interface that displays of this information. This helps with version control and it allows you to see which users have made what changes.
## How to set up git
```bash
$ Sudo apt-get install git
```
In linux just use the command above to install git. You will then run all git commands within the terminal. If you are on a Windows machine you can install git [here](http://git-scm.com/download/win). Once git is installed you can start using it immediately.
```bash
$ cd Desktop && mkdir testgit && cd testgit && git init
```
Executing the above command will create a folder on the desktop and initialize a git repository. It also creates a .git folder that contains the settings of the repository. If you have already created a project and want to set it up as a git repository just simply navigate to the root directory and use `$ git init`.
```bash
$ echo "testgit" >> README.md
```
You must create a readme file within the directory to be able to push to github. You can put anything in the readme, but github uses this file to display information about your repository on the main page.
You must also first register with Github before you can create your own repositories. When pushing to your repository, you will be asked for your login credentials. You can globally configure these if you do not wish to be asked every time.
```bash
$ git config --global user.name "John Doe"
$ git config --global user.email johndoe@example.com
```
Now that you have initialized your git repository, you can start on your project and begin adding files. When you wish to save your files to the repository, you must first add all of the changes. YOU MUST ADD CHANGES BEFORE YOU COMMIT!! I have commited and pushed without adding changes multiple times before so do not forget!
```bash
$ git add -A
```
This will add all changes of all files in the directory. Once you have added all of the changes you can now commit.
```bash
$ git commit -m "First Commit"
```
Committing your changes does NOT push them to Github. It just saves all of the new changes and sets them up to be pushed. The above command will make a commit with the message "First Commit". You must have a message with each commit! The -m parameter allows you to put a message in quotes without opening it up with an editor.
Now everything is ready to be pushed to Github. We first need to create a repository on Github that we can push to. You can simply do this by clicking the "New Repository" button on Github's website. Once the repository is created, we need to attach our newly created repository to it. We can do this by using the `remote` command.
```bash
$ git remote add origin https://github.com/username/repositoryname.git
```
Replace "username" and "repositoryname" with your username and the name of your newly created repository and now your local repository should be attached to Github. Now we can finally push all changes to Github.
```bash
$ git push origin master
```
You should be prompted to enter your username and password, unless you configured that globally before. Now, each time you want to save your changes to github, execute the follwoing commands.
```bash
$ git add -A
$ git commit -m "Commit Message"
$ git push origin master
```
If you ever lose files, or want to work on this same project on another machine you can simply clone the entire repository and continue working on it from the last time you pushed. Just navite to your repository on Github's website and copy the URL.
```bash
$ git clone https://github.com/username/repositoryname.git
```
This will copy the entire repository into its own folder. If you implement changes on one machine and you want to update the repository on another, you can pull all of the most recent changes.
```bash
$ git pull
```
Note: You must do this in the directory of the repository you wish to pull the changes.
This is just a beginner guide on how to use git for the first time. There are still many other things that git has to offer, but these are the basic commands to get you started and using git.
## Edit 10/27/15: More information on Git
I would like to discuss a few more things about git that I have learned and become familiar with. I am hosting this website on an Ubuntu VPS from Digital Ocean and I rely heavily on git to update and make changes. I've recently added a new "working" branch to my repository. This makes it easier to work on changes while not breaking the master branch. Another branch can be made on github and you can base it off another branch. I started a "working" branch and copied the master branch.
```bash
$ git checkout working
```
Now, after cloning my repository I can switch to my new working branch with the checkout command. Keep in mind that if I was working on my master branch with uncommited changes, I cannot checkout to another branch without commiting current changes to the master branch. After making new changes to my new "working" branch I can add, commit, and push them to Github. If and when I want to update my master branch I can checkout to my master branch, merge changes with "working" and push changes to Github. The master branch should then be up to date with the "working" branch.
```bash
$ git add -A
$ git commit -m "commit message"
$ git push origin working
$ git checkout master
$ git merge working
$ git push origin master
```
At any time you can check to see which branch you are working on and if any files have been updated/are ready to commit by using the status command:
```bash
$ git status
```
Now that my changes with "working" are updated on "master" I can pull the changes to my server on Digital Ocean. I sometimes have to log into my server and make minor updates to the code. This can cause merge conflicts with git and I may be unable to pull the master branch. To resolve this issue I must reset the master branch so that I can pull new changes.
```bash
$ git reset --hard
$ git pull
```
This will reset the branch to the last commit and get rid of any uncommited changes. Only do this if you have not made changes that you want to save. Because it is running on my server, I want to overwrite any changes anyway.
These are a few more git commands that I have learned since working on this project. As I become more familiar with git I will continue to update this post

View File

@@ -0,0 +1,47 @@
# ESP8266 - Cheap Wifi Microcontroller
I've recently started playing around with microcontrollers, or more specifically arduinos. I have always had some projects in mind that require the use of some sort of microcontroller, most of which requred a wifi connection. There are a few wifi options availabe for the arduino, some of which can be very expensive. I eventually stumbled upon the ESP8266, which I have finally figured out... mostly.
## Why the ESP8266?
It is extremely cheap! They can be found for roughly $3 apiece whereas the wifi arduino shield is around $70! I figured I would take my chances with the cheaper option and I ordered [three of these](http://www.banggood.com/ESP8266-Remote-Serial-Port-WIFI-Transceiver-Wireless-Module-p-947259.html). After frying two of these in process of trying to figure the darn things out, I ordered a [5 pack of the ESP-07 version.](http://www.banggood.com/ESP8266-ESP-07-Remote-Serial-Port-WIFI-Transceiver-Wireless-Module-p-968190.html) Banggood also sent me 7 of them which was a bonus.
## Why the ESP-07 version?
The ESP8266 can be programmed as a stand alone device with the [Arduino IDE!](https://github.com/esp8266/Arduino) That's right, you don't even need an arduino! Also, most of the arduino libraries work with it! I went this direction after exeriencing many frustrations using the ESP8266 in conjunction with an arduino. The ESP-07 version has more GPIO pins and is about the same price, whereas the ESP-01 only has two GPIO pins, one of which much be grounded to program the device.
## First experiences with the ESP8266
After playing around with this microcontroller for some time now, I have concluded that it can be finicky at times. Documentation for the device can also be difficult to find, especially if you run into problems. I am going to discuss what I went through to finally get the device fully working, along with the frustrations that I experienced.
When you first get the device, make sure to have solid connections to each one of the pins. You will need an FTDI programmer to connect to USB. [This is the one that I have and it works fine.](http://www.banggood.com/FT232RL-FTDI-USB-To-TTL-Serial-Converter-Adapter-Module-For-Arduino-p-917226.html) Just make sure you have one that is 3.3v capable or it will not work! The ESP8266 can draw over 200 mA at times and the FTDI programmer simply does not provide enough power. [This is the power supply I used.](http://www.banggood.com/3Pcs-MB102-Breadboard-Module-Adapter-Shield-3_3V5V-For-Arduino-Board-p-957095.html)
## Wiring
Wiring is pretty much the same across different versions of the ESP8266. Connect VCC and CH_PD to power at all times. Gnd to gnd and on the ESP-07 GPIO15 must also go to gnd. When you program the device, you must have GPIO0 to ground for programming mode. You must also connect RX on the ESP8266 to TX on the FTDI programmer and vice versa. Make sure to connect all of the power and ground wires to the power supply and NOT the FTDI programmer. The only two wires you should have connected to the FTDI programmer are RX and TX.
## Testing the ESP8266
To test out the device, plug it into your computer and use a serial monitor. You can use the arduino serial monitor, or you can also use putty. Make sure to select the correct COM port and the right baud rate. Baud rates can vary. I have had some that are 9600 and others have been 115200. Just change the baud rate until you see text that you can actually read. Once connected, you can change the baud rate of the ESP8266.
There is a set of AT commands to communicate with the ESP8266. The documentation of these commands is descent although some commands were a bit hard to find. [Here is a list of most of the commands.](https://nurdspace.nl/ESP8266) A few important commands that are often missed are to change the baud rate and update the firmware on the device. You can update the firmware by using AT+CIUPDATE. You can also change the baud rate by using AT+CIOBAUD=9600, or whatever baud rate you desire.
These commands can be used to test the ESP8266, but they are also the same commands if you wanted to use it with an arduino. Simply wire it up to serial on an arduino and print out the commands. I played around with this for a bit, but not long enough to get it fully working. This is how I was originally going to use the device, but I found it to be much easier programming the ESP8266 directly as a standalone device.
## Programming ESP8266 with Arduino IDE
A major advantage of programming the ESP8266 is that it is more powerful, yet much smaller in size. It uses a 32 bit processor and also has 1mb of ram (on the newer versions). [Link to Github ESP8266 Arduino IDE.](https://github.com/esp8266/Arduino) You can install this in your Arduino IDE by File > preferences and adding `http://arduino.esp8266.com/package_esp8266com_index.json` to Additional Boards Manager URLs. Now you can go to Sketch > Libraries > Manage Libraries and add the ESP8266 library.
Select an example sketch (WIFIScan) and make sure your ESP8266 is plugged in with GPIO0 to ground! It must be to ground to enter programming mode! Select Generic ESP8266 Module for the board and the right port. It shouldn't matter which programmer is selected. Compile and upload your sketch and it is as simple as that!
## Complications
I just want to reiterate some of my complications that I ran into throughout this process. The biggest thing to keep in mind it to check your connections! The first time I soldered all of the connections I couldn't figure out why it wasn't working. I checked each connection with a meter only to find out one of them was bad. Another thing is to make sure you use an external power supply! Connect all power and ground connections to these! Also connect the ground of your FTDI programmer to the ground of your power supply. Make sure the voltage of your power supply and FTDI programmer are 3.3v! Remember to connect GPIO0 to ground ONLY when uploading to the ESP8266.
## Sources
[https://www.sparkfun.com/products/13678](https://www.sparkfun.com/products/13678)
[http://www.esp8266.com/](http://www.esp8266.com/)
[https://github.com/esp8266/Arduino](https://github.com/esp8266/Arduino)
[http://www.pridopia.co.uk/pi-doc/ESP8266ATCommandsSet.pdf](http://www.pridopia.co.uk/pi-doc/ESP8266ATCommandsSet.pdf)
[https://nurdspace.nl/ESP8266](https://nurdspace.nl/ESP8266)
[http://www.electrodragon.com/w/ESP8266](http://www.electrodragon.com/w/ESP8266)

View File

@@ -0,0 +1,54 @@
# Wifi Controlled LED Lights
LED lights can be used for many different things. I have used LED lights in the past in my computer and even on my desk. They have always been wired to a physical switch. I now have LED lights that I can turn on and off and even change color from a simple web page that I can access with my phone.
* * *
For this project I used the ESP8266 microcontroller to connect to my wireless network, run a web server, and control an RGB LED strip based on inputs from a web form. I followed [this guide](https://learn.adafruit.com/rgb-led-strips), but incorporated Wifi to control the LEDs.
Required Components
ESP8266
12v Powersupply 1.2 Amps per meter (Keep in mind the ESP8266 can draw 200-400ma)
Wiring
[TIP120 Transistors](http://www.banggood.com/10-Pcs-TIP120-NPN-TO-220-Darlington-Transistors-p-932779.html)
12v to 3.3 volt converter
Code can be found [here](https://github.com/mgerb/wifi_leds/blob/master/wifi_RGB_strip.ino).
## Code
Depending on which version of ESP8266 you have, this might not work. You need at least 3 GPIO pins. I used version ESP-07, which has plenty of usable GPIO pins. The code is fairly simple. Basic functionality includes connecting to my Wifi network and starting up a web server. I was able to log into my router after the ESP connected to assign it a static IP. This way I could always access the web server with the same IP address.
The main program loop listens for an http request on the web server. If the cycle parameter is set to true, the color cycle function will continue to run until the server gets another HTTP request. The color cycle function happens to be a problem because it does not run asynchronously. This means that the whole color cycle must complete before it can respond to another HTTP request. This can cause delays when accessing the web page. Because this is a small personal project I didn't look into fixing this issue.
All data from the web page is passed to the server through get parameters. The program parses through these parameters and checks to see if any of the colors are out of range. If they are out of range it either sets them to a max value of 250 or a min value of 0 (off). The changeColor method is called with paramters from the web form.
## Changing Colors
The changeColor function takes in 6 parameters; one for current color and one for each color passed from the web form. This function needs the current color because it implements the changing of colors gradually. Again, it is all synchronous so during the changing of colors the web server will not be able to respond to requests.
## Cycling Colors
The color cycle function is a bit more confusing than the changeColor function. This function cycles through each color once. The delay can be adjusted to how fast you would like the cycle to last. This function will continuously get called as long as the cycle boolean is true. After each cycle the server will still check for an http request. Like I mentioned above this function is synchronous so it must complete a full cycle before any other code is executed.
This function is only chaning 2 RGB values at any one time. It contains two loops, one of which is nested. The first loop selects two pins to be cycled at one time: rgbColor[decColor] and rgbColor[incColor]. For example, it will first select Red and Green, then Green and Blue, and finally Blue and Red. During each selection, a nested loop is run, which cycles the color values of selected color pins. Red starts at 250 where Green starts at 0\. The nested loop cycles Red to 0 while cycling Green to 250, exits the nested loop, and then proceeds to cycle Green and Blue, and finally Blue and Red.
## Wiring
The wiring for this project is fairly straight forward. The image below is from Adafruit's website and it illustrates the wiring for an Arduino. The ESP8266 wiring is fairly similar, but because it runs on 3.3 volts, I have used two regulators to convert 12 volts. I used a 5v and 3.3v regulator because the 3.3v regulator could not take 12v as an input. You could also just get a 12v to 3.3v converter but I used what I had on hand.
12v PSU > 5v > 3.3v > ESP8266
The power on the LED strip is wired straight to the positive connections on the 12v power supply.
![image](https://learn.adafruit.com/system/assets/assets/000/002/692/medium800/led_strips_ledstripfet.gif?1396785823)
The image below is how I have my ESP8266 wired under my desk to control my led lights. As you can see the power comes in from the top right and the output for each RGB pin is on the left where the black is the 12v.
![image](http://i.imgur.com/4p5CKOL.jpg)
A picture of the final setup. I have one long LED strip along the back of the desk and two smaller strips on the back of each of the monitors.
![image](http://i.imgur.com/aI5clZW.jpg)

View File

@@ -0,0 +1,261 @@
# Temperature Sensor on the Web
The temperature readings that are displayed on this site come from the ESP8266 with a DHT11 sensor attached to it. I am going to explain the steps in the entire process. I will split it up into two parts. In this post I will focus on programming the ESP8266 and how to send data to a web server.
* * *
Update: I have updated some information regarding this project. Refer to these other posts.
[ESP8266 temperature sensor updates and difficulties](/?post=12-29-15.html)
[Temperatue Sensor - Server Side](/?post=1-1-2016.html)
For this project you will need an ESP8266 and a DHT temperature sensor. For prototyping purposes I prefer to use the [NodeMCU module](http://www.banggood.com/NodeMcu-Lua-ESP-12E-WIFI-Development-Board-p-985891.html). There are a variety of temperature sensors that you could use, but I prefer the DHT11 because it is easy to use and it is cheap! I ordered a 5 pack of them from [Banggood](http://www.banggood.com/5Pcs-KY-015-DHT11-Temperature-Humidity-Sensor-Module-For-Arduino-p-983263.html). The source code for this project can be found on [Github](https://github.com/mgerb/esp8266-HTTPTempSensor).
## How it works
When the ESP first boots up, it reads WiFi credentials from the EEPROM and tries to connect to an access point using those credentials. If no connection is made, it starts up it's own access point and runs a web server with a captive portal. This means that when anyone connects to it, it will redirect any dns request to itself, which forces anyone to the configuration page. The user can connect to the access point and enter in the ssid, password, and preferred sensor name. This information will be saved into EEPROM and the ESP will then reboot and start sending sensor data to the server.
![image](http://i.imgur.com/PhnDEyU.png)
## ESP8266 File System
To run the web server, I used the file system library within the arduino ESP library. This is how I store the web content in flash on the chip. It allows me to modify all of the necessary web files without having to deal with sending them as strings from the arduino code.
To use the file system, you must download the tool [here](https://github.com/esp8266/arduino-esp8266fs-plugin/releases/download/0.1.3/ESP8266FS-0.1.3.zip) to upload your files to the ESP. Once downloaded, store it in a tools folder in the arduino folder (path "/Arduino/tools/ESP8266FS/tool/esp8266fs.jar"). Now when you create a new sketch go to the sketch folder and create a "data" folder. To upload data to this folder select "Tools > ESP8266 Sketch Data Upload" and the files will be uploaded to the ESP and will be ready to use within the program. More information on the ESP8266 file system can be found [here](http://esp8266.github.io/Arduino/versions/2.0.0/doc/filesystem.html).
## Sending data to the server
There are many ways to send the data to a server, but I implemented data transfer with a simple get request. I chose this route because it is extremely simple to create a REST API in Node.js. I will cover this in a nother post. If you are unfamiliar with a get request, it is a way a web server can receive a request, and also parameters within that request. In this case, I send temperature, humidity, location, and a key as parameters in the request.
If this were a full production application, this would not be the ideal way to handle data transfer to a server because it is not encrypted. A better practice would be to implement this using another TCP transfer protocol. In my case, it would be better to send a POST request over SSL, but I have not looked into that for this project yet as the method I am currently using seems to work just fine.
## Code
```arduino
#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>
#include <DNSServer.h>
#include <ESP8266mDNS.h>
#include <EEPROM.h>
#include <FS.h>
#include <DHT.h>
DHT dht(5, DHT11);
char ssid[32] = "";
char password[32] = "";
char sensorName[32] = "";
//key used by web server to prevent unnecessary http requests
const String server_password = "";
const int httpPort = 80;
const char* host = "162.243.16.83";
boolean connected;
// DNS server
const byte DNS_PORT = 53;
// Web server
ESP8266WebServer server(80);
DNSServer dnsServer;
WiFiClient client;
/* Soft AP network parameters */
IPAddress apIP(192, 168, 1, 1);
IPAddress netMsk(255, 255, 255, 0);
void setup() {
// put your setup code here, to run once:
Serial.begin(115200);
delay(1000);
loadCredentials();
//try to connect to access point
WiFi.disconnect();
WiFi.begin(ssid,password);
int connRes = WiFi.waitForConnectResult();
Serial.print ( "connRes: " );
Serial.println ( connRes );
Serial.println("Connecting");
for (int i = 0; WiFi.status() != WL_CONNECTED && i <10; i++){
Serial.print(".");
delay(1000);
}
//if connected skip setting up access point
if(WiFi.status() != WL_CONNECTED){
connected = false;
WiFi.mode(WIFI_AP);
WiFi.softAPConfig(apIP, apIP, netMsk);
WiFi.softAP("Sensor Config");
delay(500);
server.on("/", handleRoot);
server.on("/config", handleConfig);
server.on("/bootstrap.min.css", handleBootstrap);
server.on("/style.css", handleCss);
server.onNotFound(handleNotFound);
SPIFFS.begin();
dnsServer.start(DNS_PORT, "*", apIP);
server.begin();
}
else{
connected = true;
WiFi.mode(WIFI_STA);
dht.begin();
}
}
void loop() {
//if could not connect to access point
if (!connected){
dnsServer.processNextRequest();
server.handleClient();
}
else{
if (!client.connect(host, httpPort)) {
return;
}
float temp = dht.readTemperature(true);
float humidity = dht.readHumidity();
// We now create a URI for the request
String url = "/temperature";
url += "?temperature=";
url += temp;
url += "&humidity=";
url += humidity;
url += "&key=";
url += server_password;
url += "&location=";
url += sensorName;
url.replace(" ", "+");
Serial.println("URL: " + url);
client.print(String("GET ") + url + " HTTP/1.1\r\n" +
"Host: " + host + "\r\n" +
"Connection: close\r\n\r\n");
delay(100);
while(client.available()){
String line = client.readStringUntil('\r');
Serial.print("Response: " + line);
}
//delay(10000);
ESP.deepSleep(120 * 1000000, WAKE_RF_DEFAULT);
}
}
void handleRoot(){
if(SPIFFS.exists("/index.html")){
File file = SPIFFS.open("/index.html", "r");
server.streamFile(file, "text/html");
file.close();
}
else{
server.send(404, "file doesn't exist");
}
}
void handleBootstrap(){
if(SPIFFS.exists("/bootstrap.min.css")){
File file = SPIFFS.open("/bootstrap.min.css", "r");
server.streamFile(file, "text/css");
file.close();
}
else{
server.send(404, "file doesn't exist");
}
}
void handleConfig(){
if (server.arg("password") != "" && server.arg("ssid") != "" && server.arg("name") != ""){
server.arg("ssid").toCharArray(ssid, sizeof(ssid) -1);
server.arg("password").toCharArray(password, sizeof(password) -1);
server.arg("name").toCharArray(sensorName, sizeof(sensorName) -1);
saveConfig();
server.send(200, "text/html", "Config successful. Rebooting");
ESP.restart();
}
server.send(200, "text/html", "<script>window.onload = function() { window.location = \"/\"; }</script>");
}
void handleCss(){
if(SPIFFS.exists("/style.css")){
File file = SPIFFS.open("/style.css", "r");
server.streamFile(file, "text/css");
file.close();
}
else{
server.send(404, "file doesn't exist");
}
}
void handleNotFound(){
if(SPIFFS.exists("/index.html")){
File file = SPIFFS.open("/index.html", "r");
server.streamFile(file, "text/html");
file.close();
}
else{
server.send(404, "file doesn't exist");
}
}
/** Store WLAN credentials to EEPROM */
void saveConfig() {
EEPROM.begin(512);
EEPROM.put(0, ssid);
EEPROM.put(0+sizeof(ssid), password);
EEPROM.put(0+sizeof(ssid)+sizeof(password), sensorName);
EEPROM.commit();
EEPROM.end();
}
void loadCredentials() {
EEPROM.begin(512);
EEPROM.get(0, ssid);
EEPROM.get(0+sizeof(ssid), password);
EEPROM.get(0+sizeof(ssid)+sizeof(password), sensorName);
EEPROM.end();
Serial.println("Recovered credentials:");
Serial.println(ssid);
Serial.println(password);
Serial.println(sensorName);
}
```

View File

@@ -0,0 +1,45 @@
# ESP8266 temperature sensor updates and difficulties
I'm working on setting up new sensors now that I have been developing the back end of my website to display sensor information. Throughout this process I made updates to the current code for the project. As I continue to make progress I am overcoming obstacles as they show up.
* * *
I changed the way that the ESP8266 sends web pages to the user via HTTP requests. Previously I made use of the file system in the ESP8266, but I changed the way I did this because I noticed that it was a bit slow handling all the requests, especially because I used bootstrap to make the user interface look pretty. Although I cut down the bootstrap to only the pieces that I needed, it was still slower than I wanted.
I decided to not use the file system and handle the html pages within header files. I found a fairly nice way of doing this. To do this, declare a new character array, store it in flash memory with "PROGMEM", and use the raw string literal functionality of C to escape all characters in a string as show below. This is not quite as nice a dealing with an HTML file in memory, but it is easier because you do not have to upload the files to the file system every time, which takes a good amount of time.
`const char page_html[] PROGMEM = R"=====(/*insert html here*/)=====";`
This newly created page can now be sent back to the user upon an HTTP request.
`server.send(200, "text/html", page_html);`
There were a few other updates that I made. I realized that if the access point the ESP was connected to goes down, it would not try to reconnect. It will now check if it is connected to the access point every time it tries to take a temperature reading and send data. If it is not connected to the access point it will reboot. Now that it reboots, if it still cannot connect to the access point it will be stuck in config mode. I fixed this by creating a timeout. If it has been sitting in config mode for 10 minutes the device will reboot.
These example are show below. Keep in mind that this is not the entire program, but just the new functionality that I mentioned. The source code for the project can be found [here](https://github.com/mgerb/esp8266-HTTPTempSensor/tree/working).
## Problems flashing the ESP
I previously used the ESP-07, but recently got some new ESP-12e's because of the higher flash size. I thought this would be great if I was going to make use of the file system. After spending hours and hours trying to flash these chips, I came to the conclusion that I need a better serial to USB programmer. The current one that I use came from China and was very cheap.
With the new version of ESP Arduino software (2.0.0), you get the option of flashing with the ck mode. This requires GPI0 > DHT and RESET > RST. My FTDI programmer did not have an RTS pin, but rather a CTS. I found that I could flash one of my chips with this and the other I could not. After doing some research I found that it may be a problem with my Mac. Unfortunately my Ubuntu image that I usually dual boot with acted up and would not boot. This left me without any other operating system to try at the time.
![image](http://i.imgur.com/iNacOBT.jpg)
Because I was experiencing problems flashing with the new ESP8266 Arduino software, I reverted to an older version (1.6.5). In this version I was able to put GPI0 > Ground and start the ESP8266 in flash mode. This is the part where I realized I have a bad FTDI programmer. When I flash the device, it will get stuck every once in awhile. I then have to unplug everything and try again. I then tried a different programmer and it was able to work every time.
After a good amount of frustration I was able to get my ESP8266 flashed with my new program and I got another temperature sensor set up. Here is the wiring that I used in the sensor. Keep in mind the DHT11 sensors that I used had a 10k resistor built into them.
GND > GND
GPI15 > GND
VCC > 3.3v
EN (CH_PD) > 3.3v
GPIO5 > Sensor Data
The sensor VCC and Ground then were hooked up accordingly. I used an 800ma 3.3v regulator to power everything and it is working fine.
![image](http://i.imgur.com/WrDh8oN.jpg)

View File

@@ -0,0 +1,35 @@
# Temperature Sensor - Server Side
The server side coding is a bit more complicated than programming the ESP8266 itself. I use a NoSQL database to store the information and some of the queries are complex and can be confusing to understand. I also make use of REST API's to send the data to the client side.
* * *
## Storing Information
For this project I am using MongoDB to store all of my information because it goes well with my server, which is coded in Node.js, but any database could be used. I also wanted to learn about NoSQL databases because I already have experience with SQL databases. Information on coding the ESP8266 can be found [here](/?post=12-18-2015.html) in a previous post.
`mitchellg.me/temperature?temperature=0&humidity=0&location=0&key=0`
Above is the GET request that is sent to the server from the ESP8266\. Data is transferred through GET parameters. The key is an authentication code that I set to prevent unwanted HTTP requests. This is similar to an API key. Below is the code implemented to handle the HTTP request from the ESP8266.
## Creating the REST API
There are many ways that I could display the temperature information on a graph, but I was trying to come up with a quick and easy way that was also efficient. I also wanted reusability in case I wanted to add or change things down the road. I decided I was going to use [Chart.js](http://www.chartjs.org/) because it is open source and free. These graphs are implemented on the front end using javascript. Because of this I needed to figure out a way to send the sensor data to the client side. I felt that the best way for me to do it would be creating a REST API. I plan on making another post in the future explaining all of my client side code along with how to use Chart.js.
Now that I know how I want to display my information I just need to think of what information I want to display. I thought it would be cool to display a few different graphs. As of right now I have one graph that displays data by the year and one that displays by each month. The maximum and minimum temperature of each day are displayed as well as the average humidity for that day. The user can also select which year or which month to display and the graph will adjust accordingly.
Now that I know what information I need I can start developing my REST API. I have a MongoDB collection called "temperature", which stores temperature, humidity, location, and time updated. Updated is a type of Date, which I will use in all of the queries to group by each day. To do the grouping in MongoDB I needed to use the [aggregation functionality](https://docs.mongodb.org/v3.0/aggregation/) of MongoDB. Aggregation allows me to essentially perform queries on top of queries using the MongoDB "pipeline". This is similar to an SQL query when a selection is performed within a selection.
## Explanation of MongoDB Queries
The first function takes in a location and year and returns maximum and minimum temperature readings as well as average humidity for each day in the selected year. The results are also returned sorted from newest to oldest.
In the first part of the aggregation pipeline, which is $project, I am just selecting the temperature, humidity, year, month, and day. This part is important because it allows me to pull the year, month, and day from the date that is stored in the database. This way I do not have to store each of these entries separately in the database.
This data is then passed to the next part of the aggregation pipeline. The $match pipeline stage selects out information from the collection which match with the selected year and location.
The $group aggregation operator is the stage in which I actually group the data by the location, year, month, and day. For each group I also take the max and min temperatures along with the average humidity by using the correct accumulator operators. Now that I have the appropriate information I need, I just use the $sort pipeline operator to sort the data based on time updated.
The function to display by month is the exact same, except I take in the month attribute and add it to the $match operator within the aggregation pipeline. Just like that the query is complete and all I need to do is send the response back to the client. Chart.js uses JSON format, which makes things extremely easy because MongoDB query results are in JSON. I set the content type to JSON, and use JSON.stringify() to convert the JSON to a readable format for debugging purposes. An example API request can be tested out here.
[/api/sensorbylocation/year?location=Winona%20Apartment&year=2016](/api/sensorbylocation/year?location=Winona%20Apartment&year=2016)

View File

@@ -0,0 +1,93 @@
# Hosting a Node.js Web Server on Digital Ocean
A VPS or "Virtual Private Server" is a nice way to host a web server. Digital Ocean provides a great service based on my experiences. I recently rebuilt a new server to host this site and I will go over the entire setup process.
* * *
## Creating a Droplet
Digital Ocean calls their VPS's "Droplets" and they are very simple to set up. Create an account on the site and attach a credit card. You are now ready to create a droplet. In my case I use an Ubuntu server but Digital Ocean offers a variety of linux operating systems. Select the size, location, and name and the server is up and running! You will be emailed with the login information.
## Login with SSH
Select your newly created droplet and navigate to console access. You will be prompted to change your password. Once the password is changed you can connect to the root user account via SSH.
`$ ssh root@<ip address>`
If you are using a Windows machine you can download [putty](http://www.chiark.greenend.org.uk/~sgtatham/putty/download.html) to connect with SSH. If you choose not to use SSH you can just use the console access on the Digital Ocean website.
## Firewall
The very next thing I do is set up the firewall. I only want to open ports that need to be open. I use the built in firewall tool in Ubuntu called UFW.
`$ ufw enable`
Now that UFW is enabled I can start opening the necessary ports. I must open 22 if I want to continue to SSH into the server.
`$ ufw allow 22/tcp`
This will open TCP port 22 for SSH. I also open port 80 because that is the port in which the web server will run on.
## Network Monitoring
`$ sudo apt-get install vnstat`
VNStat is a nice tool that will allow me to monitor the bandwidth my server consumes both outbound and inbound. Simply use the command "vnstat" to check the bandwidth. Note that it takes awhile for vnstat to capture bandwidth data if you just upon first installing it.
## Terminal Multitasking
`$ sudo apt-get install screen`
Screen is a really nice tool too use when dealing with multiple terminal windows. Simply use the command `$ screen` to start screen. Press CTRL>A then C to open another screen window. Navigate between screens by pressing CTRL>A then N. To check if scren is attached or detached simply use the command `$ screen -ls`. Reattach to screen by using `$ screen -r` and detach with `$ screen -d`. If screen is open and already attached, but you wish to attach to it, simply use the command `$ screen -d -r`.
## NPM
Now that I have the basics set up on my server I can start installing the dependencies for my Node.js web server. NPM is a package manager that is needed for Node.js. It makes things very easy when used properly.
```bash
$ sudo apt-get install npm
$ sudo npm update npm -g
```
The package manager on ubuntu does not have the most recent NPM packge as of right now but it is very easy to update with NPM itself. The second command listed above will download the newest version of NPM and update itself.
## Node.js
Now that NPM is installed I can use it to install Node.js.
```bash
$ sudo npm cache clean -f
$ sudo npm install -g n
$ sudo n stable
$ sudo ln -sf /usr/local/n/versions/node/<VERSION>/bin/node /usr/bin/node
```
This will install the latest version of Node.js.
## MongoDB
Installing MongoDB used to be very simple, but now they do not include the init scripts with the newest version. Installation instructions can be found [here](https://docs.mongodb.org/manual/tutorial/install-mongodb-on-ubuntu/).
Once MongoDB is installed it can be run by using the command `$ mongod`, but I am going to show an easier way using the startup script. This way MongoDB will start when the VPS boots and it can also be monitored with the `$ service` linux command.
```bash
$ cd /etc/init.d
$ sudo nano mongod
```
Once here copy the contents of [this file](https://github.com/mongodb/mongo/blob/master/debian/init.d) into mongod. We now have the startup script, but we need to change some permissions first before it can run.
```bash
$ sudo chmod 755 /etc/init.d/mongod
$ sudo chown root:root /etc/init.d/mongod
```
And to run MongoDB upon system startup.
```bash
$ update-rc.d mongod defaults
```
## Restoring Database Files
The web server is almost ready, but first I need to make sure all of my previous records are stored in the database. To do this I navigate to the folder which my records are stored in, which is contained in my git repository. I then use the command `$ mongorestore`, which loads everything from the dump into the freshly installed MongoDB. To backup MongoDB simply navigate to the desired folder to store the backup and issue the command `$ mongodump`.

View File

@@ -0,0 +1,102 @@
# Flashing the ESP8266 with the Arduino IDE
Flashing the ESP8266 with the newest version of the ESP8266/Arduino can be tedious with the off brand FTDI programmers.
* * *
## Problems with knockoff FTDI programmers
The new version of ESP8266/Arduino offers different flashing methods than the previous versions, which are the CK and NodeMCU methods. These new methods allow the flashing tool to automatically reset the ESP8266 into boot loader mode. This is convenient if you have a NodeMCU or the right FTDI programmer. I have a cheap knock off FTDI programmer in which the CK flashing method does not work with.
The table below shows the correct connections for using the esptool-ck method of flashing. The FTDI programmer that I use does not have the "RTS" pin. Alternatively it has a "CTS" pin. I figured the FTDI programmers were the exact same, but after attempting to flash with this method I was convinced otherwise.
[The current version of ESP8266/Arduino uses the esptool-ck flashing method](https://github.com/igrr/esptool-ck)
<table class="table-bordered">
<thead>
<tr>
<th>Name</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>none</td>
<td>No DTR/RTS manipulation</td>
</tr>
<tr>
<td>ck</td>
<td>RTS controls RESET or CH_PD, DTR controls GPIO0</td>
</tr>
<tr>
<td>wifio</td>
<td>TXD controls GPIO0 via PNP transistor and DTR controls RESET via a capacitor</td>
</tr>
<tr>
<td>nodemcu</td>
<td>GPIO0 and RESET controlled using two NPN transistors as in [NodeMCU devkit](https://raw.githubusercontent.com/nodemcu/nodemcu-devkit/master/Documents/NODEMCU_DEVKIT_SCH.png).</td>
</tr>
</tbody>
</table>
</div>
## The Solution
Unfortunately the current version of ESP8266/Arduino does not offer alternative methods of flashing. After hours of frustration I finally figured out a solution to the problem.
The methods to flash the ESP8266 within the Arduino IDE can be adjusted. I first thought I needed to download another method to flash with such as the [esptool.py](https://github.com/themadinventor/esptool), but I found a much easier solution. The "boards.txt" file can be edited, which allowed more customization to the flashing settings within the Arduino IDE. This is the path on my system.
/Users/$USER/Library/Arduino15/packages/esp8266/hardware/esp8266/2.1.0/boards.txt
The reset method needs to be changed from "ck" to "none" in order for this to work properly.
```bash
generic.upload.resetmethod=ck
```
to
```bash
generic.upload.resetmethod=none
```
Because we are not using the CK flashing method, we need to boot up the ESP8266 into boot loader mode manually.
```bash
GPIO0 -> gnd
VCC -> 3.3v
CH_PD -> 3.3v
GND -> gnd
TX -> RX
RX -> TX
```
The ESP8266 must be powered by an external 3.3v power supply, because the FTDI programmer cannot supply enough current. Also keep in mind that the ground of the FTDI programmer MUST be connected to the same ground that the ESP8266 is connected to. If this is not done it will not flash.
This should solve any problems flashing with a knockoff FTDI programmer.

View File

@@ -0,0 +1,27 @@
# Redoing this website for the third time
New web technologies never cease to amaze me. Every time I learn a new technology used in the web my mind is blown. Shortly after this happens
I discover something newer that is just bigger and better. Because of this, I am always learning, which is awesome!
***
## It's been awhile
It has been quite a long time since I posted anything on this site. Since the last post I've graduated college and started a career as a software engineer.
I'm currently mainly working on front end development with AngularJS, which is pretty exciting! I previously used Angular on this site to help myself learn the framework.
Angular is pretty great, but I've found a new web framework that I'm absolutely loving so far! It's a framework developed by Facebook called [ReactJS](https://facebook.github.io/react/).
I've decided to completely redo this site again because I know I can do it better as I learn more and more. Credit for most of my design can be given to [Nikolay](http://nikolay.rocks).
I love the simple design that he has come up with on his site. Some of the site is open source, but I am redoing a lot of it to my liking;
The source code for this site can be found on GitHub [here](https://github.com/mgerb/mywebsite) (currently developing on the react branch);
Here are some of the core items that I'm using to develop this site:
- [Go](https://golang.org/)
- [React](https://facebook.github.io/react/)
- [Redux](http://redux.js.org/docs/introduction/)
- [MongoDB](https://www.mongodb.com/)
- [App Engine](https://cloud.google.com/appengine/)
There are many other tools here and there such as [MarkedJS](https://github.com/chjj/marked) and [HighlightJS](https://highlightjs.org/)
that I've also used in the process. I am excited to discuss my thoughts and experiences with many other technologies like this.

55
posts/resume.md Normal file
View File

@@ -0,0 +1,55 @@
## Mitchell Gerber
Hello, my name is Mitchell and I am pursuing a career in software engineering. I have a passion for creating things and learning new technologies.
I always find enjoyment in building things, whether it's computers, drones, or software. I started programming upon entering college and I have been hooked ever since. Programming has become a hobby of mine and it has opened up many opportunities.
My primary strengths include server side development in Java as well as Node.js. I have experience working with relational databases such as MySQL. I've recently become familiar working with MongoDB as well. I helped develop a web application for Digi International that is used to debug embedded devices for testing purposes. I worked with Java/JSP, which ran on a Jetty web server and used an H2 SQL database.
My interest in developing things follows me home after work hours. I often lose track of time working on personal projects. I'm currently working on this personal website to learn Node.js and familiarize myself with newer web development technologies. In my free time I enjoy building and flying radio controlled unmanned aerial systems. I've also recently gained an interest in programming microcontrollers.
<div class="row text-center">
<div class="col-lg-6">[![Sensor Project](/public/images/sensors.png)](/?post=12-18-2015.html) [Sensor Project with ESP8266](/?post=12-18-2015.html)</div>
<div class="col-lg-6 text-center"><iframe width="300" height="200" src="https://www.youtube.com/embed/9re_9DdNV6Y" frameborder="0" allowfullscreen=""></iframe>
UAS Footage from one of my drone projects</div>
</div>
## Technical Skills
I started with Java and have been working with it since the end of 2012\. I consider myself experienced in Java as I have used it for numerous projects throughout college as well as at my job as a Software Tester/Developer. I found my interest in web development in late 2014\. Since then I've become familiar with different back end languages including Node.js, PHP, and Java. I've also worked with MongoDB as well as relational databases such as MySQL.
Beginning of Summer 2015 I started venturing into the category of microcontrollers. I've become familiar with programming the Arduino as well as a microcontroller called the ESP8266\. Throughout this process I've gained knowledge of working with C.
As of late 2014 I've been using Linux as my primary operating system for software development, which has allowed me to become more familiar with Unix systems. I've also worked with Ubuntu server, which is used to host this website.
## Related Experience
* <span class="colorRed">2014-Present</span> Software Developer/Tester - Digi International, WSU, Winona, MN
> * Conduct manual system tests on software.
> * Develop testing tools for debugging embedded devices
> * Develop firmware for an embedded system.
> * Participate in weekly conference calls and produce weekly status reports
> * Collaborate with others on software development projects.
* <span class="colorRed">2011-2014</span> Technical Assistant - South Central Education Consortium, Grand Meadow, MN
> * Support 10-20 faculty and staff with software and hardware issues.
> * Set up and image computer labs
## Education
* <span class="colorRed">2012-2016</span> Bachelor of Science, Magna Cum Laude
Winona State University, Winona, MN
Major: Computer Science
Major GPA: 3.68
Dean's List: 6 semesters
## Contacts
* [mgerb42@gmail.com](mailto:mgerb42@gmail.com)
* [GitHub](https://github.com/mgerb)
* [LinkedIn](https://www.linkedin.com/in/mitchell-gerber-125391b3)

View File

@@ -1,338 +0,0 @@
html {
overflow-y: scroll;
}
body {
font-family: Century Gothic,CenturyGothic,AppleGothic,sans-serif;
}
a {
color: #00B7FF;
}
.textCenter {
text-align: center;
}
.resume{
font-size: 12pt;
font-weight: 400;
}
.header {
height: 400px;
background: url("/public/images/headerBackground.jpg") center no-repeat;
background-size: cover;
}
.headerText{
font-size: 80px;
color: white;
margin-top: 0px;
}
.large-text{
font-size: 100px;
}
.centerDiv{
position: relative;
top: 50%;
transform: translateY(-50%);
}
.form-signin {
max-width: 500px;
padding: 15px;
margin: 0 auto;
}
.form-signin .form-control {
position: relative;
height: auto;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
padding: 10px;
font-size: 16px;
}
.form-file {
max-width: 500px;
padding: 15px;
margin: 0 auto;
}
.form-file .form-control {
position: relative;
height: 34px;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
padding: 10px;
font-size: 16px;
}
.colorRed {
color: red;
}
.colorGreen {
color: green;
}
.timeForm {
width: 30%;
display: inline;
}
.aboutMeImage {
width: 100%;
height: 100%;
}
.center {
margin-left: auto;
margin-right: auto;
display: block;
}
/*
* Override Bootstrap's default container.
*/
@media (min-width: 1200px) {
.container {
width: 970px;
}
}
/*
* Masthead for nav
*/
.blog-masthead {
background-color: #428bca;
-webkit-box-shadow: inset 0 -2px 5px rgba(0,0,0,.1);
box-shadow: inset 0 -2px 5px rgba(0,0,0,.1);
}
/* Nav links */
.blog-nav-item {
position: relative;
display: inline-block;
padding: 10px;
font-weight: 500;
color: #cdddeb;
}
.blog-nav-item:hover,
.blog-nav-item:focus {
color: #fff;
text-decoration: none;
}
/* Active state gets a caret at the bottom */
.blog-nav .active {
color: #fff;
}
.blog-nav .active:after {
position: absolute;
bottom: 0;
left: 50%;
width: 0;
height: 0;
margin-left: -5px;
vertical-align: middle;
content: " ";
border-right: 5px solid transparent;
border-bottom: 5px solid;
border-left: 5px solid transparent;
}
/*
* Blog name and description
*/
.blog-header {
padding-top: 20px;
padding-bottom: 20px;
}
.blog-title {
margin-top: 30px;
margin-bottom: 0;
font-size: 60px;
font-weight: normal;
}
.blog-description {
font-size: 20px;
color: #999;
}
/*
* Main column and sidebar layout
*/
.blog-main {
font-size: 18px;
line-height: 1.5;
}
/* Sidebar modules for boxing content */
.sidebar-module {
padding: 15px;
margin: 0 -15px 15px;
}
.sidebar-module-inset {
padding: 15px;
background-color: #f5f5f5;
border-radius: 4px;
}
.sidebar-module-inset p:last-child,
.sidebar-module-inset ul:last-child,
.sidebar-module-inset ol:last-child {
margin-bottom: 0;
}
/* Pagination */
.pager {
margin-bottom: 60px;
text-align: left;
}
.pager > li > a {
width: 140px;
padding: 10px 20px;
text-align: center;
border-radius: 30px;
}
/*
* Blog posts
*/
.blog-post {
margin-bottom: 60px;
}
.blog-post-title {
margin-bottom: 5px;
font-size: 40px;
}
.blog-post-meta {
margin-bottom: 20px;
color: #999;
}
/*
* Footer
*/
.blog-footer {
padding: 40px 0;
color: #999;
text-align: center;
background-color: #f9f9f9;
border-top: 1px solid #e5e5e5;
}
.blog-footer p:last-child {
margin-bottom: 0;
}
.lowerLeft{
color: white;
display: table-cell;
vertical-align: bottom;
height: 400px;
width: 800px;
}
.colorWhite{
color: white;
}
.mitchell-navbar{
border-radius: 0 !important;
background-color: #262626;
border: 0;
}
.gist {
width:600px !important;
}
.gist-file{
}
.gist-data{
max-height: 500px;
max-width: 600px;
}
.chart-legend li span{
display: inline-block;
width: 12px;
height: 12px;
margin-right: 5px;
}
.modal {
display: none;
position: fixed;
z-index: 1000;
top: 0;
left: 0;
height: 100%;
width: 100%;
background: rgba( 255, 255, 255, .8 )
url('http://i.stack.imgur.com/FhHRx.gif')
50% 50%
no-repeat;
}
/* When the body has the loading class, we turn
the scrollbar off with overflow:hidden */
body.loading {
overflow: hidden;
}
/* Anytime the body has the loading class, our
modal element will be visible */
body.loading .modal {
display: block;
}
.btn-file {
position: relative;
overflow: hidden;
}
.btn-file input[type=file] {
position: absolute;
top: 0;
right: 0;
min-width: 100%;
min-height: 100%;
font-size: 100px;
text-align: right;
filter: alpha(opacity=0);
opacity: 0;
outline: none;
background: white;
cursor: inherit;
display: block;
}
input[readonly] {
background-color: white !important;
cursor: text !important;
}
.hide {
display: none;
}
.show {
display: inline;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 894 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 587 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

View File

@@ -1,36 +0,0 @@
app.controller('IndexController', function($scope, $http) {
var title = ["5-22-16", "1-4-16", "1-1-16", "12-29-15", "12-18-15", "10-28-15", "8-13-15", "7-28-15", "7-21-15"];
$scope.posts = [];
for (p in title) {
var postName = title[p];
//use anonymous function calls to pass
//postName to the http callback function
//pass value p to make sure $scope.posts is in order due to asynch function
$http({
method: 'GET',
url: '/public/posts/' + postName + '.html'
}).then((function(postName, p) {
return function(response) {
var html = response.data;
var partial = {};
partial.name = postName;
partial.title = $(html).find('#title').html();
partial.date = $(html).find('#date').html();
partial.intro = $(html).find('#intro').html();
$scope.posts[p] = partial;
}
})(postName, p), function errorCallback(response) {
// called asynchronously if an error occurs
// or server returns response with an error status.
console.log("Error Loading Post");
});
}
});

View File

@@ -1,202 +0,0 @@
app.controller('SensorInfoController', function($scope, $http, $routeParams) {
$scope.location = $routeParams.location;
$http({
method: 'GET',
url: '/api/sensor/' + $scope.location
}).then(function successCallback(response) {
$scope.list = createYearObjects(response.data);
if ($scope.list.length > 0) {
$scope.selectedObject = $scope.list[0];
$scope.selectedMonth = $scope.list[0].months[$scope.list[0].months.length - 1];
$scope.loadingMonth = true;
$scope.loadingYear = true;
displayChart("info-chart-year", "legend-year", $scope.location, $scope.list[0].year, null, function() {
$scope.$apply(function() {
$scope.loadingYear = false;
});
});
displayChart("info-chart-month", "legend-month", $scope.location, $scope.list[0].year, $scope.selectedMonth, function() {
$scope.$apply(function() {
$scope.loadingMonth = false;
});
});
} else {
$scope.selectedObject = {};
$scope.selectedMonth = "";
}
}, function errorCallback(response) {});
$scope.onYearChange = function() {
resetCanvas("info-chart-year", "canvas1-id");
resetCanvas("info-chart-month", "canvas2-id");
$scope.loadingMonth = true;
$scope.loadingYear = true;
displayChart("info-chart-year", "legend-year", $scope.location, $scope.selectedObject.year, null, function() {
$scope.$apply(function() {
$scope.loadingYear = false;
});
});
displayChart("info-chart-month", "legend-month", $scope.location, $scope.selectedObject.year, $scope.selectedObject.months[0], function() {
$scope.$apply(function() {
$scope.loadingMonth = false;
});
});
}
$scope.onMonthChange = function() {
resetCanvas("info-chart-month", "canvas2-id");
$scope.loadingMonth = true;
displayChart("info-chart-month", "legend-month", $scope.location, $scope.selectedObject.year, $scope.selectedMonth, function() {
$scope.$apply(function() {
$scope.loadingMonth = false;
});
});
}
});
function createYearObjects(data) {
var list = [];
for (i in data) {
var exists = false,
index = 0;
for (j in list) {
if (data[i].year == list[j].year) {
exists = true;
index = j;
break;
}
}
if (exists == true) {
if (list[index].months.indexOf(data[i].monthname) < 0) {
list[index].months.push(data[i].monthname);
}
} else {
list.push({ year: data[i].year, months: [data[i].monthname] });
}
}
return list;
}
function resetCanvas(canvas_id, container_id) {
$("#" + canvas_id).remove(); // this is my <canvas> element
$("#" + container_id).append('<canvas class="center" id="' + canvas_id + '" width="800" height="400"></canvas>');
}
function displayChart(chart_id, chart_legend_id, location, year, month, callback) {
var api_url = "/api";
if (month == null) {
api_url += "/sensor/" + location + "/" + year;
} else {
api_url += "/sensor/" + location + "/" + year + "/" + month;
}
$.ajax({
type: 'GET',
url: api_url,
data: {},
beforeSend: function() {
},
success: function(response) {
var json = response;
var data = { labels: [], datasets: [] };
var ctx = $('#' + chart_id);
data.datasets.push({
label: "Max Temperature °F",
fill: false,
lineTension: 0.1,
backgroundColor: "rgba(255,100,100,1)",
borderColor: "rgba(255,100,100,1)",
borderCapStyle: 'butt',
borderDash: [],
borderDashOffset: 0.0,
borderJoinStyle: 'miter',
pointBorderColor: "rgba(255,100,100,1)",
pointBackgroundColor: "#fff",
pointBorderWidth: 1,
pointHoverRadius: 5,
pointHoverBackgroundColor: "rgba(255,100,100,1)",
pointHoverBorderColor: "rgba(255,100,100,1)",
pointHoverBorderWidth: 2,
pointRadius: 1,
pointHitRadius: 10,
data: []
}, {
label: "Min Temperature °F",
fill: false,
lineTension: 0.1,
backgroundColor: "rgba(151,187,205,1)",
borderColor: "rgba(151,187,205,1)",
borderCapStyle: 'butt',
borderDash: [],
borderDashOffset: 0.0,
borderJoinStyle: 'miter',
pointBorderColor: "rgba(151,187,205,1)",
pointBackgroundColor: "#fff",
pointBorderWidth: 1,
pointHoverRadius: 5,
pointHoverBackgroundColor: "rgba(151,187,205,1)",
pointHoverBorderColor: "rgba(151,187,205,1)",
pointHoverBorderWidth: 2,
pointRadius: 1,
pointHitRadius: 10,
data: []
});
for (var i in json) {
if (month == null) {
data.labels.push(json[i].month + "/" + json[i].day);
} else {
data.labels.push(json[i].day);
}
data.datasets[0].data.push(json[i].maxtemp);
data.datasets[1].data.push(json[i].mintemp);
}
var myChart = new Chart(ctx, {
type: 'line',
data: data,
options: {
scales: {
yAxes: [{
ticks: {
suggestedMax: 100,
suggestedMin: 0
}
}]
}
}
});
callback();
}
});
}

View File

@@ -1,34 +0,0 @@
app.controller('SensorsController', function($scope, $http) {
$http({
method: 'GET',
url: '/api/allsensors'
}).then(function successCallback(response) {
// this callback will be called asynchronously
// when the response is available
$scope.information = response.data;
for (i in $scope.information) {
var date = new Date($scope.information[i].updated);
var options = { month: 'numeric', day: 'numeric', year: 'numeric', hour: 'numeric', minute: 'numeric', hour12: true };
$scope.information[i].date = date.toLocaleString('en-US', options);
if ((Date.now() - date) < 420000) {
$scope.information[i].status = "Connected";
$scope.information[i].css = "colorGreen";
} else {
$scope.information[i].status = "Disconnected";
$scope.information[i].css = "colorRed";
}
}
}, function errorCallback(response) {
// called asynchronously if an error occurs
// or server returns response with an error status.
});
});

View File

@@ -1,39 +0,0 @@
var app = angular.module('app', ['ngRoute']);
app.config(['$routeProvider', '$locationProvider', function($routeProvider, $locationProvider) {
$locationProvider.html5Mode({
enabled: true,
requireBase: false
});
$routeProvider.
when('/', {
templateUrl: '/public/view/default.html',
controller: 'IndexController'
}).
when('/sensors', {
templateUrl: '/public/view/sensors.html',
controller: 'SensorsController'
}).
when('/sensors/:location', {
templateUrl: '/public/view/sensor_info.html',
controller: 'SensorInfoController'
}).
when('/post/:postName', {
templateUrl: '/public/view/post.html',
controller: 'PostController'
}).
when('/discord', {
redirect: 'https://discordapp.com/invite/0Z2tzxKECEj2BHwj'
}).
otherwise({
templateUrl: '/public/view/404.html'
});
}]);
//handle each post page after individual posts are selected
app.controller('PostController', function($scope, $route, $routeParams) {
$scope.post = "/public/posts/" + $routeParams.postName + ".html";
});

View File

@@ -1,55 +0,0 @@
<div class="blog-post">
<h2 id="title" class="blog-post-title">Temperature Sensor - Server Side</h2>
<p id="date" class="blog-post-meta">January 1, 2016 by Mitchell</p>
<p id="intro">The server side coding is a bit more complicated than programming the ESP8266 itself. I use a NoSQL database to store the information and some of the queries are complex and can be confusing to understand. I also make use of REST API's to send the data to the client side.
</p>
<hr>
<h3>Storing Information</h3>
<p>For this project I am using MongoDB to store all of my information because it goes well with my server, which is coded in Node.js, but any database could be used. I also wanted to learn about NoSQL databases because I already have experience with SQL databases. Information on coding the ESP8266 can be found <a href="/?post=12-18-2015.html">here</a> in a previous post.
</p>
<code>mitchellg.me/temperature?temperature=0&humidity=0&location=0&key=0</code>
<p>Above is the GET request that is sent to the server from the ESP8266. Data is tranferred through GET parameters. The key is an authentication code that I set to prevent unwanted HTTP requests. This is similar to an API key. Below is the code implemented to handle the HTTP request from the ESP8266.
</p>
<script src="https://gist.github.com/mgerb/4bedafa2bde264ee3135.js"></script>
<h3>Creating the REST API</h3>
<p>There are many ways that I could display the temperature information on a graph, but I was trying to come up with a quick and easy way that was also efficient. I also wanted reusability in case I wanted to add or change things down the road. I decided I was going to use <a href="http://www.chartjs.org/">Chart.js</a> because it is open source and free. These graphs are implemented on the front end using javascript. Because of this I needed to figure out a way to send the sensor data to the client side. I felt that the best way for me to do it would be creating a REST API. I plan on making another post in the future explaining all of my client side code along with how to use Chart.js.
</p>
<p>Now that I know how I want to display my information I just need to think of what information I want to display. I thought it would be cool to display a few different graphs. As of right now I have one graph that displays data by the year and one that displays by each month. The maximum and minimum temperature of each day are displayed as well as the average humidity for that day. The user can also select which year or which month to display and the graph will adjust accordingly.
</p>
<p>Now that I know what information I need I can start developing my REST API. I have a MongoDB collection called "temperature", which stores temperature, humidity, location, and time updated. Updated is a type of Date, which I will use in all of the queries to group by each day. To do the grouping in MongoDB I needed to use the <a href="https://docs.mongodb.org/v3.0/aggregation/">aggregation functionality</a> of MongoDB. Aggregation allows me to essentially perform queries on top of queries using the MongoDB "pipeline". This is similar to an SQL query when a selection is performed within a selection.
</p>
<script src="https://gist.github.com/mgerb/4879f4897f7e863e9003.js"></script>
<h3>Explanation of MongoDB Queries</h3>
<p>The first function takes in a location and year and returns maximum and minimum temperature readings as well as average humidity for each day in the selected year. The results are also returned sorted from newest to oldest.
</p>
<p>In the first part of the aggregation pipeline, which is $project, I am just selecting the temperature, humidity, year, month, and day. This part is important because it allows me to pull the year, month, and day from the date that is stored in the database. This way I do not have to store each of these entries separately in the database.
</p>
<p>This data is then passed to the next part of the aggregation pipeline. The $match pipeline stage selects out information from the collection which match with the selected year and location.
</p>
<p>The $group aggregation operator is the stage in which I actually group the data by the location, year, month, and day. For each group I also take the max and min temperatures along with the average humidity by using the correct accumulator operators. Now that I have the appropriate information I need, I just use the $sort pipeline operator to sort the data based on time updated.
</p>
<p>The function to display by month is the exact same, except I take in the month attribute and add it to the $match operator within the aggregation pipeline. Just like that the query is complete and all I need to do is send the response back to the client. Chart.js uses JSON format, which makes things extremely easy because MongoDB querie results are in JSON. I set the content type to JSON, and use JSON.stringify() to convert the JSON to a readable format for debugging purposes. An example API request can be tested out here.
</p>
<p><a href="/api/sensorbylocation/year?location=Winona%20Apartment&year=2016">/api/sensorbylocation/year?location=Winona%20Apartment&year=2016</a>
</p>
</div>

View File

@@ -1,125 +0,0 @@
<div class="blog-post">
<h2 id="title" class="blog-post-title">Hosting a Node.js Web Server on Digital Ocean</h2>
<p id="date" class="blog-post-meta">January 4, 2016 by Mitchell</p>
<p id="intro">A VPS or "Virtual Private Server" is a nice way to host a web server. Digital Ocean provides a great service based on my experiences. I recently rebuilt a new server to host this site and I will go over the entire setup process.
</p>
<hr>
<h3>Creating a Droplet</h3>
<p>Digital Ocean calls their VPS's "Droplets" and they are very simple to set up. Create an account on the site and attach a credit card. You are now ready to create a droplet. In my case I use an Ubuntu server but Digital Ocean offers a variety of linux operating systems. Select the size, location, and name and the server is up and running! You will be emailed with the login information.
</p>
<h3>Login with SSH</h3>
<p>Select your newly created droplet and navigate to console access. You will be prompted to change your password. Once the password is changed you can connect to the root user account via SSH.
</p>
<pre><code>
$ ssh root@&lt;ip address&gt;
</code></pre>
<p>If you are using a Windows machine you can download <a href="http://www.chiark.greenend.org.uk/~sgtatham/putty/download.html">putty</a> to connect with SSH. If you choose not to use SSH you can just use the console access on the Digital Ocean website.
</p>
<h3>Firewall</h3>
<p>The very next thing I do is set up the firewall. I only want to open ports that need to be open. I use the built in firewall tool in Ubuntu called UFW.
</p>
<pre><code>
$ ufw enable
</code></pre>
<p>Now that UFW is enabled I can start opening the necessary ports. I must open 22 if I want to continue to SSH into the server.
</p>
<pre><code>
$ ufw allow 22/tcp
</code></pre>
<p>This will open TCP port 22 for SSH. I also open port 80 because that is the port in which the web server will run on.
</p>
<h3>Network Monitoring</h3>
<pre><code>
$ sudo apt-get install vnstat
</code></pre>
<p>VNStat is a nice tool that will allow me to monitor the bandwidth my server consumes both outbound and inbound. Simply use the command "vnstat" to check the bandwidth. Note that it takes awhile for vnstat to capture bandwidth data if you just upon first installing it.
</p>
<h3>Terminal Multitasking</h3>
<pre><code>
$ sudo apt-get install screen
</code></pre>
<p>Screen is a really nice tool too use when dealing with multiple terminal windows. Simply use the command <code>$ screen</code> to start screen. Press CTRL>A then C to open another screen window. Navigate between screens by pressing CTRL>A then N. To check if scren is attached or detached simply use the command <code>$ screen -ls</code>. Reattach to screen by using <code>$ screen -r</code> and detach with <code>$ screen -d</code>. If screen is open and already attached, but you wish to attach to it, simply use the command <code>$ screen -d -r</code>.
</p>
<h3>NPM</h3>
<p>Now that I have the basics set up on my server I can start installing the dependencies for my Node.js web server. NPM is a package manager that is needed for Node.js. It makes things very easy when used properly.
</p>
<pre><code>
$ sudo apt-get install npm
$ sudo npm update npm -g
</code></pre>
<p>The package manager on ubuntu does not have the most recent NPM packge as of right now but it is very easy to update with NPM itself. The second command listed above will download the newest version of NPM and update itself.
</p>
<h3>Node.js</h3>
<p>Now that NPM is installed I can use it to install Node.js.
</p>
<pre><code>
$ sudo npm cache clean -f
$ sudo npm install -g n
$ sudo n stable
$ sudo ln -sf /usr/local/n/versions/node/&lt;VERSION&gt;/bin/node /usr/bin/node
</code></pre>
<p>This will install the latest version of Node.js.
</p>
<h3>MongoDB</h3>
<p>Installing MongoDB used to be very simple, but now they do not include the init scripts with the newest version. Installation instructions can be found <a href="https://docs.mongodb.org/manual/tutorial/install-mongodb-on-ubuntu/">here</a>.
</p>
<p>Once MongoDB is installed it can be run by using the command <code>$ mongod</code>, but I am going to show an easier way using the startup script. This way MongoDB will start when the VPS boots and it can also be monitored with the <code>$ service</code> linux command.
</p>
<pre><code>
$ cd /etc/init.d
$ sudo nano mongod
</code></pre>
<p>Once here copy the contents of <a href="https://github.com/mongodb/mongo/blob/master/debian/init.d">this file</a> into mongod. We now have the startup script, but we need to change some permissions first before it can run.
</p>
<pre><code>
$ sudo chmod 755 /etc/init.d/mongod
$ sudo chown root:root /etc/init.d/mongod
</code></pre>
<p>And to run MongoDB upon system startup.
</p>
<pre><code>
$ update-rc.d mongod defaults
</code></pre>
<h3>Restoring Database Files</h3>
<p>The web server is almost ready, but first I need to make sure all of my previous records are stored in the database. To do this I navigate to the folder which my records are stored in, which is contained in my git repository. I then use the command <code>$ mongorestore</code>, which loads everything from the dump into the freshly installed MongoDB. To backup MongoDB simply navigate to the desired folder to store the backup and issue the command <code>$ mongodump</code>.
</p>
</div>

View File

@@ -1,91 +0,0 @@
<div class="blog-post">
<h2 id="title" class="blog-post-title">Wifi Controlled LED Lights</h2>
<p id="date" class="blog-post-meta">October 28, 2015 by Mitchell</p>
<p id="intro">LED lights can be used for many different things. I have used LED lights in the past in my computer and even on my desk. They have always been wired to a physical switch. I now have LED lights that I can turn on and off and even change color from a simple web page that I can access with my phone.
</p>
<hr>
<p>For this project I used the ESP8266 microcontroller to connect to my wireless network, run a web server, and control an RGB LED strip based on inputs from a web form. I followed <a href="https://learn.adafruit.com/rgb-led-strips">this guide</a>, but incorporated Wifi to control the LEDs.
</p>
<p>Required Components
</p>
<p>ESP8266 <br>
12v Powersupply 1.2 Amps per meter (Keep in mind the ESP8266 can draw 200-400ma) <br>
Wiring <br>
<a href="http://www.banggood.com/10-Pcs-TIP120-NPN-TO-220-Darlington-Transistors-p-932779.html">TIP120 Transistors</a> <br>
12v to 3.3 volt converter
</p>
<br>
<p>Code can be found <a href="https://github.com/mgerb/wifi_leds/blob/master/wifi_RGB_strip.ino">here</a>.</p>
<div>
<script src="https://gist.github.com/mgerb/7858a3d20041dd94c841.js"></script>
</div>
<br>
<h3>Code</h3>
<p>Depending on which version of ESP8266 you have, this might not work. You need at least 3 GPIO pins. I used version ESP-07, which has plenty of usable GPIO pins. The code is fairly simple. Basic functionality includes connecting to my Wifi network and starting up a web server. I was able to log into my router after the ESP connected to assign it a static IP. This way I could always access the web server with the same IP address.
</p>
<p>The main program loop listens for an http request on the web server. If the cycle parameter is set to true, the color cycle function will continue to run until the server gets another HTTP request. The color cycle function happens to be a problem because it does not run asynchronously. This means that the whole color cycle must complete before it can respond to another HTTP request. This can cause delays when accessing the web page. Because this is a small personal project I didn't look into fixing this issue.
</p>
<p>All data from the web page is passed to the server through get parameters. The program parses through these parameters and checks to see if any of the colors are out of range. If they are out of range it either sets them to a max value of 250 or a min value of 0 (off). The changeColor method is called with paramters from the web form.
</p>
<h3>Changing Colors</h3>
<p>The changeColor function takes in 6 parameters; one for current color and one for each color passed from the web form. This function needs the current color because it implements the changing of colors gradually. Again, it is all synchronous so during the changing of colors the web server will not be able to respond to requests.
</p>
<h3>Cycling Colors</h3>
<p>The color cycle function is a bit more confusing than the changeColor function. This function cycles through each color once. The delay can be adjusted to how fast you would like the cycle to last. This function will continuously get called as long as the cycle boolean is true. After each cycle the server will still check for an http request. Like I mentioned above this function is synchronous so it must complete a full cycle before any other code is executed.
</p>
<p>This function is only chaning 2 RGB values at any one time. It contains two loops, one of which is nested. The first loop selects two pins to be cycled at one time: rgbColor[decColor] and rgbColor[incColor]. For example, it will first select Red and Green, then Green and Blue, and finally Blue and Red. During each selection, a nested loop is run, which cycles the color values of selected color pins. Red starts at 250 where Green starts at 0. The nested loop cycles Red to 0 while cycling Green to 250, exits the nested loop, and then proceeds to cycle Green and Blue, and finally Blue and Red.
</p>
<h3>Wiring</h3>
<p>The wiring for this project is fairly straight forward. The image below is from Adafruit's website and it illustrates the wiring for an Arduino. The ESP8266 wiring is fairly similar, but because it runs on 3.3 volts, I have used two regulators to convert 12 volts. I used a 5v and 3.3v regulator because the 3.3v regulator could not take 12v as an input. You could also just get a 12v to 3.3v converter but I used what I had on hand.
<br>
<br>
12v PSU > 5v > 3.3v > ESP8266
<br>
<br>
The power on the LED strip is wired straight to the positive connections on the 12v power supply.
</p>
<div>
<img class="img-responsive" src="https://learn.adafruit.com/system/assets/assets/000/002/692/medium800/led_strips_ledstripfet.gif?1396785823" alt="image">
</div>
<br>
<p>The image below is how I have my ESP8266 wired under my desk to control my led lights. As you can see the power comes in from the top right and the output for each RGB pin is on the left where the black is the 12v.
</p>
<div>
<img class="img-responsive" src="http://i.imgur.com/4p5CKOL.jpg" alt="image2">
</div>
<br>
<p>A picture of the final setup. I have one long LED strip along the back of the desk and two smaller strips on the back of each of the monitors.
</p>
<div>
<img class="img-responsive" src="http://i.imgur.com/aI5clZW.jpg" alt="image2">
</div>
</div>

View File

@@ -1,50 +0,0 @@
<div class="blog-post">
<h2 id="title" class="blog-post-title">Temperature Sensor on the Web</h2>
<p id="date" class="blog-post-meta">December 18, 2015 by Mitchell</p>
<p id="intro">The temperature readings that are displayed on this site come from the ESP8266 with a DHT11 sensor attached to it. I am going to explain the steps in the entire process. I will split it up into two parts. In this post I will focus on programming the ESP8266 and how to send data to a web server.
</p>
<hr>
<p><span class="colorRed">Update:</span> I have updated some information regarding this project. Refer to these other posts.
</p>
<a href="/?post=12-29-15.html">ESP8266 temperature sensor updates and difficulties</a>
<br>
<a href="/?post=1-1-2016.html">Temperatue Sensor - Server Side</a>
<br>
<br>
<p>For this project you will need an ESP8266 and a DHT temperature sensor. For prototyping purposes I prefer to use the <a href="http://www.banggood.com/NodeMcu-Lua-ESP-12E-WIFI-Development-Board-p-985891.html">NodeMCU module</a>. There are a variety of temperature sensors that you could use, but I prefer the DHT11 because it is easy to use and it is cheap! I ordered a 5 pack of them from <a href="http://www.banggood.com/5Pcs-KY-015-DHT11-Temperature-Humidity-Sensor-Module-For-Arduino-p-983263.html">Banggood</a>. The source code for this project can be found on <a href="https://github.com/mgerb/esp8266-HTTPTempSensor">Github</a>.
</p>
<h3>How it works</h3>
<p>When the ESP first boots up, it reads WiFi credentials from the EEPROM and tries to connect to an access point using those credentials. If no connection is made, it starts up it's own access point and runs a web server with a captive portal. This means that when anyone connects to it, it will redirect any dns request to itself, which forces anyone to the configuration page. The user can connect to the access point and enter in the ssid, password, and preferred sensor name. This information will be saved into EEPROM and the ESP will then reboot and start sending sensor data to the server.
</p>
<div>
<img class="img-responsive center" src="http://i.imgur.com/PhnDEyU.png" alt="image2">
</div>
<h3>ESP8266 File System</h3>
<p>To run the web server, I used the file system library within the arduino ESP library. This is how I store the web content in flash on the chip. It allows me to modify all of the necessary web files without having to deal with sending them as strings from the arduino code.
</p>
<p>To use the file system, you must download the tool <a href="https://github.com/esp8266/arduino-esp8266fs-plugin/releases/download/0.1.3/ESP8266FS-0.1.3.zip">here</a> to upload your files to the ESP. Once downloaded, store it in a tools folder in the arduino folder (path "/Arduino/tools/ESP8266FS/tool/esp8266fs.jar"). Now when you create a new sketch go to the sketch folder and create a "data" folder. To upload data to this folder select "Tools > ESP8266 Sketch Data Upload" and the files will be uploaded to the ESP and will be ready to use within the program. More information on the ESP8266 file system can be found <a href="http://esp8266.github.io/Arduino/versions/2.0.0/doc/filesystem.html">here</a>.
</p>
<h3>Sending data to the server</h3>
<p>There are many ways to send the data to a server, but I implemented data transfer with a simple get request. I chose this route because it is extremely simple to create a REST API in Node.js. I will cover this in a nother post. If you are unfamiliar with a get request, it is a way a web server can receive a request, and also parameters within that request. In this case, I send temperature, humidity, location, and a key as parameters in the request.
</p>
<p>If this were a full production application, this would not be the ideal way to handle data transfer to a server because it is not encrypted. A better practice would be to implement this using another TCP transfer protocol. In my case, it would be better to send a POST request over SSL, but I have not looked into that for this project yet as the method I am currently using seems to work just fine.
</p>
<h2>Code</h2>
<script src="https://gist.github.com/mgerb/fbed7864f8617ada797a.js"></script>
</div>

View File

@@ -1,72 +0,0 @@
<div class="blog-post">
<h2 id="title" class="blog-post-title">ESP8266 temperature sensor updates and difficulties</h2>
<p id="date" class="blog-post-meta">December 29, 2015 by Mitchell</p>
<p id="intro">I'm working on setting up new sensors now that I have been developing the back end of my website to display sensor information. Throughout this process I made updates to the current code for the project. As I continue to make progress I am overcoming obstacles as they show up.
</p>
<hr>
<p>I changed the way that the ESP8266 sends web pages to the user via HTTP requests. Previously I made use of the file system in the ESP8266, but I changed the way I did this because I noticed that it was a bit slow handling all the requests, especially because I used bootstrap to make the user interface look pretty. Although I cut down the bootstrap to only the pieces that I needed, it was still slower than I wanted.
</p>
<p>I decided to not use the file system and handle the html pages within header files. I found a fairly nice way of doing this. To do this, declare a new character array, store it in flash memory with "PROGMEM", and use the raw string literal functionality of C to escape all characters in a string as show below. This is not quite as nice a dealing with an HTML file in memory, but it is easier because you do not have to upload the files to the file system every time, which takes a good amount of time.
</p>
<code>const char page_html[] PROGMEM = R"=====(/*insert html here*/)=====";</code>
<p>This newly created page can now be sent back to the user upon an HTTP request.
</p>
<code>server.send(200, "text/html", page_html);</code>
<p>There were a few other updates that I made. I realized that if the access point the ESP was connected to goes down, it would not try to reconnect. It will now check if it is connected to the access point every time it tries to take a temperature reading and send data. If it is not connected to the access point it will reboot. Now that it reboots, if it still cannot connect to the access point it will be stuck in config mode. I fixed this by creating a timeout. If it has been sitting in config mode for 10 minutes the device will reboot.
</p>
<p>These example are show below. Keep in mind that this is not the entire program, but just the new functionality that I mentioned. The source code for the project can be found <a href="https://github.com/mgerb/esp8266-HTTPTempSensor/tree/working">here</a>.
</p>
<script src="https://gist.github.com/mgerb/61882e3676688bd4fb74.js"></script>
<h3>Problems flashing the ESP</h3>
<p>I previously used the ESP-07, but recently got some new ESP-12e's because of the higher flash size. I thought this would be great if I was going to make use of the file system. After spending hours and hours trying to flash these chips, I came to the conclusion that I need a better serial to USB programmer. The current one that I use came from China and was very cheap.
</p>
<p>With the new version of ESP Arduino software (2.0.0), you get the option of flashing with the ck mode. This requires GPI0 > DHT and RESET > RST. My FTDI programmer did not have an RTS pin, but rather a CTS. I found that I could flash one of my chips with this and the other I could not. After doing some research I found that it may be a problem with my Mac. Unfortunately my Ubuntu image that I usually dual boot with acted up and would not boot. This left me without any other operating system to try at the time.
</p>
<br>
<img class="img-responsive center" src="http://i.imgur.com/iNacOBT.jpg" alt="image">
<br>
<p>Because I was experiencing problems flashing with the new ESP8266 Arduino software, I reverted to an older version (1.6.5). In this version I was able to put GPI0 > Ground and start the ESP8266 in flash mode. This is the part where I realized I have a bad FTDI programmer. When I flash the device, it will get stuck every once in awhile. I then have to unplug everything and try again. I then tried a different programmer and it was able to work every time.
</p>
<p>After a good amount of frustration I was able to get my ESP8266 flashed with my new program and I got another temperature sensor set up. Here is the wiring that I used in the sensor. Keep in mind the DHT11 sensors that I used had a 10k resistor built into them.
</p>
<p>GND > GND</p>
<p>GPI15 > GND</p>
<p>VCC > 3.3v</p>
<p>EN (CH_PD) > 3.3v</p>
<p>GPIO5 > Sensor Data</p>
<p>The sensor VCC and Ground then were hooked up accordingly. I used an 800ma 3.3v regulator to power everything and it is working fine.</p>
<br>
<img class="img-responsive center" src="http://i.imgur.com/WrDh8oN.jpg" alt="image">
</div>

View File

@@ -1,65 +0,0 @@
<div class="blog-post">
<h2 id="title" class="blog-post-title">Flashing the ESP8266 with the Arduino IDE</h2>
<p id="date" class="blog-post-meta">May 22, 2016 by Mitchell</p>
<p id="intro">Flashing the ESP8266 with the newest version of the ESP8266/Arduino can be tedious with the off brand FTDI programmers.
</p>
<hr>
<h3>Problems with knockoff FTDI programmers</h3>
<p>The new version of ESP8266/Arduino offers different flashing methods than the previous versions, which are the CK and NodeMCU methods. These new methods allow the flashing tool to automatically reset the ESP8266 into boot loader mode. This is convenient if you have a NodeMCU or the right FTDI programmer. I have a cheap knock off FTDI programmer in which the CK flashing method does not work with.
</p>
<p>The table below shows the correct connections for using the esptool-ck method of flashing. The FTDI programmer that I use does not have the "RTS" pin. Alternatively it has a "CTS" pin. I figured the FTDI programmers were the exact same, but after attempting to flash with this method I was convinced otherwise.
</p>
<p><a href="https://github.com/igrr/esptool-ck" target="_blank">The current version of ESP8266/Arduino uses the esptool-ck flashing method</a></p>
<div class="table-responsive">
<table class="table-bordered">
<thead>
<tr>
<th>Name</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>none</td>
<td>No DTR/RTS manipulation</td>
</tr>
<tr>
<td>ck</td>
<td>RTS controls RESET or CH_PD, DTR controls GPIO0</td>
</tr>
<tr>
<td>wifio</td>
<td>TXD controls GPIO0 via PNP transistor and DTR controls RESET via a capacitor</td>
</tr>
<tr>
<td>nodemcu</td>
<td>GPIO0 and RESET controlled using two NPN transistors as in <a href="https://raw.githubusercontent.com/nodemcu/nodemcu-devkit/master/Documents/NODEMCU_DEVKIT_SCH.png">NodeMCU devkit</a>.</td>
</tr>
</tbody>
</table>
</div>
<h3>The Solution</h3>
<p>Unfortunately the current verion of ESP8266/Arduino does not offer alternative methods of flashing. After hours of frustration I finally figured out a solution to the problem.
</p>
<p>The methods to flash the ESP8266 within the Arduino IDE can be adjusted. I first thought I needed to download another method to flash with such as the <a href="https://github.com/themadinventor/esptool">esptool.py</a>, but I found a much easier solution. The "boards.txt" file can be edited, which allowed more customization to the flashing settings within the Arduino IDE. This is the path on my system.
</p>
<pre><code>/Users/$USER/Library/Arduino15/packages/esp8266/hardware/esp8266/2.1.0/boards.txt</code></pre>
<p>The reset method needs to be changed from "ck" to "none" in order for this to work properly.
</p>
<pre><code>generic.upload.resetmethod=ck</code></pre>
<p>to</p>
<pre><code>generic.upload.resetmethod=none</code></pre>
<p>Because we are not using the CK flashing method, we need to boot up the ESP8266 into boot loader mode manually.
</p>
<pre><code>
GPIO0 -> gnd
VCC -> 3.3v
CH_PD -> 3.3v
GND -> gnd
TX -> RX
RX -> TX
</code></pre>
<p>The ESP8266 must be powered by an external 3.3v power supply, because the FTDI programmer cannot supply enough current. Also keep in mind that the ground of the FTDI programmer MUST be connected to the same ground that the ESP8266 is connected to. If this is not done it will not flash.
</p>
<p>This should solve any problems flashing with a knockoff FTDI programmer.</p>
</div>

View File

@@ -1,47 +0,0 @@
<div class="blog-post">
<h2 id="title" class="blog-post-title">First Blog Post</h2>
<p id="date" class="blog-post-meta">July 21st 2015 by Mitchell</p>
<p id="intro">My first blog post introduces this website and the motives behind it. I discuss setting up a Node.js webserver for the first time and my experiences with it thus far.
</p>
<hr>
<p>I have had this site up for awhile now, but I've decided to turn it into a blog style website, more or less. I first started working on the site because I wanted to learn new web development tools, in this case, Node.js and MongoDB.
</p>
<p>I already had previous experience with SQL databases and other server side scripting languages such as PHP and JSP, but I heard good things about Node.js and I wanted to familiarize myself with a NoSQL database. At first, Node.js was a bit confusing to work with and figure everything out. Now that I am starting to get the hang of Node and MongoDB I am enjoying them more and more.
</p>
<h2>Setting up Node.js for the first time</h2>
<p>There are multiple ways to get up and running with Node. Node uses "modules" and has some that are already built in. Others need to be installed with the NPM package manager, which will become your friend. I am going focus on installing Node.js with Express, which is a web framework for Node.js.
</p>
<code>$ sudo apt-get install nodejs</code>
<br>
<code>$ sudo apt-get install npm</code>
<br>
<p>Once you have both Node.js and NPM installed, install Express and MongoDB. We are going to install and use express generator to easily create a skeleton that we can work from.
</p>
<code>$ npm install express-generator -g</code>
<br>
<p>Based on your system, installing MongoDB may be a bit different. <a href="http://docs.mongodb.org/manual/">Here is a guide on how to install.</a><p>Once express generator is installed, it is extremely simple to set up a new Node application.</p><code>$ express myapp</code><br><p>This will generate a new fold "myapp" with prebuild folders and files.
</p>
<code>$ cd myapp</code><br>
<code>$ npm install</code>
<br>
<code>$ npm start</code>
<p>Now as easy as that sounds, you have a Node web application running on your machine.
You can navigate to your web app at localhost:3000 (It's configured to use port 3000 by default)</p>
<p> Express organizes everything nicely in the <a href="https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller">Model View Controller </a>software pattern. Folders are split up into views, routes, and public files. If you are implementing a database, you would also create a "models" folder. There is also a folder which contains Node modules. When you use NPM to install new modules this is where they will be located.
</p>
<p>Node is a very powerful tool that I plan on exploring more in the future. As development of this site continues, I plan to submit more content similar to this.
</p>
</div>

View File

@@ -1,124 +0,0 @@
<div class="blog-post">
<h2 id="title" class="blog-post-title">How do I use git?</h2>
<p id="date" class="blog-post-meta">July 28 2015 by Mitchell</p>
<p id="intro">Git is a very useful file management system that you will find very handy. There are many services that host git repositories and it can also be installed on a personal server if you so choose. I'm going to explain how to set up git and discuss some useful commands and what they do.
</p>
<hr>
<p>Throughout this tutorial I am going to be focusing on <a href="http://www.github.com">Github</a>, because I find it easy to use and it is what I use most of the time. Github is just one of the many web-based git repository hosting services. It also has a nice GUI interface that may be useful to beginners, but I will mainly be focusing on git via command line. I have used both in the past and although command line may seem intimidating at first, it becomes easier and more powerful over time.
</p>
<p>What exactly does git do? Git is basically a cloud service that you can use for your coding projects. You may be wondering why use git over other cloud hosting services such as dropbox? Git is essentially the same thing, but has many built-in utilities for coding projects specifically. For example, you can see the changes between each commit (or each time you save new changes) displayed in each file. Github has a nice web interface that displays of this information. This helps with version control and it allows you to see which users have made what changes.
</p>
<h3>How to set up git</h3>
<code>$ Sudo apt-get install git</code>
<p> In linux just use the command above to install git. You will then run all git commands within the terminal. If you are on a Windows machine you can install git <a href="http://git-scm.com/download/win">here</a>. Once git is installed you can start using it immediately.
</p>
<code> $ cd Desktop && mkdir testgit && cd testgit && git init</code>
<p>Executing the above command will create a folder on the desktop and initialize a git repository. It also creates a .git folder that contains the settings of the repository. If you have already created a project and want to set it up as a git repository just simply navigate to the root directory and use <code>$ git init</code>.
</p>
<code> $ echo "testgit" >> README.md</code>
<p>You must create a readme file within the directory to be able to push to github. You can put anything in the readme, but github uses this file to display information about your repository on the main page.
</p>
<p>You must also first register with Github before you can create your own repositories. When pushing to your repository, you will be asked for your login credentials. You can globally configure these if you do not wish to be asked every time.
</p>
<code>$ git config --global user.name "John Doe"</code>
<br>
<code>$ git config --global user.email johndoe@example.com</code>
<p>Now that you have initialized your git repository, you can start on your project and begin adding files. When you wish to save your files to the repository, you must first add all of the changes. YOU MUST ADD CHANGES BEFORE YOU COMMIT!! I have commited and pushed without adding changes multiple times before so do not forget!
</p>
<code>$ git add -A</code>
<p>This will add all changes of all files in the directory. Once you have added all of the changes you can now commit.</p>
<code>$ git commit -m "First Commit"</code>
<p>Committing your changes does NOT push them to Github. It just saves all of the new changes and sets them up to be pushed. The above command will make a commit with the message "First Commit". You must have a message with each commit! The -m parameter allows you to put a message in quotes without opening it up with an editor.
</p>
<p>Now everything is ready to be pushed to Github. We first need to create a repository on Github that we can push to. You can simply do this by clicking the "New Repository" button on Github's website. Once the repository is created, we need to attach our newly created repository to it. We can do this by using the <code>remote</code> command.
</p>
<code> $ git remote add origin https://github.com/username/repositoryname.git</code>
<p>Replace "username" and "repositoryname" with your username and the name of your newly created repository and now your local repository should be attached to Github. Now we can finally push all changes to Github.
</p>
<code>$ git push origin master</code>
<p>You should be prompted to enter your username and password, unless you configured that globally before. Now, each time you want to save your changes to github, execute the follwoing commands.
</p>
<code>$ git add -A</code>
<br>
<code>$ git commit -m "Commit Message"</code>
<br>
<code>$ git push origin master</code>
<p>If you ever lose files, or want to work on this same project on another machine you can simply clone the entire repository and continue working on it from the last time you pushed. Just navite to your repository on Github's website and copy the URL.</p>
<code>$ git clone https://github.com/username/repositoryname.git</code>
<p> This will copy the entire repository into its own folder. If you implement changes on one machine and you want to update the repository on another, you can pull all of the most recent changes.
</p>
<code>$ git pull</code>
<p>Note: You must do this in the directory of the repository you wish to pull the changes.
</p>
<p>This is just a beginner guide on how to use git for the first time. There are still many other things that git has to offer, but these are the basic commands to get you started and using git.
</p>
<h3>Edit 10/27/15: More information on Git</h3>
<p>I would like to discuss a few more things about git that I have learned and become familiar with. I am hosting this website on an Ubuntu VPS from Digital Ocean and I rely heavily on git to update and make changes. I've recently added a new "working" branch to my repository. This makes it easier to work on changes while not breaking the master branch. Another branch can be made on github and you can base it off another branch. I started a "working" branch and copied the master branch.</p>
<code>$git checkout working</code>
<p>Now, after cloning my repository I can switch to my new working branch with the checkout command. Keep in mind that if I was working on my master branch with uncommited changes, I cannot checkout to another branch without commiting current changes to the master branch. After making new changes to my new "working" branch I can add, commit, and push them to Github. If and when I want to update my master branch I can checkout to my master branch, merge changes with "working" and push changes to Github. The master branch should then be up to date with the "working" branch.</p>
<code>$git add -A</code>
<br>
<code>$git commit -m "commit message"</code>
<br>
<code>$git push origin working</code>
<br>
<code>$git checkout master</code>
<br>
<code>$git merge working</code>
<br>
<code>$git push origin master</code>
<br>
<p>At any time you can check to see which branch you are working on and if any files have been updated/are ready to commit by using the status command</p>
<code>$git status</code>
<br>
<p>Now that my changes with "working" are updated on "master" I can pull the changes to my server on Digital Ocean. I sometimes have to log into my server and make minor updates to the code. This can cause merge conflicts with git and I may be unable to pull the master branch. To resolve this issue I must reset the master branch so that I can pull new changes.</p>
<code>$git reset --hard</code>
<br>
<code>$git pull</code>
<br>
<p>This will reset the branch to the last commit and get rid of any uncommited changes. Only do this if you have not made changes that you want to save. Because it is running on my server, I want to overwrite any changes anyway.</p>
<p>These are a few more git commands that I have learned since working on this project. As I become more familiar with git I will continue to update this post</p>
</div>

View File

@@ -1,64 +0,0 @@
<div class="blog-post">
<h2 id="title" class="blog-post-title">ESP8266 - Cheap Wifi Microcontroller</h2>
<p id="date" class="blog-post-meta">August 13, 2015 by Mitchell</p>
<p id="intro">I've recently started playing around with microcontrollers, or more specifically arduinos. I have always had some projects in mind that require the use of some sort of microcontroller, most of which requred a wifi connection. There are a few wifi options availabe for the arduino, some of which can be very expensive. I eventually stumbled upon the ESP8266, which I have finally figured out... mostly.
</p>
<hr>
<h3>Why the ESP8266?</h3>
<p>It is extremely cheap! They can be found for roughly $3 apiece whereas the wifi arduino shield is around $70! I figured I would take my chances with the cheaper option and I ordered <a href="http://www.banggood.com/ESP8266-Remote-Serial-Port-WIFI-Transceiver-Wireless-Module-p-947259.html">three of these</a>. After frying two of these in process of trying to figure the darn things out, I ordered a <a href="http://www.banggood.com/ESP8266-ESP-07-Remote-Serial-Port-WIFI-Transceiver-Wireless-Module-p-968190.html">5 pack of the ESP-07 version.</a> Banggood also sent me 7 of them which was a bonus.
<h3>Why the ESP-07 version?</h3>
<p>The ESP8266 can be programmed as a stand alone device with the <a href="https://github.com/esp8266/Arduino">Arduino IDE!</a> That's right, you don't even need an arduino! Also, most of the arduino libraries work with it! I went this direction after exeriencing many frustrations using the ESP8266 in conjunction with an arduino. The ESP-07 version has more GPIO pins and is about the same price, whereas the ESP-01 only has two GPIO pins, one of which much be grounded to program the device.
</p>
<h3>First experiences with the ESP8266</h3>
<p>After playing around with this microcontroller for some time now, I have concluded that it can be finicky at times. Documentation for the device can also be difficult to find, especially if you run into problems. I am going to discuss what I went through to finally get the device fully working, along with the frustrations that I experienced.
</p>
<p>When you first get the device, make sure to have solid connections to each one of the pins. You will need an FTDI programmer to connect to USB. <a href="http://www.banggood.com/FT232RL-FTDI-USB-To-TTL-Serial-Converter-Adapter-Module-For-Arduino-p-917226.html">This is the one that I have and it works fine.</a> Just make sure you have one that is 3.3v capable or it will not work! The ESP8266 can draw over 200 mA at times and the FTDI programmer simply does not provide enough power. <a href="http://www.banggood.com/3Pcs-MB102-Breadboard-Module-Adapter-Shield-3_3V5V-For-Arduino-Board-p-957095.html">This is the power supply I used.</a>
</p>
<h3>Wiring</h3>
<p>Wiring is pretty much the same across different versions of the ESP8266. Connect VCC and CH_PD to power at all times. Gnd to gnd and on the ESP-07 GPIO15 must also go to gnd. When you program the device, you must have GPIO0 to ground for programming mode. You must also connect RX on the ESP8266 to TX on the FTDI programmer and vice versa. Make sure to connect all of the power and ground wires to the power supply and NOT the FTDI programmer. The only two wires you should have connected to the FTDI programmer are RX and TX.
</p>
<h3>Testing the ESP8266</h3>
<p>To test out the device, plug it into your computer and use a serial monitor. You can use the arduino serial monitor, or you can also use putty. Make sure to select the correct COM port and the right baud rate. Baud rates can vary. I have had some that are 9600 and others have been 115200. Just change the baud rate until you see text that you can actually read. Once connected, you can change the baud rate of the ESP8266.
</p>
<p>There is a set of AT commands to communicate with the ESP8266. The documentation of these commands is descent although some commands were a bit hard to find. <a href="https://nurdspace.nl/ESP8266">Here is a list of most of the commands.</a> A few important commands that are often missed are to change the baud rate and update the firmware on the device. You can update the firmware by using AT+CIUPDATE. You can also change the baud rate by using AT+CIOBAUD=9600, or whatever baud rate you desire.
</p>
<p>These commands can be used to test the ESP8266, but they are also the same commands if you wanted to use it with an arduino. Simply wire it up to serial on an arduino and print out the commands. I played around with this for a bit, but not long enough to get it fully working. This is how I was originally going to use the device, but I found it to be much easier programming the ESP8266 directly as a standalone device.
</p>
<h3>Programming ESP8266 with Arduino IDE</h3>
<p>A major advantage of programming the ESP8266 is that it is more powerful, yet much smaller in size. It uses a 32 bit processor and also has 1mb of ram (on the newer versions). <a href="https://github.com/esp8266/Arduino">Link to Github ESP8266 Arduino IDE.</a> You can install this in your Arduino IDE by File > preferences and adding <code>http://arduino.esp8266.com/package_esp8266com_index.json</code> to Additional Boards Manager URLs. Now you can go to Sketch > Libraries > Manage Libraries and add the ESP8266 library.
</p>
<p>Select an example sketch (WIFIScan) and make sure your ESP8266 is plugged in with GPIO0 to ground! It must be to ground to enter programming mode! Select Generic ESP8266 Module for the board and the right port. It shouldn't matter which programmer is selected. Compile and upload your sketch and it is as simple as that!
</p>
<h3>Complications</h3>
<p>I just want to reiterate some of my complications that I ran into throughout this process. The biggest thing to keep in mind it to check your connections! The first time I soldered all of the connections I couldn't figure out why it wasn't working. I checked each connection with a meter only to find out one of them was bad. Another thing is to make sure you use an external power supply! Connect all power and ground connections to these! Also connect the ground of your FTDI programmer to the ground of your power supply. Make sure the voltage of your power supply and FTDI programmer are 3.3v! Remember to connect GPIO0 to ground ONLY when uploading to the ESP8266.
</p>
<h3>Sources</h3>
<a href="https://www.sparkfun.com/products/13678">https://www.sparkfun.com/products/13678</a>
<br>
<a href="http://www.esp8266.com/">http://www.esp8266.com/</a>
<br>
<a href="https://github.com/esp8266/Arduino">https://github.com/esp8266/Arduino</a>
<br>
<a href="http://www.pridopia.co.uk/pi-doc/ESP8266ATCommandsSet.pdf">http://www.pridopia.co.uk/pi-doc/ESP8266ATCommandsSet.pdf</a>
<br>
<a href="https://nurdspace.nl/ESP8266">https://nurdspace.nl/ESP8266</a>
<br>
<a href="http://www.electrodragon.com/w/ESP8266">http://www.electrodragon.com/w/ESP8266</a>
</div>

View File

@@ -1,89 +0,0 @@
<div class="resume">
<h2>Mitchell Gerber</h2>
<p>Hello, my name is Mitchell and I am pursuing a career in software engineering. I have a passion for creating things and learning new technologies.
</p>
<p>I always find enjoyment in building things, whether it's computers, drones, or software. I started programming upon entering college and I have been hooked ever since. Programming has become a hobby of mine and it has opened up many opportunities.
</p>
<p>My primary strengths include server side development in Java as well as Node.js. I have experience working with relational databases such as MySQL. I've recently become familiar working with MongoDB as well. I helped develop a web application for Digi International that is used to debug embedded devices for testing purposes. I worked with Java/JSP, which ran on a Jetty web server and used an H2 SQL database.
</p>
<p>My interest in developing things follows me home after work hours. I often lose track of time working on personal projects. I'm currently working on this personal website to learn Node.js and familiarize myself with newer web development technologies. In my free time I enjoy building and flying radio controlled unmanned aerial systems. I've also recently gained an interest in programming microcontrollers.
</p>
<div class="row text-center">
<div class="col-lg-6">
<a href="/?post=12-18-2015.html" target="_blank"><img class="img-responsive" src="/public/images/sensors.png" alt="Sensor Project"></a>
<a href="/?post=12-18-2015.html" target="_blank">Sensor Project with ESP8266</a>
</div>
<div class="col-lg-6 text-center">
<iframe width="300" height="200" src="https://www.youtube.com/embed/9re_9DdNV6Y" frameborder="0" allowfullscreen></iframe>
<br>
UAS Footage from one of my drone projects
</div>
</div>
<h2>Technical Skills</h2>
<p>I started with Java and have been working with it since the end of 2012. I consider myself experienced in Java as I have used it for numerous projects throughout college as well as at my job as a Software Tester/Developer. I found my interest in web development in late 2014. Since then I've become familiar with different back end languages including Node.js, PHP, and Java. I've also worked with MongoDB as well as relational databases such as MySQL.
</p>
<p>Beginning of Summer 2015 I started venturing into the category of microcontrollers. I've become familiar with programming the Arduino as well as a microcontroller called the ESP8266. Throughout this process I've gained knowledge of working with C.
</p>
<p>As of late 2014 I've been using Linux as my primary operating system for software development, which has allowed me to become more familiar with Unix systems. I've also worked with Ubuntu server, which is used to host this website.
</p>
<h2>Related Experience</h2>
<ul>
<li>
<span class="colorRed">2014-Present</span> Software Developer/Tester - Digi International, WSU, Winona, MN
<br>
<blockquote class="resume">
<ul>
<li>Conduct manual system tests on software.</li>
<li>Develop testing tools for debugging embedded devices</li>
<li>Develop firmware for an embedded system.</li>
<li>Participate in weekly conference calls and produce weekly status reports</li>
<li>Collaborate with others on software development projects.</li>
</ul>
</blockquote>
</li>
<li>
<span class="colorRed">2011-2014</span> Technical Assistant - South Central Education Consortium, Grand Meadow, MN
<blockquote class="resume">
<ul>
<li>Support 10-20 faculty and staff with software and hardware issues.</li>
<li>Set up and image computer labs</li>
</ul>
</blockquote>
</li>
</ul>
<h2>Education</h2>
<ul>
<li><span class="colorRed">2012-2016</span> Bachelor of Science, Magna Cum Laude
<br>
Winona State University, Winona, MN
<br>
Major: Computer Science
<br>
Major GPA: 3.68
<br>
Dean's List: 6 semesters
</li>
</ul>
<h2>Contacts</h2>
<ul>
<li><a href="mailto:mgerb42@gmail.com">mgerb42@gmail.com</a></li>
<li><a href="https://github.com/mgerb" target="_blank">GitHub</a></li>
<li><a href="https://www.linkedin.com/in/mitchell-gerber-125391b3" target="_blank">LinkedIn</a></li>
</ul>
</div>

View File

@@ -1,20 +0,0 @@
<br>
<br>
<br>
<br>
<br>
<h1 class="text-center large-text">404</h1>
<h4 class="text-center">This page may be in production, or it may not exist.</h4>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>

View File

@@ -1,26 +0,0 @@
<div class="container">
<div class="row">
<div class="col-sm-8 blog-main">
<div ng-repeat="post in posts">
<h2 class="blog-post-title">{{post.title}}</h2>
<p class="blog-post-meta">{{post.date}}</p>
<p>{{post.intro}}
<br>
<a href="/post/{{post.name}}">Continue reading...</a>
</p>
</div>
</div>
<!-- new blog posts-->
<div class="col-sm-4 blog-sidebar">
<h1 class="text-center">About Me</h1>
<img class="aboutMeImage center img-rounded" src="/public/images/aboutme.jpg">
<br>
<div class="sidebar-module sidebar-module-inset">
<p> I'm 22 years old and currently attending Winona State University as a Computer Science major. I am graduating in Spring of 2016 and plan to pursue a career in the field of software development.
</p>
</div>
</div>
</div>
</div>

View File

@@ -1,17 +0,0 @@
<div class="container">
<div class="row">
<div class="col-sm-8 blog-main">
<ng-include src="post"></ng-include>
</div>
<!-- new blog posts-->
<div class="col-sm-4 blog-sidebar">
<h1 class="text-center">About Me</h1>
<img class="aboutMeImage center img-rounded" src="/public/images/aboutme.jpg">
<br>
<div class="sidebar-module sidebar-module-inset">
<p> I'm 22 years old and currently attending Winona State University as a Computer Science major. I am graduating in Spring of 2016 and plan to pursue a career in the field of software development.
</p>
</div>
</div>
</div>
</div>

View File

@@ -1,36 +0,0 @@
<div class="container">
<br>
<h1 class="text-center" id="sensor-location">{{location}}</h1>
<hr>
<br>
<form class="text-center form-inline">
<div class="form-group">
Year:
<select class="form-control" id="option-year" ng-options="item as item.year for item in list" ng-model="selectedObject" ng-change="selectedMonth = selectedObject.months[0]; onYearChange(selectedObject);">
</select>
<img ng-show="loadingYear" src="/public/images/loading.gif" alt="loading">
</div>
</form>
<br>
<div id="canvas1-id">
<canvas class="center" id="info-chart-year" width="800" height="400"></canvas>
</div>
<br>
<form class="text-center form-inline">
<div class="form-group">
Month:
<select class="form-control" id="option-month" ng-options="month as month for month in selectedObject.months" ng-model="selectedMonth" ng-change="onMonthChange(selectedObject)">
</select>
<img ng-show="loadingMonth" src="/public/images/loading.gif" alt="loading">
</div>
</form>
<br>
<div id="canvas2-id">
<canvas class="center" id="info-chart-month" width="800" height="400"></canvas>
</div>
</div>
<br>
<br>
<div class="modal">
<!-- Place at bottom of page -->
</div>

View File

@@ -1,29 +0,0 @@
<div class="container">
<br>
<h1 class="text-center">ESP8266 Temperature Sensors</h1>
<p><span class="colorRed">Note:</span> As of 5-22-16 I have redone the entire back and front end of this website. Along with that, many database queries have been changed to be more optimized. Because of this I have gotten rid of all older data and am starting a fresh database.
</p>
<hr>
<div ng-repeat="sensorInfo in information">
<div class="col-lg-4">
<h3>{{sensorInfo.location}}</h3>
<h4>Temperature: {{sensorInfo.temperature}}°F </h4>
<span>Updated: {{sensorInfo.date}}</span>
<br>
<span class="{{sensorInfo.css}}">{{sensorInfo.status}}</span>
<br>
<br>
<a href="/sensors/{{sensorInfo.location}}">
<button class="btn btn-default">View Graph</button>
</a>
</div>
</div>
</div>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>

View File

@@ -1,107 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="description" content="Mitchell's personal site">
<meta name="keywords" content="GoLang, Node.js, Javascript, MongoDB">
<meta name="author" content="Mitchell Gerber">
<title>mitchel.io</title>
<link rel='shortcut icon' href='/public/favicon.ico' type='image/x-icon' />
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7" crossorigin="anonymous">
<link rel="stylesheet" href="/public/css/style.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.11.4/jquery-ui.min.css">
<link href='https://fonts.googleapis.com/css?family=Raleway' rel='stylesheet' type='text/css'>
<style>
[ng\:cloak],
[ng-cloak],
.ng-cloak {
display: none;
}
</style>
</head>
<script>
(function(i, s, o, g, r, a, m) {
i['GoogleAnalyticsObject'] = r;
i[r] = i[r] || function() {
(i[r].q = i[r].q || []).push(arguments)
}, i[r].l = 1 * new Date();
a = s.createElement(o),
m = s.getElementsByTagName(o)[0];
a.async = 1;
a.src = g;
m.parentNode.insertBefore(a, m)
})(window, document, 'script', 'https://www.google-analytics.com/analytics.js', 'ga');
ga('create', 'UA-78139121-1', 'auto');
ga('send', 'pageview');
</script>
<body ng-app="app" ng-init="array = [1,2,3,4];" ng-cloak>
<div class="header">
<div class="container">
<div class="lowerLeft">
<h1 class="">mitchel.io</h1>
<p class="">A site in which I share information about my personal projects.</p>
</div>
</div>
</div>
<nav class="navbar mitchell-navbar" role="navigation">
<div class="container">
<!-- Brand and toggle get grouped for better mobile display -->
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
</div>
<!-- Collect the nav links, forms, and other content for toggling -->
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav">
<li>
<a href="/">Home</a>
</li>
<li>
<a href="/sensors">Sensors</a>
</li>
<li>
<a href="/post/resume">Resume</a>
</li>
<li>
<a href="mailto:mgerb42@gmail.com">Contact</a>
</li>
</ul>
<ul class="nav navbar-nav navbar-right">
<li>
<a href="/newpost">New Post</a>
</li>
</ul>
</div>
<!-- /.navbar-collapse -->
</div>
<!-- /.container -->
</nav>
<!-- dynamically load content into the page with angular (ng-view) -->
<div ng-view></div>
<!-- ------------------------------------------------------------- -->
<footer class="blog-footer">
<p>Site created and managed by Mitchell Gerber</p>
<span>©2015-2016</span>
</footer>
</body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.0.0-beta1/jquery.min.js"></script>
<!-- <script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.11.4/i18n/jquery-ui-i18n.min.js"></script> -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-alpha/js/bootstrap.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.5/angular.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.5/angular-route.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.5/angular-resource.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.1.3/Chart.bundle.js"></script>
<script src="/public/js/app.js"></script>
<script src="/public/js/IndexController.js"></script>
<script src="/public/js/SensorsController.js"></script>
<script src="/public/js/SensorInfoController.js"></script>
</html>

View File

@@ -1,5 +1,23 @@
# Golang Site Implementation
# A new branch to rebuild this entire application using:
I have been working on converting my entire website over to the Go programming language. Along with doing this I have also converted the front end entirely to AngularJS. MongoDB is still used and the data base queries have been more optimized.
- ReactJS
- Go App Engine
I have also made this project open source. Passwords are no longer stored in the code. Rename "config-template.json" to "config.json" to start the server.
`npm run build` - builds the application using webpack and runs metadata.js
- metadata.js recursivly scans the posts folder for markdown files and then parses each into into a json object
- the posts folder is then copied into the dist folder
## TODO
- fix font sizing
- clean up css
- adjust animations
- fix goapp serve and webpack-dev-server so paths work correctly
- posts page
- add sensor page
- finally do writeups on my projects
- clean go code up
- host on app engine (run mongodb on compute engine???)
- pull everything off digital ocean
- add paging

40
server.go Normal file
View File

@@ -0,0 +1,40 @@
package main
import (
"log"
"net/http"
"strconv"
//local import paths relative to app.yaml file
"mywebsite/server/controller/api"
"mywebsite/server/db"
"mywebsite/server/route"
"mywebsite/server/utils"
)
/* for app engine
func init() {
configurations := utils.ReadConfig()
db.Configure(configurations.Database)
api.Configure(configurations.Api)
db.Mongo.Connect()
router := route.Routes()
http.Handle("/", router)
}
*/
func main(){
configurations := utils.ReadConfig()
db.Configure(configurations.Database)
api.Configure(configurations.Api)
db.Mongo.Connect()
log.Println("Starting Server...")
log.Println(http.ListenAndServe(":"+strconv.Itoa(configurations.Port), route.Routes()))
}

View File

@@ -9,8 +9,8 @@ import (
"strconv"
"time"
"github.com/mgerb42/mywebsite/model/daily_sensor"
"github.com/mgerb42/mywebsite/model/raw_sensor"
"mywebsite/server/model/daily_sensor"
"mywebsite/server/model/raw_sensor"
)
// handle http request from sensors

View File

@@ -5,6 +5,7 @@ import (
"net/http"
)
// redirects for personal use
// Redirect to discord
func DiscordRedirect(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
http.Redirect(w, r, "https://discordapp.com/invite/0Z2tzxKECEj2BHwj", 301)

66
server/db/db.go Normal file
View File

@@ -0,0 +1,66 @@
package db
import (
"gopkg.in/mgo.v2"
"log"
"time"
)
var Mongo Driver
type Driver struct {
Session *mgo.Session
Info DatabaseInfo
}
type DatabaseInfo struct {
URL string `json:"url"`
Database string `json:"database"`
Username string `json:"username,omitempty"`
Password string `json:"password,omitempty"`
}
func Configure(d DatabaseInfo) {
Mongo.Info = d
}
func (d *Driver) Connect() {
if (d.Info.URL != ""){
// Connect to MongoDB
database_info := &mgo.DialInfo{
Addrs: []string{d.Info.URL},
Database: d.Info.Database,
Timeout: 5*time.Second,
Username: d.Info.Username,
Password: d.Info.Password,
}
s, err := mgo.DialWithInfo(database_info)
if err != nil {
log.Println("MongoDB Driver Error", err)
return
}
d.Session = s
// Prevents these errors: read tcp 127.0.0.1:27017: i/o timeout
d.Session.SetSocketTimeout(10 * time.Second)
// Check if is alive
if err = d.Session.Ping(); err != nil {
log.Println("Database Error", err)
}
log.Println("Connected to database")
} else {
log.Println("Database not configured")
}
}
func (d *Driver) Connected() bool {
if d.Session != nil {
return true
}
return false
}

View File

@@ -3,11 +3,11 @@ package daily_sensor
import (
"encoding/json"
"errors"
"fmt"
"github.com/mgerb42/mywebsite/db"
"gopkg.in/mgo.v2/bson"
"log"
"time"
"mywebsite/server/db"
)
const (
@@ -31,7 +31,7 @@ func (s *Data) toJson() string {
b, err := json.MarshalIndent(s, "", " ")
if err != nil {
fmt.Println(err.Error)
log.Println(err.Error)
}
return string(b)
@@ -108,7 +108,7 @@ func GetDailySensorInfo(sensor_location string) (Data, error) {
err := c.Find(bson.M{"location": sensor_location, "day": day, "month": month, "monthname": monthname, "year": year}).One(&d)
if err != nil {
fmt.Println(err)
log.Println(err)
return d, nil
}
@@ -135,7 +135,7 @@ func GetAllSensorInfo(sensor_location string) ([]Data, error) {
{"$sort": bson.M{"year": -1, "month": 1}}}).All(&d)
if err != nil {
fmt.Println(err)
log.Println(err)
return d, nil
}
@@ -162,7 +162,7 @@ func GetAllSensorInfoByYear(sensor_location string, year int) ([]Data, error) {
{"$sort": bson.M{"year": -1, "month": 1}}}).All(&d)
if err != nil {
fmt.Println(err)
log.Println(err)
return d, nil
}
@@ -188,7 +188,7 @@ func GetAllSensorInfoByMonth(sensor_location string, year int, monthname string)
{"$sort": bson.M{"year": -1, "month": 1}}}).All(&d)
if err != nil {
fmt.Println(err)
log.Println(err)
return d, nil
}

View File

@@ -3,10 +3,11 @@ package raw_sensor
import (
"encoding/json"
"errors"
"github.com/mgerb42/mywebsite/db"
"gopkg.in/mgo.v2/bson"
"log"
"time"
"mywebsite/server/db"
)
const (
@@ -105,39 +106,29 @@ type sensorByLocation struct {
/*
func GetSensorInfoByLocation(sensor_location string) ([]DataStore_SensorByLocation, error) {
s := []DataStore_SensorByLocation{}
if db.Mongo.Connected() == true {
session := db.Mongo.Session.Copy()
defer session.Close()
c := session.DB(db.Mongo.Info.Database).C(collection)
err := c.Pipe([]bson.M{{"$project": bson.M{"location": "$location", "year": bson.M{"$year": "$updated"}, "month": bson.M{"$month": "$updated"}}},
bson.M{"$match": bson.M{"location": sensor_location}},
bson.M{"$group": bson.M{"_id": bson.M{"year": "$year", "month": "$month", "location": "$location"}}},
bson.M{"$sort": bson.M{"_id.year": -1, "_id.month": -1}}}).All(&s)
if err != nil {
log.Println(err)
return s, nil
}
return s, nil
} else {
return s, errors.New("Query failed")
}
}
*/
//********************************************************************************
/*************************
testStore := model.SensorData{
ID: bson.NewObjectId(),
Temperature: 34.2,
@@ -145,5 +136,4 @@ testStore := model.SensorData{
Location: "Grand Meadow",
Updated: time.Now(),
}
**************************/

View File

@@ -5,8 +5,8 @@ import (
"log"
"net/http"
"github.com/mgerb42/mywebsite/controller"
"github.com/mgerb42/mywebsite/controller/api"
"mywebsite/server/controller"
"mywebsite/server/controller/api"
)
func Routes() *httprouter.Router {
@@ -24,13 +24,13 @@ func Routes() *httprouter.Router {
r.GET("/discord", controller.DiscordRedirect)
r.GET("/vpn", controller.VPNRedirect)
r.GET("/camera", controller.CameraRedirect)
//set up public folder path
r.ServeFiles("/public/*filepath", http.Dir("./public"))
r.ServeFiles("/public/*filepath", http.Dir("./public/"))
//route every invalid request to template file
//routing is all handled on the client side with angular
r.NotFound = http.HandlerFunc(fileHandler("./public/view/template.html"))
r.NotFound = http.HandlerFunc(fileHandler("./public/index.html"))
return r
}

45
server/utils/config.go Normal file
View File

@@ -0,0 +1,45 @@
package utils
import (
"encoding/json"
"io/ioutil"
"log"
"os"
"mywebsite/server/controller/api"
"mywebsite/server/db"
)
//structure for application configurations
type Config struct {
Database db.DatabaseInfo `json:"database"`
Api api.ApiInfo `json:"api"`
Port int `json:"port"`
}
//read the config file and return JsonObject struct
func ReadConfig() Config {
log.Println("Reading config file...")
file, e := ioutil.ReadFile("./config.json")
if e != nil {
log.Printf("File error: %v\n", e)
os.Exit(1)
}
log.Printf("%s\n", string(file))
//m := new(Dispatch)
//var m interface{}
var result Config
err := json.Unmarshal(file, &result)
if err != nil {
log.Println(err)
}
return result
}

View File

@@ -1,12 +0,0 @@
package test
import (
"net/http"
)
// IndexGET displays the home page
func PageNotFound(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, "./public/404.html")
}

View File

@@ -1,77 +0,0 @@
package test
import (
"encoding/json"
"fmt"
"github.com/julienschmidt/httprouter"
"net/http"
)
type Person struct {
Location City
Name string
Age int
Car []Car
}
type City struct {
Coords Coordinate
Population int
}
type Coordinate struct {
Latitude int
Longitude int
}
type Car struct {
Make string
Year int
}
// IndexGET displays the home page
func IndexGet(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
params := p.ByName("test")
fmt.Println(params)
w.Header().Set("Content-Type", "application/json")
js := Person{
Location: City{
Coords: Coordinate{
Latitude: 23,
Longitude: 32,
},
Population: 5000,
},
Name: "Mitchell",
Age: 22,
Car: []Car{
Car{
Make: "Mitz",
Year: 2003,
},
Car{
Make: "Honda",
Year: 2016,
},
},
}
b, err := json.MarshalIndent(js, "", " ")
if err != nil {
fmt.Println(err.Error)
}
s := string(b)
fmt.Fprint(w, s)
}
func Api(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
param := p.ByName("name")
fmt.Fprint(w, param)
}

View File

@@ -1,13 +0,0 @@
package test
import (
"gopkg.in/mgo.v2/bson"
"time"
)
type Person struct {
ID bson.ObjectId `bson:"_id,omitempty"`
LastName string
FirstName string
Timestamp time.Time
}

View File

@@ -1,13 +0,0 @@
package test
import (
"github.com/julienschmidt/httprouter"
"net/http"
)
// Redirect to discord
func Discord(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
http.Redirect(w, r, "https://discordapp.com/invite/0Z2tzxKECEj2BHwj", 301)
}

View File

@@ -1,29 +0,0 @@
package test
import (
"encoding/json"
"fmt"
"github.com/julienschmidt/httprouter"
"net/http"
)
type ApiCall struct {
Fname string
Lname string
}
// Redirect to discord
func TestApiCall(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
s := new(ApiCall)
s.Fname = ps.ByName("fname")
s.Lname = ps.ByName("lname")
response, _ := json.MarshalIndent(s, "", " ")
w.Header().Set("Content-Type", "application/json")
fmt.Fprint(w, string(response))
}

53
webpack.config.js Normal file
View File

@@ -0,0 +1,53 @@
var debug = process.env.NODE_ENV !== "production";
var webpack = require('webpack');
var path = require('path');
var HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
devtool: debug ? "inline-sourcemap" : null,
entry: ["babel-polyfill", , "whatwg-fetch", "./client/js/app.js"],
module: {
loaders: [
{
test: /\.js?$/,
exclude: /(node_modules)/,
loader: 'babel-loader',
query: {
presets: ['react', 'es2015', 'stage-0'],
plugins: ['react-html-attrs', 'transform-class-properties', 'transform-decorators-legacy'],
}
},
{ test: /\.scss$/, loader: "style-loader!css-loader!sass-loader"},
{ test: /\.css$/, loader: "style-loader!css-loader" },
{ test: /\.png$/, loader: "url-loader?limit=100000&name=images/[hash].[ext]" },
{ test: /\.jpg$/, loader: "url-loader?limit=100000&name=images/[hash].[ext]" },
{ test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=image/svg+xml&name=images/[hash].[ext]"},
{ test: /\.woff(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=application/font-woff&name=fonts/[hash].[ext]"},
{ test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=application/font-woff&name=fonts/[hash].[ext]"},
{ test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=application/octet-stream&name=fonts/[hash].[ext]"},
{ test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, loader: "file?name=fonts/[hash].[ext]"},
{
test: require.resolve('wowjs/dist/wow.js'),
loader: 'exports?this.WOW'
}
]
},
output: {
path: __dirname + "/public/",
publicPath: "/public/",
filename: "client.min.js"
},
plugins: debug ? [] : [
new webpack.optimize.DedupePlugin(),
new webpack.optimize.OccurenceOrderPlugin(),
new webpack.optimize.UglifyJsPlugin({ mangle: false, sourcemap: false }),
],
plugins: [
new HtmlWebpackPlugin({
fileName: 'index.html',
template: 'index.html',
inject: 'body',
hash: true
})
]
};