From 7279223f06fd8fb93c24387706e367e437009366 Mon Sep 17 00:00:00 2001 From: Peter Newman Date: Sat, 27 Jun 2026 17:17:04 +0100 Subject: [PATCH 01/12] Update CI tasks in test.yml Enable nosetests --- .github/workflows/test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d46f7a1b..b2c04e9e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -7,8 +7,8 @@ jobs: strategy: fail-fast: false matrix: - task: [data-check, codespell] - #spellintian, nosetests, karma, lint, closure-compiler, + task: [nosetests, data-check, codespell] + #spellintian, karma, lint, closure-compiler, steps: - uses: actions/checkout@master - name: Install dependencies From 9b793c09efd613f2feec01738f081ffeceba5655 Mon Sep 17 00:00:00 2001 From: Peter Newman Date: Sat, 27 Jun 2026 18:06:56 +0100 Subject: [PATCH 02/12] Update test workflow to install nose conditionally Add conditional installation of 'nose' for nosetests task --- .github/workflows/test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b2c04e9e..940e673c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -15,6 +15,7 @@ jobs: env: TASK: ${{ matrix.task }} run: | + if [ "$TASK" = "nosetests" ]; then pip install nose; fi if [ "$TASK" = "nosetests" -o "$TASK" = "flake8" -o "$TASK" = "flake8-wip" ]; then pip install json-spec; fi if [ "$TASK" = "karma" -o "$TASK" = "lint" -o "$TASK" = "closure-compiler" ]; then npm install -g grunt-cli; fi if [ "$TASK" = "karma" -o "$TASK" = "lint" -o "$TASK" = "closure-compiler" ]; then npm install; fi From 28a7dc5f297f946b4b3bbfe915c427e2e2ab827a Mon Sep 17 00:00:00 2001 From: Peter Newman Date: Sat, 27 Jun 2026 22:05:24 +0100 Subject: [PATCH 03/12] Switch to pynose for more modern Python --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 940e673c..75dcb5a5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -15,7 +15,7 @@ jobs: env: TASK: ${{ matrix.task }} run: | - if [ "$TASK" = "nosetests" ]; then pip install nose; fi + if [ "$TASK" = "nosetests" ]; then pip install pynose; fi if [ "$TASK" = "nosetests" -o "$TASK" = "flake8" -o "$TASK" = "flake8-wip" ]; then pip install json-spec; fi if [ "$TASK" = "karma" -o "$TASK" = "lint" -o "$TASK" = "closure-compiler" ]; then npm install -g grunt-cli; fi if [ "$TASK" = "karma" -o "$TASK" = "lint" -o "$TASK" = "closure-compiler" ]; then npm install; fi From 6530b0b1811fcaed96ccd378dfb7fc3b993adb5c Mon Sep 17 00:00:00 2001 From: Peter Newman Date: Sat, 27 Jun 2026 22:07:03 +0100 Subject: [PATCH 04/12] Change nosetests dependency to pynose in workflow --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 940e673c..75dcb5a5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -15,7 +15,7 @@ jobs: env: TASK: ${{ matrix.task }} run: | - if [ "$TASK" = "nosetests" ]; then pip install nose; fi + if [ "$TASK" = "nosetests" ]; then pip install pynose; fi if [ "$TASK" = "nosetests" -o "$TASK" = "flake8" -o "$TASK" = "flake8-wip" ]; then pip install json-spec; fi if [ "$TASK" = "karma" -o "$TASK" = "lint" -o "$TASK" = "closure-compiler" ]; then npm install -g grunt-cli; fi if [ "$TASK" = "karma" -o "$TASK" = "lint" -o "$TASK" = "closure-compiler" ]; then npm install; fi From 2178e1323507602f96b05733c4efe05535bd23fe Mon Sep 17 00:00:00 2001 From: Peter Newman Date: Sat, 27 Jun 2026 22:26:40 +0100 Subject: [PATCH 05/12] Python 2 and 3 compatible version of execfile --- data/controller_test.py | 3 ++- data/manufacturer_test.py | 6 ++++-- data/model_test.py | 3 ++- data/node_test.py | 3 ++- data/pid_test.py | 3 ++- data/product_category_test.py | 3 ++- data/sensor_test.py | 3 ++- data/software_test.py | 3 ++- data/splitter_test.py | 3 ++- tools/missing_manufacturer_links.py | 6 ++++-- 10 files changed, 24 insertions(+), 12 deletions(-) diff --git a/data/controller_test.py b/data/controller_test.py index 9eedf6e7..4a071fa8 100644 --- a/data/controller_test.py +++ b/data/controller_test.py @@ -24,7 +24,8 @@ class TestControllerData(unittest.TestCase): def setUp(self): globals = {} locals = {} - execfile("data/controller_data.py", globals, locals) + # Python 2 and 3 compatible version of execfile + exec(open("data/controller_data.py").read(), globals, locals) self.data = locals['CONTROLLER_DATA'] def test_ControllerData(self): diff --git a/data/manufacturer_test.py b/data/manufacturer_test.py index 2076b9a7..1f4ebefe 100644 --- a/data/manufacturer_test.py +++ b/data/manufacturer_test.py @@ -30,11 +30,13 @@ class TestManufacturers(unittest.TestCase): def setUp(self): globals = {} locals = {} - execfile("data/manufacturer_data.py", globals, locals) + # Python 2 and 3 compatible version of execfile + exec(open("data/manufacturer_data.py").read(), globals, locals) self.data = locals['MANUFACTURER_DATA'] globals = {} locals = {} - execfile("data/manufacturer_links.py", globals, locals) + # Python 2 and 3 compatible version of execfile + exec(open("data/manufacturer_links.py").read(), globals, locals) self.links = locals['MANUFACTURER_LINKS'] def test_ManufacturerData(self): diff --git a/data/model_test.py b/data/model_test.py index 67e3213c..b97e148b 100644 --- a/data/model_test.py +++ b/data/model_test.py @@ -24,7 +24,8 @@ class TestDeviceModelData(unittest.TestCase): def setUp(self): globals = {} locals = {} - execfile("data/model_data.py", globals, locals) + # Python 2 and 3 compatible version of execfile + exec(open("data/model_data.py").read(), globals, locals) self.data = locals['DEVICE_MODEL_DATA'] def test_DeviceModelData(self): diff --git a/data/node_test.py b/data/node_test.py index f5b1c371..6396cec7 100644 --- a/data/node_test.py +++ b/data/node_test.py @@ -24,7 +24,8 @@ class TestNodeData(unittest.TestCase): def setUp(self): globals = {} locals = {} - execfile("data/node_data.py", globals, locals) + # Python 2 and 3 compatible version of execfile + exec(open("data/node_data.py").read(), globals, locals) self.data = locals['NODE_DATA'] def test_NodeData(self): diff --git a/data/pid_test.py b/data/pid_test.py index fd19a87e..b4feca3c 100644 --- a/data/pid_test.py +++ b/data/pid_test.py @@ -301,7 +301,8 @@ class TestPidData(unittest.TestCase): def setUp(self): globals = {} locals = {} - execfile("data/pid_data.py", globals, locals) + # Python 2 and 3 compatible version of execfile + exec(open("data/pid_data.py").read(), globals, locals) self.manufacturer_pids = locals['MANUFACTURER_PIDS'] self.esta_pids = locals['ESTA_PIDS'] self.pid_validator = jsonspec.validators.load(PID_VALIDATOR) diff --git a/data/product_category_test.py b/data/product_category_test.py index a64818df..9ef73ac2 100644 --- a/data/product_category_test.py +++ b/data/product_category_test.py @@ -24,7 +24,8 @@ class TestProductCategoryData(unittest.TestCase): def setUp(self): globals = {} locals = {} - execfile("data/product_categories.py", globals, locals) + # Python 2 and 3 compatible version of execfile + exec(open("data/product_categories.py").read(), globals, locals) self.data = locals['PRODUCT_CATEGORIES'] def test_ProductCategoryData(self): diff --git a/data/sensor_test.py b/data/sensor_test.py index 614c9759..d8e571b0 100644 --- a/data/sensor_test.py +++ b/data/sensor_test.py @@ -24,7 +24,8 @@ class TestSensorTypes(unittest.TestCase): def setUp(self): globals = {} locals = {} - execfile("data/sensor_types.py", globals, locals) + # Python 2 and 3 compatible version of execfile + exec(open("data/sensor_types.py").read(), globals, locals) self.data = locals['SENSOR_TYPES'] def test_SensorTypeData(self): diff --git a/data/software_test.py b/data/software_test.py index f8a35adb..f0e38c96 100644 --- a/data/software_test.py +++ b/data/software_test.py @@ -24,7 +24,8 @@ class TestSoftwareData(unittest.TestCase): def setUp(self): globals = {} locals = {} - execfile("data/software_data.py", globals, locals) + # Python 2 and 3 compatible version of execfile + exec(open("data/software_data.py").read(), globals, locals) self.data = locals['SOFTWARE_DATA'] def test_SoftwareData(self): diff --git a/data/splitter_test.py b/data/splitter_test.py index 5eb7d372..22858fac 100644 --- a/data/splitter_test.py +++ b/data/splitter_test.py @@ -24,7 +24,8 @@ class TestSplitterData(unittest.TestCase): def setUp(self): globals = {} locals = {} - execfile("data/splitter_data.py", globals, locals) + # Python 2 and 3 compatible version of execfile + exec(open("data/splitter_data.py").read(), globals, locals) self.data = locals['SPLITTER_DATA'] def test_SplitterData(self): diff --git a/tools/missing_manufacturer_links.py b/tools/missing_manufacturer_links.py index d3e25f1a..284e20e8 100755 --- a/tools/missing_manufacturer_links.py +++ b/tools/missing_manufacturer_links.py @@ -54,11 +54,13 @@ def Footer(): if __name__ == '__main__': globals = {} locals = {} - execfile("data/manufacturer_data.py", globals, locals) + # Python 2 and 3 compatible version of execfile + exec(open("data/manufacturer_data.py").read(), globals, locals) raw_manufacturers = locals['MANUFACTURER_DATA'] globals = {} locals = {} - execfile("data/manufacturer_links.py", globals, locals) + # Python 2 and 3 compatible version of execfile + exec(open("data/manufacturer_links.py").read(), globals, locals) raw_links = locals['MANUFACTURER_LINKS'] manufacturers = {} From cd8b7814f482bc4f7d419d16c282cf4aa1131ec7 Mon Sep 17 00:00:00 2001 From: Peter Newman Date: Sun, 28 Jun 2026 11:32:38 +0100 Subject: [PATCH 06/12] Python 2 and 3 compatible URL checking --- data/manufacturer_test.py | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/data/manufacturer_test.py b/data/manufacturer_test.py index 1f4ebefe..921442eb 100644 --- a/data/manufacturer_test.py +++ b/data/manufacturer_test.py @@ -17,13 +17,32 @@ # Copyright (C) 2015 Simon Newton import unittest -import urllib2 import pprint +import sys from socket import error as SocketError -from urllib2 import HTTPError -from urllib2 import URLError from ssl import SSLError +if sys.version_info >= (3, 0): + try: + from urllib.request import build_opener + from urllib.request import HTTPCookieProcessor + from urllib.request import Request + from urllib.error import HTTPError + from urllib.error import URLError + except ImportError: + import urllib2 + from urllib2 import build_opener + from urllib2 import HTTPCookieProcessor + from urllib2 import Request + from urllib2 import HTTPError + from urllib2 import URLError +else: + import urllib2 + from urllib2 import build_opener + from urllib2 import HTTPCookieProcessor + from urllib2 import Request + from urllib2 import HTTPError + from urllib2 import URLError class TestManufacturers(unittest.TestCase): """ Test the manufacturer data files are valid.""" @@ -66,7 +85,7 @@ def test_ManufacturerLinks(self): esta_id, name = manufacturer_data esta_ids.add(esta_id) - opener = urllib2.build_opener(urllib2.HTTPCookieProcessor()) + opener = build_opener(HTTPCookieProcessor()) for manufacturer_link in self.links: self.assertEqual(tuple, type(manufacturer_link)) @@ -92,7 +111,7 @@ def test_ManufacturerLinks(self): ua = {'User-Agent': 'Mozilla/5.0 (KHTML, like Gecko)', 'referer': 'http://example.com'} - request = urllib2.Request(link, headers=ua) + request = Request(link, headers=ua) response = opener.open(request) except URLError as e: if hasattr(e, 'reason'): From c45cebb9828221139f05d9e4994202bb157518e5 Mon Sep 17 00:00:00 2001 From: Peter Newman Date: Sun, 28 Jun 2026 12:12:05 +0100 Subject: [PATCH 07/12] Update some manufacturer links --- data/manufacturer_links.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/data/manufacturer_links.py b/data/manufacturer_links.py index 90ea831d..683bac9b 100644 --- a/data/manufacturer_links.py +++ b/data/manufacturer_links.py @@ -27,7 +27,7 @@ (0x00A8, "https://www.eye.co.jp/"), (0x00A9, "http://www.richter-lt.de"), (0x00B0, "http://www.arenaluci.com/"), - (0x00B9, "http://en.gzsjlight.com/"), + # (0x00B9, "http://en.gzsjlight.com/"), # Website currently gone (0x0104, "https://www.blizzardpro.com/"), (0x01CC, "https://portmanlights.com/"), (0x01CD, "http://www.compulite.com/"), @@ -52,7 +52,7 @@ (0x056B, "https://www.cosmolight.it/"), (0x056C, "https://lumascape.com/"), (0x05E0, "https://github.com/someweisguy/esp_dmx"), - (0x0609, "https://www.pierlite.com.au/diginet"), + (0x0609, "https://pierlite.com.au/our-brands/diginet"), (0x06E4, "https://www.dydell.com/"), (0x0710, "https://dts-lighting.it/"), (0x074F, "https://www.panasonic.com/"), @@ -70,7 +70,7 @@ (0x08A4, "https://www.adamhall.com/"), (0x08A6, "https://impulswerk.de"), (0x08EA, "https://www.cedarled.com/"), - (0x08C5, "https://ehrgeiz.glp.de/"), + # (0x08C5, "https://ehrgeiz.glp.de/"), # Now part of GLP (0x0956, "https://www.expromo.eu/"), (0x0957, "https://www.whitelight.ltd.uk/"), (0x0960, "https://fineline.solutions/"), @@ -134,17 +134,17 @@ (0x4C73, "http://www.lsclighting.com/"), (0x4D41, "http://www.malighting.com/"), (0x4D44, "https://www.mdgfog.com/"), - (0x4D50, "http://www.martin.com/"), - (0x4D56, "http://www.avolites.com/"), + (0x4D50, "https://www.martin.com/"), + (0x4D56, "https://www.avolites.com/"), (0x4F43, "https://www.offstagecontrols.com/"), (0x5000, "http://www.orangepi-dmx.org"), (0x5041, "http://www.lighting.philips.com/"), - (0x5075, "http://pulsarlight.com/"), + # (0x5075, "http://pulsarlight.com/"), # Website has gone (0x5100, "https://www.luxibel.com/"), - (0x514D, "http://www.qmaxz.com/"), - (0x5168, "http://www.sunricher.com"), + # (0x514D, "http://www.qmaxz.com/"), # Website had gone + (0x5168, "https://www.sunricher.com"), (0x5201, "https://www.crestron.com/"), - (0x5252, "http://www.rosco.com/"), + (0x5252, "https://www.rosco.com/"), (0x5253, "https://www.robe.cz/"), (0x5343, "https://www.oceaninsight.com/"), (0x5344, "https://www.stardraw.com/"), From 494c3c873f87b7454d3c535c42b93987e4da8b1b Mon Sep 17 00:00:00 2001 From: Peter Newman Date: Sun, 28 Jun 2026 13:58:32 +0100 Subject: [PATCH 08/12] Add additional links to HTTP error handling test --- data/manufacturer_test.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/data/manufacturer_test.py b/data/manufacturer_test.py index 2076b9a7..c41deeaf 100644 --- a/data/manufacturer_test.py +++ b/data/manufacturer_test.py @@ -108,7 +108,8 @@ def test_ManufacturerLinks(self): (type(e) is HTTPError and (link == 'http://www.compulite.com/' or link == 'https://www.lutron.com/en-US/Pages/default.aspx' or - link == 'https://www.panasonic.com/'))): + link == 'https://www.panasonic.com/' or + link == 'https://www.acuitybrands.com/'))): self.fail("Link %s failed due to %s, reason type: %s" % (link, e.reason, type(e))) elif hasattr(e, 'code'): self.fail("The server couldn't fulfill the request for %s. Error " From 6445feca9622cf2bbee421c40748d1611f7419d1 Mon Sep 17 00:00:00 2001 From: Peter Newman Date: Sun, 28 Jun 2026 14:13:46 +0100 Subject: [PATCH 09/12] Add NXP link to manufacturer test case --- data/manufacturer_test.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/data/manufacturer_test.py b/data/manufacturer_test.py index 1392f336..18609ef6 100644 --- a/data/manufacturer_test.py +++ b/data/manufacturer_test.py @@ -130,7 +130,8 @@ def test_ManufacturerLinks(self): (link == 'http://www.compulite.com/' or link == 'https://www.lutron.com/en-US/Pages/default.aspx' or link == 'https://www.panasonic.com/' or - link == 'https://www.acuitybrands.com/'))): + link == 'https://www.acuitybrands.com/' or + link == 'https://www.nxp.com/'))): self.fail("Link %s failed due to %s, reason type: %s" % (link, e.reason, type(e))) elif hasattr(e, 'code'): self.fail("The server couldn't fulfill the request for %s. Error " From 4db2ae3a47c0a00476d826e2a1b1469a494748d4 Mon Sep 17 00:00:00 2001 From: Peter Newman Date: Sun, 28 Jun 2026 14:17:14 +0100 Subject: [PATCH 10/12] Update manufacturer URLs for accuracy --- data/manufacturer_links.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/manufacturer_links.py b/data/manufacturer_links.py index 683bac9b..e4d86ba9 100644 --- a/data/manufacturer_links.py +++ b/data/manufacturer_links.py @@ -25,8 +25,8 @@ (0x00A2, "http://www.empdesigns.co.uk/"), (0x00A7, "https://www.syncronorm.com/"), (0x00A8, "https://www.eye.co.jp/"), - (0x00A9, "http://www.richter-lt.de"), - (0x00B0, "http://www.arenaluci.com/"), + (0x00A9, "http://www.richter-lt.de/"), + (0x00B0, "https://arenaluci.it/"), # (0x00B9, "http://en.gzsjlight.com/"), # Website currently gone (0x0104, "https://www.blizzardpro.com/"), (0x01CC, "https://portmanlights.com/"), From 67cce8c310010922fe94694538bb5e30aff9c0b9 Mon Sep 17 00:00:00 2001 From: Peter Newman Date: Mon, 29 Jun 2026 11:30:04 +0100 Subject: [PATCH 11/12] Update a few more manufacturer websites --- data/manufacturer_links.py | 6 +++--- data/manufacturer_test.py | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/data/manufacturer_links.py b/data/manufacturer_links.py index e4d86ba9..79da2fa7 100644 --- a/data/manufacturer_links.py +++ b/data/manufacturer_links.py @@ -26,7 +26,7 @@ (0x00A7, "https://www.syncronorm.com/"), (0x00A8, "https://www.eye.co.jp/"), (0x00A9, "http://www.richter-lt.de/"), - (0x00B0, "https://arenaluci.it/"), + (0x00B0, "https://arenaluci.it/en/"), # (0x00B9, "http://en.gzsjlight.com/"), # Website currently gone (0x0104, "https://www.blizzardpro.com/"), (0x01CC, "https://portmanlights.com/"), @@ -157,11 +157,11 @@ (0x544C, "http://tempest.biz/"), (0x564C, "http://www.vari-lite.com/"), (0x5753, "http://wirelessdmx.com"), - (0x5759, "https://www.wybron.com/"), + # (0x5759, "https://www.wybron.com/"), # Company, and website has gone (0x586D, "http://www.doityourselfchristmas.com/"), (0x6019, "https://artificers.co.uk/"), (0x6205, "http://www.ltechonline.com/"), - (0x6364, "http://www.lanbox.com/"), + # (0x6364, "http://www.lanbox.com/"), # Website has gone (0x646F, "https://www.eldoled.com/"), (0x6555, "https://shanteacontrols.com/"), (0x656C, "https://www.elclighting.com/"), diff --git a/data/manufacturer_test.py b/data/manufacturer_test.py index 18609ef6..aaa806d6 100644 --- a/data/manufacturer_test.py +++ b/data/manufacturer_test.py @@ -131,7 +131,8 @@ def test_ManufacturerLinks(self): link == 'https://www.lutron.com/en-US/Pages/default.aspx' or link == 'https://www.panasonic.com/' or link == 'https://www.acuitybrands.com/' or - link == 'https://www.nxp.com/'))): + link == 'https://www.nxp.com/' or + link == 'https://www.martin.com/'))): self.fail("Link %s failed due to %s, reason type: %s" % (link, e.reason, type(e))) elif hasattr(e, 'code'): self.fail("The server couldn't fulfill the request for %s. Error " From fc0aedaca84e4241da822c7b334128526033415c Mon Sep 17 00:00:00 2001 From: Peter Newman Date: Tue, 30 Jun 2026 23:33:25 +0100 Subject: [PATCH 12/12] Add new link to skip during manufacturer test --- data/manufacturer_test.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/data/manufacturer_test.py b/data/manufacturer_test.py index aaa806d6..647caffe 100644 --- a/data/manufacturer_test.py +++ b/data/manufacturer_test.py @@ -132,7 +132,8 @@ def test_ManufacturerLinks(self): link == 'https://www.panasonic.com/' or link == 'https://www.acuitybrands.com/' or link == 'https://www.nxp.com/' or - link == 'https://www.martin.com/'))): + link == 'https://www.martin.com/' or + link == 'https://www.productionwarehouse.co.za/'))): self.fail("Link %s failed due to %s, reason type: %s" % (link, e.reason, type(e))) elif hasattr(e, 'code'): self.fail("The server couldn't fulfill the request for %s. Error "