Neil Pollard
Published © GPL3+

Sky Finder - a LEGO Alexa gadget to discover the night sky

Explore space with this LEGO Mindstorms gadget and ask Alexa to show you the locations of planets, stars and even galaxies.

IntermediateFull instructions provided5 hours2,943

Things used in this project

Story

Read more

Schematics

Lego assembly instructions

Instructions for assembling the gadget. All parts from the Mindstorms EV3 set (31313)

Code

skyfinder.py

Python
Sky Finder code for the EV3 brick
#!/usr/bin/env python3

import os, sys, time, logging, json
from enum import Enum
from agt import AlexaGadget

from ev3dev2.motor import LargeMotor, MediumMotor, SpeedDPS
from ev3dev2.sensor.lego import TouchSensor, ColorSensor
from ev3dev2.led import Leds
from ev3dev2.sound import Sound

# Set the logging level to INFO to see messages from AlexaGadget
logging.basicConfig(level=logging.INFO, stream=sys.stdout, format='%(message)s')
logging.getLogger().addHandler(logging.StreamHandler(sys.stderr))
logger = logging.getLogger(__name__)

# Gear ratios for the two motors
GEAR_MM = 3
GEAR_LM = -3

# Calibration constants for initial position
# Tweak these if required to ensure starting pos is North horizon (0 az, 0 alt)
START_AZ = 388
START_ALT = 210

# Speed to move pointer arm
ARM_SPEED = 72 # degrees per second

class SkyFinder(AlexaGadget):    
    """
    A Mindstorms gadget that points to a specified position in the sky
    For use with the Sky Finder skill
    """
    def __init__(self):
        """
        Performs Alexa Gadget initialization routines and ev3dev resource allocation.
        """
        super().__init__()

        self.ts = TouchSensor()
        self.cs = ColorSensor()
        self.lm = LargeMotor()
        self.mm = MediumMotor()
        self.leds = Leds()
        self.sound = Sound()
        self.az = 0
        self.alt = 90

        self.reset_position()

    def on_connected(self, device_addr):
        """
        Gadget connected to the paired Echo device.
        :param device_addr: the address of the device we connected to
        """
        self.leds.set_color("LEFT", "GREEN")
        self.leds.set_color("RIGHT", "GREEN")
        logger.info("{} connected to Echo device".format(self.friendly_name))


    def on_disconnected(self, device_addr):
        """
        Gadget disconnected from the paired Echo device.
        :param device_addr: the address of the device we disconnected from
        """
        self.leds.set_color("LEFT", "BLACK")
        self.leds.set_color("RIGHT", "BLACK")
        logger.info("{} disconnected from Echo device".format(self.friendly_name))   

    def on_custom_mindstorms_gadget_control(self, directive):
        """
        Handles the Custom.Mindstorms.Gadget control directive.
        :param directive: the custom directive with the matching namespace and name
        """
        try:
            payload = json.loads(directive.payload.decode("utf-8"))
            print("Control payload: {}".format(payload), file=sys.stderr)
            control_type = payload["type"]
            if control_type == "move":

                # Expected params: [az, alt]
                self.go_to(payload["az"], int(payload["alt"]))

            if control_type == "reset":
                # Expected params: none
                self.reset_position()

        except KeyError:
            print("Missing expected parameters: {}".format(directive), file=sys.stderr)

    def reset_position(self):
        """
        Move arm to starting position
        """
        logger.info("Resetting arm position")

        self.lm.stop_action = 'brake'
        self.lm.reset()
        self.lm.position = 0

        # Rotate arm until it pushes touch sensor switch 
        # and then move specified distance to start position
        self.lm.on_for_rotations(speed = SpeedDPS(72), rotations = -3, block=False)
        self.ts.wait_for_pressed()
        self.lm.off()
        self.lm.on_for_degrees(SpeedDPS(72), START_AZ, block = True)

        # Rotate arm in other axis until it's seen by the colour sensor
        # and then move specified distance to start position
        self.mm.stop_action = 'brake'
        self.mm.on_for_rotations(speed = 5, rotations = -3, block=False)

        # wait until sensor sees (red) arm
        while self.cs.color != 5:
            continue

        self.mm.off()
        self.mm.reset()
        self.mm.position = 0
        self.mm.on_for_degrees(5, START_ALT, block = True)

        # turn off the distracting LED on the colour sensor
        self.cs.mode='COL-AMBIENT'

        # Reset the position counter to 0
        self.mm.off()
        self.mm.reset()
        self.mm.position = 0

        self.lm.off()
        self.lm.reset()
        self.lm.position = 0

    def go_to(self, new_az, new_alt):
        """
        Move pointer to a specified position
        :param new_az: new azimouth position (in degrees)
        :param new_alt: new altitude position (in degrees)
        """

        self.leds.set_color("LEFT", "YELLOW")
        self.leds.set_color("RIGHT", "YELLOW")

        # Normalise rotation to prevent cable tangle
        if (new_az > 270):
            az1 = new_az - 360
            alt1 = new_alt
        elif  (new_az > 90):
            az1 = new_az - 180
            alt1 = 180 - new_alt
        else:
            az1 = new_az
            alt1 = new_alt

        # Move arm
        self.lm.on_to_position(SpeedDPS(ARM_SPEED), az1 * GEAR_LM, block = False, brake = True)
        self.mm.on_to_position(SpeedDPS(ARM_SPEED), alt1 * GEAR_MM, block = True, brake = True)

        self.leds.set_color("LEFT", "GREEN")
        self.leds.set_color("RIGHT", "GREEN")

if __name__ == '__main__':

    gadget = SkyFinder()

    # Set LCD font and turn off blinking LEDs
    os.system('setfont Lat7-Terminus12x6')
    gadget.leds.set_color("LEFT", "BLACK")
    gadget.leds.set_color("RIGHT", "BLACK")

    # Startup sequence
    gadget.sound.play_song((('C4', 'e'), ('E5', 'e')))
    gadget.leds.set_color("LEFT", "GREEN")
    gadget.leds.set_color("RIGHT", "GREEN")

    # Gadget main entry point
    gadget.main()

    # Shutdown sequence
    gadget.sound.play_song((('E5', 'e'), ('C4', 'e')))
    gadget.leds.set_color("LEFT", "BLACK")
    gadget.leds.set_color("RIGHT", "BLACK")

skyfinder.ini

INI
INI file for EV3 brick. Add your Amazon ID and alexaGadgetSecret from the Alexa Developer Console.
[GadgetSettings]
amazonId = 
alexaGadgetSecret = 

[GadgetCapabilities]
Custom.Mindstorms.Gadget = 1.0

Interaction Model

JSON
Voice interaction model. Paste this JSON into the Alexa developer console.
{
    "interactionModel": {
        "languageModel": {
            "invocationName": "sky finder",
            "intents": [
                {
                    "name": "AMAZON.CancelIntent",
                    "samples": []
                },
                {
                    "name": "AMAZON.HelpIntent",
                    "samples": []
                },
                {
                    "name": "AMAZON.StopIntent",
                    "samples": []
                },
                {
                    "name": "AMAZON.NavigateHomeIntent",
                    "samples": []
                },
                {
                    "name": "LocateIntent",
                    "slots": [
                        {
                            "name": "object",
                            "type": "ObjectType"
                        }
                    ],
                    "samples": [
                        "{object}",
                        "locate {object}",
                        "find {object}",
                        "tell me about {object}",
                        "where is {object}",
                        "show me {object}"
                    ]
                },
                {
                    "name": "ScanIntent",
                    "slots": [
                        {
                            "name": "compass_direction",
                            "type": "CompassDirection",
                            "samples": [
                                "{compass_direction}"
                            ]
                        }
                    ],
                    "samples": [
                        "Tell me what's in the {compass_direction}",
                        "Scan the {compass_direction}",
                        "Scan the sky in the {compass_direction}",
                        "Look in the {compass_direction}",
                        "What's in the {compass_direction}",
                        "What can I see in the {compass_direction}",
                        "What can you see in the {compass_direction}"
                    ]
                },
                {
                    "name": "AMAZON.YesIntent",
                    "samples": []
                },
                {
                    "name": "AMAZON.NoIntent",
                    "samples": []
                }
            ],
            "types": [
                {
                    "name": "ObjectType",
                    "values": [
                        {
                            "id": "M6",
                            "name": {
                                "value": "Butterfly Cluster",
                                "synonyms": [
                                    "messier 6",
                                    "M6"
                                ]
                            }
                        },
                        {
                            "id": "M44",
                            "name": {
                                "value": "Beehive Cluster",
                                "synonyms": [
                                    "messier 44",
                                    "M44"
                                ]
                            }
                        },
                        {
                            "id": "C41",
                            "name": {
                                "value": "Hyades",
                                "synonyms": [
                                    "Caldwell 41",
                                    "the hyades"
                                ]
                            }
                        },
                        {
                            "id": "M13",
                            "name": {
                                "value": "messier 13",
                                "synonyms": [
                                    "messier 13",
                                    "Hercules Globular Cluster",
                                    "M13"
                                ]
                            }
                        },
                        {
                            "id": "M7",
                            "name": {
                                "value": "ptolemy cluster",
                                "synonyms": [
                                    "messier 7",
                                    "M7"
                                ]
                            }
                        },
                        {
                            "id": "C106",
                            "name": {
                                "value": "47 tucanae",
                                "synonyms": [
                                    "Caldwell 106"
                                ]
                            }
                        },
                        {
                            "id": "C80",
                            "name": {
                                "value": "omega centauri",
                                "synonyms": [
                                    "Caldwell 80"
                                ]
                            }
                        },
                        {
                            "id": "M42",
                            "name": {
                                "value": "orion nebula",
                                "synonyms": [
                                    "messier 42",
                                    "M42"
                                ]
                            }
                        },
                        {
                            "id": "C92",
                            "name": {
                                "value": "carina nebula",
                                "synonyms": [
                                    "Caldwell 92",
                                    "The Grand Nebula",
                                    "Grand Nebula",
                                    "NGC 3372"
                                ]
                            }
                        },
                        {
                            "id": "C14",
                            "name": {
                                "value": "double cluster",
                                "synonyms": [
                                    "Caldwell 14"
                                ]
                            }
                        },
                        {
                            "id": "M31",
                            "name": {
                                "value": "andromeda galaxy",
                                "synonyms": [
                                    "messier 31",
                                    "M31",
                                    "the andromeda galaxy"
                                ]
                            }
                        },
                        {
                            "id": "M45",
                            "name": {
                                "value": "pleiades",
                                "synonyms": [
                                    "messier 45",
                                    "M45",
                                    "the seven sisters",
                                    "seven sisters",
                                    "the pleiades"
                                ]
                            }
                        },
                        {
                            "id": "regulus",
                            "name": {
                                "value": "regulus",
                                "synonyms": [
                                    "alpha leo"
                                ]
                            }
                        },
                        {
                            "id": "deneb",
                            "name": {
                                "value": "deneb",
                                "synonyms": [
                                    "Alpha Cygni"
                                ]
                            }
                        },
                        {
                            "id": "becrux",
                            "name": {
                                "value": "becrux",
                                "synonyms": [
                                    "Beta Crucis"
                                ]
                            }
                        },
                        {
                            "id": "fomalhaut",
                            "name": {
                                "value": "fomalhaut",
                                "synonyms": [
                                    "Alpha Piscis Austrini"
                                ]
                            }
                        },
                        {
                            "id": "pollux",
                            "name": {
                                "value": "pollux",
                                "synonyms": [
                                    "Beta Geminorum"
                                ]
                            }
                        },
                        {
                            "id": "castor",
                            "name": {
                                "value": "castor",
                                "synonyms": [
                                    "Alpha Geminorum"
                                ]
                            }
                        },
                        {
                            "id": "spica",
                            "name": {
                                "value": "spica",
                                "synonyms": [
                                    "Alpha Virginis"
                                ]
                            }
                        },
                        {
                            "id": "antares",
                            "name": {
                                "value": "antares",
                                "synonyms": [
                                    "Alpha Scorpii"
                                ]
                            }
                        },
                        {
                            "id": "acrux",
                            "name": {
                                "value": "acrux",
                                "synonyms": [
                                    "Alpha Crucis"
                                ]
                            }
                        },
                        {
                            "id": "hadar",
                            "name": {
                                "value": "hadar",
                                "synonyms": [
                                    "Beta Centauri"
                                ]
                            }
                        },
                        {
                            "id": "achernar",
                            "name": {
                                "value": "achernar",
                                "synonyms": [
                                    "Alpha Eridani"
                                ]
                            }
                        },
                        {
                            "id": "rigil-kentaurus",
                            "name": {
                                "value": "rigil kentaurus",
                                "synonyms": [
                                    "Alpha Centauri"
                                ]
                            }
                        },
                        {
                            "id": "aldebaran",
                            "name": {
                                "value": "aldebaran",
                                "synonyms": [
                                    "Alpha Tauri"
                                ]
                            }
                        },
                        {
                            "id": "altair",
                            "name": {
                                "value": "altair",
                                "synonyms": [
                                    "Alpha Aquilae"
                                ]
                            }
                        },
                        {
                            "id": "procyon",
                            "name": {
                                "value": "procyon",
                                "synonyms": [
                                    "Alpha Canis Minoris"
                                ]
                            }
                        },
                        {
                            "id": "rigel",
                            "name": {
                                "value": "rigel",
                                "synonyms": [
                                    "Beta Orionis"
                                ]
                            }
                        },
                        {
                            "id": "capella",
                            "name": {
                                "value": "capella",
                                "synonyms": [
                                    "Alpha Aurigae"
                                ]
                            }
                        },
                        {
                            "id": "vega",
                            "name": {
                                "value": "vega",
                                "synonyms": [
                                    "Alpha Lyrae"
                                ]
                            }
                        },
                        {
                            "id": "arcturus",
                            "name": {
                                "value": "arcturus",
                                "synonyms": [
                                    "Alpha Bootis"
                                ]
                            }
                        },
                        {
                            "id": "sirius",
                            "name": {
                                "value": "sirius",
                                "synonyms": [
                                    "Alpha Canis Majoris",
                                    "dog star",
                                    "the dog star"
                                ]
                            }
                        },
                        {
                            "id": "canopus",
                            "name": {
                                "value": "canopus",
                                "synonyms": [
                                    "Alpha Carinae"
                                ]
                            }
                        },
                        {
                            "id": "polaris",
                            "name": {
                                "value": "polaris",
                                "synonyms": [
                                    "north star",
                                    "the north star",
                                    "Alpha Ursae Minoris",
                                    "pole star",
                                    "the pole star"
                                ]
                            }
                        },
                        {
                            "id": "betelgeuse",
                            "name": {
                                "value": "betelgeuse",
                                "synonyms": [
                                    "alpha orionis",
                                    "beetle juice"
                                ]
                            }
                        },
                        {
                            "id": "sun",
                            "name": {
                                "value": "sun",
                                "synonyms": [
                                    "sol",
                                    "the sun"
                                ]
                            }
                        },
                        {
                            "id": "pluto",
                            "name": {
                                "value": "pluto"
                            }
                        },
                        {
                            "id": "neptune",
                            "name": {
                                "value": "neptune"
                            }
                        },
                        {
                            "id": "uranus",
                            "name": {
                                "value": "uranus"
                            }
                        },
                        {
                            "id": "saturn",
                            "name": {
                                "value": "saturn"
                            }
                        },
                        {
                            "id": "jupiter",
                            "name": {
                                "value": "jupiter"
                            }
                        },
                        {
                            "id": "mars",
                            "name": {
                                "value": "mars"
                            }
                        },
                        {
                            "id": "venus",
                            "name": {
                                "value": "venus"
                            }
                        },
                        {
                            "id": "mercury",
                            "name": {
                                "value": "mercury"
                            }
                        },
                        {
                            "id": "moon",
                            "name": {
                                "value": "moon",
                                "synonyms": [
                                    "the moon"
                                ]
                            }
                        },
                        {
                            "id": "and",
                            "name": {
                                "value": "andromeda"
                            }
                        },
                        {
                            "id": "ant",
                            "name": {
                                "value": "antlia"
                            }
                        },
                        {
                            "id": "aps",
                            "name": {
                                "value": "apus"
                            }
                        },
                        {
                            "id": "aqr",
                            "name": {
                                "value": "aquarius"
                            }
                        },
                        {
                            "id": "aql",
                            "name": {
                                "value": "aquila"
                            }
                        },
                        {
                            "id": "ara",
                            "name": {
                                "value": "ara"
                            }
                        },
                        {
                            "id": "ari",
                            "name": {
                                "value": "aries"
                            }
                        },
                        {
                            "id": "aur",
                            "name": {
                                "value": "auriga"
                            }
                        },
                        {
                            "id": "boo",
                            "name": {
                                "value": "bootes"
                            }
                        },
                        {
                            "id": "cae",
                            "name": {
                                "value": "caelum"
                            }
                        },
                        {
                            "id": "cam",
                            "name": {
                                "value": "camelopardalis"
                            }
                        },
                        {
                            "id": "cnc",
                            "name": {
                                "value": "cancer"
                            }
                        },
                        {
                            "id": "cvn",
                            "name": {
                                "value": "canes venatici"
                            }
                        },
                        {
                            "id": "cma",
                            "name": {
                                "value": "canis major"
                            }
                        },
                        {
                            "id": "cmi",
                            "name": {
                                "value": "canis minor"
                            }
                        },
                        {
                            "id": "cap",
                            "name": {
                                "value": "capricornus"
                            }
                        },
                        {
                            "id": "car",
                            "name": {
                                "value": "carina"
                            }
                        },
                        {
                            "id": "cas",
                            "name": {
                                "value": "cassiopeia"
                            }
                        },
                        {
                            "id": "cen",
                            "name": {
                                "value": "centaurus"
                            }
                        },
                        {
                            "id": "cep",
                            "name": {
                                "value": "cepheus"
                            }
                        },
                        {
                            "id": "cet",
                            "name": {
                                "value": "cetus"
                            }
                        },
                        {
                            "id": "cha",
                            "name": {
                                "value": "chamaeleon",
                                "synonyms": [
                                    "chameleon"
                                ]
                            }
                        },
                        {
                            "id": "cir",
                            "name": {
                                "value": "circinus"
                            }
                        },
                        {
                            "id": "col",
                            "name": {
                                "value": "columba"
                            }
                        },
                        {
                            "id": "com",
                            "name": {
                                "value": "coma berenices"
                            }
                        },
                        {
                            "id": "cra",
                            "name": {
                                "value": "corona australis"
                            }
                        },
                        {
                            "id": "crb",
                            "name": {
                                "value": "corona borealis"
                            }
                        },
                        {
                            "id": "crv",
                            "name": {
                                "value": "corvus"
                            }
                        },
                        {
                            "id": "crt",
                            "name": {
                                "value": "crater"
                            }
                        },
                        {
                            "id": "cru",
                            "name": {
                                "value": "crux",
                                "synonyms": [
                                    "the southern cross",
                                    "southern cross"
                                ]
                            }
                        },
                        {
                            "id": "cyg",
                            "name": {
                                "value": "cygnus"
                            }
                        },
                        {
                            "id": "del",
                            "name": {
                                "value": "delphinus"
                            }
                        },
                        {
                            "id": "dor",
                            "name": {
                                "value": "dorado"
                            }
                        },
                        {
                            "id": "dra",
                            "name": {
                                "value": "draco"
                            }
                        },
                        {
                            "id": "equ",
                            "name": {
                                "value": "equuleus"
                            }
                        },
                        {
                            "id": "eri",
                            "name": {
                                "value": "eridanus"
                            }
                        },
                        {
                            "id": "for",
                            "name": {
                                "value": "fornax"
                            }
                        },
                        {
                            "id": "gem",
                            "name": {
                                "value": "gemini"
                            }
                        },
                        {
                            "id": "gru",
                            "name": {
                                "value": "grus"
                            }
                        },
                        {
                            "id": "her",
                            "name": {
                                "value": "hercules"
                            }
                        },
                        {
                            "id": "hor",
                            "name": {
                                "value": "horologium"
                            }
                        },
                        {
                            "id": "hya",
                            "name": {
                                "value": "hydra"
                            }
                        },
                        {
                            "id": "hyi",
                            "name": {
                                "value": "hydrus"
                            }
                        },
                        {
                            "id": "ind",
                            "name": {
                                "value": "indus"
                            }
                        },
                        {
                            "id": "lac",
                            "name": {
                                "value": "lacerta"
                            }
                        },
                        {
                            "id": "leo",
                            "name": {
                                "value": "leo"
                            }
                        },
                        {
                            "id": "lmi",
                            "name": {
                                "value": "leo minor"
                            }
                        },
                        {
                            "id": "lep",
                            "name": {
                                "value": "lepus"
                            }
                        },
                        {
                            "id": "lib",
                            "name": {
                                "value": "libra"
                            }
                        },
                        {
                            "id": "lup",
                            "name": {
                                "value": "lupus"
                            }
                        },
                        {
                            "id": "lyn",
                            "name": {
                                "value": "lynx"
                            }
                        },
                        {
                            "id": "lyr",
                            "name": {
                                "value": "lyra"
                            }
                        },
                        {
                            "id": "men",
                            "name": {
                                "value": "mensa"
                            }
                        },
                        {
                            "id": "mic",
                            "name": {
                                "value": "microscopium"
                            }
                        },
                        {
                            "id": "mon",
                            "name": {
                                "value": "monoceros"
                            }
                        },
                        {
                            "id": "mus",
                            "name": {
                                "value": "musca"
                            }
                        },
                        {
                            "id": "nor",
                            "name": {
                                "value": "norma"
                            }
                        },
                        {
                            "id": "oct",
                            "name": {
                                "value": "octans"
                            }
                        },
                        {
                            "id": "oph",
                            "name": {
                                "value": "ophiuchus"
                            }
                        },
                        {
                            "id": "ori",
                            "name": {
                                "value": "orion"
                            }
                        },
                        {
                            "id": "pav",
                            "name": {
                                "value": "pavo"
                            }
                        },
                        {
                            "id": "peg",
                            "name": {
                                "value": "pegasus"
                            }
                        },
                        {
                            "id": "per",
                            "name": {
                                "value": "perseus"
                            }
                        },
                        {
                            "id": "phe",
                            "name": {
                                "value": "phoenix"
                            }
                        },
                        {
                            "id": "pic",
                            "name": {
                                "value": "pictor"
                            }
                        },
                        {
                            "id": "psc",
                            "name": {
                                "value": "pisces"
                            }
                        },
                        {
                            "id": "psa",
                            "name": {
                                "value": "piscis austrinus"
                            }
                        },
                        {
                            "id": "pup",
                            "name": {
                                "value": "puppis"
                            }
                        },
                        {
                            "id": "pyx",
                            "name": {
                                "value": "pyxis"
                            }
                        },
                        {
                            "id": "ret",
                            "name": {
                                "value": "reticulum"
                            }
                        },
                        {
                            "id": "sge",
                            "name": {
                                "value": "sagitta"
                            }
                        },
                        {
                            "id": "sgr",
                            "name": {
                                "value": "sagittarius"
                            }
                        },
                        {
                            "id": "sco",
                            "name": {
                                "value": "scorpius",
                                "synonyms": [
                                    "scorpio"
                                ]
                            }
                        },
                        {
                            "id": "scl",
                            "name": {
                                "value": "sculptor"
                            }
                        },
                        {
                            "id": "sct",
                            "name": {
                                "value": "scutum"
                            }
                        },
                        {
                            "id": "ser",
                            "name": {
                                "value": "serpens"
                            }
                        },
                        {
                            "id": "sex",
                            "name": {
                                "value": "sextans"
                            }
                        },
                        {
                            "id": "tau",
                            "name": {
                                "value": "taurus"
                            }
                        },
                        {
                            "id": "tel",
                            "name": {
                                "value": "telescopium"
                            }
                        },
                        {
                            "id": "tri",
                            "name": {
                                "value": "triangulum"
                            }
                        },
                        {
                            "id": "tra",
                            "name": {
                                "value": "triangulum australe"
                            }
                        },
                        {
                            "id": "tuc",
                            "name": {
                                "value": "tucana"
                            }
                        },
                        {
                            "id": "uma",
                            "name": {
                                "value": "ursa major",
                                "synonyms": [
                                    "the big dipper",
                                    "the plough",
                                    "the great bear"
                                ]
                            }
                        },
                        {
                            "id": "umi",
                            "name": {
                                "value": "ursa minor",
                                "synonyms": [
                                    "the little dipper",
                                    "the little bear"
                                ]
                            }
                        },
...

This file has been truncated, please download it to see its full contents.

lambda_function.py

Python
The lambda function (Alexa hosted)
# Sky Finder Alexa Skill for LEGO Mindstorms
# Neil Pollard, 2019

import logging.handlers
import requests
import uuid
import data
import utils
import time

#from ask_sdk_core.skill_builder import SkillBuilder
from ask_sdk_core.skill_builder import CustomSkillBuilder
from ask_sdk_core.api_client import DefaultApiClient
from ask_sdk_core.utils import is_request_type, is_intent_name, get_slot_value
from ask_sdk_core.handler_input import HandlerInput
from ask_sdk_core.serialize import DefaultSerializer

from ask_sdk_model import IntentRequest
from ask_sdk_model.ui import PlayBehavior
from ask_sdk_model.services.directive import SendDirectiveRequest, Header as DirectiveHeader, SpeakDirective

from ask_sdk_model.interfaces.custom_interface_controller import (
    StartEventHandlerDirective, EventFilter, Expiration, FilterMatchAction,
    StopEventHandlerDirective,
    SendDirectiveDirective,
    Header,
    Endpoint,
    EventsReceivedRequest,
    ExpiredRequest
)

logger = logging.getLogger()
logger.setLevel(logging.INFO)
serializer = DefaultSerializer()
skill_builder = CustomSkillBuilder(api_client=DefaultApiClient())

# The namespace of the custom directive to be sent by this skill
NAMESPACE = "Custom.Mindstorms.Gadget"

# The name of the custom directive to be sent by this skill
NAME_CONTROL = "control"

@skill_builder.request_handler(can_handle_func=is_request_type("LaunchRequest"))
def launch_request_handler(handler_input: HandlerInput):
    logger.info("== Launch Intent ==")

    response_builder = handler_input.response_builder

    system = handler_input.request_envelope.context.system
    api_access_token = system.api_access_token
    api_endpoint = system.api_endpoint

    # Get connected gadget endpoint ID.
    endpoints = get_connected_endpoints(api_endpoint, api_access_token)
    logger.debug("Checking endpoint..")
    if not endpoints:
        logger.debug("No connected gadget endpoints available.")
        return (response_builder
                .speak(data.NO_BRICK_MESSAGE)
                .set_should_end_session(False)
                .response)

    endpoint_id = endpoints[0].get("endpointId", "")

    # Store endpoint ID for using it to send custom directives later.
    logger.debug("Received endpoints. Storing Endpoint Id: %s", endpoint_id)
    session_attr = handler_input.attributes_manager.session_attributes
    session_attr["endpoint_id"] = endpoint_id

    return (response_builder
            .speak(data.WELCOME_MESSAGE)
            .ask("Which planet, star or constellation would you like to know about?")
            .response)

@skill_builder.request_handler(can_handle_func=is_intent_name("ScanIntent"))
def scan_intent_handler(handler_input: HandlerInput):
    # Scan the sky for objects of interest
    logger.info("GuideIntent received.")
    
    # Get the compass direction the user has specified
    slots = handler_input.request_envelope.request.intent.slots
    try:
        direction = slots["compass_direction"].resolutions.resolutions_per_authority[0].values[0].value.id
    except:
        return (handler_input.response_builder 
        .speak("Sorry I couldn't find that direction")
        .response)
        
    # Scanning sound
    scanning_sfx = "<audio src=\"soundbank://soundlibrary/computers/screens/screens_07\"></audio>"
        
    # Send 'working on it' response while we find objects
        
    # Sound effect can't go here at the moment
    # https://forums.developer.amazon.com/questions/193615/audio-ssml-tag-does-not-work-using-progressive-res.html
    # working_on_it = "Scanning the sky. {}".format(object, scanning_sfx)
    working_on_it = "Scanning the sky."
    request_id_holder = handler_input.request_envelope.request.request_id
    directive_header = DirectiveHeader(request_id=request_id_holder)
    speech = SpeakDirective(speech = working_on_it)
    directive_request = SendDirectiveRequest(
        header=directive_header, directive=speech)
    directive_service_client = handler_input.service_client_factory.get_directive_service()
    directive_service_client.enqueue(directive_request)
    
    # Find a list of objects in our specified part of the sky
    logger.info("Scanning sky in {}.".format(direction))
    scan_list = utils.scan(direction)
    
    if len(scan_list) > 0:
        speak_output = "{} I have found ".format(scanning_sfx)
        spoken_list = []
        for object in scan_list:
            # Get list of proper names of object from database
            spoken_list.append(data.OBJECT_DATA[object['name']]['name'])

        speak_output += utils.comma(spoken_list)
        if (len(scan_list) > 1):
            speak_output += ". Which one would you like me to locate? "
        else:
            speak_output += ". Do you want me to show you where it is?"
            # save object ready for a possible 'yes' response from the user
            object_of_interest = scan_list[0]['name']
            session_attr = handler_input.attributes_manager.session_attributes
            session_attr["object_of_interest"] = object_of_interest
        
        return (handler_input.response_builder 
            .speak(speak_output)
            .ask("Do you want me to show you where they are?")
            .response)
    else:
        speak_output = "{} Sorry. I couldn't find anything noteworthy in that part of the sky at the moment. {}".format(scanning_sfx, data.ASK_MESSAGE)
        return (handler_input.response_builder 
            .speak(speak_output)
            .ask("What would you like me to find?")
            .response)

@skill_builder.request_handler(can_handle_func=lambda handler_input:
                               is_intent_name("AMAZON.YesIntent")(handler_input) or
                               is_intent_name("LocateIntent")(handler_input))
def locate_intent_handler(handler_input: HandlerInput):
    # Find an object in the sky
    # Look up an object's position then 
    # construct and send a custom directive to the connected gadget with data from the LocateIntent request.
    logger.info("LocateIntent received.")

    session_attr = handler_input.attributes_manager.session_attributes

    # If user has responded 'yes', see if we have a saved object to locate
    if handler_input.request_envelope.request.intent.name == "AMAZON.YesIntent":
        try:
            object = session_attr["object_of_interest"]
        except:
            # If there's no object saved, send "don't understand" response
            speak_output_with_dir = "Sorry, I didn't understand that. {}".format(name, data.ASK_MESSAGE)
            return (handler_input.response_builder 
            .speak(speak_output_with_dir)
            .ask("What do you want me to find?")
            .response)
    else:
        # Get the name of the object the user is trying to locate from the slot
        slots = handler_input.request_envelope.request.intent.slots
        try:
            # See if it's been matched to one of the specified values for this slot type in the interaction model
            object = slots["object"].resolutions.resolutions_per_authority[0].values[0].value.id
        except:
            # otherwise just capture what the user said
            object = get_slot_value(handler_input=handler_input, slot_name="object")
        
    logger.debug("User has asked to locate {}".format(object))
    
    object_located = False

    # Is the object in our database?
    if object in data.OBJECT_DATA:
        record = data.OBJECT_DATA[object]
        
        # Get official name and description from our local database
        name = record['name']
        if 'description' in record:
            description = record['description']
        else:
            description = ''
        
        # Radar ping sound
        radar_ping = "<audio src=\"soundbank://soundlibrary/scifi/amzn_sfx_scifi_radar_high_ping_01\"></audio>"
        
        # Send 'working on it' response while we look up the location...
        
        # Radar ping can't go here at the moment
        # https://forums.developer.amazon.com/questions/193615/audio-ssml-tag-does-not-work-using-progressive-res.html
        # working_on_it = "Locating {}. {}".format(object, radar_ping)
        working_on_it = "Locating {}. ".format(name)
        request_id_holder = handler_input.request_envelope.request.request_id
        directive_header = DirectiveHeader(request_id=request_id_holder)
        speech = SpeakDirective(speech = working_on_it)
        directive_request = SendDirectiveRequest(
            header=directive_header, directive=speech)
        directive_service_client = handler_input.service_client_factory.get_directive_service()
        directive_service_client.enqueue(directive_request)
        
        # Locate the object in space
        az,alt,distance = utils.locate(object)
        
        if az == None:
            # couldn't locate object
            speak_output = speak_output_with_dir = "Sorry, I couldn't locate {}. {}".format(name, data.ASK_MESSAGE)
            speak_output_with_dir
        elif alt < 0:
            # object is below the horizon
            speak_output = speak_output_with_dir = "{} {} is not visible from this location at the moment. {}".format(radar_ping, name, data.ASK_MESSAGE)
        elif distance > 0:
            # object found and visible with a defined distance
            compass = utils.degrees_to_dir(az)
            speak_output = "{} Located. {} is currently {} kilometers from your location. <break time=\"2s\"/> {} <break time=\"2s\"/> {}".format(radar_ping, name, distance, description, data.ASK_MESSAGE)
            speak_output_with_dir = "{} Located. {} is currently {} kilometers from your location in the {}, {} degrees above the horizon. <break time=\"2s\"/> {} <break time=\"2s\"/> {}".format(radar_ping, name, distance, compass, alt, description, data.ASK_MESSAGE)
            object_located = True
        else:
            # object found, but no distance available
            compass = utils.degrees_to_dir(az)
            speak_output = "{} Located {}. <break time=\"1s\"/> {} <break time=\"2s\"/> {}".format(radar_ping, name, description, data.ASK_MESSAGE)
            speak_output_with_dir = "{} Located {} {} degrees above the horizon in the {}. <break time=\"1s\"/> {} <break time=\"2s\"/> {}".format(radar_ping, name, alt, compass, description, data.ASK_MESSAGE)
            object_located = True

    else: # object is not in our database
        speak_output = speak_output_with_dir = "Sorry, I don't know about {}. {}".format(object, data.ASK_MESSAGE)

    # Send response...

    # If the Mindstorms gadget is connected and object is located then
    # construct the directive with the payload containing the move parameters

    if ("endpoint_id" in session_attr and object_located):
        payload = {
            "type": "move",
            "az": az,
            "alt": alt,
        }
        endpoint_id = session_attr["endpoint_id"]
        directive = build_send_directive(NAMESPACE, NAME_CONTROL, endpoint_id, payload)

        return (handler_input.response_builder 
            .speak(speak_output)
            .ask("What do you want me to find?")
            .add_directive(directive)
            .response)
    else: # if gadget is not connected or object is not located, just speak the response
        
        return (handler_input.response_builder 
            .speak(speak_output_with_dir)
            .ask("What do you want me to find?")
            .response)

@skill_builder.request_handler(can_handle_func = is_intent_name("AMAZON.NoIntent"))
def no_intent_handler(handler_input):
    logger.info("Received a No Intent..")
    response_builder = handler_input.response_builder

    return (response_builder
            .speak(data.ASK_MESSAGE)
            .set_should_end_session(False)
            .response)


@skill_builder.request_handler(can_handle_func=lambda handler_input:
                               is_intent_name("AMAZON.CancelIntent")(handler_input) or
                               is_intent_name("AMAZON.StopIntent")(handler_input))
def stop_and_cancel_intent_handler(handler_input):
    logger.info("Received a Stop or a Cancel Intent..")
    response_builder = handler_input.response_builder

    return (response_builder
            .speak(data.GOODBYE_MESSAGE)
            .set_should_end_session(True)
            .response)

@skill_builder.request_handler(can_handle_func=is_request_type("SessionEndedRequest"))
def session_ended_request_handler(handler_input):
    logger.info("Session ended with reason: " +
                handler_input.request_envelope.request.reason.to_str())
    return handler_input.response_builder.response

@skill_builder.exception_handler(can_handle_func=lambda i, e: True)
def error_handler(handler_input, exception):
    logger.info("==Error==")
    logger.error(exception, exc_info=True)
    return (handler_input.response_builder
            .speak("I'm sorry, something went wrong!").response)

@skill_builder.global_request_interceptor()
def log_request(handler_input):
    # Log the request for debugging purposes.
    logger.info("==Request==\r" +
                str(serializer.serialize(handler_input.request_envelope)))

@skill_builder.global_response_interceptor()
def log_response(handler_input, response):
    # Log the response for debugging purposes.
    logger.info("==Response==\r" + str(serializer.serialize(response)))
    logger.info("==Session Attributes==\r" +
                str(serializer.serialize(handler_input.attributes_manager.session_attributes)))

def get_connected_endpoints(api_endpoint, api_access_token):
    headers = {
        'Content-Type': 'application/json',
        'Authorization': 'Bearer {}'.format(api_access_token)
    }

    api_url = api_endpoint + "/v1/endpoints"
    endpoints_response = requests.get(api_url, headers=headers)

    if endpoints_response.status_code == requests.codes.get("ok", ""):
        return endpoints_response.json()["endpoints"]

def build_send_directive(namespace, name, endpoint_id, payload):
    return SendDirectiveDirective(
        header=Header(
            name=name,
            namespace=namespace
        ),
        endpoint=Endpoint(
            endpoint_id=endpoint_id
        ),
        payload=payload
    )

lambda_handler = skill_builder.lambda_handler()

utils.py

Python
Helper functions for lambda_function (Alexa hosted)
import logging
import os
import boto3
import data
import urllib
import json
import sys
import math
from botocore.exceptions import ClientError


def create_presigned_url(object_name):
    """Generate a presigned URL to share an S3 object with a capped expiration of 60 seconds

    :param object_name: string
    :return: Presigned URL as string. If error, returns None.
    """
    s3_client = boto3.client('s3', config=boto3.session.Config(signature_version='s3v4',s3={'addressing_style': 'path'}))
    try:
        bucket_name = os.environ.get('S3_PERSISTENCE_BUCKET')
        response = s3_client.generate_presigned_url('get_object',
                                                    Params={'Bucket': bucket_name,
                                                            'Key': object_name},
                                                    ExpiresIn=60*1)
    except ClientError as e:
        logging.error(e)
        return None

    # The response contains the presigned URL
    return response

def comma(items):
    start, last = items[:-1], items[-1]

    if start:
        return "{}, and {}".format(", ".join(start), last)
    else:
        return last

def is_in_direction(direction, degrees, tolerance = 45):
    """Check whether degrees are within 'tolerance' of the specified compass direction
    
    :param direction: string, degrees: int, tolerance int
    :return: boolean
    """    
    dirs = ['N', 'NE', 'E', 'SE' , 'S', 'SW', 'W', 'NW']
    middle = dirs.index(direction) * (360/len(dirs))
    top = (middle + tolerance)
    bottom = (middle - tolerance)
    return (degrees >= bottom and degrees <= top) or (degrees >= 360 + bottom)

def degrees_to_dir(degrees):
    """Convert a number from 0-359 degrees into a compass direction.
    
    :param degrees: int
    :return: compass direction (string)
    """
    dirs = ['North','NorthEast', 'East', 'SouthEast', 'South', 'SouthWest', 'West', 'NorthWest']
    ix = round(degrees / (360. / len(dirs)))
    return dirs[ix % len(dirs)]

def tidy(x, n):
    """Return 'x' rounded to 'n' significant digits.
    :param x: float
    :param n: int
    """
    y=abs(x)
    if y <= sys.float_info.min: 
        return 0
    return int( round( x, int( n-math.ceil(math.log10(y)) ) ) )

def scan(direction):
    """"Get a list of interesting objects in the specified part of the sky
    :param direction: string ('N', 'NE', 'E' etc.)
    :return: list of objects with alt and az
    """
    
    # A list of potentially 'interesting' objects
    objects = data.INTERESTING_OBJECTS
    
    # Use the REST API to get a list of positions for the objects
    try:
        url = data.PLANET_API_URL
        url = url.format(objects,data.LAT,data.LON)
        response = urllib.request.urlopen(url)
        list = json.loads(response.read())
    except:
        return None
        
    filtered_list = []
    for object in list:
        if is_in_direction(direction, object['az']) and object['alt'] > 0 and object['alt'] < 80:
            filtered_list.append(object)
                
    return filtered_list

def locate(object):
    """"Look up star or planet in our database and find its current location
    
    :param object: string
    :return: azimouth in degrees (int), altitude in degrees (int), distance in km (int). If error, return None in all fields.
    """

    # Use the REST API to find location of specified object
    try:
        url = data.PLANET_API_URL
        url = url.format(object,data.LAT,data.LON)
        response = urllib.request.urlopen(url)
        location = json.loads(response.read())
        return round(location['az']),round(location['alt']),tidy(location['distance'],4)
    except:
        return None, None, None

data.py

Python
Data for lambda_function. (Alexa hosted)
WELCOME_MESSAGE = "<audio src=\"soundbank://soundlibrary/alarms/beeps_and_bloops/intro_02\"></audio>\
    Welcome to Sky Finder. You can ask me to find any prominent star, constellation or planet in the sky and I'll show you using the Lego gadget,\
    for example, <amazon:emotion name=\"excited\" intensity=\"medium\">locate venus</amazon:emotion>.\
    You can also ask me what's in a region of the sky, for example, <amazon:emotion name=\"excited\" intensity=\"medium\">what can I see in the south</amazon:emotion>.\
    What would you like me to do?"

NO_BRICK_MESSAGE = "<audio src=\"soundbank://soundlibrary/alarms/beeps_and_bloops/intro_02\"></audio>\
    Welcome to Sky Finder. I couldn't find a Lego Mindstorms gadget connected to this Echo device, but I can continue anyway.\
    You can ask me to find any prominent star, constellation or planet in the sky,\
    for example, <amazon:emotion name=\"excited\" intensity=\"medium\">locate venus</amazon:emotion>.\
    You can also ask me what's in a region of the sky, for example, <amazon:emotion name=\"excited\" intensity=\"medium\">what can I see in the south</amazon:emotion>.\
    What would you like me to do?"

ASK_MESSAGE = "What would you like me to find next? Say stop if you'd like to finish."

GOODBYE_MESSAGE = "Goodbye. Thank you for using Sky Finder."

PLANET_API_URL = "https://uddqj5ojr9.execute-api.eu-west-1.amazonaws.com/Prod/lookup?object={}&lat={}&lon={}"

INTERESTING_OBJECTS = 'sun,moon,venus,mars,jupiter,saturn,acrux,aldebaran,antares,betelgeuse,capella,castor,deneb,fomalhaut,pollux,regulus,rigel,rigil-kentaurus,sirius,spica,polaris,vega,M6,M7,M31,M42,M44,M45,C14,C41,C80,C92,C106'

# My latitude and longitude - NB no spaces
LAT = '51.5842N'
LON = '2.9977W'

OBJECT_DATA = {
    'moon': {
        'name': 'The Moon',
        'database': 'planet',
        'description': "The moon is thought to have formed 4.51 billion years ago, shortly after Earth.\
            The Moon makes a complete orbit around Earth once every 27.3 days. \
            However, because Earth is moving in its orbit around the Sun at the same time, it takes 29.5 days for the Moon to show the same phase to Earth. \
            There have been six manned moon landings. The first was Apollo 11 in July 1969 and the last was Apollo 17 in 1972."
    },
    'sun': {
        'name': 'The Sun',
        'database': 'planet',
        'description': "Please never look directly at the sun. It can permanently damage your eyesight. The Sun is the star at the center of the Solar System with a diameter of about 1.39 million kilometers, or 109 times that of Earth. \
            Roughly three quarters of the Sun's mass is hydrogen and the rest is mostly helium.\
            It's a G-type main-sequence star with an absolute magnitude of plus 4.83, and is estimated to be brighter than about 85% of the stars in the Milky Way, most of which are red dwarfs."
    },
    #
    # Planets
    #
    'mercury': {
        'name': 'Mercury',
        'database': 'planet',
        'description': "Mercury is the innermost planet in the Solar System, and also the smallest with a diameter of 4880 km. Its orbit around the Sun takes just under 88 Earth days. \
            With very little atmosphere to retain heat, it has surface temperatures that range from minus 173 celsius at night to 427 celsius during the day."
    },
    'venus': {
        'name': 'Venus',
        'database': 'planet',
        'description': "Venus is the second planet from the Sun and is the second brightest object in the night sky after the moon. \
            The planet is similar in size and mass to The Earth, but its atmosphere consists of more than 96% carbon dioxide making it the hottest in the Solar System, with a mean surface temperature of 462 celsius.\
            Above the dense carbon dioxide layer are thick clouds consisting mainly of sulphuric acid. Due to this, it's not possible to see the surface of the planet." 
    },
    'mars': {
        'name': 'Mars',
        'database': 'planet',
        'description': "Mars is the fourth planet from the Sun and is often refrred to as the red planet due to iron oxide or rust on its surface.\
        It is a rocky planet with a thin atmosphere.\
        Liquid water cannot exist on the surface of Mars due to low atmospheric pressure, but it has two polar ice caps that appear to be made of water.\
        Mars has two relatively small natural moons, Phobos, which is about 22km in diameter and Deimos which is about 12 km in diameter."
    },
    'jupiter': {
        'name': 'Jupiter',
        'database': 'planet',
        'description': "Jupiter is a gas giant with a mass two-and-a-half times that of all the other planets in the Solar System combined.\
            Jupiter is primarily composed of hydrogen with a quarter of its mass being helium. It may also have a rocky core of heavier elements but it lacks a well-defined solid surface.\
            The outer atmosphere is visibly segregated into several bands at different latitudes, resulting in turbulence and storms along their boundaries, including the Great Red Spot, a giant storm that is known to have existed since at least the 17th century.\
            Jupiter has 79 known moons. The largest of these, Ganymede, has a diameter greater than the planet Mercury."
    },
    'saturn': {
        'name': 'Saturn',
        'database': 'planet',
        'description': "Saturn is the second-largest planet in the solar system and a gas giant. \
            Its radius is nine times that of Earth and it's 95 times more massive. \
            Saturn is famous for its prominent ring system which consists mainly of ice particles.\
            The planet also has at least 82 moons, the largest of which is Titan."
    },
    'uranus': {
        'name': 'Uranus',
        'database': 'planet',
        'description': "Uranus is the seventh planet from the sun and classified as an ice giant.\
        Its axis of rotation is tilted sideways so its poles lie where most planets have their equators.\
        It is just about visible to the naked eye, but even through a telescope it is almost featureless."
    },
    'neptune': {
        'name': 'Neptune',
        'database': 'planet',
        'descrption': "Neptune is the eighth planet from the sun and the fourth largest by diameter.\
            It is classed as a ice giant and orbits the sun every 165 years.\
            Neptune was discovered in 1846 and is not visible to the naked eye."
    },
    'pluto': {
        'name': 'Pluto',
        'database': 'planet',
        'description': "Pluto is a dwarf planet, primarily made of ice and rock.\
            It is about one-sixth the mass of the Moon and one-third its volume.\
            Pluto orbits the sun every 248 years and is not visible to the nked eye."
    },
    #
    # Deep Space objects
    #
    'M6': {
        'name': 'M6 The Butterfly Cluster',
        'database': 'none',
        'description': "The Butterfly Cluster is a naked eye star cluster found in the southern constellation of Scorpius.\
        It is around 1600 light years away and has a diameter of around 12 light years."
    },
    'M7': {
        'name': 'M7 Ptolemy Cluster',
        'database': 'none',
        'description': "The Ptolemy Cluster is a naked eye star cluster found near the stinger of the southern\
            constellation of Scorpius. It is 978 light years from Earth and has a radius of 25 light years."
    },
    'M13': {
        'name': 'Messier 13',
        'database': 'none',
        'description': "Messier 13 is a globular cluster consisting of several hundred thousand stars.\
            It is just about visible to the naked eye as a fuzzy patch of light."
    },
    'M31': {
        'name': 'M31 The Andromeda Galaxy',
        'database': 'none',
        'description': "The Andromeda Galaxy is a spiral galaxy and the nearest to our own, with a distance of \
            2.5 million light years from Earth. It is estimated to contain one trillion stars, approximately \
            twice the number of the Milky Way, and the two galaxies are expected to collide 4.5 billion years from now.\
            It is easily visible to the naked eye on moonless nights."
    },
    'M42': {
        'name': 'M42 The Orion Nebula',
        'database': 'none',
        'description': "The Orion Nebula is a cloud of gas a dusts where new stars are being created.\
            It is located between the stars of Orion's sword, underneath the three stars that make up the belt.\
            The nebula is just 1500 light years from Earth."
    },
    'M44': {
        'name': 'M44 Beehive Cluster',
        'database': 'none',
        'description': "The Beehive Cluster is a open star cluster in the constellation of Cancer.\
            The cluster contains both red giants and white dwarfs and is visible to the naked eye as \
            a fuzzy patch of light."
    },
    'M45': {
        'name': 'M45 Pleiades',
        'database': 'none',
        'description': "Also known as the Seven Sisters, Pleiades is an open star cluster in the constellation of Taurus.\
            It is the most visible star cluster to the naked eye, and is also one of the nearest star clusters to Earth.\
            Though seven stars can been easily seen, the cluster actually consists of over a thousand, most of which are very young, hot, blue stars."
    },
    'C14': {
        'name': 'The Double Cluster',
        'database': 'none',
        'description': "The double Cluster is the name for two open star clusters in the constellation of Perseus.\
            There are more than 300 blue-white super-giant stars in each of the clusters."
    },
    'C41': {
        'name': 'The Hyades',
        'database': 'none',
        'description': "The Hyades Cluster is the closest star cluster to Earth at a distance of just 150 light years.\
            Its distinctive V shape can be found in the constellation of Taurus."
    },
    'C80': {
        'name': 'Omega Centauri',
        'database': 'none',
        'description': "Omega Centauri is the largest globular star cluster in the Milky Way.\
            It consists of around 10 million stars."
    },
    'C92': {
        'name': 'Carina Nebula',
        'database': 'none',
        'description': "The Carina Nebula is one of tha largest star forming regions in our galaxy,\
            visible in the sourthern skies, and around 7500 light years from Earth."
    },
    'C106': {
        'name': '47 Tucanae',
        'database' : 'none',
        'description': "47 Tucanae is a globular star cluster located in the constellation Tucana.\
            It is approximately 13000 light years from Earth.\
            The cluster is the second brightest in the sky after Omega Centauri\
            and may contain a intermediate-mass black hole."
    },
    #
    # Major stars
    #
    'achernar': {
        'name': 'Achernar',
        'database': 'star',
        'description': "Achernar is the ninth brightest star in the sky, but is actually a binary system\
            where the secondary star orbits at 6.7 astrinomical units from the primary.\
            It is located in the system of Eridanus, the river, and the name is Arabic for end of the river.\
            Achercnar is around 1100 times as bright as our sun and is approximately 144 light years from Earth."
    },
    'acrux': {
        'name': 'Acrux',
        'database': 'star',
        'descrption': "Acrux, in the constellation of Crucis appears to be a single star\
            but is actually multiple star system with six components.\
            It appears on the flags of Australia, New Zealand, Samoa and Papua New Guinea\
            as one of the five stars of the Southern Cross."
    },
    'aldebaran': {
        'name': 'Aldebaran',
        'database': 'star',
        'description': "Aldebaran represents the bright eye of the bull in the constellation of Taurus.\
            It appears amongst the stars of the Hyades cluster, but isn't actually a member.\
            The star is a red giant located around 65 light years away, and is 153 times brighter than our sun."
    },
    'altair': {
        'name': 'altair',
        'database': 'star',
        'description': "Altair, in the constellation of Aquila, is the twelth brightest star in the sky.\
            At a distance of 16.7 light years, it is one of the closest stars visible to the naked eye.\
            Due to its rapid rotation, Altair is flattened at its poles."
    },
    'antares': {
        'name': 'Antares',
        'database': 'star',
        'description': "Antares, in the contellation of Scorpius is actually a binary system,\
            consisting of a red giant and a fainter main-sequence star.\
            It is distinctly reddish when viewed with the naked eye \
            and is a slow irregular variable star that varies considerably in brightness."
    },
    'arcturus' : {
        'name': 'Arcturus',
        'database': 'star',
        'descrption': "Arcturus is a red giant in the constellation of Bootes.\
            It is the fourth brightest star in the sky, and is relatively close at 36.7 light years from the Sun."
    },
    'becrux': {
        'name': 'Becrux',
        'database': 'star',
        'description': "Becrux, in the constellation of Crucis, is approximately 280 light years from Earth\
            and is thought to be a binary system, although its second component has not yet been observed."
    },
    'betelgeuse': {
        'name': 'Betelgeuse',
        'database': 'star',
        'description': "Betelgeuse, in the constellation of Orion, is classified as a red supergiant and is one of the largest stars visible to the naked eye.\
        If it were at the center of the Solar System, it would extend past the asteroid belt, engulfing the orbits of Mercury, Venus, Earth, Mars, and possibly Jupiter."
    },
    'canopus': {
        'name': 'Canopus',
        'database': 'star',
        'description': "Canopus is the brightest star in the southern constellation of Carina,\
            and is located around 310 light-years from Earth.\
            The rising of the star marked the date of the Ptolemaia festival in Egypt, and due to its brightness\
            it has been the subject of mythology in many other cultures."
    },
    'capella': {
        'name': 'Capella',
        'database': 'star',
        'description': "Capella is the brightest star in the constellation of Auriga,\
            the charioteer, and is the sixth brightest in the sky.\
            It actually consists of two yellow giant stars, similar in colour to our own sun,\
            approximately 42 light years away from Earth."
    },
    'castor': {
        'name': 'Castor',
        'database': 'star',
        'description': "Castor, in the constellation of Gemini, is actually a multiple system consisting of six stars.\
            The two brightest stars are both main sequence stars with red dwarf companions\
            and the third component consists of two dwarf stars."
    },
    'deneb': {
        'name': 'Deneb',
        'database': 'star',
        'description': "Deneb, the brightest star in the constellation of Cygnus,\
             is a blue-white supergiant whose luminosity is somewhere between 55,000 and 196,000 times that of our Sun."
    },
    'fomalhaut': {
        'name': 'Fomalhaut',
        'database': 'star',
        'description': "Formalhaut, in the constellation of Piscis Austrini, is a main-sequence star approximately\
            25 light years from the Sun.\
            It one of the brightest stars around which astronomers have discovered a planetary system."
    },
    'hadar': {
        'name': 'Hadar',
        'database': 'star',
        'description': 'Hadar is a triple star system in the constellation of Centaurus.\
            Its distance is approximately 390 light-years.'
    },
    'polaris': {
        'name': 'Polaris',
        'database': 'star',
        'description': "Polaris, in the constellation of Ursa Minor is also known as the North Star because it's located at the North Celestial Pole, and remains stationary in the sky.\
            It has been used as a navigational aid for hnudreds of years.\
            Polaris is actually a triple star system, consisting of a main star and two smaller companions, but these can only be seen with a telescope."
    },
    'pollux': {
        'name': 'Pollux',
        'database': 'star',
        'description': "Pollux, in the constellation of Gemini is a red giant star, about 34 light years from Earth,\
            making it the closest giant star to the Sun.\
            In 2006, a planet, later named Thestias, was found to be orbiting it at approximately\
            the same distance as Mars from our Sun."
    },
    'procyon': {
        'name': 'Procyon',
        'database': 'star',
        'description': "Procyon, the brightest star in the constellation of Canis Minor is one of our nearest neighbours\
            at a distance of 11.46 light years. It is actually as binary system consisting of a white\
            main-sequence star with a faint white dwarf companion."
    },
    'regulus': {
        'name': 'Regulus',
        'database': 'star',
        'description': "Regulus, in the constellation of Leo is a quadruple star system, consisting of\
            a blue-white main sequence star, orbited by a companion approximately 100 times the distance\
            of Pluto from our Sun. That compansion is itself a double star. The primary star also appears\
            to have another small star oribiting very close to it, probably a white dwarf. "
    },
    'rigel': {
        'name': 'Rigel',
        'database': 'star',
        'description': "Rigel is the brightest star in the constellation of Orion and the seventh brightest\
            star in the sky. It is a massive blue supergiant\
            that is between 61000 to 363000 times as luminous as the Sun.\
            Rigel is part of a system with at least three other stars that are significant fainter."
    },
    'rigil-kentaurus': {
        'name': 'Rigil Kentaurus or Alpha Centauri',
        'database': 'star',
        'description': "Alpha Centauri is the brightest star in the constellation of Centaurus.\
            It's the closest star to our sun at just over 4 light years and the third brightest star in the night sky.\
            It is actually a multistar system, consisting of Alpha Centauri A, B, and Proxima Centauri.\
            In 2012, astronomers discovered an Earth-sized planet around Alpha Centauri B, but it's probably too close to the star to support life."
    },
    'sirius': {
        'name': 'Sirius',
        'database': 'star',
        'description': "Sirius, in the constellation Canis Major is also known as The Dog Star and is the brightest star in the night sky.\
            It is actually a binary star system, consisting of a white main-sequence star and a faint white dwarf companion.\
            The star appears to be so bright, in part, because it is one of our nearest neighbours at a distance of just 6.8 light years.\
            The so-called dog days of summer have long been associated with the appearance of Sirius at that time of year."
    },
    'spica' : {
        'name': 'Spica',
        'database': 'star',
        'description': "Spica is the brightest object in the constellation of Virgo.\
        It is actually a binary star system whose components orbit each other every four days.\
        The two stars are so close that gravity distorts them into an egg shape."
    },
    'vega' : {
        'name': 'Vega',
        'database': 'star',
        'description': "Vega is the fifth brightest star in the night sky and can be found in the constellation of Lyra.\
        It is just 25 light-years from Earth and has been extensively studied.\
        Vega was the first star to be photographed in 1850 and was chosen for the first spectographic image in 1872."
    },
    #
    # Constellations
    #
    'and': {
        'name': 'Andromeda',
        'database': 'constellation'
    },
    'ant': {
        'name': 'Antlia',
        'database': 'constellation'
    },
    'aps': {
        'name': 'Apus',
        'database': 'constellation'
    },
    'aqr': {
        'name': 'Aquarius',
        'database': 'constellation'
    },
    'aql': {
        'name': 'Aquila',
        'database': 'constellation'
    },
    'ara': {
        'name': 'Ara',
        'database': 'constellation'
    },
    'ari': {
        'name': 'Aries',
        'database': 'constellation'
    },
    'aur': {
        'name': 'Auriga',
        'database': 'constellation'
    },
    'boo': {
        'name': 'Bootes',
        'database': 'constellation'
    },
    'cae': {
        'name': 'Caelum',
        'database': 'constellation'
    },
    'cam': {
        'name': 'Camelopardalis',
        'database': 'constellation'
    },
    'cnc': {
        'name': 'Cancer',
        'database': 'constellation'
    },
    'cvn': {
        'name': 'Canes Venatici',
        'database': 'constellation'
    },
    'cma': {
        'name': 'Canis Major',
        'database': 'constellation'
    },
    'cmi': {
        'name': 'Canis Minor',
        'database': 'constellation'
    },
    'cap': {
        'name': 'Capricornus',
        'database': 'constellation'
    },
    'car': {
        'name': 'Carina',
        'database': 'constellation'
    },
    'cas': {
        'name': 'Cassiopeia',
        'database': 'constellation'
    },
    'cen': {
        'name': 'Centaurus',
        'database': 'constellation'
    },
    'cep': {
        'name': 'Cepheus',
        'database': 'constellation'
    },
    'cet': {
        'name': 'Cetus',
        'database': 'constellation'
    },
    'cha': {
        'name': 'Chamaeleon',
        'database': 'constellation'
    },
    'cir': {
        'name': 'Circinus',
        'database': 'constellation'
    },
    'col': {
        'name': 'Columba',
        'database': 'constellation'
    },
    'com': {
        'name': 'Coma Berenices',
        'database': 'constellation'
    },
    'cra': {
        'name': 'Corona Australis',
        'database': 'constellation'
    },
    'crb': {
        'name': 'Corona Borealis',
        'database': 'constellation'
    },
    'crv': {
        'name': 'Corvus',
        'database': 'constellation'
    },
    'crt': {
        'name': 'Crater',
        'database': 'constellation'
    },
    'cru': {
        'name': 'Crux',
        'database': 'constellation'
    },
    'cyg': {
        'name': 'Cygnus',
        'database': 'constellation'
    },
    'del': {
        'name': 'Delphinus',
        'database': 'constellation'
    },
    'dor': {
        'name': 'Dorado',
        'database': 'constellation'
    },
    'dra': {
        'name': 'Draco',
        'database': 'constellation'
    },
    'equ': {
        'name': 'Equuleus',
        'database': 'constellation'
    },
    'eri': {
        'name': 'Eridanus',
        'database': 'constellation'
    },
    'for': {
        'name': 'Fornax',
        'database': 'constellation'
    },
    'gem': {
        'name': 'Gemini',
        'database': 'constellation'
    },
    'gru': {
        'name': 'Grus',
        'database': 'constellation'
    },
    'her': {
        'name': 'Hercules',
        'database': 'constellation'
    },
    'hor': {
        'name': 'Horologium',
        'database': 'constellation'
    },
    'hya': {
        'name': 'Hydra',
        'database': 'constellation'
    },
    'hyi': {
        'name': 'Hydrus',
        'database': 'constellation'
    },
    'ind': {
        'name': 'Indus',
        'database': 'constellation'
    },
    'lac': {
        'name': 'Lacerta',
        'database': 'constellation'
    },
    'leo': {
        'name': 'Leo',
        'database': 'constellation'
    },
    'lmi': {
        'name': 'Leo Minor',
        'database': 'constellation'
    },
    'lep': {
        'name': 'Lepus',
        'database': 'constellation'
    },
    'lib': {
        'name': 'Libra',
        'database': 'constellation'
    },
    'lup': {
        'name': 'Lupus',
        'database': 'constellation'
    },
    'lyn': {
        'name': 'Lynx',
        'database': 'constellation'
    },
    'lyr': {
        'name': 'Lyra',
        'database': 'constellation'
    },
    'men': {
        'name': 'Mensa',
        'database': 'constellation'
    },
    'mic': {
        'name': 'Microscopium',
        'database': 'constellation'
    },
    'mon': {
        'name': 'Monoceros',
        'database': 'constellation'
    },
    'mus': {
        'name': 'Musca',
        'database': 'constellation'
    },
    'nor': {
        'name': 'Norma',
        'database': 'constellation'
    },
    'oct': {
        'name': 'Octans',
        'database': 'constellation'
    },
    'oph': {
        'name': 'Ophiuchus',
        'database': 'constellation'
    },
    'ori': {
        'name': 'Orion',
        'database': 'constellation'
    },
    'pav': {
        'name': 'Pavo',
        'database': 'constellation'
    },
    'peg': {
        'name': 'Pegasus',
        'database': 'constellation'
    },
    'per': {
        'name': 'Perseus',
        'database': 'constellation'
    },
    'phe': {
        'name': 'Phoenix',
        'database': 'constellation'
    },
    'pic': {
        'name': 'Pictor',
        'database': 'constellation'
    },
    'psc': {
        'name': 'Pisces',
        'database': 'constellation'
    },
    'psa': {
        'name': 'Piscis Austrinus',
        'database': 'constellation'
    },
    'pup': {
        'name': 'Puppis',
        'database': 'constellation'
    },
    'pyx': {
        'name': 'Pyxis',
        'database': 'constellation'
    },
    'ret': {
        'name': 'Reticulum',
        'database': 'constellation'
    },
    'sge': {
        'name': 'Sagitta',
        'database': 'constellation'
    },
    'sgr': {
        'name': 'Sagittarius',
        'database': 'constellation'
    },
    'sco': {
        'name': 'Scorpius',
        'database': 'constellation'
    },
    'scl': {
        'name': 'Sculptor',
        'database': 'constellation'
    },
    'sct': {
        'name': 'Scutum',
        'database': 'constellation'
    },
    'ser': {
        'name': 'Serpens',
        'database': 'constellation'
    },
    'sex': {
        'name': 'Sextans',
        'database': 'constellation'
    },
    'tau': {
        'name': 'Taurus',
        'database': 'constellation'
    },
    'tel': {
        'name': 'Telescopium',
        'database': 'constellation'
    },
    'tri': {
        'name': 'Triangulum',
        'database': 'constellation'
    },
    'tra': {
        'name': 'Triangulum Australe',
        'database': 'constellation'
    },
    'tuc': {
        'name': 'Tucana',
        'database': 'constellation'
    },
    'uma': {
        'name': 'Ursa Major',
        'database': 'constellation'
    },
    'umi': {
        'name': 'Ursa Minor',
        'database': 'constellation'
    },
    'vel': {
        'name': 'Vela',
        'database': 'constellation'
    },
    'vir': {
        'name': 'Virgo',
        'database': 'constellation'
    },
    'vol': {
        'name': 'Volans',
        'database': 'constellation'
    },
    'vul': {
        'name': 'Vulpecula',
        'database': 'constellation'
    }
}
    

requirements.txt

Plain text
Dependencies for lambda_function (Alexa hosted)
boto3==1.9.216
ask-sdk-core==1.11.0

API code: lambda_function.py

Python
You can ignore this file if you're following the project instructions. It's part of the API code used to calculate star and planet positions, included for reference by request.

Dependency: Skyfield API https://rhodesmill.org/skyfield/
import os
import urllib
import json
import data
import utils
from skyfield.api import load, Loader, Star, Topos

def lambda_handler(event, context):
 
    # load planet ephemeris (static version)
    load = Loader('skyfield-data', expire=False)
    planets = load('de421.bsp')
    earth = planets['earth']
    ts = load.timescale(builtin=True)
    t = ts.now()
    
    try:
        lat = event['queryStringParameters']['lat']
        lon = event['queryStringParameters']['lon']
    except:
        # Default to Royal Greenwich Observatory
        lat = '51.4934N'
        lon = '0W'

    # Get list of objects from query string
    try:
        object = event['queryStringParameters']['object']
    except:
        object='sun'
    
    list = object.split(',')
    mylocation = earth + Topos(lat, lon)
    returnlist = []
    
    for body in list:
        
        if body in planets:
            target = planets[body]
        elif body + ' barycenter' in planets:
            target = planets[body+' barycenter']
        elif body in data.MAJOR_STARS:
            target = utils.make_star(body)
        else:
            continue
        
        item = {}
        astrometric = mylocation.at(t).observe(target)
        alt, az, d = astrometric.apparent().altaz()
        item['name'] = body
        item['alt'] = alt.degrees
        item['az'] = az.degrees
        if body in data.MAJOR_STARS:
            item['distance'] = 0
        else:
            item['distance'] = d.km

        returnlist.append(item)
    
    if len(returnlist) == 0:
        return {
            'statusCode': 404
        }
    elif len(returnlist) == 1:
        return {
        'statusCode': 200,
        'body': json.dumps(returnlist[0])
        }
    else:
        return {
            'statusCode': 200,
            'body': json.dumps(returnlist)
            }

API code: utils.py

Python
You can ignore this file if you're following the project instructions. It's part of the API code used to calculate star and planet positions, included for reference by request.
from skyfield.api import Star
import data

def get_highlights(planets):
    highlights = {}
    highlights['sun'] = planets['sun']
    highlights['moon'] = planets['moon']
    highlights['venus'] = planets['venus']
    highlights['mars'] = planets['mars']
    highlights['jupiter'] = planets['jupiter barycenter']
    highlights['saturn'] = planets['saturn barycenter']
    highlights['betelgeuse'] = make_star('betelgeuse')
    highlights['polaris'] = make_star('polaris')
    
    return highlights

def make_star(object):
    try:
        info = data.MAJOR_STARS[object]
    except:
        return None
    ra = info['ra'].split()
    dec = info['dec'].split()
    ra = list(map(int, ra))
    dec = list(map(int, dec))
    return Star(ra_hours=(ra[0],ra[1],ra[2]),
                dec_degrees=(dec[0],dec[1],dec[2]))

API code: data.py

Python
You can ignore this file if you're following the project instructions. It's part of the API code used to calculate star and planet positions, included for reference by request.
# Small datafile of major stars and constellations
# Skyfield can also use the Hippoarchos catalogue, but it doesn't include star names
# so we've made our own

MAJOR_STARS = {
    'achernar': {
        'ra': '1 37 42',
        'dec': '-57 14 12'
    },
    'acrux': {
        'ra': '12 26 35',
        'dec': '-63 5 57'
    },
    'aldebaran': {
        'ra': '04 35 55',
        'dec': '16 30 33'
    },
    'altair': {
        'ra': '19 50 46',
        'dec': '8 52 05'
    },
    'antares': {
        'ra': '16 29 24',
        'dec': '-26 25 55'
    },
    'arcturus' : {
        'ra': '14 15 40',
        'dec': '19 10 56'
    },
    'becrux': {
        'ra': '12 47 43',
        'dec': '-59 41 20'
    },
    'betelgeuse': {
        'ra': '5 55 10',
        'dec': '7 24 26'
    },
    'canopus': {
        'ra': '6 23 57',
        'dec': '-52 41 44'
    },
    'capella': {
        'ra': '5 16 41',
        'dec': '45 59 53'
    },
    'castor': {
        'ra': '7 34 36',
        'dec': '31 53 18'
    },
    'deneb': {
        'ra': '20 41 26',
        'dec': '45 16 49'
    },
    'fomalhaut': {
        'ra': '22 57 39',
        'dec': '-29 37 20'
    },
    'hadar': {
        'ra': '14 3 49',
        'dec': '-60 22 23'
    },
    'polaris': {
        'ra' : '2 31 49',
        'dec' : '89 15 50'
    },
    'pollux': {
        'ra': '7 45 19',
        'dec': '28 1 34'
    },
    'procyon': {
        'ra': '7 39 18',
        'dec': '5 13 30'
    },
    'regulus': {
        'ra': '10 08 22',
        'dec': '11 58 2'
    },
    'rigel': {
        'ra': '05 14 32',
        'dec': '-8 12 6'
    },
    'rigil-kentaurus': {
        'ra': '14 39 36',
        'dec': '-60 50 2'
    },
    'sirius': {
        'ra': '06 45 9',
        'dec': '-16 42 58'
    },
    'spica' : {
        'ra': '13 25 12',
        'dec': '-11 9 41'
    },
    'vega' : {
        'ra': '18 36 56',
        'dec': '38 47 01'
    },
    #
    # Messier Objects
    #
    'M1': {
        'ra':'5 35 0',
        'dec':'+22 1 0'
    },
    'M2': {
        'ra':'21 34 0',
        'dec':'-0 49 0'
    },
    'M3': {
        'ra':'13 42 0',
        'dec':'+28 23 0'
    },
    'M4': {
        'ra':'16 24 0',
        'dec':'-26 32 0'
    },
    'M5': {
        'ra':'15 19 0',
        'dec':'+2 5 0'
    },
    'M6': {
        'ra':'17 40 0',
        'dec':'-32 13 0'
    },
    'M7': {
        'ra':'17 54 0',
        'dec':'-34 49 0'
    },
    'M8': {
        'ra':'18 4 0',
        'dec':'-24 23 0'
    },
    'M9': {
        'ra':'17 19 0',
        'dec':'-18 31 0'
    },
    'M10': {
        'ra':'16 57 0',
        'dec':'-4 6 0'
    },
    'M11': {
        'ra':'18 51 0',
        'dec':'-6 16 0'
    },
    'M12': {
        'ra':'16 47 0',
        'dec':'-1 57 0'
    },
    'M13': {
        'ra':'16 42 0',
        'dec':'+36 28 0'
    },
    'M14': {
        'ra':'17 38 0',
        'dec':'-3 15 0'
    },
    'M15': {
        'ra':'21 30 0',
        'dec':'+12 10 0'
    },
    'M16': {
        'ra':'18 19 0',
        'dec':'-13 47 0'
    },
    'M17': {
        'ra':'18 21 0',
        'dec':'-16 11 0'
    },
    'M18': {
        'ra':'18 20 0',
        'dec':'-17 8 0'
    },
    'M19': {
        'ra':'17 3 0',
        'dec':'-26 16 0'
    },
    'M20': {
        'ra':'18 2 0',
        'dec':'-23 2 0'
    },
    'M21': {
        'ra':'18 5 0',
        'dec':'-22 30 0'
    },
    'M22': {
        'ra':'18 36 0',
        'dec':'-23 54 0'
    },
    'M23': {
        'ra':'17 57 0',
        'dec':'-19 1 0'
    },
    'M24': {
        'ra':'18 18 0',
        'dec':'-18 25 0'
    },
    'M25': {
        'ra':'18 32 0',
        'dec':'-19 14 0'
    },
    'M26': {
        'ra':'18 45 0',
        'dec':'-9 24 0'
    },
    'M27': {
        'ra':'19 60 0',
        'dec':'+22 43 0'
    },
    'M28': {
        'ra':'18 25 0',
        'dec':'-24 52 0'
    },
    'M29': {
        'ra':'20 24 0',
        'dec':'+38 32 0'
    },
    'M30': {
        'ra':'21 40 0',
        'dec':'-23 11 0'
    },
    'M31': {
        'ra':'0 43 0',
        'dec':'+41 16 0'
    },
    'M32': {
        'ra':'0 43 0',
        'dec':'+40 52 0'
    },
    'M33': {
        'ra':'1 34 0',
        'dec':'+30 39 0'
    },
    'M34': {
        'ra':'2 42 0',
        'dec':'+42 47 0'
    },
    'M35': {
        'ra':'6 9 0',
        'dec':'+24 20 0'
    },
    'M36': {
        'ra':'5 36 0',
        'dec':'+34 8 0'
    },
    'M37': {
        'ra':'5 52 0',
        'dec':'+32 33 0'
    },
    'M38': {
        'ra':'5 29 0',
        'dec':'+35 50 0'
    },
    'M39': {
        'ra':'21 32 0',
        'dec':'+48 26 0'
    },
    'M40': {
        'ra':'12 22 0',
        'dec':'+58 5 0'
    },
    'M41': {
        'ra':'6 47 0',
        'dec':'-20 44 0'
    },
    'M42': {
        'ra':'5 35 0',
        'dec':'-5 27 0'
    },
    'M43': {
        'ra':'5 36 0',
        'dec':'-5 16 0'
    },
    'M44': {
        'ra':'8 40 0',
        'dec':'+19 59 0'
    },
    'M45': {
        'ra':'3 47 0',
        'dec':'+24 7 0'
    },
    'M46': {
        'ra':'7 42 0',
        'dec':'-14 49 0'
    },
    'M47': {
        'ra':'7 37 0',
        'dec':'-14 30 0'
    },
    'M48': {
        'ra':'8 14 0',
        'dec':'-5 48 0'
    },
    'M49': {
        'ra':'12 30 0',
        'dec':'+8 0 0'
    },
    'M50': {
        'ra':'7 3 0',
        'dec':'-8 20 0'
    },
    'M51': {
        'ra':'13 30 0',
        'dec':'+47 12 0'
    },
    'M52': {
        'ra':'23 24 0',
        'dec':'+61 35 0'
    },
    'M53': {
        'ra':'13 13 0',
        'dec':'+18 10 0'
    },
    'M54': {
        'ra':'18 55 0',
        'dec':'-30 29 0'
    },
    'M55': {
        'ra':'19 40 0',
        'dec':'-30 58 0'
    },
    'M56': {
        'ra':'19 17 0',
        'dec':'+30 11 0'
    },
    'M57': {
        'ra':'18 54 0',
        'dec':'+33 2 0'
    },
    'M58': {
        'ra':'12 38 0',
        'dec':'+11 49 0'
    },
    'M59': {
        'ra':'12 42 0',
        'dec':'+11 39 0'
    },
    'M60': {
        'ra':'12 44 0',
        'dec':'+11 33 0'
    },
    'M61': {
        'ra':'12 22 0',
        'dec':'+4 28 0'
    },
    'M62': {
        'ra':'17 1 0',
        'dec':'-30 7 0'
    },
    'M63': {
        'ra':'13 16 0',
        'dec':'+42 2 0'
    },
    'M64': {
        'ra':'12 57 0',
        'dec':'+21 41 0'
    },
    'M65': {
        'ra':'11 19 0',
        'dec':'+13 5 0'
    },
    'M66': {
        'ra':'11 20 0',
        'dec':'+12 59 0'
    },
    'M67': {
        'ra':'8 50 0',
        'dec':'+11 49 0'
    },
    'M68': {
        'ra':'12 40 0',
        'dec':'-26 45 0'
    },
    'M69': {
        'ra':'18 31 0',
        'dec':'-32 21 0'
    },
    'M70': {
        'ra':'18 43 0',
        'dec':'-32 18 0'
    },
    'M71': {
        'ra':'19 54 0',
        'dec':'+18 47 0'
    },
    'M72': {
        'ra':'20 54 0',
        'dec':'-12 32 0'
    },
    'M73': {
        'ra':'20 59 0',
        'dec':'-12 38 0'
    },
    'M74': {
        'ra':'1 37 0',
        'dec':'+15 47 0'
    },
    'M75': {
        'ra':'20 6 0',
        'dec':'-21 55 0'
    },
    'M76': {
        'ra':'1 42 0',
        'dec':'+51 34 0'
    },
    'M77': {
        'ra':'2 43 0',
        'dec':'+0 1 0'
    },
    'M78': {
        'ra':'5 47 0',
        'dec':'+0 3 0'
    },
    'M79': {
        'ra':'5 25 0',
        'dec':'-24 33 0'
    },
    'M80': {
        'ra':'16 17 0',
        'dec':'-22 59 0'
    },
    'M81': {
        'ra':'9 56 0',
        'dec':'+69 4 0'
    },
    'M82': {
        'ra':'9 56 0',
        'dec':'+69 41 0'
    },
    'M83': {
        'ra':'13 37 0',
        'dec':'-29 52 0'
    },
    'M84': {
        'ra':'12 25 0',
        'dec':'+12 53 0'
    },
    'M85': {
        'ra':'12 25 0',
        'dec':'+18 11 0'
    },
    'M86': {
        'ra':'12 26 0',
        'dec':'+12 57 0'
    },
    'M87': {
        'ra':'12 31 0',
        'dec':'+12 24 0'
    },
    'M88': {
        'ra':'12 32 0',
        'dec':'+14 25 0'
    },
    'M89': {
        'ra':'12 36 0',
        'dec':'+12 33 0'
    },
    'M90': {
        'ra':'12 37 0',
        'dec':'+13 10 0'
    },
    'M91': {
        'ra':'12 35 0',
        'dec':'+14 30 0'
    },
    'M92': {
        'ra':'17 17 0',
        'dec':'+43 8 0'
    },
    'M93': {
        'ra':'7 45 0',
        'dec':'-23 52 0'
    },
    'M94': {
        'ra':'12 51 0',
        'dec':'+41 7 0'
    },
    'M95': {
        'ra':'10 44 0',
        'dec':'+11 42 0'
    },
    'M96': {
        'ra':'10 47 0',
        'dec':'+11 49 0'
    },
    'M97': {
        'ra':'11 15 0',
        'dec':'+55 1 0'
    },
    'M98': {
        'ra':'12 14 0',
        'dec':'+14 54 0'
    },
    'M99': {
        'ra':'12 19 0',
        'dec':'+14 25 0'
    },
    'M100': {
        'ra':'12 23 0',
        'dec':'+15 49 0'
    },
    'M101': {
        'ra':'14 3 0',
        'dec':'+54 21 0'
    },
    'M102': {
        'ra':'15 7 0',
        'dec':'+55 46 0'
    },
    'M103': {
        'ra':'1 33 0',
        'dec':'+60 42 0'
    },
    'M104': {
        'ra':'12 40 0',
        'dec':'-11 37 0'
    },
    'M105': {
        'ra':'10 48 0',
        'dec':'+12 35 0'
    },
    'M106': {
        'ra':'12 19 0',
        'dec':'+47 18 0'
    },
    'M107': {
        'ra':'16 33 0',
        'dec':'-13 3 0'
    },
    'M108': {
        'ra':'11 12 0',
        'dec':'+55 40 0'
    },
    'M109': {
        'ra':'11 58 0',
        'dec':'+53 23 0'
    },
    'M110': {
        'ra':'0 40 0',
        'dec':'+41 41 0'
    },
    #
    # Caldwell Objects
    #
    'C1': {
        'ra':'0 44 0',
        'dec':'+85 20 0'
    },
    'C2': {
        'ra':'0 13 0',
        'dec':'+72 32 0'
    },
    'C3': {
        'ra':'12 17 0',
        'dec':'+69 28 0'
    },
    'C4': {
        'ra':'21 2 0',
        'dec':'+68 12 0'
    },
    'C5': {
        'ra':'3 47 0',
        'dec':'+68 6 0'
    },
    'C6': {
        'ra':'17 59 0',
        'dec':'+66 38 0'
    },
    'C7': {
        'ra':'7 37 0',
        'dec':'+65 36 0'
    },
    'C8': {
        'ra':'1 30 0',
        'dec':'+63 18 0'
    },
    'C9': {
        'ra':'22 57 0',
        'dec':'+62 37 0'
    },
    'C10': {
        'ra':'1 46 0',
        'dec':'+61 15 0'
    },
    'C11': {
        'ra':'23 21 0',
        'dec':'+61 12 0'
    },
    'C12': {
        'ra':'20 35 0',
        'dec':'+60 9 0'
    },
    'C13': {
        'ra':'1 19 0',
        'dec':'+58 20 0'
    },
    'C14': {
        'ra':'2 20 0',
        'dec':'+57 8 0'
    },
    'C15': {
        'ra':'19 45 0',
        'dec':'+50 31 0'
    },
    'C16': {
        'ra':'22 15 0',
        'dec':'+49 53 0'
    },
    'C17': {
        'ra':'0 33 0',
        'dec':'+48 30 0'
    },
    'C18': {
        'ra':'0 39 0',
        'dec':'+48 20 0'
    },
    'C19': {
        'ra':'21 54 0',
        'dec':'+47 16 0'
    },
    'C20': {
        'ra':'20 59 0',
        'dec':'+44 20 0'
    },
    'C21': {
        'ra':'12 28 0',
        'dec':'+44 6 0'
    },
    'C22': {
        'ra':'23 26 0',
        'dec':'+42 33 0'
    },
    'C23': {
        'ra':'2 23 0',
        'dec':'+42 21 0'
    },
    'C24': {
        'ra':'3 20 0',
        'dec':'+41 31 0'
    },
    'C25': {
        'ra':'7 38 0',
        'dec':'+38 53 0'
    },
    'C26': {
        'ra':'12 18 0',
        'dec':'+37 49 0'
    },
    'C27': {
        'ra':'20 12 0',
        'dec':'+38 21 0'
    },
    'C28': {
        'ra':'1 58 0',
        'dec':'+37 41 0'
    },
    'C29': {
        'ra':'13 11 0',
        'dec':'+37 3 0'
    },
    'C30': {
        'ra':'22 37 0',
        'dec':'+34 25 0'
    },
    'C31': {
        'ra':'5 16 0',
        'dec':'+34 16 0'
    },
    'C32': {
        'ra':'12 42 0',
        'dec':'+32 32 0'
    },
    'C33': {
        'ra':'20 56 0',
        'dec':'+31 43 0'
    },
    'C34': {
        'ra':'20 46 0',
        'dec':'+30 43 0'
    },
    'C35': {
        'ra':'13 0 0',
        'dec':'+27 59 0'
    },
    'C36': {
        'ra':'12 36 0',
        'dec':'+27 58 0'
    },
    'C37': {
        'ra':'20 12 0',
        'dec':'+26 29 0'
    },
    'C38': {
        'ra':'12 36 0',
        'dec':'+25 59 0'
    },
    'C39': {
        'ra':'7 29 0',
        'dec':'+20 55 0'
    },
    'C40': {
        'ra':'11 20 0',
        'dec':'+18 21 0'
    },
    'C41': {
        'ra':'4 27 0',
        'dec':'+16 0 0'
    },
    'C42': {
        'ra':'21 2 0',
        'dec':'+16 11 0'
    },
    'C43': {
        'ra':'0 3 0',
        'dec':'+16 9 0'
    },
    'C44': {
        'ra':'23 5 0',
        'dec':'+12 19 0'
    },
    'C45': {
        'ra':'13 38 0',
        'dec':'+8 53 0'
    },
    'C46': {
        'ra':'6 39 0',
        'dec':'+8 44 0'
    },
    'C47': {
        'ra':'20 34 0',
        'dec':'+7 24 0'
    },
    'C48': {
        'ra':'9 10 0',
        'dec':'+7 2 0'
    },
    'C49': {
        'ra':'6 32 0',
        'dec':'+5 3 0'
    },
    'C50': {
        'ra':'6 32 0',
        'dec':'+4 52 0'
    },
    'C51': {
        'ra':'1 5 0',
        'dec':'+2 7 0'
    },
    'C52': {
        'ra':'12 49 0',
        'dec':'-5 48 0'
    },
    'C53': {
        'ra':'10 5 0',
        'dec':'-7 43 0'
    },
    'C54': {
        'ra':'8 0 0',
        'dec':'-10 47 0'
    },
    'C55': {
        'ra':'21 4 0',
        'dec':'-11 22 0'
    },
    'C56': {
        'ra':'0 47 0',
        'dec':'-11 53 0'
    },
    'C57': {
        'ra':'19 45 0',
        'dec':'-14 48 0'
    },
    'C58': {
        'ra':'7 18 0',
        'dec':'-15 37 0'
    },
    'C59': {
        'ra':'10 25 0',
        'dec':'-18 38 0'
    },
    'C60': {
        'ra':'12 2 0',
        'dec':'-18 52 0'
    },
    'C61': {
        'ra':'12 2 0',
        'dec':'-18 53 0'
    },
    'C62': {
        'ra':'0 47 0',
        'dec':'-20 46 0'
    },
    'C63': {
        'ra':'22 30 0',
        'dec':'-20 48 0'
    },
    'C64': {
        'ra':'7 19 0',
        'dec':'-24 57 0'
    },
    'C65': {
        'ra':'0 48 0',
        'dec':'-25 17 0'
    },
    'C66': {
        'ra':'14 40 0',
        'dec':'-26 32 0'
    },
    'C67': {
        'ra':'2 46 0',
        'dec':'-30 17 0'
    },
    'C68': {
        'ra':'19 2 0',
        'dec':'-36 57 0'
    },
    'C69': {
        'ra':'17 14 0',
        'dec':'-37 6 0'
    },
    'C70': {
        'ra':'0 55 0',
        'dec':'-37 41 0'
    },
    'C71': {
        'ra':'7 52 0',
        'dec':'-38 33 0'
    },
    'C72': {
        'ra':'0 15 0',
        'dec':'-39 11 0'
    },
    'C73': {
        'ra':'5 14 0',
        'dec':'-40 3 0'
    },
    'C74': {
        'ra':'10 8 0',
        'dec':'-40 26 0'
    },
    'C75': {
        'ra':'16 26 0',
        'dec':'-40 40 0'
    },
    'C76': {
        'ra':'16 54 0',
        'dec':'-41 48 0'
    },
    'C77': {
        'ra':'13 26 0',
        'dec':'-43 1 0'
    },
    'C78': {
        'ra':'18 8 0',
        'dec':'-43 42 0'
    },
    'C79': {
        'ra':'10 18 0',
        'dec':'-46 25 0'
    },
    'C80': {
        'ra':'13 27 0',
        'dec':'-47 29 0'
    },
    'C81': {
        'ra':'17 26 0',
        'dec':'-48 25 0'
    },
    'C82': {
        'ra':'16 41 0',
        'dec':'-48 46 0'
    },
    'C83': {
        'ra':'13 5 0',
        'dec':'-49 28 0'
    },
    'C84': {
        'ra':'13 46 0',
        'dec':'-51 22 0'
    },
    'C85': {
        'ra':'8 40 0',
        'dec':'-53 4 0'
    },
    'C86': {
        'ra':'17 41 0',
        'dec':'-53 40 0'
    },
    'C87': {
        'ra':'3 12 0',
        'dec':'-55 13 0'
    },
    'C88': {
        'ra':'15 6 0',
        'dec':'-55 36 0'
    },
    'C89': {
        'ra':'16 19 0',
        'dec':'-57 54 0'
    },
    'C90': {
        'ra':'9 21 0',
        'dec':'-58 19 0'
    },
    'C91': {
        'ra':'11 6 0',
        'dec':'-58 40 0'
    },
    'C92': {
        'ra':'10 44 0',
        'dec':'-59 52 0'
    },
    'C93': {
        'ra':'19 11 0',
        'dec':'-59 59 0'
    },
    'C94': {
        'ra':'12 54 0',
        'dec':'-60 20 0'
    },
    'C95': {
        'ra':'16 4 0',
        'dec':'-60 30 0'
    },
    'C96': {
        'ra':'7 58 0',
        'dec':'-60 52 0'
    },
    'C97': {
        'ra':'11 36 0',
        'dec':'-61 37 0'
    },
    'C98': {
        'ra':'12 42 0',
        'dec':'-62 58 0'
    },
    'C99': {
        'ra':'12 53 0',
        'dec':'-63 0 0'
    },
    'C100': {
        'ra':'11 37 0',
        'dec':'-63 2 0'
    },
    'C101': {
        'ra':'19 10 0',
        'dec':'-63 51 0'
    },
    'C102': {
        'ra':'10 43 0',
        'dec':'-64 24 0'
    },
    'C103': {
        'ra':'5 39 0',
        'dec':'-69 6 0'
    },
    'C104': {
        'ra':'1 3 0',
        'dec':'-70 51 0'
    },
    'C105': {
        'ra':'12 60 0',
        'dec':'-70 53 0'
    },
    'C106': {
        'ra':'0 24 0',
        'dec':'-72 5 0'
    },
    'C107': {
        'ra':'16 26 0',
        'dec':'-72 12 0'
    },
    'C108': {
        'ra':'12 26 0',
        'dec':'-72 40 0'
    },
    'C109': {
        'ra':'10 10 0',
        'dec':'-80 52 0'
    },
    #
    # Constellation centres
    #
    'and': {
        'ra': '0 48 0',
        'dec': '37 26 0'
    },
    'ant': {
        'ra': '10 16 0',
        'dec': '-32 29 0'
    },
    'aps': {
        'ra': '16 9 0',
        'dec': '-75 18 0'
    },
    'aqr': {
        'ra': '22 17 0',
        'dec': '-10 47 0'
    },
    'aql': {
        'ra': '19 40 0',
...

This file has been truncated, please download it to see its full contents.

Credits

Neil Pollard

Neil Pollard

1 project • 12 followers

Comments