#!/usr/bin/env php
<?php

require_once('websockets.php');

class ApplianceStatusServer extends WebSocketServer {
	//protected $maxBufferSize = 1048576; //1MB... overkill for an this server, but potentially plausible for other applications.

	private $gpio_path_prefix = "/sys/class/gpio/gpio";
	private $gpio_node_value = "/value";
	private $gpio_node_async = "/edge";
	private $sys_temp_path = "/sys/bus/iio/devices/iio:device2/in_voltage5_raw";
	
	private $pascal_device = array(
			"clip_ch2" => array(
					"name" => "clip_ch2",
					"gpio" => 11,
					"handle" => NULL,
					"value" => NULL
			),
			"dis_rp" => array(
					"name" => "dis_rp",
					"gpio" => 86,
					"handle" => NULL,
					"value" => NULL
			),
			"clip_ch1" => array(
					"name" => "clip_ch1",
					"gpio" => 87,
					"handle" => NULL,
					"value" => NULL
			),
			"temp" => array(
					"name" => "temp",
					"handle" => NULL,
					"value" => NULL
			)
	);

	private $name2stream = NULL;

	function __construct($addr, $port) {
		parent::__construct($addr, $port);
		foreach ($this->pascal_device as $key => $value) {
			if (array_key_exists("gpio", $this->pascal_device[$key])) {
				file_put_contents($this->gpio_path_prefix . $this->pascal_device[$key]["gpio"] . $this->gpio_node_async, "both");
				$this->pascal_device[$key]["handle"]= fopen($this->gpio_path_prefix . $this->pascal_device[$key]["gpio"]. $this->gpio_node_value, "r");
				stream_set_blocking($this->pascal_device[$key]["handle"], false);
				// Flush it clean
				$this->pascal_device[$key]["value"] = intval(fgets($this->pascal_device[$key]["handle"]));
				rewind($this->pascal_device[$key]["handle"]);
			}
			$this->pascal_device["temp"]["handle"]= fopen($this->sys_temp_path, "r");
			stream_set_blocking($this->pascal_device["temp"]["handle"], false);
			$temp_raw = intval(fgets($this->pascal_device["temp"]["handle"]));
			rewind($this->pascal_device["temp"]["handle"]);
			$this->pascal_device["temp"]["value"] = round($temp_raw * 100 / 3754, 1);
		}
		$this->name2stream = array_column($this->pascal_device,"handle", "name");
		unset($this->name2stream["temp"]);
	}

	protected function throttle_print($msg, &$repeat) {
		static $timestamp;
		$nowtimestamp = time();
		if (($nowtimestamp - $timestamp) >= 60) {
			$timestamp = $nowtimestamp;
			if ($repeat)
				printf("%s - repeated %u times\n", $msg, ($repeat + 1)>> 1);
			else
				print($msg . PHP_EOL);
			$repeat = 0;
		}
	}

	protected function tick() {
		// PS: It is a quick solution to put this piece of logic here to make multiple connections
		// work without touching the underlying framework of WebSocketServer class!!!

		$repeat = 0;
		while(true) {
			$write = NULL;
			$read = $this->sockets;
			$except = array_values($this->name2stream);
			if (false === ($num_changed_streams = stream_select($read, $write, $except, NULL)))
				echo "Error setting up stream" . PHP_EOL;
			elseif ($num_changed_streams > 0) {
				/* At least on one of the streams something interesting happened */
				foreach ($except as $handle) {
					$key = array_search($handle, $this->name2stream);
					$contents = intval(fgets($handle));
					rewind($handle);
					if ($this->pascal_device[$key]["value"] != $contents) {
						$this->pascal_device[$key]["value"] = $contents;
						// Probe temperature only on protection state changes
						if ($key == "dis_rp") {
							$temp_raw = intval(fgets($this->pascal_device["temp"]["handle"]));
							rewind($this->pascal_device["temp"]["handle"]);
							$this->pascal_device["temp"]["value"] = round($temp_raw * 100 / 3754, 1);
							$logmsg = sprintf("ASB amplifier protection triggered, with temperature %u C", $this->pascal_device["temp"]["value"]);
							$this->throttle_print($logmsg, $repeat);
							$repeat = $repeat + 1;
							system("touch /etc/nyquist/protection");
						}
						// If no users attached just don't process anything
						if (count($this->users) != 0) {
							$out_info = array_column($this->pascal_device, "value", "name");
							$message = json_encode($out_info);
							foreach($this->users as $next)
								$this->send($next,$message);
						}
					}
				}
				// Something on the client socket that needs handling?
				if (count($read) != 0) break;
			}
		}
	}

	protected function process ($user, $message) {
		// Do nothing: This is where the user initiated message can be processed
		// maybe a future use case in a bi-direcional messaging scheme
		// for now we are only push model
	}

	protected function connected ($user) {
		$out_info = array_column($this->pascal_device, "value", "name");
		$message = json_encode($out_info);
		$this->send($user,$message);
	}

	protected function closed ($user) {
		// Do nothing: This is where cleanup would go, in case the user had any sort of
		// open files or other objects associated with them.  This runs after the socket
		// has been closed, so there is no need to clean up the socket itself here.
	}

	function __destruct() {
		foreach ($this->pascal_device as $key => $value) {
			fclose($this->pascal_device[$key]["handle"]);
		}
	}
}

$ass = new ApplianceStatusServer("127.0.0.1","9000");

try {
	$ass->run();
}
catch (Exception $e) {
	$ass->stdout($e->getMessage());
}
