Last active
March 15, 2017 23:08
-
-
Save cj-dimaggio/e0e6023b5c6e7016e9f09a6a4299aede to your computer and use it in GitHub Desktop.
Blink Decode
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
package main | |
import ( | |
"math/rand" | |
"os" | |
"strconv" | |
"time" | |
"math" | |
"github.com/fatih/color" | |
) | |
func randomBetween(min, max int) int { | |
rand.Seed(time.Now().Unix()) | |
return rand.Intn(max-min) + min | |
} | |
var idLength = 8 | |
// Notice how we only go up to 254; 255 is *not* a valid ID | |
var id = uint(randomBetween(0, int(math.Pow(2, float64(idLength))-2))) | |
var hz = 10 | |
var waitDur = time.Second / time.Duration(hz) | |
var clockAlignRate = time.Duration(10000) | |
var quit = make(chan int) | |
// Go has no asserts! | |
func assert(cond bool, desc string) { | |
if !cond { | |
color.Red(desc) | |
} | |
} | |
func getBit(val uint, index uint) bool { | |
if index >= uint(idLength) { | |
color.Red("Tried to get an invalid bit! Quitting because we're scared of you") | |
quit <- 1 | |
} | |
mask := uint(1 << index) | |
bin := (val & mask) / mask | |
if bin == 1 { | |
return true | |
} | |
return false | |
} | |
func setBit(byt uint, index uint, val bool) uint { | |
if val { | |
return byt | (1 << index) | |
} | |
return byt | |
} | |
func nextPeriod() { | |
time.Sleep(waitDur) | |
} | |
func printBin(prefix string, val bool, print func(format string, a ...interface{})) { | |
var bin int | |
if val { | |
bin = 1 | |
} else { | |
bin = 0 | |
} | |
print("%s: %d\n", prefix, bin) | |
} | |
func emit(val bool) { | |
printBin("Emitting", val, color.Green) | |
state = val | |
nextPeriod() | |
} | |
func receive() bool { | |
val := state | |
printBin("Received", val, color.Blue) | |
nextPeriod() | |
return val | |
} | |
// This represents the state of the "wire" (obviously this is volatile; that's the point) | |
var state = false | |
func signal(id uint) { | |
for { | |
// We start by printing out a list of 1s for the length of our id times 2 | |
// to signify a message is about to start | |
for i := 0; i < idLength; i++ { | |
emit(true) | |
emit(true) | |
} | |
// Start our next sequence with a zero | |
emit(false) | |
// Now we can emit our actual binary | |
for i := 0; i < idLength; i++ { | |
// In order to keep the positional tracker every bit is prepended by an on signal | |
emit(true) | |
emit(getBit(id, uint(i))) | |
} | |
// We end our sequence with a zero | |
emit(false) | |
} | |
} | |
func alignClock(pollFreq time.Duration) { | |
color.Yellow("Trying to align clocks") | |
// Unfortunately, in order to save space, we probably can't use a clock syncing | |
// encoding such as the Manchester code. So that means we need to try to sync our | |
// clock as best as we can. How can we do that? Well I guess we can just poll very | |
// fast and wait for a state change to tell us when to kick in. It's not pretty; | |
// we'll burn CPU; any better suggestions? | |
// | |
// (this along with alignReceiver means we can miss as many as two sequence broadcasts) | |
val := state | |
for { | |
// Instead of polling as fast as we can, let's only do it at a fraction of our hz. | |
// (we're working at such a low hz this actually makes a difference) | |
time.Sleep(waitDur / pollFreq) | |
if state != val { | |
// Okay, we just witnessed a state change, let's wait half a period so we don't have | |
// *as* bad of a race condition | |
time.Sleep(waitDur / 2) | |
color.Yellow("Clocks might be aligned") | |
return | |
} | |
} | |
} | |
func alignReceiver() { | |
var val bool | |
// First things first, we need to align ourselves with the output before we can reliably | |
// parse | |
onesCounted := 0 | |
// Go has no "while" loop! | |
for onesCounted < (idLength * 2) { | |
val = receive() | |
if val { | |
onesCounted++ | |
} else { | |
onesCounted = 0 | |
} | |
} | |
color.Yellow("Receiver should now be aligned") | |
} | |
func receiverLoop() { | |
// Okay, if we've gotten here we should have a good idea where we are. We just received a long | |
// sequence of 1s and we can take it from there | |
for { | |
val := uint(0) | |
assert(receive() == false, "Expecting a 0") | |
for i := 0; i < idLength; i++ { | |
// Here comes our sequence | |
assert(receive() == true, "Expecting a 1") | |
val = setBit(val, uint(i), receive()) | |
} | |
color.Cyan("Is the id: %d : %08b?", val, val) | |
assert(receive() == false, "Expecting a 0") | |
for i := 0; i < idLength; i++ { | |
assert(receive() == true, "Expecting a 1") | |
assert(receive() == true, "Expecting a 1") | |
} | |
} | |
} | |
func receiver() { | |
alignClock(clockAlignRate) | |
alignReceiver() | |
receiverLoop() | |
} | |
// This is really shit | |
func handleArgs() { | |
if len(os.Args) > 1 { | |
_hz, _ := strconv.ParseInt(os.Args[1], 10, 0) | |
hz = int(_hz) | |
waitDur = time.Second / time.Duration(hz) | |
if len(os.Args) > 2 { | |
_idLength, _ := strconv.ParseInt(os.Args[2], 10, 0) | |
idLength = int(_idLength) | |
id = uint(randomBetween(0, int(math.Pow(2, float64(idLength))-2))) | |
if len(os.Args) > 3 { | |
_clockAlignRate, _ := strconv.ParseInt(os.Args[3], 10, 0) | |
clockAlignRate = time.Duration(_clockAlignRate) | |
} | |
} | |
} | |
} | |
func main() { | |
handleArgs() | |
color.Magenta("ID is: %d : %08b\n", id, id) | |
color.Yellow("Starting signal...") | |
go signal(id) | |
color.Yellow("Sleeping a random amount to see if it screws us up...") | |
time.Sleep(time.Duration(randomBetween(0, 3000)) * time.Millisecond) | |
color.Yellow("Starting receiver...") | |
go receiver() | |
os.Exit(<-quit) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment