diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..796968e --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +bin/* +pkg +releases +src/github.com diff --git a/README.md b/README.md index e1fee1e..9d29474 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,23 @@ RFLED-Server -============ +========== -Python Scripts to run UDP servers to emulate a LimitlessLED WiFi Bridge 4.0 unit. +Golang binary to emulate a LimitlessLED WiFi Bridge 4.0 unit. Install -======= +---- + 1. Download the latest release from the [Releases](https://github.com/riptidewave93/RFLED-Server/releases) + page for your architecture. + 2. Copy the contents of ./rfled-server/* to / on your disk as root + 3. Configure your settings in /etc/default/rfled-server as needed + 4. Enable the init.d script `systemctl enable rfled-server` + 5. Start the service `service rfled-server start` + 6. ??? + 7. Profit! - * Change the variables in both scripts to meet your needs - * Start the scripts and they will start the UDP listeners - -Startup Script -============== - - * Place script into /etc/init.d/ - * a) Ensure scripts are in /usr/local/bin/ or - * b) Adjust path in rfled-server script to path of the scripts - * Run update-rc.d rfled-server defaults to set up - -Running -======= - - * Run "/etc/init.d/rfled-server start" to start scripts without a restart +Build from Source +---- + 1. Setup a go build environment, and run `./build.sh` + 2. Once ran, you can run `./build.sh package` as root to generate a release .tar.gz + 3. Follow the above Install steps with the tar.gz in ./releases for your architecture. + 4. ????? + 5. Profit! diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..6bd795e --- /dev/null +++ b/build.sh @@ -0,0 +1,63 @@ +#!/bin/bash + +# Set env vars for building +export GOPATH=$PWD +export GOBIN=$PWD/bin + +# Do we even have go installed? +command -v go >/dev/null 2>&1 || { echo >&2 "I require go but it's not installed. Aborting."; exit 1; } + +# Are we building or cleaning house? +if [[ "$1" == "clean" ]]; then + rm $PWD/bin/* 2>/dev/null + rm -r $PWD/pkg 2>/dev/null + rm -fr $PWD/src/github.com 2>/dev/null + rm -r $PWD/releases 2>/dev/null +else + # Download dependencies + if [ ! -d "$GOPATH/src/github.com/tarm/serial" ]; then + git clone https://github.com/tarm/serial.git $GOPATH/src/github.com/tarm/serial + fi + + # Make required dir if needed + if [ ! -d "$GOBIN" ]; then + mkdir $GOBIN + fi + + # build the things! + echo "Building amd64..." + env GOOS=linux GOARCH=amd64 go build -o $GOBIN/rfled-server-amd64 $PWD/src/rfled-server.go + echo "Building armv6..." + env GOOS=linux GOARCH=arm GOARM=6 go build -o $GOBIN/rfled-server-armv6 $PWD/src/rfled-server.go + echo "Building armv7..." + env GOOS=linux GOARCH=arm GOARM=7 go build -o $GOBIN/rfled-server-armv7 $PWD/src/rfled-server.go + + # We finished! + if [ $? -eq 0 ]; then + echo "Build Complete! Binary is in $GOBIN" + fi + + # Are we building shippable releases? + if [[ "$1" == "package" ]]; then + if [[ $EUID -ne 0 ]]; then + echo "Error, package mode must be ran as root" 1>&2 + exit 1 + fi + echo "Building Releases..." + for arch in amd64 armv6 armv7; + do + echo "Packaging $arch" + if [ ! -d "$GOPATH/releases/tmp/$arch/rfled-server/usr/sbin" ]; then + mkdir -p $GOPATH/releases/tmp/$arch/rfled-server/usr/sbin + fi + cp $GOBIN/rfled-server-$arch $GOPATH/releases/tmp/$arch/rfled-server/usr/sbin/rfled-server + chmod +x $GOPATH/releases/tmp/$arch/rfled-server/usr/sbin/rfled-server + cp -r $GOPATH/src/etc $GOPATH/releases/tmp/$arch/rfled-server/ + chmod +x $GOPATH/releases/tmp/$arch/rfled-server/etc/init.d/rfled-server + chown -R root:root $GOPATH/releases/tmp/$arch/rfled-server + tar -pczf $GOPATH/releases/rfled-server-$arch-$(date +"%F-%H%M%S").tar.gz -C $GOPATH/releases/tmp/$arch ./rfled-server + done + rm -rf $GOPATH/releases/tmp/ + echo "Done! :)" + fi +fi diff --git a/rfled-server b/rfled-server deleted file mode 100755 index 97d4fa4..0000000 --- a/rfled-server +++ /dev/null @@ -1,33 +0,0 @@ -#! /bin/sh - -### BEGIN INIT INFO -# Provides: rfled-server -# Required-Start: $local_fs $remote_fs -# Required-Stop: -# Default-Start: 2 3 4 5 -# Default-Stop: -# Short-Description: Provides a small python server for smart LED control -# Description: Provides a small python server for smart LED control -### END INIT INFO - -N=/etc/init.d/rfled-server -RFLED_PATH=/usr/local/bin - -set -e - -case "$1" in - start) - # make sure privileges don't persist across reboots - /usr/bin/python3 ${RFLED_PATH}/led.py & - /usr/bin/python3 ${RFLED_PATH}/admin.py & - ;; - stop|reload|restart|force-reload|status) - - ;; - *) - echo "Usage: $N {start|stop|restart|force-reload|status}" >&2 - exit 1 - ;; -esac - -exit 0 diff --git a/source/admin.py b/source/admin.py deleted file mode 100644 index 1d3ef01..0000000 --- a/source/admin.py +++ /dev/null @@ -1,33 +0,0 @@ -#!/usr/bin/env python - -import socket - -# Set admin server settings -UDP_IP = '' # Leave empty for Broadcast support -ADMIN_PORT = 48899 - -# Local settings of your Raspberry Pi, used for app discovery -INT_IP = '10.0.1.61' -INT_MAC = '111a02bf232b' - -# Code Starts Here # - -# Create UDP socket, bind to it -adminsock = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) -adminsock.bind((UDP_IP, ADMIN_PORT)) - -# Loop forever -while True: - admindata, adminaddr = adminsock.recvfrom(64) # buffer size is 64 bytes - # Did we get a message? - if admindata is not None: - # print("admin command: ", str(admindata)) # Debugging - # If the client app is syncing to a unit - if str(admindata).find("Link_Wi-Fi") != -1: - RETURN = INT_IP + ',' + INT_MAC + ',' # Return our IP/MAC - # print("admin return: ", RETURN) # Debugging - adminsock.sendto(bytes(RETURN, "utf-8"),adminaddr) # Send Response - else: - adminsock.sendto(bytes('+ok', "utf-8"),adminaddr) # Send OK for each packet we get - else: - break \ No newline at end of file diff --git a/source/led.py b/source/led.py deleted file mode 100644 index 34af0f6..0000000 --- a/source/led.py +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env python - -import socket -import serial - -# Set LED Control server settings -UDP_IP = '' # Leave empty for Broadcast support -LED_PORT = 8899 - -# Serial Settings -TTL_PORT = "/dev/ttyAMA0" -TTL_SPEED = 9600 -ser = serial.Serial(TTL_PORT, TTL_SPEED) # Connect to serial - -# Create UDP socket, bind to it -sock = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) -sock.bind((UDP_IP, LED_PORT)) - -while True: - data, addr = sock.recvfrom(64) # buffer size is 64 bytes - - if data is not None: - # print("led command: ", str(data)) # Debugging - ser.write(data[:2]) # Write packet data out (2 bytes only) diff --git a/src/etc/default/rfled-server b/src/etc/default/rfled-server new file mode 100644 index 0000000..4923422 --- /dev/null +++ b/src/etc/default/rfled-server @@ -0,0 +1,6 @@ +# Default settings for rfled-server. This file is sourced by /bin/sh from +# /etc/init.d/rfled-server. + +# Define any options you want to be used when running. You can run rfled-server -h +# to get the list of available flags for this service. +RFLED_OPTS= diff --git a/src/etc/init.d/rfled-server b/src/etc/init.d/rfled-server new file mode 100755 index 0000000..1139418 --- /dev/null +++ b/src/etc/init.d/rfled-server @@ -0,0 +1,114 @@ +#! /bin/sh + +### BEGIN INIT INFO +# Provides: rfled-server +# Required-Start: $remote_fs $syslog +# Required-Stop: $remote_fs $syslog +# Default-Start: 2 3 4 5 +# Default-Stop: +# Short-Description: RFLED-Server for Milight LEDs +### END INIT INFO + +set -e + +# /etc/init.d/rfled-server: start and stop the rfled-server service + +test -x /usr/sbin/rfled-server || exit 0 +( /usr/sbin/rfled-server -h 2>&1 | grep -q Usage ) 2>/dev/null || exit 0 + +umask 022 + +if test -f /etc/default/rfled-server; then + . /etc/default/rfled-server +fi + +. /lib/lsb/init-functions + +if [ -n "$2" ]; then + RFLED_OPTS="$RFLED_OPTS $2" +fi + +# Are we running from init? +run_by_init() { + ([ "$previous" ] && [ "$runlevel" ]) || [ "$runlevel" = S ] +} + +check_for_upstart() { + if init_is_upstart; then + exit $1 + fi +} + +export PATH="${PATH:+$PATH:}/usr/sbin:/sbin" + +case "$1" in + start) + check_for_upstart 1 + log_daemon_msg "Starting rfled-server" "rfled-server" || true + if start-stop-daemon --start --quiet --oknodo --background --pidfile /var/run/rfled-server.pid --make-pidfile --exec /usr/sbin/rfled-server -- $RFLED_OPTS; then + log_end_msg 0 || true + else + log_end_msg 1 || true + fi + ;; + stop) + check_for_upstart 0 + log_daemon_msg "Stopping rfled-server" "rfled-server" || true + if start-stop-daemon --stop --quiet --oknodo --pidfile /var/run/rfled-server.pid; then + log_end_msg 0 || true + else + log_end_msg 1 || true + fi + ;; + + restart) + check_for_upstart 1 + log_daemon_msg "Restarting rfled-server" "rfled-server" || true + start-stop-daemon --stop --quiet --oknodo --retry 30 --pidfile /var/run/rfled-server.pid + check_dev_null log_end_msg + if start-stop-daemon --start --quiet --oknodo --background --pidfile /var/run/rfled-server.pid --make-pidfile --exec /usr/sbin/rfled-server -- $RFLED_OPTS; then + log_end_msg 0 || true + else + log_end_msg 1 || true + fi + ;; + + try-restart) + check_for_upstart 1 + log_daemon_msg "Restarting rfled-server" "rfled-server" || true + RET=0 + start-stop-daemon --stop --quiet --retry 30 --pidfile /var/run/rfled-server.pid || RET="$?" + case $RET in + 0) + # old daemon stopped + check_dev_null log_end_msg + if start-stop-daemon --start --quiet --oknodo --background --pidfile /var/run/rfled-server.pid --make-pidfile --exec /usr/sbin/rfled-server -- $RFLED_OPTS; then + log_end_msg 0 || true + else + log_end_msg 1 || true + fi + ;; + 1) + # daemon not running + log_progress_msg "(not running)" || true + log_end_msg 0 || true + ;; + *) + # failed to stop + log_progress_msg "(failed to stop)" || true + log_end_msg 1 || true + ;; + esac + ;; + + status) + check_for_upstart 1 + status_of_proc -p /var/run/rfled-server.pid /usr/sbin/rfled-server rfled-server && exit 0 || exit $? + ;; + + *) + log_action_msg "Usage: /etc/init.d/rfled-server {start|stop|restart|try-restart|status}" || true + exit 1 +esac + +exit 0 diff --git a/src/rfled-server.go b/src/rfled-server.go new file mode 100644 index 0000000..4fd320b --- /dev/null +++ b/src/rfled-server.go @@ -0,0 +1,194 @@ +package main + +import ( + "flag" + "log" + "net" + "os/exec" + "os/user" + "strconv" + "strings" + "time" + "github.com/tarm/serial" +) + +// Logging function used by the application +// w: false = log, true = fatal +// x: debug flag +// y: false = not debug output, true = debug output +// z: message +func applog(w bool, x bool, y bool, z string) { + if x && y { + if !w { + log.Printf("DEBUG: %q \n", z) + } else { + log.Fatal("DEBUG: ", z) + } + } else if !y { + if !w { + log.Printf("%q \n", z) + } else { + log.Fatal(z) + } + } +} + +// Used to clean up all the error checks we do +func error_check(err error, log bool) { + if err != nil { + applog(true, log, false, err.Error()) + } +} + +// Function to check and work with LED control packets +func led_server(conn *net.UDPConn, log bool, s *serial.Port) { + buf := make([]byte, 64) + msg, remoteAddr, err := 0, new(net.UDPAddr), error(nil) + for err == nil { + msg, remoteAddr, err = conn.ReadFromUDP(buf) + error_check(err,log) + if buf != nil { + applog(false, log, true, "LED: message was " + string(buf[:msg]) + " from " + remoteAddr.String()) + // Write to serial + _, err = s.Write(buf[:msg]) + error_check(err,log) + } + } + error_check(err,log) +} + +// Function to check and work with admin config packets +func adm_server(conn *net.UDPConn, log bool, ip string, mac string) { + buf := make([]byte, 64) + msg, remoteAddr, err := 0, new(net.UDPAddr), error(nil) + for err == nil { + msg, remoteAddr, err = conn.ReadFromUDP(buf) + error_check(err,log) + if buf != nil { + applog(false, log, true, "ADM: message was " + string(buf[:msg]) + " from " + remoteAddr.String()) + + if strings.Contains(string(buf[:msg]),"Link_Wi-Fi") { + _,err = conn.WriteToUDP([]byte(ip+","+mac+","),remoteAddr) + error_check(err,log) + applog(false, log, true, "ADM: replied "+ip+","+mac+",") + } else { + _,err = conn.WriteToUDP([]byte("+ok"),remoteAddr) + error_check(err,log) + applog(false, log, true, "ADM: replied +ok") + } + } + } + error_check(err,log) +} + +func main() { + // Set our UART vars + comport := flag.String("serial", "/dev/ttyAMA0", "Serial device to use") + comspeed := flag.Int("baud", 9600, "Serial baudrate") + debug := flag.Bool("debug", false, "Enable verbose debugging") + + // Set our IP vars + ip := flag.String("ip", "0.0.0.0", "IP address to listen on (LED Server)") + interf := flag.String("int", "eth0", "Interface to listen on, used for mac address") + adm_port := flag.Int("admport", 48899, "Port for the admin server") + led_port := flag.Int("ledport", 8899, "Port for the led server") + flag.Parse() + + // Check if we are root + usr,err := user.Current() + if err != nil { + applog(false, *debug, true, "Error with user.Current(), failing back...") + // If we are here, we are prob on arm which does NOT support user.Current() + usr, err := exec.Command("whoami").Output() + error_check(err,*debug) + if string(usr) != "root\n" { + applog(false, *debug, true, "Current user us "+string(usr)) + applog(true, *debug, false, "Not running as root, exiting!") + } + } else if usr.Uid != "0" { + applog(true, *debug, false, "Not running as root, exiting!") + } + + // Load our interface information based on user input, used for admin server + var ethz *net.Interface + if *ip == "0.0.0.0" { + // lookup interface using interf + ethz, err = net.InterfaceByName(*interf) + if err != nil { + applog(true, *debug, false, "Error, unable to lookup interface "+*interf+"!") + } + applog(false, *debug, true, "IntLookup vars: eth="+string(ethz.Name)+" ip="+*ip) + } else { + // lookup interface using IP + applog(false, *debug, true,"Looking up all interfaces") + list, err := net.Interfaces() + found := false + error_check(err,*debug) + for _, iface := range list { + applog(false, *debug, true, "Int="+iface.Name) + addrs, err := iface.Addrs() + error_check(err,*debug) + for _, addr := range addrs { + applog(false, *debug, true, " IP="+addr.String()) + if strings.Contains(addr.String(),*ip) { + applog(false, *debug, true, "Found our interface!") + ethz = &iface + found = true + break + } + } + } + if !found { + applog(true, *debug, false, "Error, unable to find an interface with the IP of "+*ip) + } + } + + // Once we found our Interface we can then get the IP/Mac (unless we have one manually set) + mymac := strings.Replace(ethz.HardwareAddr.String(),":","",-1) + if *ip == "0.0.0.0" { + addrs, err := ethz.Addrs() + error_check(err,*debug) + for _, addr := range addrs { + // Find and remove the SubNet from the IP and set to var + *ip = addr.String()[:strings.Index(addr.String(),"/")] + break + } + } + // Make sure we got our mac! (sometimes lo will not return one) + if len(mymac) < 12 { + applog(true, *debug, false, "Error, unable to lookup mac address for interface!") + } + applog(false, *debug, true,"Our Info: mac="+mymac+" ip="+*ip) + + // load serial connection + c := &serial.Config{Name: *comport, Baud: *comspeed} + s, err := serial.OpenPort(c) + error_check(err,*debug) + + // Start Admin server + adm_addr, err := net.ResolveUDPAddr("udp", ":"+strconv.Itoa(*adm_port)) + error_check(err,*debug) + adm_listen, err := net.ListenUDP("udp", adm_addr) + error_check(err,*debug) + defer adm_listen.Close() + + // Start LED server + led_addr, err := net.ResolveUDPAddr("udp", *ip+":"+strconv.Itoa(*led_port)) + error_check(err,*debug) + led_listen, err := net.ListenUDP("udp", led_addr) + error_check(err,*debug) + defer led_listen.Close() + + // Start main app loop! + applog(false, *debug, false, "rfled-server started!") + for { + // Function for Admin Server + go adm_server(adm_listen, *debug, *ip, mymac) + + // Function for LED Server + go led_server(led_listen, *debug, s) + + // Sleep so we don't just EAT the CPU + time.Sleep(100 * time.Millisecond) + } +}