Skip to content

Instantly share code, notes, and snippets.

@sielicki
Last active June 25, 2016 05:56
Show Gist options
  • Save sielicki/8cc79f0cb6a4b4c229b9786dffcabdbe to your computer and use it in GitHub Desktop.
Save sielicki/8cc79f0cb6a4b4c229b9786dffcabdbe to your computer and use it in GitHub Desktop.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
<title>Websocket Scale</title>
<style>
svg {
font: 10px sans-serif;
color: #d3d3d3;
}
.line {
fill: none;
stroke: #ffffff;
stroke-width: 4px;
}
.axis path,
.axis line {
fill: none;
stroke: #ffffff;
shape-rendering: crispEdges;
}
/* Space out content a bit */
body {
padding-top: 20px;
padding-bottom: 20px;
}
/* Everything but the jumbotron gets side spacing for mobile first views */
.header,
.marketing,
.footer {
padding-right: 15px;
padding-left: 15px;
}
/* Custom page header */
.header {
padding-bottom: 20px;
border-bottom: 1px solid #e5e5e5;
}
/* Make the masthead heading the same height as the navigation */
.header h3 {
margin-top: 0;
margin-bottom: 0;
line-height: 40px;
}
/* Custom page footer */
.footer {
padding-top: 19px;
color: #777;
border-top: 1px solid #e5e5e5;
}
/* Customize container */
@media (min-width: 768px) {
.container {
max-width: 930px;
}
}
.container-narrow > hr {
margin: 30px 0;
}
/* Main marketing message and sign up button */
.jumbotron {
text-align: center;
border-bottom: 1px solid #e5e5e5;
}
.jumbotron .btn {
padding: 14px 24px;
font-size: 21px;
}
/* Supporting marketing content */
.marketing {
margin: 40px 0;
}
.marketing p + h4 {
margin-top: 28px;
}
/* Responsive: Portrait tablets and up */
@media screen and (min-width: 768px) {
/* Remove the padding we set earlier */
.header,
.marketing,
.footer {
padding-right: 0;
padding-left: 0;
}
/* Space out the masthead */
.header {
margin-bottom: 30px;
}
/* Remove the bottom border on the jumbotron for visual effect */
.jumbotron {
border-bottom: 0;
}
}
</style>
<!-- Bootstrap -->
<link href="https://maxcdn.bootstrapcdn.com/bootswatch/3.3.6/darkly/bootstrap.min.css" rel="stylesheet" integrity="sha384-kVo/Eh0sv7ZdiwZK32nRsp1FrDT3sLRLx3zVpSSTI9UdO5H02LJNLBg5F1gwvKg0" crossorigin="anonymous">
</head>
<body>
<centering>
<div class="container">
<div class="header clearfix">
<h3 class="text-muted">AS-350D via websockets</h3>
</div>
<div class="jumbotron">
<div class="row" style="width: 100%; height: auto;">
<h2 id="Readout">Loading...</h2>
</div>
<div class="row" id="graph" style="width: 100%; height: auto;">
</div>
</div>
</div> <!-- /container -->
</centering>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js" integrity="sha384-0mSbJDEHialfmuBBQP6A4Qrprq5OVfW37PRR3j5ELqxss1yVqOtnepnHVP9aJ7xS" crossorigin="anonymous"></script>
<script src="//d3js.org/d3.v3.min.js"></script>
<script type="text/javascript">
var ws = new WebSocket("ws://" + location.host + "/ws");
var mostRecent = "";
ws.onmessage = function (evt)
{
var rec = JSON.parse(evt.data);
document.getElementById("Readout").innerHTML = rec.Pounds.toString() + " lbs. " + rec.Ounces.toString() + " oz.";
mostRecent = rec.Pounds + rec.Ounces / 16;
};
ws.onclose = function()
{
document.getElementById("Readout").innerHTML = "ERROR! Disconnected."
};
var n = 40,
random = d3.random.normal(0, .2),
data = d3.range(n).map(random);
var margin = {top: 20, right: 20, bottom: 20, left: 40},
width = 600 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x = d3.scale.linear()
.domain([0, n - 1])
.range([0, width]);
var y = d3.scale.linear()
.domain([0, 40])
.range([height, 0]);
var line = d3.svg.line()
.x(function(d, i) { return x(i); })
.y(function(d, i) { return y(d); });
var svg = d3.select("#graph").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
svg.append("defs").append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", height);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + y(0) + ")")
.call(d3.svg.axis().scale(x).orient("bottom"));
svg.append("g")
.attr("class", "y axis")
.call(d3.svg.axis().scale(y).orient("left"));
var path = svg.append("g")
.attr("clip-path", "url(#clip)")
.append("path")
.datum(data)
.attr("class", "line")
.attr("d", line);
tick();
function tick() {
// push a new data point onto the back
data.push(mostRecent);
// redraw the line, and slide it to the left
path
.attr("d", line)
.attr("transform", null)
.transition()
.duration(500)
.ease("linear")
.attr("transform", "translate(" + x(-1) + ",0)")
.each("end", tick);
// pop the old data point off the front
data.shift();
}
</script>
</body>
</html>
package main
import (
"golang.org/x/net/websocket"
"github.com/tarm/serial"
"log"
"net/http"
"bufio"
"strconv"
"strings"
)
type Reading struct {
Pounds float64
Ounces float64
}
func newReading(str string) (r Reading){
pounds, err := strconv.ParseFloat(strings.TrimSpace(string(str[3:6])), 64)
if err != nil {
log.Fatal(err)
}
ounces, err := strconv.ParseFloat(strings.TrimSpace(string(str[11:14])), 64)
if err != nil {
log.Fatal(err)
}
return Reading{Pounds: pounds, Ounces: ounces}
}
var Clients chan *websocket.Conn
var Readings chan Reading
func Read(){
c := &serial.Config{Name: "/dev/ttyUSB0", Baud: 9600}
s, err := serial.OpenPort(c)
if err != nil {
log.Fatal("Can't open: ", err)
}
_, err = s.Write([]byte{0x0E})
if err != nil {
log.Fatal("Can't write: ", err)
}
scanner := bufio.NewScanner(s)
splitfunc := func(data []byte, atEOF bool) (advance int, token []byte, err error){
// flush until 0x02
for start := 0; start < len(data); start++ {
if data[start] == 0x02 && !atEOF {
for idx, b := range data[start:] {
if b == 0x03 {
return idx+1, data[:idx], nil
}
}
}
}
return 0, nil, nil
}
scanner.Split(splitfunc)
for {
if ret := scanner.Scan(); ret != false {
Readings <- func(str string) (r Reading){
pounds, err := strconv.ParseFloat(strings.TrimSpace(string(str[3:6])), 64)
if err != nil {
log.Fatal(err)
}
ounces, err := strconv.ParseFloat(strings.TrimSpace(string(str[11:14])), 64)
if err != nil {
log.Fatal(err)
}
return Reading{Pounds: pounds, Ounces: ounces}
}(scanner.Text())
}
}
}
func Push(){
cliPool := make(map[*websocket.Conn]*interface{})
var curReading Reading
for {
select {
case newClient := <-Clients:
cliPool[newClient] = nil
log.Printf("New Client: %s\n", newClient.Request().Host)
case newReading := <-Readings:
curReading = newReading
log.Printf("New Reading: %f lbs, %f oz\n",
curReading.Pounds, curReading.Ounces)
log.Printf("SizeOf clipool: %d\n", len(cliPool))
for client, _ := range cliPool {
go func(ws *websocket.Conn){
err := websocket.JSON.Send(ws, newReading)
if err != nil {
log.Println("sendgorout: ", err)
delete(cliPool, ws)
}
}(client)
}
default:
// Nothing
}
}
}
func main(){
http.Handle("/", http.FileServer(http.Dir(".")))
http.Handle("/ws", websocket.Handler(func(ws *websocket.Conn){
Clients <- ws
for {
_, err := ws.Read(nil)
if err != nil {
log.Println("handler: ", err)
return
}
}
}))
Readings = make(chan Reading, 1)
Clients = make(chan *websocket.Conn, 24)
go Read()
go Push()
err := http.ListenAndServe(":80", nil)
if err != nil {
log.Fatal(err)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment