1
0
mirror of https://github.com/EDCD/EDMarketConnector.git synced 2025-04-21 11:27:38 +03:00

Monitor the journal file and send events to EDDN

Sends 'FSDJump', 'Docked' and 'Scan' events in draft
http://schemas.elite-markets.net/eddn/journal/1 format.
This commit is contained in:
Jonathan Harris 2016-08-22 18:40:40 +01:00
parent 0ec2a532a0
commit 4583f0e316
9 changed files with 215 additions and 138 deletions

@ -9,7 +9,8 @@ from os import mkdir
from os.path import expanduser, isdir, join
import re
import requests
from time import time, localtime, strftime
from time import time, localtime, strftime, strptime
from calendar import timegm
import Tkinter as tk
import ttk
@ -75,9 +76,6 @@ class AppWindow:
self.w.rowconfigure(0, weight=1)
self.w.columnconfigure(0, weight=1)
# Special handling for overrideredict
self.w.bind("<Map>", self.onmap)
plug.load_plugins()
if platform != 'darwin':
@ -242,6 +240,9 @@ class AppWindow:
theme.register_highlight(self.station)
theme.apply(self.w)
# Special handling for overrideredict
self.w.bind("<Map>", self.onmap)
# Load updater after UI creation (for WinSparkle)
import update
self.updater = update.Updater(self.w)
@ -252,8 +253,7 @@ class AppWindow:
hotkeymgr.register(self.w, config.getint('hotkey_code'), config.getint('hotkey_mods'))
# Install log monitoring
monitor.set_callback('Dock', self.getandsend)
monitor.set_callback('Jump', self.system_change)
self.w.bind_all('<<JournalEvent>>', self.journal_event) # user-generated
monitor.start(self.w)
# First run
@ -394,9 +394,14 @@ class AppWindow:
pass
elif not data['commander'].get('docked'):
# signal as error because the user might actually be docked but the server hosting the Companion API hasn't caught up
if not self.status['text']:
self.status['text'] = _("You're not docked at a station!")
if not event and not retrying:
# Silently retry if we got here by 'Automatically update on docking' and the server hasn't caught up
self.w.after(int(SERVER_RETRY * 1000), lambda:self.getandsend(event, True))
return # early exit to avoid starting cooldown count
else:
# Signal as error because the user might actually be docked but the server hosting the Companion API hasn't caught up
if not self.status['text']:
self.status['text'] = _("You're not docked at a station!")
else:
# Finally - the data looks sane and we're docked at a station
@ -479,31 +484,62 @@ class AppWindow:
except:
pass
def system_change(self, event, timestamp, system, coordinates):
# Handle event(s) from the journal
def journal_event(self, event):
while True:
entry = monitor.get_entry()
if entry is None:
return
system_changed = monitor.system and self.system['text'] != monitor.system
station_changed = monitor.station and self.station['text'] != monitor.station
if self.system['text'] != system:
self.system['text'] = system
# Update main window
self.cmdr['text'] = monitor.cmdr or ''
self.system['text'] = monitor.system or ''
self.station['text'] = monitor.station or (EDDB.system(monitor.system) and self.STATION_UNDOCKED or '')
self.system['image'] = ''
self.station['text'] = EDDB.system(system) and self.STATION_UNDOCKED or ''
plug.notify_journal_entry(monitor.cmdr, monitor.system, monitor.station, entry)
plug.notify_system_changed(timestamp, system, coordinates)
if system_changed:
self.system['image'] = ''
timestamp = timegm(strptime(entry['timestamp'], '%Y-%m-%dT%H:%M:%SZ'))
if config.getint('output') & config.OUT_SYS_EDSM:
try:
self.status['text'] = _('Sending data to EDSM...')
self.w.update_idletasks()
self.edsm.writelog(timestamp, system, coordinates) # Do EDSM lookup during EDSM export
# Backwards compatibility
plug.notify_system_changed(timestamp, monitor.system, monitor.coordinates)
# Update EDSM if we have coordinates - i.e. Location or FSDJump events
if config.getint('output') & config.OUT_SYS_EDSM and monitor.coordinates:
try:
self.status['text'] = _('Sending data to EDSM...')
self.w.update_idletasks()
self.edsm.writelog(timestamp, monitor.system, monitor.coordinates)
self.status['text'] = strftime(_('Last updated at {HH}:{MM}:{SS}').format(HH='%H', MM='%M', SS='%S').encode('utf-8'), localtime(timestamp)).decode('utf-8')
except Exception as e:
if __debug__: print_exc()
self.status['text'] = unicode(e)
if not config.getint('hotkey_mute'):
hotkeymgr.play_bad()
else:
self.edsm.link(monitor.system)
self.status['text'] = strftime(_('Last updated at {HH}:{MM}:{SS}').format(HH='%H', MM='%M', SS='%S').encode('utf-8'), localtime(timestamp)).decode('utf-8')
except Exception as e:
if __debug__: print_exc()
self.status['text'] = unicode(e)
if not config.getint('hotkey_mute'):
hotkeymgr.play_bad()
else:
self.edsm.link(system)
self.status['text'] = strftime(_('Last updated at {HH}:{MM}:{SS}').format(HH='%H', MM='%M', SS='%S').encode('utf-8'), localtime(timestamp)).decode('utf-8')
self.edsmpoll()
self.edsmpoll()
# Send interesting events to EDDN
if (config.getint('output') & config.OUT_SYS_EDDN and monitor.cmdr and
(entry['event'] == 'FSDJump' and system_changed or
entry['event'] == 'Docked' and station_changed or
entry['event'] == 'Scan')):
# strip out properties disallowed by the schema
for thing in ['CockpitBreach', 'BoostUsed', 'FuelLevel', 'FuelUsed', 'JumpDist']:
entry.pop(thing, None)
for thing in entry.keys():
if thing.endswith('_Localised'):
entry.pop(thing, None)
eddn.export_journal_entry(monitor.cmdr, monitor.is_beta, entry)
# Auto-Update after docking
if station_changed and config.getint('output') & (config.OUT_MKT_EDDN|config.OUT_MKT_MANUAL) == config.OUT_MKT_EDDN and entry['event'] == 'Docked':
self.w.after(int(SERVER_RETRY * 1000), self.getandsend)
def edsmpoll(self):
result = self.edsm.result

@ -68,21 +68,23 @@ Once you have created your plugin and EDMC has loaded it there are two other fun
Your events all get called on the main tkinter loop so be sure not to block for very long or the EDMC will appear to freeze. If you have a long running operation then you should take a look at how to do background updates in tkinter - http://effbot.org/zone/tkinter-threads.htm
### Arriving in a System
### Journal Entry
This gets called when EDMC uses the netlog to notice that you have arrived at a new star system.
This gets called when EDMC sees a new entry in the game's journal.
```
def system_changed(timestamp, system, coordinates):
"""
We arrived at a new system!
"""
sys.stderr.write("{} {}".format(timestamp, system))
def journal_entry(cmdr, system, station, entry):
if entry['event'] == 'FSDJump':
# We arrived at a new system!
if 'StarPos' in entry:
sys.stderr.write("Arrived at {} ({},{},{})\n".format(entry['StarSystem'], *tuple(entry['StarPos'])))
else:
sys.stderr.write("Arrived at {}\n".format(entry['StarSystem']))
```
### Getting Commander Data
This gets called when EDMC has just fetched fresh data from Frontier's servers.
This gets called when EDMC has just fetched fresh Cmdr and station data from Frontier's servers.
```
def cmdr_data(data):

@ -1,10 +1,10 @@
Elite: Dangerous Market Connector (EDMC)
========
This app downloads your data and station data from the game [Elite: Dangerous](https://www.elitedangerous.com/) and, at your choice, either:
This app downloads your Cmdr's data, system, scan and station data from the game [Elite: Dangerous](https://www.elitedangerous.com/) and, at your choice, either:
* sends the station commodity market prices and other station data to the [Elite Dangerous Data Network](http://eddn-gateway.elite-markets.net/) (“EDDN”) from where you and others can use it via online trading and shopping tools such as [eddb](http://eddb.io/), [Elite Trade Net](http://etn.io/), [Inara](http://inara.cz), [ED-TD](http://ed-td.space/), [Thrudd's Trading Tools](http://www.elitetradingtool.co.uk/), [Roguey's](http://roguey.co.uk/elite-dangerous/), etc.
* saves the station market prices to files on your computer that you can load into trading tools such as [Slopey's BPC Market Tool](https://forums.frontier.co.uk/showthread.php?t=76081), [Trade Dangerous](https://bitbucket.org/kfsone/tradedangerous/wiki/Home), [Thrudd's Trading Tools](http://www.elitetradingtool.co.uk/), [Inara](http://inara.cz), [mEDI's Elite Tools](https://github.com/mEDI-S/mEDI_s-Elite-Tools), etc.
* sends the station commodity market prices, other station data and system and scan data to the [Elite Dangerous Data Network](http://eddn-gateway.elite-markets.net/) (“EDDN”) from where you and others can use it via online trading, prospecting and shopping tools such as [eddb](http://eddb.io/), [Elite Trade Net](http://etn.io/), [Inara](http://inara.cz), [ED-TD](http://ed-td.space/), [Thrudd's Trading Tools](http://www.elitetradingtool.co.uk/), [Roguey's](http://roguey.co.uk/elite-dangerous/), etc.
* saves the station commodity market prices to files on your computer that you can load into trading tools such as [Slopey's BPC Market Tool](https://forums.frontier.co.uk/showthread.php?t=76081), [Trade Dangerous](https://bitbucket.org/kfsone/tradedangerous/wiki/Home), [Thrudd's Trading Tools](http://www.elitetradingtool.co.uk/), [Inara](http://inara.cz), [mEDI's Elite Tools](https://github.com/mEDI-S/mEDI_s-Elite-Tools), etc.
* saves a record of your ship loadout to files on your computer that you can load into outfitting tools such as [E:D&nbsp;Shipyard](http://www.edshipyard.com), [Coriolis](http://coriolis.io) or [Elite Trade Net](http://etn.io/).
* sends your flight log to [Elite:&nbsp;Dangerous Star Map](http://www.edsm.net/).
@ -46,7 +46,7 @@ Setup
The first time that you run the app you are prompted for your username and password. This is the same username and password
combination that you use to log into the Elite: Dangerous launcher, and is required so that the Frontier servers can send the app *your* data and the data for the station that *you* are docked at.
You can also choose here what data to save (refer to the next section for details), whether to “Update” automatically on docking and/or with a hotkey, and whether to attach your Cmdr name or a [pseudo-anonymized](http://en.wikipedia.org/wiki/Pseudonymity) ID to the data.
You can also choose here what data to save (refer to the next section for details), whether to “Update” Cmdr and station data automatically on docking and/or with a hotkey, and whether to attach your Cmdr name or a [pseudo-anonymized](http://en.wikipedia.org/wiki/Pseudonymity) ID to the data.
You will be prompted to authenticate with a “verification code”, which you will shortly receive by email from Frontier.
Note that each “verification code” is one-time only - if you enter the code incorrectly or quit the app before
@ -59,8 +59,7 @@ option EDMarketConnector → Preferences (Mac) or File → Settings (Windows) an
This app can save a variety of data in a variety of formats:
* Station data
* Elite Dangerous Data Network - sends station commodity market, outfitting and shipyard data to “[EDDN](http://eddn-gateway.elite-markets.net/)” from where you and others can use it via online trading tools such as [eddb](http://eddb.io/), [Elite Trade Net](http://etn.io/), [Inara](http://inara.cz), [ED-TD](http://ed-td.space/), [Thrudd's Trading Tools](http://www.elitetradingtool.co.uk/), [Roguey's](http://roguey.co.uk/elite-dangerous/), etc.
* Market data
* Slopey's BPC format file - saves commodity market data as files that you can load into [Slopey's BPC Market Tool](https://forums.frontier.co.uk/showthread.php?t=76081).
* Trade Dangerous format file - saves commodity market data as files that you can load into [Trade Dangerous](https://bitbucket.org/kfsone/tradedangerous/wiki/Home).
* CSV format file - saves commodity market data as files that you can upload to [Thrudd's Trading Tools](http://www.elitetradingtool.co.uk/), [Inara](http://inara.cz) or [mEDI's Elite Tools](https://github.com/mEDI-S/mEDI_s-Elite-Tools).
@ -72,6 +71,13 @@ By default these files will be placed in your Documents folder. Since this app w
Some options work by reading the Elite: Dangerous game's log files. Normally this app will find the log files but if you find some options greyed-out then adjust the “E:D log file location” setting described [below](#doesnt-track-systems-visited).
### EDDN
* Station data
* Sends station commodity market, outfitting and shipyard data to “[EDDN](http://eddn-gateway.elite-markets.net/)” from where you and others can use it via online trading tools such as [eddb](http://eddb.io/), [Elite Trade Net](http://etn.io/), [Inara](http://inara.cz), [ED-TD](http://ed-td.space/), [Thrudd's Trading Tools](http://www.elitetradingtool.co.uk/), [Roguey's](http://roguey.co.uk/elite-dangerous/), etc.
* System and scan data
* Sends general system information and the results of your detailed planet scans to “[EDDN](http://eddn-gateway.elite-markets.net/)” from where you and others can use it via online prospecting tools such as [eddb](http://eddb.io/), [Inara](http://inara.cz), etc.
### EDSM
You can send a record of your location to [Elite: Dangerous Star Map](http://www.edsm.net/) where you can view your flight log under My&nbsp;account &rarr; Exploration&nbsp;Logs and optionally add private comments about a system. You will need to register for an account and then follow the “[Elite Dangerous Star Map credentials](http://www.edsm.net/settings/api)” link to obtain your API key.

@ -86,6 +86,7 @@ class Config:
OUT_SYS_EDSM = 256
# OUT_SYS_AUTO = 512 # Now always automatic
OUT_MKT_MANUAL = 1024
OUT_SYS_EDDN = 2048
if platform=='darwin':

@ -32,7 +32,8 @@ class _EDDN:
'softwareVersion' : appversion,
'uploaderID' : config.getint('anonymous') and hashlib.md5(cmdr.encode('utf-8')).hexdigest() or cmdr.encode('utf-8'),
}
msg['message']['timestamp'] = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime(config.getint('querytime') or int(time.time())))
if not msg['message'].get('timestamp'): # already present in journal messages
msg['message']['timestamp'] = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime(config.getint('querytime') or int(time.time())))
r = self.session.post(self.UPLOAD, data=json.dumps(msg), timeout=timeout)
if __debug__ and r.status_code != requests.codes.ok:
@ -94,5 +95,11 @@ class _EDDN:
}
})
def export_journal_entry(self, cmdr, is_beta, entry):
self.send(cmdr, {
'$schemaRef' : 'http://schemas.elite-markets.net/eddn/journal/1' + (is_beta and '/test' or ''),
'message' : entry
})
# singleton
eddn = _EDDN()

@ -1,4 +1,6 @@
import atexit
from collections import OrderedDict
import json
import re
import threading
from os import listdir, pardir, rename, unlink, SEEK_SET, SEEK_CUR, SEEK_END
@ -6,8 +8,7 @@ from os.path import basename, exists, isdir, isfile, join
from platform import machine
import sys
from sys import platform
from time import strptime, localtime, mktime, sleep, time
from datetime import datetime
from time import sleep
if __debug__:
from traceback import print_exc
@ -68,11 +69,18 @@ class EDLogs(FileSystemEventHandler):
self.currentdir = None # The actual logdir that we're monitoring
self.logfile = None
self.observer = None
self.observed = None
self.observed = None # a watchdog ObservedWatch, or None if polling
self.thread = None
self.callbacks = { 'Jump': None, 'Dock': None }
self.last_event = None # for communicating the Jump event
self.event_queue = [] # For communicating journal entries back to main thread
# Context for journal handling
self.version = None
self.is_beta = False
self.mode = None
self.cmdr = None
self.system = None
self.station = None
self.coordinates = None
def set_callback(self, name, callback):
if name in self.callbacks:
@ -89,9 +97,6 @@ class EDLogs(FileSystemEventHandler):
self.stop()
self.currentdir = logdir
self.root.bind_all('<<MonitorJump>>', self.jump) # user-generated
self.root.bind_all('<<MonitorDock>>', self.dock) # user-generated
# Set up a watchog observer. This is low overhead so is left running irrespective of whether monitoring is desired.
# File system events are unreliable/non-existent over network drives on Linux.
# We can't easily tell whether a path points to a network drive, so assume
@ -108,17 +113,17 @@ class EDLogs(FileSystemEventHandler):
# Latest pre-existing logfile - e.g. if E:D is already running. Assumes logs sort alphabetically.
try:
logfiles = sorted([x for x in listdir(logdir) if x.startswith('netLog.')])
self.logfile = logfiles and join(logdir, logfiles[-1]) or None
logfiles = sorted([x for x in listdir(self.currentdir) if x.startswith('Journal.')])
self.logfile = logfiles and join(self.currentdir, logfiles[-1]) or None
except:
self.logfile = None
if __debug__:
print '%s "%s"' % (polling and 'Polling' or 'Monitoring', logdir)
print '%s "%s"' % (polling and 'Polling' or 'Monitoring', self.currentdir)
print 'Start logfile "%s"' % self.logfile
if not self.running():
self.thread = threading.Thread(target = self.worker, name = 'netLog worker')
self.thread = threading.Thread(target = self.worker, name = 'Journal worker')
self.thread.daemon = True
self.thread.start()
@ -128,18 +133,19 @@ class EDLogs(FileSystemEventHandler):
if __debug__:
print 'Stopping monitoring'
self.currentdir = None
self.version = self.mode = self.cmdr = self.system = self.station = self.coordinates = None
self.is_beta = False
if self.observed:
self.observed = None
self.observer.unschedule_all()
self.thread = None # Orphan the worker thread - will terminate at next poll
self.last_event = None
def running(self):
return self.thread and self.thread.is_alive()
def on_created(self, event):
# watchdog callback, e.g. client (re)started.
if not event.is_directory and basename(event.src_path).startswith('netLog.'):
if not event.is_directory and basename(event.src_path).startswith('Journal.'):
self.logfile = event.src_path
def worker(self):
@ -147,30 +153,16 @@ class EDLogs(FileSystemEventHandler):
# event_generate() is the only safe way to poke the main thread from this thread:
# https://mail.python.org/pipermail/tkinter-discuss/2013-November/003522.html
# e.g.:
# "{18:00:41} System:"Shinrarta Dezhra" StarPos:(55.719,17.594,27.156)ly NormalFlight\r\n"
# or with verboseLogging:
# "{17:20:18} System:"Shinrarta Dezhra" StarPos:(55.719,17.594,27.156)ly Body:69 RelPos:(0.334918,1.20754,1.23625)km NormalFlight\r\n"
# or:
# "... Supercruise\r\n"
# Note that system name may contain parantheses, e.g. "Pipe (stem) Sector PI-T c3-5".
regexp = re.compile(r'\{(.+)\} System:"(.+)" StarPos:\((.+),(.+),(.+)\)ly.* (\S+)') # (localtime, system, x, y, z, context)
# e.g.:
# "{14:42:11} GetSafeUniversalAddress Station Count 1 moved 0 Docked Not Landed\r\n"
# or:
# "... Undocked Landed\r\n"
# Don't use the simpler "Commander Put ..." message since its more likely to be delayed.
dockre = re.compile(r'\{(.+)\} GetSafeUniversalAddress Station Count \d+ moved \d+ (\S+) ([^\r\n]+)') # (localtime, docked_status, landed_status)
docked = False # Whether we're docked
updated = False # Whether we've sent an update since we docked
# Seek to the end of the latest log file
logfile = self.logfile
if logfile:
loghandle = open(logfile, 'r')
loghandle.seek(0, SEEK_END) # seek to EOF
for line in loghandle:
try:
self.parse_entry(line) # Some events are of interest even in the past
except:
if __debug__:
print 'Invalid journal entry "%s"' % repr(line)
else:
loghandle = None
@ -179,19 +171,13 @@ class EDLogs(FileSystemEventHandler):
while True:
if docked and not updated and not config.getint('output') & config.OUT_MKT_MANUAL:
self.root.event_generate('<<MonitorDock>>', when="tail")
updated = True
if __debug__:
print "%s :\t%s %s" % ('Updated', docked and " docked" or "!docked", updated and " updated" or "!updated")
# Check whether new log file started, e.g. client (re)started.
if emitter and emitter.is_alive():
newlogfile = self.logfile # updated by on_created watchdog callback
else:
# Poll
try:
logfiles = sorted([x for x in listdir(self.currentdir) if x.startswith('netLog.')])
logfiles = sorted([x for x in listdir(self.currentdir) if x.startswith('Journal.')])
newlogfile = logfiles and join(self.currentdir, logfiles[-1]) or None
except:
if __debug__: print_exc()
@ -207,37 +193,11 @@ class EDLogs(FileSystemEventHandler):
print 'New logfile "%s"' % logfile
if logfile:
system = visited = coordinates = None
loghandle.seek(0, SEEK_CUR) # reset EOF flag
for line in loghandle:
match = regexp.match(line)
if match:
(visited, system, x, y, z, context) = match.groups()
if system == 'ProvingGround':
system = 'CQC'
coordinates = (float(x), float(y), float(z))
else:
match = dockre.match(line)
if match:
if match.group(2) == 'Undocked':
docked = updated = False
elif match.group(2) == 'Docked':
docked = True
# do nothing now in case the API server is lagging, but update on next poll
if __debug__:
print "%s :\t%s %s" % (match.group(2), docked and " docked" or "!docked", updated and " updated" or "!updated")
if system and not docked and config.getint('output'):
# Convert local time string to UTC date and time
visited_struct = strptime(visited, '%H:%M:%S')
now = localtime()
if now.tm_hour == 0 and visited_struct.tm_hour == 23:
# Crossed midnight between timestamp and poll
now = localtime(time()-12*60*60) # yesterday
time_struct = datetime(now.tm_year, now.tm_mon, now.tm_mday, visited_struct.tm_hour, visited_struct.tm_min, visited_struct.tm_sec).timetuple() # still local time
self.last_event = (mktime(time_struct), system, coordinates)
self.root.event_generate('<<MonitorJump>>', when="tail")
self.event_queue.append(line)
if self.event_queue:
self.root.event_generate('<<JournalEvent>>', when="tail")
sleep(self._POLL)
@ -245,15 +205,39 @@ class EDLogs(FileSystemEventHandler):
if threading.current_thread() != self.thread:
return # Terminate
def jump(self, event):
# Called from Tkinter's main loop
if self.callbacks['Jump'] and self.last_event:
self.callbacks['Jump'](event, *self.last_event)
def parse_entry(self, line):
try:
entry = json.loads(line, object_pairs_hook=OrderedDict) # Preserve property order because why not?
entry['timestamp'] # we expect this to exist
if entry['event'] == 'Fileheader': # XXX or 'fileheader' ?
self.version = entry['gameversion']
self.is_beta = 'beta' in entry['gameversion'].lower()
elif entry['event'] == 'LoadGame':
self.cmdr = entry['Commander']
self.mode = entry['GameMode']
elif entry['event'] == 'NewCommander':
self.cmdr = entry['Name']
elif entry['event'] in ['Undocked']:
self.station = None
self.coordinates = None
elif entry['event'] in ['Location', 'FSDJump', 'Docked']:
if 'StarPos' in entry:
self.coordinates = tuple(entry['StarPos'])
elif self.system != entry['StarSystem']:
self.coordinates = None # Docked event doesn't include coordinates
self.system = entry['StarSystem'] == 'ProvingGround' and 'CQC' or entry['StarSystem']
self.station = entry.get('StationName') # May be None
return entry
except:
if __debug__:
print 'Invalid journal entry "%s"' % repr(line)
return { 'event': None }
def dock(self, event):
# Called from Tkinter's main loop
if self.callbacks['Dock']:
self.callbacks['Dock']()
def get_entry(self):
if not self.event_queue:
return None
else:
return self.parse_entry(self.event_queue.pop(0))
def is_valid_logdir(self, path):
return self._is_valid_logdir(path)

21
plug.py

@ -87,12 +87,33 @@ def get_plugin_pref(plugname, parent):
return None
def notify_journal_entry(cmdr, system, station, entry):
"""
Send a journal entry to each plugin.
:param cmdr: The Cmdr name, or None if not yet known
:param system: The current system, or None if not yet known
:param station: The current station, or None if not docked or not yet known
:param entry: The journal entry as a dictionary
:return:
"""
for plugname in PLUGINS:
journal_entry = _get_plugin_func(plugname, "journal_entry")
if journal_entry:
try:
# Pass a copy of the journal entry in case the callee modifies it
journal_entry(cmdr, system, station, dict(entry))
except Exception as plugerr:
print plugerr
def notify_system_changed(timestamp, system, coordinates):
"""
Send notification data to each plugin when we arrive at a new system.
:param timestamp:
:param system:
:return:
deprecated:: 2.2
Use :func:`journal_entry` with the 'FSDJump' event.
"""
for plugname in PLUGINS:
system_changed = _get_plugin_func(plugname, "system_changed")

@ -45,18 +45,21 @@ def plugin_app(parent):
return plugin_app.status
def system_changed(timestamp, system, coordinates):
def journal_entry(cmdr, system, station, entry):
"""
Arrived in a new System
:param timestamp: when we arrived
:param system: the name of the system
:param coordinates: tuple of (x,y,z) ly relative to Sol, or None if unknown
E:D client made a journal entry
:param cmdr: The Cmdr name, or None if not yet known
:param system: The current system, or None if not yet known
:param station: The current station, or None if not docked or not yet known
:param entry: The journal entry as a dictionary
:return:
"""
if coordinates:
sys.stderr.write("Arrived at {} ({},{},{})\n".format(system, *coordinates))
else:
sys.stderr.write("Arrived at {}\n".format(system))
if entry['event'] == 'FSDJump':
# We arrived at a new system!
if 'StarPos' in entry:
sys.stderr.write("Arrived at {} ({},{},{})\n".format(entry['StarSystem'], *tuple(entry['StarPos'])))
else:
sys.stderr.write("Arrived at {}\n".format(entry['StarSystem']))
def cmdr_data(data):

@ -113,11 +113,9 @@ class PreferencesDialog(tk.Toplevel):
outframe = nb.Frame(notebook)
outframe.columnconfigure(0, weight=1)
output = config.getint('output') or (config.OUT_MKT_EDDN | config.OUT_SHIP_EDS) # default settings
output = config.getint('output') or (config.OUT_MKT_EDDN | config.OUT_SYS_EDDN | config.OUT_SHIP_EDS) # default settings
nb.Label(outframe, text=_('Please choose what data to save')).grid(columnspan=2, padx=PADX, sticky=tk.W)
self.out_eddn= tk.IntVar(value = (output & config.OUT_MKT_EDDN) and 1)
nb.Checkbutton(outframe, text=_('Send station data to the Elite Dangerous Data Network'), variable=self.out_eddn, command=self.outvarchanged).grid(columnspan=2, padx=BUTTONX, sticky=tk.W)
self.out_csv = tk.IntVar(value = (output & config.OUT_MKT_CSV ) and 1)
nb.Checkbutton(outframe, text=_('Market data in CSV format file'), variable=self.out_csv, command=self.outvarchanged).grid(columnspan=2, padx=BUTTONX, sticky=tk.W)
self.out_bpc = tk.IntVar(value = (output & config.OUT_MKT_BPC ) and 1)
@ -149,13 +147,27 @@ class PreferencesDialog(tk.Toplevel):
notebook.add(outframe, text=_('Output')) # Tab heading in settings
eddnframe = nb.Frame(notebook)
HyperlinkLabel(eddnframe, text='Elite Dangerous Data Network', background=nb.Label().cget('background'), url='https://github.com/jamesremuscat/EDDN/wiki', underline=True).grid(padx=PADX, sticky=tk.W) # Don't translate
self.eddn_station= tk.IntVar(value = (output & config.OUT_MKT_EDDN) and 1)
nb.Checkbutton(eddnframe, text=_('Send station data to the Elite Dangerous Data Network'), variable=self.eddn_station, command=self.outvarchanged).grid(padx=BUTTONX, pady=(5,0), sticky=tk.W) # Output setting
self.eddn_auto_button = nb.Checkbutton(eddnframe, text=_('Automatically update on docking'), variable=self.out_auto, command=self.outvarchanged) # Output setting
self.eddn_auto_button.grid(padx=BUTTONX, sticky=tk.W)
self.eddn_system = tk.IntVar(value = (output & config.OUT_SYS_EDDN) and 1)
self.eddn_system_button = nb.Checkbutton(eddnframe, text=_('Send system and scan data to the Elite Dangerous Data Network'), variable=self.eddn_system, command=self.outvarchanged) # Output setting new in E:D 2.2
self.eddn_system_button.grid(padx=BUTTONX, pady=(5,0), sticky=tk.W)
notebook.add(eddnframe, text='EDDN') # Not translated
edsmframe = nb.Frame(notebook)
edsmframe.columnconfigure(1, weight=1)
HyperlinkLabel(edsmframe, text='Elite Dangerous Star Map', background=nb.Label().cget('background'), url='https://www.edsm.net/', underline=True).grid(columnspan=2, padx=PADX, sticky=tk.W) # Don't translate
self.edsm_log = tk.IntVar(value = (output & config.OUT_SYS_EDSM) and 1)
self.edsm_log_button = nb.Checkbutton(edsmframe, text=_('Send flight log to Elite Dangerous Star Map'), variable=self.edsm_log, command=self.outvarchanged)
self.edsm_log_button.grid(columnspan=2, padx=BUTTONX, sticky=tk.W)
self.edsm_log_button.grid(columnspan=2, padx=BUTTONX, pady=(5,0), sticky=tk.W)
nb.Label(edsmframe).grid(sticky=tk.W) # big spacer
self.edsm_label = HyperlinkLabel(edsmframe, text=_('Elite Dangerous Star Map credentials'), background=nb.Label().cget('background'), url='https://www.edsm.net/settings/api', underline=True) # Section heading in settings
@ -196,7 +208,8 @@ class PreferencesDialog(tk.Toplevel):
_('Browse...')), # Folder selection button on Windows
command = lambda:self.filebrowse(_('E:D log file location'), self.logdir))
self.logbutton.grid(row=10, column=2, padx=PADX, sticky=tk.EW)
nb.Button(configframe, text=_('Default'), command=self.logdir_reset, state = monitor.logdir and tk.NORMAL or tk.DISABLED).grid(column=2, padx=PADX, pady=(5,0), sticky=tk.EW) # Appearance theme and language setting
if monitor.logdir:
nb.Button(configframe, text=_('Default'), command=self.logdir_reset, state = monitor.logdir and tk.NORMAL or tk.DISABLED).grid(column=2, padx=PADX, pady=(5,0), sticky=tk.EW) # Appearance theme and language setting
if platform == 'win32':
ttk.Separator(configframe, orient=tk.HORIZONTAL).grid(columnspan=3, padx=PADX, pady=PADY*8, sticky=tk.EW)
@ -298,11 +311,14 @@ class PreferencesDialog(tk.Toplevel):
logvalid = monitor.is_valid_logdir(logdir)
local = self.out_bpc.get() or self.out_td.get() or self.out_csv.get() or self.out_ship_eds.get() or self.out_ship_coriolis.get()
self.out_auto_button['state'] = (local or self.out_eddn.get()) and logvalid and tk.NORMAL or tk.DISABLED
self.out_auto_button['state'] = local and logvalid and tk.NORMAL or tk.DISABLED
self.outdir_label['state'] = local and tk.NORMAL or tk.DISABLED
self.outbutton['state'] = local and tk.NORMAL or tk.DISABLED
self.outdir['state'] = local and 'readonly' or tk.DISABLED
self.eddn_auto_button['state'] = self.eddn_station.get() and logvalid and tk.NORMAL or tk.DISABLED
self.eddn_system_button['state']= logvalid and tk.NORMAL or tk.DISABLED
self.edsm_log_button['state'] = logvalid and tk.NORMAL or tk.DISABLED
edsm_state = logvalid and self.edsm_log.get() and tk.NORMAL or tk.DISABLED
self.edsm_label['state'] = edsm_state
@ -425,13 +441,14 @@ class PreferencesDialog(tk.Toplevel):
config.set('password', self.password.get().strip())
config.set('output',
(self.out_eddn.get() and config.OUT_MKT_EDDN) +
(self.out_bpc.get() and config.OUT_MKT_BPC) +
(self.out_td.get() and config.OUT_MKT_TD) +
(self.out_csv.get() and config.OUT_MKT_CSV) +
(config.OUT_MKT_MANUAL if not self.out_auto.get() else 0) +
(self.out_ship_eds.get() and config.OUT_SHIP_EDS) +
(self.out_ship_coriolis.get() and config.OUT_SHIP_CORIOLIS) +
(self.eddn_station.get() and config.OUT_MKT_EDDN) +
(self.eddn_system.get() and config.OUT_SYS_EDDN) +
(self.edsm_log.get() and config.OUT_SYS_EDSM))
config.set('outdir', self.outdir.get().startswith('~') and join(config.home, self.outdir.get()[2:]) or self.outdir.get())