Last active
June 5, 2019 13:30
-
-
Save jfryman/57c436e233aabc57c46234328a30bf24 to your computer and use it in GitHub Desktop.
Control your Geekdesk with an Arduino via MQTT
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* 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; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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