This is part 2 of my Ethereum IOT Kid Grounding Device project. In this part, we will discuss the business logic behind Grounder.sol, the Smart Contract that does the real work behind grounding my kid. You may read part 1 here.
I like to equate Smart Contract to the business logic layer of conventional apps which developers would have developed as JSON web services in tools such as node.js (if you like JavaScript) or django (if you like Python).
This is a useful article to read if you are interested in one or more of the following concepts in Solidity Smart Contracts:
- States
- Modifiers
- Arrays
- Storing date and time and uint variables
So here goes.
The Codes
We begin with line 5.
address public parent;
parent - that's me. This variable is an address because it stores the address of the Ethereum account that will execute the Kid Grounding contract.
enum State { startGround, endGround }
State public state;
Here are lines 6 and 7. Unlike codes in conventional programs, Smart contract has the ability to maintain a state. The Kid Grounder contract has 2 states - you could start the grounding (startGround) and end the grounding (endGround). I have declared the state as a public variable so anyone who knows my contract address can see if I have finished grounding my kid.
uint public startGroundTimeStamp;
uint public endGroundTimeStamp;
uint[] public moveTimeStamp;
uint[] public callTimeStamp;
Here are lines 8 to 11. I maintain a timestamp where grounding starts, and another timestamp where grounding ends. I declared them as uint which are unsigned integers that stores up to 256 bits worth of numbers. Why not date and time you ask. Unfortunately, there isn't a time datatype in Solidity, which make sense because the notion of date and time (i.e. 12th of August 2018 at 9pm) does not exist in the Ethereum Blockchain. So date and time will be stored as unsigned integers in my Smart Contract.
moveTimeStamp and callTimeStamp are declared as arrays of uint because my kid will call for help or move herself many times during the grounding process.
event groundStarted();
event groundEnded();
event moved();
event calledforhelp();
Lines 13 to 16 declares the events that could fire when grounding starts and ends or when the kid moves or calls for help.
modifier onlyParent(){
require(msg.sender == parent);
_;
}
modifier inState(State _state) {
require(state == _state);
_;
}
Lines 18 to 26 declare 2 modifier - onlyParent() and inState(). onlyParent() says that only the sender can do THIS (we will figure what THIS is later). inState() states that the contract must be in THIS state to do this (again, we will figure what THIS is later).
constructor(uint _timeGround)
public
{
startGroundTimeStamp = _timeGround;
state = State.startGround;
parent = msg.sender;
emit groundStarted();
}
The Smart Contract's constructor is declared between lines 28 and 35. The constructor is executed when the Smart Contract is first created. To create it, you need to provide the starting date and time of this grounding. It updates startGroundTimeStamp with this date and time, sets the state to startGround. It also sets the parent variable to the address of the account that has just executed this contract. It emits groundStarted(), which fires the groundStarted() event. I didn't do anything in the groundStarted() event but with this emit feature of Solidity, you can write a JavaScript based event listener in a web UI to trigger when it hears this event and then display some messages on the web to say that a grounding session has just started.
function move(uint _timeMove)
public
onlyParent
inState(State.startGround)
{
moveTimeStamp.push(_timeMove);
emit moved();
}
function callforhelp(uint _timeCall)
public
onlyParent
inState(State.startGround)
{
callTimeStamp.push(_timeCall);
emit calledforhelp();
}
Lines 37 to 54 declare move() and callforhelp(). move() will be triggered by my IOT PIR sensor and callforhelp() will be triggered when my kid presses the Sigfox help button during her grounding.
move() accepts a uint which will provided by the caller of this function to tell the contract that the kid has just moved. Note that the modifier of the function says onlyParent and inState(State.startGround) which has been declared ealier to check if (1) this function is called by the same person who launched this smart contract and (2) if the state of this contract is startGround. This makes sense because you don't want someone else, other than the person who started this contract to initiate a move (this is not his kid!) or track his movement if the kid is no longer grounded. It pushes a new timestamp that the kid has moved to the moveTimeStamp array. It also triggers the moved() event (which I haven't done anything with). callforhelp() works the same way as move() but triggers when the kid calls for help.
function endGround(uint _timeEnd)
public
onlyParent
inState(State.startGround)
{
endGroundTimeStamp = _timeEnd;
state = State.endGround;
emit groundEnded();
}
function reGround(uint _timeGround)
public
onlyParent
inState(State.endGround)
{
startGroundTimeStamp = _timeGround;
endGroundTimeStamp = 0;
delete moveTimeStamp;
delete callTimeStamp;
state = State.startGround;
emit groundStarted();
}
endGround() and reGround() are declared from lines 55 to 76.
endGround() ends the grounding by setting an ending timestamp to the grounding and changing the state to State.endGround.
reGround() restarts a new grounding. To run reGround() the contract checks to see if the contract is in State.endGround. You can't reground a kid who's currently grounded (finish one punishment first!). It updates the Smart Contract with a new grounding time, changes the endGroundTimeStamp to 0 and clears the moveTimeStamp and callTimeStamp arrays. It also resets the state to State.startGround.
function getMoveCount()
public
constant
returns(uint moveCount)
{
return moveTimeStamp.length;
}
function getCallCount()
public
constant
returns(uint callCount)
{
return callTimeStamp.length;
}
The last 2 functions (lines 78 to 92) returns the number of times the kid moved or called for help. This is useful because it determines the quality of this punishment. In my perspective, the best kind of groundings are the ones where the kid stays really still and doesn't disturb me by calling me every 10 to 20 seconds.
Grounder in Action
To test the grounder contract, copy and paste the codes into Remix and run it in the JavaScript VM environment.
Deploy the grounder contract by providing a timestamp where grounding begins. Here, I have provided a numeric equivalent of date and time. To convert your own date and time to its numeric value, visit this page on w3schools to run the example.
Once the contract is deployed, the list of functions will become available.
Assuming that your kid moved, enter the timestamp and click [move]. As shown in part 1, this would have been triggered by the IOT PIR Sensor.
Now lets assume that the kid moved again.
And assume that he called for help. This function would have been triggered when the kid pressed his Unabell button.
You can press [getCallCount] and [getMoveCount] to see how many times the kid called for help or moved.
You can also view the timestamp where this grounding started by clicking [startGroundTimeStamp]
To view the timestamp of one of the many times he moved, enter the position of this move in the moveTimeStamp array. In this case, I am viewing the time stamp of the 0th (1st) time he moved. If you wish the view the timestamp for the second time he moved, enter 1.
To end the grounding, enter endGroundTimeStamp.
What if you have ended the ground and then attempts to execute a callForHelp()? It wouldn't work because you have had a modifier that says that a callForHelp() can only be triggered while the kid is being grounded and not after he's been released.
What's Next
In part 3 that follows, I will be walking through the node.js web service codes that trigger the creation of this smart contract as well as its function.
Photo by André Willms on Unsplash