IoT Coffeebot 4000
Introductions
James Alexander
Andrew Kaczorek
.NET Developer
Python Fanboy
Part Time Coffee Drinker
Linux Administrator
Cloud Architect
DIY Enthusiast
Open Source Developer
Cloud Architect
Automate All The Things Specialist
Chris Chalfant
Code & Slides
Core Python bot:
https://github.com/yanigisawa/coffee-scale
LED Svc:
https://github.com/akaczorek/ledsvc
Serverless DynamoDB:
https://github.com/chalfant/serverless-coffee-scale
Slides:
Motivation
http://keirawong.com/blog/wp-content/uploads/2015/05/62110937.jpg?6bec58
The One Scale
Other scales
That probably will work too
uint16_t scales[NSCALES][2] = {\
// Stamps.com Model 510 5LB Scale
{0x1446, 0x6a73},
// USPS (Elane) PS311 "XM Elane Elane UParcel 30lb"
{0x7b7c, 0x0100},
// Stamps.com Stainless Steel 5 lb. Digital Scale
{0x2474, 0x0550},
// Stamps.com Stainless Steel 35 lb. Digital Scale
{0x2474, 0x3550},
// Mettler Toledo
{0x0eb8, 0xf000},
// SANFORD Dymo 10 lb USB Postal Scale
{0x6096, 0x0158},
// Fairbanks Scales SCB-R9000
{0x0b67, 0x555e},
// Dymo-CoStar Corp. M25 Digital Postal Scale
{0x0922, 0x8004},
// DYMO 1772057 Digital Postal Scale
{0x0922, 0x8003}
};
https://github.com/erjiang/usbscale/blob/master/scales.h
Do I really need to Know C?
http://www.raywenderlich.com/wp-content/uploads/2014/01/pic2.png
Python to the Rescue
dev = "/dev/usb/hiddev0"
fd = os.open(dev, os.O_RDONLY)
Integer Value | Name | File stream |
---|---|---|
0 | Standard Input | stdin |
1 | Standard Output | stdout |
2 | Standard Error | stderr |
with open("someFile.txt", "r") as f:
line = f.readline()
!=
fd is a File Descriptor
f is a File Object
File Descriptor Integers
Python to the Rescue
from os import * # DON'T DO THIS
# Above import causes a name collision on "open"
with open("file.txt", "r") as f:
content = f.read()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: an integer is required
C - Inspired Python
# Read 4 unsigned integers from USB device
hiddev_event_fmt = "IIII"
bytes_to_read = struct.calcsize(hiddev_event_fmt)
usb_binary_read = struct.unpack(hiddev_event_fmt, os.read(fd, bytes_to_read))
All Together
def getWeightInGrams(self, dev="/dev/usb/hiddev0"):
grams = -1
try:
fd = os.open(dev, os.O_RDONLY)
# Read 4 unsigned integers from USB device
hiddev_event_fmt = "IIII"
bytes_to_read = struct.calcsize(hiddev_event_fmt)
usb_binary_read = struct.unpack(hiddev_event_fmt, os.read(fd, bytes_to_read))
grams = usb_binary_read[3]
os.close(fd)
except OSError as e:
print("{0} - Failed to read from USB device".format(datetime.utcnow()))
return grams
Main Loop
def main(self):
self._currentWeight = self.getWeightInGrams()
while True:
try:
self._loopCount += 1
tmpWeight = self.getWeightInGrams()
if self.shouldLogWeight(tmpWeight):
self._currentWeight = tmpWeight
self.logToInitialState()
self.writeToDynamo()
if self.shouldPostToLed():
self._loopCount = 0
self.postToLed()
if self.potIsLifted():
self._mostRecentLiftedTime = datetime.now()
except Exception as e:
self._logger.error(e)
sleep(1)
Logging To Initial State
def logToInitialState(self):
utcnow = datetime.utcnow()
bucketKey = "{0} - coffee_scale_data".format(self.environment)
streamer = Streamer(bucket_name="{0} - Coffee Scale Data".format(self.environment),
bucket_key=bucketKey, access_key=self.initialStateKey)
if self.potIsLifted():
streamer.log("Coffee Pot Lifted", True)
streamer.log("Coffee Weight", self._currentWeight)
streamer.close()
Initial State Dashboard
Simple RESTful APIs Made Easy
-
Combines AWS API Gateway with AWS Lambda (python, java, or nodejs)
-
Approaching 1.0
-
Hides complexity and implements deployment workflow
-
Makes simple APIs very easy and inexpensive
Caveats
-
Rich error handling is still difficult
-
Framework is still a bit unstable as they head toward 1.0
-
Code with compiled libraries can be tricky to build/deploy
-
Enable API Gateway authentication!
Code
https://github.com/chalfant/serverless-coffee-scale
Python code using boto3 library to read/write records to DynamoDb
Curl To Get Entries
Coffee Graph
Display Hardware DIY
Andrew Kaczorek
It Begins With Hipchat
- The move to a room based chat system added a new social outlet
- Several Iterations of Chatbot
- How could we bridge our chat system into the real world?
V1: Spark + (Tiny) OLED Fed by a Script Watching a Chat Room
Tiny Display Becomes Tiresome
- I2C implementation in Spark was Unreliable
- Integration with Spark Cloud required non-standard outgoing port
Time for Something Bigger
Also, a Raspberry Pi Model B
-
Allowed for running code other than Arduino-Compatible
-
Multiple developers could easily contribute
Wiring was Pretty Easy
Purchased ribbon cables for each end and patched the pinouts with breadboard wires
Linux Wifi on the Pi Was Flaky
TESTIP=10.1.1.1
ping -c4 ${TESTIP} > /dev/null
if [ $? != 0 ]
then
logger -t $0 "WiFi seems down, restarting"
sudo /sbin/ifdown --force wlan0
sleep 10
sudo /sbin/ifup wlan0
fi
Run via cron every 5 minutes and call me in the morning
def render(mytext)
draw=Magick::Draw.new {
self.font_family = 'Comic Sans MS'
self.fill="#6495ED"
self.pointsize = 16
self.font_weight = 600
self.gravity = Magick::SouthWestGravity
}
metrics=draw.get_type_metrics(mytext)
image=Magick::Image.new(metrics['width']+30,16) {
self.background_color = "black"
self.format = "PPM"
self.depth = 8
}
draw.annotate(image,0,0,0,0,mytext) {
self.font_family = 'Comic Sans MS'
self.fill="#6495ED"
self.pointsize = 16
self.font_weight = 600
self.gravity = Magick::SouthWestGravity
}
How We Drive the Display
Ruby ->
-
led-matrix -t 10 -D 1 runtext.ppm
C++ ->
Adding the Ability for Multiple Data Feeds to the LED Panel
class Display
extend Resque::Plugins::Logger
@queue = :leddisplay
def self.perform(localpath)
logger.info "displaying #{localpath}"
system("/root/ledsvc/led-matrix","-r16","-D1","-t30","#{localpath}")
File.delete(localpath)
rescue Resque::TermException
logger.error "display #{localpath} failed"
Resque.enqueue(self, localpath)
end
end
Ruby Resque to the Rescue: https://github.com/resque/resque
Let's Monitor Just in Case
Process 'ledsvc'
status Running
monitoring status Monitored
pid 2247
parent pid 1
uptime 23d 0h 17m
children 0
memory kilobytes 32776
memory kilobytes total 32776
memory percent 7.3%
memory percent total 7.3%
cpu percent 1.6%
cpu percent total 1.6%
data collected Fri, 24 Jun 2016 13:23:31
I used monit: https://mmonit.com/monit/
The Scale Project and LCD Matrix Project Come Together
- Initially, these ran on separate Pis
- Have been coexisting for the past ~14mo
Future Plans for Display
-
Display can be daisy-chained. Let’s do that.
-
Variable time for message display
-
Fixed-display statistics plus scrolling messages