├── .gitmodules ├── DmxController ├── run_dmx.cmd ├── run_dmx.ps1 ├── pretty_print.py ├── findEthernetDMX.py ├── internet.py ├── dmx_mock.py └── lights_off.py ├── AdaKioskService ├── Setup │ ├── sysadmin.cmd │ ├── AdaKioskUnitTest.cmd │ ├── ClearAssignedAccess.ps1 │ ├── DisableAutoUpgrade.reg │ ├── SetupAssignedAccess.ps1 │ ├── InstallService.ps1 │ ├── AssignedAccessConfig.xsd │ ├── AssignedAccess2020Config.xsd │ ├── AssignedAccessProfile.xml │ └── PublishBinaries.py ├── Version │ └── Version.props ├── ProjectInstaller.cs ├── build.cmd ├── Program.cs ├── AdaKioskService.sln ├── packages.config ├── ServiceLog.cs ├── AdaKioskService.Designer.cs ├── Properties │ └── AssemblyInfo.cs ├── ProjectInstaller.Designer.cs └── App.config ├── Server ├── flint.cmd ├── run_server.ps1 ├── find_network.ps1 ├── run_client.ps1 ├── internet.py ├── zone_map_3.json ├── zone_map_1.json ├── priority_queue.py ├── zone_map_2.json ├── ping_server.py ├── dump_emotions.py ├── color_test.py ├── data.json ├── hls_color.py ├── add_permissions.py ├── animation.py ├── main.css └── utilities.py ├── TeensyFirmware ├── .gitignore ├── build.png ├── install.png ├── datasheets │ └── WS2812B.pdf ├── platformio.ini ├── flash.py ├── TeensyFirmware.code-workspace ├── include │ ├── crc32.h │ ├── Status.h │ ├── Color.h │ ├── Timer.h │ └── Vector.h ├── flash.sh ├── PublishFirmware.py └── readme.md ├── design.png ├── Azure ├── readme.md ├── PublishFirmware.py └── connect_arc.ps1 ├── ada-feature.jpg ├── AdaKiosk ├── tools │ ├── xsl.exe │ ├── UpdateVersion.dll │ ├── UpdateVersion.exe │ ├── CleanupPublishFolder.dll │ ├── CleanupPublishFolder.exe │ ├── UpdateVersion.runtimeconfig.json │ ├── CleanupPublishFolder.runtimeconfig.json │ ├── UpdateVersion.deps.json │ └── CleanupPublishFolder.deps.json ├── images │ ├── home.png │ ├── control.png │ ├── kiosk.png │ └── simulation.png ├── Version │ ├── Version.xsl │ └── Version.props ├── App.xaml.cs ├── ColorTestWindow.xaml ├── LedControl.xaml ├── AssemblyInfo.cs ├── App.xaml ├── Controls │ ├── ScreenSaver.xaml │ ├── ContactPanel.xaml.cs │ ├── ContactPanel.xaml │ └── ScreenSaver.xaml.cs ├── Utilities │ ├── UiDispatcher.cs │ ├── BatteryInfo.cs │ ├── ZoneMap.cs │ └── ServerConfig.cs ├── readme.md ├── AdaKiosk.csproj ├── LedControl.xaml.cs ├── Properties │ └── PublishProfiles │ │ └── ClickOnceProfile.pubxml ├── publish.cmd ├── ColorTestWindow.xaml.cs └── MainWindow.xaml ├── KasaBridge ├── photo.png ├── run_bridge.ps1 ├── internet.py ├── readme.md ├── light_schedule.py └── bridge_client.py ├── .flake8 ├── TeensyUnitTest ├── example │ ├── test.png │ └── rainbow.png ├── ArduinoMock.cpp ├── TestWindow.h ├── MultiWS2812Mock.cpp ├── MultiWS2812Mock.h ├── Timer.h ├── TeensyUnitTest.sln ├── ArduinoMock.h └── readme.md ├── AdaServerRelay ├── images │ └── workflow.png ├── host.json ├── readme.md ├── Startup.cs ├── Properties │ └── PublishProfiles │ │ └── ada-server-functions - Zip Deploy.pubxml ├── AdaServerRelay.sln ├── AdaServerRelay.csproj └── index.html ├── run.cmd ├── DMX-Drivers └── readme.md ├── RpiController ├── build.cmd ├── Utils │ ├── crc32.h │ ├── Timer.h │ ├── Color.h │ ├── FileSystem.h │ └── StreamWriter.h ├── Ports │ ├── SocketInit.h │ ├── SocketInit.cpp │ ├── Port.h │ ├── UdpClientPort.h │ ├── SerialPort.h │ ├── TcpClientPort.h │ ├── linux │ │ └── LinuxFindSerialPorts.cpp │ └── Port.cpp ├── build.sh ├── run.sh ├── run.py ├── buildtools.py └── CMakeLists.txt ├── requirements.txt ├── updatepi.cmd ├── CODE_OF_CONDUCT.md ├── AdaKioskUnitTest ├── AdaKioskUnitTest.csproj ├── readme.md ├── AdaKioskUnitTest.sln └── Program.cs ├── Ada-Serial-Test ├── Makefile └── Ada SerialTest.sln ├── HistoricalData ├── summarize.py ├── Grouped_by_Hour_3FKCamera01.csv ├── Grouped_by_Hour_3FECamera01.csv ├── Grouped_by_Hour_4FKCamera02.csv ├── Grouped_by_Hour_4FECamera02.csv ├── Grouped_by_Hour_3FECamera02.csv ├── Grouped_by_Hour_3FKCamera02.csv └── Grouped_by_Hour_2FKCamera01.csv ├── LICENSE ├── SUPPORT.md ├── AdaWebPubSub └── test.py └── SECURITY.md /.gitmodules: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /DmxController/run_dmx.cmd: -------------------------------------------------------------------------------- 1 | pwsh -f run_dmx.ps1 2 | -------------------------------------------------------------------------------- /AdaKioskService/Setup/sysadmin.cmd: -------------------------------------------------------------------------------- 1 | psexec -i -s cmd.exe -------------------------------------------------------------------------------- /Server/flint.cmd: -------------------------------------------------------------------------------- 1 | black --line-length 120 . 2 | isort . 3 | flake8 . -------------------------------------------------------------------------------- /TeensyFirmware/.gitignore: -------------------------------------------------------------------------------- 1 | .pioenvs 2 | .piolibdeps 3 | .pio 4 | .vscode -------------------------------------------------------------------------------- /design.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/ada/main/design.png -------------------------------------------------------------------------------- /Azure/readme.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/ada/main/Azure/readme.md -------------------------------------------------------------------------------- /ada-feature.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/ada/main/ada-feature.jpg -------------------------------------------------------------------------------- /AdaKiosk/tools/xsl.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/ada/main/AdaKiosk/tools/xsl.exe -------------------------------------------------------------------------------- /KasaBridge/photo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/ada/main/KasaBridge/photo.png -------------------------------------------------------------------------------- /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | ignore = E111,E402,E722,W503,W504,F405,F403 3 | max-line-length = 120 4 | -------------------------------------------------------------------------------- /AdaKiosk/images/home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/ada/main/AdaKiosk/images/home.png -------------------------------------------------------------------------------- /TeensyFirmware/build.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/ada/main/TeensyFirmware/build.png -------------------------------------------------------------------------------- /AdaKiosk/images/control.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/ada/main/AdaKiosk/images/control.png -------------------------------------------------------------------------------- /AdaKiosk/images/kiosk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/ada/main/AdaKiosk/images/kiosk.png -------------------------------------------------------------------------------- /TeensyFirmware/install.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/ada/main/TeensyFirmware/install.png -------------------------------------------------------------------------------- /AdaKiosk/images/simulation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/ada/main/AdaKiosk/images/simulation.png -------------------------------------------------------------------------------- /AdaKiosk/tools/UpdateVersion.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/ada/main/AdaKiosk/tools/UpdateVersion.dll -------------------------------------------------------------------------------- /AdaKiosk/tools/UpdateVersion.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/ada/main/AdaKiosk/tools/UpdateVersion.exe -------------------------------------------------------------------------------- /AdaKioskService/Setup/AdaKioskUnitTest.cmd: -------------------------------------------------------------------------------- 1 | %~dp0..\..\AdaKioskUnitTest\bin\Debug\net5.0\AdaKioskUnitTest.exe 2 | -------------------------------------------------------------------------------- /DmxController/run_dmx.ps1: -------------------------------------------------------------------------------- 1 | $Host.UI.RawUI.WindowTitle = "DMX Controller" 2 | &python.exe dmx_control.py --port com3 -------------------------------------------------------------------------------- /TeensyUnitTest/example/test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/ada/main/TeensyUnitTest/example/test.png -------------------------------------------------------------------------------- /AdaServerRelay/images/workflow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/ada/main/AdaServerRelay/images/workflow.png -------------------------------------------------------------------------------- /TeensyUnitTest/example/rainbow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/ada/main/TeensyUnitTest/example/rainbow.png -------------------------------------------------------------------------------- /TeensyFirmware/datasheets/WS2812B.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/ada/main/TeensyFirmware/datasheets/WS2812B.pdf -------------------------------------------------------------------------------- /AdaKiosk/tools/CleanupPublishFolder.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/ada/main/AdaKiosk/tools/CleanupPublishFolder.dll -------------------------------------------------------------------------------- /AdaKiosk/tools/CleanupPublishFolder.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/ada/main/AdaKiosk/tools/CleanupPublishFolder.exe -------------------------------------------------------------------------------- /run.cmd: -------------------------------------------------------------------------------- 1 | pushd %~dp0 2 | call conda activate ada 3 | 4 | pwsh -f Server\find_network.ps1 5 | 6 | pushd Server 7 | start pwsh -f run_server.ps1 8 | popd 9 | -------------------------------------------------------------------------------- /KasaBridge/run_bridge.ps1: -------------------------------------------------------------------------------- 1 | $Host.UI.RawUI.WindowTitle = "Kasa Power Bridge" 2 | # ensure C:\Users\Administrator\.conda\envs\ada\ is in the path 3 | &python bridge.py -------------------------------------------------------------------------------- /Server/run_server.ps1: -------------------------------------------------------------------------------- 1 | $Host.UI.RawUI.WindowTitle = "Ada Server" 2 | # ensure C:\Users\Administrator\.conda\envs\ada\ is in the path 3 | &python ada_server.py --loop -------------------------------------------------------------------------------- /DMX-Drivers/readme.md: -------------------------------------------------------------------------------- 1 | 2 | http://www.dmxking.com/downloads/eDMX2%20PRO%20User%20Manual%20(EN).pdf 3 | 4 | http://www.dmxking.com/downloads-list 5 | 6 | https://pages.productinfo.io/DMXking?m=qr -------------------------------------------------------------------------------- /AdaKiosk/tools/UpdateVersion.runtimeconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "runtimeOptions": { 3 | "tfm": "net7.0", 4 | "framework": { 5 | "name": "Microsoft.NETCore.App", 6 | "version": "7.0.0" 7 | } 8 | } 9 | } -------------------------------------------------------------------------------- /RpiController/build.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | cd %~dp0 3 | if not exist build mkdir build 4 | cd build 5 | cmake -G "Visual Studio 16 2019" -Thost=x64 -A x64 .. 6 | cmake --build . --config Release -- /m /verbosity:minimal -------------------------------------------------------------------------------- /AdaKiosk/tools/CleanupPublishFolder.runtimeconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "runtimeOptions": { 3 | "tfm": "net7.0", 4 | "framework": { 5 | "name": "Microsoft.NETCore.App", 6 | "version": "7.0.0" 7 | } 8 | } 9 | } -------------------------------------------------------------------------------- /TeensyFirmware/platformio.ini: -------------------------------------------------------------------------------- 1 | [env:teensy40] 2 | platform = teensy 3 | framework = arduino 4 | board = teensy40 5 | 6 | ; change microcontroller 7 | board_build.mcu = imxrt1062 8 | 9 | ; change MCU frequency 10 | board_build.f_cpu = 600000000L 11 | -------------------------------------------------------------------------------- /TeensyFirmware/flash.py: -------------------------------------------------------------------------------- 1 | import RPi.GPIO as GPIO 2 | 3 | # to use Raspberry Pi board pin numbers 4 | GPIO.setmode(GPIO.BOARD) 5 | 6 | # set up the GPIO channels - one input and one output 7 | GPIO.setup(7, GPIO.OUT) 8 | GPIO.output(7, GPIO.LOW) 9 | GPIO.setup(7, GPIO.IN) 10 | -------------------------------------------------------------------------------- /AdaServerRelay/host.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0", 3 | "logging": { 4 | "applicationInsights": { 5 | "samplingSettings": { 6 | "isEnabled": true, 7 | "excludedTypes": "Request" 8 | } 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /TeensyFirmware/TeensyFirmware.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": "." 5 | } 6 | ], 7 | "settings": { 8 | "files.associations": { 9 | "array": "cpp", 10 | "initializer_list": "cpp", 11 | "xstring": "cpp", 12 | "xutility": "cpp", 13 | "string": "cpp" 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /RpiController/Utils/crc32.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | #ifndef _CRC32_H 4 | #define _CRC32_H 5 | 6 | #include 7 | 8 | // calculate a checksum on a buffer, length = bytelength 9 | uint32_t crc32(uint8_t* buffer, uint32_t bytelength); 10 | 11 | #endif -------------------------------------------------------------------------------- /RpiController/Ports/SocketInit.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | #ifndef SERIAL_COM_SOCKETINIT_HPP 5 | #define SERIAL_COM_SOCKETINIT_HPP 6 | 7 | class SocketInit 8 | { 9 | static bool socket_initialized_; 10 | public: 11 | SocketInit(); 12 | }; 13 | 14 | #endif -------------------------------------------------------------------------------- /TeensyFirmware/include/crc32.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | #ifndef _CRC32_H 4 | #define _CRC32_H 5 | 6 | #include 7 | 8 | // calculate a checksum on a buffer, length = bytelength 9 | uint32_t crc32(uint8_t *buffer, uint32_t bytelength); 10 | 11 | #endif -------------------------------------------------------------------------------- /TeensyFirmware/include/Status.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | #ifndef _STATUS_H 4 | #define _STATUS_H 5 | 6 | struct TeensyStatus 7 | { 8 | int draws; 9 | int headers; 10 | int commands; 11 | }; 12 | 13 | extern TeensyStatus gTeensyStatus; 14 | 15 | #endif 16 | -------------------------------------------------------------------------------- /TeensyFirmware/flash.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cd ~/git/Ada/TeensyFirmware 4 | # using teensy_loader from https://github.com/PaulStoffregen/teensy_loader_cli 5 | /home/pi/git/teensy_loader_cli/teensy_loader_cli --mcu=imxrt1062 -w -v firmware.hex & 6 | sleep 1 7 | gpio mode 7 out;gpio write 7 0; gpio mode 7 in 8 | sleep 1 9 | 10 | -------------------------------------------------------------------------------- /RpiController/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | SOURCE="${BASH_SOURCE[0]}" 4 | echo "Building $(dirname $SOURCE)" 5 | cd "$( dirname "$SOURCE" )" 6 | 7 | if [[ ! -d "build" ]] 8 | then 9 | echo "Creating build directory" 10 | mkdir build 11 | cd build 12 | cmake .. 13 | cd .. 14 | fi 15 | 16 | 17 | cd build 18 | make 19 | cd .. 20 | 21 | -------------------------------------------------------------------------------- /AdaKiosk/Version/Version.xsl: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /DmxController/pretty_print.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. 2 | # Licensed under the MIT license. 3 | def print_info(message: str): 4 | print("==================================================") 5 | print(message) 6 | 7 | 8 | def print_error(message: str): 9 | print("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx") 10 | print(message) 11 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | azure-identity 2 | azure-keyvault-keys 3 | azure-keyvault-secrets 4 | azure-storage-blob 5 | azure-cosmosdb-table 6 | azure-data-tables 7 | azure-messaging-webpubsubservice 8 | aiohttp 9 | opencv-contrib-python 10 | coloredlogs 11 | comtypes 12 | imutils 13 | pyserial 14 | suntime 15 | paramiko 16 | websockets 17 | asyncio 18 | pandas 19 | types-paramiko 20 | 21 | -------------------------------------------------------------------------------- /Server/find_network.ps1: -------------------------------------------------------------------------------- 1 | 2 | while ($True) { 3 | $addr = Get-NetIPAddress -AddressFamily IPv4 | Where-Object -FilterScript { ($_.PrefixOrigin -Eq "Dhcp") -And (-Not ($_.IPAddress.StartsWith("192."))) } 4 | if ($addr -ne $Nul) { 5 | $ip = $addr.IPAddress 6 | Write-Host "Found network address: $ip" 7 | Exit 0 8 | } 9 | Write-Host "Sleeping 10 seconds waiting for network..." 10 | Sleep 10 11 | } 12 | -------------------------------------------------------------------------------- /updatepi.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | if %1=="" goto :noarg 3 | 4 | pushd %~dp0 5 | call "C:\ProgramData\Anaconda3\Scripts\activate.bat" Ada 6 | 7 | scp -r c:\git\Ada\RpiController pi@%1:/home/pi/git/Ada 8 | 9 | ssh pi@%1 -t chmod u+x /home/pi/git/Ada/RpiController/build.sh 10 | ssh pi@%1 -t chmod u+x /home/pi/git/Ada/RpiController/run.sh 11 | ssh pi@%1 -t /home/pi/git/Ada/RpiController/build.sh 12 | 13 | goto :eof 14 | 15 | :noarg 16 | echo Please specify name of pi to update. -------------------------------------------------------------------------------- /TeensyUnitTest/ArduinoMock.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | #include "ArduinoMock.h" 4 | #include 5 | #include 6 | 7 | SerialInput Serial;; 8 | 9 | void digitalWrite(int pin, int value) 10 | { 11 | 12 | } 13 | 14 | void pinMode(int pin, int mode) 15 | { 16 | 17 | } 18 | 19 | void delay(float seconds) 20 | { 21 | std::this_thread::sleep_for(std::chrono::milliseconds((long long)(seconds * 1000))); 22 | } -------------------------------------------------------------------------------- /AdaKiosk/Version/Version.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 0 5 | 1.0.10.0 6 | 1.0.10.0 7 | 1.0.10.0 8 | false 9 | Microsoft 10 | AdaKiosk 11 | 60 12 | 13 | 14 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Microsoft Open Source Code of Conduct 2 | 3 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 4 | 5 | Resources: 6 | 7 | - [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/) 8 | - [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) 9 | - Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns 10 | -------------------------------------------------------------------------------- /AdaKiosk/App.xaml.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Configuration; 6 | using System.Data; 7 | using System.Linq; 8 | using System.Threading.Tasks; 9 | using System.Windows; 10 | 11 | namespace AdaKiosk 12 | { 13 | /// 14 | /// Interaction logic for App.xaml 15 | /// 16 | public partial class App : Application 17 | { 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /AdaKioskService/Version/Version.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 0 5 | 1.0.10.0 6 | 1.0.10.0 7 | 1.0.10.0 8 | false 9 | Microsoft 10 | AdaKiosk 11 | 60 12 | 13 | 14 | -------------------------------------------------------------------------------- /AdaKioskService/Setup/ClearAssignedAccess.ps1: -------------------------------------------------------------------------------- 1 | # From https://docs.microsoft.com/en-us/windows/client-management/mdm/using-powershell-scripting-with-the-wmi-bridge-provider 2 | # and https://docs.microsoft.com/en-us/windows/win32/dmwmibridgeprov/mdm-assignedaccess 3 | $nameSpaceName="root\cimv2\mdm\dmmap" 4 | $className="MDM_AssignedAccess" 5 | $obj = Get-CimInstance -Namespace $namespaceName -ClassName $className 6 | Write-Host ($obj | Format-List | Out-String) 7 | $obj.Configuration = $null 8 | Set-CimInstance -CimInstance $obj -------------------------------------------------------------------------------- /RpiController/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | while : 3 | do 4 | if [ "`ping -c 1 ada-core`" ] 5 | then 6 | echo "network is available" 7 | break 8 | else 9 | echo "ping failed, waiting for network..." 10 | sleep 1 11 | fi 12 | done 13 | 14 | while true 15 | do 16 | ~/git/Ada/RpiController/build/bin/RpiController --autostart --firmware ~/git/Ada/TeensyFirmware/firmware.hex 17 | sleep 10 18 | pushd ~/git/Ada/TeensyFirmware 19 | ./flash.sh 20 | popd 21 | sleep 10 22 | done 23 | 24 | -------------------------------------------------------------------------------- /AdaKiosk/tools/UpdateVersion.deps.json: -------------------------------------------------------------------------------- 1 | { 2 | "runtimeTarget": { 3 | "name": ".NETCoreApp,Version=v7.0", 4 | "signature": "" 5 | }, 6 | "compilationOptions": {}, 7 | "targets": { 8 | ".NETCoreApp,Version=v7.0": { 9 | "UpdateVersion/1.0.0": { 10 | "runtime": { 11 | "UpdateVersion.dll": {} 12 | } 13 | } 14 | } 15 | }, 16 | "libraries": { 17 | "UpdateVersion/1.0.0": { 18 | "type": "project", 19 | "serviceable": false, 20 | "sha512": "" 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /AdaKioskService/ProjectInstaller.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.ComponentModel; 5 | using System.Configuration.Install; 6 | using System.Linq; 7 | using System.Threading.Tasks; 8 | 9 | namespace AdaKioskService 10 | { 11 | [RunInstaller(true)] 12 | public partial class ProjectInstaller : System.Configuration.Install.Installer 13 | { 14 | public ProjectInstaller() 15 | { 16 | InitializeComponent(); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /AdaKiosk/tools/CleanupPublishFolder.deps.json: -------------------------------------------------------------------------------- 1 | { 2 | "runtimeTarget": { 3 | "name": ".NETCoreApp,Version=v7.0", 4 | "signature": "" 5 | }, 6 | "compilationOptions": {}, 7 | "targets": { 8 | ".NETCoreApp,Version=v7.0": { 9 | "CleanupPublishFolder/1.0.0": { 10 | "runtime": { 11 | "CleanupPublishFolder.dll": {} 12 | } 13 | } 14 | } 15 | }, 16 | "libraries": { 17 | "CleanupPublishFolder/1.0.0": { 18 | "type": "project", 19 | "serviceable": false, 20 | "sha512": "" 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /AdaKioskUnitTest/AdaKioskUnitTest.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net7.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /AdaKioskService/build.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | pushd %~dp0 3 | msbuild /target:restore "/p:Platform=Any CPU" /p:Configuration=Release AdaKioskService.sln 4 | msbuild /p:Configuration=Release "/p:Platform=Any CPU" /p:Configuration=Release AdaKioskService.sln 5 | 6 | set serviceZipfile=bin\AdaKioskService.zip 7 | if exist %serviceZipfile% del %serviceZipfile% 8 | powershell -c "Compress-Archive -Path .\bin\Release\* -DestinationPath %serviceZipfile%" 9 | if ERRORLEVEL 1 goto :err_zip 10 | 11 | popd 12 | goto :eof 13 | 14 | 15 | :err_zip 16 | echo Error creating AdaKiosk.zip 17 | popd 18 | exit /b 1 -------------------------------------------------------------------------------- /AdaKioskUnitTest/readme.md: -------------------------------------------------------------------------------- 1 | # AdaKioskUnitTest 2 | 3 | This is a simple C# app that connects to the Azure Web Pub Sub service 4 | and prints out all messages being sent between the AdaKiosk 5 | and the Ada Server. You can also send your own adhoc messages from 6 | the command line, for example, to find out what version of AdaKiosk 7 | is running send this message `/kiosk/version/?` and the AdaKiosk 8 | should response with this: 9 | 10 | ```json 11 | {"type":"message","from":"group","fromUserId":"kiosk","group":"demogroup","dataType":"json","data": "/kiosk/version/1.0.0.29"} 12 | ``` 13 | 14 | -------------------------------------------------------------------------------- /AdaKioskService/Setup/DisableAutoUpgrade.reg: -------------------------------------------------------------------------------- 1 | Windows Registry Editor Version 5.00 2 | 3 | ;Created by Vishal Gupta for AskVG.com 4 | 5 | [HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate] 6 | "DisableOSUpgrade"=dword:00000001 7 | 8 | [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\OSUpgrade] 9 | "AllowOSUpgrade"=dword:00000000 10 | 11 | [HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\WindowsStore] 12 | "DisableOSUpgrade"=dword:00000001 13 | 14 | [HKEY_LOCAL_MACHINE\SYSTEM\Setup\UpgradeNotification] 15 | "UpgradeAvailable"=dword:00000000 16 | 17 | -------------------------------------------------------------------------------- /AdaKiosk/ColorTestWindow.xaml: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /Server/run_client.ps1: -------------------------------------------------------------------------------- 1 | param ( 2 | [Parameter(Mandatory=$true, 3 | HelpMessage="Name of the raspberry pi to connect to")] 4 | [string]$name 5 | ) 6 | $Host.UI.RawUI.WindowTitle = $name 7 | 8 | # wait for DHCP to kick in. 9 | $addr = $null 10 | while (-not $addr) { 11 | $addr = Get-NetIPAddress -AddressFamily IPv4 | Where-Object -FilterScript { ($_.PrefixOrigin -Eq "Dhcp") } 12 | } 13 | 14 | $out = $null 15 | while (-not $out) { 16 | $out = Test-Connection -ComputerName $name 17 | if ($out) { 18 | &ssh "pi@$name" -t "/home/pi/git/Ada/RpiController/run.sh" 19 | } 20 | Start-Sleep -s 10 21 | } -------------------------------------------------------------------------------- /AdaKiosk/LedControl.xaml: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /TeensyUnitTest/TestWindow.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | #pragma once 4 | 5 | #include 6 | #include "Bitmap.h" 7 | 8 | class TestWindow 9 | { 10 | public: 11 | TestWindow(); 12 | ~TestWindow(); 13 | void SetSize(int width, int height); 14 | void DrawBitmap(const Bitmap& bitmap); 15 | void Run(); // run the message loop 16 | void OnResize(int width, int height); 17 | void OnPaint(); 18 | void OnClose(); 19 | void Invalidate(); 20 | void Close(); 21 | void OnUpdateBitmap(); 22 | private: 23 | struct TestWindowImpl; 24 | TestWindowImpl* _impl; 25 | }; 26 | -------------------------------------------------------------------------------- /RpiController/run.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. 2 | # Licensed under the MIT license. 3 | import os 4 | import time 5 | import buildtools 6 | 7 | 8 | builder = buildtools.BuildTools() 9 | os.chdir("/home/pi/git/Ada/RpiController/build") 10 | builder.run(["git", "pull"]) 11 | builder.run("make") 12 | 13 | while True: 14 | os.chdir("/home/pi/git/Ada/RpiController/build/bin") 15 | rc = builder.run( 16 | ["/home/pi/git/Ada/RpiController/build/bin/RpiController", "--autostart"] 17 | ) 18 | if rc == 1: 19 | # recover or update the teensy by re-flashing it! 20 | builder.run("/home/pi/git/Ada/TeensyFirmware/flash.sh") 21 | time.sleep(5) 22 | -------------------------------------------------------------------------------- /KasaBridge/internet.py: -------------------------------------------------------------------------------- 1 | import socket 2 | import time 3 | 4 | 5 | def wait_for_internet(): 6 | while True: 7 | try: 8 | s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 9 | s.connect(("8.8.8.8", 80)) 10 | ip = s.getsockname()[0] 11 | s.close() 12 | print(f"Found internet on local ip {ip}") 13 | return ip 14 | except Exception as e: 15 | print(str(e)) 16 | time.sleep(10) 17 | 18 | 19 | def get_local_ip(): 20 | wait_for_internet() 21 | local_ip = socket.gethostbyname(socket.gethostname()) 22 | print(f"Using local ip address {local_ip}") 23 | return local_ip 24 | 25 | -------------------------------------------------------------------------------- /RpiController/Ports/SocketInit.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | #include "SocketInit.h" 5 | #include "Utils.h" 6 | #include 7 | 8 | #ifdef _WIN32 9 | #include 10 | #endif 11 | 12 | bool SocketInit::socket_initialized_ = false; 13 | 14 | SocketInit::SocketInit() 15 | { 16 | if (!socket_initialized_) { 17 | socket_initialized_ = true; 18 | #ifdef _WIN32 19 | WSADATA wsaData; 20 | // Initialize Winsock 21 | int rc = WSAStartup(MAKEWORD(2, 2), (LPWSADATA)&wsaData); 22 | if (rc != 0) { 23 | throw std::runtime_error(Utils::stringf("WSAStartup failed with error : %d\n", rc)); 24 | } 25 | #endif 26 | } 27 | } -------------------------------------------------------------------------------- /TeensyUnitTest/MultiWS2812Mock.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | #include "MultiWS2812Mock.h" 4 | #include "Color.h" 5 | #include "Bitmap.h" 6 | #include "TestWindow.h" 7 | 8 | void MultiWS2812::show() 9 | { 10 | if (window != nullptr && buffer != nullptr) 11 | { 12 | int w = ledsPerStrip; 13 | int h = numStrips; 14 | Bitmap bitmap(w, h); 15 | 16 | for (int y = 0; y < h; y++) 17 | { 18 | for (int x = 0; x < w; x++) 19 | { 20 | uint32_t* pixel = (uint32_t*)(buffer + (x * numStrips) + y); 21 | Color c = Color::from(*pixel); 22 | bitmap.SetPixel(x, y, c.r, c.g, c.b); 23 | } 24 | } 25 | window->DrawBitmap(bitmap); 26 | } 27 | } -------------------------------------------------------------------------------- /AdaKioskService/Setup/SetupAssignedAccess.ps1: -------------------------------------------------------------------------------- 1 | # From https://docs.microsoft.com/en-us/windows/client-management/mdm/using-powershell-scripting-with-the-wmi-bridge-provider 2 | # and https://docs.microsoft.com/en-us/windows/win32/dmwmibridgeprov/mdm-assignedaccess 3 | $nameSpaceName="root\cimv2\mdm\dmmap" 4 | $className="MDM_AssignedAccess" 5 | $obj = Get-CimInstance -Namespace $namespaceName -ClassName $className 6 | Write-Host "got instance $obj.Configuration" 7 | Add-Type -AssemblyName System.Web 8 | $xml = Get-Content -Path "AssignedAccessProfile.xml" -Encoding "utf8" | Out-String 9 | $html = [System.Web.HttpUtility]::HtmlEncode($xml) 10 | $obj.Configuration = $html 11 | Set-CimInstance -CimInstance $obj 12 | 13 | -------------------------------------------------------------------------------- /AdaServerRelay/readme.md: -------------------------------------------------------------------------------- 1 | ## AdaServerRelay 2 | 3 | This project provides a simple HTTP gateway to the `Azure Web Pub Sub` service used for providing 4 | 3 simple commands. For the full Web Pub Sub interface see [AdaWebPubSub](../AdaWebPubSub/readme.md). 5 | 6 | Setup for this Azure resource is included in 7 | the `~/Azure/setup.ps1` script. 8 | 9 | You can use the HTTP gateway to send these messages: 10 | 11 | - /ping 12 | - /bridge 13 | - /kiosk/version/? 14 | 15 | This relay can then be used in an Azure Logic App to ping the Ada Server every 15 minutes to make sure 16 | it is still responding correctly and if a timeout occurs, send an email to someone to fix it. 17 | 18 | ![workflow](images/workflow.png) 19 | -------------------------------------------------------------------------------- /AdaKiosk/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | using System.Windows; 4 | 5 | [assembly: ThemeInfo( 6 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located 7 | //(used if a resource is not found in the page, 8 | // or application resource dictionaries) 9 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located 10 | //(used if a resource is not found in the page, 11 | // app, or any theme specific resource dictionaries) 12 | )] 13 | -------------------------------------------------------------------------------- /AdaServerRelay/Startup.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Azure.WebJobs; 2 | using Microsoft.Azure.WebJobs.Hosting; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using Microsoft.Extensions.DependencyInjection.Extensions; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Text; 8 | 9 | [assembly: WebJobsStartup(typeof(AdaServerRelay.Startup))] 10 | 11 | namespace AdaServerRelay 12 | { 13 | public class Startup : IWebJobsStartup 14 | { 15 | public void Configure(IWebJobsBuilder builder) 16 | { 17 | // this doesn't work long term for some reason. 18 | // builder.Services.Add(ServiceDescriptor.Singleton(new WebPubSubGroup())); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /DmxController/findEthernetDMX.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. 2 | # Licensed under the MIT license. 3 | import socket 4 | 5 | 6 | def findDMXDevices(): 7 | # listen for Art-Net broadcast 8 | artnet_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 9 | artnet_sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) 10 | 11 | artnet_sock.bind(("", 6454)) 12 | 13 | while True: 14 | data, addr = artnet_sock.recvfrom(1024) # buffer size is 1024 bytes 15 | if len(data) >= 7: 16 | header = data[:7].decode("utf8") 17 | if header == "Art-Net": 18 | yield addr 19 | 20 | 21 | if __name__ == "__main__": 22 | for addr in findDMXDevices(): 23 | print(addr) 24 | -------------------------------------------------------------------------------- /AdaKiosk/App.xaml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Ada-Serial-Test/Makefile: -------------------------------------------------------------------------------- 1 | #OS = LINUX 2 | #OS = MACOSX 3 | OS = LINUX 4 | 5 | ifeq ($(OS), LINUX) 6 | CC = g++ 7 | CFLAGS = -Wall -O2 -DLINUX 8 | TARGET = receive_test 9 | 10 | else ifeq ($(OS), MACOSX) 11 | CC = gcc 12 | CFLAGS = -Wall -O2 -DMACOSX 13 | TARGET = receive_test 14 | 15 | else ifeq ($(OS), WINDOWS) 16 | CC = i586-mingw32msvc-gcc 17 | CFLAGS = -Wall -O2 -DWINDOWS 18 | TARGET = receive_test.exe 19 | endif 20 | all: $(TARGET) 21 | 22 | $(TARGET): Ada\ SerialTest.cpp Adafruit_NeoPixel.cpp Adafruit_NeoPixel.h LED_Helper.h Serial_Functions.h Ada_Central_Cone.h Makefile 23 | $(CC) $(CFLAGS) -pthread -o $(TARGET) Ada\ SerialTest.cpp Adafruit_NeoPixel.cpp Adafruit_NeoPixel.h LED_Helper.h Serial_Functions.h Ada_Central_Cone.h 24 | 25 | clean: 26 | rm -f receive_test receive_test.exe 27 | 28 | -------------------------------------------------------------------------------- /DmxController/internet.py: -------------------------------------------------------------------------------- 1 | import socket 2 | import time 3 | 4 | 5 | def wait_for_internet(): 6 | while True: 7 | try: 8 | s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 9 | s.connect(("8.8.8.8", 80)) 10 | ip = s.getsockname()[0] 11 | s.close() 12 | print(f"Found internet on local ip {ip}") 13 | return ip 14 | except Exception as e: 15 | print(str(e)) 16 | time.sleep(10) 17 | 18 | 19 | def get_local_ip(): 20 | wait_for_internet() 21 | local_ip = socket.gethostbyname(socket.gethostname()) 22 | print(f"Using local ip address {local_ip}") 23 | return local_ip 24 | 25 | 26 | def main(): 27 | get_local_ip() 28 | 29 | 30 | if __name__ == "__main__": 31 | main() 32 | -------------------------------------------------------------------------------- /TeensyUnitTest/MultiWS2812Mock.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | #pragma once 4 | #include 5 | 6 | class TestWindow; 7 | 8 | class MultiWS2812 9 | { 10 | int ledsPerStrip; 11 | int numStrips; 12 | uint32_t* buffer = nullptr; 13 | TestWindow* window = nullptr; 14 | public: 15 | MultiWS2812(int ledsPerStrip, int numStrips) 16 | { 17 | this->ledsPerStrip = ledsPerStrip; 18 | this->numStrips = numStrips; 19 | } 20 | 21 | void setWindow(TestWindow* window) 22 | { 23 | this->window = window; 24 | } 25 | 26 | void enableOutputPin(int port, int pin) 27 | { 28 | } 29 | 30 | void show(); 31 | 32 | void setBuffer(uint32_t* buffer) 33 | { 34 | this->buffer = buffer; 35 | } 36 | void printTimingCycleStats() 37 | { 38 | 39 | } 40 | }; -------------------------------------------------------------------------------- /AdaKioskService/Setup/InstallService.ps1: -------------------------------------------------------------------------------- 1 | 2 | $setup = [System.IO.Path]::GetDirectoryName($PSScriptRoot) 3 | $root = [System.IO.Path]::GetDirectoryName($setup) 4 | Write-Host "Installing to $root\AdaKioskService" 5 | 6 | &net stop AdaKioskService 7 | 8 | 9 | try { 10 | if (Test-Path -Path "$root\AdaKioskService") { 11 | # rename will fail if files are locked. 12 | Rename-Item "$root\AdaKioskService" "$root\AdaKioskServiceOld" 13 | Remove-Item "$root\AdaKioskServiceOld" -recurse 14 | } 15 | 16 | Rename-Item "$setup" "$root\AdaKioskService" 17 | } catch { 18 | $e = $_ 19 | if (-not (Test-Path -Path "$root\adakioskservice.log")) { 20 | Set-Content -Path "$root\adakioskservice.log" -Value "Logfile" 21 | } 22 | Add-Content -Path "$root\adakioskservice.log" -Value "Error updating service: $e" 23 | } 24 | 25 | &net start AdaKioskService 26 | -------------------------------------------------------------------------------- /AdaKiosk/Controls/ScreenSaver.xaml: -------------------------------------------------------------------------------- 1 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /AdaKioskService/Setup/AssignedAccessConfig.xsd: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /AdaKiosk/Utilities/UiDispatcher.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | using System; 4 | using System.Windows.Threading; 5 | 6 | namespace AdaSimulation 7 | { 8 | /// 9 | /// A simple helper class that gives a way to run things on the UI thread. The app must call Initialize once during app start, using inside OnLaunch. 10 | /// 11 | public class UiDispatcher 12 | { 13 | static UiDispatcher instance; 14 | Dispatcher dispatcher; 15 | 16 | 17 | public static UiDispatcher Initialize() 18 | { 19 | instance = new UiDispatcher() 20 | { 21 | dispatcher = Dispatcher.CurrentDispatcher 22 | }; 23 | return instance; 24 | } 25 | 26 | public void RunOnUIThread(Action a) 27 | { 28 | _ = dispatcher.BeginInvoke(a); 29 | } 30 | 31 | public static UiDispatcher Instance { get { return instance; } } 32 | 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Server/internet.py: -------------------------------------------------------------------------------- 1 | import socket 2 | import time 3 | from urllib.parse import urlparse 4 | 5 | 6 | def wait_for_internet(): 7 | while True: 8 | try: 9 | s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 10 | s.connect(("8.8.8.8", 80)) 11 | ip = s.getsockname()[0] 12 | s.close() 13 | print(f"Found internet on local ip {ip}") 14 | return ip 15 | except Exception as e: 16 | print(str(e)) 17 | time.sleep(10) 18 | return str(e) 19 | 20 | 21 | def get_ip_address(url: str): 22 | 23 | try: 24 | parsed_url = urlparse(url) 25 | hostname = parsed_url.hostname 26 | s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 27 | s.connect((hostname, 80)) 28 | ip = s.getsockname()[0] 29 | s.close() 30 | print(f"Found internet via local ip {ip}") 31 | return ip 32 | except Exception as e: 33 | print(str(e)) 34 | time.sleep(10) 35 | return str(e) 36 | -------------------------------------------------------------------------------- /AdaServerRelay/Properties/PublishProfiles/ada-server-functions - Zip Deploy.pubxml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | ZipDeploy 8 | AzureWebSite 9 | Release 10 | Any CPU 11 | http://ada-server-functions.azurewebsites.net 12 | False 13 | /subscriptions/002e766a-81ef-4827-8e4e-1dd0d0f0f752/resourceGroups/ada-server-rg/providers/Microsoft.Web/sites/ada-server-functions 14 | $ada-server-functions 15 | <_SavePWD>True 16 | https://ada-server-functions.scm.azurewebsites.net/ 17 | 18 | -------------------------------------------------------------------------------- /HistoricalData/summarize.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pandas as pd 3 | import numpy as np 4 | 5 | for name in os.listdir(): 6 | if name.endswith(".csv"): 7 | print("{}==========================================".format(name)) 8 | temp = pd.read_csv(name) 9 | for i in range(temp.shape[0]): 10 | row = temp.iloc[i] 11 | 12 | scores = [] 13 | emotions = [ 14 | "neutral", 15 | "happiness", 16 | "sadness", 17 | "anger", 18 | "fear", 19 | "surprise", 20 | "disgust", 21 | "contempt", 22 | ] 23 | for key in emotions: 24 | scores += [float(row[key])] 25 | 26 | idx = np.argsort(scores) 27 | 28 | # print(["{}({:.2f})".format(se[i], scores[i]) for i in reversed(range(len(idx)))]) 29 | for i in reversed(range(len(idx))): 30 | if scores[idx[i]] > 0.01: 31 | print( 32 | "{} ({:.2f})".format(emotions[idx[i]], scores[idx[i]]), end=", " 33 | ) 34 | print() 35 | -------------------------------------------------------------------------------- /RpiController/Ports/Port.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | #ifndef PORT_H 5 | #define PORT_H 6 | #include 7 | #include 8 | 9 | class Port 10 | { 11 | public: 12 | virtual ~Port(); 13 | 14 | // write to the port, return number of bytes written or -1 if error. 15 | virtual int write(const uint8_t* ptr, int count) = 0; 16 | 17 | // read a given number of bytes from the port (blocking until the requested bytes are available). 18 | // return the number of bytes read or -1 if error. 19 | virtual int read(uint8_t* buffer, int bytesToRead) = 0; 20 | 21 | // read up to next new line char and return null terminatted buffer, or nullptr if 22 | // there is nothing to read right now. 23 | virtual const char* readline(int timeoutMilliseconds = 0); 24 | 25 | // close the port. 26 | virtual void close() = 0; 27 | 28 | virtual bool isClosed() = 0; 29 | 30 | virtual void flush() = 0; 31 | 32 | private: 33 | // readline buffer 34 | char* readlineBuffer = nullptr; 35 | int bufferSize = 0; 36 | int totalBytesRead = 0; 37 | int totalBytesWritten = 0; 38 | 39 | }; 40 | #endif // !PORT_H 41 | -------------------------------------------------------------------------------- /AdaKioskService/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.ServiceProcess; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace AdaKioskService 9 | { 10 | static class Program 11 | { 12 | /// 13 | /// The main entry point for the application. 14 | /// 15 | static async Task Main() 16 | { 17 | if (System.Diagnostics.Debugger.IsAttached) 18 | { 19 | await Task.Run(async () => 20 | { 21 | var service = new AdaKioskService(); 22 | service.UpdateCheckDelay = 1000; // 1 second! 23 | service.DebugStart(); 24 | await Task.Delay(1000 * 60 * 30); 25 | }); 26 | } 27 | else 28 | { 29 | ServiceBase[] ServicesToRun; 30 | ServicesToRun = new ServiceBase[] 31 | { 32 | new AdaKioskService() 33 | }; 34 | ServiceBase.Run(ServicesToRun); 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Microsoft Corporation. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE 22 | -------------------------------------------------------------------------------- /AdaKioskService/AdaKioskService.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.0.32112.339 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AdaKioskService", "AdaKioskService.csproj", "{B8473D96-C142-4733-A655-9E86D3EC643E}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {B8473D96-C142-4733-A655-9E86D3EC643E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {B8473D96-C142-4733-A655-9E86D3EC643E}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {B8473D96-C142-4733-A655-9E86D3EC643E}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {B8473D96-C142-4733-A655-9E86D3EC643E}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {871F1A22-7B44-4B1A-B579-E3AEE4F98240} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /AdaServerRelay/AdaServerRelay.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.31702.278 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AdaServerRelay", "AdaServerRelay.csproj", "{7C4943F9-76A0-4CFA-A821-3F2B381D6E14}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {7C4943F9-76A0-4CFA-A821-3F2B381D6E14}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {7C4943F9-76A0-4CFA-A821-3F2B381D6E14}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {7C4943F9-76A0-4CFA-A821-3F2B381D6E14}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {7C4943F9-76A0-4CFA-A821-3F2B381D6E14}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {C32CDAC8-14E8-45C6-BE72-A99D71EF90AE} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /AdaKioskService/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /AdaKioskUnitTest/AdaKioskUnitTest.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.31702.278 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AdaKioskUnitTest", "AdaKioskUnitTest.csproj", "{C59DB7F0-F0A1-40D2-86B4-31B0AC8DA0D6}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {C59DB7F0-F0A1-40D2-86B4-31B0AC8DA0D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {C59DB7F0-F0A1-40D2-86B4-31B0AC8DA0D6}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {C59DB7F0-F0A1-40D2-86B4-31B0AC8DA0D6}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {C59DB7F0-F0A1-40D2-86B4-31B0AC8DA0D6}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {D977F75C-D9C5-4941-A9E8-29B7510EEC7D} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /AdaServerRelay/AdaServerRelay.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netcoreapp3.1 4 | v3 5 | 6 | 7 | 8 | 9 | 10 | 11 | PreserveNewest 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | PreserveNewest 23 | 24 | 25 | PreserveNewest 26 | Never 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /AdaKioskService/ServiceLog.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Threading; 5 | 6 | namespace AdaKioskService 7 | { 8 | class ServiceLog 9 | { 10 | string fileName; 11 | static ServiceLog _instance; 12 | 13 | public ServiceLog(string fileName) 14 | { 15 | _instance = this; 16 | this.fileName = fileName; 17 | if (File.Exists(fileName)) 18 | { 19 | // don't let the log file get too big! Truncate it to the last 1000 lines. 20 | var lines = new List(File.ReadAllLines(fileName)); 21 | if (lines.Count > 1000) 22 | { 23 | lines.RemoveRange(0, lines.Count - 1000); 24 | File.WriteAllLines(fileName, lines.ToArray()); 25 | } 26 | } 27 | } 28 | 29 | public static ServiceLog Instance => _instance; 30 | 31 | public void WriteMessage(string format, params object[] args) 32 | { 33 | var msg = DateTime.Now.ToString("g") + ": " + string.Format(format, args) + "\n"; 34 | File.AppendAllText(this.fileName, msg); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /AdaKioskService/AdaKioskService.Designer.cs: -------------------------------------------------------------------------------- 1 | 2 | namespace AdaKioskService 3 | { 4 | partial class AdaKioskService 5 | { 6 | /// 7 | /// Required designer variable. 8 | /// 9 | private System.ComponentModel.IContainer components = null; 10 | 11 | /// 12 | /// Clean up any resources being used. 13 | /// 14 | /// true if managed resources should be disposed; otherwise, false. 15 | protected override void Dispose(bool disposing) 16 | { 17 | if (disposing && (components != null)) 18 | { 19 | components.Dispose(); 20 | } 21 | base.Dispose(disposing); 22 | } 23 | 24 | #region Component Designer generated code 25 | 26 | /// 27 | /// Required method for Designer support - do not modify 28 | /// the contents of this method with the code editor. 29 | /// 30 | private void InitializeComponent() 31 | { 32 | components = new System.ComponentModel.Container(); 33 | this.ServiceName = "AdaKioskService"; 34 | } 35 | 36 | #endregion 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /TeensyFirmware/include/Color.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | #ifndef _COLOR_H 4 | #define _COLOR_H 5 | #include 6 | 7 | // Simple rgb color class. 8 | class Color 9 | { 10 | public: 11 | uint8_t r; 12 | uint8_t g; 13 | uint8_t b; 14 | 15 | uint32_t pack() const { 16 | // the Teensy colors are packed in GRB order. 17 | return (g << 16) + (r << 8) + b; 18 | } 19 | 20 | static Color from(uint32_t value) 21 | { 22 | uint8_t r = (uint8_t)((value >> 8) & 0xff); 23 | uint8_t g = (uint8_t)((value >> 16) & 0xff); 24 | uint8_t b = (uint8_t)(value & 0xff); 25 | return Color{ r, g, b }; 26 | } 27 | 28 | static Color fromrgb(uint32_t value) 29 | { 30 | uint8_t r = (uint8_t)((value >> 16) & 0xff); 31 | uint8_t g = (uint8_t)((value >> 8) & 0xff); 32 | uint8_t b = (uint8_t)(value & 0xff); 33 | return Color{ r, g, b }; 34 | } 35 | 36 | bool operator==(const Color& other) const 37 | { 38 | return r == other.r && g == other.g && b == other.b; 39 | } 40 | 41 | bool operator!=(const Color& other) const 42 | { 43 | return r != other.r || g != other.g || b != other.b; 44 | } 45 | }; 46 | 47 | #endif -------------------------------------------------------------------------------- /RpiController/Utils/Timer.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | #ifndef _TIMER_H 4 | #define _TIMER_H 5 | 6 | #include 7 | 8 | class Timer 9 | { 10 | private: 11 | typedef std::chrono::high_resolution_clock clock; 12 | unsigned long long start_time; 13 | unsigned long long end_time; 14 | bool running; 15 | 16 | public: 17 | void start() 18 | { 19 | start_time = now(); 20 | running = true; 21 | } 22 | 23 | void stop() { 24 | end_time = now(); 25 | running = false; 26 | } 27 | 28 | double microseconds() { 29 | auto end = end_time; 30 | if (running) { 31 | end = now(); 32 | } 33 | return (double)(end - start_time) / 1000.0; 34 | } 35 | double milliseconds() { 36 | auto end = end_time; 37 | if (running) { 38 | end = now(); 39 | } 40 | return (double)(end - start_time) / 1000000.0; 41 | } 42 | double seconds() { 43 | auto end = end_time; 44 | if (running) { 45 | end = now(); 46 | } 47 | return (double)(end - start_time) / 1000000000.0; 48 | } 49 | 50 | unsigned long long now() 51 | { 52 | return clock::now().time_since_epoch().count(); 53 | } 54 | }; 55 | 56 | 57 | #endif -------------------------------------------------------------------------------- /TeensyUnitTest/Timer.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | #ifndef _TIMER_H 4 | #define _TIMER_H 5 | 6 | #include 7 | 8 | class Timer 9 | { 10 | private: 11 | typedef std::chrono::high_resolution_clock clock; 12 | unsigned long long start_time = 0; 13 | unsigned long long end_time = 0; 14 | bool running = false; 15 | 16 | public: 17 | void start() 18 | { 19 | start_time = now(); 20 | running = true; 21 | } 22 | 23 | void stop() { 24 | end_time = now(); 25 | running = false; 26 | } 27 | 28 | double microseconds() { 29 | auto end = end_time; 30 | if (running) { 31 | end = now(); 32 | } 33 | return (double)(end - start_time) / 1000.0; 34 | } 35 | float milliseconds() { 36 | auto end = end_time; 37 | if (running) { 38 | end = now(); 39 | } 40 | return (float)(end - start_time) / 1000000.0f; 41 | } 42 | float seconds() { 43 | auto end = end_time; 44 | if (running) { 45 | end = now(); 46 | } 47 | return (float)(end - start_time) / 1000000000.0f; 48 | } 49 | 50 | unsigned long long now() 51 | { 52 | return clock::now().time_since_epoch().count(); 53 | } 54 | }; 55 | 56 | 57 | #endif -------------------------------------------------------------------------------- /SUPPORT.md: -------------------------------------------------------------------------------- 1 | # TODO: The maintainer of this repo has not yet edited this file 2 | 3 | **REPO OWNER**: Do you want Customer Service & Support (CSS) support for this product/project? 4 | 5 | - **No CSS support:** Fill out this template with information about how to file issues and get help. 6 | - **Yes CSS support:** Fill out an intake form at [aka.ms/spot](https://aka.ms/spot). CSS will work with/help you to determine next steps. More details also available at [aka.ms/onboardsupport](https://aka.ms/onboardsupport). 7 | - **Not sure?** Fill out a SPOT intake as though the answer were "Yes". CSS will help you decide. 8 | 9 | *Then remove this first heading from this SUPPORT.MD file before publishing your repo.* 10 | 11 | # Support 12 | 13 | ## How to file issues and get help 14 | 15 | This project uses GitHub Issues to track bugs and feature requests. Please search the existing 16 | issues before filing new issues to avoid duplicates. For new issues, file your bug or 17 | feature request as a new Issue. 18 | 19 | For help and questions about using this project, please **REPO MAINTAINER: INSERT INSTRUCTIONS HERE 20 | FOR HOW TO ENGAGE REPO OWNERS OR COMMUNITY FOR HELP. COULD BE A STACK OVERFLOW TAG OR OTHER 21 | CHANNEL. WHERE WILL YOU HELP PEOPLE?**. 22 | 23 | ## Microsoft Support Policy 24 | 25 | Support for this **PROJECT or PRODUCT** is limited to the resources listed above. 26 | -------------------------------------------------------------------------------- /RpiController/buildtools.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. 2 | # Licensed under the MIT license. 3 | import sys 4 | import subprocess 5 | from threading import Thread 6 | 7 | 8 | class BuildTools: 9 | def logstream(self, stream): 10 | try: 11 | while True: 12 | out = stream.readline() 13 | if out: 14 | msg = out.decode("utf-8") 15 | print(msg.strip("\r\n")) 16 | else: 17 | break 18 | except: 19 | errorType, value, traceback = sys.exc_info() 20 | msg = "### Exception: %s: %s" % (str(errorType), str(value)) 21 | print(msg) 22 | 23 | def run(self, command, print_output=True, shell=False): 24 | # cmdstr = command if isinstance(command, str) else " ".join(command) 25 | with subprocess.Popen( 26 | command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, bufsize=0 27 | ) as proc: 28 | stdout_thread = Thread(target=self.logstream, args=(proc.stdout,)) 29 | stderr_thread = Thread(target=self.logstream, args=(proc.stderr,)) 30 | 31 | stdout_thread.start() 32 | stderr_thread.start() 33 | 34 | while stdout_thread.isAlive() or stderr_thread.isAlive(): 35 | pass 36 | 37 | proc.wait() 38 | 39 | return proc.returncode 40 | -------------------------------------------------------------------------------- /DmxController/dmx_mock.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. 2 | # Licensed under the MIT license. 3 | from dmx import DmxDevice 4 | from pretty_print import print_info 5 | 6 | 7 | class Dmx: 8 | def __init__(self, port_name: str): 9 | self.port_name = port_name 10 | 11 | def open_serial(self): 12 | pass 13 | 14 | def try_reconnect(self): 15 | print_info("DMX: Re-opening port") 16 | 17 | def send_data(self, label: int, data=bytes()): 18 | pass 19 | 20 | def receive_data(self, label): 21 | return None 22 | 23 | def set_api_key(self): 24 | pass 25 | 26 | def enable_dmx_ports(self, port_1_enable, port_2_enable): 27 | pass 28 | 29 | def get_serial_number(self): 30 | return 0 31 | 32 | def get_hardware_version(self): 33 | return 0 34 | 35 | def add_device(self, dmx_universe: int, dmx_device: DmxDevice): 36 | pass 37 | 38 | def set_output_by_type(self, dmx_universe: int, device_type: str, output_values): 39 | pass 40 | 41 | def set_output_by_name(self, dmx_universe: int, device_name: str, output_values): 42 | pass 43 | 44 | def send_update(self, dmx_universe: int): 45 | pass 46 | 47 | # smoothly fades each light, where there is an old and new color for each light to send 48 | def smooth_fade(self, universe, lights, oldcolors, newcolors, seconds): 49 | pass 50 | -------------------------------------------------------------------------------- /Server/zone_map_3.json: -------------------------------------------------------------------------------- 1 | { 2 | "core_leds": [ 3 | { 4 | "col": 6, 5 | "leds": [ 8, 21, 33, 46, 58, 71, 84 ] 6 | }, 7 | { 8 | "col": 8, 9 | "leds": [ 11, 25, 38, 50, 62, 74, 88 ] 10 | }, 11 | { 12 | "col": 9, 13 | "leds": [ 10, 23, 35, 48, 60, 74, 86 ] 14 | }, 15 | { 16 | "col": 10, 17 | "leds": [ 8, 21, 33 ] 18 | } 19 | ], 20 | "zone_leds": [ 21 | { 22 | "col": 7, 23 | "zone": 1, 24 | "length": 389 25 | }, 26 | { 27 | "col": 12, 28 | "zone": 5, 29 | "length": 75 30 | }, 31 | { 32 | "col": 13, 33 | "zone": 5, 34 | "length": 203 35 | }, 36 | { 37 | "col": 2, 38 | "zone": 5, 39 | "length": 279 40 | }, 41 | { 42 | "col": 1, 43 | "zone": 5, 44 | "length": 366 45 | }, 46 | { 47 | "col": 14, 48 | "zone": 5, 49 | "length": 74 50 | }, 51 | { 52 | "col": 4, 53 | "zone": 5, 54 | "length": 271 55 | }, 56 | { 57 | "col": 3, 58 | "zone": 5, 59 | "length": 364 60 | } 61 | ] 62 | } -------------------------------------------------------------------------------- /Server/zone_map_1.json: -------------------------------------------------------------------------------- 1 | { 2 | "core_leds": [ 3 | { 4 | "col": 5, 5 | "leds": [ 10, 23, 35, 47, 60, 72, 85 ] 6 | }, 7 | { 8 | "col": 7, 9 | "leds": [ 6, 18, 30, 44, 56, 68, 81 ] 10 | } 11 | ], 12 | "zone_leds": [ 13 | { 14 | "col": 3, 15 | "zone": 0, 16 | "length": 385 17 | }, 18 | { 19 | "col": 1, 20 | "zone": 0, 21 | "length": 384 22 | }, 23 | { 24 | "col": 0, 25 | "zone": 0, 26 | "length": 389 27 | }, 28 | { 29 | "col": 14, 30 | "zone": 0, 31 | "length": 389 32 | }, 33 | { 34 | "col": 10, 35 | "zone": 2, 36 | "length": 330 37 | }, 38 | { 39 | "col": 11, 40 | "zone": 2, 41 | "length": 381 42 | }, 43 | { 44 | "col": 13, 45 | "zone": 4, 46 | "length": 364 47 | }, 48 | { 49 | "col": 8, 50 | "zone": 4, 51 | "length": 197 52 | }, 53 | { 54 | "col": 15, 55 | "zone": 4, 56 | "length": 78 57 | }, 58 | { 59 | "col": 12, 60 | "zone": 5, 61 | "length": 75 62 | } 63 | ] 64 | } -------------------------------------------------------------------------------- /KasaBridge/readme.md: -------------------------------------------------------------------------------- 1 | ## Kasa Bridge 2 | 3 | We use a [GL.iNet GL-MT300N-V2 Mini Wifi Access Point](https://www.amazon.com/gp/product/B073TSK26W/ref=ppx_yo_dt_b_asin_title_o00_s00?ie=UTF8&psc=1) inside the Server panel to 4 | connect to the Wifi only 5 | [https://www.amazon.com/gp/product/B091FXLMS8/ref=ppx_yo_dt_b_search_asin_title?ie=UTF8&psc=1](Kasa EP10 Smart Plugs) which power the Ada Led strips. 6 | 7 | ![image](photo.png) 8 | 9 | This wifi access point is connected to the Security Gateway (router) 10 | and the Kasa EP10 power switches are connected to this Raspberry Pi 11 | Wifi network on (192.168.4.*). 12 | 13 | This bridge controls the 14 | Kasa EP10 power switches so the Kasa EP10 power switches are NOT on a corporate wifi network. 15 | 16 | The `bridge_client.py` script runs in the `Server` to provide access 17 | to the relay, and the `bridge.py` script runs in a 18 | separate window on the ada-core machine to relay commands to the Kasa EP10 switches. 19 | 20 | Supported commands are: 21 | - **list** to list the discovered EP10 devices 22 | - **status** get the on/off status of the devices 23 | - **on** turn on all switches 24 | - **off** turn off all switches 25 | 26 | The `bridge.py` uses the `tplink_smartplug.py` to discover the devices. 27 | All devices that respond to valid UDP `get_sysinfo` with a model string containing `HS105`, `HS103`, or `EP10` are the 28 | correct devices to work with, this way no fixed ip addresses 29 | need to be configured anywhere in this bridge system. 30 | -------------------------------------------------------------------------------- /RpiController/Ports/UdpClientPort.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | #ifndef SERIAL_COM_UDPCLIENTPORT_HPP 5 | #define SERIAL_COM_UDPCLIENTPORT_HPP 6 | 7 | #include "Port.h" 8 | 9 | class UdpClientPort : public Port 10 | { 11 | public: 12 | UdpClientPort(); 13 | ~UdpClientPort(); 14 | 15 | // Connect can set you up two different ways. Pass 0 for local port to get any free local 16 | // port and pass a fixed remotePort if you want to send to a specific remote port. 17 | // Conversely, pass a fix local port to bind to, and 0 for remotePort if you want to 18 | // allow any remote sender to send to your specific local port. localHost allows you to 19 | // be specific about local adapter ip address. 20 | void connect(const std::string& localHost, int localPort, const std::string& remoteHost, int remotePort); 21 | 22 | // write the given bytes to the port, return number of bytes written or -1 if error. 23 | virtual int write(const uint8_t* ptr, int count); 24 | 25 | // read some bytes from the port, return the number of bytes read or -1 if error. 26 | virtual int read(uint8_t* buffer, int bytesToRead); 27 | 28 | // close the port. 29 | virtual void close(); 30 | 31 | virtual bool isClosed(); 32 | 33 | virtual void flush() {}; 34 | 35 | std::string remoteAddress(); 36 | int remotePort(); 37 | 38 | private: 39 | class UdpSocketImpl; 40 | std::unique_ptr impl_; 41 | }; 42 | 43 | 44 | #endif // SERIAL_COM_UDPCLIENTPORT_HPP 45 | -------------------------------------------------------------------------------- /Server/priority_queue.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. 2 | # Licensed under the MIT license. 3 | from threading import Lock 4 | 5 | 6 | class PriorityQueue: 7 | def __init__(self): 8 | self.queue = [] 9 | self.lock = Lock() 10 | 11 | def enqueue(self, priority, data): 12 | self.lock.acquire() 13 | inserted = False 14 | for i in range(len(self.queue)): 15 | item = self.queue[i] 16 | if item[0] > priority: 17 | self.queue.insert(i, (priority, data)) 18 | inserted = True 19 | break 20 | if not inserted: 21 | self.queue += [(priority, data)] 22 | self.lock.release() 23 | 24 | def dequeue(self): 25 | """returns a tuple containing the (priority, data) that was enqueued.""" 26 | self.lock.acquire() 27 | item = None 28 | if len(self.queue) > 0: 29 | item = self.queue[0] 30 | del self.queue[0] 31 | self.lock.release() 32 | return item 33 | 34 | def peek(self): 35 | """returns a tuple containing the (priority, data) that was enqueued.""" 36 | self.lock.acquire() 37 | item = None 38 | if len(self.queue) > 0: 39 | item = self.queue[0] 40 | self.lock.release() 41 | return item 42 | 43 | def size(self): 44 | self.lock.acquire() 45 | rc = len(self.queue) 46 | self.lock.release() 47 | return rc 48 | -------------------------------------------------------------------------------- /AdaWebPubSub/test.py: -------------------------------------------------------------------------------- 1 | import os 2 | import asyncio 3 | import _thread 4 | from messagebus import WebPubSubGroup 5 | 6 | CONNECTION_STRING_NAME = "ADA_WEBPUBSUB_CONNECTION_STRING" 7 | 8 | 9 | def on_message(user, message): 10 | print(f"{user}: {message}") 11 | 12 | 13 | def prompt_for_command(input_queue): 14 | while True: 15 | cmd = input("Enter command to send or 'x' to exit: ") 16 | input_queue.put_nowait(cmd.strip()) 17 | if cmd == "x": 18 | return 19 | 20 | 21 | async def async_command_line(msgbus): 22 | # console input has to be in a separate thread otherwise it somehow 23 | # blocks all asyncio, including the msgbus.listen task. 24 | input_queue = asyncio.Queue() 25 | _thread.start_new_thread(prompt_for_command, (input_queue,)) 26 | while True: 27 | cmd = await input_queue.get() 28 | if cmd == "x": 29 | msgbus.close() 30 | return 31 | else: 32 | msgbus.send(cmd) 33 | 34 | 35 | async def _main(): 36 | constr = os.getenv(CONNECTION_STRING_NAME) 37 | if not constr: 38 | raise Exception(f"{CONNECTION_STRING_NAME} environment variable not set") 39 | 40 | msgbus = WebPubSubGroup(constr, "AdaKiosk", "test", "demogroup") 41 | await msgbus.connect() 42 | msgbus.add_listener(on_message) 43 | await asyncio.gather(async_command_line(msgbus), msgbus.listen(), msgbus.consume()) 44 | 45 | 46 | if __name__ == "__main__": 47 | asyncio.get_event_loop().run_until_complete(_main()) 48 | -------------------------------------------------------------------------------- /Ada-Serial-Test/Ada SerialTest.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29306.81 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Ada SerialTest", "Ada SerialTest.vcxproj", "{5AB4457B-9AF0-4884-8E55-0B3961137E58}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|x64 = Debug|x64 11 | Debug|x86 = Debug|x86 12 | Release|x64 = Release|x64 13 | Release|x86 = Release|x86 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {5AB4457B-9AF0-4884-8E55-0B3961137E58}.Debug|x64.ActiveCfg = Debug|x64 17 | {5AB4457B-9AF0-4884-8E55-0B3961137E58}.Debug|x64.Build.0 = Debug|x64 18 | {5AB4457B-9AF0-4884-8E55-0B3961137E58}.Debug|x86.ActiveCfg = Debug|Win32 19 | {5AB4457B-9AF0-4884-8E55-0B3961137E58}.Debug|x86.Build.0 = Debug|Win32 20 | {5AB4457B-9AF0-4884-8E55-0B3961137E58}.Release|x64.ActiveCfg = Release|x64 21 | {5AB4457B-9AF0-4884-8E55-0B3961137E58}.Release|x64.Build.0 = Release|x64 22 | {5AB4457B-9AF0-4884-8E55-0B3961137E58}.Release|x86.ActiveCfg = Release|Win32 23 | {5AB4457B-9AF0-4884-8E55-0B3961137E58}.Release|x86.Build.0 = Release|Win32 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {824A27E7-B90A-4AD8-97E3-9789CA0D16A6} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /AdaKioskService/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("AdaKioskService")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("Microsoft Corporation")] 12 | [assembly: AssemblyProduct("AdaKioskService")] 13 | [assembly: AssemblyCopyright("Copyright © 2023")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("b8473d96-c142-4733-a655-9e86d3ec643e")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.1.0")] 36 | [assembly: AssemblyFileVersion("1.0.1.0")] 37 | -------------------------------------------------------------------------------- /Azure/PublishFirmware.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. 2 | # Licensed under the MIT license. 3 | 4 | # This firmware publishes the built firmware.hex file to an Azure blob store 5 | # so the Server can find it there and distribute it to the Raspberry Pi Devices. 6 | 7 | import os 8 | import sys 9 | import binascii 10 | import hashlib 11 | import hmac 12 | 13 | script_dir = os.path.dirname(os.path.abspath(__file__)) 14 | hexfile = os.path.join(script_dir, ".pio/build/teensy40/firmware.hex") 15 | with open(hexfile, "rb") as f: 16 | bytes = f.read() 17 | byte_key = binascii.unhexlify("414441") 18 | hash = hmac.new(byte_key, bytes, hashlib.sha256).hexdigest() 19 | 20 | print("Publish firmware with hash: {}".format(hash)) 21 | 22 | if not os.path.isfile(hexfile): 23 | print("Please build the firmware using 'code TeensyFirmware.code-workspace") 24 | sys.exit(1) 25 | 26 | # this assumes the existence of a Cosmos Database named "adastorage". 27 | from azure.storage.blob import BlobServiceClient 28 | 29 | connection_string = os.getenv("ADA_STORAGE_CONNECTION_STRING") 30 | if not connection_string: 31 | print("Please set ADA_STORAGE_CONNECTION_STRING environment variable") 32 | sys.exit(1) 33 | 34 | service = BlobServiceClient.from_connection_string(conn_str=connection_string) 35 | 36 | container = service.get_container_client("firmware") 37 | if not container.exists(): 38 | container.create_container(public_access="Container") 39 | 40 | with open(hexfile, "rb") as data: 41 | container.upload_blob("TeensyFirmware.TEENSY40.hex", data, overwrite=True) 42 | 43 | print("Success") 44 | -------------------------------------------------------------------------------- /RpiController/Utils/Color.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | #ifndef _COLOR_H 4 | #define _COLOR_H 5 | #include 6 | 7 | // Simple rgb color class. 8 | class Color 9 | { 10 | public: 11 | uint8_t r; 12 | uint8_t g; 13 | uint8_t b; 14 | 15 | uint32_t pack() const { 16 | // the Teensy colors are packed in GRB order. 17 | return (g << 16) + (r << 8) + b; 18 | } 19 | 20 | static Color from(uint32_t value) 21 | { 22 | uint8_t r = (uint8_t)((value >> 8) & 0xff); 23 | uint8_t g = (uint8_t)((value >> 16) & 0xff); 24 | uint8_t b = (uint8_t)(value & 0xff); 25 | return Color{ r, g, b }; 26 | } 27 | 28 | static Color fromrgb(uint32_t value) 29 | { 30 | uint8_t r = (uint8_t)((value >> 16) & 0xff); 31 | uint8_t g = (uint8_t)((value >> 8) & 0xff); 32 | uint8_t b = (uint8_t)(value & 0xff); 33 | return Color{ r, g, b }; 34 | } 35 | 36 | bool operator==(const Color& other) const 37 | { 38 | return r == other.r && g == other.g && b == other.b; 39 | } 40 | 41 | bool operator!=(const Color& other) const 42 | { 43 | return r != other.r || g != other.g || b != other.b; 44 | } 45 | }; 46 | 47 | 48 | class Pixel 49 | { 50 | public: 51 | int strip; 52 | int led; 53 | Color color; 54 | 55 | bool operator==(const Pixel& other) const 56 | { 57 | return strip == other.strip && led == other.led && color == other.color; 58 | } 59 | 60 | bool operator!=(const Pixel& other) const 61 | { 62 | return !operator==(other); 63 | } 64 | }; 65 | 66 | #endif -------------------------------------------------------------------------------- /Azure/connect_arc.ps1: -------------------------------------------------------------------------------- 1 | try { 2 | $env:SUBSCRIPTION_ID = "002e766a-81ef-4827-8e4e-1dd0d0f0f752"; 3 | $env:RESOURCE_GROUP = "ada-server-rg"; 4 | $env:TENANT_ID = "72f988bf-86f1-41af-91ab-2d7cd011db47"; 5 | $env:LOCATION = "westus2"; 6 | $env:AUTH_TYPE = "token"; 7 | $env:CORRELATION_ID = "0be9cf83-1a43-4818-8a51-34c79df59206"; 8 | $env:CLOUD = "AzureCloud"; 9 | 10 | 11 | [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor 3072; 12 | 13 | # Download the installation package 14 | Invoke-WebRequest -UseBasicParsing -Uri "https://aka.ms/azcmagent-windows" -TimeoutSec 30 -OutFile "$env:TEMP\install_windows_azcmagent.ps1"; 15 | 16 | # Install the hybrid agent 17 | & "$env:TEMP\install_windows_azcmagent.ps1"; 18 | if ($LASTEXITCODE -ne 0) { exit 1; } 19 | 20 | # Run connect command 21 | & "$env:ProgramW6432\AzureConnectedMachineAgent\azcmagent.exe" connect --resource-group "$env:RESOURCE_GROUP" --tenant-id "$env:TENANT_ID" --location "$env:LOCATION" --subscription-id "$env:SUBSCRIPTION_ID" --cloud "$env:CLOUD" --correlation-id "$env:CORRELATION_ID"; 22 | } 23 | catch { 24 | $logBody = @{subscriptionId="$env:SUBSCRIPTION_ID";resourceGroup="$env:RESOURCE_GROUP";tenantId="$env:TENANT_ID";location="$env:LOCATION";correlationId="$env:CORRELATION_ID";authType="$env:AUTH_TYPE";operation="onboarding";messageType=$_.FullyQualifiedErrorId;message="$_";}; 25 | Invoke-WebRequest -UseBasicParsing -Uri "https://gbl.his.arc.azure.com/log" -Method "PUT" -Body ($logBody | ConvertTo-Json) | out-null; 26 | Write-Host -ForegroundColor red $_.Exception; 27 | } 28 | -------------------------------------------------------------------------------- /AdaKioskService/Setup/AssignedAccess2020Config.xsd: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /TeensyFirmware/PublishFirmware.py: -------------------------------------------------------------------------------- 1 | # This firmware publishes the built firmware.hex file to an Azure blob store 2 | # so the Server can find it there and distribute it to the Raspberry Pi Devices. 3 | 4 | import os 5 | import sys 6 | import binascii 7 | import hashlib 8 | import hmac 9 | from azure.storage.blob import BlobServiceClient 10 | 11 | 12 | script_dir = os.path.dirname(os.path.abspath(__file__)) 13 | hexfile = os.path.join(script_dir, ".pio/build/teensy40/firmware.hex") 14 | if not os.path.isfile(hexfile): 15 | print("Please build the firmware using 'code TeensyFirmware.code-workspace") 16 | sys.exit(1) 17 | 18 | 19 | connection_string = os.getenv("ADA_STORAGE_CONNECTION_STRING") 20 | if not connection_string: 21 | print("Please set ADA_STORAGE_CONNECTION_STRING environment variable") 22 | sys.exit(1) 23 | 24 | 25 | def upload_file(filename, blob_container_name): 26 | with open(hexfile, "rb") as f: 27 | bytes = f.read() 28 | byte_key = binascii.unhexlify("414441") 29 | hash = hmac.new(byte_key, bytes, hashlib.sha256).hexdigest() 30 | 31 | print("Publish firmware with hash: {}".format(hash)) 32 | 33 | service = BlobServiceClient.from_connection_string(conn_str=connection_string) 34 | 35 | container = service.get_container_client(blob_container_name) 36 | if not container.exists(): 37 | container.create_container(public_access="Container") 38 | 39 | with open(hexfile, "rb") as data: 40 | container.upload_blob(filename, data, overwrite=True) 41 | 42 | data = bytearray(hash, "utf-8") 43 | container.upload_blob(filename + ".hash", data, overwrite=True) 44 | print("Success") 45 | 46 | 47 | upload_file("TeensyFirmware.TEENSY40.hex", "firmware") 48 | -------------------------------------------------------------------------------- /AdaKiosk/Controls/ContactPanel.xaml.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | using AdaKiosk.Utilities; 4 | using System; 5 | using System.Windows.Controls; 6 | 7 | namespace AdaKiosk.Controls 8 | { 9 | /// 10 | /// Interaction logic for ContactPanel.xaml 11 | /// 12 | public partial class ContactPanel : UserControl 13 | { 14 | NetworkObserver observer; 15 | 16 | public ContactPanel() 17 | { 18 | InitializeComponent(); 19 | } 20 | 21 | internal NetworkObserver Observer 22 | { 23 | get => observer; 24 | set 25 | { 26 | if (this.observer != null) 27 | { 28 | observer.NetworkStatusChanged -= OnNetworkStatusChanged; 29 | } 30 | observer = value; 31 | if (observer != null) 32 | { 33 | observer.NetworkStatusChanged += OnNetworkStatusChanged; 34 | } 35 | } 36 | } 37 | 38 | private void OnNetworkStatusChanged(object sender, EventArgs e) 39 | { 40 | TextBlockAddress.Text = observer?.IpAddress; 41 | } 42 | 43 | public void Show() 44 | { 45 | TextBlockContact.Text = "helloada@microsoft.com "; 46 | TextBlockIssues.Text = "https://github.com/microsoft/ada"; 47 | 48 | var assembly = this.GetType().Assembly; 49 | this.TextBlockVersion.Text = assembly.GetName().Version.ToString(); 50 | this.TextBlockUserName.Text = Environment.GetEnvironmentVariable("USERNAME"); 51 | 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /AdaKiosk/readme.md: -------------------------------------------------------------------------------- 1 | ## AdaKiosk 2 | 3 | This is a Windows WPF app that provides a nice Kiosk experience to 4 | accompany Ada. 5 | 6 | Install: [setup.exe](https://adaserverstorage.blob.core.windows.net/adakiosk/ClickOnce/setup.exe) 7 | 8 | ![kiosk](images/kiosk.png) 9 | 10 | ## Home page 11 | 12 | The app comes up by default in full screen mode. Press F11 to get out of full screen mode. 13 | 14 | This page is the default and provides access to the beautiful blog on Ada detailing the design 15 | concepts and construction effort. This app connects to Ada via the very cool new [Azure Web Pub Sub service](../AdaWebPubSub/readme.md), 16 | and shows realtime information on what Ada is thinking while also allowing the user to control Ada. 17 | It also auto-updates itself via the [AdaKioskService](../AdaKioskService/readme.md). 18 | 19 | ![image](images/home.png) 20 | 21 | ## Simulation 22 | 23 | This page runs a visual simulation of what Ada is doing. You 24 | can also click on the kitchens to generate random color events. 25 | This page will also tell you when Ada is sleeping and whether the 26 | Ada server is offline. 27 | 28 | ![image](images/simulation.png) 29 | 30 | ## Control 31 | 32 | This page provides some simple controls over Ada so you override the Ada 33 | default schedule with the On/Off buttons, you can set specific colors or 34 | pick the specified emotion and you can run specific animations as defined 35 | in the Server/config.json. 36 | 37 | ![image](images/control.png) 38 | 39 | ## Debug 40 | 41 | This is an advanced page for testing individual LED's on Ada. 42 | This page is only made visible when a /debug/true message is sent 43 | to the Kiosk. 44 | 45 | 46 | ## Unit Testing 47 | 48 | See [AdaKioskUnitTest.exe](../AdaKioskUnitTest/readme.md). -------------------------------------------------------------------------------- /RpiController/Ports/SerialPort.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | #ifndef SERIAL_COM_SERIALPORT_HPP 5 | #define SERIAL_COM_SERIALPORT_HPP 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include "Port.h" 12 | 13 | enum Parity { 14 | Parity_None = (0x0100), 15 | Parity_Odd = (0x0200), 16 | Parity_Even = (0x0400), 17 | Parity_Mark = (0x0800), 18 | Parity_Space = (0x1000) 19 | }; 20 | 21 | enum StopBits 22 | { 23 | StopBits_None = 0, 24 | StopBits_10 = (0x0001), 25 | StopBits_15 = (0x0002), 26 | StopBits_20 = (0x0004) 27 | }; 28 | 29 | enum Handshake 30 | { 31 | Handshake_None, 32 | Handshake_XonXoff, 33 | Handshake_RequestToSend, 34 | Handshake_RequestToSendXonXoff 35 | }; 36 | 37 | struct SerialPortInfo 38 | { 39 | std::wstring displayName; 40 | std::wstring portName; 41 | int vid; 42 | int pid; 43 | }; 44 | 45 | class SerialPort : public Port 46 | { 47 | public: 48 | SerialPort(); 49 | ~SerialPort(); 50 | 51 | // open the serial port 52 | virtual int connect(const char* portName, int baudRate); 53 | 54 | // write to the serial port 55 | virtual int write(const uint8_t* ptr, int count); 56 | 57 | // read a given number of bytes from the port. 58 | virtual int read(uint8_t* buffer, int bytesToRead); 59 | 60 | // close the port. 61 | virtual void close(); 62 | 63 | virtual bool isClosed(); 64 | 65 | static std::vector FindSerialPorts(int vid, int pid); 66 | 67 | virtual void flush(); 68 | private: 69 | int setAttributes(int baud_rate, Parity parity, int data_bits, StopBits bits, Handshake hs, int readTimeout, int writeTimeout); 70 | 71 | 72 | class serialport_impl; 73 | std::unique_ptr impl_; 74 | }; 75 | 76 | #endif -------------------------------------------------------------------------------- /Server/zone_map_2.json: -------------------------------------------------------------------------------- 1 | { 2 | "core_leds": [ 3 | { 4 | "col": 1, 5 | "leds": [ 10, 23, 35, 48, 60, 73, 84 ] 6 | }, 7 | { 8 | "col": 3, 9 | "leds": [ 12, 24, 38, 50, 62, 74, 88 ] 10 | }, 11 | { 12 | "col": 5, 13 | "leds": [ 14, 27, 39, 52, 63, 75, 88 ] 14 | }, 15 | { 16 | "col": 6, 17 | "leds": [ 9, 22, 33, 46, 58, 72, 84 ] 18 | }, 19 | { 20 | "col": 13, 21 | "leds": [ 10, 23, 36, 48, 61, 72, 85 ] 22 | } 23 | ], 24 | "zone_leds": [ 25 | { 26 | "col": 15, 27 | "zone": 1, 28 | "length": 391 29 | }, 30 | { 31 | "col": 0, 32 | "zone": 1, 33 | "length": 382 34 | }, 35 | { 36 | "col": 2, 37 | "zone": 1, 38 | "length": 391 39 | }, 40 | { 41 | "col": 4, 42 | "zone": 3, 43 | "length": 319 44 | }, 45 | { 46 | "col": 9, 47 | "zone": 3, 48 | "length": 388 49 | }, 50 | { 51 | "col": 14, 52 | "zone": 4, 53 | "length": 276 54 | }, 55 | { 56 | "col": 12, 57 | "zone": 4, 58 | "length": 75 59 | }, 60 | { 61 | "col": 7, 62 | "zone": 4, 63 | "length": 319 64 | }, 65 | { 66 | "col": 11, 67 | "zone": 4, 68 | "length": 366 69 | }, 70 | { 71 | "col": 8, 72 | "zone": 4, 73 | "length": 195 74 | } 75 | ] 76 | } -------------------------------------------------------------------------------- /TeensyUnitTest/TeensyUnitTest.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29306.81 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TeensyUnitTest", "TeensyUnitTest.vcxproj", "{14AF200B-CC19-4214-A969-F7BE055E7339}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Debug|x64 = Debug|x64 12 | Debug|x86 = Debug|x86 13 | Release|Any CPU = Release|Any CPU 14 | Release|x64 = Release|x64 15 | Release|x86 = Release|x86 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {14AF200B-CC19-4214-A969-F7BE055E7339}.Debug|Any CPU.ActiveCfg = Debug|Win32 19 | {14AF200B-CC19-4214-A969-F7BE055E7339}.Debug|x64.ActiveCfg = Debug|x64 20 | {14AF200B-CC19-4214-A969-F7BE055E7339}.Debug|x64.Build.0 = Debug|x64 21 | {14AF200B-CC19-4214-A969-F7BE055E7339}.Debug|x86.ActiveCfg = Debug|Win32 22 | {14AF200B-CC19-4214-A969-F7BE055E7339}.Debug|x86.Build.0 = Debug|Win32 23 | {14AF200B-CC19-4214-A969-F7BE055E7339}.Release|Any CPU.ActiveCfg = Release|Win32 24 | {14AF200B-CC19-4214-A969-F7BE055E7339}.Release|x64.ActiveCfg = Release|x64 25 | {14AF200B-CC19-4214-A969-F7BE055E7339}.Release|x64.Build.0 = Release|x64 26 | {14AF200B-CC19-4214-A969-F7BE055E7339}.Release|x86.ActiveCfg = Release|Win32 27 | {14AF200B-CC19-4214-A969-F7BE055E7339}.Release|x86.Build.0 = Release|Win32 28 | EndGlobalSection 29 | GlobalSection(SolutionProperties) = preSolution 30 | HideSolutionNode = FALSE 31 | EndGlobalSection 32 | GlobalSection(ExtensibilityGlobals) = postSolution 33 | SolutionGuid = {F90902D3-80E7-4F67-B5E1-1B83EDCE346C} 34 | EndGlobalSection 35 | EndGlobal 36 | -------------------------------------------------------------------------------- /AdaKiosk/AdaKiosk.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | WinExe 5 | net7.0-windows 6 | true 7 | $(ADA_KIOSK_STORAGE_URL) 8 | Microsoft Corporation 9 | https://github.com/microsoft/ada 10 | 11 | 12 | 13 | 14 | PreserveNewest 15 | 16 | 17 | PreserveNewest 18 | 19 | 20 | PreserveNewest 21 | 22 | 23 | PreserveNewest 24 | 25 | 26 | PreserveNewest 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /DmxController/lights_off.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. 2 | # Licensed under the MIT license. 3 | from dmx import Dmx, DmxDevice 4 | 5 | # In RPi, use the symlinks in /dev/serial/by-id/ instead of /dev/serialX 6 | DMX_SERIAL_PORT = "COM3" 7 | 8 | SERVER_PORT = 12345 9 | SOCKET_BUFFER_SIZE = 32768 10 | 11 | dmx = Dmx(DMX_SERIAL_PORT) 12 | 13 | DMX_UNIVERSE = 1 14 | 15 | # This device will be assigned start DMX address 1, and will use up to address 6 16 | dmx.add_device(DMX_UNIVERSE, DmxDevice("light_1", "power_lights", 6, 1)) 17 | 18 | # This device will be assigned start DMX address 7 (6 from the previous one, + 1) 19 | # and will use up to address 12 20 | dmx.add_device(DMX_UNIVERSE, DmxDevice("light_2", "power_lights", 6, 1)) 21 | 22 | # This device will be assigned start DMX address 13 (6 from the previous one, + 1) 23 | # and will use up to address 12 24 | dmx.add_device(DMX_UNIVERSE, DmxDevice("light_3", "power_lights", 6, 1)) 25 | 26 | # This device will be assigned start DMX address 19 (6 from the previous one, + 1) 27 | # and will use up to address 18 28 | dmx.add_device(DMX_UNIVERSE, DmxDevice("light_4", "power_lights", 6, 1)) 29 | 30 | # This device will be assigned start DMX address 29 (6 from the previous one, + 1) 31 | # and will use up to address 23 32 | dmx.add_device(DMX_UNIVERSE, DmxDevice("light_5", "power_lights", 6, 1)) 33 | 34 | # This device will be assigned start DMX address 31 (6 from the previous one, + 1) 35 | # and will use up to address 30 36 | dmx.add_device(DMX_UNIVERSE, DmxDevice("light_6", "power_lights", 6, 1)) 37 | 38 | # Value mapping is: [red, green, blue, white, amber, uv] 39 | dmx.set_output_by_type(DMX_UNIVERSE, "power_lights", [0, 0, 0, 0, 0, 0]) 40 | 41 | all_lights = ["light_1", "light_2", "light_3", "light_4", "light_5", "light_6"] 42 | 43 | new_colors = [[255, 0, 0, 0, 0, 0]] * 6 44 | dmx.smooth_fade(DMX_UNIVERSE, all_lights, new_colors, new_colors, 10) 45 | -------------------------------------------------------------------------------- /AdaKiosk/Utilities/BatteryInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.Runtime.InteropServices; 7 | 8 | namespace AdaKiosk.Utilities 9 | { 10 | enum ACLineStatus : byte 11 | { 12 | Offline = 0, 13 | Online = 1, 14 | Unknown = 255 15 | } 16 | 17 | enum BatteryFlag : byte 18 | { 19 | High = 1, 20 | Low = 2, 21 | Critical = 4, 22 | Charging = 8, 23 | NoBattery = 128, 24 | Unknown = 255 25 | } 26 | 27 | enum SystemStatus : byte 28 | { 29 | SaverOff = 0, 30 | SaverOn = 1 31 | } 32 | 33 | [StructLayout(LayoutKind.Sequential)] 34 | internal struct SYSTEM_POWER_STATUS 35 | { 36 | public ACLineStatus ACLineStatus; 37 | public BatteryFlag BatteryFlag; 38 | 39 | /// 40 | /// The percentage of full battery charge remaining. This member can be a value in the range 0 to 100, or 255 if status is unknown. 41 | /// 42 | public byte BatteryLifePercent; 43 | 44 | public SystemStatus SystemStatusFlag; 45 | 46 | /// 47 | /// The number of seconds of battery life remaining, or –1 if remaining seconds are unknown or if the device is connected to AC power. 48 | /// 49 | public uint BatteryLifeTime; 50 | 51 | /// 52 | /// The number of seconds of battery life remaining, or –1 if remaining seconds are unknown or if the device is connected to AC power. 53 | /// 54 | public uint BatteryFullLifeTime; 55 | } 56 | 57 | internal class BatteryInfo 58 | { 59 | [DllImport("Kernel32")] 60 | internal static extern bool GetSystemPowerStatus(out SYSTEM_POWER_STATUS lpSystemPowerStatus); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /RpiController/Ports/TcpClientPort.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | #ifndef SERIAL_COM_TCPCLIENTPORT_HPP 5 | #define SERIAL_COM_TCPCLIENTPORT_HPP 6 | 7 | #include "Port.h" 8 | #include 9 | 10 | class TcpClientPort : public Port 11 | { 12 | public: 13 | TcpClientPort(); 14 | ~TcpClientPort(); 15 | 16 | // Connect can set you up two different ways. Pass 0 for local port to get any free local 17 | // port. localHost allows you to be specific about which local adapter to use in case you 18 | // have multiple ethernet adapters. 19 | void connect(const std::string& localHost, int localPort, const std::string& remoteHost, int remotePort); 20 | 21 | // Connect to server using any available local address and port. 22 | void connect(const std::string& remoteHost, int remotePort); 23 | 24 | // start listening on the local adapter, and accept one connection request from a remote machine. 25 | void accept(const std::string& localHost, int localPort); 26 | 27 | // write the given bytes to the port, return number of bytes written or -1 if error. 28 | virtual int write(const uint8_t* ptr, int count); 29 | 30 | // read some bytes from the port, return the number of bytes read or -1 if error. 31 | virtual int read(uint8_t* buffer, int bytesToRead); 32 | 33 | // return true if there is something available to read 34 | bool available(); 35 | 36 | // close the port. 37 | virtual void close(); 38 | 39 | virtual void flush() {}; 40 | 41 | virtual bool isClosed(); 42 | 43 | std::string remoteAddress(); 44 | int remotePort(); 45 | 46 | std::string localAddress(); 47 | int localPort(); 48 | 49 | static std::string getHostName(); 50 | 51 | static std::string getHostByName(const std::string& name); 52 | 53 | private: 54 | class TcpSocketImpl; 55 | std::unique_ptr impl_; 56 | }; 57 | 58 | 59 | #endif // SERIAL_COM_UDPCLIENTPORT_HPP 60 | -------------------------------------------------------------------------------- /AdaKioskUnitTest/Program.cs: -------------------------------------------------------------------------------- 1 | using AdaKiosk.Utilities; 2 | using Azure.Messaging.WebPubSub; 3 | using System; 4 | using System.Net.WebSockets; 5 | using System.Text; 6 | using System.Text.Json; 7 | using System.Threading.Tasks; 8 | using Websocket.Client; 9 | 10 | namespace AdaKioskUnitTest 11 | { 12 | class Program 13 | { 14 | WebPubSubGroup client; 15 | 16 | static async Task Main(string[] args) 17 | { 18 | Program p = new Program(); 19 | await Task.Run(p.SocketTest); 20 | while (true) 21 | { 22 | Console.Write("Enter a command to send or 'x' to exit: "); 23 | string cmd = Console.ReadLine(); 24 | if (cmd == "x") 25 | { 26 | return; 27 | } 28 | _ = p.client.SendMessage("\"" + cmd + "\""); 29 | } 30 | } 31 | 32 | async void SocketTest() 33 | { 34 | var connectionString = Environment.GetEnvironmentVariable("ADA_WEBPUBSUB_CONNECTION_STRING"); 35 | if (string.IsNullOrEmpty(connectionString)) 36 | { 37 | Console.WriteLine("Please set ADA_WEBPUBSUB_CONNECTION_STRING"); 38 | return; 39 | } 40 | client = new WebPubSubGroup(); 41 | client.MessageReceived += Client_MessageReceived; 42 | await client.Connect(connectionString, "AdaKiosk", "unittest", "demogroup", TimeSpan.FromSeconds(30)); 43 | Console.WriteLine("Ada Web PubSub connected..."); 44 | } 45 | 46 | private void Client_MessageReceived(object sender, string msg) 47 | { 48 | if (!msg.Contains("fromUserId\":\"unittest")) 49 | { 50 | Console.WriteLine(msg); 51 | Console.WriteLine("Enter a command to send or 'x' to exit: "); 52 | } 53 | } 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /TeensyFirmware/readme.md: -------------------------------------------------------------------------------- 1 | ## TeensyFirmware 2 | 3 | You can build this project using VS Code after installing the [ 4 | PlatformIO extension](https://marketplace.visualstudio.com/items?itemName=platformio.platformio-ide). 5 | 6 | You'll have to wait for PlatformIO to finish installing before you attempt 7 | to build the code. Wait until the following spinning wheel is finished: 8 | 9 | ![installing](install.png) 10 | 11 | Then run `code TeensyFirmware.code-workspace` to load this workspace and use the PlatformIO build button 12 | in the VS Code taskbar, which is the check icon you see below: 13 | 14 | ![build](build.png) 15 | 16 | 17 | # Setup Teensy Firmware 18 | 19 | Typically you build this on your own developer machine, and not on `ada-core`. PlatformIO will build 20 | the `firmware.hex` file in `.pio\build\teensy40\` so to get this over to the raspberry pi devices, 21 | there's a `PublishFirmware.py` in the `Azure` folder. Run `python PublishFirmware.py` to send 22 | the built firmware.hex file to an Azure blob store using your `ADA_STORAGE_CONNECTION_STRING` 23 | environment variable. 24 | 25 | The `ada_server.py` script will check this Azure firmware once a day and will send it to the 26 | raspberry pi devices for automatic over the air firmware upgrades. 27 | 28 | You should see it complete the programming job quickly. 29 | 30 | # Raspberry Pi Setup 31 | 32 | On each new Raspberry Pi you will need to run these setup steps: 33 | 34 | ``` 35 | sudo usermod -a -G gpio pi 36 | sudo apt-get install libusb-dev 37 | cd ~ 38 | mkdir git 39 | cd git 40 | git clone https://github.com/PaulStoffregen/teensy_loader_cli 41 | make 42 | ``` 43 | 44 | The resulting `teensy_loader_cli` tool is used by the `TeensyFirmware\flash.sh` script to update the firmware on the Teensy. 45 | 46 | The Teensy should show up in a path like this `/dev/serial/by-id/usb-Teensyduino_USB_Serial_6116170-if00`. If it is not there then there may be a problem with the Teensy or the serial connection. 47 | -------------------------------------------------------------------------------- /AdaKiosk/Utilities/ZoneMap.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using System.Linq; 7 | using System.Runtime.Serialization; 8 | using System.Runtime.Serialization.Json; 9 | using System.Text; 10 | using System.Threading.Tasks; 11 | using System.Windows.Documents; 12 | 13 | namespace AdaSimulation 14 | { 15 | [DataContract] 16 | public class ZoneMap 17 | { 18 | [DataMember(Name="zone_leds")] 19 | public List ZoneLeds; 20 | 21 | [DataMember(Name = "core_leds")] 22 | public List CoreLeds; 23 | 24 | internal string FileName; 25 | 26 | public void RemoveZone(int zone) 27 | { 28 | foreach (var item in ZoneLeds.ToArray()) 29 | { 30 | if (item.zone == zone) 31 | { 32 | ZoneLeds.Remove(item); 33 | } 34 | } 35 | } 36 | 37 | public static ZoneMap Load(string filename) 38 | { 39 | using (var fs = new FileStream(filename, FileMode.Open, FileAccess.Read)) 40 | { 41 | DataContractJsonSerializer s = new DataContractJsonSerializer(typeof(ZoneMap)); 42 | var result = s.ReadObject(fs) as ZoneMap; 43 | result.FileName = filename; 44 | return result; 45 | } 46 | } 47 | 48 | public void Save(string filename) 49 | { 50 | using (var fs = new FileStream(filename, FileMode.Create, FileAccess.Write)) 51 | { 52 | DataContractJsonSerializer s = new DataContractJsonSerializer(typeof(ZoneMap)); 53 | s.WriteObject(fs, this); 54 | } 55 | } 56 | } 57 | 58 | [DataContract] 59 | public class StripMap 60 | { 61 | [DataMember] 62 | public int col; 63 | 64 | [DataMember] 65 | public int zone = -1; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /TeensyFirmware/include/Timer.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | #ifndef _TIMER_H 4 | #define _TIMER_H 5 | 6 | #include 7 | 8 | #if (ARDUINO_TEENSY40) 9 | #include 10 | #else 11 | #include 12 | #include 13 | #endif 14 | #if defined(__AVR__) || defined(KINETISK) || defined(__MKL26Z64__) 15 | #error "Sorry, this code only works on 32 bit Teensy boards, Teensy 4.0 specifically. AVR isn't supported." 16 | #endif 17 | 18 | class Timer 19 | { 20 | private: 21 | uint32_t start_time; 22 | uint32_t end_time; 23 | bool running; 24 | 25 | public: 26 | static void init() { 27 | // Very accurate timing for uS scale time periods (used for determining frame latch pulse delay) 28 | // https://github.com/manitou48/teensy4/blob/master/gpt_micros.ino 29 | CCM_CCGR1 |= CCM_CCGR1_GPT(CCM_CCGR_ON) | 30 | CCM_CCGR1_GPT1_SERIAL(CCM_CCGR_ON); // enable GPT1 module 31 | GPT1_CR = 0; 32 | GPT1_PR = 23; // prescale+1 33 | GPT1_SR = 0x3F; // clear all prior status 34 | GPT1_CR = GPT_CR_EN | GPT_CR_CLKSRC(1) | GPT_CR_FRR;// 1 ipg 24mhz 4 32khz 35 | } 36 | 37 | void start() 38 | { 39 | start_time = now(); 40 | running = true; 41 | } 42 | 43 | void stop() { 44 | end_time = now(); 45 | running = false; 46 | } 47 | 48 | uint32_t microseconds() { 49 | auto end = get_end(); 50 | return end - start_time; 51 | } 52 | 53 | float milliseconds() { 54 | auto end = get_end(); 55 | return (float)(end - start_time) / 1000.0; 56 | } 57 | 58 | float seconds() { 59 | auto end = get_end(); 60 | return (float)(end - start_time) / 1000000.0; 61 | } 62 | 63 | uint32_t now() 64 | { 65 | return GPT1_CNT; 66 | } 67 | 68 | uint32_t get_end() { 69 | if (running) { 70 | return now(); 71 | } else { 72 | return end_time; 73 | } 74 | } 75 | 76 | }; 77 | 78 | 79 | #endif -------------------------------------------------------------------------------- /TeensyFirmware/include/Vector.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | #pragma once 4 | 5 | #include "SimpleString.h" 6 | 7 | template 8 | class Vector 9 | { 10 | T* items = nullptr; 11 | size_t allocated = 0; 12 | size_t used = 0; 13 | public: 14 | Vector() 15 | { 16 | } 17 | 18 | Vector(const Vector& other) 19 | { 20 | for (size_t i = 0; i < other.used; i++) 21 | { 22 | push_back(other[i]); 23 | } 24 | } 25 | 26 | ~Vector() 27 | { 28 | if (items != nullptr) 29 | { 30 | delete[] items; 31 | } 32 | } 33 | 34 | Vector& operator=(const Vector& other) 35 | { 36 | clear(); 37 | for (size_t i = 0; i < other.used; i++) 38 | { 39 | push_back(other[i]); 40 | } 41 | return *this; 42 | } 43 | 44 | void reserve(size_t newSize) 45 | { 46 | if (allocated < newSize) { 47 | 48 | T* newBuffer = new T[newSize]; 49 | if (newBuffer == nullptr) 50 | { 51 | CrashPrint("### Vector: out of memory!\r\n"); 52 | return; 53 | } 54 | for (size_t i = 0; i < used; i++) 55 | { 56 | newBuffer[i] = items[i]; 57 | } 58 | if (items != nullptr) { 59 | delete[] items; 60 | } 61 | items = newBuffer; 62 | allocated = newSize; 63 | } 64 | } 65 | 66 | void push_back(const T& x) 67 | { 68 | if (used == allocated) 69 | { 70 | size_t newSize = (allocated * 2) + 10; 71 | reserve(newSize); 72 | } 73 | items[used++] = x; 74 | } 75 | 76 | size_t reserved() const 77 | { 78 | return allocated; 79 | } 80 | 81 | size_t size() const 82 | { 83 | return used; 84 | } 85 | 86 | T& operator[](size_t index) const 87 | { 88 | return items[index]; 89 | } 90 | 91 | void clear() 92 | { 93 | used = 0; 94 | } 95 | }; -------------------------------------------------------------------------------- /TeensyUnitTest/ArduinoMock.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | #pragma once 4 | 5 | #include 6 | #include 7 | #include 8 | #include "TcpClientPort.h" 9 | 10 | const int HIGH = 1; 11 | const int LOW = 0; 12 | 13 | void digitalWrite(int pin, int value); 14 | 15 | const int INPUT = 0; 16 | const int OUTPUT = 1; 17 | 18 | void pinMode(int pin, int mode); 19 | 20 | void delay(float seconds); 21 | 22 | // mocks the Arduino Serial library 23 | class SerialInput 24 | { 25 | public: 26 | void setTimeout(int t) { 27 | 28 | } 29 | 30 | bool available() 31 | { 32 | if (connected) { 33 | return client.available(); 34 | } 35 | return position < size; 36 | } 37 | 38 | void connect(const std::string& ipaddress, int port) 39 | { 40 | client.accept(ipaddress, port); 41 | connected = true; 42 | } 43 | 44 | int readBytes(char* buffer, int length) 45 | { 46 | if (connected) 47 | { 48 | int rc = client.read((uint8_t*)buffer, length); 49 | if (rc < 0) 50 | { 51 | return 0; 52 | } 53 | return rc; 54 | } 55 | else if (position < size) 56 | { 57 | int available = size - position; 58 | if (available < length) 59 | { 60 | length = available; 61 | } 62 | ::memcpy(buffer, &this->buffer[position], length); 63 | position += length; 64 | return length; 65 | } 66 | return 0; 67 | } 68 | 69 | void setBuffer(char* buffer, int length) 70 | { 71 | this->buffer = buffer; 72 | this->size = length; 73 | this->position = 0; 74 | } 75 | 76 | void print(const char* message) 77 | { 78 | if (connected) 79 | { 80 | int len = (int)strlen(message); 81 | client.write((const uint8_t*)message, len); 82 | } 83 | else 84 | { 85 | std::cout << message << "\n"; 86 | } 87 | } 88 | 89 | void flush() {} 90 | 91 | private: 92 | char* buffer; 93 | int size; 94 | int position; 95 | TcpClientPort client; 96 | bool connected; 97 | }; 98 | 99 | extern SerialInput Serial; -------------------------------------------------------------------------------- /Server/ping_server.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. 2 | # Licensed under the MIT license. 3 | import argparse 4 | import json 5 | import socket 6 | 7 | 8 | def receive_rgb_lists_from_server(server_endpoint, buffer_size): 9 | with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: 10 | sock.connect(server_endpoint) 11 | sock.sendall(bytes("GET\n", "utf-8")) 12 | json_msg = "" 13 | while len(json_msg) == 0: 14 | json_msg = str(sock.recv(buffer_size), "utf-8") 15 | command = json.loads(json_msg) 16 | if command["command"] == "sensei": 17 | colors = command["colors"] 18 | received_rgb_lists_per_zone = [(x[0], x[1], x[2]) for x in colors] 19 | 20 | return received_rgb_lists_per_zone 21 | 22 | 23 | def update_server_with_ipcamera_emotion(server_endpoint, emotion, score): 24 | with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: 25 | sock.connect(server_endpoint) 26 | sock.sendall(bytes("IPCAMERA {} {}\n".format(emotion, score), "utf-8")) 27 | 28 | 29 | if __name__ == "__main__": 30 | parser = argparse.ArgumentParser( 31 | description="Pulls Sensei data from the ada_server for display on the raspberry pi" 32 | ) 33 | parser.add_argument( 34 | "--ip", 35 | help="optional IP address of the server (default 'localhost')", 36 | default="localhost", 37 | ) 38 | parser.add_argument( 39 | "--emotion", 40 | help="optional parameter to set the ipcamera emotion in the server", 41 | default=None, 42 | ) 43 | parser.add_argument( 44 | "--score", 45 | help="optional parameter to set the ipcamera score for teh emotion", 46 | default=None, 47 | ) 48 | args = parser.parse_args() 49 | 50 | server_port = 12345 51 | socket_buffer_size = 32768 52 | server_endpoint = (args.ip, server_port) 53 | if args.emotion is not None: 54 | update_server_with_ipcamera_emotion(server_endpoint, args.emotion, args.score) 55 | data = receive_rgb_lists_from_server(server_endpoint, socket_buffer_size) 56 | 57 | print(data) 58 | -------------------------------------------------------------------------------- /AdaKiosk/LedControl.xaml.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | using AdaSimulation; 4 | using System; 5 | using System.Windows.Controls; 6 | using System.Windows.Media; 7 | 8 | namespace AdaKiosk 9 | { 10 | /// 11 | /// Interaction logic for LedControl.xaml 12 | /// 13 | public partial class LedControl : UserControl 14 | { 15 | Led model; 16 | 17 | public LedControl() 18 | { 19 | InitializeComponent(); 20 | } 21 | 22 | public Led Model 23 | { 24 | get 25 | { 26 | return model; 27 | } 28 | 29 | set 30 | { 31 | if (model != null) 32 | { 33 | model.PropertyChanged -= OnLedPropertyChanged; 34 | } 35 | model = value; 36 | 37 | if (model != null) 38 | { 39 | model.PropertyChanged += OnLedPropertyChanged; 40 | UpdateWidth(); 41 | UpdateColor(); 42 | } 43 | } 44 | } 45 | 46 | SolidColorBrush brush; 47 | 48 | private void OnLedPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) 49 | { 50 | if (e.PropertyName == "Color") 51 | { 52 | UpdateColor(); 53 | } 54 | else if (e.PropertyName == "Width") 55 | { 56 | UpdateWidth(); 57 | } 58 | } 59 | 60 | private void UpdateColor() 61 | { 62 | if (this.model != null) 63 | { 64 | if (brush == null) 65 | { 66 | brush = new SolidColorBrush(); 67 | Border.Background = brush; 68 | } 69 | brush.Color = model.Color; 70 | } 71 | } 72 | 73 | private void UpdateWidth() 74 | { 75 | if (this.model != null && this.model.Width > 0) 76 | { 77 | this.Width = this.model.Width; 78 | } 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /AdaKiosk/Properties/PublishProfiles/ClickOnceProfile.pubxml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 0 8 | 1.0.10.0 9 | True 10 | Release 11 | True 12 | true 13 | True 14 | Web 15 | https://adaserverstorage.blob.core.windows.net/adakiosk/ClickOnce/ 16 | False 17 | True 18 | 50D58171E20BB6188B199ACA7C20A26DA0DFBF35 19 | http://timestamp.comodoca.com 20 | True 21 | False 22 | ARM 23 | AdaKiosk 24 | bin\Release\net7.0-windows\app.publish\ 25 | bin\publish\ 26 | Chris Lovett 27 | ClickOnce 28 | False 29 | False 30 | False 31 | sha384RSA 32 | True 33 | https://github.com/microsoft/ada/tree/main/AdaKiosk 34 | net7.0-windows 35 | True 36 | Foreground 37 | False 38 | Publish.html 39 | 40 | 41 | 42 | True 43 | .NET Desktop Runtime 7.0.2 (x64) 44 | 45 | 46 | -------------------------------------------------------------------------------- /Server/dump_emotions.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. 2 | # Licensed under the MIT license. 3 | import json 4 | import os 5 | from collections import namedtuple 6 | 7 | from azure.cosmosdb.table import TableService 8 | from dateutil import tz 9 | 10 | script_dir = os.path.dirname(os.path.abspath(__file__)) 11 | 12 | 13 | def dump_emotions(config, partition): 14 | query = "FaceCount gt 0 and PartitionKey eq '" + partition + "'" 15 | all_sentiments = sorted(config.colors_for_emotions.keys()) 16 | headers = ["Timestamp"] 17 | for sentiment in all_sentiments: 18 | headers += [sentiment] 19 | 20 | filename = partition + ".csv" 21 | with open(filename, "w") as log: 22 | log.write(",".join(headers) + "\n") 23 | 24 | storage_account = TableService(connection_string=config.connection_string) 25 | 26 | count = 0 27 | try: 28 | query_results = list( 29 | storage_account.query_entities( 30 | "Psi", filter=query, num_results=1000, timeout=30 31 | ) 32 | ) 33 | for entity in query_results: 34 | for x in ["Face1", "Face2", "Face3", "Face4", "Face5"]: 35 | d = entity["Timestamp"] 36 | localtime = d.astimezone(tz.tzlocal()) 37 | row = [str(localtime)] 38 | for sentiment in all_sentiments: 39 | key = "{}_{}".format(x, sentiment) 40 | if key in entity: 41 | row += [entity[key]] 42 | if len(row) > 1: 43 | log.write(",".join(row) + "\n") 44 | count += 1 45 | except Exception as e: 46 | print(e) 47 | 48 | print("wrote {} records to {}".format(count, filename)) 49 | 50 | 51 | def dump_emotions_from_all_cameras(config): 52 | for zone in config.camera_zones: 53 | for cam in zone: 54 | dump_emotions(config, cam) 55 | 56 | 57 | if __name__ == "__main__": 58 | with open(os.path.join(script_dir, "config.json"), "r") as f: 59 | d = json.load(f) 60 | config = namedtuple("Config", d.keys())(*d.values()) 61 | 62 | dump_emotions_from_all_cameras(config) 63 | -------------------------------------------------------------------------------- /RpiController/Ports/linux/LinuxFindSerialPorts.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | #include "../SerialPort.h" 5 | #include "Utils.h" 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | std::vector SerialPort::FindSerialPorts(int vid, int pid) 15 | { 16 | unused(vid); // avoid warning: unused parameter 17 | unused(pid); // avoid warning: unused parameter 18 | // todo: alternative: probably need to do an lstat on '/dev/serial/by-id' and find 19 | // something that looks like PX4 and return that name, or follow the symbolic link to /dev/ttyACM0... 20 | 21 | std::string bash_command = 22 | "bash -c '" 23 | "for sysdevpath in $(find /sys/bus/usb/devices/usb*/ -name dev); do " 24 | "syspath=\"${sysdevpath%/dev}\"; devname=\"$(udevadm info -q name -p $syspath)\"; " 25 | "[[ \"$devname\" == \"bus/\"* ]] && continue; " 26 | "eval \"$(udevadm info -q property --export -p $syspath)\"; " 27 | "[[ -z \"$ID_SERIAL\" ]] && continue; " 28 | "echo \"/dev/$devname \"$ID_SERIAL\"\"; " 29 | "done" 30 | "'"; 31 | 32 | std::vector ports; 33 | std::string command_result; 34 | { 35 | std::array buffer; 36 | std::shared_ptr pipe(popen(bash_command.c_str(), "r"), pclose); 37 | if (!pipe) throw std::runtime_error("popen() failed!"); 38 | while (!feof(pipe.get())) { 39 | if (fgets(buffer.data(), 128, pipe.get()) != nullptr) 40 | command_result += buffer.data(); 41 | } 42 | } 43 | 44 | { 45 | std::stringstream ss(command_result); 46 | std::string port_name, display_name; 47 | std::wstring_convert> converter; 48 | 49 | while (std::getline(ss, port_name, ' ') && std::getline(ss, display_name)) { 50 | SerialPortInfo portInfo; 51 | portInfo.pid = 0; 52 | portInfo.vid = 0; 53 | portInfo.displayName = converter.from_bytes(display_name); 54 | portInfo.portName = converter.from_bytes(port_name); 55 | 56 | ports.push_back(portInfo); 57 | } 58 | } 59 | 60 | return ports; 61 | } 62 | -------------------------------------------------------------------------------- /AdaKioskService/ProjectInstaller.Designer.cs: -------------------------------------------------------------------------------- 1 | 2 | namespace AdaKioskService 3 | { 4 | partial class ProjectInstaller 5 | { 6 | /// 7 | /// Required designer variable. 8 | /// 9 | private System.ComponentModel.IContainer components = null; 10 | 11 | /// 12 | /// Clean up any resources being used. 13 | /// 14 | /// true if managed resources should be disposed; otherwise, false. 15 | protected override void Dispose(bool disposing) 16 | { 17 | if (disposing && (components != null)) 18 | { 19 | components.Dispose(); 20 | } 21 | base.Dispose(disposing); 22 | } 23 | 24 | #region Component Designer generated code 25 | 26 | /// 27 | /// Required method for Designer support - do not modify 28 | /// the contents of this method with the code editor. 29 | /// 30 | private void InitializeComponent() 31 | { 32 | this.serviceProcessInstaller1 = new System.ServiceProcess.ServiceProcessInstaller(); 33 | this.serviceInstaller1 = new System.ServiceProcess.ServiceInstaller(); 34 | // 35 | // serviceProcessInstaller1 36 | // 37 | this.serviceProcessInstaller1.Account = System.ServiceProcess.ServiceAccount.LocalSystem; 38 | this.serviceProcessInstaller1.Password = null; 39 | this.serviceProcessInstaller1.Username = null; 40 | // 41 | // serviceInstaller1 42 | // 43 | this.serviceInstaller1.ServiceName = "AdaKioskService"; 44 | this.serviceInstaller1.StartType = System.ServiceProcess.ServiceStartMode.Automatic; 45 | // 46 | // ProjectInstaller 47 | // 48 | this.Installers.AddRange(new System.Configuration.Install.Installer[] { 49 | this.serviceProcessInstaller1, 50 | this.serviceInstaller1}); 51 | 52 | } 53 | 54 | #endregion 55 | 56 | private System.ServiceProcess.ServiceProcessInstaller serviceProcessInstaller1; 57 | private System.ServiceProcess.ServiceInstaller serviceInstaller1; 58 | } 59 | } -------------------------------------------------------------------------------- /AdaKiosk/publish.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | cd %~dp0 3 | SET ROOT=%~dp0 4 | set BITS=.\bin\publish 5 | 6 | if "%ADA_STORAGE_CONNECTION_STRING%x" == "x" goto :noconnection 7 | for /f "usebackq" %%i in (`tools\xsl -e -s Version\version.xsl Version\version.props`) do ( 8 | set VERSION=%%i 9 | ) 10 | echo ### Publishing version %VERSION%... 11 | 12 | copy .\Version\Version.props ..\AdaKioskService\Version\Version.props 13 | call ..\AdaKioskService\build.cmd 14 | if ERRORLEVEL 1 goto :err_build_service 15 | 16 | %ROOT%\tools\UpdateVersion.exe %VERSION% %ROOT%\Properties\PublishProfiles\ClickOnceProfile.pubxml 17 | 18 | msbuild /target:restore AdaKiosk.sln 19 | if ERRORLEVEL 1 goto :err_restore 20 | 21 | msbuild /p:Configuration=Release "/p:Platform=Any CPU" AdaKiosk.sln 22 | if ERRORLEVEL 1 goto :err_build 23 | 24 | msbuild /target:publish /p:PublishProfile=.\Properties\PublishProfiles\ClickOnceProfile.pubxml AdaKiosk.csproj /p:PublishDir=.\bin\publish /p:Configuration=Release "/p:Platform=Any CPU" 25 | if ERRORLEVEL 1 goto :err_publish 26 | 27 | if not EXIST "%BITS%\AdaKiosk.application" goto :nobits 28 | if not EXIST "%BITS%\setup.exe" goto :nobits 29 | 30 | %ROOT%\tools\CleanupPublishFolder.exe %VERSION% bin\publish 31 | 32 | echo "Please check contents of %BITS%..." 33 | pause 34 | 35 | set zipfile=bin\AdaKiosk.zip 36 | if exist %zipfile% del %zipfile% 37 | powershell -c "Compress-Archive -Path .\bin\Release\net7.0-windows\* -DestinationPath %zipfile%" 38 | if ERRORLEVEL 1 goto :err_zip 39 | 40 | echo Creating new github release for version %VERSION% 41 | gh release create %VERSION% "%zipfile%" "..\AdaKioskService\%serviceZipfile%" --generate-notes --title "AdaKiosk %VERSION%" 42 | if ERRORLEVEL 1 goto :err_gh 43 | 44 | goto :eof 45 | 46 | :err_restore 47 | echo nuget restore failed 48 | exit /b 1 49 | 50 | :err_build 51 | echo msbuild failed 52 | exit /b 1 53 | 54 | :err_publish 55 | popd 56 | echo dotnet publish failed 57 | exit /b 1 58 | 59 | :nobits 60 | echo %BITS% folder seems incomplete, please check publish step. 61 | exit /b 1 62 | 63 | :noconnection 64 | echo Please set ADA_STORAGE_CONNECTION_STRING 65 | exit /b 1 66 | 67 | :err_zip 68 | echo Error creating AdaKiosk.zip 69 | exit /b 1 70 | 71 | :err_gh 72 | echo Error creating github release 73 | exit /b 1 74 | 75 | :err_build_service 76 | echo Error building AdaKioskService 77 | exit /b 1 -------------------------------------------------------------------------------- /Server/color_test.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. 2 | # Licensed under the MIT license. 3 | import argparse 4 | import json 5 | import socketserver 6 | import time 7 | 8 | 9 | class Config: 10 | server_port = 12345 11 | debug = False 12 | debug_commands = False 13 | 14 | 15 | class GlobalState(object): 16 | colors = [[80, 0, 0], [0, 80, 0], [0, 0, 80]] 17 | color_index = 0 18 | color_time = time.time() 19 | 20 | 21 | class ClientHandler(socketserver.StreamRequestHandler): 22 | def handle(self): 23 | cmd = str(self.rfile.readline(), "utf-8").strip() 24 | 25 | if Config.debug: 26 | print(cmd, self.client_address) 27 | 28 | if cmd.startswith("GET"): 29 | self.get_sensei(cmd) 30 | else: 31 | self.wfile.write(bytes("{'error': 'invalid command'}", "utf-8")) 32 | 33 | def get_sensei(self, cmd): 34 | # parts = cmd.split(" ") 35 | # target = "adapi1" 36 | # sequence = 0 37 | 38 | # if len(parts) > 1: 39 | # target = parts[1] 40 | # if len(parts) > 2: 41 | # sequence = int(parts[2]) 42 | 43 | # cycle through the 3 colors every 4 seconds. 44 | index = (int)((time.time() - GlobalState.color_time) / 4) % 3 45 | command = { 46 | "command": "CrossFade", 47 | "colors": [GlobalState.colors[index]], 48 | "seconds": 4, 49 | } 50 | # {"command": "StartRain", "size": 12, "amount": 40.0 } 51 | 52 | json_string = json.dumps(command) 53 | if Config.debug_commands: 54 | print(json_string) 55 | self.wfile.write(bytes(json_string, "utf-8")) 56 | 57 | 58 | def _main(ip_address): 59 | endpoint = (ip_address, Config.server_port) 60 | try: 61 | server = socketserver.TCPServer(endpoint, ClientHandler) 62 | server.serve_forever() 63 | except Exception as e: 64 | print(e) 65 | 66 | 67 | if __name__ == "__main__": 68 | parser = argparse.ArgumentParser( 69 | "ada_server makes Sensei database available to the ada raspberry pi devices" 70 | ) 71 | parser.add_argument( 72 | "--ip", 73 | help="optional IP address of the server (default 'localhost')", 74 | default="localhost", 75 | ) 76 | args = parser.parse_args() 77 | _main(args.ip) 78 | -------------------------------------------------------------------------------- /Server/data.json: -------------------------------------------------------------------------------- 1 | { 2 | // This is what the data looks like from Sensei 3 | "PartitionKey": "2FKCamera01", "RowKey": "02518321640772419124", "Timestamp": datetime.datetime(2019, 4 | 10, 5 | 3, 6 | 21, 7 | 5, 8 | 27, 9 | 770949, tzinfo=tzutc()), "OriginatingTime": datetime.datetime(2019, 10 | 10, 11 | 3, 12 | 21, 13 | 5, 14 | 18, 15 | 461569, tzinfo=tzutc()), "OriginatingTime_Local": "2019-10-03T14: 05: 18.4615698-07: 00", "AddedTime": datetime.datetime(2019, 16 | 10, 17 | 3, 18 | 21, 19 | 5, 20 | 18, 21 | 567686, tzinfo=tzutc()), "AddedTime_Local": "2019-10-03T14: 05: 18.5676869-07: 00", "FaceCount": 0.13333333333333333, "Face1_Top": 273.0, 22 | "Face1_Left": 505.0, "Face1_Width": 81.5, "Face1_Height": 81.5, "Face1_PupilLeft_X": "524.088", "Face1_PupilLeft_Y": "296.2216", 23 | "Face1_PupilRight_X": "558.9354", "Face1_PupilRight_Y": "291.5338", "Face1_NoseTip_X": "557.5325", "Face1_NoseTip_Y": "315.9968", 24 | "Face1_MouthLeft_X": "531.1119", "Face1_MouthLeft_Y": "337.4429", "Face1_MouthRight_X": "564.4709", "Face1_MouthRight_Y": "331.9509", 25 | "Face1_EyebrowLeftOuter_X": "507.1124", "Face1_EyebrowLeftOuter_Y": "291.3806", "Face1_EyebrowLeftInner_X": "536.3497", 26 | "Face1_EyebrowLeftInner_Y": "285.9453", "Face1_EyebrowRightInner_X": "551.8265", "Face1_EyebrowRightInner_Y": "284.7943", 27 | "Face1_EyebrowRightOuter_X": "568.5891", "Face1_EyebrowRightOuter_Y": "282.0251", "Face1_NoseRootLeft_X": "538.3018", 28 | "Face1_NoseRootLeft_Y": "294.0946", "Face1_NoseRootRight_X": "549.9566", "Face1_NoseRootRight_Y": "292.4172", 29 | "Face1_UpperLipTop_X": "554.3179", "Face1_UpperLipTop_Y": "328.0484", "Face1_UpperLipBottom_X": "553.892", 30 | "Face1_UpperLipBottom_Y": "331.7117", "Face1_UnderLipTop_X": "553.6698", "Face1_UnderLipTop_Y": "343.0649", 31 | "Face1_UnderLipBottom_X": "554.4282", "Face1_UnderLipBottom_Y": "347.7436", "BucketKey": 63705733518.0, 32 | "BucketTime": datetime.datetime(2019, 33 | 10, 34 | 3, 35 | 21, 36 | 5, 37 | 18, tzinfo=tzutc()), "BucketTime_Local": "2019-10-03T14: 05: 18.0000000-07: 00", "AggregatedTime": datetime.datetime(2019, 38 | 10, 39 | 3, 40 | 21, 41 | 5, 42 | 22, 43 | 757543, tzinfo=tzutc()), "AggregatedTime_Local": "2019-10-03T14: 05: 22.7575435-07: 00", "ApplicationVersion": "v1.0.0.0", 44 | "etag": "W/"datetime\"2019-10-03T21%3A05%3A27.7709493Z\""" 45 | } -------------------------------------------------------------------------------- /RpiController/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 2 | # cmake file for RpiController project 3 | # 4 | cmake_minimum_required(VERSION 3.7 FATAL_ERROR) 5 | 6 | # define project 7 | project(RpiController) 8 | set(tool_name RpiController) 9 | 10 | # Set C++ version 11 | set(CMAKE_CXX_STANDARD 14) 12 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 13 | 14 | # Apply -fPIC where applicable to the platform 15 | set(CMAKE_POSITION_INDEPENDENT_CODE ON) 16 | 17 | IF(UNIX) 18 | 19 | ELSE() 20 | set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_WIN32_WINNT=0x0600 /GS /W4 /wd4100 /wd4505 /wd4820 /wd4464 /wd4514 /wd4710 /wd4571 /Zc:wchar_t /ZI /Zc:inline /fp:precise /D_SCL_SECURE_NO_WARNINGS /D_CRT_SECURE_NO_WARNINGS /D_UNICODE /DUNICODE /WX- /Zc:forScope /Gd /EHsc /D_SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING") 21 | set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /NXCOMPAT /DYNAMICBASE /INCREMENTAL:NO ") 22 | ENDIF() 23 | 24 | # Find the platform-specific way of working with threads 25 | set(THREADS_PREFER_PTHREAD_FLAG ON) 26 | set(CMAKE_THREAD_PREFER_PTHREAD ON) 27 | find_package(Threads REQUIRED) 28 | 29 | set(src 30 | Ports/Port.cpp 31 | Ports/SerialPort.cpp 32 | Ports/SocketInit.cpp 33 | Ports/TcpClientPort.cpp 34 | Ports/UdpClientPort.cpp 35 | Utils/crc32.cpp 36 | main.cpp 37 | ) 38 | 39 | set(include 40 | Controller/Controller.h 41 | Controller/TeensyPixelBuffer.h 42 | Controller/Commands.h 43 | Controller/Sensei.h 44 | Ports/Port.h 45 | Ports/SerialPort.h 46 | Ports/SocketInit.h 47 | Ports/TcpClientPort.h 48 | Ports/UdpClientPort.h 49 | Utils/Color.h 50 | Utils/Utils.h 51 | Utils/Timer.h 52 | Utils/Json.h 53 | Utils/Settings.h 54 | Utils/FileSystem.h 55 | Utils/StreamWriter.h 56 | Utils/crc32.h 57 | ) 58 | 59 | IF(UNIX) 60 | LIST(APPEND src "Ports/linux/LinuxFindSerialPorts.cpp") 61 | set(EXTRA_LIBS Threads::Threads) 62 | ELSE() 63 | LIST(APPEND src "Ports/windows/WindowsFindSerialPorts.cpp") 64 | set(EXTRA_LIBS Threads::Threads Setupapi.lib Cfgmgr32.lib) 65 | ENDIF() 66 | 67 | source_group("src" FILES ${src}) 68 | source_group("include" FILES ${include}) 69 | 70 | # create executable in build\bin 71 | set(GLOBAL_BIN_DIR ${CMAKE_BINARY_DIR}/bin) 72 | set(EXECUTABLE_OUTPUT_PATH ${GLOBAL_BIN_DIR}) 73 | add_executable(${tool_name} ${src} ${include}) 74 | target_link_libraries(${tool_name} ${EXTRA_LIBS}) 75 | target_include_directories(${tool_name} PRIVATE Controller Utils Ports) 76 | -------------------------------------------------------------------------------- /AdaKioskService/Setup/AssignedAccessProfile.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | ]]> 33 | 34 | 35 | 36 | 37 | 38 | 39 | adatablet\ada 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /HistoricalData/Grouped_by_Hour_3FKCamera01.csv: -------------------------------------------------------------------------------- 1 | hour,FaceCount,neutral,happiness,sadness,anger,fear,surprise,disgust,contempt,sample 2 | 0,0.627994228,0.963196862,0.011354082,0.016934262,0.00296691,8.87E-05,0.002506297,0.000893372,0.00205948,1 3 | 1,1,0.99013745,4.35E-05,0.007306244,0.000268994,1.68E-05,0.000751849,3.97E-05,0.001435438,1 4 | 2,1,0.99013745,4.35E-05,0.007306244,0.000268994,1.68E-05,0.000751849,3.97E-05,0.001435438,1 5 | 3,1,0.99013745,4.35E-05,0.007306244,0.000268994,1.68E-05,0.000751849,3.97E-05,0.001435438,1 6 | 4,0.720250775,0.912573386,0.004203939,0.06994902,0.007597298,0.000156868,0.002358447,0.002270193,0.000890856,1 7 | 5,0.710515873,0.932717936,0.027063651,0.030356773,0.004311969,0.000314942,0.002480771,0.000839286,0.001914667,1 8 | 6,0.762740484,0.8994561,0.03905127,0.043724032,0.010275042,0.000273176,0.002729051,0.002239103,0.002252224,1 9 | 7,0.772070203,0.899261186,0.047846971,0.044106148,0.005296249,8.11E-05,0.001075447,0.000743667,0.00158922,1 10 | 8,0.837473792,0.831686137,0.122062313,0.032612576,0.005061654,0.000246637,0.004107423,0.000965998,0.003257263,1 11 | 9,0.829474297,0.851790233,0.110048604,0.024798932,0.0040642,0.000269971,0.005171732,0.001001714,0.002854613,1 12 | 10,0.832869473,0.84152331,0.114823978,0.021409863,0.006257559,0.000265181,0.010414884,0.001687008,0.003618217,1 13 | 11,0.839543894,0.841004853,0.115239391,0.026040172,0.004644215,0.000478442,0.008445595,0.00116348,0.002983851,1 14 | 12,0.771891905,0.831663109,0.125621677,0.023356597,0.005331757,0.000335473,0.007845821,0.001595421,0.004250142,1 15 | 13,0.832506075,0.804539821,0.159841346,0.017299502,0.004595522,0.000293965,0.009411821,0.001217117,0.002800907,1 16 | 14,0.820457621,0.842366998,0.115410715,0.024726088,0.00646524,0.000413205,0.00550712,0.001316293,0.00379434,1 17 | 15,0.822410168,0.838923643,0.121362881,0.019748408,0.005455345,0.000511604,0.008736681,0.001438984,0.003822455,1 18 | 16,0.789256587,0.827746389,0.133178206,0.023021404,0.004157909,0.000454951,0.006965493,0.000821167,0.00365448,1 19 | 17,0.793106875,0.84195236,0.111700573,0.027738398,0.006801174,0.000296807,0.006211318,0.00109033,0.00420904,1 20 | 18,0.726043784,0.920218731,0.03802468,0.025444111,0.005430176,0.000412305,0.005280977,0.001269625,0.003919396,1 21 | 19,0.700075723,0.914804703,0.038355351,0.024487736,0.004497119,0.001579819,0.00854794,0.001299588,0.006427748,1 22 | 20,0.813847773,0.90286124,0.06774192,0.013452659,0.00286889,0.000323927,0.0082819,0.001279994,0.003189471,1 23 | 21,0.758894269,0.927703402,0.02414363,0.036472869,0.007593621,0.000122961,0.001135681,0.001072357,0.001755475,1 24 | 22,0.765461919,0.864622806,0.111661741,0.009981665,0.003265095,0.000271598,0.006688753,0.000861698,0.002646647,1 25 | 23,0.70825581,0.952846398,0.019804042,0.011930451,0.003425896,6.90E-05,0.002620061,0.001062866,0.008241271,1 26 | -------------------------------------------------------------------------------- /KasaBridge/light_schedule.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. 2 | # Licensed under the MIT license. 3 | import argparse 4 | from tplink_smartplug import * 5 | import datetime 6 | from astral import Astral 7 | 8 | 9 | class Schedule: 10 | def __init__(self): 11 | self.lights = [] 12 | 13 | def add_light(self, addr): 14 | self.lights += [TplinkSmartPlug(addr)] 15 | 16 | def turn_off(self): 17 | for i in self.lights: 18 | i.turn_off() 19 | 20 | def turn_on(self): 21 | for i in self.lights: 22 | i.turn_on() 23 | 24 | def run(self): 25 | # we want to turn off at midnight and on at sunset. 26 | # the 'time_to_sunset' is negative if it is time to turn them on, 27 | # otherwise it is positive. 28 | 29 | while True: 30 | now = datetime.datetime.now() 31 | # now = datetime.datetime(2020,1,8,22,0,0) 32 | midnight = datetime.datetime( 33 | now.year, now.month, now.day, 0, 0, 0 34 | ) + datetime.timedelta(days=1) 35 | 36 | a = Astral() 37 | city = a["Seattle"] 38 | sun = city.sun(date=now, local=True) 39 | sunset = sun["sunset"] 40 | 41 | time_to_sunset = sunset.replace(tzinfo=None) - now 42 | time_to_midnight = midnight - now 43 | 44 | print( 45 | "turning on in {} seconds".format(int(time_to_sunset.total_seconds())), 46 | flush=True, 47 | ) 48 | print( 49 | "turning off in {} seconds".format( 50 | int(time_to_midnight.total_seconds()) 51 | ), 52 | flush=True, 53 | ) 54 | 55 | if time_to_sunset.days < 0: 56 | print("turning on", flush=True) 57 | self.turn_on() 58 | seconds = int(time_to_midnight.total_seconds()) + 1 59 | else: 60 | print("turning off", flush=True) 61 | self.turn_off() 62 | seconds = int(time_to_sunset.total_seconds()) + 1 63 | 64 | print("sleeping for {} seconds".format(seconds), flush=True) 65 | time.sleep(seconds) 66 | 67 | 68 | if __name__ == "__main__": 69 | parser = argparse.ArgumentParser( 70 | "light_schedule turns lights on at sunset and off at midnight" 71 | ) 72 | parser.add_argument( 73 | "--ip", help="one or more ip addresses of HS105 devices", nargs="+" 74 | ) 75 | args = parser.parse_args() 76 | 77 | if args.ip is None: 78 | print("Please specify --ip option", flush=True) 79 | else: 80 | s = Schedule() 81 | for ip in args.ip: 82 | s.add_light(ip) 83 | s.run() 84 | -------------------------------------------------------------------------------- /HistoricalData/Grouped_by_Hour_3FECamera01.csv: -------------------------------------------------------------------------------- 1 | hour,FaceCount,neutral,happiness,sadness,anger,fear,surprise,disgust,contempt,sample 2 | 0,0.72625,0.907134097,0.067189961,0.004853346,0.01110578,0.000466147,0.007789191,0.000386956,0.001074512,1 3 | 1,0.912280702,0.964942716,0.00607861,0.008458351,0.008330898,0.000791068,0.009356973,0.000683802,0.001357579,1 4 | 2,0.466666667,0.9856336,0.006381061,0.00249667,0.001290075,0.000161322,0.001621339,0.000448565,0.001967333,1 5 | 3,0.92,0.84295728,0.094928987,0.012825042,0.00503635,0.001208365,0.039949235,0.001654948,0.001439818,1 6 | 4,0.92,0.84295728,0.094928987,0.012825042,0.00503635,0.001208365,0.039949235,0.001654948,0.001439818,1 7 | 5,0.92,0.84295728,0.094928987,0.012825042,0.00503635,0.001208365,0.039949235,0.001654948,0.001439818,1 8 | 6,0.616666667,0.7999321,0.005879007,0.005358222,0.009393548,0.004657175,0.172261863,0.001777167,0.000740905,1 9 | 7,0.653333333,0.905666733,0.025448587,0.002579043,0.001006783,0.000205705,0.06467681,5.95E-05,0.000356827,1 10 | 8,0.694003901,0.874657284,0.080915258,0.01802294,0.007214773,0.00125001,0.013394381,0.002106228,0.002439128,1 11 | 9,0.910941763,0.799537727,0.169045929,0.011733198,0.002150815,0.00069162,0.014494675,0.00071771,0.001628324,1 12 | 10,0.935577755,0.801394357,0.150865687,0.006349832,0.00844812,0.000811983,0.026076778,0.004508711,0.001544532,1 13 | 11,0.829405866,0.78825056,0.179137294,0.007503243,0.003061179,0.000919121,0.017585905,0.001036706,0.002505993,1 14 | 12,0.777121064,0.837187281,0.120816793,0.017326307,0.003203258,0.000760834,0.017892606,0.001492809,0.001320113,1 15 | 13,0.835047041,0.788466918,0.18008984,0.005370325,0.002580832,0.000737704,0.020422511,0.001091923,0.001239948,1 16 | 14,0.789911063,0.806309893,0.158697077,0.007839131,0.004812685,0.00167824,0.017480306,0.001816066,0.001366603,1 17 | 15,0.935384693,0.760393494,0.202189749,0.009015931,0.005892545,0.001801502,0.01677284,0.001573886,0.002360054,1 18 | 16,0.745370472,0.820532988,0.137716141,0.00701274,0.003342335,0.001387981,0.026838211,0.001358345,0.001811259,1 19 | 17,0.892030614,0.66229261,0.29823239,0.004998975,0.00268336,0.001015677,0.026229422,0.001000201,0.003547365,1 20 | 18,0.819070956,0.855913911,0.10765188,0.014936936,0.004547861,0.001036446,0.012881098,0.001280406,0.001751461,1 21 | 19,0.840922619,0.918964174,0.033404169,0.005497051,0.003078364,0.000671769,0.035494041,0.000632893,0.00225754,1 22 | 20,0.648891626,0.663418137,0.292458725,0.013631204,0.002458677,0.001064867,0.018371814,0.001076181,0.007520399,1 23 | 21,0.577777778,0.990645533,0.003987778,0.003298659,0.001364911,2.84E-05,0.000157807,0.000160321,0.000356555,1 24 | 22,0.61372549,0.958616629,0.026174468,0.007685827,0.002131952,0.000196742,0.003036729,0.000386155,0.001771503,1 25 | 23,0.777142857,0.943245743,0.045487756,0.006818826,0.001300366,0.000159104,0.001962056,0.000279736,0.000746409,1 26 | -------------------------------------------------------------------------------- /HistoricalData/Grouped_by_Hour_4FKCamera02.csv: -------------------------------------------------------------------------------- 1 | hour,FaceCount,neutral,happiness,sadness,anger,fear,surprise,disgust,contempt,sample 2 | 0,0.60593692,0.816683201,0.159219429,0.015717568,0.001689154,9.71E-05,0.001329804,0.000297338,0.004966386,1 3 | 1,0.252525253,0.974689233,0.010973081,0.010815354,0.001864622,9.02E-05,0.001452832,5.41E-05,6.05E-05,1 4 | 2,0.252525253,0.974689233,0.010973081,0.010815354,0.001864622,9.02E-05,0.001452832,5.41E-05,6.05E-05,1 5 | 3,0.252525253,0.974689233,0.010973081,0.010815354,0.001864622,9.02E-05,0.001452832,5.41E-05,6.05E-05,1 6 | 4,0.252525253,0.974689233,0.010973081,0.010815354,0.001864622,9.02E-05,0.001452832,5.41E-05,6.05E-05,1 7 | 5,0.252525253,0.974689233,0.010973081,0.010815354,0.001864622,9.02E-05,0.001452832,5.41E-05,6.05E-05,1 8 | 6,0.735720104,0.915308238,0.012701261,0.038854497,0.00244074,0.000314458,0.027349062,0.000626888,0.002404859,1 9 | 7,0.81916091,0.824298073,0.114689451,0.038908535,0.008774655,0.000503569,0.007340489,0.00151275,0.003972476,1 10 | 8,0.829693578,0.849359406,0.090491107,0.037964342,0.00598755,0.000323019,0.010398436,0.001287934,0.004188204,1 11 | 9,0.826160492,0.797367971,0.155124407,0.031561566,0.005748747,0.000397636,0.005484922,0.001144819,0.003169932,1 12 | 10,0.816840637,0.817961275,0.137641645,0.025780656,0.006186431,0.000373098,0.006736644,0.001585738,0.003734512,1 13 | 11,0.772414406,0.843141045,0.113195153,0.025916602,0.006115931,0.000308265,0.006639504,0.000999932,0.003683568,1 14 | 12,0.768314442,0.799822442,0.13430754,0.047689406,0.007177549,0.000568187,0.006409404,0.001284521,0.002740951,1 15 | 13,0.789356233,0.811861904,0.138440647,0.022888316,0.008633076,0.000516997,0.012208013,0.002014151,0.003436896,1 16 | 14,0.802778582,0.832396024,0.124155034,0.026603195,0.005489312,0.000338015,0.006529109,0.001448184,0.003041126,1 17 | 15,0.752938797,0.886294038,0.07501407,0.020729118,0.005882599,0.000397374,0.006534971,0.001028178,0.004119654,1 18 | 16,0.754967605,0.866225713,0.093028271,0.022459576,0.007993038,0.000314946,0.006264546,0.000900369,0.00281354,1 19 | 17,0.718496615,0.810033359,0.137257452,0.020785533,0.008027433,0.000335411,0.01304297,0.005925667,0.004592172,1 20 | 18,0.713068876,0.942702172,0.034343572,0.009704847,0.002550859,9.46E-05,0.007729267,0.000297832,0.002576834,1 21 | 19,0.648395835,0.935302142,0.006456227,0.014377395,0.005293317,0.000838851,0.034496395,0.001618357,0.001617316,1 22 | 20,0.531296166,0.952011302,0.019421802,0.007337022,0.007083503,0.000189795,0.00998491,0.001015135,0.002956522,1 23 | 21,0.610283868,0.963280755,0.01411284,0.006187432,0.003411265,0.000274225,0.010999834,0.000500982,0.001232658,1 24 | 22,0.620952001,0.985948642,0.002069276,0.004750251,0.002377032,0.000147088,0.003589312,0.000289232,0.000829159,1 25 | 23,0.533022774,0.870299182,0.061272033,0.057184712,0.002790603,0.000279733,0.001627478,0.001176492,0.005369758,1 26 | -------------------------------------------------------------------------------- /HistoricalData/Grouped_by_Hour_4FECamera02.csv: -------------------------------------------------------------------------------- 1 | hour,FaceCount,neutral,happiness,sadness,anger,fear,surprise,disgust,contempt,sample 2 | 0,0.901325758,0.889665272,0.059330457,0.011038219,0.01100282,0.000536417,0.022356134,0.003831742,0.002238938,1 3 | 1,0.558730159,0.813894013,0.15482576,0.004121855,0.002261227,6.87E-05,0.001097804,0.002134513,0.021596099,1 4 | 2,0.558730159,0.813894013,0.15482576,0.004121855,0.002261227,6.87E-05,0.001097804,0.002134513,0.021596099,1 5 | 3,0.558730159,0.813894013,0.15482576,0.004121855,0.002261227,6.87E-05,0.001097804,0.002134513,0.021596099,1 6 | 4,0.066666667,0.843659,0.108133,0.004177467,0.01168941,0.001647419,0.02831281,0.001058414,0.001322401,1 7 | 5,0.666666667,0.6642854,0.3329214,0.000806235,7.04E-05,3.25E-05,0.001365292,0.000158703,0.000359969,1 8 | 6,0.788942308,0.899113403,0.089026957,0.004741931,0.000435279,0.00038902,0.005721246,0.000291726,0.000280429,1 9 | 7,0.604632035,0.911180668,0.046638055,0.010781574,0.002545624,0.000363643,0.027613057,0.000333171,0.000544199,1 10 | 8,0.748186283,0.911988921,0.043562277,0.028432473,0.007354326,0.000272708,0.006189385,0.00100935,0.001190561,1 11 | 9,0.701430114,0.842229115,0.12777825,0.010414422,0.006352635,0.000453961,0.009684097,0.001413615,0.001673907,1 12 | 10,0.777408921,0.846670134,0.113527444,0.013638261,0.006923541,0.000765857,0.013976303,0.001824793,0.002673667,1 13 | 11,0.744844507,0.835930123,0.130456273,0.008290713,0.006853948,0.000872069,0.014138297,0.001565974,0.001892603,1 14 | 12,0.811473826,0.777700769,0.191017409,0.008833351,0.00706056,0.000404035,0.010134234,0.001860339,0.002989305,1 15 | 13,0.778810625,0.842593624,0.124093419,0.007525213,0.006768778,0.000889936,0.015085665,0.001408375,0.001634991,1 16 | 14,1.043490648,0.768445984,0.161290628,0.018258402,0.009826981,0.000771762,0.034254338,0.004152627,0.00299928,1 17 | 15,0.816127978,0.835922608,0.128103527,0.010878512,0.006248155,0.000683196,0.013800897,0.001622989,0.002740116,1 18 | 16,0.72940941,0.869235267,0.102521444,0.007168738,0.004577877,0.000608158,0.012209408,0.001413399,0.00226571,1 19 | 17,0.712489998,0.881442933,0.081449619,0.01634541,0.005930915,0.000795469,0.010279491,0.001069197,0.002686968,1 20 | 18,0.636090592,0.881586206,0.078306854,0.01024064,0.011417546,0.001213724,0.012124677,0.001846897,0.003263455,1 21 | 19,0.650746143,0.850746514,0.117565062,0.009826152,0.002673976,0.00197508,0.014886867,0.000919927,0.001406418,1 22 | 20,0.710284479,0.912057687,0.050228362,0.022550673,0.005591941,0.000242674,0.003319078,0.000806738,0.005202855,1 23 | 21,0.721984127,0.721841875,0.233821924,0.002515947,0.009929061,0.001547925,0.024049873,0.001007157,0.005286232,1 24 | 22,0.723809524,0.99535965,0.000433484,0.000375135,0.000241859,4.07E-05,0.003436709,1.83E-05,9.41E-05,1 25 | 23,1.004145854,0.918762889,0.052086747,0.016663752,0.004476436,0.000147613,0.006134757,0.000539701,0.001188106,1 26 | -------------------------------------------------------------------------------- /HistoricalData/Grouped_by_Hour_3FECamera02.csv: -------------------------------------------------------------------------------- 1 | hour,FaceCount,neutral,happiness,sadness,anger,fear,surprise,disgust,contempt,sample 2 | 0,0.585185185,0.976159444,0.006655044,0.012944814,0.000743662,5.97E-05,0.000753461,0.000878634,0.001805266,1 3 | 1,0.768253968,0.847984914,0.144825855,0.004602185,0.000873531,5.48E-05,0.000683772,0.000139585,0.000835377,1 4 | 2,0.768253968,0.847984914,0.144825855,0.004602185,0.000873531,5.48E-05,0.000683772,0.000139585,0.000835377,1 5 | 3,0.768253968,0.847984914,0.144825855,0.004602185,0.000873531,5.48E-05,0.000683772,0.000139585,0.000835377,1 6 | 4,0.768253968,0.847984914,0.144825855,0.004602185,0.000873531,5.48E-05,0.000683772,0.000139585,0.000835377,1 7 | 5,0.7,0.98798775,0.007517938,0.000791853,0.000505432,6.23E-05,0.00255371,4.96E-05,0.000531465,1 8 | 6,0.761904762,0.633474324,0.359954731,0.003242074,0.000333061,0.000268313,0.001574291,0.000349798,0.000803392,1 9 | 7,0.65978836,0.906253257,0.049700972,0.018919338,0.013162108,0.00060422,0.00810434,0.001104071,0.002151691,1 10 | 8,0.955767849,0.890648672,0.071966079,0.022314583,0.005777929,0.000503684,0.005778643,0.00083456,0.002175855,1 11 | 9,0.819218694,0.855168906,0.121543186,0.006558551,0.002887684,0.000425959,0.010275216,0.001395125,0.001745376,1 12 | 10,0.781221959,0.847790281,0.121949113,0.007186335,0.004390456,0.000850239,0.015787261,0.000846407,0.001199907,1 13 | 11,0.703653041,0.798841616,0.169846427,0.007209179,0.003295749,0.001275215,0.015844234,0.001075106,0.002612474,1 14 | 12,0.888818955,0.844786583,0.108035724,0.008855635,0.005346517,0.002205886,0.026535041,0.002233487,0.002001128,1 15 | 13,0.92235807,0.738050529,0.225826295,0.010268524,0.00422376,0.000778595,0.017545338,0.001500874,0.001806085,1 16 | 14,0.902798441,0.78698432,0.169884146,0.008294926,0.004808579,0.001510368,0.021199186,0.001887559,0.005430915,1 17 | 15,0.84024218,0.826083705,0.133247893,0.009249226,0.005890953,0.000897472,0.019585227,0.002541351,0.002504171,1 18 | 16,0.78717071,0.791608368,0.162704081,0.011394396,0.00561254,0.00173154,0.02171818,0.002150487,0.003080409,1 19 | 17,0.989689507,0.700733968,0.252535256,0.006583585,0.002986941,0.001246978,0.032809362,0.000965984,0.002137927,1 20 | 18,0.677454035,0.754612392,0.196172112,0.010431793,0.004996529,0.00085424,0.028498143,0.001267223,0.003167564,1 21 | 19,0.710405134,0.835868906,0.135310094,0.012737412,0.002754926,0.000645213,0.009064048,0.00082729,0.002792114,1 22 | 20,0.625609756,0.853496569,0.106399942,0.008298895,0.00973813,0.003589582,0.014173772,0.001523919,0.002779195,1 23 | 21,0.640634921,0.82391231,0.142264383,0.010684827,0.002163253,0.002335194,0.016881222,0.000808147,0.000950665,1 24 | 22,0.983274442,0.633954798,0.334667216,0.006858688,0.008074257,0.00126324,0.010725356,0.002280478,0.002175969,1 25 | 23,0.843589744,0.808761318,0.171784226,0.004409226,0.001213594,0.006302644,0.006618415,0.000327949,0.000582623,1 26 | -------------------------------------------------------------------------------- /HistoricalData/Grouped_by_Hour_3FKCamera02.csv: -------------------------------------------------------------------------------- 1 | hour,FaceCount,neutral,happiness,sadness,anger,fear,surprise,disgust,contempt,sample 2 | 0,0.596076146,0.985685756,0.002710888,0.003618569,0.002268258,0.000116371,0.003831579,0.00087686,0.000891717,1 3 | 1,0.133333333,0.9069409,0.05358274,0.02217435,0.007202116,0.002554708,0.005532428,0.000924614,0.001088114,1 4 | 2,0.066666667,0.8368506,0.03249184,0.1176962,0.005819657,0.001433469,0.003178664,0.000686386,0.001843129,1 5 | 3,0.066666667,0.8368506,0.03249184,0.1176962,0.005819657,0.001433469,0.003178664,0.000686386,0.001843129,1 6 | 4,0.59599359,0.932528283,0.0146576,0.033190395,0.008886262,0.000932734,0.00367791,0.004814142,0.001312665,1 7 | 5,0.438290197,0.859131798,0.054885997,0.045521411,0.013723812,0.005446174,0.01610796,0.002873747,0.002309102,1 8 | 6,0.485919312,0.903803987,0.036886996,0.029906534,0.010680169,0.002229927,0.011121381,0.003004081,0.002366925,1 9 | 7,0.722217293,0.914789401,0.036659751,0.027629104,0.008318015,0.000137403,0.00672121,0.001724008,0.00402111,1 10 | 8,0.824495969,0.787032239,0.184370454,0.011857701,0.003815233,0.000340957,0.007970433,0.001386659,0.003226325,1 11 | 9,0.776271252,0.816900552,0.151539715,0.013396843,0.005346163,0.000274175,0.005055347,0.002435389,0.005051818,1 12 | 10,0.824803116,0.8129804,0.160479585,0.008513712,0.005177431,0.000547559,0.007782989,0.001188352,0.00332997,1 13 | 11,0.734080634,0.802869992,0.174741576,0.007025346,0.004637023,0.000225804,0.006492484,0.001817116,0.002190659,1 14 | 12,0.730507106,0.698266326,0.262452604,0.017808675,0.005642904,0.000472728,0.011445985,0.001484248,0.002426529,1 15 | 13,0.728911837,0.815434172,0.141501961,0.008798163,0.006805159,0.000460367,0.021474443,0.002579441,0.002946295,1 16 | 14,0.717051639,0.796361472,0.163607051,0.013746399,0.006501367,0.000804255,0.014406498,0.001625126,0.002947834,1 17 | 15,0.677769453,0.827732223,0.131151972,0.011088666,0.005302935,0.000472979,0.020325775,0.001398904,0.002526546,1 18 | 16,0.79924382,0.909121839,0.055621024,0.015649474,0.006216322,0.000389581,0.009022724,0.001083289,0.002895747,1 19 | 17,0.670489041,0.872135968,0.101535782,0.009268654,0.005381316,0.000449734,0.007844528,0.000590479,0.00279354,1 20 | 18,0.581345574,0.882470003,0.087801379,0.012008032,0.004206172,0.000335001,0.00789318,0.001880511,0.003405731,1 21 | 19,0.541364566,0.948539595,0.027261906,0.007305605,0.002922879,0.000644852,0.011873182,0.000460772,0.000991207,1 22 | 20,0.596948514,0.835608712,0.136325623,0.011313515,0.003200828,0.000278546,0.011514168,0.00078945,0.000969159,1 23 | 21,0.49375,0.905566183,0.043801782,0.009920774,0.004017079,0.005888439,0.027091861,0.00068926,0.003024619,1 24 | 22,0.528349653,0.88683952,0.044352073,0.016983462,0.026497023,0.003979364,0.016832359,0.001313139,0.003203063,1 25 | 23,0.730519481,0.914126518,0.04198331,0.014631328,0.005340293,0.000440096,0.017858691,0.003360353,0.002259412,1 26 | -------------------------------------------------------------------------------- /HistoricalData/Grouped_by_Hour_2FKCamera01.csv: -------------------------------------------------------------------------------- 1 | hour,FaceCount,neutral,happiness,sadness,anger,fear,surprise,disgust,contempt,sample 2 | 0,0.524002849,0.872302233,0.040621258,0.012084268,0.003696204,0.011193269,0.058427581,0.001172267,0.00050292,1 3 | 1,0.341666667,0.64483354,0.315687016,0.005242366,0.001557529,0.00026588,0.027457885,0.000567693,0.00438812,1 4 | 2,0.963369963,0.955023523,0.002326687,0.0329107,0.004436522,0.000416617,0.003018543,0.000400689,0.001466706,1 5 | 3,0.403174603,0.789953367,0.040268453,0.069420706,0.055345536,0.003616274,0.013042634,0.026782784,0.001570273,1 6 | 4,0.403174603,0.789953367,0.040268453,0.069420706,0.055345536,0.003616274,0.013042634,0.026782784,0.001570273,1 7 | 5,0.538194444,0.837423225,0.037759665,0.038898181,0.081989604,6.63E-05,0.00131775,0.000340689,0.0022046,1 8 | 6,0.77260101,0.823447436,0.140180007,0.019223758,0.004228916,0.00014102,0.001284003,0.002596993,0.00889785,1 9 | 7,0.895467063,0.770359217,0.195789407,0.018592815,0.008176167,0.000189087,0.002695561,0.001887253,0.002310492,1 10 | 8,0.747590287,0.824803682,0.148520955,0.015125156,0.002409588,0.000225292,0.006777451,0.000560679,0.001577199,1 11 | 9,0.805579944,0.847608366,0.116004829,0.024073336,0.004512391,0.000247481,0.00463569,0.000862549,0.00205536,1 12 | 10,0.81661596,0.77479695,0.194995699,0.013957546,0.006398163,0.00037691,0.006566336,0.001004928,0.001903466,1 13 | 11,0.715862592,0.852399963,0.11480694,0.015600001,0.005004816,0.000432986,0.007217359,0.001506256,0.003031677,1 14 | 12,0.850656668,0.756043344,0.214777967,0.014730246,0.004426712,0.000455582,0.006397029,0.000838331,0.002330791,1 15 | 13,0.811731047,0.762300711,0.208070457,0.011930379,0.003609952,0.000566591,0.009766362,0.001078885,0.002676664,1 16 | 14,0.759072093,0.803017873,0.149885447,0.024614568,0.006893251,0.000694249,0.009996528,0.001572726,0.003325356,1 17 | 15,0.768500458,0.81310253,0.148507051,0.020582481,0.007837931,0.000453263,0.006428313,0.000910299,0.002178132,1 18 | 16,0.730841703,0.860225261,0.100259738,0.018968684,0.009023707,0.000343825,0.008423637,0.001044559,0.001710586,1 19 | 17,0.663640882,0.817912496,0.137491083,0.023558627,0.004488129,0.000265473,0.013113357,0.001015992,0.002154844,1 20 | 18,0.671116779,0.813167854,0.154816273,0.01299131,0.003968557,0.00059423,0.009849114,0.001144454,0.003468208,1 21 | 19,0.661852007,0.820489157,0.148338006,0.018563778,0.004086881,0.000299593,0.003344825,0.001005675,0.00387208,1 22 | 20,0.67065053,0.837195221,0.127635888,0.015254207,0.0058557,0.000435464,0.008906019,0.000879555,0.003837949,1 23 | 21,0.681159162,0.821052939,0.142100384,0.008715911,0.006276279,0.000574013,0.017469839,0.002041827,0.001768809,1 24 | 22,0.616148052,0.953849206,0.017904058,0.017637749,0.004996761,0.000228808,0.003256543,0.000829468,0.001297407,1 25 | 23,0.628861365,0.91114709,0.080234322,0.005897909,0.000588962,3.72E-05,0.000588264,0.000166254,0.001340043,1 26 | -------------------------------------------------------------------------------- /RpiController/Ports/Port.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | #include "Port.h" 5 | #include "Timer.h" 6 | #include 7 | #include 8 | #include 9 | 10 | Port::~Port() 11 | { 12 | if (readlineBuffer != nullptr) 13 | { 14 | delete[] readlineBuffer; 15 | } 16 | } 17 | 18 | const char* 19 | Port::readline(int timeoutMilliseconds) 20 | { 21 | if (readlineBuffer == nullptr) 22 | { 23 | bufferSize = 8000; 24 | readlineBuffer = new char[bufferSize]; 25 | ::memset(readlineBuffer, 0, bufferSize); 26 | } 27 | Timer timer; 28 | timer.start(); 29 | 30 | do 31 | { 32 | if (totalBytesRead > 0) 33 | { 34 | // shift buffer down. 35 | int pos = 0; 36 | for (int j = totalBytesRead; j < totalBytesWritten; j++) 37 | { 38 | readlineBuffer[pos++] = readlineBuffer[j]; 39 | } 40 | totalBytesRead = 0; 41 | totalBytesWritten = pos; 42 | } 43 | readlineBuffer[totalBytesWritten] = '\0'; 44 | 45 | for (int i = totalBytesRead; i < totalBytesWritten; i++) 46 | { 47 | if (readlineBuffer[i] == '\n') 48 | { 49 | readlineBuffer[i] = '\0'; 50 | if (i > 0 && readlineBuffer[i - 1] == '\r') 51 | { 52 | // Arduino println seems to return '\r\n' 53 | // this trims the '\r' from the buffer also. 54 | readlineBuffer[i - 1] = '\0'; 55 | } 56 | 57 | totalBytesRead = i + 1; 58 | return readlineBuffer; 59 | } 60 | } 61 | int len = this->read((uint8_t*)&readlineBuffer[totalBytesWritten], bufferSize - totalBytesWritten - 1); 62 | if (len < 0) 63 | { 64 | return nullptr; 65 | } 66 | else if (len == 0) 67 | { 68 | // no data retruned, so try again up to the timeout. 69 | if (timer.milliseconds() > timeoutMilliseconds) 70 | { 71 | // return what we have so far because we don't seem to be getting any more. 72 | readlineBuffer[totalBytesWritten] = '\0'; 73 | totalBytesRead = totalBytesWritten; 74 | return readlineBuffer; 75 | } 76 | 77 | std::this_thread::sleep_for(std::chrono::milliseconds(50)); 78 | } 79 | totalBytesWritten += len; 80 | readlineBuffer[totalBytesWritten] = '\0'; 81 | } while (totalBytesWritten < bufferSize && !isClosed()); 82 | 83 | // no new lines, and buffer is full, so return everything so far... 84 | totalBytesRead = totalBytesWritten; 85 | return readlineBuffer; 86 | } 87 | -------------------------------------------------------------------------------- /AdaKiosk/ColorTestWindow.xaml.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Windows; 8 | using System.Windows.Controls; 9 | using System.Windows.Data; 10 | using System.Windows.Documents; 11 | using System.Windows.Input; 12 | using System.Windows.Media; 13 | using System.Windows.Media.Imaging; 14 | using System.Windows.Shapes; 15 | 16 | namespace AdaSimulation 17 | { 18 | /// 19 | /// Interaction logic for ColorTestWindow.xaml 20 | /// 21 | public partial class ColorTestWindow : Window 22 | { 23 | public ColorTestWindow() 24 | { 25 | InitializeComponent(); 26 | this.SizeChanged += ColorTestWindow_SizeChanged; 27 | } 28 | 29 | private void ColorTestWindow_SizeChanged(object sender, SizeChangedEventArgs e) 30 | { 31 | var width = (int)e.NewSize.Width; // for example 32 | var height = (int)e.NewSize.Height; // for example 33 | var dpiX = 96d; 34 | var dpiY = 96d; 35 | var pixelFormat = PixelFormats.Rgb24; 36 | var bytesPerPixel = (pixelFormat.BitsPerPixel + 7) / 8; // == 1 in this example 37 | var stride = bytesPerPixel * width; // == width in this example 38 | 39 | byte[] buffer = new byte[width * height * 3]; 40 | int pos = 0; 41 | double halfHeight = height / 2; 42 | for (int j = 0; j < height; j++) 43 | { 44 | for (int i = 0; i < width; i++) 45 | { 46 | double hue = (double)i * 360.0 / (double)width; 47 | double saturation = 0; 48 | double luminance = 0; 49 | if (j > halfHeight) 50 | { 51 | // top half is fully bright, ramp saturation 52 | luminance = 0.5; 53 | saturation = (double)(halfHeight - (j - halfHeight)) / (double)halfHeight; 54 | } 55 | else 56 | { 57 | // bottom half is fully saturated, ramp luminance 58 | luminance = (double)(halfHeight - j) / (double)halfHeight; 59 | saturation = 1; 60 | } 61 | Color rgb = new HlsColor(hue, luminance, saturation).RgbColor; 62 | buffer[pos++] = rgb.R; 63 | buffer[pos++] = rgb.G; 64 | buffer[pos++] = rgb.B; 65 | } 66 | } 67 | 68 | ColorWheel.Source = BitmapSource.Create(width, height, dpiX, dpiY, pixelFormat, null, buffer, stride); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /RpiController/Utils/FileSystem.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | #ifndef _FILESYSTEM_H 4 | #define _FILESYSTEM_H 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | class FileSystem 13 | { 14 | public: 15 | 16 | // please use the combine() method instead. 17 | static const char kPathSeparator = 18 | #ifdef _WIN32 19 | '\\'; 20 | #else 21 | '/'; 22 | #endif 23 | 24 | // Combine two paths using standard path separator also being careful not to create two 25 | // separators if one of the paths already has one. 26 | static std::string combine(const std::string& parentFolder, const std::string& child) { 27 | if (child.size() == 0) 28 | return parentFolder; 29 | 30 | size_t len = parentFolder.size(); 31 | if (parentFolder.size() > 0 && parentFolder[len - 1] == kPathSeparator) { 32 | // parent already ends with '/' 33 | return parentFolder + child; 34 | } 35 | len = child.size(); 36 | if (len > 0 && child[0] == kPathSeparator) { 37 | // child already starts with '/' 38 | return parentFolder + child; 39 | } 40 | return parentFolder + kPathSeparator + child; 41 | } 42 | 43 | 44 | static std::string getUserHomeFolder() 45 | { 46 | //Windows uses USERPROFILE, Linux uses HOME 47 | #ifdef _WIN32 48 | std::wstring userProfile = _wgetenv(L"USERPROFILE"); 49 | std::wstring_convert, wchar_t> converter; 50 | return converter.to_bytes(userProfile); 51 | #else 52 | return std::getenv("HOME"); 53 | #endif 54 | } 55 | 56 | static void openTextFile(const std::string& filepath, std::ifstream& file) { 57 | 58 | #ifdef _WIN32 59 | // WIN32 will create the wrong file names if we don't first convert them to UTF-16. 60 | std::wstring_convert, wchar_t> converter; 61 | std::wstring wide_path = converter.from_bytes(filepath); 62 | file.open(wide_path, std::ios::in); 63 | #else 64 | file.open(filepath, std::ios::in); 65 | #endif 66 | } 67 | 68 | static void createTextFile(const std::string& filepath, std::ofstream& file) { 69 | 70 | #ifdef _WIN32 71 | // WIN32 will create the wrong file names if we don't first convert them to UTF-16. 72 | std::wstring_convert, wchar_t> converter; 73 | std::wstring wide_path = converter.from_bytes(filepath); 74 | file.open(wide_path, std::ios::out | std::ios::trunc); 75 | #else 76 | file.open(filepath, std::ios::trunc); 77 | #endif 78 | if (file.fail()) 79 | throw std::ios_base::failure(std::strerror(errno)); 80 | } 81 | 82 | }; 83 | 84 | #endif 85 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Security 4 | 5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). 6 | 7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://docs.microsoft.com/en-us/previous-versions/tn-archive/cc751383(v=technet.10)), please report it to us as described below. 8 | 9 | ## Reporting Security Issues 10 | 11 | **Please do not report security vulnerabilities through public GitHub issues.** 12 | 13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://msrc.microsoft.com/create-report). 14 | 15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://www.microsoft.com/en-us/msrc/pgp-key-msrc). 16 | 17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc). 18 | 19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: 20 | 21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) 22 | * Full paths of source file(s) related to the manifestation of the issue 23 | * The location of the affected source code (tag/branch/commit or direct URL) 24 | * Any special configuration required to reproduce the issue 25 | * Step-by-step instructions to reproduce the issue 26 | * Proof-of-concept or exploit code (if possible) 27 | * Impact of the issue, including how an attacker might exploit the issue 28 | 29 | This information will help us triage your report more quickly. 30 | 31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://microsoft.com/msrc/bounty) page for more details about our active programs. 32 | 33 | ## Preferred Languages 34 | 35 | We prefer all communications to be in English. 36 | 37 | ## Policy 38 | 39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://www.microsoft.com/en-us/msrc/cvd). 40 | 41 | -------------------------------------------------------------------------------- /AdaKiosk/MainWindow.xaml: -------------------------------------------------------------------------------- 1 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 27 | 28 | 29 | Story 30 | Simulation 31 | Control 32 | Contact 33 | Debug 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /Server/hls_color.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. 2 | # Licensed under the MIT license. 3 | 4 | 5 | class HlsColor: 6 | def __init__(self, hue, luminance, saturation): 7 | self.hue = hue 8 | self.luminance = luminance 9 | self.saturation = saturation 10 | 11 | def Lighten(self, percent): 12 | self.luminance *= 1.0 + percent 13 | if self.luminance > 1.0: 14 | self.luminance = 1.0 15 | 16 | def Darken(self, percent): 17 | self.luminance *= 1.0 - percent 18 | 19 | def ToHLS(self, red, green, blue): 20 | minval = min(red, min(green, blue)) 21 | maxval = max(red, max(green, blue)) 22 | 23 | mdiff = float(maxval - minval) 24 | msum = float(maxval + minval) 25 | 26 | self.luminance = msum / 510.0 27 | 28 | if maxval == minval: 29 | self.saturation = 0.0 30 | self.hue = 0.0 31 | else: 32 | rnorm = (maxval - red) / mdiff 33 | gnorm = (maxval - green) / mdiff 34 | bnorm = (maxval - blue) / mdiff 35 | 36 | if self.luminance <= 0.5: 37 | self.saturation = mdiff / msum 38 | else: 39 | self.saturation = mdiff / (510.0 - msum) 40 | 41 | if red == maxval: 42 | self.hue = 60.0 * (6.0 + bnorm - gnorm) 43 | 44 | if green == maxval: 45 | self.hue = 60.0 * (2.0 + rnorm - bnorm) 46 | 47 | if blue == maxval: 48 | self.hue = 60.0 * (4.0 + gnorm - rnorm) 49 | 50 | if self.hue > 360.0: 51 | self.hue = self.hue - 360.0 52 | 53 | def ToRGB(self): 54 | red = 0 55 | green = 0 56 | blue = 0 57 | 58 | if self.saturation == 0.0: 59 | red = int(self.luminance * 255) 60 | green = red 61 | blue = red 62 | else: 63 | rm1 = 0.0 64 | rm2 = 0.0 65 | 66 | if self.luminance <= 0.5: 67 | rm2 = self.luminance + self.luminance * self.saturation 68 | else: 69 | rm2 = ( 70 | self.luminance + self.saturation - self.luminance * self.saturation 71 | ) 72 | 73 | rm1 = 2.0 * self.luminance - rm2 74 | red = self.ToRGB1(rm1, rm2, self.hue + 120.0) 75 | green = self.ToRGB1(rm1, rm2, self.hue) 76 | blue = self.ToRGB1(rm1, rm2, self.hue - 120.0) 77 | 78 | return [red, green, blue] 79 | 80 | def ToRGB1(rm1, rm2, rh): 81 | if rh > 360.0: 82 | rh -= 360.0 83 | elif rh < 0.0: 84 | rh += 360.0 85 | 86 | if rh < 60.0: 87 | rm1 = rm1 + (rm2 - rm1) * rh / 60.0 88 | elif rh < 180.0: 89 | rm1 = rm2 90 | elif rh < 240.0: 91 | rm1 = rm1 + (rm2 - rm1) * (240.0 - rh) / 60.0 92 | 93 | return int(rm1 * 255) 94 | -------------------------------------------------------------------------------- /TeensyUnitTest/readme.md: -------------------------------------------------------------------------------- 1 | ## TeensyUnitTest 2 | 3 | This is a Windows C++ application that tests all the TeensyFirmware code and provides a handy 4 | UI that shows the animations that would be sent to the LED strips. This is important because 5 | the Teensy board itself is not debuggable so we need to stress test every line of code to 6 | ensure it works properly. 7 | 8 | ## Unit Test mode 9 | 10 | To just run the unit tests simply launch the program with no command line arguments. 11 | You will see a window popup that shows the animations being tested like this rainbow test: 12 | 13 | ![image](example/rainbow.png) 14 | 15 | ## Integration Test mode 16 | 17 | You can also use this app to test the end-to-end stack from `ada_server` through `RpiController` as follows: 18 | 19 | 1. Run the `ada_server.py` on `localhost`. 20 | 2. Run `TeensyUnitTest` with the command line argument `--client`. 21 | This causes the TeensyUnitTest to listen on a TCP socket for commands, testing the exact same command processing 22 | loop that runs in the TeensyFirmware. 23 | 3. Run `RpiController` on the same machine with `--tcp localhost`. 24 | This connects the RpiController to the TeensyUnitTest so that it drives the animations. 25 | 4. You can also unit test RpiController on a raspberry pi talking to TeensyUnitTest on a PC by providing the ip address of the machine running TeensyUnitTest `--tcp 192.168.1.18`, and providing this same address on the TeensyUnitTest command line with `TeensyUnitTest --client 192.168.1.18` so now the raspberry pi is driving the animations. 26 | 27 | You can then enter command line commands in RpiController and you 28 | will immediately see the result in the TeensyUnitTest window. 29 | If you type "s" in the RpiController it will get commands from the 30 | `ada_server` also running on the same machine. 31 | 32 | Now you can run this for a week and make sure everything is 100% stable, 33 | and especially make sure there are no memory leaks in the TeensyUnitTest 34 | because that would be a big problem in the TeensyFirmware. 35 | 36 | You can also reduce the `playback_delay` in the Server `config.json` 37 | to make it run much faster which makes a better stress test. 38 | 39 | For example, when the server advances to this row of playback data: 40 | ``` 41 | Got new emotions: ['Happiness', 'Happiness', 'Surprise', 'Surprise', 'Happiness', 'Surprise'] 42 | ``` 43 | You will see this in the TeensyUnitTest window: 44 | 45 | ![test](example/test.png) 46 | 47 | **Note:** The strips are laid out horizontally and 48 | 16 of them are stacked vertically. This mapping from camera zones to led strips is described in the Server `zone_map_1.json` (since RpiController is pretending to be `adapi1`). 49 | 50 | Now also fire up `IPCameraGUI/demo_test.py` and have some real fun with 51 | the animations that respond to movement, 3 faces, and facial expressions. This script 52 | defaults to a 10 second timeout between different events, so change that to something small to create a really good test. -------------------------------------------------------------------------------- /AdaServerRelay/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Ada 7 | 8 | 11 | 12 |

Enter a message

13 | 14 | 15 | 16 | 17 | 18 | 19 |
Api Key
Hub name
Group name
Message
20 | 21 |

Message log

22 |
23 | 24 | 80 | -------------------------------------------------------------------------------- /AdaKiosk/Utilities/ServerConfig.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using System.Linq; 7 | using System.Runtime.Serialization; 8 | using System.Runtime.Serialization.Json; 9 | using System.Windows.Media; 10 | 11 | namespace AdaSimulation 12 | { 13 | [DataContract] 14 | public class ServerConfig 15 | { 16 | [DataMember(Name = "colors_for_emotions")] 17 | public EmotionColors EmotionColors; 18 | 19 | public static ServerConfig Load(string filename) 20 | { 21 | using (var fs = new FileStream(filename, FileMode.Open, FileAccess.Read)) 22 | { 23 | DataContractJsonSerializer s = new DataContractJsonSerializer(typeof(ServerConfig)); 24 | var result = s.ReadObject(fs) as ServerConfig; 25 | return result; 26 | } 27 | } 28 | } 29 | 30 | [DataContract] 31 | public class EmotionColors 32 | { 33 | [DataMember] 34 | public int[] anger; 35 | [DataMember] 36 | public int[] contempt; 37 | [DataMember] 38 | public int[] disgust; 39 | [DataMember] 40 | public int[] fear; 41 | [DataMember] 42 | public int[] happiness; 43 | [DataMember] 44 | public int[] neutral; 45 | [DataMember] 46 | public int[] sadness; 47 | [DataMember] 48 | public int[] surprise; 49 | 50 | public Color GetColor(string name) 51 | { 52 | int[] values = null; 53 | switch (name) 54 | { 55 | case "anger": 56 | values = this.anger; 57 | break; 58 | case "contempt": 59 | values = this.contempt; 60 | break; 61 | case "disgust": 62 | values = this.disgust; 63 | break; 64 | case "fear": 65 | values = this.fear; 66 | break; 67 | case "happiness": 68 | values = this.happiness; 69 | break; 70 | case "neutral": 71 | values = this.neutral; 72 | break; 73 | case "sadness": 74 | values = this.sadness; 75 | break; 76 | case "surprise": 77 | values = this.surprise; 78 | break; 79 | default: 80 | break; 81 | } 82 | if (values != null) 83 | { 84 | int r = values.Length > 0 ? values[0] : 0; 85 | int g = values.Length > 1 ? values[1] : 0; 86 | int b = values.Length > 2 ? values[2] : 0; 87 | return Color.FromRgb((byte)r, (byte)g, (byte)b); 88 | } 89 | return Colors.Black; 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /Server/add_permissions.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import json 3 | import os 4 | import subprocess 5 | 6 | storage_roles = [ 7 | "Reader", 8 | "Storage Blob Data Reader", 9 | "Storage Blob Data Contributor", 10 | "Storage Queue Data Reader", 11 | "Storage Queue Data Contributor", 12 | "Storage Table Data Reader", 13 | "Storage Table Data Contributor", 14 | "Storage File Data Privileged Reader", 15 | ] 16 | 17 | 18 | def get_storage_scope(subscription, resource_group, storage_account): 19 | return ( 20 | f"/subscriptions/{subscription}/resourceGroups/{resource_group}/providers/" 21 | f"Microsoft.Storage/storageAccounts/{storage_account}" 22 | ) 23 | 24 | 25 | def get_keyvault_scope(subscription, resource_group, key_vault): 26 | return ( 27 | f"/subscriptions/{subscription}/resourceGroups/{resource_group}/providers/" 28 | f"Microsoft.KeyVault/vaults/{key_vault}" 29 | ) 30 | 31 | 32 | def find_az_cmd(): 33 | if os.name == "nt": 34 | for path in os.environ["PATH"].split(os.pathsep): 35 | az = os.path.join(path, "az.cmd") 36 | if os.path.exists(az): 37 | return az 38 | return None 39 | return "az" 40 | 41 | 42 | def add_role_assignment(az, scope, user, role, resource): 43 | cmd = f'{az} role assignment create --assignee "{user}" --role "{role}" --scope "{scope}"' 44 | print(cmd) 45 | return 46 | result = subprocess.run(cmd, capture_output=True, text=True) 47 | if result.returncode != 0: 48 | print(f"Error: {result.stderr}") 49 | return 50 | json.loads(result.stdout) # make sure result is valid json. 51 | print(f"Role '{role}' added for identity '{user}' on {resource}") 52 | 53 | 54 | def parse_command_line(): 55 | parser = argparse.ArgumentParser( 56 | description="Add user roles to our Azure storage account and key vault." 57 | + " The user should be an SC-ALT account." 58 | ) 59 | parser.add_argument( 60 | "--subscription", "-s", help="The subscription to use", required=True 61 | ) 62 | parser.add_argument( 63 | "--resource_group", "-g", help="The resource group to use", required=True 64 | ) 65 | parser.add_argument( 66 | "--storage_account", "-a", help="The storage account to use", required=True 67 | ) 68 | parser.add_argument( 69 | "--object_id", "-o", help="The assignee being added", required=True 70 | ) 71 | return parser.parse_args() 72 | 73 | 74 | def main(): 75 | az = find_az_cmd() 76 | if az is None: 77 | print("Error: az.cmd not found in PATH") 78 | return 79 | 80 | args = parse_command_line() 81 | 82 | storage_account = args.storage_account 83 | storage_scope = get_storage_scope( 84 | args.subscription, args.resource_group, storage_account 85 | ) 86 | assignee = args.object_id 87 | for role in storage_roles: 88 | add_role_assignment(az, storage_scope, assignee, role, storage_account) 89 | 90 | 91 | if __name__ == "__main__": 92 | main() 93 | -------------------------------------------------------------------------------- /AdaKiosk/Controls/ContactPanel.xaml: -------------------------------------------------------------------------------- 1 | 9 | 10 | 16 | 18 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /Server/animation.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. 2 | # Licensed under the MIT license. 3 | import time 4 | 5 | 6 | class AnimationLoop: 7 | """ 8 | This class plays sequence of animation commands from the config.json file. 9 | """ 10 | 11 | def __init__(self): 12 | self.animation = None 13 | self.start_time = None 14 | self.timeout = 0 15 | self.reset() 16 | 17 | def reset(self): 18 | self.animation_index = 0 19 | self.next_animation = time.time() 20 | 21 | def start(self, animation, timeout=0): 22 | """ 23 | Start the given animation with a maximum timeout in seconds. 24 | If timeout is 0 it could run forever, depending on the animation. 25 | """ 26 | print("### playing animation: {}".format(animation["Name"])) 27 | self.animation = animation 28 | self.animation_index = 0 29 | self.next_animation = time.time() 30 | self.start_time = time.time() 31 | self.timeout = timeout 32 | 33 | def ready(self): 34 | """ 35 | Return true if the animation is ready to play the next step. 36 | """ 37 | return time.time() > self.next_animation 38 | 39 | def completed(self): 40 | """ 41 | Returns true if we've reached the timeout given to the start method. 42 | If the timeout was 0 it always returns false. 43 | """ 44 | return self.timeout != 0 and time.time() > self.start_time + self.timeout 45 | 46 | def next(self): 47 | """ 48 | Move to the next step in the animation sequence. 49 | You should call this when 'ready' has returned True. 50 | This method returns the list of commands that need to 51 | be executed or None. 52 | """ 53 | duration = 0 54 | result = None 55 | if self.animation is not None: 56 | commands = self.animation["Commands"] 57 | while self.animation_index < len(commands): 58 | c = commands[self.animation_index] 59 | self.animation_index += 1 60 | if result is None: 61 | result = [] 62 | result += [c] 63 | timeout = 0 64 | if "seconds" in c: 65 | timeout = c["seconds"] 66 | if "hold" in c: 67 | timeout += c["hold"] 68 | if timeout > duration: 69 | duration = timeout 70 | if "start" in c and c["start"] == "after-previous": 71 | break 72 | if self.animation_index == len(commands): 73 | if "repeat" in self.animation and self.animation["repeat"] == "forever": 74 | self.animation_index = 0 75 | 76 | self.next_animation = time.time() + duration 77 | if result: 78 | print( 79 | "Running animation '{}', step {} for {} seconds".format( 80 | self.animation["Name"], self.animation_index, duration 81 | ) 82 | ) 83 | return result 84 | -------------------------------------------------------------------------------- /Server/main.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin:0px; 3 | } 4 | 5 | .statusbar { 6 | background: #c0c0c0; 7 | bottom: 0; 8 | padding-top: 3px; 9 | padding-left:5px; 10 | padding-bottom: 3px; 11 | height: 26px; 12 | width: 99%; 13 | } 14 | 15 | .tileX { 16 | float: left; 17 | margin:10px; 18 | user-select: none; 19 | -ms-user-select: none; 20 | -webkit-user-select: none; 21 | display: table; 22 | font-weight:bold; 23 | } 24 | 25 | .tile { 26 | float: left; 27 | margin:10px; 28 | overflow: hidden; 29 | user-select: none; 30 | -ms-user-select: none; 31 | -webkit-user-select: none; 32 | display: table; 33 | font-weight:bold; 34 | } 35 | 36 | .tile:hover { 37 | border-width:3px; 38 | border-style:solid; 39 | border-color:darkmagenta; 40 | cursor: pointer; 41 | } 42 | 43 | .tile:active { 44 | margin:13px; 45 | } 46 | 47 | .caption { 48 | display: table-cell; 49 | vertical-align: middle; 50 | text-align: center; 51 | width:90%; 52 | } 53 | 54 | 55 | /*small screens (phones)*/ 56 | @media screen and (min-width: 300px) { 57 | 58 | .tilelist { 59 | font-size: 10pt; 60 | max-width:300px; 61 | } 62 | .tile { 63 | width:120px; 64 | height:100px; 65 | } 66 | 67 | .tile:active { 68 | width:114px; 69 | height:94px; 70 | margin:13px; 71 | } 72 | } 73 | 74 | /*small screens (phones)*/ 75 | @media screen and (min-width: 480px) { 76 | 77 | .tilelist { 78 | font-size: 10pt; 79 | max-width:480px; 80 | } 81 | .tile { 82 | width:120px; 83 | height:100px; 84 | } 85 | 86 | .tile:active { 87 | width:114px; 88 | height:94px; 89 | margin:13px; 90 | } 91 | } 92 | 93 | /*small screens (phones)*/ 94 | @media screen and (min-width: 600px) { 95 | 96 | .tilelist { 97 | font-size: 10pt; 98 | max-width:600px; 99 | } 100 | .tile { 101 | width:120px; 102 | height:100px; 103 | } 104 | 105 | .tile:active { 106 | width:114px; 107 | height:94px; 108 | margin:13px; 109 | } 110 | } 111 | 112 | /* medium screens (tablets)*/ 113 | @media screen and (min-width: 1280px) { 114 | 115 | .tilelist { 116 | font-size: 12pt; 117 | max-width:1024px; 118 | } 119 | 120 | .tile { 121 | width:120px; 122 | height:100px; 123 | } 124 | 125 | .caption-plus { 126 | font-size: 48pt; 127 | } 128 | 129 | .tile:active { 130 | width:114px; 131 | height:94px; 132 | margin:13px; 133 | } 134 | } 135 | 136 | /*larger screens */ 137 | @media screen and (min-width: 1400px) { 138 | 139 | .tilelist { 140 | font-size: 18pt; 141 | max-width:1620px; 142 | } 143 | .tile { 144 | width:250px; 145 | height:200px; 146 | } 147 | 148 | .tile:active { 149 | width:244px; 150 | height:194px; 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /RpiController/Utils/StreamWriter.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | #pragma once 4 | 5 | #include 6 | #include 7 | #include "crc32.h" 8 | 9 | class StreamWriter 10 | { 11 | char* buffer; 12 | int pos = 0; 13 | int length; 14 | 15 | void ReAllocate(int len) 16 | { 17 | if (pos + len >= length) 18 | { 19 | int newSize = (pos + len + 10) * 2; 20 | char* newBuffer = new char[newSize]; 21 | if (buffer != nullptr) { 22 | ::memcpy(newBuffer, buffer, pos); 23 | delete[] buffer; 24 | } 25 | buffer = newBuffer; 26 | length = newSize; 27 | } 28 | } 29 | 30 | inline void CheckAllocate(int len) 31 | { 32 | if (pos + len >= length) 33 | { 34 | ReAllocate(len); 35 | } 36 | } 37 | 38 | public: 39 | StreamWriter() 40 | { 41 | length = 1000; 42 | buffer = new char[length]; 43 | } 44 | ~StreamWriter() 45 | { 46 | if (buffer != nullptr) { 47 | delete[] buffer; 48 | } 49 | } 50 | 51 | void WriteString(const char* ptr) 52 | { 53 | int len = (int)strlen(ptr); 54 | CheckAllocate(len); 55 | strcpy(&buffer[pos], ptr); 56 | pos += len; 57 | } 58 | 59 | void WriteByte(uint8_t x) 60 | { 61 | CheckAllocate(1); 62 | buffer[pos++] = x; 63 | } 64 | 65 | // patch the length field at offset with the current size - offset 66 | void WriteLength(uint32_t offset, uint32_t len) 67 | { 68 | // write in little endian order 69 | for (size_t j = 0; j < 4; j++) 70 | { 71 | buffer[j + offset] = len & 0xff; 72 | len >>= 8; 73 | } 74 | } 75 | 76 | inline void WriteInt(uint32_t i) 77 | { 78 | CheckAllocate(4); 79 | // write out in little endian order 80 | for (size_t j = 0; j < 4; j++) 81 | { 82 | WriteByte(i & 0xff); 83 | i >>= 8; 84 | } 85 | } 86 | 87 | void WriteIntBuffer(uint32_t* data, uint32_t len) 88 | { 89 | int bytes = len * sizeof(uint32_t); 90 | CheckAllocate(bytes); 91 | ::memcpy(&buffer[pos], data, bytes); 92 | pos += bytes; 93 | } 94 | 95 | void WriteFloat(float f) 96 | { 97 | CheckAllocate(4); 98 | float* ptr = (float*)& buffer[pos]; 99 | *ptr = f; 100 | pos += 4; 101 | } 102 | 103 | void WriteCRC(uint32_t offset) 104 | { 105 | if (offset == 0) 106 | { 107 | uint32_t crc = crc32(nullptr, 0); 108 | WriteInt(crc); 109 | } 110 | else 111 | { 112 | uint32_t crc = crc32((uint8_t*)& buffer[offset], pos - offset); 113 | WriteInt(crc); 114 | } 115 | } 116 | 117 | char* GetBuffer() { return buffer; } 118 | 119 | int Size() 120 | { 121 | return pos; 122 | } 123 | 124 | void Clear() 125 | { 126 | pos = 0; 127 | } 128 | }; -------------------------------------------------------------------------------- /AdaKioskService/Setup/PublishBinaries.py: -------------------------------------------------------------------------------- 1 | # This script publishes a zip file containing the release build of this 2 | # AppKiosk binaries to the Azure Blob store at ADA_STORAGE_CONNECTION_STRING. 3 | 4 | import os 5 | import sys 6 | import binascii 7 | import hashlib 8 | import hmac 9 | import zipfile 10 | from azure.storage.blob import BlobServiceClient 11 | from shutil import rmtree 12 | 13 | connection_string = os.getenv("ADA_STORAGE_CONNECTION_STRING") 14 | if not connection_string: 15 | print("Please set ADA_STORAGE_CONNECTION_STRING environment variable") 16 | sys.exit(1) 17 | 18 | 19 | zip_file_name = "AdaKiosk.zip" 20 | blob_store_container = "adakiosk" 21 | 22 | script_dir = os.path.dirname(os.path.abspath(__file__)) 23 | folder = os.path.join( 24 | script_dir, "..", "..", "AdaKiosk", "bin", "Release", "net5.0-windows" 25 | ) 26 | 27 | 28 | def zipdir(path, ziph): 29 | count = 0 30 | # ziph is zipfile handle 31 | for root, dirs, files in os.walk(path): 32 | for file in files: 33 | fullPath = os.path.join(root, file) 34 | offset = len(path) + 1 35 | relative_path = fullPath[offset:] 36 | print("zipping: " + relative_path) 37 | ziph.write(fullPath, relative_path) 38 | count += 1 39 | return count 40 | 41 | 42 | def create_zip(filename, folder): 43 | print("Creating zip file: " + filename) 44 | if os.path.exists(filename): 45 | os.remove(filename) 46 | with zipfile.ZipFile(filename, "w", zipfile.ZIP_DEFLATED) as zipf: 47 | return zipdir(folder, zipf) 48 | 49 | 50 | def upload_file(filename, container): 51 | with open(filename, "rb") as f: 52 | bytes = f.read() 53 | byte_key = binascii.unhexlify("414441") 54 | hash = hmac.new(byte_key, bytes, hashlib.sha256).hexdigest() 55 | print(f"Uploading {filename} with hash: {hash}") 56 | 57 | service = BlobServiceClient.from_connection_string(conn_str=connection_string) 58 | 59 | container = service.get_container_client(blob_store_container) 60 | if not container.exists(): 61 | container.create_container(public_access="Container") 62 | 63 | with open(filename, "rb") as data: 64 | container.upload_blob(filename, data, overwrite=True, validate_content=True) 65 | 66 | hashData = bytearray(hash, "utf-8") 67 | container.upload_blob(filename + ".hash", hashData, overwrite=True) 68 | 69 | 70 | if __name__ == "__main__": 71 | if not os.path.exists(folder): 72 | print("Please build the Release build.") 73 | sys.exit(1) 74 | 75 | # remove webview 2 files created when debugging the app locally. 76 | webview2tempfiles = os.path.join(folder, "AdaKiosk.exe.WebView2") 77 | if os.path.exists(webview2tempfiles): 78 | rmtree(webview2tempfiles) 79 | 80 | # remove any msix publishing stuff. 81 | win_x86 = os.path.join(folder, "win-x86") 82 | if os.path.exists(win_x86): 83 | rmtree(win_x86) 84 | 85 | count = create_zip(zip_file_name, folder) 86 | if count > 40: 87 | print("Found too many files in the bin folder. Does it contain some junk?") 88 | sys.exit(1) 89 | 90 | upload_file(zip_file_name, blob_store_container) 91 | os.remove(zip_file_name) 92 | -------------------------------------------------------------------------------- /KasaBridge/bridge_client.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. 2 | # Licensed under the MIT license. 3 | import numpy as np 4 | import time 5 | 6 | 7 | class KasaBridgeClient: 8 | _commands = ["list", "on", "off", "status"] 9 | 10 | def __init__(self, name, client, address, ping_delay=600): 11 | self.name = name 12 | self.client = client # a socket client. 13 | self.address = address 14 | self.lights_on = None 15 | self.bridge_error = None 16 | self.last_ping = 0 17 | self.ping_delay = ping_delay # make sure socket stays alive. 18 | 19 | def turn_on_lights(self): 20 | if not self.client: 21 | self.bridge_error = "disconnected" 22 | return "disconnected" 23 | response = self._send_bridge_command("on") 24 | if response == "ok": 25 | self.lights_on = True 26 | else: 27 | msg = "Failed to turn on the lights: {}".format(response) 28 | print(msg) 29 | self.bridge_error = msg 30 | 31 | def turn_off_lights(self): 32 | if not self.client: 33 | self.bridge_error = "disconnected" 34 | return "disconnected" 35 | response = self._send_bridge_command("off") 36 | if response == "ok": 37 | self.lights_on = False 38 | else: 39 | msg = "Failed to turn off the lights: {}".format(response) 40 | print(msg) 41 | self.bridge_error = msg 42 | 43 | def get_switch_status(self): 44 | result = self._send_bridge_command("status") 45 | if result != "error": 46 | parts = result.split(",") 47 | states = [] 48 | for device in parts: 49 | pair = device.split(":") 50 | if len(pair) == 2: 51 | is_on = pair[1] == "True" 52 | states += [is_on] 53 | if np.all(states): 54 | self.lights_on = True 55 | else: 56 | self.lights_on = False 57 | return result 58 | 59 | def update_switch_status(self): 60 | if not self.client: 61 | self.lights_on = None # we don't know yet 62 | return 63 | 64 | # Throttle this call so that the lighting designer 65 | # can call it in the animation loop 66 | if self.last_ping + self.ping_delay > time.time(): 67 | return 68 | 69 | result = self.get_switch_status() 70 | self.last_ping = time.time() 71 | return result 72 | 73 | def _send_command(self, command): 74 | if self.client: 75 | self.client.sendall(bytes(command, "utf-8")) 76 | retries = 10 77 | while retries > 0 and self.client: 78 | retries -= 1 79 | response = str(self.client.recv(16000), "utf-8") 80 | if response: 81 | return response 82 | return "no response" 83 | 84 | def _send_bridge_command(self, command): 85 | try: 86 | self.bridge_error = None 87 | return self._send_command(command) 88 | except Exception as e: 89 | self.client = None 90 | self.bridge_error = str(e) 91 | return "error" 92 | -------------------------------------------------------------------------------- /Server/utilities.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. 2 | # Licensed under the MIT license. 3 | import _thread 4 | import os 5 | import subprocess 6 | import time 7 | from pathlib import Path 8 | 9 | 10 | class TimedLatch: 11 | """ 12 | This class provides a timed latch, meaning a switch that triggers no more frequently 13 | than the given delay. This can be used to smooth a signal. The is_on method is also 14 | a latch that resets automatically so it only returns True once. Then it will not return 15 | True again until the delay is reached. 16 | """ 17 | 18 | def __init__(self, delay=10): 19 | self.changed = time.time() 20 | self.delay = delay 21 | self.on = False 22 | self.switch_time = time.time() - delay - 1 23 | 24 | def is_on(self): 25 | if self.on: 26 | # toggle the latch, return True once and then wait for timeout before 27 | # allowing it to turn on again. 28 | self.on = False 29 | return True 30 | return False 31 | 32 | def switch(self, new_value): 33 | changed = False 34 | if new_value: 35 | if self.switch_time + self.delay < time.time(): 36 | # delay has elapsed, so it is ok to turn it on again. 37 | self.switch_time = time.time() 38 | self.on = True 39 | changed = True 40 | return changed 41 | 42 | 43 | def find_program(name): 44 | """Find the given program in the PATH and return the full path to it""" 45 | extensions = [""] if os.name == "posix" else [".cmd", ".bat", ".exe"] 46 | for path in os.environ["PATH"].split(os.pathsep): 47 | for ext in extensions: 48 | exe_file = os.path.join(path, name + ext) 49 | if os.path.isfile(exe_file): 50 | return exe_file 51 | raise Exception(f"Could not find program '{name}' in PATH") 52 | 53 | 54 | class Process: 55 | def __init__(self, cmd_line: str, cwd: str, log_file: str): 56 | """Run subprocess and send the output to the given log file.""" 57 | args = cmd_line.split(" ") 58 | cmd = args[0] 59 | args = args[1:] 60 | if not cwd.startswith("/") and cmd[1] != ":": 61 | os_cwd = os.getcwd() 62 | resolved = Path(os_cwd) / Path(cwd) 63 | cwd = os.path.realpath(str(resolved)) 64 | cmd = find_program(cmd) 65 | 66 | self.proc = subprocess.Popen( 67 | [cmd] + args, 68 | stdout=subprocess.PIPE, 69 | stderr=subprocess.PIPE, 70 | encoding="utf-8", 71 | cwd=cwd, 72 | ) 73 | 74 | self.closed = False 75 | self.output = open(log_file, "a") 76 | 77 | if self.proc.stdout is not None: 78 | _thread.start_new_thread(self.write_output, (self.proc.stdout,)) 79 | 80 | if self.proc.stderr is not None: 81 | _thread.start_new_thread(self.write_output, (self.proc.stderr,)) 82 | 83 | def terminate(self): 84 | self.closed = True 85 | self.proc.terminate() 86 | self.output.close() 87 | 88 | def write_output(self, file): 89 | for line in file: 90 | if self.closed: 91 | break 92 | self.output.write(line) 93 | self.output.flush() 94 | print("write_output terminating") 95 | -------------------------------------------------------------------------------- /AdaKioskService/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /AdaKiosk/Controls/ScreenSaver.xaml.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | using AdaSimulation; 4 | using System; 5 | using System.Windows; 6 | using System.Windows.Controls; 7 | using System.Windows.Input; 8 | using System.Windows.Media; 9 | using System.Windows.Media.Animation; 10 | 11 | // The User Control item template is documented at https://go.microsoft.com/fwlink/?LinkId=234236 12 | 13 | namespace AdaKiosk.Controls 14 | { 15 | public sealed partial class ScreenSaver : UserControl 16 | { 17 | DelayedActions actions = new DelayedActions(); 18 | Random rand = new Random(Environment.TickCount); 19 | const int FadeDelay = 5; 20 | const int BubbleMoveDelay = 10; 21 | int startTick = 0; 22 | 23 | public event EventHandler Closed; 24 | 25 | public ScreenSaver() 26 | { 27 | this.InitializeComponent(); 28 | } 29 | 30 | public void Start() 31 | { 32 | startTick = Environment.TickCount; 33 | this.Bubble.Visibility = Visibility.Collapsed; 34 | this.Visibility = Visibility.Visible; 35 | SolidColorBrush brush = new SolidColorBrush() { Color = Colors.Transparent }; 36 | ScreenBackground.Background = brush; 37 | var animation = new ColorAnimation() { To = Colors.Black, Duration = new Duration(TimeSpan.FromSeconds(FadeDelay)) }; 38 | brush.BeginAnimation(SolidColorBrush.ColorProperty, animation); 39 | actions.StartDelayedAction("move", RandomBubblePlacement, TimeSpan.FromSeconds(BubbleMoveDelay)); 40 | } 41 | 42 | protected override void OnTouchDown(TouchEventArgs e) 43 | { 44 | e.Handled = true; 45 | if (Environment.TickCount > startTick + 5000) 46 | { 47 | actions.StartDelayedAction("event", () => SendClosedEvent("Touch"), TimeSpan.FromMilliseconds(10)); 48 | } 49 | base.OnTouchDown(e); 50 | } 51 | 52 | protected override void OnPreviewMouseMove(MouseEventArgs e) 53 | { 54 | e.Handled = true; 55 | if (Environment.TickCount > startTick + 5000) 56 | { 57 | actions.StartDelayedAction("event", () => SendClosedEvent("Mouse"), TimeSpan.FromMilliseconds(10)); 58 | } 59 | base.OnPreviewMouseMove(e); 60 | } 61 | 62 | protected override void OnPreviewKeyDown(KeyEventArgs e) 63 | { 64 | e.Handled = true; 65 | if (Environment.TickCount > startTick + 5000) 66 | { 67 | actions.StartDelayedAction("event", () => SendClosedEvent("Keyboard"), TimeSpan.FromMilliseconds(10)); 68 | } 69 | base.OnPreviewKeyDown(e); 70 | } 71 | 72 | void SendClosedEvent(string name) 73 | { 74 | if (Closed != null) 75 | { 76 | Closed(this, name); 77 | } 78 | } 79 | 80 | private void RandomBubblePlacement() 81 | { 82 | this.Bubble.Visibility = Visibility.Visible; 83 | var xrange = this.ActualWidth - this.Bubble.Width; 84 | var yrange= this.ActualHeight - this.Bubble.Height; 85 | var x = rand.Next(0, (int)xrange); 86 | var y = rand.Next(0, (int)yrange); 87 | this.Bubble.Margin = new Thickness(x, y, 0, 0); 88 | actions.StartDelayedAction("move", RandomBubblePlacement, TimeSpan.FromSeconds(BubbleMoveDelay)); 89 | } 90 | 91 | public void Stop() 92 | { 93 | this.Visibility = Visibility.Collapsed; 94 | this.actions.Close(); 95 | } 96 | } 97 | } 98 | --------------------------------------------------------------------------------