From 2ae556f9d944d2049c737deece44245c32c456e5 Mon Sep 17 00:00:00 2001 From: James Pattinson Date: Fri, 24 Oct 2025 11:07:03 +0100 Subject: [PATCH] Initial commit --- Dockerfile | 11 + README.md | 47 ++++ docker-compose.yaml | 9 + nginx.conf | 25 ++ src/agcs.html | 613 ++++++++++++++++++++++++++++++++++++++++++++ src/archive.php | 42 +++ src/mobile.html | 611 +++++++++++++++++++++++++++++++++++++++++++ src/wlproxy.php | 260 +++++++++++++++++++ 8 files changed, 1618 insertions(+) create mode 100644 Dockerfile create mode 100644 README.md create mode 100644 docker-compose.yaml create mode 100644 nginx.conf create mode 100644 src/agcs.html create mode 100644 src/archive.php create mode 100644 src/mobile.html create mode 100644 src/wlproxy.php diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..bd9673a --- /dev/null +++ b/Dockerfile @@ -0,0 +1,11 @@ +FROM alpine:latest + +RUN apk add --no-cache nginx php83 php83-fpm php83-mysqli php83-curl + +RUN mkdir -p /run/nginx + +COPY nginx.conf /etc/nginx/nginx.conf + +EXPOSE 80 + +CMD php-fpm83 -D && nginx -g 'daemon off;' \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..64cd912 --- /dev/null +++ b/README.md @@ -0,0 +1,47 @@ +# AGCS v2 Weather Display + +A self-contained, dockerized weather display application with desktop and mobile views. + +## Features + +- Real-time weather data display via MQTT +- Compass visualization for wind direction +- Archive data retrieval from database +- WeatherLink API proxy +- Responsive design for mobile and desktop + +## Setup + +1. Ensure Docker and Docker Compose are installed. + +2. Clone or copy this project to your local machine. + +3. Run the application: + + ```bash + docker-compose up --build + ``` + +4. Access the application: + - Desktop view: http://localhost:8080/agcsnew.html + - Mobile view: http://localhost:8080/mobile.html + - Archive: http://localhost:8080/archive.php + +## Configuration + +- MQTT broker: Configured to connect to 'ikarus.egfh.internal:8083' +- Database: MySQL with credentials (user: weewx, pass: weewx, db: weewx) +- WeatherLink API: Proxy configured with API key and secret + +## Troubleshooting + +- If MQTT connection fails, ensure the broker is running and accessible. +- For database issues, check if the db service is up and data is populated. +- PHP errors: Check container logs with `docker-compose logs web` + +## Files + +- `agcsnew.html`: Desktop weather display +- `mobile.html`: Mobile-optimized weather display +- `archive.php`: PHP script for database archive data +- `wlproxy.php`: Proxy for WeatherLink API \ No newline at end of file diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..0c97448 --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,9 @@ +version: '3.8' + +services: + web: + build: . + ports: + - "8088:80" + volumes: + - ./src:/var/www/html \ No newline at end of file diff --git a/nginx.conf b/nginx.conf new file mode 100644 index 0000000..f6268e1 --- /dev/null +++ b/nginx.conf @@ -0,0 +1,25 @@ +events { + +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + server { + listen 80; + root /var/www/html; + index index.html index.php; + + location / { + try_files $uri $uri/ =404; + } + + location ~ \.php$ { + fastcgi_pass 127.0.0.1:9000; + fastcgi_index index.php; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + include fastcgi_params; + } + } +} \ No newline at end of file diff --git a/src/agcs.html b/src/agcs.html new file mode 100644 index 0000000..6f44ca8 --- /dev/null +++ b/src/agcs.html @@ -0,0 +1,613 @@ + + + + + + EGFH Tower + + + + + + + + + + +
+
QNH
+
QFE
+
+
+
XXXX
+
XXXX
+
+
+
+
Surface Wind
1
Instant Wind
1
+
+
+ +
+
+
2min Gust
+
OAT
+
+ +
+
XXX
+
XXX
+
+
+ +
+
QNH: XXX
+
+
+ +
+
+
+ +

+ + + + + + diff --git a/src/archive.php b/src/archive.php new file mode 100644 index 0000000..ce01b68 --- /dev/null +++ b/src/archive.php @@ -0,0 +1,42 @@ +connect_error) { + die("Connection failed: " . $conn->connect_error); + } + + return $conn; + +} + +$conn = connectDb(); + +$sql = "SELECT dateTime,windSpeed,windDir,windGust,windGustDir,outTemp FROM archive ORDER BY dateTime DESC LIMIT 1"; +$result = $conn->query($sql); + +$data = []; +if ($result->num_rows > 0) { + while ($row = $result->fetch_assoc()) { + $data[] = $row; + } +} + +$conn->close(); + +header('Content-Type: application/json'); +echo json_encode($data[0], JSON_PRETTY_PRINT); + +?> diff --git a/src/mobile.html b/src/mobile.html new file mode 100644 index 0000000..a4b171b --- /dev/null +++ b/src/mobile.html @@ -0,0 +1,611 @@ + + + + + + EGFH Info + + + + + + + + +

Swansea EGFH - Live Conditions

+ +
+
XXXX
+
XXXX
+
+ +
+
Sfc Wind
XXX/XX
+
Inst Wind
XXX/XX
+
+ +
+ +
+
2min Gust
1
+
OAT
1
+
+ +
Do not rely on this information for flight safety
+ +
+
+
+ + + + diff --git a/src/wlproxy.php b/src/wlproxy.php new file mode 100644 index 0000000..2709bc2 --- /dev/null +++ b/src/wlproxy.php @@ -0,0 +1,260 @@ + and +// are disabled by default, see for more information. +// callback - If specified, the response JSON will be wrapped in this named +// function call. This parameter and are disabled by +// default, see for more information. +// user_agent - This value will be sent to the remote URL request as the +// `User-Agent:` HTTP request header. If omitted, the browser user agent +// will be passed through. +// send_cookies - If send_cookies=1, all cookies will be forwarded through to +// the remote URL request. +// send_session - If send_session=1 and send_cookies=1, the SID cookie will be +// forwarded through to the remote URL request. +// full_headers - If a JSON request and full_headers=1, the JSON response will +// contain detailed header information. +// full_status - If a JSON request and full_status=1, the JSON response will +// contain detailed cURL status information, otherwise it will just contain +// the `http_code` property. +// +// Topic: POST Parameters +// +// All POST parameters are automatically passed through to the remote URL +// request. +// +// Topic: JSON requests +// +// This request will return the contents of the specified url in JSON format. +// +// Request: +// +// > ba-simple-proxy.php?url=http://example.com/ +// +// Response: +// +// > { "contents": "...", "headers": {...}, "status": {...} } +// +// JSON object properties: +// +// contents - (String) The contents of the remote URL resource. +// headers - (Object) A hash of HTTP headers returned by the remote URL +// resource. +// status - (Object) A hash of status codes returned by cURL. +// +// Topic: JSONP requests +// +// This request will return the contents of the specified url in JSONP format +// (but only if $enable_jsonp is enabled in the PHP script). +// +// Request: +// +// > ba-simple-proxy.php?url=http://example.com/&callback=foo +// +// Response: +// +// > foo({ "contents": "...", "headers": {...}, "status": {...} }) +// +// JSON object properties: +// +// contents - (String) The contents of the remote URL resource. +// headers - (Object) A hash of HTTP headers returned by the remote URL +// resource. +// status - (Object) A hash of status codes returned by cURL. +// +// Topic: Native requests +// +// This request will return the contents of the specified url in the format it +// was received in, including the same content-type and other headers (but only +// if $enable_native is enabled in the PHP script). +// +// Request: +// +// > ba-simple-proxy.php?url=http://example.com/&mode=native +// +// Response: +// +// > ... +// +// Topic: Notes +// +// * Assumes magic_quotes_gpc = Off in php.ini +// +// Topic: Configuration Options +// +// These variables can be manually edited in the PHP file if necessary. +// +// $enable_jsonp - Only enable if you really need to. If you +// install this script on the same server as the page you're calling it +// from, plain JSON will work. Defaults to false. +// $enable_native - You can enable , but you should only do +// this if you also whitelist specific URLs using $valid_url_regex, to avoid +// possible XSS vulnerabilities. Defaults to false. +// $valid_url_regex - This regex is matched against the url parameter to +// ensure that it is valid. This setting only needs to be used if either +// $enable_jsonp or $enable_native are enabled. Defaults to '/.*/' which +// validates all URLs. +// +// ############################################################################ + +// Change these configuration options if needed, see above descriptions for info. +$enable_jsonp = false; +$enable_native = false; +$valid_url_regex = '/.*/'; + +$apikey = "beliqdwnzkkeqdar4sb9xfhxzdv3rf03"; +$apisecret = "zpdi1jrycd17gjmx0ldqydj9meboavwg"; + +$url = 'https://api.weatherlink.com/v2/' . $_GET['api'] . "?api-key=" . $apikey; + +// ############################################################################ + +if ( !$url ) { + + // Passed url not specified. + $contents = 'ERROR: url not specified'; + $status = array( 'http_code' => 'ERROR' ); + +} else if ( !preg_match( $valid_url_regex, $url ) ) { + + // Passed url doesn't match $valid_url_regex. + $contents = 'ERROR: invalid url'; + $status = array( 'http_code' => 'ERROR' ); + +} else { + $ch = curl_init( $url ); + + if ( strtolower($_SERVER['REQUEST_METHOD']) == 'post' ) { + curl_setopt( $ch, CURLOPT_POST, true ); + curl_setopt( $ch, CURLOPT_POSTFIELDS, $_POST ); + } + + if ( $_GET['send_cookies'] ) { + $cookie = array(); + foreach ( $_COOKIE as $key => $value ) { + $cookie[] = $key . '=' . $value; + } + if ( $_GET['send_session'] ) { + $cookie[] = SID; + } + $cookie = implode( '; ', $cookie ); + + curl_setopt( $ch, CURLOPT_COOKIE, $cookie ); + } + + curl_setopt( $ch, CURLOPT_FOLLOWLOCATION, true ); + curl_setopt( $ch, CURLOPT_HEADER, true ); + curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true ); + + curl_setopt($ch, CURLOPT_HTTPHEADER, ["X-Api-Secret: $apisecret"] ); + + curl_setopt( $ch, CURLOPT_USERAGENT, $_GET['user_agent'] ? $_GET['user_agent'] : $_SERVER['HTTP_USER_AGENT'] ); + + list( $header, $contents ) = preg_split( '/([\r\n][\r\n])\\1/', curl_exec( $ch ), 2 ); + + $status = curl_getinfo( $ch ); + + curl_close( $ch ); +} + +// Split header text into an array. +$header_text = preg_split( '/[\r\n]+/', $header ); + +if ( $_GET['mode'] == 'native' ) { + if ( !$enable_native ) { + $contents = 'ERROR: invalid mode'; + $status = array( 'http_code' => 'ERROR' ); + } + + // Propagate headers to response. + foreach ( $header_text as $header ) { + if ( preg_match( '/^(?:Content-Type|Content-Language|Set-Cookie):/i', $header ) ) { + header( $header ); + } + } + + print $contents; + +} else { + + // $data will be serialized into JSON data. + $data = array(); + + // Propagate all HTTP headers into the JSON data object. + if ( $_GET['full_headers'] ) { + $data['headers'] = array(); + + foreach ( $header_text as $header ) { + preg_match( '/^(.+?):\s+(.*)$/', $header, $matches ); + if ( $matches ) { + $data['headers'][ $matches[1] ] = $matches[2]; + } + } + } + + // Propagate all cURL request / response info to the JSON data object. + if ( $_GET['full_status'] ) { + $data['status'] = $status; + } else { + $data['status'] = array(); + $data['status']['http_code'] = $status['http_code']; + } + + // Set the JSON data object contents, decoding it from JSON if possible. + $decoded_json = json_decode( $contents ); + $data['contents'] = $decoded_json ? $decoded_json : $contents; + + // Generate appropriate content-type header. + $is_xhr = strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest'; + header( 'Content-type: application/' . ( $is_xhr ? 'json' : 'x-javascript' ) ); + + // Get JSONP callback. + $jsonp_callback = $enable_jsonp && isset($_GET['callback']) ? $_GET['callback'] : null; + + // Generate JSON/JSONP string + $json = json_encode( $data ); + + print $jsonp_callback ? "$jsonp_callback($json)" : $json; + +} + +?>