Skip to content

Commit

Permalink
Merge pull request #57 from sparkmicro/0.4.4
Browse files Browse the repository at this point in the history
(0.4.4) Mouser API support
  • Loading branch information
eeintech committed Aug 18, 2021
2 parents 62267da + 0fc7813 commit 2343556
Show file tree
Hide file tree
Showing 17 changed files with 592 additions and 34 deletions.
4 changes: 3 additions & 1 deletion .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,6 @@ omit =
kintree/kintree_gui.py
kintree/common/progress.py
# Skip wrapt_timeout_decorator
kintree/wrapt_timeout_decorator/*
kintree/wrapt_timeout_decorator/*
# Skip Mouser API base files
kintree/search/mouser/*
5 changes: 3 additions & 2 deletions .github/workflows/test_deploy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@ name: tests | linting | publishing
on:
push:
branches:
- master
- main
tags:
- "*.*.*"
paths-ignore:
- README.md
- images/**
pull_request:
branches:
- master
- main

jobs:
style:
Expand Down Expand Up @@ -86,6 +86,7 @@ jobs:
env:
DIGIKEY_CLIENT_ID: ${{ secrets.DIGIKEY_CLIENT_ID }}
DIGIKEY_CLIENT_SECRET: ${{ secrets.DIGIKEY_CLIENT_SECRET }}
MOUSER_PART_API_KEY: ${{ secrets.MOUSER_PART_API_KEY }}
- name: Coveralls
run: |
coveralls --version
Expand Down
14 changes: 9 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Ki-nTree (pronounced "Key Entry" or "Key 'n' Tree") aims to:
* synchronize parts data between KiCad and InvenTree

Ki-nTree works with:
- Digi-Key's **enormous** part database and [free API](https://developer.digikey.com/)
- [Digi-Key](https://developer.digikey.com/), [Mouser](https://www.mouser.com/api-hub/) and [LCSC](https://lcsc.com/) **enormous** part databases and free APIs
- the awesome open-source [Digi-Key API python library](https://github.com/peeter123/digikey-api) built and maintained by [@peeter123](https://github.com/peeter123)
- the awesome open-source [InvenTree Inventory Management System](https://github.com/inventree/inventree) built and maintained by [@SchrodingersGat](https://github.com/SchrodingersGat)
- [KiCad](https://kicad-pcb.org/) (of course!) and their open-source [library utils](https://github.com/KiCad/kicad-library-utils)
Expand All @@ -30,6 +30,7 @@ Ki-nTree was developped by [@eeintech](https://github.com/eeintech) for [SPARK M

* Ki-nTree is currently tested for Python 3.7 to 3.9 versions.
* Ki-nTree requires a Digi-Key **production** API instance. To create one, go to https://developer.digikey.com/. Create an account, an organization and add a **production** API to your organization. Save both Client ID and Secret keys.
* Ki-nTree requires a Mouser Search API key. To request one, head over to https://www.mouser.ca/api-search/ and click on "Sign Up for Search API"
> [Here is a video](https://youtu.be/OI1EGEc0Ju0) to help with the different steps
### Installation (system wide)
Expand Down Expand Up @@ -102,10 +103,13 @@ Lastly, a new page will open with a "You may now close this window." message, pr
</details>

#### Part Number Search
1. In the main window, enter the part number and click "OK", it will start by fetching part data using the Digi-Key API
2. In the case the Digi-Key API token is not found or expired, a browser window will pop-up. To get a new token: [follow those steps](#get-digi-key-api-token)
3. Once the part data has been successfully fetched from Digi-Key, you will be prompted to add/confirm/edit the `Category` and `Subcategory` to use for this part (Ki-nTree tries to match them automatically)
4. Then, you will be prompted with selecting the KiCad symbol library, template and footprint library to use for this part

Ki-nTree currently supports APIs for the following electronics suppliers: Digi-Key, Mouser and LCSC.

1. In the main window, enter the part number and select the supplier in drop-down list, then click "CREATE". It will start by fetching part data using the supplier's API
2. In the case Digi-Key has been selected and the API token is not found or expired, a browser window will pop-up. To get a new token: [follow those steps](#get-digi-key-api-token)
3. Once the part data has been successfully fetched from the supplier's API, you will be prompted to add/confirm/edit the part information, followed by the `Category` and `Subcategory` to use for this part (Ki-nTree tries to match them automatically)
4. Then, you will be prompted with selecting the KiCad symbol library, template and footprint library to use for this part
5. It will take some time to complete the part creation in InvenTree and/or KiCad, once it finishes you'll be notified of the result
6. Finally, if the part was created or found in InvenTree, your browser will automatically open a new tab with the part information

Expand Down
7 changes: 7 additions & 0 deletions kintree/config/config_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,13 @@ def load_config(path):
except:
cprint('[INFO]\tWarning: Failed to load Digi-Key configuration', silent=silent)
result = False
# Load Mouser configuration files
try:
config_files = os.path.join(path_to_root, 'mouser', '')
load_config(config_files)
except:
cprint('[INFO]\tWarning: Failed to load Mouser configuration', silent=silent)
result = False
# Load LCSC configuration files
try:
config_files = os.path.join(path_to_root, 'lcsc', '')
Expand Down
1 change: 1 addition & 0 deletions kintree/config/digikey/digikey_config.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
SUPPLIER_INVENTREE_NAME: Digi-Key
SEARCH_NAME: null
SEARCH_DESCRIPTION: null
SEARCH_REVISION: null
Expand Down
1 change: 1 addition & 0 deletions kintree/config/lcsc/lcsc_config.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
SUPPLIER_INVENTREE_NAME: LCSC Electronics
SEARCH_NAME: null
SEARCH_DESCRIPTION: null
SEARCH_REVISION: null
Expand Down
1 change: 1 addition & 0 deletions kintree/config/mouser/mouser_api.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
MOUSER_PART_API_KEY: null
10 changes: 10 additions & 0 deletions kintree/config/mouser/mouser_config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
SUPPLIER_INVENTREE_NAME: Mouser Electronics
SEARCH_NAME: null
SEARCH_DESCRIPTION: null
SEARCH_REVISION: null
SEARCH_KEYWORDS: null
SEARCH_SKU: null
SEARCH_MANUFACTURER: null
SEARCH_MPN: null
SEARCH_SUPPLIER_URL: null
SEARCH_DATASHEET: null
10 changes: 8 additions & 2 deletions kintree/config/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
version_info = {
'MAJOR_REVISION': 0,
'MINOR_REVISION': 4,
'RELEASE_STATUS': 3, # Digit means stable version
'RELEASE_STATUS': 4, # Digit means stable version
}
try:
version = '.'.join([str(v) for v in version_info.values()])
Expand Down Expand Up @@ -95,6 +95,9 @@ def load_user_config():
CONFIG_DIGIKEY_CATEGORIES = os.path.join(CONFIG_USER_FILES, 'digikey_categories.yaml')
# CONFIG_DIGIKEY_PARAMETERS = os.path.join(CONFIG_USER_FILES, 'digikey_parameters.yaml')

# Mouser
CONFIG_MOUSER_API = os.path.join(CONFIG_USER_FILES, 'mouser_api.yaml')

# KiCad
CONFIG_KICAD = os.path.join(CONFIG_USER_FILES, 'kicad.yaml')
CONFIG_KICAD_CATEGORY_MAP = os.path.join(CONFIG_USER_FILES, 'kicad_map.yaml')
Expand All @@ -120,14 +123,17 @@ def load_user_config():
AUTOMATIC_BROWSER_OPEN = CONFIG_GENERAL.get('AUTOMATIC_BROWSER_OPEN', False)

# Supported suppliers APIs
SUPPORTED_SUPPLIERS_API = ['Digi-Key', 'LCSC']
SUPPORTED_SUPPLIERS_API = ['Digi-Key', 'Mouser', 'LCSC']

# Digi-Key user configuration
CONFIG_DIGIKEY = config_interface.load_file(os.path.join(CONFIG_USER_FILES, 'digikey_config.yaml'))

# LCSC user configuration
CONFIG_LCSC = config_interface.load_file(os.path.join(CONFIG_USER_FILES, 'lcsc_config.yaml'))

# Mouser user configuration
CONFIG_MOUSER = config_interface.load_file(os.path.join(CONFIG_USER_FILES, 'mouser_config.yaml'))

# Generic API user configuration
CONFIG_SEARCH_API = config_interface.load_file(os.path.join(CONFIG_USER_FILES, 'search_api.yaml'))

Expand Down
31 changes: 27 additions & 4 deletions kintree/database/inventree_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from ..config import config_interface
from ..database import inventree_api
from fuzzywuzzy import fuzz
from ..search import search_api, digikey_api, lcsc_api
from ..search import search_api, digikey_api, mouser_api, lcsc_api


def connect_to_server(timeout=5) -> bool:
Expand Down Expand Up @@ -250,6 +250,8 @@ def get_value_from_user_key(user_key: str, default_key: str, default_value=None)
user_search_key = None
if supplier == 'Digi-Key':
user_search_key = settings.CONFIG_DIGIKEY.get(user_key, None)
elif supplier == 'Mouser':
user_search_key = settings.CONFIG_MOUSER.get(user_key, None)
elif supplier == 'LCSC':
user_search_key = settings.CONFIG_LCSC.get(user_key, None)
else:
Expand All @@ -262,12 +264,31 @@ def get_value_from_user_key(user_key: str, default_key: str, default_value=None)
# Get value for user key, return value from default key if not found
return part_info.get(user_search_key, part_info.get(default_key, default_value))

def get_supplier_name(supplier: str) -> str:
''' Get InvenTree supplier name '''
# Check that supplier is supported
if supplier not in settings.SUPPORTED_SUPPLIERS_API:
return ''

if supplier == 'Digi-Key':
supplier_name = settings.CONFIG_DIGIKEY.get('SUPPLIER_INVENTREE_NAME', None)
elif supplier == 'Mouser':
supplier_name = settings.CONFIG_MOUSER.get('SUPPLIER_INVENTREE_NAME', None)
elif supplier == 'LCSC':
supplier_name = settings.CONFIG_LCSC.get('SUPPLIER_INVENTREE_NAME', None)
else:
supplier_name = supplier

return supplier_name

# Check that supplier argument is valid
if not supplier or (supplier != 'custom' and supplier not in settings.SUPPORTED_SUPPLIERS_API):
return part_form
# Get default keys
if supplier == 'Digi-Key':
default_search_keys = digikey_api.get_default_search_keys()
elif supplier == 'Mouser':
default_search_keys = mouser_api.get_default_search_keys()
elif supplier == 'LCSC':
default_search_keys = lcsc_api.get_default_search_keys()
else:
Expand All @@ -279,7 +300,7 @@ def get_value_from_user_key(user_key: str, default_key: str, default_value=None)
part_form['description'] = get_value_from_user_key('SEARCH_DESCRIPTION', default_search_keys[1], default_value='')
part_form['revision'] = get_value_from_user_key('SEARCH_REVISION', default_search_keys[2], default_value=settings.INVENTREE_DEFAULT_REV)
part_form['keywords'] = get_value_from_user_key('SEARCH_KEYWORDS', default_search_keys[1], default_value='')
part_form['supplier_name'] = supplier if supplier in settings.SUPPORTED_SUPPLIERS_API else ''
part_form['supplier_name'] = get_supplier_name(supplier)
part_form['supplier_part_number'] = get_value_from_user_key('SEARCH_SKU', default_search_keys[4], default_value='')
part_form['supplier_link'] = get_value_from_user_key('SEARCH_SUPPLIER_URL', default_search_keys[7], default_value='')
part_form['manufacturer_name'] = get_value_from_user_key('SEARCH_MANUFACTURER', default_search_keys[5], default_value='')
Expand Down Expand Up @@ -309,10 +330,12 @@ def supplier_search(supplier: str, part_number: str, test_mode=False) -> dict:
cprint(f'\n[MAIN]\t{supplier} search for {part_number}', silent=settings.SILENT)
if supplier == 'Digi-Key':
part_info = digikey_api.fetch_part_info(part_number)
elif supplier == 'Mouser':
part_info = mouser_api.fetch_part_info(part_number)
elif supplier == 'LCSC':
part_info = lcsc_api.fetch_part_info(part_number)

# Check Digi-Key data exist
# Check supplier data exist
if not part_info:
cprint(f'[INFO]\tError: Failed to fetch data for "{part_number}"', silent=settings.SILENT)

Expand All @@ -325,7 +348,7 @@ def supplier_search(supplier: str, part_number: str, test_mode=False) -> dict:

def inventree_create(part_info: dict, categories: list, kicad=False, symbol=None, footprint=None, show_progress=True, is_custom=False):
''' Create InvenTree part from supplier part data and categories '''
# TODO: Make 'supplier' a variable for use with other APIs (eg. LCSC, Mouser, etc)

part_pk = 0
new_part = False

Expand Down
85 changes: 71 additions & 14 deletions kintree/kintree_gui.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
import PySimpleGUI as sg
# Digi-Key API
from .search import digikey_api as digikey_api
# Mouser API
from .search import mouser_api as mouser_api
# SnapEDA API
from .search import snapeda_api as snapeda_api
# Progress
Expand Down Expand Up @@ -77,8 +79,8 @@ def user_settings_window():
return


def search_api_settings_window():
''' Part search API settings window '''
def digikey_api_settings_window():
''' Digi-Key API settings window '''

user_settings = config_interface.load_file(settings.CONFIG_DIGIKEY_API)

Expand Down Expand Up @@ -131,6 +133,55 @@ def save_settings(user_settings: dict):
return


def mouser_api_settings_window():
''' Mouser API settings window '''

user_settings = config_interface.load_file(settings.CONFIG_MOUSER_API)

search_api_layout = [
[
sg.Text('Mouser Part API Key '),
sg.InputText(user_settings['MOUSER_PART_API_KEY'], key='api_key'),
],
[
sg.Button('Test', size=(15, 1)),
sg.Button('Save', size=(15, 1)),
],
]
search_api_window = sg.Window(
'Mouser API (v1) Settings', search_api_layout, location=(500, 500)
)

while True:
api_event, api_values = search_api_window.read()

def save_settings(user_settings: dict):
new_settings = {
'MOUSER_PART_API_KEY': api_values['api_key'],
}
user_settings = {**user_settings, **new_settings}
config_interface.dump_file(user_settings, settings.CONFIG_MOUSER_API)
mouser_api.setup_environment(force=True)

if api_event == sg.WIN_CLOSED:
search_api_window.close()
return
elif api_event == 'Test':
# Automatically save settings
save_settings(user_settings)
if mouser_api.test_api():
result_message = 'Successfully connected to Mouser API'
else:
result_message = 'Failed to connect to Mouser API'
sg.popup_ok(result_message,
title='Mouser API Connect Test',
location=(500, 500))
else:
save_settings(user_settings)
search_api_window.close()
return


def inventree_settings_window():
''' InvenTree settings window '''

Expand Down Expand Up @@ -778,12 +829,23 @@ def build_choices(items: dict, category: str, subcategory=None) -> list:

return symbol, template, footprint

# Main

# Init
def init():
# Disable Digi-Key API logger
digikey_api.disable_api_logger()
# Fix for Windows EXE
import multiprocessing
multiprocessing.freeze_support()


# Main
def main():
''' Main GUI window '''

# Disable Digi-Key logger
init()

CREATE_CUSTOM = False

# Select PySimpleGUI theme
Expand All @@ -796,6 +858,7 @@ def main():
[
'User',
'Digi-Key',
'Mouser',
'KiCad',
'InvenTree',
],
Expand Down Expand Up @@ -842,7 +905,9 @@ def main():
if event == 'User':
user_settings_window()
elif event == 'Digi-Key':
search_api_settings_window()
digikey_api_settings_window()
elif event == 'Mouser':
mouser_api_settings_window()
elif event == 'InvenTree':
inventree_settings_window()
elif event == 'KiCad':
Expand Down Expand Up @@ -924,6 +989,8 @@ def main():
'\n- Part number is valid and not blank'
if values['supplier'] == 'Digi-Key':
error_message += '\n- Digi-Key API settings are correct ("Settings > Digi-Key")'
elif values['supplier'] == 'Mouser':
error_message += '\n- Mouser API settings are correct ("Settings > Mouser")'
elif values['supplier'] == 'LCSC':
error_message += '\n- Part number starts with "C" (LCSC code)'
# Missing Part Information
Expand Down Expand Up @@ -1176,13 +1243,3 @@ def main():
CREATE_CUSTOM = False

window.close()


if __name__ == '__main__':
# Disable Digi-Key API logger
digikey_api.disable_api_logger()
# Fix for Windows EXE
import multiprocessing
multiprocessing.freeze_support()
# Run main window
main()
1 change: 0 additions & 1 deletion kintree/search/lcsc_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ def get_default_search_keys():
def find_categories(part_details: str):
''' Find categories '''
try:
part_details['parentCatalogName']
return part_details['parentCatalogName'], part_details['catalogName']
except:
return None, None
Expand Down
Loading

0 comments on commit 2343556

Please sign in to comment.