BLYNK
HOME       📲 GETTING STARTED       📗 DOCS       ❓HELP CENTER       👉 SKETCH BUILDER

Arduino-based Camera Slider project using Blynk

                        The Camera Slider Chronicles of 2020

Using the Rational Unified Process (RUP) project methodology, my challenge is to
design and create an Arduino-based multi-axis camera slider ultimately using Blynk.

Project Milestones -
Inception :heavy_check_mark:, Elaboration :heavy_check_mark:, Construction :heavy_check_mark:, Software Transition :heavy_check_mark: ,
and Support (in progress). Check back now-and-then. Project iterations follow below.

Disclaimers:
No deadline - Ongoing low-priority project during the quarantine.
Low budget: - Arduino Zero board $38, Blynk App widgets $7, and HelTec ESP8266 OLED display $12.
                      Does not include any costs for the camera rail slider.

2 Likes

Looks good but I won’t vote on the concept alone… :wink:

1 Like

Inception - Fresh off the completion of our last Arduino project, I decided to create an Arduino-based camera slider for our “quarentainment” (quarantine entertainment). I looked at all the $$$ ready-made solutions, some slider kits, DIY construction posts, and lots of videos. I found plenty of breadcrumbs leading down the rabbit hole; apparently I’m following in the footsteps of a lot of other people who have already done this before me. My own twist (or undoing!) will be to see if I can use Blynk to interface to it, like I did with my Arduino-based digital salt lamp remote control using Infrared, BlueTooth, and Wifi.

Elaboration - Would it be an expensive, ready-made unit, or a DIY (do it yourself) solution using off-the-shelf components readily sourced on the internet? Would it use servos, stepper-motors, or DC brushless motors? Would it use 3-D printed plastic parts, CNC routed parts, or just simple bolts, nuts, rails, and mounting plates? Would its motion be X-axis movement down the rail, or pan and tilt (Y and Z-axis) movement as well? Should I use an inexpensive off-the-shelf rail slider to speed up construction and add my own motors and electronics, or start from scratch and design that just for the experience?

X-axis control could be a heavy-duty stepper motor, and the Y & Z-axis (pan and tilt) could be two lighter-weight servo motors that draw less current. This first schematic is a starting point; Maybe I’m going to hack this to what I want to do ultimately with either IR, BlueTooth, or WiFi remote control via an iPhone App and ultimately Blynk. It’s designed to shoot 35mm stop-motion, 35mm slow-motion, or full-motion video. The first cut will be a single X-axis stepper on a Neewer slide rail just to get my feet wet.


Here is another schematic of a single-stepper unit already using an IR remote control:

Both have open-source Arduino sketches available to download.

Construction -
Iteration 1.0 - Meade telescope motor mounted to 100 cm length Neewer camera slider. Take an old Meade DS-60 telescope, cannibalize the azimuth motor, the handheld control, 12-volt cable plug-panel, and mount the motor to the Neewer frame at one end with duct-tape, and use parachute cord or clothesline rope as a timing belt to connect them. When running, the motor was very noisy, and the cord would fall off the rotating drive bolt sometimes, but it worked. Clumsy to use with the Meade handheld remote control. Inspired by this YouTube video: New Amsterdam Audio/Video


image

Iteration 1.1 - Neewer non-motorized camera slider with a TurnsPro time-lapse camera mount on a bracket (mounting kit supplied by them) on one end, and a pulley and bracket on the other end. The timing belt threads thru slots already cut in the slider leg mounts. Normally a TurnsPro is mounted on a tripod to do time-lapse, slow-motion, or stop-motion photography, but with their kit it mounts on a slider frame. This is a simple non-Arduino solution that provides for X-axis camera movement down the slider rail. Works great; powered by 4 AA batteries. Highly portable solution with no dangling wires to mess with. Turnspro is based in Plymouth, UK. See the TurnsPro.com website.
This video shows how to assemble their slider bracket kit: Turnspro Adapter Kit Setup.

Iteration 1.2 - Add two robotic servo motors to the trolley platform to provide Y and Z-axis rotation (pan and tilt). The TurnsPro moves the trolley down the slider, and the servos will pan and tilt controlled by an Arduino. The hobby servos and 3D-printed mounting bracket kit are sold many places on the Internet; cheap and easy to assemble. Typical usage is for robotics remote control. Unfortunately, they are not rigid enough to keep them from shaking while riding down the slider rail mounted to the trolley. Disadvantages include separate power required for the Turnspro and Arduino, and manually sequenced startup and stopping. I decided I won’t be using this configuration.

The 3 stepper-motor schematic configuration above uses a joystick, some push-buttons, and potentiometers. I didn’t want a whole lot of wires attached to the Arduino, so I ordered and received this Keyestudio JoyStick gaming shield that has most of what I need: Arduino Joystick Shield. Cheap, it’s only $5 US; this is just for testing and evaluation, not necessarily my final configuration. I have two slider rails here; this will be the electronics used with an Arduino to drive one of them:


If the Arduino sketch keeps track of the slider platform position properly, the movement limit switches are another item (and associated wires) that can be eliminated. NEMA17’s seem to be the stepper-motor of choice for most projects; they are easily sourced. I wanted to use lighter-weight servo-motors, but changed my mind after testing the plastic mountings. People also use brushless DC motors if they want to send position information back to the Arduino, but the sketch code has to be able to accept it. Final decision - NEMA17 motors.

This is the board I’m going to use for the second slider rail, a Devia Robotics control board. It can handle 4 servo-motors or 3 stepper-motors from the 12V power supply, has built-in ESP8266 Wifi, and looks like an Arduino Zero with a USB interface. Like all my hobbyist projects, I usually complete two different configurations to learn how they work and then give one to my brother for parallel testing:


There are plenty of DIY camera sliders that use custom 3D-printed or CNC-routed plastic parts; another design goal is to use as much off-the-shelf stuff as much as possible. The gears, pulleys, and timing belts are in abundant supply on the Internet because they are used for 3D-printers. Servos and steppers plug directly onto this board, and there are headers for the stepper-motor drivers already soldered on the board.

Construction -
Iteration 2.0 - X-axis (rail) motion. Removed the TurnsPro Time Lapse camera mount and bracket from the Neewer trolley, and left the pulley and bracket attached to the other end. Using a new separate NEMA17 mounting bracket, I attached it to the slider base plate, then attached the motor to the bracket using 4 supplied bracket screws. Next I dropped a pulley from an Amazon 3D-printer pulley kit onto the NEMA17 motor shaft, and secured the hex “grub” screws using the supplied Allen wrench. Finally, plugged the stepper-motor cable into the Arduino Zero header and plugged in the 12V 2A power plug. Pretty much a bare-naked slider and trolley now with an Arduino Zero in a crystal project case hanging out the rear, but it worked perfectly.

It’s really quiet with the timing belt almost free-floating. This is not a finished product by any longshot; it’s just for testing. The NEMA17 stepper motor pulled the slider trolley easily with my 35mm Canon PowerShot screwed into a Neewer camera mount. Finally, I shot test videos showing X-axis-only movement down the rail. That yellow sticky “flag” on the pulley is so I can see the pulley turning; the black timing belt blends with the slider frame 100% and renders it almost invisible.
Here are some video clips; right-click and hit start.
PowerShot SX260HS X-axis Demo, Camera Slider Hardware, and iPhone 5 X-axis Demo

1 Like

Construction -
Iteration 2.1 - Y-axis (pan) motion. Invert the pulley bracket 180 degrees on the slider rail end so the pulley is on top now for the timing belt. The belt has a straight shot to the trolley instead of going thru the slots in the end bracket for the TurnsPro camera mount. It’s even quieter with the timing belt not passing thru the bracket.
I removed the Neewer camera mount from the trolley. When I searched the Internet for NEMA17 pan/tilt mounts, the prices were so astronomical, I gave up for now. I cannibalized a pan mount from another slider and will figure out how to secure it to the Neewer slider; the bolts and holes don’t line up. I used two 1/4"-20 hex standoffs to raise the pan mount so the stepper would clear the camera mount on the trolley beneath it; see picture #2. The 3rd and 4th pictures show the slider trolley with its one big roving camera eye. “Cyclops” is ready.

The first video shows the trolley moving 400mm down the rail with the camera panning to keep the wine bottle (at 250mm down the rail and about 500mm away) in center-focus. It was carefully positioned to give a total solar eclipse of the wine stopper passing thru the sunlight. Success. The second video shows the clay pots again, but with a Y-axis camera pan this time.
See Camera Slider X-axis and Y-axis video - world globe map bottle.
See Camera Slider X-axis and Y-axis video - blue pot with stars

1 Like

Construction -
Iteration 2.2 - Z-axis (tilt) motion. Rather than just add a clone tilt mount on top of the pan mount, I want to spend some time cruising the Internet in the quest for a cheap pan/tilt mount that handles NEMA17 stepper-motors. I will revisit the Z-axis step at a later date. I’m going to jump ahead to the Software Iteration phase of the project and get started using Blynk to control everything.

Software Transition -
Iteration 3.0 - First, design a Blynk App to control the stepper-motor controller.
Here is my short-list of desirable features that would be great to implement:

App parameters :
Total rail length 20-1,023 mm start to stop.
X-axis length 20-1,023 mm (base to object)
Y-axis length 20-1,023 mm (rail-to-object)
Z-axis tilt 0-180 degrees
App sliders :
Delay time (15-1,023 seconds)
Rail speed (15-1,023 seconds)
App switches :
Object tracking (OFF/ON)
Delayed start (OFF/ON)
App controls :
[Start]
[Stop]
[Continue]
Stepper data displayed :
Object Tracking – Arctangent of the object height to X-axis length ratio, in degrees.
Axis Tracking – stepper-motor X-Y-Z axis telemetry displayed on OLED readout.

Software Transition -
Iteration 3.1 - App completed, now finalize the conceptual breadboard design.


All the short-list desirable features were implemented in the Blynk App layout shown above running on my iPhone. The App transmits to the Blynk cloud server, and then back down to the HelTec ESP8266 Blynk message broker, and then ultimately to the Arduino Zero stepper-motor controller. The camera slider will in-turn respond to the command(s) and transmit positional axis location telemetry back to the OLED display in real-time.

Next milestone: simulated sending the positional telemetry information back to the HelTec ESP8266 executing Blynk. Final step will be to show the X & Y slider coordinates on the blue OLED display. Can the refresh rate on the display can keep up with the speed of the incoming telemetry data? If not, I may have to display every other line, every other forth line, or something else.

Telemetry data below shows a timestamp, X-axis position along the rail, Y-axis position from the rail to the tracking object (“height”), the arctangent angle of the Y & X coordinates, stepper Motor 1 speed, stepper Motor 2 speed, stepper Motor 3 Z-axis speed and PAN angle.
You can see M1 is constant, while M2 is continually adjusted as the motor rides down the rail.
M3 speed and PAN angle will be zero until a 3rd stepper-motor is mounted to the camera platform on the trolley.

10:46:34.574 -> 
10:46:34.574 -> Blynk Online Message Broker (the BOMB)
10:46:34.608 -> Oasis Drive router
10:46:34.608 -> [261] Connecting to ATnT
10:46:37.635 -> [3304] Connected to WiFi
10:46:37.635 -> [3304] IP: 192.168.1.80
10:46:37.668 -> [3304] 
10:46:37.668 ->     ___  __          __
10:46:37.701 ->    / _ )/ /_ _____  / /__
10:46:37.735 ->   / _  / / // / _ \/  '_/
10:46:37.769 ->  /____/_/\_, /_//_/_/\_\
10:46:37.802 ->         /___/ v0.6.1 on NodeMCU
10:46:37.836 -> 
10:46:37.836 -> [3380] Connecting to blynk-cloud.com:80
10:46:37.869 -> [3566] <[1D|00|01|00]
10:46:37.972 -> [3665] >[00|00|01|00|C8]
10:46:37.972 -> [3665] Ready (ping: 98ms).
10:46:38.007 -> [3665] Free RAM: 48568
10:46:38.041 -> [3732] <[11|00|02|00]Hver[00]0.6.1[00]h-beat[00]30[00]
10:46:38.176 -> [3845] >[00|00|02|00|C8]
10:46:38.210 -> [3846] <[14|00|03|00|09]vw[00]19[00]clr
10:46:38.243 -> [3913] <[14|00|04|00]Fvw[00]19[00]
10:46:38.344 -> [3990] <[14|00|05|00|1A]vw[00]19[00]2020-0524[0D|0A|0D|0A]
10:47:08.147 -> [33846] <[06|00|06|00|00]
(telemetry starts here ---)
10:47:26.717 -> PX:0.03 PY:30.94 A:30.96  M1:1590.50 M2:0.00 M3:0.00 PAN:0.00
10:47:26.784 -> PX:1.01 PY:30.91 A:30.88  M1:1590.50 M2:-59.36 M3:0.00 PAN:0.00
10:47:26.851 -> PX:1.99 PY:30.83 A:30.80  M1:1590.50 M2:-58.74 M3:0.00 PAN:0.00
10:47:26.918 -> PX:2.96 PY:30.77 A:30.72  M1:1590.50 M2:-58.83 M3:0.00 PAN:0.00
10:47:26.986 -> PX:3.94 PY:30.68 A:30.63  M1:1590.50 M2:-58.93 M3:0.00 PAN:0.00
10:47:27.053 -> PX:4.91 PY:30.60 A:30.55  M1:1590.50 M2:-59.01 M3:0.00 PAN:0.00
10:47:27.121 -> PX:5.89 PY:30.52 A:30.47  M1:1590.50 M2:-59.11 M3:0.00 PAN:0.00
10:47:27.188 -> PX:6.87 PY:30.43 A:30.38  M1:1590.50 M2:-59.97 M3:0.00 PAN:0.00
10:47:27.258 -> PX:7.85 PY:30.35 A:30.30  M1:1590.50 M2:-59.30 M3:0.00 PAN:0.00

Speed Bump #1: When I add the statement #include “heltec.h” to the Arduino “BOMB” sketch, it starts up and executes fine. But when I attempt to execute an OLED call to
Heltec.display->clear();, the serial monitor log contains “gibberish” endlessly repeating this exception error (29) sequence over and over again:

08:40:32.661 -> Exception (29):
08:40:32.694 -> epc1=0x4000e1b2 epc2=0x00000000 epc3=0x00000000 excvaddr=0x00000000 depc=0x00000000
08:40:32.763 -> 
08:40:32.763 -> >>>stack>>>
08:40:32.797 -> 
08:40:32.797 -> ctx: cont
08:40:32.797 -> sp: 3ffffd90 end: 3fffffc0 offset: 01a0
08:40:32.831 -> 3fffff30:  3fffdad0 3ffeef8c 00002580 402029c9  
08:40:32.898 -> 3fffff40:  00000040 00000000 feefeffe feefeffe  
08:40:32.932 -> 3fffff50:  feefeffe feefeffe feefeffe feefeffe  
08:40:33.000 -> 3fffff60:  feefeffe feefeffe feefeffe feefeffe  
08:40:33.034 -> 3fffff70:  feefeffe feefeffe feefeffe feefeffe  
08:40:33.100 -> 3fffff80:  feefeffe feefeffe feefeffe feefeffe  
08:40:33.134 -> 3fffff90:  feefeffe feefeffe feefeffe 3ffef0b8  
08:40:33.203 -> 3fffffa0:  3fffdad0 00000000 3ffef078 40206198  
08:40:33.237 -> 3fffffb0:  feefeffe feefeffe 3ffe84fc 40101591  
08:40:33.306 -> <<<stack<<<

There is some apparent code collision between the Blynk code and the HelTec OLED code. HelTec responded to my inquiry immediately; I will let them see what they can find, and will just concentrate on finishing off the App.

What does your exception decoder say?
Is there any chance that it’s a power supply issue?

Pete.

Hey, Pete, thanks for looking. The HelTec board works fine running my Blynk code, and the OLED display runs fine standalone using the standard ESP8266 library instead of the BlynkESP8266 library, so I am not suspecting a power supply issue right now. HelTec gave me a direct contact to work with at their factory; they were very responsive to my inquiry.

Exception 29 is well-documented on the Internet: ESP8266 Exception 29.
Exception 29 is ‘storeprohibitedcause,’ meaning that code tried to write to a protected area in memory. The value given for “excvaddr” shows the virtual memory address that caused the exception.
I also saw suggestions that it was caused by unstable power. I’m not suspecting this.

Speed Bump detour; back on track again. Changed the order of initialization of the OLED code libraries and Blynk code libraries in the sketch code startup{}, and downloaded the latest version of <heltec.h>. Here is Blynk running on the HelTec board. I really like their WiFi Kit 8 OLED display over the standard Arduino 16X2 LCD display.

Updated project budget:

The Arduino Zero Devia controller board costs $38, HelTec ESP8266 OLED display $12, bi-directional 3.3V-5V Voltage Level shifter $0.75, Blue 3mm LED - $0.10, 220 ohm 1/8 watt resistor $0.10, custom PCB to hold all the parts for $2+shipping – I used JLCPCB, Blynk “Energy Credits” (widgets) for the Blynk App UI $7, and an optional rechargeable 12V 2A lithium battery $53. Also add 2 4-pin header cables @ $0.80 each to connect the Devia Controller to the Heltec PCB board, or just use good PCB jumper wires like Schmartboard ones. You can also download their free Arduino sketch slider code from their website.

The JJRobots Camera Slider comes as a kit with all slider parts and includes an Arduino Zero Devia Controller; the entry price is just $72. You need to add these “optional” parts to complete the kit: 3D-plastic printed parts, a 12V 2A power supply (USA/EUR/UK plug type), camera swivel mount, 700 mm 2020 BLACK anodized aluminum v-slot profile rail, and 2 x NEMA 17 stepper motor (MT-1703HS168A) + 75 cm + 14 cm cable (with plug and play connectors). My total price for everything was $194 which included fast Fedex shipping from the UK.

The Neewer 1000mm manual camera slider is available for $86 on Amazon. Add a NEMA-17 stepper motor, motor bracket, 2 pulleys, a timing belt, cables, Arduino Devia controller, two stepper cables and a pulley bracket

1 Like

Three … two … one … ignition !!

With the latest fix from HelTec to get Blynk running in their WiFi Kit 8 ESP8266 OLED board, everything is working now, pretty much the way I had envisioned it. My Blynk App talks to to the HelTec message broker from the Blynk Cloud, which in turn sends the commands to the Arduino Zero stepper-motor controller. The 1st picture is a screenshot of the Blynk App UI, The 2nd picture shows the Blynk logon to the Cloud when the HelTec powers up.

The main reason for using the HelTec ESP8266 was to send positional telemetry to its OLED display in real-time as the slider moves down the rail. The Arduino Zero Devia controller has unused pins; one analog and one digital. Everything else connects to a servo or stepper. I reprogrammed digital pin D13 using firmware commands to work as a serial UART; it worked, but pin D10 isn’t working yet to get the telemetry back from the Zero. Hopefully just more testing and debug needed.

The HelTec OLED board takes only 4 wires to connect: Vcc, Gnd, Data In and Data Out. It looks like an ESP8266 to the Arduino IDE. The Blynk cellphone App sends camera slider commands up to the Blynk Cloud and then back to a receiving device, kinda like a moon-bounce. If you want to take your camera slider into the field away from your home WiFi router, enable your cellphone hotspot mode and use it as the Blynk router instead. You’ll also need a rechargeable 12V 2A lithium battery to drive the electronics and the NEMA stepper motors if you use your slider in the field.

The third picture shows the Devia Controller board inside a standard Arduino crystal project case, I don’t think air flow will be adequate with the top lid closed even though I am using ultra-large blue-aluminum 3D plastic printing heatsinks instead of much smaller plain ones. I will have to rethink this …
The fourth picture is a prototype PCB plugboard with the HelTec OLED display and its logic.

=> New seven-minute Blynk project video: Blynk Camera Slider Project 2020-0607


2 Likes

Stepper Motor Drivers – technology shift …
The older classic A4988 board has been updated with the newer TMC2208 board. Pictured below is a Devia Controller board with two stepper motor drivers installed for comparison. The red one on the left with the small aluminum heatsink is the older A4988; the white one on the right with the larger blue aluminum heatsink is the TMC2208. If you made a camera slider in the past and thought it seemed “noisy” while running, the newer ultra-quiet driver can help.
See the product Wiki website at: TMC2208 Wiki.

1 Like

Blynk PCB update; evolution from breadboard to prototype shield to AutoDesk EAGLE PCB layout. Could not find a PCB parts definition layout for the HelTec Wifi Kit 8 board anywhere on the Internet (it’s too new), so I improvised. Just take the standard layout for an Arduino NANO board and use it instead. The only thing that changes is that there are fewer pins used. Otherwise they have the same pin layout, pin size, pin spacing, etc. I just relabeled the original pins to match the HelTec board. Hopefully, an Eagle part definition for the Heltec WiFi Kit 8 board will become available soon and I can use it instead of the hacked NANO layout and pinout definitions.


2020-06-22_8-36-23 BOMB board 1D

1 Like

PCBs received 6 days early! Stuffed one with components. Will figure out prototype packaging for both my JJRobots and Neewer camera sliders to hold the Arduino Devia board and the Blynk Messenger board together as a rigid unit. Will probably use hex standoffs.

Update 6/24: OK, next 3 pictures are a preview with the Heltec OLED display mounted to the PCB board with a minimal bunch of components. The wiring harness connects everything to the Devia slider controller 4-pin headers at J16 and J17 so that Blynk can communicate with it to control the stepper-motors. The harness curls up nicely and fits inside the crystal acrylic plexiglass project case.

NOTE - the headers on the Devia have only 4 solder pads each; be extremely careful when disconnecting cables and wires that you don’t pull up the headers from the PCB board. I use needle-nose pliers on the headers to solve this. JJR is epoxying the headers until their next board production run.

There are 4 packaging scenarios – 2 camera sliders (JJRobots and Neewer), and two Blynk message boards. If you use the Heltec board, stack it on top of the Devia with taller spacers to allow adequate ventilation for the stepper-driver heatsinks.

If the cool Heltec WiFiKit8 OLED display isn’t your thing, just use a Wemos D1 Mini as the Blynk ESP8266 message board. It has the same standard Arduino drill hole pattern as the Devia controller, so mount both of them on hex standoffs with adequate room to connect the two boards. Stack the Devia on top for better ventilation of the stepper-driver heatsinks. Here are four pics of this configuration:

This is the JJRobots Camera Slider with the Devia Controller mounted on the stepper-motor rail end on its side underneath an orange 3D-printed plastic cover. To add Blynk connectivity, unscrew two Philips screws to release the cover, and attach the Heltec “BOMB” board mounted on aluminum hex standoffs as shown in two of the PCB screw holes. Instead of the original bulky wiring harness, I used Schmartboard jumpers to connect to power, data, and ground between the boards.

If you choose to go with a generic ESP8266 for the Blynk message broker board instead of the Heltec, here is a Wemos D1 Mini stacked beneath the Devia Controller board on the JJRobots slider using hex standoffs. You can even install their original orange 3D-printed plastic cover right back on the Devia Controller. The Wemos D1 has the same board dimensions & drill holes, so this is an easy solution.

1 Like

This is the Arduino sketch code used to communicate between the Blynk On-Line Message Broker “BOMB” (NodeMCU, Wemos D1Mini, or Heltec WiFiKit8 configuration) and the Arduino Zero stepper-motor “Devia” controller. It receives commands from the Blynk cellphone App via the Blynk Cloud, and sends them to the Devia. Maybe not the final version, but close enough to see what is going on under the covers. This code matches the Blynk App UI screenshot previously posted on this project thread.

/* ************************************************************
  Blynk is a platform with iOS and Android apps to control the
  Arduino and Raspberry Pi for the IOT (Internet of Things).
  You can easily build graphic interfaces for all your
  projects by simply dragging and dropping widgets.

    Downloads, docs, tutorials: http://www.blynk.cc
    Sketch generator:           http://examples.blynk.cc
    Blynk community:            http://community.blynk.cc
    Follow us:                  http://www.fb.com/blynkapp
                                http://twitter.com/blynk_app

  The Blynk library is licensed under MIT license;
  Therefore, this example code is also in the public domain.
 *************************************************************
  WARNING!
  It's very tricky to get it working. Please read this article:
  http://help.blynk.cc/hardware-and-libraries/arduino/esp8266-with-at-firmware
  This example shows how values can be pushed from Arduino to
  the Blynk App.
  NOTE: BlynkTimer provides SimpleTimer functionality:
  http://playground.arduino.cc/Code/SimpleTimer
 ************************************************************ */

// Blynk On-line Message Broker (The "BOMB")
// License: Open Software GPL License v2
// Copyright (C) 2020 All Rights Reserved
// Author: Michael Stoddard (BaxRoad.com)
// Timeline:
// 2020-0401 Inception
// 2020-0422 Elaboration
// 2020-0423 Construction
// 2020-0501 Software Transition
// 2020-0701 Support
// Updates:
// 2020-0609 Add BXTD Time Delay and X5CS Speed SET messages
// 2020-0609 Add Heltec display invert & CLEAR commands
// 2020-0621 Use #ifdef conditional compilation for the boards

// Arduino IDE: compiled with 1.8.13

// THIS SOFTWARE IS PROVIDED "AS IS" AND THERE IS NO 
// EXPRESS OR IMPLIED WARRANTIES WHATSOEVER WITH RESPECT TO 
// ITS FUNCTIONALITY, OPERABILITY, OR USE, INCLUDING, WITHOUT 
// LIMITATION, ANY IMPLIED WARRANTIES OF MERCHANTABILITY, 
// FITNESS FOR A PARTICULAR PURPOSE, OR INFRINGEMENT. 
// THERE SHALL BE NO LIABILITY WHATSOEVER FOR ANY DIRECT, 
// INDIRECT, CONSEQUENTIAL, INCIDENTAL OR SPECIAL DAMAGES, 
// INCLUDING, WITHOUT LIMITATION, LOST REVENUES, LOST PROFITS, 
// LOSSES RESULTING FROM BUSINESS INTERRUPTION OR LOSS OF DATA, 
// REGARDLESS OF THE FORM OF ACTION OR LEGAL THEORY UNDER WHICH
// THE LIABILITY MAY BE ASSERTED, EVEN IF ADVISED OF THE 
// POSSIBILITY OR LIKELIHOOD OF SUCH DAMAGES.

/* Comment next line out to disable prints and save space */
#define BLYNK_PRINT Serial
#define BLYNK_DEBUG // Debug - enable verbose mode
#define BLYNK_HEARTBEAT 30
#define BLYNK_NO_FLOAT // Disable floating point operations

// Sketch definitions to run in Arduino:
//#include <ESP8266_Lib.h>
//#include <BlynkSimpleShieldEsp8266.h>
//---------------------------//
// Sketch definitions to run in ESP8266:
#include <ESP8266WiFi.h>
#include <BlynkSimpleEsp8266.h>

#define MSGMAXLEN 20 // Max Devia Controller App message length
#include "heltec.h" // if using Heltec board
#define D2 2

// Hardware Serial on Mega, Leonardo, Micro...
// #define EspSerial Serial1
// -------------------------------------- //
// or Software Serial on Uno, Nano...
#include <SoftwareSerial.h>
SoftwareSerial ESPSerial(11, 10); // Software Serial RX, TX

// Attach virtual serial terminal to Virtual Pin V19
WidgetTerminal terminal(V19);

//ESP8266 wifi(&EspSerial);
//BlynkTimer timer;

// Define and set board definition for Blynk:
String Board = "Heltec WiFi Kit 8 ";
#define WiFiKit8
// or
//String Board = "NodeMCU ESP8266 ";
//#define NodeMCU
// or
//String Board = "Wemos D1 Mini ";
//#define WemosD1Mini

String BlynkOnline = "Blynk On-line Message Broker";
String Version = Board + "2020-0621"; // compile date

bool object_tracking = false;
bool delayed_start = false;
bool display_inverted = false;
unsigned int speed_time = 15;
unsigned int delay_time = 15;
unsigned int rail_length = 600;
unsigned int xaxis_length = 500;
unsigned int yaxis_length = 300;
unsigned int zaxis_tilt = 0;

char prbuff[100];
char packetBuffer[MSGMAXLEN];

// This function sends Arduino's up-time every X seconds to
// Virtual Pin (21). In the app, the Widget's reading frequency
// should be set to PUSH. This means that you define how often
// to send data to Blynk App.
//void myTimerEvent()
//{
//   You can send any value at any time.
//   Don't send more that 10 values per second.
//   Blynk.virtualWrite(V21, millis() / 1000);
//}
 
// The functions listed below will be called every time
// widgits in the Blynk app write values to their Virtual Pin.

BLYNK_WRITE(V0) // "0" -- Object Tracking
{
  int pinValue = param.asInt(); // assigning incoming value
  // You can also use:
  // String i = param.asStr();
  // double d = param.asDouble();
  Serial.print("V0 Button value is: ");
  Serial.println(pinValue);
  terminal.print("Object Tracking is set to: ");
  if (pinValue != 0) {
    terminal.println("ON");
    object_tracking = true;
  } else {
    terminal.println("OFF");
    object_tracking = false;
  }
  terminal.flush();
}

BLYNK_WRITE(V1) // "1" -- Delayed Start
{
  int pinValue = param.asInt(); // assigning incoming value
  // You can also use:
  // String i = param.asStr();
  // double d = param.asDouble();
  Serial.print("V1 Button value is: ");
  Serial.println(pinValue);
  terminal.print("Delayed Start is set to: ");
  if (pinValue != 0) {
    terminal.println("ON");
    delayed_start = true;
  } else {
    terminal.println("OFF");
    delayed_start = false;
  }
  terminal.flush();
}

BLYNK_WRITE(V2) // "2" -- Travel Time in seconds
{
  int pinValue = param.asInt(); // assigning incoming value
  // You can also use:
  // String i = param.asStr();
  // double d = param.asDouble();
  Serial.print("V2 Slider value is: ");
  Serial.println(pinValue);
  terminal.print("TRAVEL TIME is set to: ");
  terminal.print(pinValue);
  terminal.println(" seconds");
  terminal.flush();
  speed_time = pinValue;
  sprintf(prbuff, "%04d", speed_time);
  memset(packetBuffer, '0', MSGMAXLEN);
  packetBuffer[0] = 'X';
  packetBuffer[1] = '5';
  packetBuffer[2] = 'C';
  packetBuffer[3] = 'S';
  packetBuffer[16] = prbuff[0];
  packetBuffer[17] = prbuff[1];
  packetBuffer[18] = prbuff[2];
  packetBuffer[19] = prbuff[3];
  ESPSerial.print(packetBuffer);
  memset(prbuff, 0, (sizeof(prbuff)/sizeof(prbuff[0])));
  Serial.println(packetBuffer);
}

BLYNK_WRITE(V3) // "3" -- Time Delay
{
  int pinValue = param.asInt(); // assigning incoming value
  // You can also use:
  // String i = param.asStr();
  // double d = param.asDouble();
  Serial.print("V3 Slider value is: ");
  Serial.println(pinValue);
  terminal.print("TIME DELAY is set to: ");
  terminal.print(pinValue);
  terminal.println(" seconds");
  terminal.flush();
  delay_time = pinValue;
  sprintf(prbuff, "%04d", delay_time);
  memset(packetBuffer, '0', MSGMAXLEN);
  packetBuffer[0] = 'B';
  packetBuffer[1] = 'X';
  packetBuffer[2] = 'T';
  packetBuffer[3] = 'D';
  packetBuffer[16] = prbuff[0];
  packetBuffer[17] = prbuff[1];
  packetBuffer[18] = prbuff[2];
  packetBuffer[19] = prbuff[3];
  ESPSerial.print(packetBuffer);
  memset(prbuff, 0, (sizeof(prbuff)/sizeof(prbuff[0])));
  Serial.println(packetBuffer);
}

BLYNK_WRITE(V4) // "4" -- Rail Length
{
  int pinValue = param.asInt(); // assigning incoming value
  // You can also use:
  // String i = param.asStr();
  // double d = param.asDouble();
  Serial.print("V4 Slider value is: ");
  Serial.println(pinValue);
  terminal.print("RAIL LENGTH is set to: ");
  terminal.print(pinValue);
  terminal.println(" mm");
  rail_length = pinValue;
  terminal.flush();
}

BLYNK_WRITE(V5) // "5" -- X-Axis Length
{
  int pinValue = param.asInt(); // assigning incoming value
  // You can also use:
  // String i = param.asStr();
  // double d = param.asDouble();
  Serial.print("V5 Slider value is: ");
  Serial.println(pinValue);
  terminal.print("X-AXIS LENGTH is set to: ");
  terminal.print(pinValue);
  terminal.println(" mm");
  xaxis_length = pinValue;
  terminal.flush();
}

BLYNK_WRITE(V6) // "6" -- Y-Axis Length
{
  int pinValue = param.asInt(); // assigning incoming value
  // You can also use:
  // String i = param.asStr();
  // double d = param.asDouble();
  Serial.print("V6 Slider value is: ");
  Serial.println(pinValue);
  terminal.print("Y-AXIS HEIGHT is set to: ");
  terminal.print(pinValue);
  terminal.println(" mm");
  yaxis_length = pinValue;
  terminal.flush();
}

BLYNK_WRITE(V7) // "7" -- Z-Axis Tilt
{
  int pinValue = param.asInt(); // assigning incoming value
  // You can also use:
  // String i = param.asStr();
  // double d = param.asDouble();
  Serial.print("V7 Slider value is: ");
  Serial.println(pinValue);
  terminal.print("Z-AXIS tilt is set to: ");
  terminal.print(pinValue);
  terminal.println(" degrees");
  zaxis_tilt = pinValue;
  terminal.flush();
}

BLYNK_WRITE(V8) // "8" -- START
{
  if ( param.asInt() != 0 ) { // debounce
    int pinValue = param.asInt(); // assigning incoming value
    // You can also use:
    // String i = param.asStr();
    // double d = param.asDouble();
    Serial.print("V8 Button value is: ");
    Serial.println(pinValue);
    terminal.println("");
    terminal.println("START!");
    terminal.print("Time Delay ");
    if (delayed_start) {
      //terminal.print("ON for ");
      terminal.print(delay_time);
      terminal.print(" seconds");
    } else {
      terminal.print("off  ");
    }
    terminal.println("");
    terminal.print("Object Tracking ");
    if (object_tracking) {
      terminal.print("ON -- Angle is ");
      volatile int32_t object_angle = atan2(float(yaxis_length), float(xaxis_length)) * 180 / PI;
      terminal.print(object_angle); terminal.print(" degrees");
    } else {
      terminal.print("off");
    }
    terminal.println("");
    terminal.print("Rail Length "); terminal.print(rail_length); terminal.print("mm ");
    terminal.print(" ");
    terminal.print("Z-axis Tilt "); terminal.print(zaxis_tilt); terminal.print(" degrees");
    terminal.println("");
    terminal.print("X-axis Length "); terminal.print(xaxis_length); terminal.print("mm ");
    terminal.print(" ");
    terminal.print("Y-axis Height "); terminal.print(yaxis_length); terminal.print("mm ");
    terminal.println("");
    unsigned int t = speed_time;
    unsigned int s = t % 60;
    t = (t - s) / 60;
    unsigned int m = t % 60;
    t = (t - m) / 60;
    unsigned int h = t;
    terminal.print("Travel Time ");
    if (h > 0) {
      terminal.print(h);
      terminal.print(" Hour(s) ");
    }
    if (m > 0) {
      terminal.print(m);
      terminal.print(" Mins ");
    }
    terminal.print(s); terminal.print(" Seconds");
    terminal.println("");
    terminal.flush();
    if (delayed_start == true) // start with delay
    {
      terminal.println("START with DELAY");
      terminal.flush();
      memset(packetBuffer, '0', MSGMAXLEN);
      packetBuffer[0] = 'X';
      packetBuffer[1] = '3';
      packetBuffer[2] = 'C';
      packetBuffer[3] = 'S';
        sprintf(prbuff, "%04d", rail_length);
        packetBuffer[4] = prbuff[0];
        packetBuffer[5] = prbuff[1];
        packetBuffer[6] = prbuff[2];
        packetBuffer[7] = prbuff[3];
          sprintf(prbuff, "%04d", xaxis_length);
          packetBuffer[8] = prbuff[0];
          packetBuffer[9] = prbuff[1];
          packetBuffer[10] = prbuff[2];
          packetBuffer[11] = prbuff[3];
            sprintf(prbuff, "%04d", yaxis_length);
            packetBuffer[12] = prbuff[0];
            packetBuffer[13] = prbuff[1];
            packetBuffer[14] = prbuff[2];
            packetBuffer[15] = prbuff[3];
              sprintf(prbuff, "%04d", speed_time);
              packetBuffer[16] = prbuff[0];
              packetBuffer[17] = prbuff[1];
              packetBuffer[18] = prbuff[2];
              packetBuffer[19] = prbuff[3];
      if (object_tracking == false) {
        for (int j=8; j<16; j++) {
            packetBuffer[j] = '0';  
        }
      }
      ESPSerial.print(packetBuffer);
      memset(prbuff, 0, (sizeof(prbuff)/sizeof(prbuff[0])));
      Serial.println(packetBuffer); 
    }
    else
    {
      terminal.println("START with no delay");
      terminal.flush();
      memset(packetBuffer, '0', MSGMAXLEN);
      packetBuffer[0] = 'X';
      packetBuffer[1] = '1';
      packetBuffer[2] = 'C';
      packetBuffer[3] = 'S';
        sprintf(prbuff, "%04d", rail_length);
        packetBuffer[4] = prbuff[0];
        packetBuffer[5] = prbuff[1];
        packetBuffer[6] = prbuff[2];
        packetBuffer[7] = prbuff[3];
          sprintf(prbuff, "%04d", xaxis_length);
          packetBuffer[8] = prbuff[0];
          packetBuffer[9] = prbuff[1];
          packetBuffer[10] = prbuff[2];
          packetBuffer[11] = prbuff[3];
            sprintf(prbuff, "%04d", yaxis_length);
            packetBuffer[12] = prbuff[0];
            packetBuffer[13] = prbuff[1];
            packetBuffer[14] = prbuff[2];
            packetBuffer[15] = prbuff[3];
              sprintf(prbuff, "%04d", speed_time);
              packetBuffer[16] = prbuff[0];
              packetBuffer[17] = prbuff[1];
              packetBuffer[18] = prbuff[2];
              packetBuffer[19] = prbuff[3];
      if (object_tracking == false) {
        for (int j=8; j<16; j++) {
            packetBuffer[j] = '0';  
        }
      }
      ESPSerial.print(packetBuffer);
      memset(prbuff, 0, (sizeof(prbuff)/sizeof(prbuff[0])));
      Serial.println(packetBuffer); 
    }
  }
}

BLYNK_WRITE(V9) // "9" -- STOP
{
  if ( param.asInt() != 0 ) { // debounce
    int pinValue = param.asInt(); // assigning incoming value
    // You can also use:
    // String i = param.asStr();
    // double d = param.asDouble();
    Serial.print("V9 Button value is: ");
    Serial.println(pinValue);
    terminal.println("STOP");
    terminal.flush();
    memset(packetBuffer, '0', MSGMAXLEN);
    packetBuffer[0] = 'X';
    packetBuffer[1] = '0';
    packetBuffer[2] = 'C';
    packetBuffer[3] = 'S';
    ESPSerial.print(packetBuffer);
    memset(prbuff, 0, (sizeof(prbuff)/sizeof(prbuff[0])));
    Serial.println(packetBuffer);
  }
}

BLYNK_WRITE(V10) // "10" -- Continue
{
  if ( param.asInt() != 0 ) { // debounce
    int pinValue = param.asInt(); // assigning incoming value from pin V1 to a variable
    // You can also use:
    // String i = param.asStr();
    // double d = param.asDouble();
    Serial.print("V10 Button value is: ");
    Serial.println(pinValue);
    if (delayed_start == true) // continue with delay
    {
      terminal.println("CONTINUE with DELAY");
      terminal.flush();
      memset(packetBuffer, '0', MSGMAXLEN);
      packetBuffer[0] = 'X';
      packetBuffer[1] = '4';
      packetBuffer[2] = 'C';
      packetBuffer[3] = 'S';
        sprintf(prbuff, "%04d", rail_length);
        packetBuffer[4] = prbuff[0];
        packetBuffer[5] = prbuff[1];
        packetBuffer[6] = prbuff[2];
        packetBuffer[7] = prbuff[3];
          sprintf(prbuff, "%04d", xaxis_length);
          packetBuffer[8] = prbuff[0];
          packetBuffer[9] = prbuff[1];
          packetBuffer[10] = prbuff[2];
          packetBuffer[11] = prbuff[3];
            sprintf(prbuff, "%04d", yaxis_length);
            packetBuffer[12] = prbuff[0];
            packetBuffer[13] = prbuff[1];
            packetBuffer[14] = prbuff[2];
            packetBuffer[15] = prbuff[3];
              sprintf(prbuff, "%04d", speed_time);
              packetBuffer[16] = prbuff[0];
              packetBuffer[17] = prbuff[1];
              packetBuffer[18] = prbuff[2];
              packetBuffer[19] = prbuff[3];
      if (object_tracking == false) {
        for (int j=8; j<16; j++) {
            packetBuffer[j] = '0';  
        }
      }
      ESPSerial.print(packetBuffer);
      memset(prbuff, 0, (sizeof(prbuff)/sizeof(prbuff[0])));
      Serial.println(packetBuffer);  
    }
    else // continue with no delay
    {
      terminal.println("CONTINUE with no delay");
      terminal.flush();
      memset(packetBuffer, '0', MSGMAXLEN);
      packetBuffer[0] = 'X';
      packetBuffer[1] = '2';
      packetBuffer[2] = 'C';
      packetBuffer[3] = 'S';
        sprintf(prbuff, "%04d", rail_length);
        packetBuffer[4] = prbuff[0];
        packetBuffer[5] = prbuff[1];
        packetBuffer[6] = prbuff[2];
        packetBuffer[7] = prbuff[3];
          sprintf(prbuff, "%04d", xaxis_length);
          packetBuffer[8] = prbuff[0];
          packetBuffer[9] = prbuff[1];
          packetBuffer[10] = prbuff[2];
          packetBuffer[11] = prbuff[3];
            sprintf(prbuff, "%04d", yaxis_length);
            packetBuffer[12] = prbuff[0];
            packetBuffer[13] = prbuff[1];
            packetBuffer[14] = prbuff[2];
            packetBuffer[15] = prbuff[3];
              sprintf(prbuff, "%04d", speed_time);
              packetBuffer[16] = prbuff[0];
              packetBuffer[17] = prbuff[1];
              packetBuffer[18] = prbuff[2];
              packetBuffer[19] = prbuff[3];
      if (object_tracking == false) {
        for (int j=8; j<16; j++) {
            packetBuffer[j] = '0';  
        }
      }
      ESPSerial.print(packetBuffer);
      memset(prbuff, 0, (sizeof(prbuff)/sizeof(prbuff[0])));
      Serial.println(packetBuffer);
    }
  }
}

BLYNK_WRITE(V19) // Virtual Terminal
{
  while(true) {
    if (String(param.asStr()) == String("clear")) {
      terminal.clear(); // Clear the Blynk terminal
      terminal.println(BlynkOnline + " (the BOMB)");
      terminal.println(Version + " BaxRoad.com");
      terminal.println("") ;
      terminal.flush();
      #ifdef WiFiKit8
      Heltec.display->clear(); // Clear the HelTec display
      Heltec.display->drawString(0, 0, BlynkOnline);
      Heltec.display->drawString(0, 10, Version);
      Heltec.display->display();
      #endif
      break;
      }

    #ifdef WiFiKit8
    if (String(param.asStr()) == String("invert")) {
      if (!display_inverted) { // only works once ...
        Heltec.display->clear(); // Clear the HelTec display
        Heltec.display->init(); // invert the display 180 degrees
        Heltec.display->drawString(0, 0, BlynkOnline);
        Heltec.display->drawString(0, 10, Version);
        Heltec.display->display();
        display_inverted = true;
        terminal.println("OK") ;
        terminal.flush();
        }
        break;
      }
    #endif
    
    if (String(param.asStr()) == String("marco")) {
      terminal.println("You said: 'Marco' !") ;
      terminal.println("I say: 'Polo' !") ;
      terminal.flush();
      break;
      }
      
    terminal.println("  ??") ; // unknown command
    terminal.flush();
    break;
  }
}
  
BLYNK_WRITE(V20) // WiFi Test
{
  if ( param.asInt() != 0 ) { // debounce
    String LocalIP = "IP address: " + WiFi.localIP().toString();
    terminal.println(LocalIP);
    terminal.flush();

    #ifdef WiFiKit8
    Heltec.display->clear(); // Heltec
    Heltec.display->drawString(0, 0, BlynkOnline);
    Heltec.display->drawString(0, 10, Version);
    Heltec.display->drawString(0, 20, LocalIP);
    Heltec.display->display();
    digitalWrite(D2, HIGH); // HelTec but actually the LED is on; this is because
    #endif

    #ifdef WemosD1Mini
    digitalWrite(BUILTIN_LED, LOW); // Wemos Mini
    #endif
    
    #ifdef NodeMCU
    digitalWrite(BUILTIN_LED, LOW);  // NodeMCU - Turn the BLUE LED on (Note that LOW is the voltage level
    #endif
    
    delay(500);             // Wait a bit

    #ifdef NodeMCU
    digitalWrite(BUILTIN_LED, HIGH); // turn the LED off by making the voltage HIGH
    #endif

    #ifdef WemosD1Mini
    digitalWrite(BUILTIN_LED, HIGH); // turn it off
    #endif

    #ifdef WiFiKit8
    digitalWrite(D2,LOW);
    Heltec.display->clear();
    Heltec.display->drawString(0, 0, BlynkOnline);
    Heltec.display->drawString(0, 10, Version);
    Heltec.display->display();
    #endif
    
    int pinValue = param.asInt(); // assigning incoming value from pin V1 to a variable
    // You can also use:
    // String i = param.asStr();
    // double d = param.asDouble();
    memset(packetBuffer, '0', MSGMAXLEN);
    packetBuffer[0] = 'B';
    packetBuffer[1] = 'X';
    packetBuffer[2] = 'W';
    packetBuffer[3] = 'T';
    ESPSerial.print(packetBuffer);
    Serial.println(packetBuffer);
  }
}

void setup()
{
  #ifdef WiFiKit8
  Heltec.begin(true /*DisplayEnable Enable*/, true /*Serial Enable*/);
  #endif
  Serial.begin(9600);  // Start serial port
  delay(1000);
  ESPSerial.begin(9600); // start Devia Controller comm port
  delay(1000);
  Serial.println("");
  Serial.println("Blynk Online Message Broker (the BOMB) BaxRoad.com");

  // Router authentification variables: (use your own)
  char auth[] = "YourAuthString";
  char ssid[] = "YourSSID";
  char pass[] = "YourPassword";
  
  String Connecting = "Connecting to "+ String(ssid);
  Serial.println(Connecting);
  #ifdef WiFiKit8
  //Heltec.display->clear();
  //Heltec.display->init(); // inverts the display if needed
  Heltec.display->drawString(0, 20, Connecting );
  Heltec.display->display();
  #endif
  
  Blynk.begin(auth, ssid, pass); // Fireup the Blynk engine
  delay(1000); // stabilize

  #ifdef WemosD1Mini
  pinMode(BUILTIN_LED, OUTPUT); // Wemos D1 Mini
  digitalWrite(BUILTIN_LED, HIGH); // Turn it off to start
  #endif

  #ifdef NodeMCU
  pinMode(BUILTIN_LED, OUTPUT); // NodeMCU
  digitalWrite(BUILTIN_LED, HIGH); // Turn it off to start
  #endif

  #ifdef WiFiKit8
  pinMode(D2, OUTPUT); // HelTec
  digitalWrite(D2, LOW); // Turn it off to start
  #endif
  
  // Setup a function to be called every one second if needed
  //timer.setInterval(1000L, myTimerEvent);

  terminal.clear();
  terminal.println(BlynkOnline + " (the BOMB)");
  terminal.println(Version + " BaxRoad.com");
  terminal.println("") ;
  terminal.flush();

  #ifdef WiFiKit8
  Heltec.display->clear();
  Heltec.display->drawString(0, 0, BlynkOnline);
  Heltec.display->drawString(0, 10, Version);
  String LocalIP = "IP address: " + WiFi.localIP().toString();
  Heltec.display->drawString(0, 20, LocalIP);
  Heltec.display->display();
  delay(2000);
  Heltec.display->clear();
  Heltec.display->drawString(0, 0, BlynkOnline);
  Heltec.display->drawString(0, 10, Version);
  Heltec.display->display();
  #endif
  
  //Serial.swap(); // $Debug -- move Rx/Tx connections
}

void loop()
{

  if (Serial.available()) // Check if incoming data is available
  {
    //Serial.print("$");
    byte byteRead = Serial.read(); // Read the most recent byte
    if (byteRead != 0x0D) {
      Serial.print(char(byteRead)); // Echo the byte
      ESPSerial.print(char(byteRead)); // Echo the byte to Serial2
      if (byteRead == 0x0A) {
          Serial.println("");  // force a linefgeed
      }
    }
  }

  if (ESPSerial.available()) // Check if incoming data is available
  {
    //Serial.print("$");
    byte byteRead = ESPSerial.read(); // Read the most recent byte
    if (byteRead != 0x0D) {
      Serial.print(char(byteRead)); // Echo the byte
      ESPSerial.print(char(byteRead)); // Echo the byte to Serial2
      if (byteRead == 0x0A) {
          Serial.println("");  // force a linefgeed
      }
    }
  }

  Blynk.run();   // Keep Blynk running; refresh
  //timer.run(); // Initiate BlynkTimer here if needed
}

// Message Definitions:
// ===================
// Message J5CS : Change speed camera slider + 4 params ()
// Message J4CS : Continue with delay camera slider + 4 params ()
// Message J3CS : Start from init with delay camera slider + 4 params ()
// Message J2CS : Continue camera slider + 4 params ()
// Message J1CS : Start from init camera slider + 4 params ()
// Message J0CS : STOP
// Message BXTD : Set Time Delay
// Message BXWT : WiFi Test for Blynk-to-Bomb connectivity