Introduction: Candy Claw Machine With Online Monitoring

Candy Claw Machine

In this project, we will be making a small candy claw machine connected to tingg.io platform. The 3D printed claw can be controlled with a joystick and a couple buttons in all 3 dimensions. It is quite light and easy to transport. If you are interested to see how it came to life, then keep on reading!

Step 1: List of Materials

The housing:

  • Spruce studs for the frame (28*28mm)
  • Poplar plywood sheets for the floor, and the side walls (6mm)
  • PET-G transparent sheet for the “windows” (1mm)
  • Acrylic paint & a brush
  • Screws

The grabber:

  • 1x Makeblock XY Plotter Robot Kit V2.0 (for the X&Y axis)
  • 1x Makeblock 42BYG stepper motor, 42x42mm (for the Z axis)
  • Spool made of Lego: 2x 24-tooth wheels and some connectors for the cord
  • 1x Mason cord
  • 1x MG90S metal transmission servo motor to operate the claw
  • 1x 3D printed claw - https://www.thingiverse.com/thing:7109
  • 1x Makeblock Versatile Motor Bracket (mount) 61x27x3mm to mount the claw to the plotter

The control box:

  • Hikig LED DIY Arcade Kit:
    6x 30 mm LED Buttons,
    1x 8-Way Joystick
    1x Zero Delay USB Encoder for Mame Jamma PC and Raspberry Pi Games*
    [in our machine we had problems with the PCB that came with this kit out-of-the-box, so we had to solder our own PCB to connect the buttons directly to the GPIOs of the RPi]
  • Box made of poplar plywood

Electronics

  • 1x Arduino/ATmega328/Makeblock BaseBoard Orion (included in the Makeblock XY Kit)
  • 1x Raspberry Pi (for the readings of the buttons and the connection to tingg.io)

***TIPS:

  1. We found out that the servo with the plastic transmission we used in the prototype of the claw worn off very fast, so we suggest using a servo with a metal transmission wheel.
  2. There are many 3d printed claw project available on Thingiverse, after testing a few, we found out the one linked is the best for grabbing candy, since the bigger surface of the claw suits more for grabbing candy firmly.

Step 2: Hardware Connection

USB Connection from Raspberry Pi to the Arduino/Makeblock BaseBoard Orion
Connection from Raspberry Pi GPIOs to Joystick/Buttons:

  • JOYSTICK_UP: GPIO20
  • JOYSTICK_RIGHT: GPIO26
  • JOYSTICK_DOWN: GPIO21
  • JOYSTICK_LEFT: GPIO16
  • BUTTON_UP: GPIO12
  • BUTTON_DOWN: GPIO19
  • BUTTON_OPEN: GPIO13
  • BUTTON_CLOSE: GPIO6
  • BUTTON_RESET: GPIO5
  • BUTTON_STOP: GPIO24

Connections from the Arduino to the stepper motors, the servo, the limiter switches for all three axis

Step 3: Programming

C-Software to control the XYZ stepper motors and the servo for the crawl => written for Arduino (ATmega328 which is attached to the Makeblock BaseBoard Orion)

#include "ServoTimer2.h" // https://github.com/nabontra/ServoTimer2
#include "TimerOne.h" // https://playground.arduino.cc/Code/Timer1/ #include "candyXyPlotter.h" // ################################### // general definitions #define FALSE 0 #define TRUE 1 // left / down / close (L/D/C) must be 0 and right / up / open (R/U/O) must be 1 for the for the bitshifting in the "do_step" function #define MOVEMENT_DIR_L_D_C 0 #define MOVEMENT_DIR_R_U_O 1 #define MOVEMENT_DISABLED 2 #define LIMIT_NO_ERROR 0 #define LIMIT_ERROR_MAX 1 #define LIMIT_ERROR_MIN 2 #define REACHED_NOTHING 0 #define REACHED_TARGET 1 #define REACHED_MAX 2 #define REACHED_MIN 3 #define REACHED_MIN_SW 4 // ################################### // Settings // CAUTION!! YOU CAN RUN INTO MAX IF YOU DONT INIT THE STEPPERS, BECAUSE THE MACHINE ZERO POSITION IS THEN WHERE YOU START #define INIT_STEPPERS TRUE #define ENABLE_Z_AXIS TRUE #define ENABLE_Z_SERVO TRUE #define DEBUG_OUTPUTS FALSE // if you choose to litte values (under 90) it can happen, that the serial interface could not be processed completely in the main-loop // and probably the stop command can't reach the controller and you can't control it from the joystick/RPi anymore (connected via serial) #define DEFAULT_STEP_SPEED_IN_MICROSEC 90 #define SLOW_STEP_SPEED_IN_MICROSEC 500 #define FAST_STEP_SPEED_IN_MICROSEC 30 #define SERVO_STEP_SIZE 1 #define SERVO_MIN_POS 1300 #define SERVO_MAX_POS 2250 #define SERVO_START_POS (int)(SERVO_MIN_POS + ((SERVO_MAX_POS - SERVO_MIN_POS)/2)) // A7 is probably not working #define X_STEP_PIN 10 #define X_DIR_PIN 11 //#define X_ENABLE_PIN 4 #define X_MIN_PIN 12 #define Y_STEP_PIN 9 #define Y_DIR_PIN 3 //#define Y_ENABLE_PIN 4 #define Y_MIN_PIN 13 #define Z_STEP_PIN 2 #define Z_DIR_PIN 8 //#define Z_ENABLE_PIN 4 #define Z_MIN_PIN A2 #define Z_SERVO_PIN A3 #define X_MOTOR_STEPS 16L #define X_STEPS_PER_MM 21.168 * X_MOTOR_STEPS //#define X_MAX_POS_MM 360L //#define X_MAX_STEPS X_MAX_POS_MM * X_STEPS_PER_MM // => X_MAX_STEPS = 121927 // => X_MAX_COUNTER_3 = floor(X_MAX_STEPS / 256 / 256) // => X_MAX_COUNTER_2 = floor((X_MAX_STEPS - (X_MAX_COUNTER_3 * 256 * 256)) / 256 ) // => X_MAX_COUNTER_1 = X_MAX_STEPS - (X_MAX_COUNTER_3 * 256 * 256) - (X_MAX_COUNTER_2 * 256) #define X_MAX_COUNTER_3 1 #define X_MAX_COUNTER_2 220 #define Y_MOTOR_STEPS 16L #define Y_STEPS_PER_MM 21.168 * Y_MOTOR_STEPS // without Z_END_SWITCH attached: 395L //#define Y_MAX_POS_MM 370L //#define Y_MAX_STEPS Y_MAX_POS_MM * Y_STEPS_PER_MM // => Y_MAX_STEPS = 121927 // => Y_MAX_COUNTER_3 = floor(Y_MAX_STEPS / 256 / 256) // => Y_MAX_COUNTER_2 = floor((Y_MAX_STEPS - (Y_MAX_COUNTER_3 * 256 * 256)) / 256 ) // => Y_MAX_COUNTER_1 = Y_MAX_STEPS - (Y_MAX_COUNTER_3 * 256 * 256) - (Y_MAX_COUNTER_2 * 256) #define Y_MAX_COUNTER_3 1 #define Y_MAX_COUNTER_2 235 #define Z_MOTOR_STEPS 16L #define Z_STEPS_PER_MM 13.5 * Z_MOTOR_STEPS //#define Z_MAX_POS_MM 600L //#define Z_MAX_STEPS Z_MAX_POS_MM * Z_STEPS_PER_MM // => Z_MAX_STEPS = 121927 // => Z_MAX_COUNTER_3 = floor(Z_MAX_STEPS / 256 / 256) // => Z_MAX_COUNTER_2 = floor((Z_MAX_STEPS - (Z_MAX_COUNTER_3 * 256 * 256)) / 256 ) // => Z_MAX_COUNTER_1 = Z_MAX_STEPS - (Z_MAX_COUNTER_3 * 256 * 256) - (Z_MAX_COUNTER_2 * 256) #define Z_MAX_COUNTER_3 1 #define Z_MAX_COUNTER_2 250 // ToDo #define Z_MIN_POS_MM 150L #define Z_MIN_STEPS Z_MIN_POS_MM * Z_STEPS_PER_MM #define COMMAND_SIZE 32 #define MAX_COUNTER_VAL 255 // ################################### // automatic definitions #if ENABLE_Z_AXIS #define STEPMOTOR_AMOUNT 3 #else #define STEPMOTOR_AMOUNT 2 #endif #define STEPARRAY_EL_X 0 #define STEPARRAY_EL_Y 1 #define STEPARRAY_EL_Z 2 // ################################### struct stepperAttr { char coordinateName; short move_and_dir; short counted_steps_1; short counted_steps_2; short counted_steps_3; float currentPosInMm; float targetPosInMm; short maxSteps_counter_3; short maxSteps_counter_2; double stepsPerMm; short step_pin; short dir_pin; //short enable_pin; short switch_min_pin; }; volatile stepperAttr stepperAttrArray[STEPMOTOR_AMOUNT]; char commands[COMMAND_SIZE]; byte serial_count; long no_data = 0; byte limit_error = LIMIT_NO_ERROR; char writeSerialBuffer[50]; int slowDownCounter = 0; #if ENABLE_Z_SERVO ServoTimer2 servo; int servoCurrentPos = SERVO_START_POS; int servoStatus = MOVEMENT_DISABLED; #endif // ########################## void setup() { //Do startup stuff here Serial.begin(115200); init_steppers(); // init Timer interrupt for stepper control Timer1.initialize(SLOW_STEP_SPEED_IN_MICROSEC); // interrupt will go off every x microseconds Timer1.attachInterrupt(do_step); #if INIT_STEPPERS goto_machine_zero(); #else Serial.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); Serial.println("!! CAUTION - INITIALIZATION IS DISABLED!"); Serial.println(" YOU CAN RUN INTO MAX IF YOU DON'T INIT THE STEPPERS! "); Serial.println(" (BECAUSE THE MACHINE ZERO POSITION IS NOW WHERE YOU STARTED) "); Serial.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); #endif #if ENABLE_Z_SERVO servo.attach(Z_SERVO_PIN); servo.write(servoCurrentPos); #endif } void loop() { char c; // read in characters if we got them. if (Serial.available() > 0) { c = Serial.read(); no_data = 0; // newlines are ends of commands. if (c != '\n') { commands[serial_count] = c; serial_count++; } } else { no_data++; // if theres a pause or we got a real command, do it if (serial_count && (c == '\n' || no_data > 100)) { // process our command! process_string(commands, serial_count); // clear command init_process_string(); } // no data? turn off all movements after awhile if (no_data > 500000L) { stop_all_movements(); no_data = 0; #if DEBUG_OUTPUTS Serial.println("No movements for a long time: stop steppers and servo!"); #endif } if (limit_error != LIMIT_NO_ERROR) { if (limit_error == LIMIT_ERROR_MAX) { Serial.println("!! counter limit error - above 256**3 !!"); } else if (limit_error == LIMIT_ERROR_MIN) { Serial.println("!! counter limit error - underneath 0 !!"); } limit_error = LIMIT_NO_ERROR; } } // check if we reached something and stop // also send the current position to the RPi for tingg.io for (short el = 0; el < STEPMOTOR_AMOUNT; el++) { short tmp = reached_target_or_end(el); // ToDo: add some more operations like set to zero when reaching End-switch etc if (tmp == REACHED_MAX || tmp == REACHED_MIN_SW) { stepperAttrArray[el].move_and_dir = MOVEMENT_DISABLED; #if DEBUG_OUTPUTS if (tmp == REACHED_MAX) { sprintf(writeSerialBuffer,"!! reached max for %c !!", stepperAttrArray[el].coordinateName); Serial.println(writeSerialBuffer); } else if (tmp == REACHED_MIN_SW) { sprintf(writeSerialBuffer,"!! reached min switch for %c !!", stepperAttrArray[el].coordinateName); Serial.println(writeSerialBuffer); } #endif } } // send current positions (without tiny changes in counter_1) to RPi #if ENABLE_Z_AXIS if (slowDownCounter % 15000 == 0) { sprintf(writeSerialBuffer, "x%d;y%d;z%d", (stepperAttrArray[STEPARRAY_EL_X].counted_steps_3 * 256) + stepperAttrArray[STEPARRAY_EL_X].counted_steps_2, (stepperAttrArray[STEPARRAY_EL_Y].counted_steps_3 * 256) + stepperAttrArray[STEPARRAY_EL_Y].counted_steps_2, (stepperAttrArray[STEPARRAY_EL_Z].counted_steps_3 * 256) + stepperAttrArray[STEPARRAY_EL_Z].counted_steps_2); Serial.println(writeSerialBuffer); slowDownCounter = 0; delayMicroseconds(1); } slowDownCounter++; #endif #if ENABLE_Z_SERVO // check if the servo position should change if (servoStatus != MOVEMENT_DISABLED) { if (!servo.attached()) servo.attach(Z_SERVO_PIN); if (servoStatus == MOVEMENT_DIR_L_D_C) { if (servoCurrentPos < SERVO_MAX_POS) { servoCurrentPos = servoCurrentPos + SERVO_STEP_SIZE; servo.write(servoCurrentPos); delayMicroseconds(500); // waits for the servo to get there } else servoStatus = MOVEMENT_DISABLED; } else if (servoStatus == MOVEMENT_DIR_R_U_O) { if (servoCurrentPos > SERVO_MIN_POS) { servoCurrentPos = servoCurrentPos - SERVO_STEP_SIZE; servo.write(servoCurrentPos); delayMicroseconds(500); // waits for the servo to get there } else servoStatus = MOVEMENT_DISABLED; } } #endif } void init_steppers() { // X stepperAttrArray[STEPARRAY_EL_X].coordinateName = 'X'; stepperAttrArray[STEPARRAY_EL_X].move_and_dir = MOVEMENT_DISABLED; stepperAttrArray[STEPARRAY_EL_X].currentPosInMm = 0.0; stepperAttrArray[STEPARRAY_EL_X].targetPosInMm = 0.0; stepperAttrArray[STEPARRAY_EL_X].counted_steps_1 = 0; stepperAttrArray[STEPARRAY_EL_X].counted_steps_2 = 0; stepperAttrArray[STEPARRAY_EL_X].counted_steps_3 = 0; stepperAttrArray[STEPARRAY_EL_X].maxSteps_counter_3 = X_MAX_COUNTER_3; stepperAttrArray[STEPARRAY_EL_X].maxSteps_counter_2 = X_MAX_COUNTER_2; stepperAttrArray[STEPARRAY_EL_X].stepsPerMm = X_STEPS_PER_MM; stepperAttrArray[STEPARRAY_EL_X].step_pin = X_STEP_PIN; stepperAttrArray[STEPARRAY_EL_X].dir_pin = X_DIR_PIN; //stepperAttrArray[STEPARRAY_EL_X].enable_pin = X_ENABLE_PIN; stepperAttrArray[STEPARRAY_EL_X].switch_min_pin = X_MIN_PIN; // Y stepperAttrArray[STEPARRAY_EL_Y].coordinateName = 'Y'; stepperAttrArray[STEPARRAY_EL_Y].move_and_dir = MOVEMENT_DISABLED; stepperAttrArray[STEPARRAY_EL_Y].currentPosInMm = 0.0; stepperAttrArray[STEPARRAY_EL_Y].targetPosInMm = 0.0; stepperAttrArray[STEPARRAY_EL_Y].counted_steps_1 = 0; stepperAttrArray[STEPARRAY_EL_Y].counted_steps_2 = 0; stepperAttrArray[STEPARRAY_EL_Y].counted_steps_3 = 0; stepperAttrArray[STEPARRAY_EL_Y].maxSteps_counter_3 = Y_MAX_COUNTER_3; stepperAttrArray[STEPARRAY_EL_Y].maxSteps_counter_2 = Y_MAX_COUNTER_2; stepperAttrArray[STEPARRAY_EL_Y].stepsPerMm = Y_STEPS_PER_MM; stepperAttrArray[STEPARRAY_EL_Y].step_pin = Y_STEP_PIN; stepperAttrArray[STEPARRAY_EL_Y].dir_pin = Y_DIR_PIN; //stepperAttrArray[STEPARRAY_EL_Y].enable_pin = Y_ENABLE_PIN; stepperAttrArray[STEPARRAY_EL_Y].switch_min_pin = Y_MIN_PIN; // Z #if ENABLE_Z_AXIS stepperAttrArray[STEPARRAY_EL_Z].coordinateName = 'Z'; stepperAttrArray[STEPARRAY_EL_Z].move_and_dir = MOVEMENT_DISABLED; stepperAttrArray[STEPARRAY_EL_Z].currentPosInMm = 0.0; stepperAttrArray[STEPARRAY_EL_Z].targetPosInMm = 0.0; stepperAttrArray[STEPARRAY_EL_Z].counted_steps_1 = 0; stepperAttrArray[STEPARRAY_EL_Z].counted_steps_2 = 0; stepperAttrArray[STEPARRAY_EL_Z].counted_steps_3 = 0; stepperAttrArray[STEPARRAY_EL_Z].maxSteps_counter_3 = Z_MAX_COUNTER_3; stepperAttrArray[STEPARRAY_EL_Z].maxSteps_counter_2 = Z_MAX_COUNTER_2; stepperAttrArray[STEPARRAY_EL_Z].stepsPerMm = Z_STEPS_PER_MM; stepperAttrArray[STEPARRAY_EL_Z].step_pin = Z_STEP_PIN; stepperAttrArray[STEPARRAY_EL_Z].dir_pin = Z_DIR_PIN; //stepperAttrArray[STEPARRAY_EL_Z].enable_pin = Z_ENABLE_PIN; stepperAttrArray[STEPARRAY_EL_Z].switch_min_pin = Z_MIN_PIN; #endif // turn them off to start //disable_all_steppers(); for (short i = 0; i < STEPMOTOR_AMOUNT; i++) { pinMode(stepperAttrArray[i].step_pin, OUTPUT); // Step-Pin pinMode(stepperAttrArray[i].dir_pin, OUTPUT); // Dir-Pin //pinMode(stepperAttrArray[i].enable_pin, OUTPUT); // Enable-Pin pinMode(stepperAttrArray[i].switch_min_pin, INPUT_PULLUP); // Min-Limiter-Switch-Pin } } /* void enable_stepper(short stepperAttrArrayEl) { digitalWrite(stepperAttrArray[stepperAttrArrayEl].enable_pin, HIGH); // ToDo: enable pin is not attached to the motor driver thru the telefon cable } void disable_stepper(short stepperAttrArrayEl) { digitalWrite(stepperAttrArray[stepperAttrArrayEl].enable_pin, LOW); // ToDo: enable pin is not attached to the motor driver thru the telefon cable stepperAttrArray[stepperAttrArrayEl].move_and_dir = MOVEMENT_DISABLED; } void disable_all_steppers() { for (short el = 0; el < STEPMOTOR_AMOUNT; el++) {


Python3-Software for the tingg.io connection, the readings of the state of the joystick and buttons, the communication and controlling of the XYZ plotter by a serial connection to the Arduino-Board is written in Python3 for the Raspberry Pi

#!/usr/bin/env python<br># coding: utf8
# Prerequirements
# pip3 install paho-mqtt
import sys
if sys.version_info[0] < 3:
    raise Exception("Please use Python3 !")
####################
import os
import serial
import time
import RPi.GPIO as GPIO
import threading
import datetime
import json
import paho.mqtt.client as mqtt
import math
####################
# TINGG.IO SETTINGS
MQTT_IP_URL = "mqtt.tingg.io"
MQTT_PORT = 1883
TINGG_MQTT_DEVICE_ID = "hdjs7854-fuu9-4989-bjd9-ba68sjhjqwe02f"
TINGG_MQTT_USER = "thing"
TINGG_MQTT_PASSWORD = "c23x5fo6bkqwhekjwe897987zytlu0p"
####################
# SETTINGS
DEBUG_MODE = True
PATH = '/home/pi/CandyMachine/RPi_Firmware/'
interval = 0.05           # Time between keyboard updates in seconds, smaller responds faster but uses more processor time
# Define some colors
BLACK  = (   0,   0,   0)
WHITE  = ( 255, 255, 255)
GPIO_JOYSTICK_UP     = 20
GPIO_JOYSTICK_RIGHT  = 26
GPIO_JOYSTICK_DOWN   = 21
GPIO_JOYSTICK_LEFT   = 16
GPIO_BUTTON_UP       = 12
GPIO_BUTTON_DOWN     = 19
GPIO_BUTTON_OPEN     = 13
GPIO_BUTTON_CLOSE    = 6
GPIO_BUTTON_RESET    = 5
GPIO_BUTTON_STOP     = 24
GPIO_BUTTON_SPARE_1  = 23
GPIO_BUTTON_SPARE_2  = 27
GPIO_BOUNCETIME_MS   = 25
####################
# GENERAL DEFINITONS
# inverted, becauso of Pull-Up and connected to GND when pressed
BUTTON_PRESSED     = 0
BUTTON_RELEASED    = 1
####################
# CLASSES
#  https://raspberrypi.stackexchange.com/questions/7...
class ButtonHandler(threading.Thread):
  def __init__(self, pin, func, edge='both', bouncetime=200):
    super().__init__(daemon=True)
    self.edge = edge
    self.func = func
    self.pin = pin
    self.bouncetime = float(bouncetime)/1000
    self.lastpinval = GPIO.input(self.pin)
    self.lock = threading.Lock()
  def __call__(self, *args):
    if not self.lock.acquire(blocking=False):
      return
    t = threading.Timer(self.bouncetime, self.read, args=args)
    t.start()
  def read(self, *args):
    pinval = GPIO.input(self.pin)
    if ( ((pinval == BUTTON_PRESSED and self.lastpinval == BUTTON_RELEASED) and
         (self.edge in ['falling', 'both'])) or
        ((pinval == BUTTON_RELEASED and self.lastpinval == BUTTON_PRESSED) and
         (self.edge in ['rising', 'both'])) ):
      self.func(pinval, *args)
    self.lastpinval = pinval
    self.lock.release()
####################
# FUNCTIONS
# Button functions
def cb_debounced_jskUP(pinval, *args):
  if pinval == BUTTON_PRESSED:
    ser.write("DX-1\r\n".encode('utf-8'))
  else:
    ser.write("DX0\r\n".encode('utf-8'))
def cb_debounced_jskDOWN(pinval,*args):
  if pinval == BUTTON_PRESSED:
    ser.write("DX+1\r\n".encode('utf-8'))
  else:
    ser.write("DX0\r\n".encode('utf-8'))
def cb_debounced_jskLEFT(pinval,*args):
  if pinval == BUTTON_PRESSED:
    ser.write("DY-1\r\n".encode('utf-8'))
  else:
    ser.write("DY0\r\n".encode('utf-8'))
    
def cb_debounced_jskRIGHT(pinval,*args):
  if pinval == BUTTON_PRESSED:
    ser.write("DY+1\r\n".encode('utf-8'))
  else:
    ser.write("DY0\r\n".encode('utf-8'))
def cb_debounced_btnUP(pinval,*args):
  if pinval == BUTTON_PRESSED:
    ser.write("DZ-1\r\n".encode('utf-8'))
  else:
    ser.write("DZ0\r\n".encode('utf-8'))
def cb_debounced_btnDOWN(pinval,*args):
  if pinval == BUTTON_PRESSED:
    ser.write("DZ+1\r\n".encode('utf-8'))
  else:
    ser.write("DZ0\r\n".encode('utf-8'))
def cb_debounced_btnOPEN(pinval,*args):
  if pinval == BUTTON_PRESSED:
    ser.write("C-1\r\n".encode('utf-8'))
  else:
    ser.write("C0\r\n".encode('utf-8'))
def cb_debounced_btnCLOSE(pinval,*args):
  if pinval == BUTTON_PRESSED:
    ser.write("C+1\r\n".encode('utf-8'))
  else:
    ser.write("C0\r\n".encode('utf-8'))
def cb_debounced_btnSTOP(pinval,*args):
  if pinval == BUTTON_PRESSED:
    ser.write("S\r\n".encode('utf-8')) # stop
def cb_debounced_btnRESET(pinval,*args):
  if pinval == BUTTON_PRESSED:
    # dont go to Home when already there
    if x != 0 or y != 0 or z != 0:
      ser.write("H\r\n".encode('utf-8')) # go to home (position 0,0,0)
  
# other functions
def observeGoToHomeProcessTillEndAndInitData():
  print("Wait for XYZ-plotter to get to position 0,0,0")
  while True: # check serial input for "start" to know taht the xy-plotter is ready
    read_data = ser.readline().rstrip('\r\n'.encode('utf-8')).decode('utf-8')
    if read_data != "":
      print(read_data)
      if read_data == "GoToHome done":
        break
  print("XY-Plotter is ready!")
  # init values
  x = 0
  x_prev = 0
  y = 0
  y_prev = 0
  z = 0
  z_prev = 0
  db["numberOfSessionsPlayed"] += 1 # increase the session
  distanceTravelledInASession = 0
  # Log preparation
  if not os.path.isdir(PATH + 'logs'):
    os.makedirs(PATH + 'logs')
  log_path = os.path.join(PATH + 'logs', 'log%s.csv' % db["numberOfSessionsPlayed"])
  header = "date time x y z distanceTravelledInASession overallDistanceTravelledInMm"
  with open(log_path, 'a') as log_file:
    log_file.write(header + "\n")
  print("New Logfile for the new session is created!")
  print("New Session can start!")
  
# MQTT funcs
def on_connect(client, userdata, flags, rc):
  print("Connection to tingg.io established!")
  
def sendAllDataToTingg():
  client.publish(topic = 'x', payload = x)
  client.publish(topic = 'y', payload = y)
  client.publish(topic = 'z', payload = z)
  client.publish(topic = 'distance_travelled_mm', payload = db["overallDistanceTravelledInMm"])
  client.publish(topic = 'number_of_sessions_played', payload = db["numberOfSessionsPlayed"])  
  client.publish(topic = 'distance_travelled_in_session', payload = distanceTravelledInASession)
  print("Sended initial data to tingg.io!")
###############
# INITIALIZATIONS
# Variables
close = False #Loop until the user clicks the close button
x = 0
x_prev = 0
y = 0
y_prev = 0
z = 0
z_prev = 0
distanceTravelledInASession = 0
# init the database for storing long term values
print("Restore values from database-file (like travelled distance so far e.g.)")
try:
  with open(PATH + 'db.json', 'r') as f:
    db = json.load(f)
    print("Values restored are:")
    print(db)
    print("")
except:
    db = {"overallDistanceTravelledInMm": 0, "numberOfSessionsPlayed": -1}
    with open(PATH + 'db.json', 'w') as f:
      json.dump(db, f)
# init the joystick/buttons
try:
  GPIO.setmode(GPIO.BCM)
  
  GPIO.setup(GPIO_JOYSTICK_UP, GPIO.IN, pull_up_down=GPIO.PUD_UP)
  cb_jskUP = ButtonHandler(GPIO_JOYSTICK_UP, cb_debounced_jskUP, edge='both', bouncetime=GPIO_BOUNCETIME_MS)
  cb_jskUP.start()
  GPIO.add_event_detect(GPIO_JOYSTICK_UP, GPIO.BOTH, callback=cb_jskUP)
  GPIO.setup(GPIO_JOYSTICK_RIGHT, GPIO.IN, pull_up_down=GPIO.PUD_UP)
  cb_jskRIGHT = ButtonHandler(GPIO_JOYSTICK_RIGHT, cb_debounced_jskRIGHT, edge='both', bouncetime=GPIO_BOUNCETIME_MS)
  cb_jskRIGHT.start()
  GPIO.add_event_detect(GPIO_JOYSTICK_RIGHT, GPIO.BOTH, callback=cb_jskRIGHT)
  
  GPIO.setup(GPIO_JOYSTICK_DOWN, GPIO.IN, pull_up_down=GPIO.PUD_UP)
  cb_jskDOWN = ButtonHandler(GPIO_JOYSTICK_DOWN, cb_debounced_jskDOWN, edge='both', bouncetime=GPIO_BOUNCETIME_MS)
  cb_jskDOWN.start()
  GPIO.add_event_detect(GPIO_JOYSTICK_DOWN, GPIO.BOTH, callback=cb_jskDOWN)
  
  GPIO.setup(GPIO_JOYSTICK_LEFT, GPIO.IN, pull_up_down=GPIO.PUD_UP)
  cb_jskLEFT = ButtonHandler(GPIO_JOYSTICK_LEFT, cb_debounced_jskLEFT, edge='both', bouncetime=GPIO_BOUNCETIME_MS)
  cb_jskLEFT.start()
  GPIO.add_event_detect(GPIO_JOYSTICK_LEFT, GPIO.BOTH, callback=cb_jskLEFT)
  
  GPIO.setup(GPIO_BUTTON_UP, GPIO.IN, pull_up_down=GPIO.PUD_UP)
  cb_btnUP = ButtonHandler(GPIO_BUTTON_UP, cb_debounced_btnUP, edge='both', bouncetime=GPIO_BOUNCETIME_MS)
  cb_btnUP.start()
  GPIO.add_event_detect(GPIO_BUTTON_UP, GPIO.BOTH, callback=cb_btnUP)
  GPIO.setup(GPIO_BUTTON_DOWN, GPIO.IN, pull_up_down=GPIO.PUD_UP)
  cb_btnDOWN = ButtonHandler(GPIO_BUTTON_DOWN, cb_debounced_btnDOWN, edge='both', bouncetime=GPIO_BOUNCETIME_MS)
  cb_btnDOWN.start()
  GPIO.add_event_detect(GPIO_BUTTON_DOWN, GPIO.BOTH, callback=cb_btnDOWN) 
  GPIO.setup(GPIO_BUTTON_OPEN, GPIO.IN, pull_up_down=GPIO.PUD_UP)
  cb_btnOPEN = ButtonHandler(GPIO_BUTTON_OPEN, cb_debounced_btnOPEN, edge='both', bouncetime=GPIO_BOUNCETIME_MS)
  cb_btnOPEN.start()
  GPIO.add_event_detect(GPIO_BUTTON_OPEN, GPIO.BOTH, callback=cb_btnOPEN) 
  GPIO.setup(GPIO_BUTTON_CLOSE, GPIO.IN, pull_up_down=GPIO.PUD_UP)
  cb_btnCLOSE = ButtonHandler(GPIO_BUTTON_CLOSE, cb_debounced_btnCLOSE, edge='both', bouncetime=GPIO_BOUNCETIME_MS)
  cb_btnCLOSE.start()
  GPIO.add_event_detect(GPIO_BUTTON_CLOSE, GPIO.BOTH, callback=cb_btnCLOSE) 
  GPIO.setup(GPIO_BUTTON_STOP, GPIO.IN, pull_up_down=GPIO.PUD_UP)
  cb_btnSTOP = ButtonHandler(GPIO_BUTTON_STOP, cb_debounced_btnSTOP, edge='both', bouncetime=GPIO_BOUNCETIME_MS)
  cb_btnSTOP.start()
  GPIO.add_event_detect(GPIO_BUTTON_STOP, GPIO.BOTH, callback=cb_btnSTOP) 
  GPIO.setup(GPIO_BUTTON_RESET, GPIO.IN, pull_up_down=GPIO.PUD_UP)
  cb_btnRESET = ButtonHandler(GPIO_BUTTON_RESET, cb_debounced_btnRESET, edge='both', bouncetime=GPIO_BOUNCETIME_MS)
  cb_btnRESET.start()
  GPIO.add_event_detect(GPIO_BUTTON_RESET, GPIO.BOTH, callback=cb_btnRESET)  
  
except Exception as e:
  print("Error open joystick/buttons: " + str(e))
  print("")
  raise
  exit()
print("Init joystick/buttons is done!")
# Tingg.io connection
try:
  print("Try to connect to tingg.io ...")
  client = mqtt.Client(client_id = TINGG_MQTT_DEVICE_ID)
  client.username_pw_set(TINGG_MQTT_USER, TINGG_MQTT_PASSWORD)
  client.on_connect = on_connect
  client.connect(host = MQTT_IP_URL, port = MQTT_PORT)
  client.loop_start()
except Exception as e:
  print("Error connecting to tingg.io: " + str(e))
  print("")
  exit()
  
# open the serial port to the XY-plotter arduino
ser = serial.Serial()
ser.port = "/dev/ttyUSB0"
ser.baudrate = 115200
ser.bytesize = serial.EIGHTBITS  # number of bits per bytes
ser.parity = serial.PARITY_NONE  # set parity check: no parity
ser.stopbits = serial.STOPBITS_TWO # number of stop bits
ser.timeout = 0.5          # timeout in seconds when no block is read. other options: None
ser.xonxoff = False        # disable software flow control
ser.rtscts = False         # disable hardware (RTS/CTS) flow control
ser.dsrdtr = False         # disable hardware (DSR/DTR) flow control
try:
  ser.open()
  print("Serial successfully opened!")
except Exception as e:
  print("Error open serial port: " + str(e))
  print("")
  exit()
  
####################
####################
# MAIN
# -------- Main Program Loop -----------
try:  
  print("")
  # init xy-plotter, drive head to Home (0,0,0)
  #ser.write('H\r\n'.encode('utf-8')) # ToDo: currently when serial inits, the arduino starts from the beginning 
  observeGoToHomeProcessTillEndAndInitData()
  sendAllDataToTingg()
  
  print("####################")
  print("")
  
  # MAIN-Loop
  while close == False:
    read_data = ser.readline().rstrip('\r\n'.encode('utf-8')).decode('utf-8')
    while read_data != "":
      # current positions
      if read_data[0] == "x":
        semicolonPos = read_data.find(';')
        x = int(read_data[1:semicolonPos])
        rest = read_data[semicolonPos+1:]
        semicolonPos = rest.find(';')
        y = int(rest[1:semicolonPos])
        z = int(rest[semicolonPos+2:])
        
        # in millimeter
        x = int(x * 256 / 21.168 / 16)
        y = int(y * 256 / 21.168 / 16)
        z = int(z * 256 / 13.5 / 16)
        
        # just send and calculate distance when there was a move
        if x != x_prev or y != y_prev or z != z_prev:                    
          # send to tingg.io
          client.publish(topic = 'x', payload = x)
          client.publish(topic = 'y', payload = y)
          client.publish(topic = 'z', payload = z)
          
          # calc droved way
          x_diff = abs(x - x_prev)
          y_diff = abs(y - y_prev)
          z_diff = abs(z - z_prev)
          
          tmpDistanceTravelledInMm = int(math.sqrt(x_diff*x_diff + y_diff*y_diff + z_diff*z_diff))
          
          db["overallDistanceTravelledInMm"] += tmpDistanceTravelledInMm
          client.publish(topic = 'distance_travelled_mm', payload = db["overallDistanceTravelledInMm"])
          
          distanceTravelledInASession += tmpDistanceTravelledInMm
          client.publish(topic = 'distance_travelled_in_session', payload = distanceTravelledInASession)
          
          x_prev = x
          y_prev = y
          z_prev = z
          
          # log
          timestamp = str(datetime.datetime.now()).split('.')[0]
          log_path = os.path.join(PATH + 'logs', 'log%s.csv' % db["numberOfSessionsPlayed"])
          logData = timestamp + ' ' + str(x) + ' ' + str(y) + ' ' + str(z) + ' ' + str(distanceTravelledInASession) + ' ' + str(db["overallDistanceTravelledInMm"])
          with open(log_path, 'a') as log_file:
            log_file.write(logData + "\n")
      elif read_data == "GoToHome":
        observeGoToHomeProcessTillEndAndInitData()
        sendAllDataToTingg()
        print("####################")
        print("")
      else:
        print(read_data)
      read_data = ser.readline().rstrip('\r\n'.encode('utf-8')).decode('utf-8')
    time.sleep(interval)
    
# CTRL+C
except KeyboardInterrupt:
  print("CTRL+C pressed => close app!")
except Exception as e:
  print("Error: " + str(e))
  print("")
  raise
# run in all cases at end
finally:
  print("")
  print("!!!!!!!!!!!!!!!!!!!!!!!!!!!")
  print("EXIT")
  print("Write current values back to database-file (like travel distance e.g.)!")
  with open(PATH + 'db.json', 'w') as f:
    json.dump(db, f)
  print("GPIO cleanup!")
  GPIO.cleanup()
  print("Disconnect from tingg.io!")
  client.loop_stop()
  client.disconnect()
  print("!!!!!!!!!!!!!!!!!!!!!!!!!!!")
  print("")

Step 4: Connecting to Tingg.io

In case of our candy machine, we would like to monitor the movement of the claw in all 3 dimensions X, Y and Z. We will also measure the overall distance the grabber is making. For that we use tingg.io platform. With tingg.io we can easily connect our machine to the cloud, collect, store and visualize the data in a dashboard. Tingg.io console displays the X,Y,Z position of the grabber in real time and shows the historical movement of the claw.

  1. Open https://tingg.io and register a tingg.io account.
  2. Connect a Thing. Give it a name and a description.Then click on Connect.
  3. Enter See details to configure the Thing
  4. Configure the resource. In the case of our machine we have 3 resources: the X axis, the Y axis and Z axis. All 3 resources “send” data to tingg, that is why we configure them as “Publishing” data to the platform.
  5. Configure your thing.
    Find the following configuration credentials related to your thing in the settings tab https://tingg.io
    Set the DEVICE_ID with the tingg.io <thing_id>
    Set the MQTT_PASSWORD with tingg.io <thing_key>
    Set the MQTT_USER as "thing"
    Set the MQTT_IP_URL as "mqtt.tingg.io"
    Set the MQTT_PORT to 1883

More information on tingg.io platform can be found here >>> https://docs.tingg.io/ If you have any questions regarding the platform or the project you can also reach the tingg.io team directly at support@tingg.io