Kristian MadunicDaniel StümkeHodyra DanielMichael KoidisA Team
Published

I.S.I - Intelligente Schwerkraft Infusion

I.S.I is an intelligent gravity infusion stand that provides information about the state of its infusion.

IntermediateWork in progressOver 8 days635
I.S.I - Intelligente Schwerkraft Infusion

Things used in this project

Hardware components

Android device
Android device
×1
Photon
Particle Photon
×1
portable weigh scale
×1

Software apps and online services

Android Studio
Android Studio
Visual Studio 2015
Microsoft Visual Studio 2015
Particle Build Web IDE
Particle Build Web IDE

Story

Read more

Code

I.S.I Webapplication

HTML
<html><head>
	<title>I.S.I. Overview</title>
	<style>
	* {
		margin: 0;
		padding: 0;
	}
	body {
		background-color: #F2EEE9;
		font: normal 13px/1.5 Arial, Serif;
		color: #333;
	}
	.wrapper {
		width: 705px;
		margin: 20px auto;
		padding: 20px;
	}
	h1 {
		color: #59c4f2;
		font-size: 20px;
		font-weight: normal;
		text-transform: uppercase;
		padding: 8px 0px;
	}
	.clear {
		clear: both;
	}
	.items {
		display: block;
	}
	.item {
		background-color: #fff;
		float: left;
		margin: 0 10px 10px 0;
		width: 205px;
		padding: 10px;
	}
	.item img {
		display: block;
		margin: auto;
		width: 170px;
		padding: 15px;
	}
	h2 {
		font-size: 13px;
		display: block;
		border-bottom: 1px solid #ccc;
		margin: 0 0 10px 0;
		padding: 0 0 5px 0;
	}
	label,
	button {
		border: 1px solid #722A1B;
		padding: 4px 14px;
		background-color: #fff;
		color: #722A1B;
		text-transform: uppercase;
		margin: 5px 0;
		font-weight: bold;
		cursor: pointer;
	}
	label {
		float: right;
	}
	img#logo {
    width: 100px;
	}

	.loader {
	  border: 16px solid #f3f3f3;
	  border-radius: 50%;
	  border-top: 16px solid #3498db;
	  width: 120px;
	  height: 120px;
    margin: auto;
	  -webkit-animation: spin 2s linear infinite; /* Safari */
	  animation: spin 2s linear infinite;
	}

		/* Safari */
		@-webkit-keyframes spin {
		  0% { -webkit-transform: rotate(0deg); }
		  100% { -webkit-transform: rotate(360deg); }
		}

		@keyframes spin {
		  0% { transform: rotate(0deg); }
		  100% { transform: rotate(360deg); }
		}

		.trigger, .disabled_trigger{
			text-transform: uppercase;
			margin-top: 10%;
			display: block;
		}

		.disabled_trigger {
			opacity: 0.5;
			pointer-events: none;
		}

		.popup__textbox{
			color: #b43d54;
			line-height: 25px;
			font-size: 1.1em;
			letter-spacing: 1px;
		}

	/* Start popup css */

	@keyframes bg {
	    from {opacity: 0;}
	    to {opacity: 1;}
	}

	@keyframes inner {
	    0% {transform: scale(0.8);}
	    50% {transform: scale(1.06);}
		100% {transform: scale(1);}
	}

	@-webkit-keyframes bg {
	    from {opacity: 0;}
	    to {opacity: 1;}
	}

	@-webkit-keyframes inner {
	    0% {transform: scale(0.8);}
	    50% {transform: scale(1.06);}
		100% {transform: scale(1);}
	}

	.popup__check{
		display: none;
	}

	.popup__base, .popup__bg{
		position: fixed;
		top: 0;
		right: 0;
		bottom: 0;
		left: 0;
		opacity: 0;
		cursor:zoom-out;
	}

	.popup__base{
		background-color: rgba(0,0,0,0.5);
		display: none;
	}

	.popup__check:checked + .popup__base{
		display: block;
		animation-name: bg;
	    animation-duration: .5s;
		animation-fill-mode:forwards;
		-webkit-animation-name: bg;
	    -webkit-animation-duration: .5s;
		-webkit-animation-fill-mode:forwards;
	}

	.popup__inner{
		position: absolute;
		z-index: 10;
		width: 70%;
		height: 70%;
		background-color: #fff;
		top: 15%;
		left: 15%;
		display: block;
		cursor:default;
	}

	.popup__check:checked + .popup__base .popup__inner{
		animation-name: inner;
	    animation-duration: .5s;
		animation-fill-mode:forwards;
		-webkit-animation-name: inner;
	    -webkit-animation-duration: .5s;
		-webkit-animation-fill-mode:forwards;
	}

	.popup__textbox{
		height: 95%;
		width: 95%;
		padding-left: 2.5%;
		padding-right: 2.5%;
		margin-top: 10px;
		overflow: auto;
	}

	.popup__calign{
		float: right;
		padding-right: 60px;
		font-size: 50px;
	}

	.popup__close{
		-webkit-transform: rotate(45deg);
		-moz-transform: rotate(45deg);
		-ms-transform: rotate(45deg);
		-o-transform: rotate(45deg);
		display: block;
		position: absolute;
		z-index: 10;
		text-align: right;
		cursor: pointer;
		color: #b43d54;
    border: none;
    padding: 0;
	}
	</style>
</head>
<body>
	<!-- wrapper -->
	<div class="wrapper">

		<img id="logo" src="logo.png"><h1>System Overview:</h1>



		<div class="clear"></div>
		<!-- items -->
		<div class="items" id="item-container">
		</div>
			<!--/ items -->
		</div>
		<!--/ wrapper -->

		<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.18.0/axios.js"></script>
		<script src="https://cdnjs.cloudflare.com/ajax/libs/bluebird/3.5.3/bluebird.js"></script>
		<script>
			let timeLimit = 90;

			let infusions = [
				{
					deviceId: '3e0022000c47353136383631',
					accessToken: '1e43056f563df6c892b932875ca1e3c03efaca75',
					location: '',
					weight: 0,
					counter: 0,
					online: null,
					initialized: null,
					ready: false,
					debug: false
				},
				{
					deviceId: '123456789012345678901234',
					accessToken: 'demo',
					location: 'Demo location 1',
					weight: 0,
					counter: Math.floor(Math.random() * 600),
					online: null,
					initialized: null,
					ready: false,
					debug: false
				},
				{
					deviceId: '098765432109876543210987',
					accessToken: 'demo',
					location: 'Demo location 2',
					weight: 0,
					counter: Math.floor(Math.random() * 600),
					online: null,
					initialized: null,
					ready: false,
					debug: false
				}
			];

			function RefreshFromCloud() {
				for(i in infusions) {
					if('demo' == infusions[i].accessToken) {
						// Debug mode
						infusions[i].debug = true;
            infusions[i].ready = true;
            infusions[i].online = true;
            infusions[i].initialized = true;
            let newCounter = infusions[i].counter + 10;
            if(newCounter >= 10*60) newCounter = 0;
            infusions[i].weight = 1;
            infusions[i].counter = newCounter;
					} else {
						// Normal mode
						axios.get ("https://api.particle.io/v1/devices/"
											+ infusions[i].deviceId
											+ "/serialized",
						{
							params:
							{
								access_token: infusions[i].accessToken
							},
							timeout: 1000
						})
					  .then ((function (i, response) {
							// Deserialize response
							console.log(response);
							if(response.data.result.length >= 25) {
								let deserialized = DeserializeResponse(response.data.result);
								infusions[i] = {...infusions[i], ...deserialized, online: true, ready: true};
								console.log("Fetch successfull");
								console.log(infusions[i]);
							}
					  }).bind(this, i))
					  .catch ((function (i, error) {
					   	infusions[i] = {...infusions[i], online: false, ready: true};
							console.log("Fetch failed");
							console.log(error);
							console.log(infusions[i]);
					  }).bind(this, i));
					}
				}
			}

			function DeserializeResponse(serialized) {
				let weight = parseFloat(serialized.substr(0,16).trim());
				let counter = parseInt(serialized.substr(16,8).trim());
				let initialized = serialized.charAt(24) == '1';
				let location = initialized ? serialized.substr(25) : "";

				return {weight, counter, initialized, location};
			}

			function RenderItems() {
				let output = "";
				let id = 1;
				for(i in infusions) {
					infusion = infusions[i];
					output += `
					<div class="item" id="infusion-item-${id}">
						<div id="infusion-${id}" style="display: none">
							<img id="infusion-image-${id}" src="infusion.png" alt="item">
							<h2>Id: <span id="infusion-id-${id}"></span></h2>
							<p>Ort: <em style="float: right"><span id="infusion-location-${id}"></span></em>
							</p>
							<p>Status: <em style="float: right"><span id="infusion-status-${id}"><span></em>
							</p>

							<label class="trigger" id="infusion-button-${id}" for="popup__${id}"></label>
						</div>
						<div class="loader" id="infusion-load-${id}"></div>
					</div>

					<input type="checkbox" id="popup__${id}" class="popup__check" />
					<div class="popup__base">
						<label for="popup__${id}" class="popup__bg"></label>
						<div class="popup__inner">
							<div class="popup__calign">
								<label for="popup__${id}" class="popup__close">+</label>
							</div>
							<div class="popup__textbox">
								<h1><span id="popup-headline-${id}"></span></h1>
								<p>
									<u>Trage hier die Werte ein:</u>
								<p>
								<p>
									Ort: <input type="Text" id="input-location-${id}"/>
								</p>
								<br/>
								<button type="button" id="input-button-${id}">bernehmen</button>
							</div>
						</div>
					</div>
					`;
					id++;
				}
				let container = document.getElementById('item-container');
				container.innerHTML = output;
			}

			RenderItems();

			function Update() {
				RefreshFromCloud();

				let id = 1;
				for(i in infusions) {
					infusion = infusions[i];
					if(infusion.ready) {
						// Hide loading animation
						let loader = document.getElementById(`infusion-load-${id}`);
						loader.style.display = "none";

						let infusionBlock = document.getElementById(`infusion-${id}`);
						infusionBlock.style.display = "block";

						let infusionId = document.getElementById(`infusion-id-${id}`);
						infusionId.innerHTML = infusion.deviceId;

						if(infusion.online) {
							let inputButton = document.getElementById(`input-button-${id}`);
							inputButton.onclick = (function(j) {
								let inputLocation = document.getElementById(`input-location-${j}`);
								let newLocation = inputLocation.value;

								if((newLocation = newLocation.trim()) != "") {
									// Send new value to device ...
									infusions[j-1] = {...infusions[j-1], location: newLocation};
									if(!infusions[j-1].debug)
										axios.post ("https://api.particle.io/v1/devices/"
															+ infusions[j-1].deviceId
															+ "/init?access_token="
															+ encodeURI(infusions[j-1].accessToken),
										`arg=${infusions[j-1].location}`,
										{
											headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
											timeout: 5000
										})
									  .then (function (response) {
									  })
									  .catch (function (error) {
									  });

										// Collapse popup
										let checkbox = document.getElementById(`popup__${j}`);
										checkbox.checked = false;
								} else {
									alert("Unvollstndige Eingabe!");
								}

							}).bind(this, id);

							if(infusion.initialized) {
									let infusionButton = document.getElementById(`infusion-button-${id}`);
									infusionButton.innerHTML = "ndern";
									infusionButton.className = "trigger";

									let popupHeadline = document.getElementById(`popup-headline-${id}`);
									popupHeadline.innerHTML = "ndern";

									let inputLocation = document.getElementById(`input-location-${id}`);
									inputLocation.placeholder = infusion.location;


									let infusionStatus = document.getElementById(`infusion-status-${id}`);
									if(infusion.counter < 60) {
					            infusionStatus.innerHTML = "Infusion luft";
					        } else {
					            let minutes = Math.floor(infusion.counter / 60);
					            if(minutes == 1) {
					                infusionStatus.innerHTML = "Seit " + minutes + " Minute leer";
					            } else {
					                infusionStatus.innerHTML = "Seit " + minutes + " Minuten leer";
					            }
					        }


									// Calculate color.
					        // Starts with green, transforms to red over yellow.
					        let red = 0, green = 0, blue = 0;
					        let maxTime = Math.floor(timeLimit*3/4); // Full red after 3 quarter of time limit
					        let time = infusion.counter - 60;
					        if(time < 0) {
					            time = 0;
					        } else if(time > maxTime) {
					            time = maxTime;
					        }
					        let f = time / maxTime; // convert to [0 ... 1]
					        let a = (1.0 - f)*2.0;
					        let x = Math.floor(a);
					        let y = Math.floor(255.0*(a-x));
					        let group = Math.floor(x); // Group: 0->red 1->yellow 2->green
					        switch(group)
					        {
					            case 0:
					                red=255;
					                green=y;
					                blue=0;
					                break;
					            case 1:
					                red=255-y;
					                green=255;
					                blue=0;
					                break;
					            case 2:
					                red=0;
					                green=255;
					                blue=y;
					                break;
					        }
									let infusionItem = document.getElementById(`infusion-item-${id}`);
									infusionItem.style.backgroundColor = `rgb(${red},${green},${blue})`;


									let infusionLocation = document.getElementById(`infusion-location-${id}`);
									infusionLocation.innerHTML = infusion.location;
							} else {
								let infusionButton = document.getElementById(`infusion-button-${id}`);
								infusionButton.innerHTML = "Einrichten";

								let popupHeadline = document.getElementById(`popup-headline-${id}`);
								popupHeadline.innerHTML = "Einrichten";

								let infusionStatus = document.getElementById(`infusion-status-${id}`);
								infusionStatus.innerHTML = "Nicht eingerichtet";

								let infusionLocation = document.getElementById(`infusion-location-${id}`);
								infusionLocation.innerHTML = "---";
							}
						} else {
							let infusionButton = document.getElementById(`infusion-button-${id}`);
							infusionButton.innerHTML = "Einrichten";
							infusionButton.className = "disabled_trigger";

							let infusionStatus = document.getElementById(`infusion-status-${id}`);
							infusionStatus.innerHTML = "Offline";

							let infusionLocation = document.getElementById(`infusion-location-${id}`);
							infusionLocation.innerHTML = "---";
						}
					}

					id++;
				}
			}

			setInterval(function() {
			  Update();
			}, 1000);

			function OfflineAnimation(colorA, colorB) {
				for(let i=0; i<infusions.length; i++) {
					if(infusions[i].ready && !infusions[i].online) {
						let id = i+1;
						let infusionItem = document.getElementById(`infusion-item-${id}`);
						infusionItem.style.backgroundColor = colorA;
					}
				}
				setTimeout(OfflineAnimation.bind(this,colorB,colorA), 700);
			}

			OfflineAnimation("rgb(150,150,150)","rgb(255,255,255)");

		</script>

	</body></html>

Photon Application

C/C++
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Neo-Circle stuff (copy from: https://github.com/MrDio/IoT-Prototyping-Framework/tree/master/IoT-Eco-NeoPIXEL_Ring_Sample)
//

/**
 * 
 * Wiring for neo-pixel:
 * 
 * PHO (D2) - NEO (Data Input)
 * PHO (GND) - NEO (GND)
 * PHO (3V3) - NEO (5V)
 * 
 */

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

#include "HX711ADC.h"
#include "application.h"
#include "math.h"

class IoTEcoSys_NeoPIXEL_Ring
{
private:

public:
    IoTEcoSys_NeoPIXEL_Ring(int pixel);
    IoTEcoSys_NeoPIXEL_Ring();
    bool init();
    int setGlRGB( String color);
    void neoRingClockWise(int circles, int del, String color);
    void neoRingFillClockWise(int showuptime, int del, String color);
    void neoRingDoubleCounterClockWise(int circles, int del, String color);
    void neoRingCounterClockWise(int circles, int del, String color);
    void changeColor(uint32_t color);
};

// IMPORTANT: Set pixel PIN, COUNT, and TYPE
// Supported pixel types: WS2812, WS2812B, WS2812B2, WS2811, TM1803, TM1829, SK6812RGBW
#define PIXEL_PIN D2
#define PIXEL_COUNT 16
#define PIXEL_TYPE SK6812RGBW
#define BRIGHTNESS 250 // 0 - 255

Adafruit_NeoPixel _ring = Adafruit_NeoPixel(PIXEL_COUNT, PIXEL_PIN, PIXEL_TYPE);
int p1[] = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16};
int p2[] = {8,9,10,11,12,13,14,15,16,0,1,2,3,4,5,6,7};
int gl_red = 0;
int gl_green = 0;
int gl_blue = 0;

// Constructor inits NeoPIXEL-Ring related to pixels (default is pin 2)
/*!
  \param pixels amount of pixels on ring
  \return obj insctance of class
*/
IoTEcoSys_NeoPIXEL_Ring::IoTEcoSys_NeoPIXEL_Ring() {
}

IoTEcoSys_NeoPIXEL_Ring::IoTEcoSys_NeoPIXEL_Ring(int pixel) {
}

//! The init function - nothing to do in this class
/*!
*/
bool IoTEcoSys_NeoPIXEL_Ring::init(){
    Serial.begin(9600);

    _ring.setBrightness(BRIGHTNESS);
    _ring.begin();
    _ring.show(); // Initialize all pixels to 'off'
    return true;
}


// Set RGB
/*!
  \param color red, green, blue, white or off
  \return int err = 0, else = 1
*/
int IoTEcoSys_NeoPIXEL_Ring::setGlRGB( String color) {
  if(color == "red") {
    gl_green = 0;
    gl_red = 255;
    gl_blue = 0;
    //ring.Color(0, 255, 0); // GRB
  }
  else if(color == "green") {
    gl_green = 255;
    gl_red = 0;
    gl_blue = 0;
    //ring.Color(255, 0, 0); // GRB
  }
  else if(color == "blue") {
    gl_green = 0;
    gl_red = 0;
    gl_blue = 255;
    //ring.Color(0, 0, 255);
  }
  else if(color == "white") {
    gl_green = 255;
    gl_red = 255;
    gl_blue = 255;
    _ring.Color(gl_green, gl_red, gl_blue, 255); // GRB+W
  }
  else if(color == "off") {
    gl_green = 0;
    gl_red = 0;
    gl_blue = 0;
    _ring.Color(gl_green, gl_red, gl_blue); // GRB+W
  }
}


// NeoPIXEL ring rotate clockwise
/*!
  \param circles amount of circles to animate
  \param del delay in ms
  \param color red, green, blue, white or off
*/
void IoTEcoSys_NeoPIXEL_Ring::neoRingClockWise(int circles, int del, String color){

    for(int c=0;c<circles;c++){
       setGlRGB("off");
       changeColor(_ring.Color(gl_green, gl_red, gl_blue)); // GRB
       _ring.show();
       for(int i=0;i<16;i++){
          setGlRGB(color);
         _ring.setPixelColor(15-p1[i],_ring.Color(gl_green, gl_red, gl_blue));
          if(i > -1){
            _ring.setPixelColor(15-(p1[i]-1),_ring.Color(0, 0, 0));
          }
          _ring.show();
          delay(del);  
      }
    }
    changeColor(_ring.Color(0, 0, 0)); // GRB
    _ring.show();
}

// NeoPIXEL ring fill clockwise
/*!
  \param showuptime time to showup in ms
  \param del delay in ms
  \param color red, green, blue, white or off
*/
void IoTEcoSys_NeoPIXEL_Ring::neoRingFillClockWise(int showuptime, int del, String color){
    changeColor(_ring.Color(0, 0, 0)); // GRB
    _ring.show();
       for(int i=0;i<16;i++){
          setGlRGB(color);
          _ring.setPixelColor(15-p1[i],_ring.Color(gl_green, gl_red, gl_blue));
          if(i > -1){
          }
          _ring.show();
          delay(del);  
      }
    delay(showuptime);
    changeColor(_ring.Color(0, 0, 0)); // GRB
    _ring.show();
}

// NeoPIXEL ring double counter clockwise
/*!
  \param circles amount of circles to animate
  \param del delay in ms
  \param color red, green, blue, white or off
*/
void IoTEcoSys_NeoPIXEL_Ring::neoRingDoubleCounterClockWise(int circles, int del, String color){

    for(int c=0;c<circles;c++){
       changeColor(_ring.Color(0, 0, 0)); // GRB
       _ring.show();
       for(int i=0;i<16;i++){
          setGlRGB(color);
          _ring.setPixelColor(p1[i],_ring.Color(gl_green, gl_red, gl_blue));
          _ring.setPixelColor(p2[i],_ring.Color(gl_green, gl_red, gl_blue));
          if(i > -1){
            setGlRGB("off");
            _ring.setPixelColor(p1[i]-1,_ring.Color(gl_green, gl_red, gl_blue));
            _ring.setPixelColor(p2[i]-1,_ring.Color(gl_green, gl_red, gl_blue));
          }
          _ring.show();
          delay(del);  
      } 
    }
    changeColor(_ring.Color(0, 0, 0)); // GRB
    _ring.show();
}

// NeoPIXEL ring double clockwise
/*!
  \param circles amount of circles to animate
  \param del delay in ms
  \param color red, green, blue, white or off
*/
void IoTEcoSys_NeoPIXEL_Ring::neoRingCounterClockWise(int circles, int del, String color){

    for(int c=0;c<circles;c++){
       changeColor(_ring.Color(0, 0, 0)); // GRB
       _ring.show();
       for(int i=0;i<16;i++){
          setGlRGB(color); 
          _ring.setPixelColor(p1[i],_ring.Color(gl_green, gl_red, gl_blue));
          if(i > -1){
            setGlRGB("off");
            _ring.setPixelColor(p1[i]-1,_ring.Color(gl_green, gl_red, gl_blue));
          }
          _ring.show();
          delay(del);  
      } 
    }
    changeColor(_ring.Color(0, 0, 0)); // GRB
    _ring.show();
}


// NeoPIXEL ring change color
/*!
  \param color color to change
*/

void IoTEcoSys_NeoPIXEL_Ring::changeColor(uint32_t color) {
  for(uint16_t i=0; i < _ring.numPixels(); i++) {
    _ring.setPixelColor(i, color);
    _ring.show();
  }
}



/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// I.S.I. Stuff goes here:
//

// HX711.DOUT	- pin #A1
// HX711.PD_SCK	- pin #A0

HX711ADC scale(A1, A0);		// parameter "gain" is ommited; the default value 128 is used by the library

double scaleValue = 0.0;    // Reading from ADC


////////////////////////////////////////////////////////////////////
// Diskreter Tiefpass Filter (FIR)
#define N_FILTER 16

const double A[]={0.001,0.005,0.02,0.04,0.07,0.1,0.1,0.1,0.1,0.1,0.1,0.07,0.04,0.02,0.005,0.001};

double filteredValue = 0.0;

double filter(double x) {
    static bool init = false;
    static double w[N_FILTER];
    
    // Initialize w's
    if(false == init) {
        for(int i=0; i<N_FILTER; i++) {
            w[i] = 0.0;
        }
        init = true;
    }
    
    for(int i=N_FILTER-1; i>0; i--) {
        w[i] = w[i-1];
    }
    
    w[0] = x;
    
    double y = 0;
    for(int i=0; i<N_FILTER; i++) {
        y += (A[i] * w[i]);
    }
    
    return y;
}


////////////////////////////////////////////////////////////////////
//  Main Program
double deltaMin = 0.01;
double deltaResume = 0.030;
int timeLimit = 90;
int counter = 0;
int initialized = 0;
String location = "";
IoTEcoSys_NeoPIXEL_Ring ring(16);

////////////////////////////////////////////////////////////////////
//  Serialization
String serialized = "0000000000000000000000000";
void SerializeData() {
    serialized = String::format("%16.5f%8d%d%s", filteredValue, counter, initialized?1:0, location.c_str());
}

////////////////////////////////////////////////////////////////////
//  Initialization via cloud
int Initialize(String argLocation) {
    location = argLocation;
    initialized = true;
    return location.length();
}

void setup() {
  SerializeData();
  Particle.variable("serialized", serialized);
  Particle.variable("weight", scaleValue);
  Particle.variable("avgWeight", filteredValue);
  Particle.variable("counter", counter);
  Particle.variable("location", location);
  Particle.function("init", Initialize);
  scale.set_scale(57000.f); // Original value was 2280. Normalized to kg
  scale.tare();	
  
  // Initialize Ring
  ring.init();  
}

unsigned long seconds = 0;

void loop() {
    if(initialized) {
        // Initialized, run state machines
        static unsigned long lastMillis = millis();
        
        if(millis() - lastMillis >= 500) {
            Particle.syncTime();
            lastMillis = millis();
            
            static int halfSeconds = 0;
            if(++halfSeconds == 2) {
                halfSeconds = 0;
                
                // Every second
                seconds++;
            }
            
            // Every 500ms
            ReadInputs();
        }
        
        // Update statemachines very often
        StateMachineCounter();
        StateMachineInfusion();
    } else {
        // Not initialized, blink light
        uint32_t color;
        
        color = (uint32_t)(255<<16) | (uint32_t)(255<<8) | (uint32_t)(255);
        ring.changeColor(color);
        
        delay(500);
        
        color = 0;
        ring.changeColor(color);
        
        delay(500);
    }
    
    
    SerializeData();
}

////////////////////////////////////////////////////////////////////
//  Input processing
void ReadInputs() {
    scaleValue = scale.get_units(5);
    filteredValue = filter(scaleValue);
    scale.power_down();		// put the ADC in sleep mode
    delay(100);
    scale.power_up();
}

////////////////////////////////////////////////////////////////////
//  State machine "counter"
void StateMachineCounter() {
	// Machine states
	static enum { INIT, COUNT } state = INIT;

	// Active transition (see docs)
	static int transition = 0,
		previousTransition = 0;

	// Global variables

	// Handle states
	switch (state) {
	case INIT:
	{
		if (previousTransition) {
			// Entry action

		}
		else {
			// Do action


			// Check transition conditions
			transition = 1;
		}


		if (transition) {
			// Exit action

		}
	}
	break;

	case COUNT:
	{
		// Local variable
		static unsigned long t;

		if (previousTransition) {
			// Entry action
			t = seconds;
		}
		else {
			// Do action


			// Check transition conditions
			if (seconds - t >= 1) {
				transition = 2;
			}
		}


		if (transition) {
			// Exit action

		}
	}
	break;
	}

#ifdef TESTBENCH
	const char *state2str[] = {
		"INIT",
		"COUNT"
	};
	if (transition) {
		PrintTimestamp();
		std::cout << "StateMachine: Counter, State: " << state2str[state] << ", Transition: " << transition << std::endl;
	}
#endif

	// Handle transition
	switch (transition) {
	case 1:
		// Init -> Count
	{
		// Perform transition action

		// Change state
		state = COUNT;
	}
	break;

	case 2:
		// Count -> Count
	{
		// Perform transition action
		counter++;

		// Change state
		state = COUNT;
	}
	break;
	}

	previousTransition = transition;

	// Deactivate transition
	transition = 0;

}

////////////////////////////////////////////////////////////////////
//  State machine "infusion"
void StateMachineInfusion() {
	// Machine states
	static enum { INIT, RUNNING, STOPPED } state = INIT;

	// Active transition (see docs)
	static int transition = 0,
		previousTransition = 0;

	// Global variables
	static double m;

	// Handle states
	switch (state) {
	case INIT:
	{
		if (previousTransition) {
			// Entry action

		}
		else {
			// Do action


			// Check transition conditions
			transition = 1;
		}


		if (transition) {
			// Exit action

		}
	}
	break;

	case RUNNING:
	{
		// Local variable

		if (previousTransition) {
			// Entry action
			m = filteredValue;
			ChangeColorByTime(0);
		}
		else {
			// Do action


			// Check transition conditions
			if (counter > 30) {
				transition = 3;
			}

			if (filteredValue - m < -1.0 * deltaMin) {
				transition = 2;
			}

			if (filteredValue - m > 0) {
				transition = 6;
			}
		}


		if (transition) {
			// Exit action

		}
	}
	break;

	case STOPPED:
	{
		// Local variables
		static unsigned long t;

		if (previousTransition) {
			// Entry action
			m = filteredValue;
			t = seconds;
		}
		else {
			// Do action
			ChangeColorByTime(seconds - t);

			// Check transition conditions
			static unsigned long t0 = seconds;
			if (seconds - t0 >= 60) {
				// Every 60 seconds
				t0 = seconds;

				if (filteredValue - m < -1.0 * deltaResume) {
					transition = 5;
				}
			}

			if (filteredValue - m > 50) {
				transition = 4;
			}
		}


		if (transition) {
			// Exit action
			counter = 0;
		}
	}
	break;

	}

#ifdef TESTBENCH
	const char *state2str[] = {
		"INIT",
		"RUNNING",
		"STOPPED"
	};
	if (transition) {
		PrintTimestamp();
		std::cout << "StateMachine: Infusion, Counter: " << counter << ", State: " << state2str[state] << ", Transition: " << transition << std::endl;
	}
#endif

	// Handle transition
	switch (transition) {
	case 1:
		// Init -> Running
	{
		// Perform transition action

		// Change state
		state = RUNNING;
	}
	break;

	case 2:
		// Running -> Running
	{
		// Perform transition action
		counter = 0;

		// Change state
		state = RUNNING;
	}
	break;

	case 3:
		// Running -> Stopped
	{
		// Perform transition action

		// Change state
		state = STOPPED;
	}
	break;

	case 4:
		// Stopped -> Running
	{
		// Perform transition action

		// Change state
		state = RUNNING;
	}
	break;

	case 5:
		// Stopped -> Running
	{
		// Perform transition action

		// Change state
		state = RUNNING;
	}
	break;

	case 6:
		// Running -> Running
	{
		// Perform transition action
		counter = 0;

		// Change state
		state = RUNNING;
	}
	break;

	}

	previousTransition = transition;

	// Deactivate transition
	transition = 0;
}

////////////////////////////////////////////////////////////////////
//  Time to color conversion
void ChangeColorByTime(int t) {
    // Calculate color.
    // Starts with green, transforms to red over yellow.
    int red = 0, green = 0, blue = 0;
    int maxTime = timeLimit*3/4; // Full red after this time
    int currentTime = t;
    if(currentTime > maxTime) {
        currentTime = maxTime;
    }
    float f = (float)currentTime / maxTime; // convert to [0 ... 1]
    float a = (/*1.f - */f)*2.f;
    float x = floor(a);
    float y = floor(255.f*(a-x));
    int group = (int)x; // Group: 0->red 1->yellow 2->green
    switch(group)
    {
        case 0:
            red=255;
            green=(int)y;
            blue=0;
            break;
        case 1:
            red=255-(int)y;
            green=255;
            blue=0;
            break;
        case 2:
            red=0;
            green=255;
            blue=(int)y;
            break;
    }
    uint32_t color = (uint32_t)(red<<16) | (uint32_t)(green<<8) | (uint32_t)(blue);
    ring.changeColor(color);
}

Filter Photon

C/C++
#include <stdio.h>

double scaleValue = 0.0;    // Reading from adc (scale)

// Diskreter Tiefpass Filter
#define N_FILTER 16

const double A[]={0.001,0.005,0.02,0.04,0.07,0.1,0.1,0.1,0.1,0.1,0.1,0.07,0.04,0.02,0.005,0.001};

double filteredValue = 0.0;

double filter(double x) {
    static double w[N_FILTER] = {
        0.0,
        0.0,
        0.0,
        0.0,
        0.0,
        0.0,
        0.0,
        0.0,
        0.0,
        0.0,
        0.0,
        0.0,
        0.0,
        0.0,
        0.0,
        0.0
    };
    
    for(int i=N_FILTER-1; i>0; i--) {
        w[i] = w[i-1];
    }
    
    w[0] = x;
    
    double y = 0;
    for(int i=0; i<N_FILTER; i++) {
        y += (A[i] * w[i]);
    }
    
    return y;
}

#include "mex.h"

void mexFunction( int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]){
	int samples = mxGetNumberOfElements(prhs[0]);
    double *x = mxGetData(prhs[0]);
    double *y = malloc(sizeof(double) * samples);
    
    
    for(int i=0; i<samples; i++) {
        y[i] = filter(x[i]);
        mexPrintf("%f => %f\n", x[i], y[i]);
    }
    
    plhs[0] = mxCreateDoubleMatrix(samples, 1, mxREAL);
    memcpy(mxGetPr(plhs[0]), y, samples * sizeof(double));
    free(y);
}

I.S.I Monitor

Android App

Credits

Kristian Madunic

Kristian Madunic

1 project • 1 follower
Daniel Stümke

Daniel Stümke

2 projects • 1 follower
Hodyra Daniel

Hodyra Daniel

1 project • 0 followers
Michael Koidis

Michael Koidis

1 project • 0 followers
A Team

A Team

1 project • 0 followers

Comments

Add projectSign up / Login