diff --git a/README.md b/README.md new file mode 100644 index 0000000..4dbcb53 --- /dev/null +++ b/README.md @@ -0,0 +1,22 @@ + + +## Related + +- https://github.com/asplendidday/ACbus +- https://github.com/feuerrot/aseag-python +- https://github.com/RobertKrajewski/PyASEAG +- https://github.com/RobinMeis/pyASEAG +- https://github.com/lebu/ASEAG-Busabfahrten +- https://git.rwth-aachen.de/markus.witt/aseag-cli +- https://github.com/stklcode/juraclient +- https://github.com/acmurmeltier69/Alexa-Busauskunft + + +- http://content.tfl.gov.uk/tfl-live-bus-river-bus-arrivals-api-documentation.pdf + + +# API Endpoints + +http://countdown.api.tfl.gov.uk +http://ivu.aseag.de/interfaces/ +http://opendata.avv.de/current_GTFS/AVV_GTFS_mit_SPNV.zip diff --git a/connectiq/.settings/IQ_IDE.prefs b/connectiq/.settings/IQ_IDE.prefs new file mode 100644 index 0000000..10bb291 --- /dev/null +++ b/connectiq/.settings/IQ_IDE.prefs @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +project_manifest=manifest.xml diff --git a/connectiq/bin/ura-busstops-settings.json b/connectiq/bin/ura-busstops-settings.json new file mode 100644 index 0000000..3221ce5 --- /dev/null +++ b/connectiq/bin/ura-busstops-settings.json @@ -0,0 +1 @@ +{"settings":[{"key":"api_url_index","valueType":"number","defaultValue":1,"configTitle":"ApiUrl","configPrompt":"ApiUrlPrompt","configHelpUrl":null,"configError":null,"configType":"list","configReadonly":false,"configRequired":false,"configOptions":[{"display":"ApiUrlLocalhost","value":0},{"display":"ApiUrlNulll","value":1}],"configMin":null,"configMax":null,"configMaxLength":null}],"languages":{"valyrian":{"ApiUrl":"API URL","ApiUrlNulll":"https://connectiq-ura.0l.de","ApiUrlLocalhost":"http://localhost:8080","AppName":"Bus Stops","ApiUrlPrompt":"API URL:"}}} \ No newline at end of file diff --git a/connectiq/manifest.xml b/connectiq/manifest.xml index 646f7b4..3b9f280 100644 --- a/connectiq/manifest.xml +++ b/connectiq/manifest.xml @@ -1,16 +1,16 @@ - - + - - - + - + + + - - - + + deu + eng + diff --git a/connectiq/monkey.jungle b/connectiq/monkey.jungle new file mode 100644 index 0000000..45c1bf8 --- /dev/null +++ b/connectiq/monkey.jungle @@ -0,0 +1,2 @@ +project.manifest = manifest.xml + diff --git a/connectiq/resources/drawables/ASEAG.svg b/connectiq/resources/drawables/ASEAG.svg new file mode 100644 index 0000000..6f04314 --- /dev/null +++ b/connectiq/resources/drawables/ASEAG.svg @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/connectiq/resources/drawables/bus_icon.png b/connectiq/resources/drawables/bus_icon.png new file mode 100644 index 0000000..ec4260a Binary files /dev/null and b/connectiq/resources/drawables/bus_icon.png differ diff --git a/connectiq/resources/drawables/bus_stop_sign.png b/connectiq/resources/drawables/bus_stop_sign.png new file mode 100644 index 0000000..44b9e37 Binary files /dev/null and b/connectiq/resources/drawables/bus_stop_sign.png differ diff --git a/connectiq/resources/drawables/bus_stop_sign.svg b/connectiq/resources/drawables/bus_stop_sign.svg new file mode 100644 index 0000000..fc78172 --- /dev/null +++ b/connectiq/resources/drawables/bus_stop_sign.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/connectiq/resources/drawables/drawables.xml b/connectiq/resources/drawables/drawables.xml index d0de5d6..66f0bd4 100644 --- a/connectiq/resources/drawables/drawables.xml +++ b/connectiq/resources/drawables/drawables.xml @@ -1,3 +1,3 @@ - + \ No newline at end of file diff --git a/connectiq/resources/drawables/launcher_icon.png b/connectiq/resources/drawables/launcher_icon.png index d3594e7..28847fb 100644 Binary files a/connectiq/resources/drawables/launcher_icon.png and b/connectiq/resources/drawables/launcher_icon.png differ diff --git a/connectiq/resources/porperties.xml b/connectiq/resources/porperties.xml new file mode 100644 index 0000000..1316df4 --- /dev/null +++ b/connectiq/resources/porperties.xml @@ -0,0 +1,15 @@ + + + + 1 + + + + + + @Strings.ApiUrlLocalhost + @Strings.ApiUrlNulll + + + + diff --git a/connectiq/resources/strings/strings.xml b/connectiq/resources/strings/strings.xml index ec44fdc..88febd5 100644 --- a/connectiq/resources/strings/strings.xml +++ b/connectiq/resources/strings/strings.xml @@ -1,3 +1,7 @@ - ASEAG Fahrplan + Bus Stops + API URL + API URL: + http://localhost:8080 + https://connectiq-ura.0l.de \ No newline at end of file diff --git a/connectiq/source/BusStopApp.mc b/connectiq/source/BusStopApp.mc index df3e1cc..af55e85 100644 --- a/connectiq/source/BusStopApp.mc +++ b/connectiq/source/BusStopApp.mc @@ -1,54 +1,57 @@ -using Toybox.Application as App; -using Toybox.Position as Position; -using Toybox.Time as Time; +using Toybox.Application; +using Toybox.Position; +using Toybox.Time; +using Toybox.Sensor; +using Toybox.System; -class BusStopApp extends App.AppBase { - var mView; - var mDelegate; +class BusStopApp extends Application.AppBase { + var view; + var delegate; + var position; + var sensors; + var api_url; - function intialize() { - AppBase.initialize(); - } - - //! onStart() is called on application start up - function onStart() { - Position.enableLocationEvents(Position.LOCATION_CONTINUOUS, method(:onPosition)); - } - - //! onStop() is called when your application is exiting - function onStop() { - Position.enableLocationEvents(Position.LOCATION_DISABLE, method(:onPosition)); + var offset = 0; + var stopId = 0; + + function initialize() { + Application.AppBase.initialize(); + + loadSettings(); } - function fakePosition() { - var pos = new Position.Info(); - - pos.accuracy = 0; - pos.altitude = 0; - pos.heading = 0; - pos.speed = 0; - pos.when = Time.now(); - pos.position = new Position.Location({ - :latitude => 50.7855, - :longitude => 6.0541, - :format => :degrees - }); - - mView.setPosition(pos); + function onSettingsChanged() { + loadSettings(); + } + + function loadSettings() { + var api_url_index = 1; //getProperty("api_url_index"); + + switch (api_url_index) { + case 0: + api_url = "https://localhost:8080"; + break; + + case 1: + api_url = "https://connectiq-ura.0l.de"; + break; + } + + System.println(api_url); } - function onPosition(info) { - positionView.setPosition(info); + function onStart(state) { + } + + function onStop(state) { } //! Return the initial view of your application here function getInitialView() { - mView = new BusStopView(); - mDelegate = new BusStopDelegate(); + view = new BusStopView(self); + delegate = new BusStopDelegate(self); - fakePosition(); - - return [ mView, mDelegate ]; + return [ view, delegate ]; } } \ No newline at end of file diff --git a/connectiq/source/BusStopDelegate.mc b/connectiq/source/BusStopDelegate.mc index 6fd792e..d36ec75 100644 --- a/connectiq/source/BusStopDelegate.mc +++ b/connectiq/source/BusStopDelegate.mc @@ -1,10 +1,36 @@ -using Toybox.WatchUi as Ui; -using Toybox.Graphics as Gfx; -using Toybox.System as Sys; +using Toybox.WatchUi; -var last_key = null; - -class InputTestDelegate extends Ui.BehaviorDelegate { +class BusStopDelegate extends WatchUi.BehaviorDelegate { + var app; + function initialize(a) { + WatchUi.BehaviorDelegate.initialize(); + + app = a; + } + + function onNextPage() { + app.view.offset += 1; + + WatchUi.requestUpdate(); + + return true; + } + + function onPreviousPage() { + if (app.view.offset > 0) { + app.view.offset -= 1; + + WatchUi.requestUpdate(); + } + + return true; + } + function onSelect() { + app.offset = 0; + WatchUi.pushView(new DepartureView(app), new DepartureDelegate(app), WatchUi.SLIDE_LEFT); + + return true; + } } \ No newline at end of file diff --git a/connectiq/source/BusStopView.mc b/connectiq/source/BusStopView.mc index 18accd2..182de96 100644 --- a/connectiq/source/BusStopView.mc +++ b/connectiq/source/BusStopView.mc @@ -1,105 +1,205 @@ -using Toybox.WatchUi as Ui; -using Toybox.Graphics as Gfx; -using Toybox.System as Sys; -using Toybox.Lang as Lang; -using Toybox.Communications as Comm; -using Toybox.System as Sys; -using Toybox.Time as Time; -using Toybox.Timer as Timer; +using Toybox.WatchUi; +using Toybox.Graphics; +using Toybox.System; +using Toybox.Lang; +using Toybox.Communications; +using Toybox.System; -class BusStopView extends Ui.View { - var schedule = null; - var tmr; +class CompassDrawable extends WatchUi.Drawable { + + var bearing = 0; + var aWidth = 2; + var aLength = 6; + var aColor = Graphics.COLOR_RED; + + function initialize(settings) { + WatchUi.Drawable.initialize(settings); + + aWidth = settings[:aWidth]; + aLength = settings[:aLength]; + aColor = settings[:aColor]; + } + + function setBearing(b) { + bearing = Math.PI * (b - 90) / 180; + } + + function draw(dc) { + var rPts = new[7]; + var aPts = [ + [-0.5*aLength, -0.5*aWidth], + [ 0.0, -0.5*aWidth], + [ 0.0, -1.0*aWidth], + [ 0.5*aLength, 0], + [ 0.0, 1.0*aWidth], + [ 0.0, 0.5*aWidth], + [-0.5*aLength, 0.5*aWidth] + ]; + + var s = Math.sin(bearing); + var c = Math.cos(bearing); + + for (var i = 0; i < 7; i++) { + var p = aPts[i]; + rPts[i] = [ + locX + width * (p[0] * c - p[1] * s), + locY + height * (p[0] * s + p[1] * c) + ]; + } + + dc.setColor(aColor, Graphics.COLOR_TRANSPARENT); + dc.fillPolygon(rPts); + } +} + + +class BusStopView extends WatchUi.View { + var stops = null; + var app; + var compass; + + var offset = 0; + var sensors; + var position; - function intialize() { - View.initialize(); - } - - //! Load your resources here - function onLayout(dc) { - } - - //! Called when this View is brought to the foreground. Restore - //! the state of this View and prepare it to be shown. This includes - //! loading resources into memory. - function onShow() { - tmr = new Timer.Timer(); - tmr.start(method(:redraw), 1000, true); - } - - //! Called when this View is removed from the screen. Save the - //! state of this View here. This includes freeing resources from - //! memory. - function onHide() { - tmr.stop(); - } - - //! Update the view - function onUpdate(dc) { - var string; - - // Set background color - dc.setColor( Gfx.COLOR_TRANSPARENT, Gfx.COLOR_BLACK ); - dc.clear(); - dc.setColor( Gfx.COLOR_WHITE, Gfx.COLOR_TRANSPARENT ); - - if( schedule != null ) { - var ts = new Time.Moment(schedule[1][4] / 1000); - var duration = Time.now().subtract(ts); - var ts_greg = Time.Gregorian.info(ts, Time.FORMAT_MEDIUM); - var dur_greg = Time.Gregorian.info(new Time.Moment(duration.value()), Time.FORMAT_SHORT); - - /* Timezone offset */ - dur_greg.hour -= 2; - - var arr = Lang.format("$1$:$2$:$3$", [ - (ts_greg.hour - 2), - (ts_greg.min).format("%02u"), - (ts_greg.sec).format("%02u") - ]); - - var due = ""; - if (dur_greg.hour > 0) { - due += dur_greg.hour + " h "; - } - if (dur_greg.min > 0) { - due += dur_greg.min + " min "; - } - if (dur_greg.sec > 0) { - due += dur_greg.sec + " sec "; - } - - dc.drawText( (dc.getWidth() / 2), (dc.getHeight() / 2) - 80, Gfx.FONT_LARGE, schedule[1][2], Gfx.TEXT_JUSTIFY_CENTER ); - dc.drawText( (dc.getWidth() / 2), (dc.getHeight() / 2) - 40, Gfx.FONT_SMALL, schedule[1][1], Gfx.TEXT_JUSTIFY_CENTER ); - dc.drawText( (dc.getWidth() / 2), (dc.getHeight() / 2) - 20, Gfx.FONT_SMALL, schedule[1][3], Gfx.TEXT_JUSTIFY_CENTER ); - dc.drawText( (dc.getWidth() / 2), (dc.getHeight() / 2) + 10, Gfx.FONT_SMALL, arr, Gfx.TEXT_JUSTIFY_CENTER ); - dc.drawText( (dc.getWidth() / 2), (dc.getHeight() / 2) + 40, Gfx.FONT_SMALL, due, Gfx.TEXT_JUSTIFY_CENTER ); - } - else { - dc.drawText( (dc.getWidth() / 2), (dc.getHeight() / 2), Gfx.FONT_SMALL, "No schedule avail", Gfx.TEXT_JUSTIFY_CENTER ); - } - } - - function setPosition(info) { - var url = "http://web.0l.de:8080/"; - var parameters = { "Circle" => info.position.toDegrees()[0] + "," + - info.position.toDegrees()[1] + ",150", - "ReturnList" => "StopPointName,DestinationName,LineName,EstimatedTime" }; - var options = { :method => Comm.HTTP_REQUEST_METHOD_GET }; + function initialize(a) { + WatchUi.View.initialize(); - Comm.makeJsonRequest(url, parameters, options, method(:requestCompleted)); + app = a; + } + + function onLayout(dc) { + var compassX = dc.getWidth() / 2; + var compassY = dc.getHeight() / 2 - 60; + + compass = new CompassDrawable({ + :locX => compassX, + :locY => compassY, + :width => 11, + :height => 11, + :aWidth => 2, + :aLength => 6, + :aColor => Graphics.COLOR_DK_RED + }); + } + + function onShow() { + Position.enableLocationEvents(Position.LOCATION_CONTINUOUS, method(:onPosition)); + + Sensor.setEnabledSensors([Sensor.SENSOR_HEARTRATE]); + Sensor.enableSensorEvents(method(:onSensor)); + } + + function onHide() { + Position.enableLocationEvents(Position.LOCATION_DISABLE, method(:updateStops)); + } + + function onPosition(positionInfo) { + System.println("Position: " + positionInfo.position.toDegrees()); + + position = positionInfo; + updateStops(); + } + + function onSensor(sensorInfo) { + System.println("Heading: " + 180.0 * Math.PI / sensorInfo.heading); + + sensors = sensorInfo; + requestUpdate(); + } + + function onUpdate(dc) { + // Set background color + dc.setColor(Graphics.COLOR_TRANSPARENT, Graphics.COLOR_BLACK); + dc.clear(); + + var midW = dc.getWidth() / 2; + var midH = dc.getHeight() / 2; + var top = Graphics.getFontAscent(Graphics.FONT_SMALL); + + if (stops != null) { + if (stops.size() > 0) { + if (offset >= stops.size()) { + offset = stops.size() - 1; + } + + var stop = stops[offset]; + + var dist = stop["dist"] < 1000 + ? stop["dist"].format("%.0f") + " m" + : (stop["dist"] / 1000).format("%.1f") + " km"; + + var bearing = stop["bearing"].format("%.0f") + "° (" + stop["bearing_str"] + ")"; + + var y = midH; + + var bDeg = stop["bearing"]; + var hDeg = 180.0 * sensors.heading / Math.PI; + + dc.setColor(Graphics.COLOR_RED, Graphics.COLOR_TRANSPARENT); + dc.setPenWidth(4); + dc.drawArc(midW, midH, midW-2, Graphics.ARC_CLOCKWISE, -bDeg+5+90, -bDeg-5+90); + + dc.setColor(Graphics.COLOR_YELLOW, Graphics.COLOR_TRANSPARENT); + dc.setPenWidth(4); + dc.drawArc(midW, midH, midW-2, Graphics.ARC_CLOCKWISE, -hDeg+5, -hDeg-5); + + compass.setBearing(bDeg); + compass.draw(dc); + + dc.setColor(Graphics.COLOR_WHITE, Graphics.COLOR_TRANSPARENT); + dc.drawText(midW, y, Graphics.FONT_MEDIUM, stop["name"], Graphics.TEXT_JUSTIFY_CENTER + Graphics.TEXT_JUSTIFY_VCENTER); + + y += dc.getFontAscent(Graphics.FONT_SMALL) + dc.getFontDescent(Graphics.FONT_MEDIUM); + + if (stop["indicator"].length() > 0) { + dc.setColor(Graphics.COLOR_WHITE, Graphics.COLOR_TRANSPARENT); + dc.drawText(midW, y, Graphics.FONT_SMALL, stop["indicator"], Graphics.TEXT_JUSTIFY_CENTER + Graphics.TEXT_JUSTIFY_VCENTER); + } + + y += dc.getFontAscent(Graphics.FONT_TINY) + dc.getFontDescent(Graphics.FONT_SMALL); + + dc.setColor(Graphics.COLOR_LT_GRAY, Graphics.COLOR_TRANSPARENT); + dc.drawText(midW, y, Graphics.FONT_TINY, dist, Graphics.TEXT_JUSTIFY_CENTER); + + y += dc.getFontAscent(Graphics.FONT_TINY) + dc.getFontDescent(Graphics.FONT_TINY); + + dc.drawText(midW, y, Graphics.FONT_TINY, bearing, Graphics.TEXT_JUSTIFY_CENTER); + + app.stopId = stop["id"]; + } + else { + dc.drawText(midW, midH, Graphics.FONT_SMALL, "No stops found!", Graphics.TEXT_JUSTIFY_CENTER); + } + } + else { + dc.drawText(midW, midH, Graphics.FONT_SMALL, "Loading...", Graphics.TEXT_JUSTIFY_CENTER); + } + } + + function updateStops() { + var degs = position.position.toDegrees(); + var params = { + "latitude" => degs[0], + "longitude" => degs[1], + "distance" => 10000, + "limit" => 50 + }; + + var options = { + :method => Communications.HTTP_REQUEST_METHOD_GET, + :responseType => Communications.HTTP_RESPONSE_CONTENT_TYPE_JSON + }; + + Communications.makeWebRequest(app.api_url + "/stops", params, options, method(:requestCompleted)); } function requestCompleted(responseCode, data) { - Sys.println("Request completed: " + responseCode); + System.println("Request completed: " + responseCode); if (responseCode == 200) { - schedule = data; - redraw(); + stops = data; + WatchUi.requestUpdate(); } } - - function redraw() { - WatchUi.requestUpdate(); - } } diff --git a/connectiq/source/DepartureDelegate.mc b/connectiq/source/DepartureDelegate.mc new file mode 100644 index 0000000..0dbf09a --- /dev/null +++ b/connectiq/source/DepartureDelegate.mc @@ -0,0 +1,36 @@ +using Toybox.WatchUi; + +class DepartureDelegate extends WatchUi.BehaviorDelegate { + var app; + + function initialize(a) { + WatchUi.BehaviorDelegate.initialize(); + + app = a; + } + + function onNextPage() { + app.offset += 1; + + WatchUi.requestUpdate(); + + return true; + } + + function onPreviousPage() { + if (app.offset > 0) { + app.offset -= 1; + + WatchUi.requestUpdate(); + } + + return true; + } + + function onBack() { + app.offset = 0; + WatchUi.popView(WatchUi.SLIDE_RIGHT); + + return true; + } +} \ No newline at end of file diff --git a/connectiq/source/DepartureView.mc b/connectiq/source/DepartureView.mc new file mode 100644 index 0000000..f43cf82 --- /dev/null +++ b/connectiq/source/DepartureView.mc @@ -0,0 +1,84 @@ +using Toybox.WatchUi; +using Toybox.Graphics; +using Toybox.System; +using Toybox.Communications; +using Toybox.Timer; + +class DepartureView extends WatchUi.View { + var departures = null; + var tmr; + var app; + + function initialize(a) { + WatchUi.View.initialize(); + + app = a; + + updateDepartures(); + } + + function onLayout(dc) { + + } + + function onShow() { + tmr = new Timer.Timer(); + tmr.start(method(:updateDepartures), 10000, true); + } + + + function onHide() { + tmr.stop(); + } + + function onUpdate(dc) { + dc.setColor(Graphics.COLOR_TRANSPARENT, Graphics.COLOR_BLACK); + dc.clear(); + dc.setColor(Graphics.COLOR_WHITE, Graphics.COLOR_TRANSPARENT); + + var mid = dc.getWidth() / 2; + var top = Graphics.getFontAscent(Graphics.FONT_SMALL); + + if (departures != null) { + if (departures.size() > 0) { + for (var i = app.offset; i < departures.size(); i++) { + var dep = departures[i]; + + dc.drawText(mid, top, Graphics.FONT_SMALL, dep["line"] + " " + dep["dest"], Graphics.TEXT_JUSTIFY_CENTER); + top += Graphics.getFontAscent(Graphics.FONT_TINY); + dc.drawText(mid, top, Graphics.FONT_XTINY, dep["delta_str"], Graphics.TEXT_JUSTIFY_CENTER); + + top += Graphics.getFontHeight(Graphics.FONT_SMALL); + } + } + else { + dc.drawText( (dc.getWidth() / 2), (dc.getHeight() / 2), Graphics.FONT_SMALL, "No departures!", Graphics.TEXT_JUSTIFY_CENTER ); + } + } + else { + dc.drawText( (dc.getWidth() / 2), (dc.getHeight() / 2), Graphics.FONT_SMALL, "Loading...", Graphics.TEXT_JUSTIFY_CENTER ); + } + } + + function updateDepartures() { + var parameters = { + "id" => app.stopId, + "limit" => 10 + }; + var options = { + :method => Communications.HTTP_REQUEST_METHOD_GET, + :responseType => Communications.HTTP_RESPONSE_CONTENT_TYPE_JSON + }; + + Communications.makeWebRequest(app.api_url + "/departures", parameters, options, method(:requestCompleted)); + } + + function requestCompleted(responseCode, data) { + System.println("Request completed: " + responseCode); + + if (responseCode == 200) { + departures = data; + WatchUi.requestUpdate(); + } + } +} diff --git a/rev-eng/script.sh b/rev-eng/script.sh new file mode 100644 index 0000000..54f45ec --- /dev/null +++ b/rev-eng/script.sh @@ -0,0 +1,8 @@ +API_BASE="http://ivu.aseag.de/interfaces/ura" + +LOC="50.7802655,6.0752138,400" +RL="StopPointName,StopID,StopPointState,StopPointIndicator,Latitude,Longitude,VisitNumber,TripID,VehicleID,LineID,LineName,DirectionID,DestinationName,DestinationText,EstimatedTime,BaseVersion" + +# curl "${API_BASE}/location?searchString=*&maxResults=10000" | jq . + +curl "${API_BASE}/instant_V1?Circle=${LOC}&StopPointState=0&ReturnList=${RL}"