initial commit
This commit is contained in:
commit
fae3804518
|
@ -0,0 +1,21 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2020 Lars Hoogestraat <inbox-kodi@hoogi.eu> (https://hoogi.eu)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
|
@ -0,0 +1,74 @@
|
|||
## Description
|
||||
|
||||
Bias lighting for your Kodi media center using Pimoroni Mote lights.
|
||||
|
||||
## How to install?
|
||||
|
||||
**Requirements**
|
||||
|
||||
* Pimoroni Mote ([https://shop.pimoroni.com/products/mote)](https://shop.pimoroni.com/products/mote)
|
||||
* Tape
|
||||
* Kodi Leia 18.x version supported
|
||||
|
||||
I'm just starting with some Kodi and Python development, there is no repository available. Just copy the zip to some folder
|
||||
accessible by the user running kodi and choose to install from zip file. You have to enable unknown sources (Settings -> System -> Addons)
|
||||
|
||||
The service will run in the background.
|
||||
|
||||
The add-on integrates the Python Mote library from Pimoroni: [https://github.com/pimoroni/mote](https://github.com/pimoroni/mote)
|
||||
|
||||
|
||||
## How to configure?
|
||||
|
||||
The configuration can be done in the Kodi add-on setting page.
|
||||
|
||||
You can configure the position of your Mote LED stripes like the following:
|
||||
|
||||
```
|
||||
|
||||
TOP LEFT TOP MIDDLE TOP RIGHT
|
||||
---------------- ---------------- -------------
|
||||
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
SIDE | | | | SIDE
|
||||
LEFT | | | | RIGHT
|
||||
TOP | | | | TOP
|
||||
| |
|
||||
| |
|
||||
| |
|
||||
SIDE | | | | SIDE
|
||||
LEFT | | | | RIGHT
|
||||
MIDDLE | | | | MIDDLE
|
||||
| |
|
||||
| |
|
||||
| |
|
||||
SIDE | | | | SIDE
|
||||
LEFT | | | | RIGHT
|
||||
BOTTOM | | | | BOTTOM
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
---------------- ---------------- -------------
|
||||
BOTTOM LEFT BOTTOM MIDDLE BOTTOM RIGHT
|
||||
```
|
||||
|
||||
Inverting the output is also supported, you don't have to pay attention to correct direction.
|
||||
|
||||
For setting up the correct positions download the test_color.mp4 video and play/pause it in Kodi while configuring your Mote.
|
||||
|
||||
You must deactivate / activate add-on to apply settings.
|
||||
|
||||
|
||||
## Todos
|
||||
|
||||
* If capture is switching colors fast the lightning is noisy
|
||||
* Something like a color picker for custom colors?
|
||||
|
||||
## Bugs, patches and feedback always welcome.
|
||||
|
||||
* Without registration: inbox-kodi@hoogi.eu
|
||||
|
||||
|
||||
## Credits
|
||||
* Pimoroni Mote Python Library [https://github.com/pimoroni/mote](https://github.com/pimoroni/mote)
|
||||
* resources/fanart.jpg and resources/icon.png
|
||||
* Stephan Legachev ([https://commons.wikimedia.org/wiki/File:Ambilight-2.jpg](https://commons.wikimedia.org/wiki/File:Ambilight-2.jpg)), „Ambilight-2“, [https://creativecommons.org/licenses/by/3.0/legalcode](https://creativecommons.org/licenses/by/3.0/legalcode)
|
|
@ -0,0 +1,41 @@
|
|||
================================================================================
|
||||
Pimoroni Mote
|
||||
|
||||
Github:
|
||||
https://github.com/pimoroni/mote
|
||||
================================================================================
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017 Pimoroni Ltd.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
================================================================================
|
||||
Stephan Legachev
|
||||
|
||||
- resources/fanart.jpg
|
||||
- resources/icong.png
|
||||
|
||||
Wikimedia:
|
||||
https://commons.wikimedia.org/wiki/File:Ambilight-2.jpg
|
||||
================================================================================
|
||||
|
||||
This file is licensed under the Creative Commons Attribution 3.0 Unported license.
|
||||
https://creativecommons.org/licenses/by/3.0/legalcode
|
|
@ -0,0 +1,307 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
import xbmc
|
||||
import xbmcaddon
|
||||
from PIL import Image
|
||||
from resources.lib.mote import Mote
|
||||
|
||||
NUM_PIXEL = 16
|
||||
|
||||
colors = [
|
||||
(0, 0, 0), # None
|
||||
(255, 0, 0), # Red
|
||||
(0, 255, 0), # Green
|
||||
(0, 0, 255), # Blue
|
||||
(255, 255, 0), # Yellow
|
||||
(255, 255, 255) # White
|
||||
]
|
||||
|
||||
mote = Mote()
|
||||
|
||||
mote.configure_channel(1, NUM_PIXEL, True)
|
||||
mote.configure_channel(2, NUM_PIXEL, True)
|
||||
mote.configure_channel(3, NUM_PIXEL, True)
|
||||
mote.configure_channel(4, NUM_PIXEL, True)
|
||||
|
||||
mote.set_clear_on_exit()
|
||||
|
||||
|
||||
class Position():
|
||||
top = 1
|
||||
bottom = 2
|
||||
left = 3
|
||||
right = 4
|
||||
|
||||
|
||||
def set_static_color(channels, index, r, g, b, brightness=None):
|
||||
for k, v in channels.items():
|
||||
mote.set_pixel(v.get("channel"), index, r, g, b, brightness)
|
||||
|
||||
|
||||
def clear_mote():
|
||||
mote.clear()
|
||||
mote.show()
|
||||
|
||||
|
||||
monitor = xbmc.Monitor()
|
||||
|
||||
width, height = 32, 32
|
||||
settings = xbmcaddon.Addon(id='script.service.biaslightmote')
|
||||
|
||||
|
||||
mote.clear()
|
||||
|
||||
mote1 = settings.getSetting("mote1")
|
||||
mote1_pos = int(settings.getSetting("mote1_position"))
|
||||
mote1_inv = settings.getSetting("mote1_invert")
|
||||
|
||||
mote2 = settings.getSetting("mote2")
|
||||
mote2_pos = int(settings.getSetting("mote2_position"))
|
||||
mote2_inv = settings.getSetting("mote2_invert")
|
||||
|
||||
mote3 = settings.getSetting("mote3")
|
||||
mote3_pos = int(settings.getSetting("mote3_position"))
|
||||
mote3_inv = settings.getSetting("mote3_invert")
|
||||
|
||||
mote4 = settings.getSetting("mote4")
|
||||
mote4_pos = int(settings.getSetting("mote4_position"))
|
||||
mote4_inv = settings.getSetting("mote4_invert")
|
||||
|
||||
mode = int(settings.getSetting("mode"))
|
||||
color = int(settings.getSetting("color"))
|
||||
|
||||
custom_color_red = int(settings.getSetting("color_custom_red"))
|
||||
custom_color_green = int(settings.getSetting("color_custom_green"))
|
||||
custom_color_blue = int(settings.getSetting("color_custom_blue"))
|
||||
colors.append((custom_color_red, custom_color_green, custom_color_blue))
|
||||
|
||||
brightness = float(float(settings.getSetting("brightness")) / float(100))
|
||||
|
||||
channels = {}
|
||||
|
||||
if mote1 == "true":
|
||||
channels.update({"1": {"channel": 1, "invert": mote1_inv}})
|
||||
# TOP LEFT
|
||||
if mote1_pos == 0:
|
||||
channels.get("1").update({"direction": Position.top, "start": 0, "stop": 16})
|
||||
# TOP MIDDLE
|
||||
elif mote1_pos == 1:
|
||||
channels.get("1").update({"direction": Position.top, "start": 8, "stop": 24})
|
||||
# TOP RIGHT
|
||||
elif mote1_pos == 2:
|
||||
channels.get("1").update({"direction": Position.top, "start": 16, "stop": 32})
|
||||
# SIDE LEFT TOP
|
||||
elif mote1_pos == 3:
|
||||
channels.get("1").update({"direction": Position.left, "start": 0, "stop": 16})
|
||||
# SIDE LEFT MIDDLE
|
||||
elif mote1_pos == 4:
|
||||
channels.get("1").update({"direction": Position.left, "start": 8, "stop": 24})
|
||||
# SIDE LEFT BOTTOM
|
||||
elif mote1_pos == 5:
|
||||
channels.get("1").update({"direction": Position.left, "start": 16, "stop": 32})
|
||||
# SIDE RIGHT TOP
|
||||
elif mote1_pos == 6:
|
||||
channels.get("1").update({"direction": Position.right, "start": 0, "stop": 16})
|
||||
# SIDE RIGHT MIDDLE
|
||||
elif mote1_pos == 7:
|
||||
channels.get("1").update({"direction": Position.right, "start": 8, "stop": 24})
|
||||
# SIDE RIGHT BOTTOM
|
||||
elif mote1_pos == 8:
|
||||
channels.get("1").update({"direction": Position.right, "start": 16, "stop": 32})
|
||||
# BOTTOM LEFT
|
||||
elif mote1_pos == 9:
|
||||
channels.get("1").update({"direction": Position.bottom, "start": 0, "stop": 16})
|
||||
# BOTTOM MIDDLE
|
||||
elif mote1_pos == 10:
|
||||
channels.get("1").update({"direction": Position.bottom, "start": 8, "stop": 24})
|
||||
# BOTTOM RIGHT
|
||||
elif mote1_pos == 11:
|
||||
channels.get("1").update({"direction": Position.bottom, "start": 16, "stop": 32})
|
||||
if mote2 == "true":
|
||||
channels.update({"2": {"channel": 2, "invert": mote2_inv}})
|
||||
# TOP LEFT
|
||||
if mote2_pos == 0:
|
||||
channels.get("2").update({"direction": Position.top, "start": 0, "stop": 16})
|
||||
# TOP MIDDLE
|
||||
elif mote2_pos == 1:
|
||||
channels.get("2").update({"direction": Position.top, "start": 8, "stop": 24})
|
||||
# TOP RIGHT
|
||||
elif mote2_pos == 2:
|
||||
channels.get("2").update({"direction": Position.top, "start": 16, "stop": 32})
|
||||
# SIDE LEFT TOP
|
||||
elif mote2_pos == 3:
|
||||
channels.get("2").update({"direction": Position.left, "start": 0, "stop": 16})
|
||||
# SIDE LEFT MIDDLE
|
||||
elif mote2_pos == 4:
|
||||
channels.get("2").update({"direction": Position.left, "start": 8, "stop": 24})
|
||||
# SIDE LEFT BOTTOM
|
||||
elif mote2_pos == 5:
|
||||
channels.get("2").update({"direction": Position.left, "start": 16, "stop": 32})
|
||||
# SIDE RIGHT TOP
|
||||
elif mote2_pos == 6:
|
||||
channels.get("2").update({"direction": Position.right, "start": 0, "stop": 16})
|
||||
# SIDE RIGHT MIDDLE
|
||||
elif mote2_pos == 7:
|
||||
channels.get("2").update({"direction": Position.right, "start": 8, "stop": 24})
|
||||
# SIDE RIGHT BOTTOM
|
||||
elif mote2_pos == 8:
|
||||
channels.get("2").update({"direction": Position.right, "start": 16, "stop": 32})
|
||||
# BOTTOM LEFT
|
||||
elif mote2_pos == 9:
|
||||
channels.get("2").update({"direction": Position.bottom, "start": 0, "stop": 16})
|
||||
# BOTTOM MIDDLE
|
||||
elif mote2_pos == 10:
|
||||
channels.get("2").update({"direction": Position.bottom, "start": 8, "stop": 24})
|
||||
# BOTTOM RIGHT
|
||||
elif mote2_pos == 11:
|
||||
channels.get("2").update({"direction": Position.bottom, "start": 16, "stop": 32})
|
||||
if mote3 == "true":
|
||||
channels.update({"3": {"channel": 3, "invert": mote3_inv}})
|
||||
# TOP LEFT
|
||||
if mote3_pos == 0:
|
||||
channels.get("3").update({"direction": Position.top, "start": 0, "stop": 16})
|
||||
# TOP MIDDLE
|
||||
elif mote3_pos == 1:
|
||||
channels.get("3").update({"direction": Position.top, "start": 8, "stop": 24})
|
||||
# TOP RIGHT
|
||||
elif mote3_pos == 2:
|
||||
channels.get("3").update({"direction": Position.top, "start": 16, "stop": 32})
|
||||
# SIDE LEFT TOP
|
||||
elif mote3_pos == 3:
|
||||
channels.get("3").update({"direction": Position.left, "start": 0, "stop": 16})
|
||||
# SIDE LEFT MIDDLE
|
||||
elif mote3_pos == 4:
|
||||
channels.get("3").update({"direction": Position.left, "start": 8, "stop": 24})
|
||||
# SIDE LEFT BOTTOM
|
||||
elif mote3_pos == 5:
|
||||
channels.get("3").update({"direction": Position.left, "start": 16, "stop": 32})
|
||||
# SIDE RIGHT TOP
|
||||
elif mote3_pos == 6:
|
||||
channels.get("3").update({"direction": Position.right, "start": 0, "stop": 16})
|
||||
# SIDE RIGHT MIDDLE
|
||||
elif mote3_pos == 7:
|
||||
channels.get("3").update({"direction": Position.right, "start": 8, "stop": 24})
|
||||
# SIDE RIGHT BOTTOM
|
||||
elif mote3_pos == 8:
|
||||
channels.get("3").update({"direction": Position.right, "start": 16, "stop": 32})
|
||||
# BOTTOM LEFT
|
||||
elif mote3_pos == 9:
|
||||
channels.get("3").update({"direction": Position.bottom, "start": 0, "stop": 16})
|
||||
# BOTTOM MIDDLE
|
||||
elif mote3_pos == 10:
|
||||
channels.get("3").update({"direction": Position.bottom, "start": 8, "stop": 24})
|
||||
# BOTTOM RIGHT
|
||||
elif mote3_pos == 11:
|
||||
channels.get("3").update({"direction": Position.bottom, "start": 16, "stop": 32})
|
||||
if mote4 == "true":
|
||||
channels.update({"4": {"channel": 4, "invert": mote4_inv}})
|
||||
# TOP LEFT
|
||||
if mote4_pos == 0:
|
||||
channels.get("4").update({"direction": Position.top, "start": 0, "stop": 16})
|
||||
# TOP MIDDLE
|
||||
elif mote4_pos == 1:
|
||||
channels.get("4").update({"direction": Position.top, "start": 8, "stop": 24})
|
||||
# TOP RIGHT
|
||||
elif mote4_pos == 2:
|
||||
channels.get("4").update({"direction": Position.top, "start": 16, "stop": 32})
|
||||
# SIDE LEFT TOP
|
||||
elif mote4_pos == 3:
|
||||
channels.get("4").update({"direction": Position.left, "start": 0, "stop": 16})
|
||||
# SIDE LEFT MIDDLE
|
||||
elif mote4_pos == 4:
|
||||
channels.get("4").update({"direction": Position.left, "start": 8, "stop": 24})
|
||||
# SIDE LEFT BOTTOM
|
||||
elif mote4_pos == 5:
|
||||
channels.get("4").update({"direction": Position.left, "start": 16, "stop": 32})
|
||||
# SIDE RIGHT TOP
|
||||
elif mote4_pos == 6:
|
||||
channels.get("4").update({"direction": Position.right, "start": 0, "stop": 16})
|
||||
# SIDE RIGHT MIDDLE
|
||||
elif mote4_pos == 7:
|
||||
channels.get("4").update({"direction": Position.right, "start": 8, "stop": 24})
|
||||
# SIDE RIGHT BOTTOM
|
||||
elif mote4_pos == 8:
|
||||
channels.get("4").update({"direction": Position.right, "start": 16, "stop": 32})
|
||||
# BOTTOM LEFT
|
||||
elif mote4_pos == 9:
|
||||
channels.get("4").update({"direction": Position.bottom, "start": 0, "stop": 16})
|
||||
# BOTTOM MIDDLE
|
||||
elif mote4_pos == 10:
|
||||
channels.get("4").update({"direction": Position.bottom, "start": 8, "stop": 24})
|
||||
# BOTTOM RIGHT
|
||||
elif mote4_pos == 11:
|
||||
channels.get("4").update({"direction": Position.bottom, "start": 16, "stop": 32})
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
monitor = xbmc.Monitor()
|
||||
|
||||
if mode == 0:
|
||||
if color == 0:
|
||||
clear_mote()
|
||||
for c in channels.items():
|
||||
for pixel in range(NUM_PIXEL):
|
||||
set_static_color(channels, pixel, colors[color][0], colors[color][1], colors[color][2], brightness)
|
||||
mote.show()
|
||||
|
||||
elif mode == 1:
|
||||
while not monitor.abortRequested():
|
||||
if monitor.waitForAbort(0.6):
|
||||
break
|
||||
|
||||
capture = xbmc.RenderCapture()
|
||||
capture.capture(width, height)
|
||||
pixels = capture.getImage(1000)
|
||||
|
||||
if not pixels:
|
||||
if color == 0:
|
||||
clear_mote()
|
||||
else:
|
||||
for pixel in range(NUM_PIXEL):
|
||||
set_static_color(channels, pixel, colors[color][0], colors[color][1], colors[color][2], brightness)
|
||||
mote.show()
|
||||
else:
|
||||
image = Image.frombytes("RGBA", (width, height), str(pixels), "raw", "BGRA")
|
||||
|
||||
for k, v in channels.items():
|
||||
channel = v["channel"]
|
||||
|
||||
invert = v["invert"]
|
||||
inv_index = 0
|
||||
if invert == "true":
|
||||
inv_index = 15
|
||||
|
||||
direction = v["direction"]
|
||||
start = v["start"]
|
||||
stop = v["stop"]
|
||||
|
||||
if direction == Position.top:
|
||||
index = 0
|
||||
while start < stop:
|
||||
pixel = image.getpixel((0, start))
|
||||
mote.set_pixel(channel, abs(index-inv_index), pixel[0], pixel[1], pixel[2], brightness)
|
||||
start += 1
|
||||
index += 1
|
||||
if direction == Position.left:
|
||||
index = 0
|
||||
while start < stop:
|
||||
pixel = image.getpixel((start, 0))
|
||||
mote.set_pixel(channel, abs(index-inv_index), pixel[0], pixel[1], pixel[2], brightness)
|
||||
start += 1
|
||||
index += 1
|
||||
if direction == Position.right:
|
||||
index = 0
|
||||
while start < stop:
|
||||
pixel = image.getpixel((width-1, start))
|
||||
mote.set_pixel(channel, abs(index-inv_index), pixel[0], pixel[1], pixel[2], brightness)
|
||||
start += 1
|
||||
index += 1
|
||||
if direction == Position.bottom:
|
||||
index = 0
|
||||
while start < stop:
|
||||
pixel = image.getpixel((start, height-1))
|
||||
mote.set_pixel(channel, abs(index-inv_index), pixel[0], pixel[1], pixel[2], brightness)
|
||||
start += 1
|
||||
index += 1
|
||||
mote.show()
|
||||
clear_mote()
|
|
@ -0,0 +1,28 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<addon id="script.service.biaslightmote" name="Bias lighting for Pimoroni Mote" version="0.1.0" provider-name="snafu">
|
||||
<requires>
|
||||
<import addon="xbmc.python" version="2.26.0"/>
|
||||
<import addon="script.module.pyserial"/>
|
||||
<import addon="script.module.pil"/>
|
||||
</requires>
|
||||
<extension point="xbmc.service" library="addon.py" />
|
||||
<extension point="xbmc.addon.metadata">
|
||||
<summary lang="en_GB">Bias lighting for your Kodi media center using Pimoroni Mote lights</summary>
|
||||
<description lang="en_GB">
|
||||
Bias lighting for your Kodi media center using Pimoroni Mote lights.
|
||||
Picture credit:
|
||||
Stephan Legachev (https://commons.wikimedia.org/wiki/File:Ambilight-2.jpg), „Ambilight-2“, https://creativecommons.org/licenses/by/3.0/legalcode
|
||||
</description>
|
||||
<platform>all</platform>
|
||||
<license>MIT</license>
|
||||
<website>https://hoogi.eu</website>
|
||||
<email>inbox-kodi@hoogi.eu</email>
|
||||
<source>https://git.hoogi.eu/snafu/script.service.biaslightmote</source>
|
||||
<news>v0.1.0
|
||||
- initial release</news>
|
||||
<assets>
|
||||
<icon>resources/icon.png</icon>
|
||||
<fanart>resources/fanart.jpg</fanart>
|
||||
</assets>
|
||||
</extension>
|
||||
</addon>
|
Binary file not shown.
Binary file not shown.
After Width: | Height: | Size: 297 KiB |
Binary file not shown.
After Width: | Height: | Size: 316 KiB |
|
@ -0,0 +1 @@
|
|||
This folder will be the home of your translations
|
|
@ -0,0 +1,162 @@
|
|||
# Kodi Media Center language file
|
||||
# Addon Name: Pimote
|
||||
# Addon id: script.service.pimote
|
||||
# Addon Provider: snafu
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: XBMC Addons\n"
|
||||
"Report-Msgid-Bugs-To: alanwww1@xbmc.org\n"
|
||||
"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: Kodi Translation Team\n"
|
||||
"Language-Team: English (http://www.transifex.com/projects/p/xbmc-addons/language/en/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: en\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
msgctxt "#30000"
|
||||
msgid "Mote general"
|
||||
msgstr ""
|
||||
|
||||
|
||||
msgctxt "#30201"
|
||||
msgid "Activate Mote 1 Channel"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#30202"
|
||||
msgid "Activate Mote 2 Channel"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#30203"
|
||||
msgid "Activate Mote 3 Channel"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#30204"
|
||||
msgid "Activate Mote 4 Channel"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#30210"
|
||||
msgid "Position"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#30211"
|
||||
msgid "Top Left"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#30212"
|
||||
msgid "Top Middle"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#30213"
|
||||
msgid "Top Right"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#30214"
|
||||
msgid "Side Left Top"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#30215"
|
||||
msgid "Side Left Middle"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#30216"
|
||||
msgid "Side Left Bottom"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#30217"
|
||||
msgid "Side Right Top"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#30218"
|
||||
msgid "Side Right Middle"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#30219"
|
||||
msgid "Side Right Bottom"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#30220"
|
||||
msgid "Bottom Left"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#30221"
|
||||
msgid "Bottom Middle"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#30222"
|
||||
msgid "Bottom Right"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#30230"
|
||||
msgid "Invert"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#30900"
|
||||
msgid "Clear Mote"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#31000"
|
||||
msgid "Mote colors"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#31100"
|
||||
msgid "Mode"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#31101"
|
||||
msgid "Static color"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#31102"
|
||||
msgid "Bias lighting"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#31110"
|
||||
msgid "Color (static or when capture is not available)"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#31111"
|
||||
msgid "Red"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#31112"
|
||||
msgid "Green"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#31113"
|
||||
msgid "Blue"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#31114"
|
||||
msgid "Yellow"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#31115"
|
||||
msgid "White"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#31116"
|
||||
msgid "Custom"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#31117"
|
||||
msgid "None"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#31121"
|
||||
msgid "Red"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#31122"
|
||||
msgid "Green"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#31123"
|
||||
msgid "Blue"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#31130"
|
||||
msgid "Brightness"
|
||||
msgstr ""
|
Binary file not shown.
|
@ -0,0 +1,295 @@
|
|||
import time
|
||||
from sys import exit
|
||||
|
||||
try:
|
||||
import serial
|
||||
import serial.tools.list_ports
|
||||
except ImportError:
|
||||
print("This library requires the serial module\nInstall with: sudo pip install pyserial")
|
||||
|
||||
__version__ = '0.0.4'
|
||||
|
||||
VID = 0x16d0
|
||||
PID = 0x08c4
|
||||
NAME = 'Mote USB Dock'
|
||||
|
||||
MAX_PIXELS = 512
|
||||
MAX_PIXELS_PER_CHANNEL = int(MAX_PIXELS / 4)
|
||||
|
||||
class Mote:
|
||||
"""Represents a connected Mote device, communicating over USB serial.
|
||||
The Mote class allows you to configure the 4 channels and set individual pixels.
|
||||
It will attach to the first available Mote device unless `port_name` is specified at init.
|
||||
:param port_name: Override auto-detect and specify an explicit port to use. Must be a complete path ie: /dev/tty.usbmodem1234 (default None)
|
||||
"""
|
||||
|
||||
def __init__(self, port_name=None, white_point=(1.0, 1.0, 1.0)):
|
||||
self.port_name = port_name
|
||||
self._channel_count = 4
|
||||
self._channels = [None] * self._channel_count
|
||||
self._channel_flags = [0] * self._channel_count
|
||||
self._clear_on_exit = False
|
||||
|
||||
self.white_point = white_point
|
||||
|
||||
if self.port_name is None:
|
||||
self.port_name = self._find_serial_port(VID, PID, NAME)
|
||||
|
||||
if self.port_name is None:
|
||||
raise IOError("Unable to find Mote device")
|
||||
|
||||
self.port = serial.Serial(self.port_name, 115200, timeout=1)
|
||||
|
||||
def _find_serial_port(self, vid, pid, name):
|
||||
"""Find a serial port by VID, PID and text name
|
||||
:param vid: USB vendor ID to locate
|
||||
:param pid: USB product ID to locate
|
||||
:param name: USB device name to find where VID/PID match fails
|
||||
"""
|
||||
|
||||
check_for = "USB VID:PID={vid:04x}:{pid:04x}".format(vid=vid, pid=pid).upper()
|
||||
ports = serial.tools.list_ports.comports()
|
||||
|
||||
for check_port in ports:
|
||||
if hasattr(serial.tools, 'list_ports_common'):
|
||||
if (check_port.vid, check_port.pid) == (VID, PID):
|
||||
return check_port.device
|
||||
continue
|
||||
|
||||
if check_for in check_port[2].upper() or name == check_port[1]:
|
||||
return check_port[0]
|
||||
|
||||
return None
|
||||
|
||||
def configure_channel(self, channel, num_pixels, gamma_correction=False):
|
||||
"""Configure a channel
|
||||
:param channel: Channel, either 1, 2, 3 or 4 corresponding to numbers on Mote
|
||||
:param num_pixels: Number of pixels to configure for this channel
|
||||
:param gamma_correction: Whether to enable gamma correction (default False)
|
||||
"""
|
||||
|
||||
if channel > self._channel_count or channel < 1:
|
||||
raise ValueError("Channel index must be between 1 and {}".format(self._channel_count))
|
||||
if num_pixels > MAX_PIXELS_PER_CHANNEL:
|
||||
raise ValueError("Number of pixels can not be more than {max}".format(max=MAX_PIXELS_PER_CHANNEL))
|
||||
|
||||
self._channels[channel - 1] = [(0, 0, 0, 1.0)] * num_pixels
|
||||
self._channel_flags[channel - 1] = 1 if gamma_correction else 0
|
||||
|
||||
buf = bytearray()
|
||||
buf.append(channel)
|
||||
buf.append(num_pixels)
|
||||
buf.append(self._channel_flags[channel - 1])
|
||||
|
||||
self.port.write(b'mote')
|
||||
self.port.write(b'c')
|
||||
self.port.write(buf)
|
||||
|
||||
def get_pixel_count(self, channel):
|
||||
"""Get the number of pixels a channel is configured to use
|
||||
:param channel: Channel, either 1, 2 3 or 4 corresponding to numbers on Mote
|
||||
"""
|
||||
|
||||
if channel > self._channel_count or channel < 1:
|
||||
raise ValueError("Channel index must be between 1 and 4")
|
||||
if self._channels[channel - 1] is None:
|
||||
raise ValueError("Channel {channel} has not been set up".format(channel=channel))
|
||||
|
||||
return len(self._channels[channel - 1])
|
||||
|
||||
def get_pixel(self, channel, index):
|
||||
"""Get the RGB colour of a single pixel, on a single channel
|
||||
:param channel: Channel, either 1, 2, 3 or 4 corresponding to numbers on Mote
|
||||
:param index: Index of pixel to set, from 0 up
|
||||
"""
|
||||
|
||||
if channel > self._channel_count or channel < 1:
|
||||
raise ValueError("Channel index must be between 1 and 4")
|
||||
|
||||
if self._channels[channel - 1] is None:
|
||||
raise ValueError("Please set up channel {channel} before using it".format(channel=channel))
|
||||
|
||||
if index >= len(self._channels[channel - 1]):
|
||||
raise ValueError("Pixel index must be < {length}".format(length=len(self._channels[channel - 1])))
|
||||
|
||||
return self._channels[channel - 1][index]
|
||||
|
||||
def set_white_point(self, r, g, b):
|
||||
"""Set the white point.
|
||||
:param r: Red amount, from 0.0 to 1.0
|
||||
:param g: Green amount, from 0.0 to 1.0
|
||||
:param b: Blue amount, from 0.0 to 1.0
|
||||
"""
|
||||
|
||||
self.white_point = (r, g, b)
|
||||
|
||||
def set_pixel(self, channel, index, r, g, b, brightness=None):
|
||||
"""Set the RGB colour of a single pixel, on a single channel
|
||||
:param channel: Channel, either 1, 2, 3 or 4 corresponding to numbers on Mote
|
||||
:param index: Index of pixel to set, from 0 up
|
||||
:param r: Amount of red: 0-255
|
||||
:param g: Amount of green: 0-255
|
||||
:param b: Amount of blue: 0-255
|
||||
:param brightness: (Optional) brightness of pixel from 0.0 to 1.0. Will scale R,G,B accordingly.
|
||||
"""
|
||||
|
||||
if channel > self._channel_count or channel < 1:
|
||||
raise ValueError("Channel index must be between 1 and 4")
|
||||
|
||||
if self._channels[channel - 1] is None:
|
||||
raise ValueError("Please set up channel {channel} before using it!".format(channel=channel))
|
||||
|
||||
if index >= len(self._channels[channel - 1]):
|
||||
raise ValueError("Pixel index must be < {length}".format(length=len(self._channels[channel - 1])))
|
||||
|
||||
if brightness is None and self._channels[channel - 1][index] is not None:
|
||||
brightness = self._channels[channel - 1][index][3]
|
||||
|
||||
self._channels[channel - 1][index] = (r & 0xff, g & 0xff, b & 0xff, brightness)
|
||||
|
||||
def clear(self, channel=None):
|
||||
"""Clear the buffer of a specific or all channels
|
||||
:param channel: If set, clear a specific channel, otherwise all (default None)
|
||||
"""
|
||||
|
||||
if channel is None:
|
||||
for index, data in enumerate(self._channels):
|
||||
if data is not None:
|
||||
self.clear(index + 1)
|
||||
return
|
||||
|
||||
if channel > self._channel_count or channel < 1:
|
||||
raise ValueError("Channel index must be between 1 and 4")
|
||||
|
||||
if self._channels[channel - 1] is None:
|
||||
raise ValueError("Please set up channel {channel} before using it!".format(channel=channel))
|
||||
|
||||
self._channels[channel - 1] = [(0, 0, 0, 1.0)] * len(self._channels[channel - 1])
|
||||
|
||||
def show(self):
|
||||
"""Send the pixel buffer to the hardware"""
|
||||
|
||||
buf = bytearray()
|
||||
for index, data in enumerate(self._channels):
|
||||
if data is None: continue
|
||||
|
||||
for pixel in data:
|
||||
r, g, b, brightness = pixel
|
||||
r, g, b = [int(x * brightness * self.white_point[i]) for i, x in enumerate([r, g, b])]
|
||||
buf.append(b)
|
||||
buf.append(g)
|
||||
buf.append(r)
|
||||
|
||||
self.port.write(b'mote')
|
||||
self.port.write(b'o')
|
||||
self.port.write(buf)
|
||||
|
||||
def set_all(self, r, g, b, brightness=None, channel=None):
|
||||
"""Set the RGB value and optionally brightness of all pixels
|
||||
If you don't supply a brightness value, the last value set for each pixel be kept.
|
||||
:param r: Amount of red: 0 to 255
|
||||
:param g: Amount of green: 0 to 255
|
||||
:param b: Amount of blue: 0 to 255
|
||||
:param brightness: Brightness: 0.0 to 1.0 (default around 0.2)
|
||||
:param channel: Optional channel: 1, 2, 3 or 4 (default to all)
|
||||
"""
|
||||
|
||||
if channel in range(1, self._channel_count + 1):
|
||||
for x in range(NUM_PIXELS_PER_CHANNEL):
|
||||
self.set_pixel(channel, x, r, g, b, brightness)
|
||||
return
|
||||
|
||||
for c in range(1, self._channel_count + 1):
|
||||
if self._channels[c - 1] is None:
|
||||
continue
|
||||
for x in range(len(self._channels[c - 1])):
|
||||
self.set_pixel(c, x, r, g, b, brightness)
|
||||
|
||||
def set_brightness(self, brightness):
|
||||
"""Set the brightness of all pixels
|
||||
This sets the brightness of each pixel individually, it can be overriden
|
||||
by supplying the brightness argument to set_pixel.
|
||||
:param brightness: Brightness: 0.0 to 1.0
|
||||
|
||||
"""
|
||||
|
||||
if not 0.0 <= brightness <= 1.0:
|
||||
raise ValueError("Brightness should be between 0.0 and 1.0, was: {}".format(brightness))
|
||||
|
||||
for channel in range(self._channel_count):
|
||||
pixels = self.get_pixel_count(channel + 1)
|
||||
for pixel in range(pixels):
|
||||
r, g, b, br = self._channels[channel][pixel]
|
||||
self._channels[channel][pixel] = (r, g, b, brightness)
|
||||
|
||||
def set_clear_on_exit(self, value=True):
|
||||
"""Set whether Mote should be cleared upon exit.
|
||||
By default Mote will leave the pixels on upon exit, but calling::
|
||||
mote.set_clear_on_exit()
|
||||
Will ensure that they are cleared.
|
||||
|
||||
"""
|
||||
|
||||
self._clear_on_exit = value
|
||||
|
||||
def __exit__(self):
|
||||
if self._clear_on_exit:
|
||||
self.clear()
|
||||
|
||||
self.port.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from colorsys import hsv_to_rgb
|
||||
|
||||
mote = Mote()
|
||||
|
||||
num_pixels = 16
|
||||
|
||||
mote.configure_channel(1, num_pixels, False)
|
||||
mote.configure_channel(2, num_pixels, False)
|
||||
mote.configure_channel(3, num_pixels, False)
|
||||
mote.configure_channel(4, num_pixels, False)
|
||||
|
||||
colors = [
|
||||
(255, 0, 0),
|
||||
(0, 255, 0),
|
||||
(0, 0, 255),
|
||||
(255, 255, 255)
|
||||
]
|
||||
|
||||
try:
|
||||
for step in range(4):
|
||||
for channel in range(4):
|
||||
for pixel in range(num_pixels):
|
||||
r, g, b = colors[channel]
|
||||
mote.set_pixel(channel + 1, pixel, r, g, b)
|
||||
mote.show()
|
||||
time.sleep(0.01)
|
||||
|
||||
colors.append(colors.pop(0))
|
||||
|
||||
for step in range(170):
|
||||
for channel in range(4):
|
||||
for pixel in range(num_pixels):
|
||||
r, g, b, z = [int(c * 0.99) for c in mote.get_pixel(channel + 1, pixel)]
|
||||
mote.set_pixel(channel + 1, pixel, r, g, b)
|
||||
|
||||
time.sleep(0.001)
|
||||
mote.show()
|
||||
|
||||
brightness = 0
|
||||
for h in range(10000):
|
||||
for channel in range(4):
|
||||
for pixel in range(num_pixels):
|
||||
hue = (h + (channel * num_pixels * 4) + (pixel * 4)) % 360
|
||||
r, g, b = [int(c * brightness) for c in hsv_to_rgb(hue / 360.0, 1.0, 1.0)]
|
||||
mote.set_pixel(channel + 1, pixel, r, g, b)
|
||||
mote.show()
|
||||
time.sleep(0.01)
|
||||
if brightness < 255: brightness += 1
|
||||
|
||||
except KeyboardInterrupt:
|
||||
mote.clear()
|
||||
mote.show()
|
||||
time.sleep(0.1)
|
Binary file not shown.
|
@ -0,0 +1,26 @@
|
|||
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
|
||||
<settings>
|
||||
<category label="30000">
|
||||
<setting label="30201" type="bool" id="mote1" default="false"/>
|
||||
<setting id="mote1_position" type="select" label="30201" lvalues="30211|30212|30213|30214|30215|30216|30217|30218|30219|30220|30221|30222" subsetting="true" enable="eq(-1,true)" default="0" />
|
||||
<setting label="30230" type="bool" id="mote1_invert" default="false" enable="eq(-2,true)" subsetting="true" />
|
||||
<setting label="30202" type="bool" id="mote2" default="false"/>
|
||||
<setting id="mote2_position" type="select" label="30201" lvalues="30211|30212|30213|30214|30215|30216|30217|30218|30219|30220|30221|30222" subsetting="true" enable="eq(-1,true)" default="2" />
|
||||
<setting label="30230" type="bool" id="mote2_invert" default="false" enable="eq(-2,true)" subsetting="true" />
|
||||
<setting label="30203" type="bool" id="mote3" default="false"/>
|
||||
<setting id="mote3_position" type="select" label="30201" lvalues="30211|30212|30213|30214|30215|30216|30217|30218|30219|30220|30221|30222" subsetting="true" enable="eq(-1,true)" default="5" />
|
||||
<setting label="30230" type="bool" id="mote3_invert" default="false" enable="eq(-2,true)" subsetting="true" />
|
||||
<setting label="30204" type="bool" id="mote4" default="false"/>
|
||||
<setting id="mote4_position" type="select" label="30201" lvalues="30211|30212|30213|30214|30215|30216|30217|30218|30219|30220|30221|30222" subsetting="true" enable="eq(-1,true)" default="8" />
|
||||
<setting label="30230" type="bool" id="mote4_invert" default="false" enable="eq(-2,true)" subsetting="true"/>
|
||||
<setting label="30900" type="action" action="RunScript(script.service.pimote, clear_mote)"/>
|
||||
</category>
|
||||
<category label="31000">
|
||||
<setting label="31100" type="select" id="mode" lvalues="31101|31102" default="0"/>
|
||||
<setting label="31110" type="select" id="color" lvalues="31117|31111|31112|31113|31114|31115|31116" default="0" subsetting="true" />
|
||||
<setting label="31121" type="slider" id="color_custom_red" range="0,255" option="int" default="0" subsetting="true" visible="eq(-1,6)" enable="eq(-1,6)" />
|
||||
<setting label="31122" type="slider" id="color_custom_green" range="0,255" option="int" default="0" subsetting="true" visible="eq(-2,6)" enable="eq(-2,6)" />
|
||||
<setting label="31123" type="slider" id="color_custom_blue" range="0,255" option="int" default="0" subsetting="true" visible="eq(-3,6)" enable="eq(-3,6)" />
|
||||
<setting label="31130" type="slider" id="brightness" default="100" range="0,100" option="int" />
|
||||
</category>
|
||||
</settings>
|
Binary file not shown.
Loading…
Reference in New Issue