<commit_before>"""
Public facing API views
"""
import collections

from django.conf import settings
from django.views.generic import View
from django.contrib.contenttypes.models import ContentType
from rest_framework import routers, status, viewsets
from rest_framework.response import Response
from opal.models import Episode, Synonym, Team, Macro
from opal.core import application, exceptions, plugins
from opal.core import glossolalia
from opal.core.lookuplists import LookupList
from opal.utils import stringport, camelcase_to_underscore
from opal.core import schemas
from opal.core.subrecords import subrecords
from opal.core.views import _get_request_data, _build_json_response

app = application.get_app()

# TODO This is stupid - we can fully deprecate this please?
try:
    options = stringport(settings.OPAL_OPTIONS_MODULE)
    micro_test_defaults = options.micro_test_defaults
except AttributeError:
    class options:
        model_names = []
    micro_test_defaults = []

class OPALRouter(routers.DefaultRouter):
    def get_default_base_name(self, viewset):
        name = getattr(viewset, 'base_name', None)
        if name is None:
            return routers.DefaultRouter.get_default_base_name(self, viewset)
        return name

router = OPALRouter()

def item_from_pk(fn):
    """
    Decorator that passes an instance or returns a 404 from pk kwarg.
    """
    def get_item(self, request, pk=None):
        try:
            item = self.model.objects.get(pk=pk)
        except self.model.DoesNotExist:
            return Response({'error': 'Item does not exist'}, status=status.HTTP_404_NOT_FOUND)
        return fn(self, request, item)
    return get_item

def episode_from_pk(fn):
    """
    Decorator that passes an episode or returns a 404 from pk kwarg.
    """
    def get_item(self, request, pk=None):
        from opal.models import Episode
        try:
            return fn(self, request, Episode.objects.get(pk=pk))
        except Episode.DoesNotExist:
            return Response({'error': 'Episode does not exist'}, status=status.HTTP_404_NOT_FOUND)
    return get_item

class FlowViewSet(viewsets.ViewSet):
    """
    Return the Flow routes for this application.

    For more detail on OPAL Flows, see the documentation
    """
    base_name = 'flow'

    def list(self, request):
        flows = app.flows()
        return Response(flows)

class RecordViewSet(viewsets.ViewSet):
    """
    Return the serialization of all active record types ready to
    initialize on the client side.
    """
    base_name = 'record'

    def list(self, request):
        return Response(schemas.list_records())

class ListSchemaViewSet(viewsets.ViewSet):
    """
    Returns the schema for our active lists
    """
    base_name = 'list-schema'

    def list(self, request):
        return Response(schemas.list_schemas())

class ExtractSchemaViewSet(viewsets.ViewSet):
    """
    Returns the schema to build our extract query builder
    """
    base_name = 'extract-schema'

    def list(self, request):
        return Response(schemas.extract_schema())

# TODO:
# Deprecate this fully
class OptionsViewSet(viewsets.ViewSet):
    """
    Returns various metadata concerning this OPAL instance:
    Lookuplists, micro test defaults, tag hierarchy, macros.
    """
    base_name = 'options'

    def list(self, request):

        data = {}
        subclasses = LookupList.__subclasses__()
        for model in subclasses:
            options = list(model.objects.all().values_list("name", flat=True))
            data[model.__name__.lower()] = options

        model_to_ct = ContentType.objects.get_for_models(
            *subclasses
        )

        for model, ct in model_to_ct.iteritems():
            synonyms = Synonym.objects.filter(content_type=ct).values_list(
                "name", flat=True
            )
            data[model.__name__.lower()].extend(synonyms)

        for name in data:
            data[name].sort()

        data['micro_test_defaults'] = micro_test_defaults

        tag_hierarchy = collections.defaultdict(list)
        tag_visible_in_list = []
        tag_direct_add = []
        tag_display = {}

        if request.user.is_authenticated():
            teams = Team.for_user(request.user)
            for team in teams:
                if team.parent:
                    continue # Will be filled in at the appropriate point!
                tag_display[team.name] = team.title

                if team.visible_in_list:
                    tag_visible_in_list.append(team.name)

                if team.direct_add:
                    tag_direct_add.append(team.name)

                subteams = [st for st in teams if st.parent == team]
                tag_hierarchy[team.name] = [st.name for st in subteams]
                for sub in subteams:
                    tag_display[sub.name] = sub.title

                    if sub.visible_in_list:
                        tag_visible_in_list.append(sub.name)

        data['tag_hierarchy'] = tag_hierarchy
        data['tag_display'] = tag_display
        data['tag_visible_in_list'] = tag_visible_in_list
        data['tag_direct_add'] = tag_direct_add
        data['macros'] = Macro.to_dict()

        return Response(data)

class SubrecordViewSet(viewsets.ViewSet):
    """
    This is the base viewset for our subrecords.
    """

    def _item_to_dict(self, item, user):
        """
        Given an item, serialize either the patient or episode it is a
        subrecord of.
        """
        try:
            return item.episode.to_dict(user)
        except AttributeError:
            return item.patient.to_dict(user)

    def create(self, request):
        """
        * Create a subrecord
        * Ping our integration upstream interface
        * Render the created subrecord back to the requester

        Raise appropriate errors for:

        * Nonexistant episode
        * Unexpected fields being passed in
        """
        from opal.models import Episode, PatientSubrecord

        subrecord = self.model()
        try:
            episode = Episode.objects.get(pk=request.data['episode_id'])
        except Episode.DoesNotExist:
            return Response('Nonexistant episode', status=status.HTTP_400_BAD_REQUEST)
        pre = episode.to_dict(request.user)

        if isinstance(subrecord, PatientSubrecord):
            del request.data['episode_id']
            patient_id = episode.patient.pk
            request.data['patient_id'] = patient_id

        try:
            subrecord.update_from_dict(request.data, request.user)
        except exceptions.APIError:
            return Response({'error': 'Unexpected field name'}, status=status.HTTP_400_BAD_REQUEST)

        episode = Episode.objects.get(pk=episode.pk)
        post = episode.to_dict(request.user)
        glossolalia.change(pre, post)

        return Response(subrecord.to_dict(request.user), status=status.HTTP_201_CREATED)

    @item_from_pk
    def retrieve(self, request, item):
        return Response(item.to_dict(request.user))

    @item_from_pk
    def update(self, request, item):
        pre = self._item_to_dict(item, request.user)
        try:
            item.update_from_dict(request.data, request.user)
        except exceptions.APIError:
            return Response({'error': 'Unexpected field name'},
                            status=status.HTTP_400_BAD_REQUEST)
        except exceptions.ConsistencyError:
            return Response({'error': 'Item has changed'}, status=status.HTTP_409_CONFLICT)
        glossolalia.change(pre, self._item_to_dict(item, request.user))
        return Response(item.to_dict(request.user), status=status.HTTP_202_ACCEPTED)

    @item_from_pk
    def destroy(self, request, item):
        pre = self._item_to_dict(item, request.user)
        item.delete()
        glossolalia.change(pre, self._item_to_dict(item, request.user))
        return Response('deleted', status=status.HTTP_202_ACCEPTED)

class UserProfileViewSet(viewsets.ViewSet):
    """
    Returns the user profile details for the currently logged in user
    """
    base_name = 'userprofile'

    def list(self, request):
        if not request.user.is_authenticated():
            return Response(
                {'error': 'Only valid for authenticated users'},
                status=status.HTTP_401_UNAUTHORIZED)
        profile = request.user.profile
        return Response(profile.to_dict())

class TaggingViewSet(viewsets.ViewSet):
    """
    Associating episodes with teams
    """
    base_name = 'tagging'

    @episode_from_pk
    def retrieve(self, request, episode):
        return Response(episode.tagging_dict(request.user)[0], status=status.HTTP_200_OK)

    @episode_from_pk
    def update(self, request, episode):
        if 'id' in request.data:
            del request.data['id']
        tag_names = [n for n, v in request.data.items() if v]
        pre = episode.to_dict(request.user)
        episode.set_tag_names(tag_names, request.user)
        post = episode.to_dict(request.user)
        glossolalia.transfer(pre, post)
        return Response(episode.tagging_dict(request.user)[0], status=status.HTTP_202_ACCEPTED)

class EpisodeViewSet(viewsets.ViewSet):
    """
    Episodes of care
    """
    base_name = 'episode'

    def list(self, request):
        from opal.models import Episode

        tag    = request.query_params.get('tag', None)
        subtag = request.query_params.get('subtag', None)

        filter_kwargs = {}
        if subtag:
            filter_kwargs['tagging__team__name'] = subtag
        elif tag:
            filter_kwargs['tagging__team__name'] = tag

        if tag == 'mine':
            filter_kwargs['tagging__user'] = request.user

        if not filter_kwargs:
            return Response([e.to_dict(request.user) for e in Episode.objects.all()])

        serialised = Episode.objects.serialised_active(
            request.user, **filter_kwargs)

        return Response(serialised)

    def create(self, request):
        from opal.models import Patient

        hospital_number = request.data.pop('patient_hospital_number', None)
        tagging = request.data.pop('tagging', {})
        if hospital_number:
            patient, created = Patient.objects.get_or_create(
                demographics__hospital_number=hospital_number)
            if created:
                demographics = patient.demographics_set.get()
                demographics.hospital_number = hospital_number
                demographics.save()
        else:
            patient = Patient.objects.create()

        episode = Episode(patient=patient)
        episode.update_from_dict(request.data, request.user)
        episode.set_tag_names([n for n, v in tagging[0].items() if v], request.user)
        serialised = episode.to_dict(request.user)
        return Response(serialised, status=status.HTTP_201_CREATED)

    @episode_from_pk
    def retrieve(self, request, episode):
        return Response(episode.to_dict(request.user))

class PatientViewSet(viewsets.ViewSet):
    base_name = 'patient'

    def list(self, request):
        from opal.models import Patient

        return Response([p.to_dict(request.user) for p in Patient.objects.all()])

    def retrieve(self, request, pk=None):
        from opal.models import Patient

        patient = Patient.objects.get(pk=pk)
        return Response(patient.to_dict(request.user))

router.register('patient', PatientViewSet)
router.register('episode', EpisodeViewSet)
router.register('flow', FlowViewSet)
router.register('record', RecordViewSet)
router.register('list-schema', ListSchemaViewSet)
router.register('extract-schema', ExtractSchemaViewSet)
router.register('options', OptionsViewSet)
router.register('userprofile', UserProfileViewSet)
router.register('tagging', TaggingViewSet)

for subrecord in subrecords():
    sub_name = camelcase_to_underscore(subrecord.__name__)
    class SubViewSet(SubrecordViewSet):
        base_name = sub_name
        model     = subrecord

    router.register(sub_name, SubViewSet)

for plugin in plugins.plugins():
    for api in plugin.apis:
        router.register(*api)

class APIAdmitEpisodeView(View):
    """
    Admit an episode from upstream!
    """
    def post(self, *args, **kwargs):
        data = _get_request_data(self.request)
        resp = {'ok': 'Got your admission just fine - thanks!'}
        return _build_json_response(resp)

class APIReferPatientView(View):
    """
    Refer a particular episode of care to a new team
    """
    def post(self, *args, **kwargs):
        """
        Expects PATIENT, EPISODE, TARGET
        """
        from opal.models import Episode

        data = _get_request_data(self.request)
        episode = Episode.objects.get(pk=data['episode'])
        current_tags = episode.get_tag_names(None)
        if not data['target'] in current_tags:
            current_tags.append(data['target'])
            episode.set_tag_names(current_tags, None)
        resp = {'ok': 'Got your referral just fine - thanks!'}
        return _build_json_response(resp)
<commit_msg>Make Tag display/direct add options api respect parent/children<commit_after>"""
Public facing API views
"""
import collections

from django.conf import settings
from django.views.generic import View
from django.contrib.contenttypes.models import ContentType
from rest_framework import routers, status, viewsets
from rest_framework.response import Response
from opal.models import Episode, Synonym, Team, Macro
from opal.core import application, exceptions, plugins
from opal.core import glossolalia
from opal.core.lookuplists import LookupList
from opal.utils import stringport, camelcase_to_underscore
from opal.core import schemas
from opal.core.subrecords import subrecords
from opal.core.views import _get_request_data, _build_json_response

app = application.get_app()

# TODO This is stupid - we can fully deprecate this please?
try:
    options = stringport(settings.OPAL_OPTIONS_MODULE)
    micro_test_defaults = options.micro_test_defaults
except AttributeError:
    class options:
        model_names = []
    micro_test_defaults = []

class OPALRouter(routers.DefaultRouter):
    def get_default_base_name(self, viewset):
        name = getattr(viewset, 'base_name', None)
        if name is None:
            return routers.DefaultRouter.get_default_base_name(self, viewset)
        return name

router = OPALRouter()

def item_from_pk(fn):
    """
    Decorator that passes an instance or returns a 404 from pk kwarg.
    """
    def get_item(self, request, pk=None):
        try:
            item = self.model.objects.get(pk=pk)
        except self.model.DoesNotExist:
            return Response({'error': 'Item does not exist'}, status=status.HTTP_404_NOT_FOUND)
        return fn(self, request, item)
    return get_item

def episode_from_pk(fn):
    """
    Decorator that passes an episode or returns a 404 from pk kwarg.
    """
    def get_item(self, request, pk=None):
        from opal.models import Episode
        try:
            return fn(self, request, Episode.objects.get(pk=pk))
        except Episode.DoesNotExist:
            return Response({'error': 'Episode does not exist'}, status=status.HTTP_404_NOT_FOUND)
    return get_item

class FlowViewSet(viewsets.ViewSet):
    """
    Return the Flow routes for this application.

    For more detail on OPAL Flows, see the documentation
    """
    base_name = 'flow'

    def list(self, request):
        flows = app.flows()
        return Response(flows)

class RecordViewSet(viewsets.ViewSet):
    """
    Return the serialization of all active record types ready to
    initialize on the client side.
    """
    base_name = 'record'

    def list(self, request):
        return Response(schemas.list_records())

class ListSchemaViewSet(viewsets.ViewSet):
    """
    Returns the schema for our active lists
    """
    base_name = 'list-schema'

    def list(self, request):
        return Response(schemas.list_schemas())

class ExtractSchemaViewSet(viewsets.ViewSet):
    """
    Returns the schema to build our extract query builder
    """
    base_name = 'extract-schema'

    def list(self, request):
        return Response(schemas.extract_schema())

# TODO:
# Deprecate this fully
class OptionsViewSet(viewsets.ViewSet):
    """
    Returns various metadata concerning this OPAL instance:
    Lookuplists, micro test defaults, tag hierarchy, macros.
    """
    base_name = 'options'

    def list(self, request):

        data = {}
        subclasses = LookupList.__subclasses__()
        for model in subclasses:
            options = list(model.objects.all().values_list("name", flat=True))
            data[model.__name__.lower()] = options

        model_to_ct = ContentType.objects.get_for_models(
            *subclasses
        )

        for model, ct in model_to_ct.iteritems():
            synonyms = Synonym.objects.filter(content_type=ct).values_list(
                "name", flat=True
            )
            data[model.__name__.lower()].extend(synonyms)

        for name in data:
            data[name].sort()

        data['micro_test_defaults'] = micro_test_defaults

        tag_hierarchy = collections.defaultdict(list)
        tag_visible_in_list = []
        tag_direct_add = []
        tag_display = {}

        if request.user.is_authenticated():
            teams = Team.for_user(request.user)
            for team in teams:
                if team.parent:
                    continue # Will be filled in at the appropriate point!
                tag_display[team.name] = team.title

                if not team.has_subteams:

                    if team.visible_in_list:
                        tag_visible_in_list.append(team.name)

                    if team.direct_add:
                        tag_direct_add.append(team.name)

                subteams = [st for st in teams if st.parent == team]
                tag_hierarchy[team.name] = [st.name for st in subteams]
                for sub in subteams:
                    tag_display[sub.name] = sub.title

                    if sub.visible_in_list:
                        tag_visible_in_list.append(sub.name)

                    if sub.direct_add:
                        tag_direct_add.append(sub.name)

        data['tag_hierarchy'] = tag_hierarchy
        data['tag_display'] = tag_display
        data['tag_visible_in_list'] = tag_visible_in_list
        data['tag_direct_add'] = tag_direct_add
        data['macros'] = Macro.to_dict()

        return Response(data)

class SubrecordViewSet(viewsets.ViewSet):
    """
    This is the base viewset for our subrecords.
    """

    def _item_to_dict(self, item, user):
        """
        Given an item, serialize either the patient or episode it is a
        subrecord of.
        """
        try:
            return item.episode.to_dict(user)
        except AttributeError:
            return item.patient.to_dict(user)

    def create(self, request):
        """
        * Create a subrecord
        * Ping our integration upstream interface
        * Render the created subrecord back to the requester

        Raise appropriate errors for:

        * Nonexistant episode
        * Unexpected fields being passed in
        """
        from opal.models import Episode, PatientSubrecord

        subrecord = self.model()
        try:
            episode = Episode.objects.get(pk=request.data['episode_id'])
        except Episode.DoesNotExist:
            return Response('Nonexistant episode', status=status.HTTP_400_BAD_REQUEST)
        pre = episode.to_dict(request.user)

        if isinstance(subrecord, PatientSubrecord):
            del request.data['episode_id']
            patient_id = episode.patient.pk
            request.data['patient_id'] = patient_id

        try:
            subrecord.update_from_dict(request.data, request.user)
        except exceptions.APIError:
            return Response({'error': 'Unexpected field name'}, status=status.HTTP_400_BAD_REQUEST)

        episode = Episode.objects.get(pk=episode.pk)
        post = episode.to_dict(request.user)
        glossolalia.change(pre, post)

        return Response(subrecord.to_dict(request.user), status=status.HTTP_201_CREATED)

    @item_from_pk
    def retrieve(self, request, item):
        return Response(item.to_dict(request.user))

    @item_from_pk
    def update(self, request, item):
        pre = self._item_to_dict(item, request.user)
        try:
            item.update_from_dict(request.data, request.user)
        except exceptions.APIError:
            return Response({'error': 'Unexpected field name'},
                            status=status.HTTP_400_BAD_REQUEST)
        except exceptions.ConsistencyError:
            return Response({'error': 'Item has changed'}, status=status.HTTP_409_CONFLICT)
        glossolalia.change(pre, self._item_to_dict(item, request.user))
        return Response(item.to_dict(request.user), status=status.HTTP_202_ACCEPTED)

    @item_from_pk
    def destroy(self, request, item):
        pre = self._item_to_dict(item, request.user)
        item.delete()
        glossolalia.change(pre, self._item_to_dict(item, request.user))
        return Response('deleted', status=status.HTTP_202_ACCEPTED)

class UserProfileViewSet(viewsets.ViewSet):
    """
    Returns the user profile details for the currently logged in user
    """
    base_name = 'userprofile'

    def list(self, request):
        if not request.user.is_authenticated():
            return Response(
                {'error': 'Only valid for authenticated users'},
                status=status.HTTP_401_UNAUTHORIZED)
        profile = request.user.profile
        return Response(profile.to_dict())

class TaggingViewSet(viewsets.ViewSet):
    """
    Associating episodes with teams
    """
    base_name = 'tagging'

    @episode_from_pk
    def retrieve(self, request, episode):
        return Response(episode.tagging_dict(request.user)[0], status=status.HTTP_200_OK)

    @episode_from_pk
    def update(self, request, episode):
        if 'id' in request.data:
            del request.data['id']
        tag_names = [n for n, v in request.data.items() if v]
        pre = episode.to_dict(request.user)
        episode.set_tag_names(tag_names, request.user)
        post = episode.to_dict(request.user)
        glossolalia.transfer(pre, post)
        return Response(episode.tagging_dict(request.user)[0], status=status.HTTP_202_ACCEPTED)

class EpisodeViewSet(viewsets.ViewSet):
    """
    Episodes of care
    """
    base_name = 'episode'

    def list(self, request):
        from opal.models import Episode

        tag    = request.query_params.get('tag', None)
        subtag = request.query_params.get('subtag', None)

        filter_kwargs = {}
        if subtag:
            filter_kwargs['tagging__team__name'] = subtag
        elif tag:
            filter_kwargs['tagging__team__name'] = tag

        if tag == 'mine':
            filter_kwargs['tagging__user'] = request.user

        if not filter_kwargs:
            return Response([e.to_dict(request.user) for e in Episode.objects.all()])

        serialised = Episode.objects.serialised_active(
            request.user, **filter_kwargs)

        return Response(serialised)

    def create(self, request):
        from opal.models import Patient

        hospital_number = request.data.pop('patient_hospital_number', None)
        tagging = request.data.pop('tagging', {})
        if hospital_number:
            patient, created = Patient.objects.get_or_create(
                demographics__hospital_number=hospital_number)
            if created:
                demographics = patient.demographics_set.get()
                demographics.hospital_number = hospital_number
                demographics.save()
        else:
            patient = Patient.objects.create()

        episode = Episode(patient=patient)
        episode.update_from_dict(request.data, request.user)
        episode.set_tag_names([n for n, v in tagging[0].items() if v], request.user)
        serialised = episode.to_dict(request.user)
        return Response(serialised, status=status.HTTP_201_CREATED)

    @episode_from_pk
    def retrieve(self, request, episode):
        return Response(episode.to_dict(request.user))

class PatientViewSet(viewsets.ViewSet):
    base_name = 'patient'

    def list(self, request):
        from opal.models import Patient

        return Response([p.to_dict(request.user) for p in Patient.objects.all()])

    def retrieve(self, request, pk=None):
        from opal.models import Patient

        patient = Patient.objects.get(pk=pk)
        return Response(patient.to_dict(request.user))

router.register('patient', PatientViewSet)
router.register('episode', EpisodeViewSet)
router.register('flow', FlowViewSet)
router.register('record', RecordViewSet)
router.register('list-schema', ListSchemaViewSet)
router.register('extract-schema', ExtractSchemaViewSet)
router.register('options', OptionsViewSet)
router.register('userprofile', UserProfileViewSet)
router.register('tagging', TaggingViewSet)

for subrecord in subrecords():
    sub_name = camelcase_to_underscore(subrecord.__name__)
    class SubViewSet(SubrecordViewSet):
        base_name = sub_name
        model     = subrecord

    router.register(sub_name, SubViewSet)

for plugin in plugins.plugins():
    for api in plugin.apis:
        router.register(*api)

class APIAdmitEpisodeView(View):
    """
    Admit an episode from upstream!
    """
    def post(self, *args, **kwargs):
        data = _get_request_data(self.request)
        resp = {'ok': 'Got your admission just fine - thanks!'}
        return _build_json_response(resp)

class APIReferPatientView(View):
    """
    Refer a particular episode of care to a new team
    """
    def post(self, *args, **kwargs):
        """
        Expects PATIENT, EPISODE, TARGET
        """
        from opal.models import Episode

        data = _get_request_data(self.request)
        episode = Episode.objects.get(pk=data['episode'])
        current_tags = episode.get_tag_names(None)
        if not data['target'] in current_tags:
            current_tags.append(data['target'])
            episode.set_tag_names(current_tags, None)
        resp = {'ok': 'Got your referral just fine - thanks!'}
        return _build_json_response(resp)
<|endoftext|>


<commit_before># setup script for py2exe to create the executeable of all the python tools
# this setup uses one shared zip file for the library to save space
#
# $Id: setup-combined-tools-py2exe.py,v 1.5 2008/06/05 12:49:59 cliechti Exp $

from distutils.core import setup
import glob, sys, py2exe, os, zipfile

sys.path.insert(0, os.path.abspath('..'))
os.chdir('..')
print "changed directory to %s" % os.path.abspath(os.curdir)

sys.argv.append("py2exe")

setup(
    name = 'http://mspgcc.sourceforge.net',
    author="Chris Liechti",
    author_email="cliechti@gmx.net",
    url="http://launchpad.net/python-msp430-tools/",

    version = '0.6',
    options = { "py2exe":
        {
            'dist_dir': 'bin',
            'excludes': ['javax.comm', 'macpath', 'TERMIOS', 'FCNTL',
                    'os2emxpath', '_parjtag',
                    'IronPythonConsole', 'System', 'System.IO.Ports', 'System.Windows.Forms.Clipboard', 'clr',
                    'modes.editingmodes', 'startup', 'Carbon', 'Carbon.Files', 'wx'],
            'packages': ['msp430', 'msp430.asm', 'msp430.memory', 'msp430.shell',
                        'msp430.gdb', 'msp430.jtag', 'msp430.bsl', 'msp430.bsl5'],
            'includes': ['pywinusb', 'EasyDialogs'],
            'dll_excludes': [
                    'HIL.dll', 'MSP430.dll', 'MSP430mspgcc.dll',
                    'API-MS-Win-Core-LocalRegistry-L1-1-0.dll',
                    'API-MS-Win-Core-ProcessThreads-L1-1-0.dll',
                    'API-MS-Win-Security-Base-L1-1-0.dll',
                    'POWRPROF.dll',
                    ],
            'optimize': 2,
        }
    },
    console = [
        "scripts/msp430-bsl.py",
        "scripts/msp430-jtag.py",
        "scripts/msp430-tool.py",
    ],
    windows = [
        {
            'script': "scripts/msp430-downloader.py",
            'icon_resources': [(0x0001, 'win32/downloader.ico')]
        },
    ],
    zipfile = "lib/shared.zip",
)

# py2exe does not look at the package_data option of distutils. add files manually
package_data = [
        'msp430/asm/definitions/msp430-mcu-list.txt',
        'msp430/bsl/BL_150S_14x.txt',
        'msp430/bsl/BL_150S_44x.txt',
        'msp430/bsl/BS_150S_14x.txt',
        'msp430/bsl/patch.txt',
        'msp430/bsl5/RAM_BSL.00.05.04.34.txt',
        ]

print "appending package data..."
archive = zipfile.PyZipFile("bin/lib/shared.zip", 'a')
for path in package_data:
    archive.write(
            path,
            path,
            compress_type=zipfile.ZIP_DEFLATED
            )
<commit_msg>improve settings used for py2exe<commit_after># setup script for py2exe to create the executeable of all the python tools
# this setup uses one shared zip file for the library to save space
#
# $Id: setup-combined-tools-py2exe.py,v 1.5 2008/06/05 12:49:59 cliechti Exp $

from distutils.core import setup
import glob, sys, py2exe, os, zipfile

sys.path.insert(0, os.path.abspath('..'))
os.chdir('..')
print "changed directory to %s" % os.path.abspath(os.curdir)

sys.argv.append("py2exe")
#~ sys.argv.append("--xref")

setup(
    name = 'http://mspgcc.sourceforge.net',
    author="Chris Liechti",
    author_email="cliechti@gmx.net",
    url="http://launchpad.net/python-msp430-tools/",

    version = '0.6',
    options = { "py2exe":
        {
            'dist_dir': 'bin',
            'excludes': [
                    'os2emxpath', '_parjtag','pdb', 'pyreadline', 'plistlib', 'doctest',
                    #~ 'IronPythonConsole', 'System', 'System.Windows.Forms.Clipboard', 'clr',
                    'modes.editingmodes', 'startup', 'Carbon', 'Carbon.Files', 'wx',
                    # pyserial, other platforms
                    'serial.serialjava', 'serial.serialcli', 'serial.serialposix',
                    'serial.tools.list_ports_posix',
                    ],
            'packages': ['msp430', 'msp430.asm', 'msp430.memory', 'msp430.shell',
                        'msp430.gdb', 'msp430.jtag', 'msp430.bsl', 'msp430.bsl5'],
            'includes': [
                    'pywinusb', 'EasyDialogs',
                    'serial.urlhandler.protocol_hwgrep', 'serial.urlhandler.protocol_rfc2217',
                    'serial.urlhandler.protocol_socket', 'serial.urlhandler.protocol_loop',
                    ],
            'dll_excludes': [
                    # misc windows stuff
                    'OLEAUT32.dll', 'USER32.dll', 'SHELL32.dll', 'KERNEL32.dll',
                    'ADVAPI32.dll', 'WS2_32.dll', 'VERSION.dll', 'ole32.dll',
                    'RPCRT4.dll', 'COMDLG32.dll', 'WSOCK32.dll', 'COMCTL32.dll',
                    'WINSPOOL.DRV', 'GDI32.dll', 'gdiplus.dll', 'WINMM.dll',
                    # our own
                    'HIL.dll', 'MSP430.dll', 'MSP430mspgcc.dll',
                    # win7
                    'API-MS-Win-Core-LocalRegistry-L1-1-0.dll',
                    'API-MS-Win-Core-ProcessThreads-L1-1-0.dll',
                    'API-MS-Win-Security-Base-L1-1-0.dll',
                    'POWRPROF.dll',
                    # other files
                    'MSVCP90.dll',
                    'MSVCRTR90.dll',
                    ],
            'optimize': 2,
        }
    },
    console = [
        "scripts/msp430-bsl.py",
        "scripts/msp430-jtag.py",
        "scripts/msp430-tool.py",
    ],
    windows = [
        {
            'script': "scripts/msp430-downloader.py",
            'icon_resources': [(0x0001, 'win32/downloader.ico')]
        },
    ],
    zipfile = "lib/shared.zip",
)

# py2exe does not look at the package_data option of distutils. add files manually
package_data = [
        'msp430/asm/definitions/msp430-mcu-list.txt',
        'msp430/bsl/BL_150S_14x.txt',
        'msp430/bsl/BL_150S_44x.txt',
        'msp430/bsl/BS_150S_14x.txt',
        'msp430/bsl/patch.txt',
        'msp430/bsl5/RAM_BSL.00.05.04.34.txt',
        ]

print "appending package data..."
archive = zipfile.PyZipFile("bin/lib/shared.zip", 'a')
for path in package_data:
    archive.write(
            path,
            path,
            compress_type=zipfile.ZIP_DEFLATED
            )
<|endoftext|>


<commit_before>import os.path
release = False
__version__ = '0.1'

_repository_path = os.path.split(__file__)[0]
_svn_file_path = os.path.join(_repository_path, '__svn_version__.py')

def get_svn_revision():
    '''
    Gets the "global" SVN revision number for the repository, using the
    path to this file.
    '''
    from subprocess import Popen, PIPE
    rev = ''
    try:
        proc = Popen(['svnversion', '-n', _repository_path], stdout=PIPE)
        text = proc.stdout.readline()
        if text != 'exported':
            if ':' in text:
                text = text[text.find(':')+1:]
            rev = '.dev' + text
    except OSError:
        # SVN not installed, don't worry about finding the revision
        pass
    return rev

def write_svn_version():
    'Write the SVN revision to a file.'
    svnfile = open(_svn_file_path, 'w')
    svnfile.write('rev = "%s"\n' % get_svn_revision())
    svnfile.close()

def get_version():
    '''
    Get the version of the package, including the SVN revision if this
    is an actual release.
    '''
    version = __version__
    if not release:
        try:
            import __svn_version__
            version += __svn_version__.rev
        except ImportError:
            version += get_svn_revision()
            pass
    return version
<commit_msg>Convert this file to one that uses GIT instead of SVN.  This is a modified version of the Numpy versioning routine.<commit_after>import os.path
import subprocess
release = False
__version__ = '0.1'

_repository_path = os.path.split(__file__)[0]
_git_file_path = os.path.join(_repository_path, '__git_version__.py')

def _minimal_ext_cmd(cmd):
    # construct minimal environment
    env = {}
    for k in ['SYSTEMROOT', 'PATH']:
        v = os.environ.get(k)
        if v is not None:
            env[k] = v
    # LANGUAGE is used on win32
    env['LANGUAGE'] = 'C'
    env['LANG'] = 'C'
    env['LC_ALL'] = 'C'
    out = subprocess.Popen(cmd, stdout=subprocess.PIPE, env=env).communicate()[0]
    return out

def get_git_hash():
    '''
    Gets the last GIT commit hash and date for the repository, using the
    path to this file.
    '''
    try:
        out = _minimal_ext_cmd(['git', 'rev-parse', 'HEAD'])
        GIT_REVISION = out.strip().decode('ascii')
    except:
        GIT_REVISION = None

    return GIT_REVISION

def get_git_revision():
    hash = get_git_hash()
    if hash :
        rev = '.dev.' + hash[:7]
        try:
            cmd = ['git', 'show', '%s' % (hash), '--date=short',
                   '--format="(%ad)"']
            date = _minimal_ext_cmd(cmd).split('"')[1]
            rev += date
        except:
            pass
    else:
        rev = ".dev.Unknown"

    return rev

def write_git_version():
    'Write the GIT revision to a file.'
    rev = get_git_revision()
    if rev == ".dev.Unknown":
        if os.path.isfile(_git_file_path):
            return
    gitfile = open(_git_file_path, 'w')
    gitfile.write('rev = "%s"\n' % rev)
    gitfile.close()

def get_version():
    '''
    Get the version of the package, including the GIT revision if this
    is an actual release.
    '''
    version = __version__
    if not release:
        try:
            import __git_version__
            version += __git_version__.rev
        except ImportError:
            version += get_git_revision()

    return version
<|endoftext|>


<commit_before>#!/usr/bin/env python

import sys
import os
import shutil
import re
import json
import time
import rpm
import subprocess
import stat
import tarfile
import time
import hashlib
import copy

import anchore.anchore_utils

analyzer_name = "file_checksums"

try:
    config = anchore.anchore_utils.init_analyzer_cmdline(sys.argv, analyzer_name)
except Exception as err:
    print str(err)
    sys.exit(1)

imgname = config['imgid']
imgid = config['imgid_full']
outputdir = config['dirs']['outputdir']
unpackdir = config['dirs']['unpackdir']

meta = anchore.anchore_utils.get_distro_from_path('/'.join([unpackdir, "rootfs"]))
distrodict = anchore.anchore_utils.get_distro_flavor(meta['DISTRO'], meta['DISTROVERS'], likedistro=meta['LIKEDISTRO'])
flavor = distrodict['flavor']

record = {'digest': None, 'md5': None, 'mode': None, 'group': None, 'user': None, 'size': None, 'package': None}
result = {}
resultlist = {}

if flavor == "RHEL":
    try:
        rpmdbdir = anchore.anchore_utils.rpm_prepdb(unpackdir)
    except:
        rpmdbdir = os.path.join(unpackdir, 'rootfs', 'var', 'lib', 'rpm')

    cmd = ['rpm', '--dbpath='+rpmdbdir, '-qa', '--queryformat', '"[%{FILENAMES}|ANCHORETOK|%{FILEDIGESTS}|ANCHORETOK|%{FILEMD5S}|ANCHORETOK|%{FILEMODES}|ANCHORETOK|%{FILEGROUPNAME}|ANCHORETOK|%{FILEUSERNAME}|ANCHORETOK|%{FILESIZES}|ANCHORETOK|%{=NAME}\n]"']
    try:
        pipes = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        o, e = pipes.communicate()
        exitcode = pipes.returncode
        soutput = o
        serror = e

        if exitcode == 0:
            for l in soutput.splitlines():
                l = l.strip()
                try:
                    (fname, fdigest, fmd5, fmode, fgroup, fuser, fsize, fpackage)= l.split("|ANCHORETOK|")
                    result[fname] = copy.deepcopy(record)
                    result[fname].update({'digest': fdigest, 'md5': fmd5, 'mode': fmode, 'group': fgroup, 'user': fuser, 'size': fsize, 'package': fpackage})
                except Exception as err:
                    print "WARN: unparsable output line - exception: " + str(err)

    except Exception as err:
        print "WARN: distro package metadata gathering failed - exception: " + str(err)

elif flavor == 'DEB':
    pass
else:
    # do nothing, distro not supported
    pass

if result:
    for f in result.keys():
        try:
            resultlist[f] = json.dumps(result[f], sort_keys=True)
        except:
            resultlist[f] = ""

if resultlist:
    ofile = os.path.join(outputdir, 'distro.pkgfilemeta')
    anchore.anchore_utils.write_kvfile_fromdict(ofile, resultlist)

# now run the distro package verifier, if present

verify_result = {}
verifylist = {}

try:
    verify_result, voutput, verror, vexitcode = anchore.anchore_utils.verify_file_packages(unpackdir, flavor)
except Exception as err:
    print "WARN: could not run distro package verifier - exception: " + str(err)

if verify_result:
    for f in verify_result.keys():
        try:
            verifylist[f] = json.dumps(verify_result[f])
        except:
            verifylist[f] = ""

if verifylist:
    ofile = os.path.join(outputdir, 'distro.verifyresult')
    anchore.anchore_utils.write_kvfile_fromdict(ofile, verifylist)

<commit_msg>prototype the package verify info analyzer<commit_after>#!/usr/bin/env python

import sys
import os
import shutil
import re
import json
import time
import rpm
import subprocess
import stat
import tarfile
import time
import hashlib
import copy

import anchore.anchore_utils

def deb_get_file_package_metadata(unpackdir, record_template):

    result = {}
    metafiles = {}
    metapath = os.path.join(unpackdir, "rootfs", "var", "lib", "dpkg", "info")

    try:
        if os.path.exists(metapath):
            for f in os.listdir(metapath):
                patt = re.match("(.*)\.md5sums", f)
                if patt:
                    pkgraw = patt.group(1)
                    patt = re.match("(.*):.*", pkgraw)
                    if patt:
                        pkg = patt.group(1)
                    else:
                        pkg = pkgraw

                    metafiles[pkg] = os.path.join(metapath, f)
        else:
            raise Exception("no dpkg info path found in image: " + str(metapath))

        for pkg in metafiles.keys():
            dinfo = None
            with open(metafiles[pkg], 'r') as FH:
                dinfo = FH.read()

            if dinfo:
                for line in dinfo.splitlines():
                    line.strip()
                    (csum, fname) = line.split()
                    fname = '/' + fname
                    fname = re.sub("\/\/", "\/", fname)
                    if fname in result:
                        print "WARNING: meta file name collision: " + str(fname)
                    result[fname] = copy.deepcopy(record_template)
                    result[fname].update({"package": pkg or None, "md5": csum or None, "digest": csum or None, "digestalgo": "md5"})

    except Exception as err:
        raise Exception("WARN: could not find/parse dpkg info metadata files - exception: " + str(err))

    return(result)

def rpm_get_file_package_metadata(unpackdir, record_template):
    result = {}

    try:
        rpmdbdir = anchore.anchore_utils.rpm_prepdb(unpackdir)
    except:
        rpmdbdir = os.path.join(unpackdir, 'rootfs', 'var', 'lib', 'rpm')

    cmdstr = 'rpm --dbpath='+rpmdbdir+' -qa --queryformat "[%{FILENAMES}|ANCHORETOK|%{FILEDIGESTS}|ANCHORETOK|%{FILEMD5S}|ANCHORETOK|%{FILEMODES}|ANCHORETOK|%{FILEGROUPNAME}|ANCHORETOK|%{FILEUSERNAME}|ANCHORETOK|%{FILESIZES}|ANCHORETOK|%{=NAME}|ANCHORETOK|%{FILEFLAGS:fflags}\\n]"'
    cmd = cmdstr.split()
    print cmdstr
    try:
        pipes = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        o, e = pipes.communicate()
        exitcode = pipes.returncode
        soutput = o
        serror = e

        if exitcode == 0:
            for l in soutput.splitlines():
                l = l.strip()
                try:
                    (fname, fdigest, fmd5, fmode, fgroup, fuser, fsize, fpackage, fflags)= l.split("|ANCHORETOK|")

                    cfile = False
                    if 'c' in str(fflags):
                        cfile = True

                    result[fname] = copy.deepcopy(record_template)
                    result[fname].update({'digest': fdigest or None, 'digestalgo': 'sha256', 'md5': fmd5 or None, 'mode': fmode or None, 'group': fgroup or None, 'user': fuser or None, 'size': fsize or None, 'package': fpackage or None, 'conffile': cfile})
                except Exception as err:
                    print "WARN: unparsable output line - exception: " + str(err)
        else:
            raise Exception("rpm file metadata command failed with exitcode ("+str(exitcode)+") - stdoutput: " + str(soutput) + " : stderr: " + str(serror))

    except Exception as err:
        raise Exception("WARN: distro package metadata gathering failed - exception: " + str(err))

    return(result)

analyzer_name = "file_package_verify"

try:
    config = anchore.anchore_utils.init_analyzer_cmdline(sys.argv, analyzer_name)
except Exception as err:
    print str(err)
    sys.exit(1)

imgname = config['imgid']
imgid = config['imgid_full']
outputdir = config['dirs']['outputdir']
unpackdir = config['dirs']['unpackdir']

meta = anchore.anchore_utils.get_distro_from_path('/'.join([unpackdir, "rootfs"]))
distrodict = anchore.anchore_utils.get_distro_flavor(meta['DISTRO'], meta['DISTROVERS'], likedistro=meta['LIKEDISTRO'])
flavor = distrodict['flavor']

# gather file metadata from installed packages

record = {'digest': None, 'digestalgo': None, 'md5': None, 'mode': None, 'group': None, 'user': None, 'size': None, 'package': None, 'conffile': False}
result = {}
resultlist = {}

if flavor == "RHEL":
    try:
        result = rpm_get_file_package_metadata(unpackdir, record)
    except Exception as err:
        print "WARN: " + str(err)

elif flavor == 'DEB':
    try:
        result = deb_get_file_package_metadata(unpackdir, record)
    except Exception as err:
        print "WARN: " + str(err)
else:
    # do nothing, flavor not supported for getting metadata about files from pkg manager
    pass

if result:
    for f in result.keys():
        try:
            resultlist[f] = json.dumps(result[f], sort_keys=True)
        except Exception as err:
            print "WARN: " + str(err)
            resultlist[f] = ""

if resultlist:
    ofile = os.path.join(outputdir, 'distro.pkgfilemeta')
    anchore.anchore_utils.write_kvfile_fromdict(ofile, resultlist)

# now run the distro package verifier, if present

verify_result = {}
verifylist = {}
try:
    verify_result, voutput, verror, vexitcode = anchore.anchore_utils.verify_file_packages(unpackdir, flavor)
except Exception as err:
    print "WARN: could not run distro package verifier - exception: " + str(err)

if verify_result:
    for f in verify_result.keys():
        try:
            verifylist[f] = json.dumps(verify_result[f])
        except:
            verifylist[f] = ""

if verifylist:
    ofile = os.path.join(outputdir, 'distro.verifyresult')
    anchore.anchore_utils.write_kvfile_fromdict(ofile, verifylist)

sys.exit(0)
<|endoftext|>


<commit_before>#!/usr/bin/env python

"""
@file coi-services/mi.idk/config.py
@author Bill French
@brief Configuration object for the IDK.  It reads values from
and IDK yaml file and can write the user configuration to a file.

ConfigManager is the main configuration object and Config provides
a mechanism to access the CM singleton.

Usage:

from mi.core.log import log

Config().rebase()
repo = Config().working_repo()

"""

__author__ = 'Bill French'
__license__ = 'Apache 2.0'

import os
import sys
import yaml
from git import Repo

import mi.core.common

from mi.idk.exceptions import IDKConfigMissing
from mi.idk.exceptions import IDKWrongRunningDirectory
from mi.idk.exceptions import WorkingRepoNotSet

from mi.core.log import log
from mi.idk.common import Singleton
import pyon.util.config

####
#    Config defaults.  These are hard coded because they shouldn't
#    be overrided
####
PATH = os.path.join(os.path.expanduser("~"), ".idk")
CONFIG_FILENAME = "idk.yml"

DEFAULT_CONFIG = "extern/ion-definitions/res/config/idk.yml"
IDK_YAML_GROUP = "idk"
YAML_CONFIG_WORKING_REPO = "working_repo"
YAML_CONFIG_TEMPLATE_DIR = "template_dir"

MI_REPO_NAME = "marine-integrations"

class ConfigManager(Singleton):
    """
    Config Manager.
    Creates a config file if it doesn't already exist.
    """
    def init(self, config_dir = PATH):
        self.yaml = dict()
        self.yaml[IDK_YAML_GROUP] = dict()

        log.debug("config dir: %s" % config_dir)
        self.config_dir = config_dir

        if not os.path.isdir(config_dir):
            try:
                os.mkdir(config_dir) # create dir if it doesn't exist
            except:
                raise IOError("Couldn't create \"" + config_dir + "\" folder. Check" \
                              " permissions")

        ## Initialize the config file if one doesn't exist
        cfgpath = os.path.join(config_dir, CONFIG_FILENAME)
        log.debug("config file: %s" % cfgpath)
        if not os.path.exists(cfgpath):
            log.debug("User IDK config doesn't exist: " + cfgpath)
            self.rebase()

        ## Read the user config file once to get the working repo dir, then again with the default and user config
        self.read_config([cfgpath])

        if not self.get("working_repo"):
            raise WorkingRepoNotSet()

        self.read_config([ os.path.join(self.get("working_repo"), DEFAULT_CONFIG),
                           cfgpath ])
        pass

    def read_config(self, config_list):
        config = mi.core.common.Config(config_list)
        self.yaml = config.as_dict()
        pass

    def rebase(self):
        """
        @brief determine if we are in the MI working git repo.  If so set the
               local yaml file with the path.  Note, this MUST be run from the
               root of the local git working directory.
        """
        log.debug("Rebase IDK working repository")
        idk_repo = os.getcwd();

        # We assume we are in the root of the local repository directory because
        # DEFAULT_CONFIG is a relative path from there
        log.debug("Check for GIT information in: " + os.curdir);
        repo = Repo(idk_repo)

        if repo.bare:
            raise IDKWrongRunningDirectory(msg="Please run this process from the root your local MI git repository")

        ### This would be nice to ultimately pull from the repo object, but the version of gitpython
        ### installed doesn't support remotes.
        origin = idk_repo
        #origin = repo.remotes.origin.url
        #log.debug("Current repo origin: " + origin)

        log.debug( "Does '%s' contain '%s'" % (origin, MI_REPO_NAME))
        if origin.find(MI_REPO_NAME) < 0:
            raise IDKWrongRunningDirectory(msg="Please run this process from the root your local MI git repository")

        self.set(YAML_CONFIG_WORKING_REPO, idk_repo)

    def write(self):
        """
        @brief write the current yaml config to the user
        """
        cfgpath = os.path.join(self.config_dir, CONFIG_FILENAME)
        ofile = open(cfgpath, 'w')
        if ofile:
            log.debug( "Write config: %s" % cfgpath )
            cfg = yaml.dump(self.yaml, default_flow_style=False)
            log.debug( "Config:\n%s" % cfg)
            ofile.write(cfg)
            ofile.close()

    def set(self, name, path):
        """
        @brief change a value in the yaml file
        """
        self.yaml[IDK_YAML_GROUP][name] = path
        self.write()

    def get(self, config_name):
        """
        @brief get a named parameter from the yaml file
        @retval value from the yaml file
        """
        return self.yaml[IDK_YAML_GROUP].get(config_name)

class Config(object):
    """
    Config object.
    """
    def __init__(self, config_dir = PATH):
        log.debug("cfg: %s" % config_dir)
        self.cm = ConfigManager(config_dir) # ConfigManager instance
        if not config_dir == self.cm.config_dir:
            self.cm.init(config_dir)

    def get(self, name):
        """
        @brief get a named parameter from the yaml file
        @retval value from the yaml file
        """
        return self.cm.get(name)

    ###
    #    IDK Configuration Compositions
    ###
    def base_dir(self):
        """
        @brief base directory for the new driver
        @retval dir name
        """
        return self.cm.get(YAML_CONFIG_WORKING_REPO)

    def idk_config_dir(self):
        """
        @brief base directory for the new driver
        @retval dir name
        """
        return self.cm.config_dir

    def template_dir(self):
        """
        @brief directory where code templates are stored
        @retval template dir name
        """
        return self.cm.get(YAML_CONFIG_TEMPLATE_DIR)

    def rebase(self):
        """
        @brief reset the working repository directory
        """
        self.cm.rebase()

<commit_msg>fixed bug when idk.yml local config exists, but working_repo was not set an execption was thrown.<commit_after>#!/usr/bin/env python

"""
@file coi-services/mi.idk/config.py
@author Bill French
@brief Configuration object for the IDK.  It reads values from
and IDK yaml file and can write the user configuration to a file.

ConfigManager is the main configuration object and Config provides
a mechanism to access the CM singleton.

Usage:

from mi.core.log import log

Config().rebase()
repo = Config().working_repo()

"""

__author__ = 'Bill French'
__license__ = 'Apache 2.0'

import os
import sys
import yaml
from git import Repo

import mi.core.common

from mi.idk.exceptions import IDKConfigMissing
from mi.idk.exceptions import IDKWrongRunningDirectory
from mi.idk.exceptions import WorkingRepoNotSet

from mi.core.log import log
from mi.idk.common import Singleton
import pyon.util.config

####
#    Config defaults.  These are hard coded because they shouldn't
#    be overrided
####
PATH = os.path.join(os.path.expanduser("~"), ".idk")
CONFIG_FILENAME = "idk.yml"

DEFAULT_CONFIG = "extern/ion-definitions/res/config/idk.yml"
IDK_YAML_GROUP = "idk"
YAML_CONFIG_WORKING_REPO = "working_repo"
YAML_CONFIG_TEMPLATE_DIR = "template_dir"

MI_REPO_NAME = "marine-integrations"

class ConfigManager(Singleton):
    """
    Config Manager.
    Creates a config file if it doesn't already exist.
    """
    def init(self, config_dir = PATH):
        self.yaml = dict()
        self.yaml[IDK_YAML_GROUP] = dict()

        log.debug("config dir: %s" % config_dir)
        self.config_dir = config_dir

        if not os.path.isdir(config_dir):
            try:
                os.mkdir(config_dir) # create dir if it doesn't exist
            except:
                raise IOError("Couldn't create \"" + config_dir + "\" folder. Check" \
                              " permissions")

        ## Initialize the config file if one doesn't exist
        cfgpath = os.path.join(config_dir, CONFIG_FILENAME)
        log.debug("config file: %s" % cfgpath)
        if not self.get("working_repo"):
            log.debug("User IDK config doesn't exist: " + cfgpath)
            self.rebase()

        ## Read the user config file once to get the working repo dir, then again with the default and user config
        self.read_config([cfgpath])

        if not self.get("working_repo"):
            raise WorkingRepoNotSet()

        self.read_config([ os.path.join(self.get("working_repo"), DEFAULT_CONFIG),
                           cfgpath ])
        pass

    def read_config(self, config_list):
        config = mi.core.common.Config(config_list)
        self.yaml = config.as_dict()
        pass

    def rebase(self):
        """
        @brief determine if we are in the MI working git repo.  If so set the
               local yaml file with the path.  Note, this MUST be run from the
               root of the local git working directory.
        """
        log.debug("Rebase IDK working repository")
        idk_repo = os.getcwd();

        # We assume we are in the root of the local repository directory because
        # DEFAULT_CONFIG is a relative path from there
        log.debug("Check for GIT information in: " + os.curdir);
        repo = Repo(idk_repo)

        if repo.bare:
            raise IDKWrongRunningDirectory(msg="Please run this process from the root your local MI git repository")

        ### This would be nice to ultimately pull from the repo object, but the version of gitpython
        ### installed doesn't support remotes.
        origin = idk_repo
        #origin = repo.remotes.origin.url
        #log.debug("Current repo origin: " + origin)

        log.debug( "Does '%s' contain '%s'" % (origin, MI_REPO_NAME))
        if origin.find(MI_REPO_NAME) < 0:
            raise IDKWrongRunningDirectory(msg="Please run this process from the root your local MI git repository")

        self.set(YAML_CONFIG_WORKING_REPO, idk_repo)

    def write(self):
        """
        @brief write the current yaml config to the user
        """
        cfgpath = os.path.join(self.config_dir, CONFIG_FILENAME)
        ofile = open(cfgpath, 'w')
        if ofile:
            log.debug( "Write config: %s" % cfgpath )
            cfg = yaml.dump(self.yaml, default_flow_style=False)
            log.debug( "Config:\n%s" % cfg)
            ofile.write(cfg)
            ofile.close()

    def set(self, name, path):
        """
        @brief change a value in the yaml file
        """
        self.yaml[IDK_YAML_GROUP][name] = path
        self.write()

    def get(self, config_name):
        """
        @brief get a named parameter from the yaml file
        @retval value from the yaml file
        """
        if self.yaml and self.yaml.get(IDK_YAML_GROUP):
            return self.yaml[IDK_YAML_GROUP].get(config_name)
        else:
            return None

class Config(object):
    """
    Config object.
    """
    def __init__(self, config_dir = PATH):
        log.debug("cfg: %s" % config_dir)
        self.cm = ConfigManager(config_dir) # ConfigManager instance
        if not config_dir == self.cm.config_dir:
            self.cm.init(config_dir)

    def get(self, name):
        """
        @brief get a named parameter from the yaml file
        @retval value from the yaml file
        """
        return self.cm.get(name)

    ###
    #    IDK Configuration Compositions
    ###
    def base_dir(self):
        """
        @brief base directory for the new driver
        @retval dir name
        """
        return self.cm.get(YAML_CONFIG_WORKING_REPO)

    def idk_config_dir(self):
        """
        @brief base directory for the new driver
        @retval dir name
        """
        return self.cm.config_dir

    def template_dir(self):
        """
        @brief directory where code templates are stored
        @retval template dir name
        """
        return self.cm.get(YAML_CONFIG_TEMPLATE_DIR)

    def rebase(self):
        """
        @brief reset the working repository directory
        """
        self.cm.rebase()

<|endoftext|>


<commit_before>#!/usr/bin/env python3
# hashbang but it's meant to be run on windows ._.

# Python Helper to interact with the NanoLab Microcontroller
# Microcontroller Model: Micos 1860SMC Basic
# Made 2019, Sun Yudong
# sunyudong [at] outlook [dot] sg
# github.com/sunjerry019/photonLauncher

# IMPT: THIS IS A HELPER FILE
# RUNNING IT DIRECTLY YIELDS INTERACTIVE TERMINAL

# Errors to be caught: RuntimeError, NotImplementedError, AssertionError

# You can use python-sphinx to generate docs for this, but I mildly no time
# You can just get a brief idea by opening this in a modern text-editor and minimizing all the functions
# by clicking the small triangle/arrow at the side

import serial
import sys, os
import time
import signal
import json
import warnings

import shutter

class Stage():
    def __init__(self, stageAsDict = None):
        # WARNING: THIS IS A STATIC OBJECT THAT DOES NOT DO ANY STAGE MANIPULATION
        # MEANT TO STORE LIMITS AND POSITIONING DATA OF STAGE
        # IF EXCEPTIONS INVOLVING THE STAGE MUST BE HANDLED, PLEASE HANDLE BEFORE PASSING TO THIS

        # set some default values
        # we assume stage is initialized (homed)

        # currently only supports 2-axis. To extend to 3 axis, adjust where appropriate

        # stageAsDict = property -> value
        # e.g.  { "xlim" : [-5,5] } etc.

        # Set xlim and ylim to be 2 identical value for it to automatically find the range (USE WITH CAUTION)

        self.xlim = [-10000, 0]
        self.ylim = [-10000, 0]
        self.x    = 0
        self.y    = 0

        if stageAsDict:
            self.update(stageAsDict)

    def __repr__(self):
        return "Stage <x [{},{}], y [{},{}]>".format(self.xlim[0], self.xlim[1], self.ylim[0], self.ylim[1])

    def update(self, stageAsDict):
        for k, v in stageAsDict.items():
            if k.endswith("lim") and type(v) is not list:
                raise TypeError("Limit must be a list [lower, upper]")
            setattr(self, k, v)

    def setpos(self, x, y):
        # We keep track of our own coordinates
        # Coordinates replied from the stage is not very consistent

        # Check if x and y are within range and refuse to move if outside range
        assert self.xlim[0] <= x <= self.xlim[1], "Outside xrange"
        assert self.ylim[0] <= x <= self.ylim[1], "Outside yrange"

        self.x = x
        self.y = y

class Micos():
	def __init__(self, stageConfig = None, noCtrlCHandler = False, unit = "um", noHome = False):
		# stageConfig can be a dictionary or a json filename
		# See self.help for documentation

		cfg = {
			"port"      : "COM1",
			"baudrate"  : 9600, #19200,
			"parity"    : serial.PARITY_NONE,
			"stopbits"  : serial.STOPBITS_ONE,
			"bytesize"  : serial.EIGHTBITS,
			"timeout"   : 2
		}

		self.ENTER = b'\x0D' #chr(13)  # CR

		self.stage = Stage()
		if stageConfig:
			if type(stageConfig) is str:
				with open(stageConfig, 'r') as f:
					stageConfig = json.load(f)

			self.stage.update(stageConfig)

		self.units = { "microstep": 0,  "um": 1, "mm": 2, "cm": 3, "m": 4, "in": 5, "mil": 6 }

		try:
			# BEGIN SERIAL SETUP
			self.dev = serial.Serial(
					port 		= cfg['port'],
					baudrate 	= cfg['baudrate'],
					parity 		= cfg['parity'],
					stopbits    = cfg['stopbits'],
					bytesize    = cfg['bytesize'],
					timeout     = cfg['timeout']
				)
			if self.dev.isOpen():
				self.dev.close()
				self.dev.open()

			time.sleep(2)
			print("Initalised serial communication")
			# END SERIAL SETUP

			if not noCtrlCHandler: self.startInterruptHandler()
			self.shutter = shutter.Shutter()

			# BEGIN INITIALIZATION
			print("Stage Initialization...", end="\r")
			self.setunits(unit)
			self.setvel(200)
			self.homed = False

			if not noHome:
				warnings.warn("Stage will not be homed. Proceed with caution.", RuntimeWarning)
				self.homeStage()

			print("Stage Initialization Finished")
			# END INITIALIZATION

		except Exception as e:
			print(e)
			print("Unable to establish serial communication. Please check port settings and change configuration file accordingly. For more help, consult the documention.")
			sys.exit(0)

	def startInterruptHandler(self):
		# https://stackoverflow.com/a/4205386/3211506
		signal.signal(signal.SIGINT, self.KeyboardInterruptHandler)

	def KeyboardInterruptHandler(self, signal, frame):
		print("^C Detected: Aborting the FIFO stack and closing port.")
		print("Shutter will be closed as part of the aborting process.")
		x.abort()
		x.dev.close()
		print("Exiting")
		sys.exit(1)
		# use os._exit(1) to avoid raising any SystemExit exception

	def homeStage(self):
		# return x.send("cal") 		# DO NOT USE CAL ON RUDOLPH => there are some physical/mechanical issues
		xl = abs(self.stage.xlim[1] - self.stage.xlim[0])
        yl = abs(self.stage.ylim[1] - self.stage.ylim[0])

		if xl > 0 and yl > 0:
			# we use faster speed to home the stage
			_oV = self.velocity
			self.setvel(1000)

			# Home the stage
			self.send("rm") 			# send to maximum
			self.setpos(0, 0)
            self.setlimits(self.stage.xlim[0], self.stage.ylim[0], self.stage.xlim[1], self.stage.ylim[1])
			self.rmove(x = -xl/2, y = -yl/2)

			# we set the speed back
			self.setvel(_ov)
		else:
			raise NotImplementedError("Setting the limits to zero asks the script to find the limits of the stage, which is not implemented yet.")
			# UNTESTED CODE
			# self.send("cal")
			# self.setpos(0, 0)
			# self.send("rm")
			# q = self.getpos()

		self.homed = True

	def rmove(self, x, y):
		# Relative move
		# Always call self.stage.setpos first to check limits

		# assertion checks
		assert isinstance(x, (int, float)), "x must be integer or float"
		assert isinstance(y, (int, float)), "y must be integer or float"

        try:
            self.stage.setpos(self.stage.x + x, self.stage.y + y) # Note this is not Micos.setpos
            return self.send("{} {} r".format(x, y))
        except Exception as e:
            pass

    def move(self, x, y):
    	# Absolute move
    	# Always call self.stage.setpos first to check limits

    	# assertion checks
		assert isinstance(x, (int, float)), "x must be integer or float"
		assert isinstance(y, (int, float)), "y must be integer or float"

    	try:
    		warnings.warn("This function may not work as intended. Please use with caution.", RuntimeWarning)
            self.stage.setpos(x, y) # Note this is not Micos.setpos
            return self.send("{} {} m".format(x, y))
        except Exception as e:
            pass

	def setpos(self, x, y):
		# assertion checks
		assert isinstance(x, (int, float)), "x must be integer or float"
		assert isinstance(y, (int, float)), "y must be integer or float"

        self.stage.setpos(x, y)
		return self.send("{} {} setpos".format(x, y))

	def getpos(self):
        # IMPT THIS DOES NOT UPDATE INTERNAL TRACKING COORDS
		# returns the position as reported by the stage in the form of [x, y]
        # Empty split will split by whitespace
		return self.send("p", waitClear = True).strip().split()

	def setlimits(self, xmin, ymin, xmax, ymax):
		# -A1, -A2, +A1, +A2
		# ' '.join("'{0}'".format(n) for n in limits)

		# assertion checks
		assert isinstance(xmin, (int, float)), "xmin must be integer or float"
		assert isinstance(ymin, (int, float)), "ymin must be integer or float"
		assert isinstance(xmax, (int, float)), "xmax must be integer or float"
		assert isinstance(ymax, (int, float)), "ymax must be integer or float"

		self.stage.update({
				"xlim": [xmin, xmax],
				"ylim": [ymin, ymax]
			})
		return self.send("{} {} {} {} setlimit".format(xmin, ymin, xmax, ymax))

	def setunits(self, unit = "um"):
		# 0 = microstep = 1 motor revolution / 40000 steps, 1 = set to um, 2 = mm, 3 = cm, 4 = m, 5 = in, 6 = mil = 1/1000in

		un = self.units.get(unit, 1) # default to um

		# Just in case it is an invalid unit
		# If not 1, it must have found an entry in the list
		self.unit = "um" if un == 1 else unit

		self.send("{} 1 setunit".format(un)) # x-axis
		self.send("{} 2 setunit".format(un)) # y-axis
		self.send("{} 0 setunit".format(un)) # velocity

		return self.send("ge")

	def setvel(self, velocity):
		# Set velocity
		assert isinstance(velocity, (int, float)) , "velocity must be integer or float"

		self.velocity = velocity
		return self.send("{} setvel".format(velocity))

	def send(self, cmd, waitClear = False, raw = False):
		# Writes cmd to the serial channel, returns the data as a list
		cmd = cmd.encode("ascii") + self.ENTER if not raw else cmd

		if waitClear: self.waitClear()

		self.dev.write(cmd)

		return self.read()

	def read(self):
		time.sleep(1)

		out = b''
		while self.dev.inWaiting() > 0:
			out += self.dev.read(1)

		return out.strip() if len(out) else None

	def waitClear(self):
		# we wait until all commands are done running and the stack is empty
		while self.getStatus(0):
			print("Waiting for stack to clear...", end="\r")
			time.sleep(1)
		print("Waiting for stack to clear...cleared")

		return True

	def getStatus(self, digit = None):
		# Get the status of the controller

		assert digit is None or (isinstance(digit, int) and 0 <= digit <= 8), "Invalid digit"

		self.dev.write("st".encode("ascii") + self.ENTER)
		time.sleep(1)
		x = int(self.read())

		if digit is None:
			# we return the full status in a string of binary digits
			return bin(x)[2:]
		else:
			# generate bitmask
			mask = 1 << digit
			return (x & mask)

		# LSB
		# D0  1   Status current command execution
		# D1  2   Status Joystick or Handwheel
		# D2  4   Status Button A
		# D3  8   Machine Error
		# D4  16  Status speed mode
		# D5  32  Status In-Window
		# D6  64  Status setinfunc
		# D7  128 Status motor enable, safety device
		# D8  256 Joystick button
		# MSB

	def abort(self, closeShutter = True):
		# return x.send("abort")
		# Send Ctri + C + self.Enter
		# Set closeShutter to False to not close the shutter when aborting
		self.send(b"\x03\x0D", raw=True)

		if closeShutter:
			self.shutter.close()

	def getError(self):
		return self.send("ge", waitClear=True)

	def help(self):
		self.dipswitches = {
			1: "Baudrate Switch",
			2: "Baudrate Switch",
				# Baudrate apparently determined by Dip-Switch 1 and 2
				# I referenced the manual for Corvus, but I suspect this model is different
				# It works for now and I can't care enough to experiment
				# DS1 DS2
				# 0   0    9600
				# 0   1    19200
				# 1   0    57600
				# 1   1    115200
			3: "Closed Loop On/Off",
			6: "ON = Terminal Mode; OFF = Host Mode",
				# Terminal Mode returns the actual coordinates immediately
				# Host Mode only returns data when asked
		}

	def __enter__(self):
		return self

	def __exit__(self, e_type, e_val, traceback):
		# self.abort()
		self.dev.close()

if __name__ == '__main__':
	with Micos() as m:
		print("m = Micos()\n\n")
		# import pdb; pdb.set_trace()
		import code; code.interact(local=locals())
<commit_msg>reduce sleep time<commit_after>#!/usr/bin/env python3
# hashbang but it's meant to be run on windows ._.

# Python Helper to interact with the NanoLab Microcontroller
# Microcontroller Model: Micos 1860SMC Basic
# Made 2019, Sun Yudong
# sunyudong [at] outlook [dot] sg
# github.com/sunjerry019/photonLauncher

# IMPT: THIS IS A HELPER FILE
# RUNNING IT DIRECTLY YIELDS INTERACTIVE TERMINAL

# Errors to be caught: RuntimeError, NotImplementedError, AssertionError

# You can use python-sphinx to generate docs for this, but I mildly no time
# You can just get a brief idea by opening this in a modern text-editor and minimizing all the functions
# by clicking the small triangle/arrow at the side

import serial
import sys, os
import time
import signal
import json
import warnings

import shutter

class Stage():
    def __init__(self, stageAsDict = None):
        # WARNING: THIS IS A STATIC OBJECT THAT DOES NOT DO ANY STAGE MANIPULATION
        # MEANT TO STORE LIMITS AND POSITIONING DATA OF STAGE
        # IF EXCEPTIONS INVOLVING THE STAGE MUST BE HANDLED, PLEASE HANDLE BEFORE PASSING TO THIS

        # set some default values
        # we assume stage is initialized (homed)

        # currently only supports 2-axis. To extend to 3 axis, adjust where appropriate

        # stageAsDict = property -> value
        # e.g.  { "xlim" : [-5,5] } etc.

        # Set xlim and ylim to be 2 identical value for it to automatically find the range (USE WITH CAUTION)

        self.xlim = [-10000, 0]
        self.ylim = [-10000, 0]
        self.x    = 0
        self.y    = 0

        if stageAsDict:
            self.update(stageAsDict)

    def __repr__(self):
        return "Stage <x [{},{}], y [{},{}]>".format(self.xlim[0], self.xlim[1], self.ylim[0], self.ylim[1])

    def update(self, stageAsDict):
        for k, v in stageAsDict.items():
            if k.endswith("lim") and type(v) is not list:
                raise TypeError("Limit must be a list [lower, upper]")
            setattr(self, k, v)

    def setpos(self, x, y):
        # We keep track of our own coordinates
        # Coordinates replied from the stage is not very consistent

        # Check if x and y are within range and refuse to move if outside range
        assert self.xlim[0] <= x <= self.xlim[1], "Outside xrange"
        assert self.ylim[0] <= x <= self.ylim[1], "Outside yrange"

        self.x = x
        self.y = y

class Micos():
	def __init__(self, stageConfig = None, noCtrlCHandler = False, unit = "um", noHome = False):
		# stageConfig can be a dictionary or a json filename
		# See self.help for documentation

		cfg = {
			"port"      : "COM1",
			"baudrate"  : 9600, #19200,
			"parity"    : serial.PARITY_NONE,
			"stopbits"  : serial.STOPBITS_ONE,
			"bytesize"  : serial.EIGHTBITS,
			"timeout"   : 2
		}

		self.ENTER = b'\x0D' #chr(13)  # CR

		self.stage = Stage()
		if stageConfig:
			if type(stageConfig) is str:
				with open(stageConfig, 'r') as f:
					stageConfig = json.load(f)

			self.stage.update(stageConfig)

		self.units = { "microstep": 0,  "um": 1, "mm": 2, "cm": 3, "m": 4, "in": 5, "mil": 6 }
		self.axes  = ['x', 'y'] 		# Dictionaries are only ordered from python 3.6 onwards

		try:
			# BEGIN SERIAL SETUP
			self.dev = serial.Serial(
					port 		= cfg['port'],
					baudrate 	= cfg['baudrate'],
					parity 		= cfg['parity'],
					stopbits    = cfg['stopbits'],
					bytesize    = cfg['bytesize'],
					timeout     = cfg['timeout']
				)
			if self.dev.isOpen():
				self.dev.close()
				self.dev.open()

			time.sleep(2)
			print("Initalised serial communication")
			# END SERIAL SETUP

			if not noCtrlCHandler: self.startInterruptHandler()
			self.shutter = shutter.Shutter()

			# BEGIN INITIALIZATION
			print("Stage Initialization...", end="\r")
			self.setunits(unit)
			self.setvel(200)
			self.homed = False

			if not noHome:
				warnings.warn("Stage will not be homed. Proceed with caution.", RuntimeWarning)
				self.homeStage()

			print("Stage Initialization Finished")
			# END INITIALIZATION

		except Exception as e:
			print(e)
			print("Unable to establish serial communication. Please check port settings and change configuration file accordingly. For more help, consult the documention.")
			sys.exit(0)

	def startInterruptHandler(self):
		# https://stackoverflow.com/a/4205386/3211506
		signal.signal(signal.SIGINT, self.KeyboardInterruptHandler)

	def KeyboardInterruptHandler(self, signal, frame):
		print("^C Detected: Aborting the FIFO stack and closing port.")
		print("Shutter will be closed as part of the aborting process.")
		x.abort()
		x.dev.close()
		print("Exiting")
		sys.exit(1)
		# use os._exit(1) to avoid raising any SystemExit exception

	def homeStage(self):
		# return x.send("cal") 		# DO NOT USE CAL ON RUDOLPH => there are some physical/mechanical issues
		xl = abs(self.stage.xlim[1] - self.stage.xlim[0])
        yl = abs(self.stage.ylim[1] - self.stage.ylim[0])

		if xl > 0 and yl > 0:
			# we use faster speed to home the stage
			_oV = self.velocity
			self.setvel(1000)

			# Home the stage
			self.send("rm") 			# send to maximum
			self.setpos(0, 0)
            self.setlimits(self.stage.xlim[0], self.stage.ylim[0], self.stage.xlim[1], self.stage.ylim[1])
			self.rmove(x = -xl/2, y = -yl/2)

			# we set the speed back
			self.setvel(_ov)
		else:
			raise NotImplementedError("Setting the limits to zero asks the script to find the limits of the stage, which is not implemented yet.")
			# UNTESTED CODE
			# self.send("cal")
			# self.setpos(0, 0)
			# self.send("rm")
			# q = self.getpos()

		self.homed = True

	def rmove(self, x, y):
		# Relative move
		# Always call self.stage.setpos first to check limits

		# assertion checks
		assert isinstance(x, (int, float)), "x must be integer or float"
		assert isinstance(y, (int, float)), "y must be integer or float"

        try:
            self.stage.setpos(self.stage.x + x, self.stage.y + y) # Note this is not Micos.setpos
            return self.send("{} {} r".format(x, y))
        except Exception as e:
            pass

    def move(self, x, y):
    	# Absolute move
    	# Always call self.stage.setpos first to check limits

    	# assertion checks
		assert isinstance(x, (int, float)), "x must be integer or float"
		assert isinstance(y, (int, float)), "y must be integer or float"

    	try:
    		warnings.warn("This function may not work as intended. Please use with caution.", RuntimeWarning)
            self.stage.setpos(x, y) # Note this is not Micos.setpos
            return self.send("{} {} m".format(x, y))
        except Exception as e:
            pass

	def setpos(self, x, y):
		# assertion checks
		assert isinstance(x, (int, float)), "x must be integer or float"
		assert isinstance(y, (int, float)), "y must be integer or float"

        self.stage.setpos(x, y)
		return self.send("{} {} setpos".format(x, y))

	def getpos(self):
        # IMPT THIS DOES NOT UPDATE INTERNAL TRACKING COORDS
		# returns the position as reported by the stage in the form of [x, y]
        # Empty split will split by whitespace
		return self.send("p", waitClear = True).strip().split()

	def setlimits(self, xmin, ymin, xmax, ymax):
		# -A1, -A2, +A1, +A2
		# ' '.join("'{0}'".format(n) for n in limits)

		# assertion checks
		assert isinstance(xmin, (int, float)), "xmin must be integer or float"
		assert isinstance(ymin, (int, float)), "ymin must be integer or float"
		assert isinstance(xmax, (int, float)), "xmax must be integer or float"
		assert isinstance(ymax, (int, float)), "ymax must be integer or float"

		self.stage.update({
				"xlim": [xmin, xmax],
				"ylim": [ymin, ymax]
			})
		return self.send("{} {} {} {} setlimit".format(xmin, ymin, xmax, ymax))

	def setunits(self, unit = "um"):
		# 0 = microstep = 1 motor revolution / 40000 steps, 1 = set to um, 2 = mm, 3 = cm, 4 = m, 5 = in, 6 = mil = 1/1000in

		un = self.units.get(unit, 1) # default to um

		# Just in case it is an invalid unit
		# If not 1, it must have found an entry in the list
		self.unit = "um" if un == 1 else unit

		self.send("{} 1 setunit".format(un)) # x-axis
		self.send("{} 2 setunit".format(un)) # y-axis
		self.send("{} 0 setunit".format(un)) # velocity

		return self.send("ge")

	def setvel(self, velocity):
		# Set velocity
		assert isinstance(velocity, (int, float)) , "velocity must be integer or float"

		self.velocity = velocity
		return self.send("{} setvel".format(velocity))

	def send(self, cmd, waitClear = False, raw = False):
		# Writes cmd to the serial channel, returns the data as a list
		cmd = cmd.encode("ascii") + self.ENTER if not raw else cmd

		if waitClear: self.waitClear()

		self.dev.write(cmd)

		return self.read()

	def read(self):
		time.sleep(0.05)

		out = b''
		while self.dev.inWaiting() > 0:
			out += self.dev.read(1)

		return out.strip() if len(out) else None

	def waitClear(self):
		# we wait until all commands are done running and the stack is empty
		while self.getStatus(0):
			print("Waiting for stack to clear...", end="\r")
			time.sleep(0.1)
		print("Waiting for stack to clear...cleared")

		return True

	def getStatus(self, digit = None):
		# Get the status of the controller

		assert digit is None or (isinstance(digit, int) and 0 <= digit <= 8), "Invalid digit"

		self.dev.write("st".encode("ascii") + self.ENTER)
		time.sleep(1)
		x = int(self.read())

		if digit is None:
			# we return the full status in a string of binary digits
			return bin(x)[2:]
		else:
			# generate bitmask
			mask = 1 << digit
			return (x & mask)

		# LSB
		# D0  1   Status current command execution
		# D1  2   Status Joystick or Handwheel
		# D2  4   Status Button A
		# D3  8   Machine Error
		# D4  16  Status speed mode
		# D5  32  Status In-Window
		# D6  64  Status setinfunc
		# D7  128 Status motor enable, safety device
		# D8  256 Joystick button
		# MSB

	def abort(self, closeShutter = True):
		# return x.send("abort")
		# Send Ctri + C + self.Enter
		# Set closeShutter to False to not close the shutter when aborting
		self.send(b"\x03\x0D", raw=True)

		if closeShutter:
			self.shutter.close()

	def getError(self):
		return self.send("ge", waitClear=True)

	def help(self):
		self.dipswitches = {
			1: "Baudrate Switch",
			2: "Baudrate Switch",
				# Baudrate apparently determined by Dip-Switch 1 and 2
				# I referenced the manual for Corvus, but I suspect this model is different
				# It works for now and I can't care enough to experiment
				# DS1 DS2
				# 0   0    9600
				# 0   1    19200
				# 1   0    57600
				# 1   1    115200
			3: "Closed Loop On/Off",
			6: "ON = Terminal Mode; OFF = Host Mode",
				# Terminal Mode returns the actual coordinates immediately
				# Host Mode only returns data when asked
		}

	def __enter__(self):
		return self

	def __exit__(self, e_type, e_val, traceback):
		# self.abort()
		self.dev.close()

if __name__ == '__main__':
	with Micos() as m:
		print("m = Micos()\n\n")
		# import pdb; pdb.set_trace()
		import code; code.interact(local=locals())
<|endoftext|>