Skip to content

Instantly share code, notes, and snippets.

@jfryman
Last active June 5, 2019 13:30
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jfryman/57c436e233aabc57c46234328a30bf24 to your computer and use it in GitHub Desktop.
Save jfryman/57c436e233aabc57c46234328a30bf24 to your computer and use it in GitHub Desktop.
Control your Geekdesk with an Arduino via MQTT
#!/usr/bin/env ruby
require 'rubygems'
require 'em/cron'
require 'em/mqtt'
require 'em-rubyserial'
require 'scrolls'
require 'pry'
LOCATION = 'bikeshed'
DESK_NAME = 'frydesk'
TOPIC_ROOT = ['geekdesk', LOCATION, DESK_NAME].join('/')
@poll_time = Time.now
Scrolls.add_timestamp = true
# Start main EM loop
EventMachine.run do
Scrolls.log(:at => 'mqtt-queue-connection')
queue = EventMachine::MQTT::ClientConnection.connect 'localhost'
Scrolls.log(:at => 'serial-connection')
serial = EventMachine.open_serial('/dev/ttyS0', 115200, 8)
# Post to state endpoint if it's a measurement. Otherwise, use the serial topic
# to indicate response from arduino directly.
Scrolls.context(:context => "serial-data") do
serial.on_data do |data|
Scrolls.log(:at => "received-data", :data => data)
publish_topic_endpoint = data =~ /(\d+)/ ? 'state' : 'serial'
topic = [TOPIC_ROOT, publish_topic_endpoint].join('/')
Scrolls.log(:at => "mqtt-publish-data", :topic => topic, :data => data.strip)
queue.publish(topic, data.strip)
end
end
# Pass all MQTT commands directly through to serial / arduino code
queue.subscribe "#{TOPIC_ROOT}/command"
Scrolls.context(:context => "mqtt-callback") do
queue.receive_callback do |message|
Scrolls.log(:at => "send-serial-data", :payload => message.payload.strip)
serial.send_data "#{message.payload}\n"
end
end
# Periodically poll for state
Scrolls.context(:context => "cron-runloop") do
EventMachine::Cron.schedule("* * * * *") do |time|
if @poll_time < time
Scrolls.log(:at => "poll-for-current-height", :time => time)
serial.send_data "CURRENT\n"
@poll_time = time
end
end
end
end
/* FryDesk Control Code
This sketch is designed to manage James Fryman's GeekDesk via hardware controls.
Sketch is designed to allow for the following:
* Read the current height
* Adjust height up until a specific height
* Adjust height down until a specific height
Communication occurs via the Serial device.
Pin Configurations:
* PIN 7: Ping Sensor
* PIN 6: Relay Switch #2 ( down )
* PIN 5: Relay Switch #1 ( up )
*/
const int pingPin = 7;
const int downPin = 6;
const int upPin = 5;
const int loopDelay = 100;
boolean upwardMotion = false;
boolean downwardMotion = false;
String serialCommand = "";
boolean incomingSerialCommand = false;
/* Application */
void increaseHeightInCm(long cm) {
increaseHeight();
while (currentHeightInCm() < cm) {
delay(loopDelay);
}
stopMotion();
}
void increaseHeight() {
if (! inMotion()) {
digitalWrite(upPin, LOW);
upwardMotion = true;
outputIncreasingHeight();
} else { errorDeskAlreadyInMotion(); }
}
void stopIncreaseHeight() {
if (inMotion()) {
digitalWrite(upPin, HIGH);
upwardMotion = false;
outputStoppedMotion();
} else { errorDeskNotInMotion(); }
}
void decreaseHeightInCm(long cm) {
decreaseHeight();
while (currentHeightInCm() > cm) {
delay(loopDelay);
}
stopMotion();
}
void decreaseHeight() {
if (! inMotion()) {
digitalWrite(downPin, LOW);
downwardMotion = true;
outputDecreasingHeight();
} else { errorDeskAlreadyInMotion(); }
}
void stopDecreaseHeight() {
if (inMotion()) {
digitalWrite(downPin, HIGH);
downwardMotion = false;
outputStoppedMotion();
} else { errorDeskNotInMotion(); }
}
void stopMotion() {
if (upwardMotion) stopIncreaseHeight();
if (downwardMotion) stopDecreaseHeight();
}
void setHeightInCm(long cm) {
long currentHeight = currentHeightInCm();
if (currentHeight < cm) { increaseHeightInCm(cm); }
else if (currentHeight > cm) { decreaseHeightInCm(cm); }
else if (currentHeight == cm) { errorDeskAlreadyAtHeight(); }
}
boolean inMotion() {
if ((upwardMotion) || (downwardMotion)) { return true; }
else { return false; }
}
long pingDuration() {
pinMode(pingPin, OUTPUT);
digitalWrite(pingPin, LOW);
delayMicroseconds(2);
digitalWrite(pingPin, HIGH);
delayMicroseconds(5);
digitalWrite(pingPin, LOW);
pinMode(pingPin, INPUT);
return pulseIn(pingPin, HIGH);
}
long currentHeightInCm() {
return microsecondsToCentimeters(pingDuration());
}
long microsecondsToCentimeters(long microseconds) {
return microseconds / 29 / 2;
}
void processIncommingCommand(String incomingCommand) {
int spaceIndex = incomingCommand.indexOf(" ");
String command = incomingCommand.substring(0, spaceIndex);
String argument = incomingCommand.substring(spaceIndex+1);
if (command == "CURRENT") {
outputCurrentHeight();
} else if (command == "SET") {
long height = argument.toInt();
setHeightInCm(height);
} else if (command == "UP") {
increaseHeight();
} else if (command == "DOWN") {
decreaseHeight();
} else if (command == "STOP") {
stopMotion();
} else if (command == "HELP") {
outputHelp();
} else {
errorUnknownCommand();
}
}
/* Output Conditions */
void outputCurrentHeight() {
Serial.println(currentHeightInCm());
}
void outputIncreasingHeight() {
Serial.println("OK:UP");
}
void outputDecreasingHeight() {
Serial.println("OK:DOWN");
}
void outputStoppedMotion() {
Serial.println("OK:STOP");
}
void outputHelp() {
Serial.println("Commands: CURRENT, SET, INCREASE, DECREASE, STOP, HELP");
}
/* Error Conditions */
void errorDeskAlreadyInMotion() {
Serial.println("ERR:MOT");
}
void errorDeskNotInMotion() {
Serial.println("ERR:NOMO");
}
void errorDeskAlreadyAtHeight() {
Serial.println("ERR:EQ");
}
void errorUnknownCommand() {
Serial.println("ERR:?");
}
/* Arduino Reserved */
void setup() {
Serial.begin(115200);
serialCommand.reserve(200);
pinMode(upPin, OUTPUT);
pinMode(downPin, OUTPUT);
digitalWrite(upPin, HIGH);
digitalWrite(downPin, HIGH);
delay(2000);
}
/* Built-in runloop */
void serialEvent() {
while (Serial.available()) {
char inChar = (char)Serial.read();
serialCommand += inChar;
if (inChar == '\n') {
serialCommand.trim();
incomingSerialCommand = true;
}
}
}
void loop() {
if (incomingSerialCommand) {
processIncommingCommand(serialCommand);
serialCommand = "";
incomingSerialCommand = false;
}
}
source 'https://rubygems.org'
gem 'em-cron', '~> 0.2.0'
gem 'em-mqtt', '~> 0.0.5'
gem 'em-rubyserial', '~> 0.0.2'
gem 'scrolls'
gem 'foreman'
gem 'pry'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment