diff --git a/hm_pyhelper/hardware_definitions.py b/hm_pyhelper/hardware_definitions.py index 9f31c46..dafac99 100644 --- a/hm_pyhelper/hardware_definitions.py +++ b/hm_pyhelper/hardware_definitions.py @@ -1,30 +1,79 @@ import os +from enum import Enum, auto +from collections import namedtuple from hm_pyhelper.exceptions import UnknownVariantException, \ - UnknownVariantAttributeException + UnknownVariantAttributeException +BALENA_ENV_RASPBERRY_PI_MODELS = [ + 'raspberry-pi2', + 'raspberrypi3', + 'raspberrypi3-64', + 'raspberrypi4-64', + 'nebra-hnt', + 'raspberrypicm4-ioboard' +] -def is_rockpi(): - return 'rockpi-4b-rk3399' in os.getenv('BALENA_DEVICE_TYPE') +BALENA_ENV_ROCKPI_MODELS = ['rockpi-4b-rk3399'] +SBCInfo = namedtuple('SBCInfo', ['vendor_id', 'vendor_name', 'model_name']) -def is_raspberry_pi(): + +class DeviceVendor(Enum): + """ + Enum for device vendors. + """ + INVALID = auto() + ROCK_PI = auto() + RASPBERRY_PI = auto() + + +def device_model(): + with open('/proc/device-tree/model', 'r') as f: + return f.readline().strip() + + +def sbc_info() -> SBCInfo: + ''' + return SBCInfo formed by reading '/proc/device-tree/model' + ''' + sbc_info = SBCInfo(vendor_id=DeviceVendor.INVALID, vendor_name='', model_name='') + dev_model = device_model() + if dev_model.lower().find('raspberry') >= 0: + sbc_info = SBCInfo(vendor_id=DeviceVendor.RASPBERRY_PI, + vendor_name='Raspberry Pi', + model_name=dev_model) + elif dev_model.lower().find('rock') >= 0: + sbc_info = SBCInfo(vendor_id=DeviceVendor.ROCK_PI, + vendor_name='Radxa Rock Pi', + model_name=dev_model) + return sbc_info + + +def is_rockpi() -> bool: + device_type = os.getenv('BALENA_DEVICE_TYPE') + + # use device tree supplied model name if evn not set + if not device_type: + return sbc_info().vendor_id == DeviceVendor.ROCK_PI + + # honor env override + if device_type in BALENA_ENV_ROCKPI_MODELS: + return True + return False + + +def is_raspberry_pi() -> bool: """ Pulled from https://www.balena.io/docs/reference/base-images/devicetypes/ """ device_type = os.getenv('BALENA_DEVICE_TYPE') - device_type_match = [ - 'raspberry-pi2', - 'raspberrypi3', - 'raspberrypi3-64', - 'raspberrypi4-64', - 'nebra-hnt', - 'raspberrypicm4-ioboard' - ] - - for device in device_type_match: - if device in device_type: - return True + if not device_type: + return sbc_info().vendor_id == DeviceVendor.RASPBERRY_PI + + # honor env override + if device_type in BALENA_ENV_RASPBERRY_PI_MODELS: + return True return False @@ -46,7 +95,7 @@ def is_raspberry_pi(): 'CONTAINS_FCC_IDS': ['2AHRD-EPN8531', '2AB8JCSR40', '2ARPP-GL5712UX'], 'IC_IDS': ['27187-HNTIN'], 'CONTAINS_IC_IDS': [] - }, + }, # Nebra Indoor Hotspot, Old identifier 'Indoor': { @@ -65,7 +114,7 @@ def is_raspberry_pi(): 'CONTAINS_FCC_IDS': ['2AHRD-EPN8531', '2AB8JCSR40', '2ARPP-GL5712UX'], 'IC_IDS': ['27187-HNTIN'], 'CONTAINS_IC_IDS': [] - }, + }, # Nebra Outdoor Hotspot Gen1 'NEBHNT-OUT1': { @@ -85,7 +134,7 @@ def is_raspberry_pi(): 'XMR201903EG25G', '2AZDM-WIFIRP'], 'IC_IDS': ['27187-HNTOUT'], 'CONTAINS_IC_IDS': [] - }, + }, # Nebra Outdoor Hotspot Old Identifier 'Outdoor': { @@ -105,7 +154,7 @@ def is_raspberry_pi(): 'XMR201903EG25G', '2AZDM-WIFIRP'], 'IC_IDS': ['27187-HNTOUT'], 'CONTAINS_IC_IDS': [] - }, + }, # Nebra Pi 0 Light Hotspot SPI Ethernet 'NEBHNT-LGT-ZS': { @@ -124,7 +173,7 @@ def is_raspberry_pi(): 'CONTAINS_FCC_IDS': ['2ABCB-RPI0W', '2ARPP-GL5712UX'], 'IC_IDS': ['27187-HNTLGTMC'], 'CONTAINS_IC_IDS': ['20953-RPI0W'] - }, + }, # Nebra Pi 0 Light Hotspot USB Ethernet 'NEBHNT-LGT-ZX': { @@ -143,7 +192,7 @@ def is_raspberry_pi(): 'CONTAINS_FCC_IDS': [], 'IC_IDS': [], 'CONTAINS_IC_IDS': [] - }, + }, # Nebra Beaglebone Light Hotspot 'NEBHNT-BBB': { @@ -162,7 +211,7 @@ def is_raspberry_pi(): 'CONTAINS_FCC_IDS': [], 'IC_IDS': [], 'CONTAINS_IC_IDS': [] - }, + }, # Nebra Pocket Beagle Light Hotspot 'NEBHNT-PBB': { @@ -181,7 +230,7 @@ def is_raspberry_pi(): 'CONTAINS_FCC_IDS': [], 'IC_IDS': [], 'CONTAINS_IC_IDS': [] - }, + }, # Nebra Hotspot HAT ROCK Pi 4 Indoor 'NEBHNT-HHRK4': { @@ -202,7 +251,7 @@ def is_raspberry_pi(): 'CONTAINS_FCC_IDS': ['2ARPP-GL5712UX', '2A3PA-ROCKPI4'], 'IC_IDS': ['27187-HHRK4'], 'CONTAINS_IC_IDS': [] - }, + }, # Nebra Hotspot HAT ROCK Pi 4 Outdoor 'NEBHNT-HHRK4-OUT': { @@ -225,7 +274,7 @@ def is_raspberry_pi(): 'XMR201903EG25G'], 'IC_IDS': ['27187-HHRK4-OUT'], 'CONTAINS_IC_IDS': [] - }, + }, # Nebra Hotspot HAT RPi 3/4 Full 'NEBHNT-HHRPI': { @@ -244,7 +293,7 @@ def is_raspberry_pi(): 'CONTAINS_FCC_IDS': [], 'IC_IDS': [], 'CONTAINS_IC_IDS': [] - }, + }, # Nebra Hotspot HAT RPi Light 'NEBHNT-HHRPL': { @@ -263,7 +312,7 @@ def is_raspberry_pi(): 'CONTAINS_FCC_IDS': [], 'IC_IDS': [], 'CONTAINS_IC_IDS': [] - }, + }, # Nebra Hotspot HAT Tinkerboard 1 'NEBHNT-HHTK': { @@ -282,7 +331,7 @@ def is_raspberry_pi(): 'CONTAINS_FCC_IDS': [], 'IC_IDS': [], 'CONTAINS_IC_IDS': [] - }, + }, # Nebra Hotspot HAT Tinkerboard 2 'NEBHNT-HHTK2': { @@ -301,7 +350,7 @@ def is_raspberry_pi(): 'CONTAINS_FCC_IDS': [], 'IC_IDS': [], 'CONTAINS_IC_IDS': [] - }, + }, # RAKwireless Hotspot Miner 'COMP-RAKHM': { @@ -319,7 +368,7 @@ def is_raspberry_pi(): 'CONTAINS_FCC_IDS': [], 'IC_IDS': [], 'CONTAINS_IC_IDS': [] - }, + }, # Helium Hotspot 'COMP-HELIUM': { @@ -337,7 +386,7 @@ def is_raspberry_pi(): 'CONTAINS_FCC_IDS': [], 'IC_IDS': [], 'CONTAINS_IC_IDS': [] - }, + }, # SenseCAP M1 Hotspot 'COMP-SENSECAPM1': { @@ -396,7 +445,7 @@ def is_raspberry_pi(): 'CONTAINS_FCC_IDS': [], 'IC_IDS': [], 'CONTAINS_IC_IDS': [] - }, + }, # DIY Pi Supply Hotspot HAT 'DIY-PISLGH': { @@ -414,7 +463,7 @@ def is_raspberry_pi(): 'CONTAINS_FCC_IDS': [], 'IC_IDS': [], 'CONTAINS_IC_IDS': [] - }, + }, # Nebra Indoor Hotspot 'DIY-RAK2287': { @@ -432,7 +481,7 @@ def is_raspberry_pi(): 'CONTAINS_FCC_IDS': [], 'IC_IDS': [], 'CONTAINS_IC_IDS': [] - } + } } diff --git a/hm_pyhelper/tests/test_hardware_definitions.py b/hm_pyhelper/tests/test_hardware_definitions.py index ab7dddf..3b332d9 100644 --- a/hm_pyhelper/tests/test_hardware_definitions.py +++ b/hm_pyhelper/tests/test_hardware_definitions.py @@ -1,12 +1,16 @@ +import os import pytest from unittest import TestCase -from unittest.mock import patch +from unittest.mock import patch, mock_open from hm_pyhelper.exceptions import UnknownVariantException, \ - UnknownVariantAttributeException + UnknownVariantAttributeException -from hm_pyhelper.hardware_definitions import variant_definitions, \ - get_variant_attribute +from hm_pyhelper.hardware_definitions import is_rockpi, variant_definitions, \ + get_variant_attribute, is_raspberry_pi, BALENA_ENV_RASPBERRY_PI_MODELS, \ + BALENA_ENV_ROCKPI_MODELS + +BUILTINS_OPEN_LITERAL = "builtins.open" class TestHardwareDefinitions(TestCase): @@ -73,3 +77,65 @@ def test_get_variant_attribute_unknown_variant(self): def test_get_variant_attribute_unknown_attribute(self): with pytest.raises(UnknownVariantAttributeException): get_variant_attribute('NEBHNT-XYZ', 'Nonexistant') + + # raspberry pi model names picked from pi kernel sources + # https://github.com/raspberrypi/linux + # grep -ir "raspberry" linux/arch/arm* | grep "model =" | cut -d "=" -f2 + mock_known_dts_pi_models = [ + "Raspberry Pi Model B+", + "Raspberry Pi Model B", + "Raspberry Pi Compute Module", + "Raspberry Pi Zero", + "Raspberry Pi 2 Model B rev 1.2", + "Raspberry Pi Compute Module 3", + "Raspberry Pi Zero 2 W", + "Raspberry Pi 4 Model B", + "Raspberry Pi 400", + "Raspberry Pi Compute Module 4", + "Raspberry Pi Compute Module 4S", + "Raspberry Pi Model A+", + "Raspberry Pi Model A", + "Raspberry Pi Model B rev2", + "Raspberry Pi Compute Module IO board rev1", + "Raspberry Pi Zero W", + "Raspberry Pi 2 Model B", + "Raspberry Pi 3 Model A+", + "Raspberry Pi 3 Model B+", + "Raspberry Pi 3 Model B", + "Raspberry Pi Compute Module 3 IO board V3.0" + ] + + def test_is_raspberry_pi(self): + for model in self.mock_known_dts_pi_models: + with patch(BUILTINS_OPEN_LITERAL, new_callable=mock_open, read_data=model): + self.assertTrue(is_raspberry_pi()) + with patch(BUILTINS_OPEN_LITERAL, new_callable=mock_open, read_data="Rock something"): + self.assertFalse(is_raspberry_pi()) + + # test balena env based detection + for model in BALENA_ENV_RASPBERRY_PI_MODELS: + with patch.dict(os.environ, {'BALENA_DEVICE_TYPE': model}): + self.assertTrue(is_raspberry_pi()) + # in absence of the env, it should look for /proc/device-tree/model + # which will not exist on test environment. + with self.assertRaises(FileNotFoundError): + self.assertFalse(is_raspberry_pi()) + + mock_known_rock_dts_models = ["ROCK PI 4B"] + + def test_is_rock_pi(self): + for model in self.mock_known_rock_dts_models: + with patch(BUILTINS_OPEN_LITERAL, new_callable=mock_open, read_data=model): + self.assertTrue(is_rockpi()) + with patch(BUILTINS_OPEN_LITERAL, new_callable=mock_open, + read_data="raspberry something"): + self.assertFalse(is_rockpi()) + + # test balena env based detection + for model in BALENA_ENV_ROCKPI_MODELS: + with patch.dict(os.environ, {'BALENA_DEVICE_TYPE': model}): + self.assertTrue(is_rockpi()) + # in absence of the env, it should look for /proc/device-tree/model + # which will not exist on test environment. + with self.assertRaises(FileNotFoundError): + self.assertFalse(is_rockpi()) diff --git a/setup.py b/setup.py index 1ee8a1c..8f100a7 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ setup( name='hm_pyhelper', - version='0.13.14', + version='0.13.15', author="Nebra Ltd", author_email="support@nebra.com", description="Helium Python Helper",