TCP Control for Panasonic projector

Questions and comments specific to a particular plugin should go here.
Post Reply
Dan Miller
Posts: 5
Joined: Sat Oct 12, 2019 9:45 pm

TCP Control for Panasonic projector

Post by Dan Miller » Sat Oct 12, 2019 10:32 pm

Hi,

I've been looking for a simple way to do this for a while. I use a dual lamp projector for my primary viewing and gaming. Most of the day I use it in its single lamp mode with the lamp running at its lowest power. However, if I'm watching during the day, on bright afternoons beginning around 3:00 PM the sun is streaming in and I need to switch the projector into its highest brightness mode. There is no discrete IR command but there is a discrete TCP command. I always used hyperterminal in the past to send commands but I have been looking for an automated solution. All of our (I work for Panasonic) commands are very simple ASCII strings that get sent after a communication session is open. My ideal plugin here would be a simple telnet process where I specify the IP address of my projector (it needs no login or password) and it would give me a configuration page where I could "create" whatever commands to send that I need.

I have WireShark captures and logs from Packet Sender to assist if necessary. About the only "weirdness" here is that the instant the projector executes a command it closes the socket, so for example in Packet Sender I checked the "listen before sending" box in the options which works perfectly. The projector immediately responds with "NTCONTROL 0" and waits for a command. For example the dual lamp command is: 00LPM:0\r (30 30 4F 4C 50 3A 38 0D).

I also have vast amounts of documentation if it helps...
Last edited by Dan Miller on Wed Oct 16, 2019 4:03 pm, edited 1 time in total.

User avatar
kgschlosser
Site Admin
Posts: 5214
Joined: Fri Jun 05, 2015 5:43 am
Location: Rocky Mountains, Colorado USA

Re: TCP Control for Panasonic projector

Post by kgschlosser » Sun Oct 13, 2019 2:40 am

I removed the new user blocks on your account. You can attach files.. Would you please attach any documentation you have available.

It sounds as tho this would be a really simple thing to get going for ya as there is no goofy kind of security or authentication process.
If you like the work I have been doing then feel free to Image

Dan Miller
Posts: 5
Joined: Sat Oct 12, 2019 9:45 pm

Re: TCP Control for Panasonic projector

Post by Dan Miller » Sun Oct 13, 2019 5:05 am

Thanks for looking at this...

The first attachment are the pages from the owner's manual that discusses control over IP with no password in place.

There is also a log from Packet Sender, a screenshot, and a save from Wireshark of that particular session.

I hope this helps.
TCP Command Structure.zip
(211.2 KiB) Downloaded 28 times

User avatar
kgschlosser
Site Admin
Posts: 5214
Joined: Fri Jun 05, 2015 5:43 am
Location: Rocky Mountains, Colorado USA

Re: TCP Control for Panasonic projector

Post by kgschlosser » Sun Oct 13, 2019 6:15 pm

If it is only the single command you are looking for then it is easy to do. The problem is with the Panasonic projectors is that there is a very large variance in the control commands. It is like they cannot settle on an API. So the commands are very much projector specific.. I cannot even find the LPM: command in any of the specifications..

The nice thing is they have set up the API so that it is the same commands as what would be transmitted if the connection was serial rs232. then you also have the PJLink (Projector Link) Specification the projectors have. This has a better connection scheme so you can keep the connection persistent simply by polling the projectors power state. The crappy thing about their API is the constant connect/disconnect. It is expensive to make a connection. it slows things down quite a bit. This is probably why I am not able to locate a huge amount of information about the "Protocol 2 Web Connection". If it is only the single command that you want then I can write that for you in a python script and it does not need to be a full blown plugin. Last night I did write a PJLink plugin I am going to put the finishing touches on it today. The PJ link gives you simple commands like power on and power off. power query things of that nature. For the most part once a projector is set up the settings usually do not get changed on it. PJLink also does provide Errors and bulb hours. so this is a nice thing to have if a projector is not functioning properly and no output is available other then a flashing light.

If more control is wanted other then the really simple things I am going to recommend using RS232. This is because it does not require making a connection over and over again. an example would be the ability to send a command once every 20 millisecond or so VS being able to send a command every 300-400 milliseconds or so.. Now that may not seem like a big deal until you actually use it. It's horrible when the connection is not persistent. It seems like Panasonic's goal was to shift the API to PJLink but that has kind of stalled for some reason.


What is the model number of your projector?.
If you like the work I have been doing then feel free to Image

User avatar
kgschlosser
Site Admin
Posts: 5214
Joined: Fri Jun 05, 2015 5:43 am
Location: Rocky Mountains, Colorado USA

Re: TCP Control for Panasonic projector

Post by kgschlosser » Sun Oct 13, 2019 6:34 pm

If you wanted a simple python script to change the bulb here is it. create a Macro.. add the python script action.. paste this code into it.. OK/save the script.. add the event you want to use to run the script. Finished!

I added comments to the script so you know what is happening in the script. This will help you if you wanted to add other commands. This is going to send the command and not wait for a response to that command from the projector. If you want me to add the responses from the projector I am going to need the RS232 command list for your projector.


I have not tested this code so there might be a typo in it.. let me know if you get any errors.

Code: Select all


# change this to the IP of the projector
IP_ADDRESS = '0.0.0.0'

import socket

# create a socket..
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# connect that socket
sock.connect((IP_ADDRESS, 1024))

data = ''

# here we are going to create a loop that will keep on receiving until a 
# carriage return (\r) is in the data
while '\r' not in data:
    We want to catch any errors. so that is what the try/except does.. This provides a
    mechanism to gracefully handle any issues and allows us to close the socket.
    try:
        data += sock.recv(4096)
    except socket.timeout:
        continue
    except socket.error:
        # Here we print out the error and break out of the loop to receive
        import traceback
        traceback.print_exc()
        break
        
# Make sure the projector responded then send the command
if 'NTCONTROL 0' in data:
    sock.send('00LPM:0\r')
else:
    eg.PrintError('NTCONTROL not found')

# make sure the socket is closed.
# if we do not do this you can end up with an 
# orphaned socket connection.       
try:
    sock.close()
except socket.error:
    pass
If you like the work I have been doing then feel free to Image

Dan Miller
Posts: 5
Joined: Sat Oct 12, 2019 9:45 pm

Re: TCP Control for Panasonic projector

Post by Dan Miller » Mon Oct 14, 2019 2:02 pm

While having to request the connection every time adds an extra step, I don't think it's as much of a burden as you think. When I was experimenting with Packet Sender, the only thing it required was checking the "Attempt receive before Send" option in the settings because apparently it's not an uncommon behavior. And while it might slow things down, it is absolutely imperceptible. I have been able to shutter 20 projectors simultaneously (I know it wasn't actually instantaneous because each projector needed to be given the command separately, but it would have taken a high speed camera to see the stepping as it literally appeared to happen all at once to your eyes. I think some have worked around this by simply issuing a command twice. The first is ignored but opens the socket, although I'm not positive about that. But at the end of the day, these devices aren't usually asked to do that much, or that often, so the extra step isn't really that big of a deal. And since the projector closes the connection immediately, it means a controller doesn't have to. If I could use PJLink i would do it in a heartbeat, but the command set is limited.

You want slow, see what happens if a user fails to remove the password. Then when a connection is requested the projector sends back a hash which is then used to calculate a checksum. Every time. What a pain.

Below is a resource page you might find helpful. This is maintained by the factory and has just about everything on our projectors you might need.

https://eww.pavc.panasonic.co.jp/projec ... index.html

That page is actually a redirect from our global web page which also has tremendous information as well. For projectors:

https://panasonic.net/cns/projector/

For flat panels:

https://panasonic.net/cns/prodisplays/

As far as all of the different commands, in reality we have kept the same commands and protocol for over 12 years, except in cases where a model might have a unique feature or technology that might have a command attached. Mine is one of them, because it was the last of the lamp based large venue projectors before the introduction of Laser/Phosphor as an illumination source. It is the PT-DZ13K. The LPM command was added for that one because it has three power settings for the lamps as opposed to the two typically found. But for the most part the command list has not varied. Whether I use Hyperterminal, Putty, Packet Sender, etc... the format is always two zeros followed by the command and if a modifier is necessary it goes in after a colon, finishing with the CR.

Here are screenshots of the setting dialog in Packet Sender and the actual GUI with the Power ON and Power OFF commands saved.
Packet Sender Option.PNG
Packet Sender GUI.PNG
I will be traveling for the next three days, so I won't be able to test anything until then... But again, I appreciate this tremendously and incidentally, I'm not alone in my desire for "other" ways to automate things. For many, Crestron or the like is overkill. I will spread the word.


Thanks again

Dan

User avatar
kgschlosser
Site Admin
Posts: 5214
Joined: Fri Jun 05, 2015 5:43 am
Location: Rocky Mountains, Colorado USA

Re: TCP Control for Panasonic projector

Post by kgschlosser » Tue Oct 15, 2019 1:14 am

for lack of better description EG is a translator. It allows devices/software to be able to communicate with one another when they are not able to natively. EG provides an API that allows devices and software to interact with one another.. put it to you this way... If it can communicate then EG can talk to it. The real power of EG actually lies in it's real time scripting support.

I provided you with the script that you can use to send that single command. if you are going to be sending the command to more then a single projector let me know I will write it differently using multiple threads. I can make all of the projectors change at the same time.

The password hash thing is easy to do and it is also pretty quick as well. you take the password append it to the end of the "cookie" provided by the projector MD5 it and prepend it to the command.

I am going to look at the commands for the projector. you may not notice the speed when opening the connection for a single command.. you will notice it if you want to press a direction key a slew of times in rapid succession.

Of all of the HA software that I have tried EG is hands down the easiest to use and the most flexible, it is also the fastest one. It does not have a web GUI which is important for a lot of folks. I do not think that people truly understand what Home Automation is. Software that provides a fancy GUI for controlling things is not Home Automation software (technically speaking), it is Smart Home software. Home Automation is devices/software operating "Automatically" without the need for a user to load a GUI to click something. Take any of the assortment of voice control systems like Alexa.. they have a web GUI only for creating the voice to actions. not for actual control. once it is set up you should not ever need to go into that GUI again unless you are making changes.
If you like the work I have been doing then feel free to Image

Dan Miller
Posts: 5
Joined: Sat Oct 12, 2019 9:45 pm

Re: TCP Control for Panasonic projector

Post by Dan Miller » Tue Oct 15, 2019 3:02 am

Understood. My front door unlocks as I pull in to the driveway, but only if I'm alone. I've been doing HA of some sort or another since Z-wave. This is why I think EG is so cool. In my ideal world it would base the event on the relative angular position of the sun to my house, with a conditional of cloud cover. The lamps are ridiculously expensive; the more the projector is using only one of them in low power mode, the better I like it. During winter months I might have to flip into 2 lamp high mode for 4-5 hrs. So basically I'm looking to automate two events which right now I have to interrupt everything for, torch mode (both lamps at full power) and returning things to normal (one lamp low power). I can figure out the trigger timing. I'll try the script when I get back.

User avatar
kgschlosser
Site Admin
Posts: 5214
Joined: Fri Jun 05, 2015 5:43 am
Location: Rocky Mountains, Colorado USA

Re: TCP Control for Panasonic projector

Post by kgschlosser » Wed Oct 16, 2019 8:56 am

You do know there is a Z-Wave plugin for EventGhost?... I have been working on python-openzwave. and also openzwave. I have basically done an almost entire rewrite of python-openzwave.. I have been working with the author of openzwave and hammering out how to handle different aspects of the Z-Wave protocol. I can say this.. For all of the different HA programs that have ZWave support hands down this new engine we have developed is by far the fastest.. I know this is not a great reference, but I used to own a MiCasaVerde Vera and that thing would always need to restart the engine even if something as simple as a name change for a node took place.. at the time I had approx 15 nodes and that thing would take at least 3-5 minutes to restart the engine.. I now have about 50 nodes, the engine never needs to be restarted and I do mean never. It is stable to the point that I decided to add the ability to run it as a service so an application is able to be restarted without the need to restart the engine.. But in all honesty it really doesn't even matter because this new engine is able to provide about 95% of the networks information in less then a second. and the last 5% will be filled in while the engine is running, so the time is variable but usually no more then 30 seconds. The point is that an application is able to display the UI for the network to the user almost immediately.

This will be added to EventGhost soon enough.. I am running into some road blocks with the developer of openzwave because there are some portions of the specification that are not going to work properly and he says that it is to much work to fix/update. The way I look at it is I wrote almost 150K lines of code in python openzwave in order to get it to run like it does.. I am willing to put in the time.. I have been thinking about not using openzwave and writing the low level end of things myself. This way I would have the ability to repair/update the low level end of things as well.

The specification for the serial communications to a UZB stick is not available or None that I have been able to locate. So I would have to do a whole lot of reading hex decimal to figure out how the packets are organized
If you like the work I have been doing then feel free to Image

Dan Miller
Posts: 5
Joined: Sat Oct 12, 2019 9:45 pm

Re: TCP Control for Panasonic projector

Post by Dan Miller » Wed Oct 16, 2019 4:07 pm

For everything else I am using Smartthings. It has been bulletproof for the two years I have used it and I don't want to keep my PC on all the time. If I am watching the projector I will be using the PC so I think this will be the answer. I get back tonight; if I'm not wiped out I should know pretty quick.

User avatar
kgschlosser
Site Admin
Posts: 5214
Joined: Fri Jun 05, 2015 5:43 am
Location: Rocky Mountains, Colorado USA

Re: TCP Control for Panasonic projector

Post by kgschlosser » Wed Oct 16, 2019 7:47 pm

well I am about to make you want to change the whole smart thing use...

:D :shock:

ok so this is the "hard part" if your ideal lamp setup.
at the top of the script you will need to enter your lat and lon also if your are observing daylight savings (USA and Great Brittan(I think))
and also the hour difference to GMT (UTC Offset) an this is going to spit out where the sun is in the sky for the exact time the script gets run. right down to the second.

Code: Select all

from __future__ import print_function

# these are the settings you need to change.
LATITUDE = 39.5855
LONGITUDE = -105.363621
DAYLIGHT_SAVINGS = False
UTC_OFFSET = 7


# do not edit anything below this point
import math

class Month(object):
    def __init__(self, name, numdays, abbr):
        self.name = name
        self.numdays = numdays
        self.abbr = abbr


class ANS(object):

    def __init__(self, daySave, value):
        self.daySave = daySave
        self.value = value


class City(object):

    def __init__(self, name, lat, lng, zoneHr):
        self.name = name
        self.lat = lat
        self.lng = lng
        self.zoneHr = zoneHr


monthList = [
    Month("January", 31, "Jan"),
    Month("February", 28, "Feb"),
    Month("March", 31, "Mar"),
    Month("April", 30, "Apr"),
    Month("May", 31, "May"),
    Month("June", 30, "Jun"),
    Month("July", 31, "Jul"),
    Month("August", 31, "Aug"),
    Month("September", 30, "Sep"),
    Month("October", 31, "Oct"),
    Month("November", 30, "Nov"),
    Month("December", 31, "Dec")
]


yesno = [
    ANS("No",0),
    ANS("Yes",60)
]


city = [
    City("Enter Lat/Long -->", 0, 0, 0),
    City("", 0, 0, 0),
    City("US CITIES", 0, 0, 0),
    City("Albuquerque, NM", 35.0833, 106.65, 7),
    City("Anchorage, AK", 61.217, 149.90, 9),
    City("Atlanta, GA", 33.733, 84.383, 5),
    City("Austin, TX", 30.283, 97.733, 6),
    City("Birmingham, AL", 33.521, 86.8025, 6),
    City("Bismarck, ND", 46.817, 100.783, 6),
    City("Boston, MA", 42.35, 71.05, 5),
    City("Boulder, CO", 40.125, 105.237, 7),
    City("Chicago, IL", 41.85, 87.65, 6),
    City("Dallas, TX", 32.46, 96.47, 6),
    City("Denver, CO", 39.733, 104.983, 7),
    City("Detroit, MI", 42.333, 83.05, 5),
    City("Honolulu, HI", 21.30, 157.85, 10),
    City("Houston, TX", 29.75, 95.35, 6),
    City("Indianapolis, IN", 39.767, 86.15, 5),
    City("Jackson, MS", 32.283, 90.183, 6),
    City("Kansas City, MO", 39.083, 94.567, 6),
    City("Los Angeles, CA", 34.05, 118.233, 8),
    City("Menomonee Falls, WI", 43.11, 88.10, 6),
    City("Miami, FL", 25.767, 80.183, 5),
    City("Minneapolis, MN", 44.967, 93.25, 6),
    City("New Orleans, LA", 29.95, 90.067, 6),
    City("New York City, NY", 40.7167, 74.0167, 5),
    City("Oklahoma City, OK", 35.483, 97.533, 6),
    City("Philadelphia, PA", 39.95, 75.15, 5),
    City("Phoenix, AZ", 33.433, 112.067, 7),
    City("Pittsburgh, PA",40.433,79.9833,5),
    City("Portland, ME", 43.666, 70.283, 5),
    City("Portland, OR", 45.517, 122.65, 8),
    City("Raleigh, NC", 35.783, 78.65, 5),
    City("Richmond, VA", 37.5667, 77.450, 5),
    City("Saint Louis, MO", 38.6167, 90.1833, 6),
    City("San Antonio, TX", 29.53, 98.47, 6),
    City("San Diego, CA", 32.7667, 117.2167, 8),
    City("San Francisco, CA", 37.7667, 122.4167, 8),
    City("Seattle, WA", 47.60, 122.3167, 8),
    City("Washington DC", 38.8833, 77.0333, 5),
    City("", 0, 0, 0),
    City("WORLD CITIES", 0, 0, 0),
    City("Beijing, China", 39.9167, -116.4167, -8),
    City("Berlin, Germany", 52.33, -13.30, -1),
    City("Bombay, India", 18.9333, -72.8333, -5.5),
    City("Buenos Aires, Argentina", -34.60, 58.45, 3),
    City("Cairo, Egypt", 30.10, -31.3667, -2),
    City("Cape Town, South Africa", -33.9167, -18.3667, -2),
    City("Caracas, Venezuela", 10.50, 66.9333, 4),
    City("Helsinki, Finland", 60.1667, -24.9667,-2),
    City("Hong Kong, China", 22.25,-114.1667, -8),
    City("Jerusalem, Israel", 31.7833, -35.2333, -2),
    City("London, England", 51.50, 0.1667, 0),
    City("Mexico City, Mexico", 19.4, 99.15, 6),
    City("Moscow, Russia", 55.75, -37.5833, -3),
    City("New Delhi, India", 28.6, -77.2, -5.5),
    City("Ottawa, Canada", 45.41667, 75.7,5),
    City("Paris, France", 48.8667, -2.667, -1),
    City("Rio de Janeiro, Brazil", -22.90, 43.2333, 3),
    City("Riyadh, Saudi Arabia", 24.633, -46.71667, -3),
    City("Rome, Italy", 41.90, -12.4833, -1),
    City("Sydney, Australia", -33.8667, -151.2167, -10),
    City("Tokyo, Japan", 35.70, -139.7667, -9),
    City("Zurich, Switzerland", 47.3833, -8.5333, -1),
    City("", 0, 0, 0),
    City("SURFRAD NETWORK", 0, 0, 0),
    City("Goodwin Creek, MS", 34.2544444, 89.8738888, 6),
    City("Fort Peck, MT", 48.310555, 105.1025, 7),
    City("Bondville, IL", 40.055277, 88.371944, 6),
    City("Table Mountain, CO", 40.125, 105.23694, 7),
    City("Desert Rock, NV", 36.626, 116.018, 8),
    City("Penn State, PA", 40.72, 77.93, 5),
    City("Canaan Valley, WV", 39.1, 79.4, 5),
    City("Sioux Falls, SD", 43.733, 96.6233, 6),
    City("", 0, 0, 0),
    City("ARM/CART NETWORK", 0, 0, 0),
    City("Atqasuk, AK", 70.47215, 157.4078, 9),
    City("Barrow, AK", 71.30, 156.683, 9),
    City("Manus Island, PNG", -2.06, -147.425, -10),
    City("Nauru Island", -0.52, -166.92, -12),
    City("Darwin, Australia", -12.425, -130.891, -9.5),
    City("SGP Central Facility", 36.6167, 97.5, 6),
    City("",0,0,0),
    City("SOLRAD NETWORK", 0, 0, 0),
    City("Albuquerque, NM", 35.04, 106.62, 7),
    City("Bismarck, ND", 46.77, 100.77, 6),
    City("Hanford, CA", 36.31, 119.63, 8),
    City("Madison, WI", 43.13, 89.33, 6),
    City("Oak Ridge, TN", 35.96, 84.37, 5),
    City("Salt Lake City, UT", 40.77, 111.97, 7),
    City("Seattle, WA", 47.68, 122.25, 8),
    City("Sterling, VA", 38.98, 77.47, 5),
    City("Tallahassee, FL", 30.38, 84.37, 5)
]

def set_lat_long(f, index):

    f["latDeg"] = city[index].lat
    f["lonDeg"] = city[index].lng

    f["latMin"] = 0
    f["latSec"] = 0
    f["lonMin"] = 0
    f["lonSec"] = 0

    conv_lat_long(f)

    f["hrsToGMT"] =  city[index].zoneHr

def is_leap_year(yr):
    yr = int(yr)
    return (
        (yr % 4 == 0 and yr % 100 != 0) or
        yr % 400 == 0
    )

def is_pos_integer(inputVal):     
    inputStr = str(inputVal)
    for oneChar in list(inputStr):
        if oneChar < "0" or oneChar > "9":
            return False

    return True


def is_valid_input(f, index, lat_long_form): 
    if f["day"] == "": # see if the day field is empty
        print("You must enter a day before attempting the calculation.")
        return False

    elif f["year"] == "": 	# see if year field is empty
        print("You must enter a year before attempting the calculation.")
        return False

    elif is_pos_integer(f["day"]) is False or f["day"] == 0:
        print("The day must be a positive integer.")
        return False

    elif is_pos_integer(f["year"]) is False:
        print("The year must be a positive integer.")
        return False

    elif f["hour"] == "": # see if hour field is empty
        print("You must enter a time before attempting the calculation.")
        return False

    elif (
        is_pos_integer(f["hour"]) is False or
        is_pos_integer(f["mins"]) is False or
        is_pos_integer(f["secs"]) is False
    ):
        print("The time fields must contain positive integers.")
        return False

    elif int(f["hour"]) > 23:
        print("Hour must be between 0 and 23.")
        return False

    elif int(f["mins"]) > 59:
        print("Minutes must be between 0 and 59.")
        return False

    elif int(f["secs"]) > 59:
        print("Seconds must be between 0 and 59.")
        return False

    elif index != 1 and int(f["day"]) > monthList[index].numdays:
        print("There are only " + str(monthList[index].numdays) + " days in " + str(monthList[index].name) + ".")
        return False


    elif index == 1:
        if is_leap_year(f["year"]):
            if int(f["day"]) > monthList[index].numdays + 1:
                print("There are only " + str(monthList[index].numdays + 1) + " days in " + str(monthList[index].name) + ".")
                return False
            else:
                return True

        else: # year entered is not a leap year
            if int(f["day"]) > monthList[index].numdays:
                print("There are only " + str(monthList[index].numdays) + " days in " + str(monthList[index].name) + ".")
                return False

            else:
                return True
    else:
        return True


def conv_lat_long(f):

    if f["latDeg"] == "":
        f["latDeg"] = '0'

    if f["latMin"] == "":
        f["latMin"] = '0'

    if f["latSec"] == "":
        f["latSec"] = '0'

    if f["lonDeg"] == "":
        f["lonDeg"] = '0'

    if f["lonMin"] == "":
        f["lonMin"] = '0'

    if f["lonSec"] == "":
        f["lonSec"] = '0'


    neg = 0
    if f["latDeg"] < 0:
        neg = 1

    if neg != 1:

        latSeconds = (
            (f["latDeg"] * 3600) +
            (f["latMin"] * 60) +
            (f["latSec"] * 1)
        )

        f["latDeg"] = math.floor(latSeconds / 3600)
        f["latMin"] = math.floor((latSeconds - (f["latDeg"] * 3600)) / 60)
        f["latSec"] = math.floor((latSeconds - (f["latDeg"] * 3600) - (f["latMin"] * 60)) + 0.5)


    elif f["latDeg"] > -1:

        latSeconds = (
            (f["latDeg"] * 3600) -
            (f["latMin"] * 60) -
            (f["latSec"] * 1)
        )

        f["latDeg"] = 0
        f["latMin"] = math.floor(-latSeconds / 60)
        f["latSec"] = math.floor( (-latSeconds - (f["latMin"] * 60)) + 0.5)


    else:
        latSeconds = (
            (f["latDeg"] * 3600) -
            (f["latMin"] * 60) -
            (f["latSec"] * 1)
        )

        f["latDeg"] = math.ceil(latSeconds / 3600)
        f["latMin"] = math.floor((-latSeconds + (f["latDeg"] * 3600)) / 60)
        f["latSec"] = math.floor((-latSeconds + (f["latDeg"] * 3600) - (f["latMin"] * 60)) + 0.5)



    neg = 0
    if f["lonDeg"] < 0:
        neg = 1


    if neg != 1:
        lonSeconds = (
            (f["lonDeg"] * 3600) +
            (f["lonMin"] * 60) +
            (f["lonSec"] * 1)
        )
        
        f["lonDeg"] = math.floor(lonSeconds / 3600)
        f["lonMin"] = math.floor((lonSeconds - (f["lonDeg"] * 3600)) / 60)
        f["lonSec"] = math.floor((lonSeconds - (f["lonDeg"] * 3600) - (f["lonMin"]) * 60) + 0.5)

    elif f["lonDeg"] > -1:

        lonSeconds = (
            (f["lonDeg"] * 3600) -
            (f["lonMin"] * 60) -
            (f["lonSec"] * 1)
        )

        f["lonDeg"] = 0
        f["lonMin"] = math.floor(-lonSeconds / 60)
        f["lonSec"] = math.floor((-lonSeconds - (f["lonMin"] * 60)) + 0.5)


    else:
        lonSeconds = (
            (f["lonDeg"] * 3600) -
            (f["lonMin"] * 60) -
            (f["lonSec"] * 1)
        )

        f["lonDeg"] = math.ceil(lonSeconds / 3600)
        f["lonMin"] = math.floor((-lonSeconds + (f["lonDeg"] * 3600)) / 60)
        f["lonSec"] = math.floor((-lonSeconds + (f["lonDeg"] * 3600) - (f["lonMin"] * 60)) + 0.5)

    # Test for invalid lat/long input

    if latSeconds > 323280:
        print("You have entered an invalid latitude.\nSetting lat=89.8.")
        f["latDeg"] = 89.8
        f["latMin"] = 0
        f["latSec"] = 0

    if latSeconds < -323280:
        print ("You have entered an invalid latitude.\n  Setting lat= -89.8.")
        f["latDeg"] = -89.8
        f["latMin"] = 0
        f["latSec"] = 0

    if lonSeconds > 648000:
        print ("You have entered an invalid longitude.\n Setting lon= 180.")
        f["lonDeg"] = 180
        f["lonMin"] = 0
        f["lonSec"] = 0

    if lonSeconds < -648000:
        print("You have entered an invalid latitude.\n Setting lon= -180.")
        f["lonDeg"] = -180
        f["lonMin"] = 0
        f["lonSec"] =0


def rad_to_deg(angle_rad): 
    return 180.0 * angle_rad / math.pi


def deg_to_rad(angle_deg): 
    return math.pi * angle_deg / 180.0


def calc_day_of_year(mn, dy, lpyr): 
    k = 2 if lpyr else 1
    return math.floor((275 * mn) / 9) - k * math.floor((mn + 9) / 12) + dy -30


def calc_day_of_week(juld):
    dow = [
        "Sunday",
        "Monday",
        "Tuesday",
        "Wednesday",
        "Thursday",
        "Friday",
        "Saturday"
    ]
    return dow[int((juld + 1.5) % 7)]


def calc_jd(year, month, day):
    if month <= 2:
        year -= 1
        month += 12

    a = math.floor(year / 100)
    b = 2 - a + math.floor(a / 4)

    return math.floor(365.25 * (year + 4716)) + math.floor(30.6001 * (month + 1)) + day + b - 1524.5


def calc_time_julian_cent(jd):
    return (jd - 2451545.0) / 36525.0


def calc_geom_mean_long_sun(t):
    l0 = 280.46646 + t * (36000.76983 + 0.0003032 * t)
    while l0 > 360.0:
        l0 -= 360.0

    while l0 < 0.0:
        l0 += 360.0

    return l0


def calc_geom_mean_anomaly_sun(t):
    return 357.52911 + t * (35999.05029 - 0.0001537 * t)


def calc_eccentricity_earth_orbit(t):
    return 0.016708634 - t * (0.000042037 + 0.0000001267 * t)


def calc_sun_eq_of_center(t):
    m = calc_geom_mean_anomaly_sun(t)

    mrad = deg_to_rad(m)
    sinm = math.sin(mrad)
    sin2m = math.sin(mrad + mrad)
    sin3m = math.sin(mrad + mrad + mrad)
    return sinm * (1.914602 - t * (0.004817 + 0.000014 * t)) + sin2m * (0.019993 - 0.000101 * t) + sin3m * 0.000289


def calc_sun_true_long(t):
    l0 = calc_geom_mean_long_sun(t)
    c = calc_sun_eq_of_center(t)
    return l0 + c


def calc_sun_true_anomaly(t):
    m = calc_geom_mean_anomaly_sun(t)
    c = calc_sun_eq_of_center(t)
    return m + c


def calc_sun_rad_vector(t):
    v = calc_sun_true_anomaly(t)
    e = calc_eccentricity_earth_orbit(t)
    return (1.000001018 * (1 - e * e)) / (1 + e * math.cos(deg_to_rad(v)))


def calc_sun_apparent_long(t):
    o = calc_sun_true_long(t)

    omega = 125.04 - 1934.136 * t
    lbda = o - 0.00569 - 0.00478 * math.sin(deg_to_rad(omega))
    return lbda


def calc_mean_obliquity_of_ecliptic(t):
    seconds = 21.448 - t * (46.8150 + t * (0.00059 - t * 0.001813))
    e0 = 23.0 + (26.0 + (seconds/60.0)) / 60.0
    return e0


def calc_obliquity_correction(t):
    e0 = calc_mean_obliquity_of_ecliptic(t)

    omega = 125.04 - 1934.136 * t
    e = e0 + 0.00256 * math.cos(deg_to_rad(omega))
    return e


def calc_sun_right_ascension(t):
    e = calc_obliquity_correction(t)
    lbda = calc_sun_apparent_long(t)
 
    tananum = (math.cos(deg_to_rad(e)) * math.sin(deg_to_rad(lbda)))
    tanadenom = (math.cos(deg_to_rad(lbda)))
    alpha = rad_to_deg(math.atan2(tananum, tanadenom))
    return alpha


def calc_sun_declination(t):
    e = calc_obliquity_correction(t)
    lbda = calc_sun_apparent_long(t)

    sint = math.sin(deg_to_rad(e)) * math.sin(deg_to_rad(lbda))
    theta = rad_to_deg(math.asin(sint))
    return theta


def calc_equation_of_time(t):
    epsilon = calc_obliquity_correction(t)
    l0 = calc_geom_mean_long_sun(t)
    e = calc_eccentricity_earth_orbit(t)
    m = calc_geom_mean_anomaly_sun(t)

    y = math.tan(deg_to_rad(epsilon) / 2.0)
    y *= y

    sin2l0 = math.sin(2.0 * deg_to_rad(l0))
    sinm   = math.sin(deg_to_rad(m))
    cos2l0 = math.cos(2.0 * deg_to_rad(l0))
    sin4l0 = math.sin(4.0 * deg_to_rad(l0))
    sin2m  = math.sin(2.0 * deg_to_rad(m))

    e_time = y * sin2l0 - 2.0 * e * sinm + 4.0 * e * y * sinm * cos2l0 - 0.5 * y * y * sin4l0 - 1.25 * e * e * sin2m

    return rad_to_deg(e_time) * 4.0


def calc_hour_angle(tme, longitude, eqtime):
    return 15.0 * (tme - (longitude / 15.0) - (eqtime / 60.0))


def get_latitude(lat_long_form):
    neg = 0
    degs = lat_long_form["latDeg"]

    if lat_long_form["latDeg"] < 0:
        neg = 1

    mins = lat_long_form["latMin"]
    secs = lat_long_form["latSec"]

    if neg != 1:
        return degs + (mins / 60) + (secs / 3600)
    elif neg == 1:
        return degs - (mins / 60) - (secs / 3600)
    else:
        return -9999


def get_longitude(lat_long_form):
    neg = 0
    degs = lat_long_form["lonDeg"]
    if lat_long_form["lonDeg"] < 0:
        neg = 1

    mins = lat_long_form["lonMin"]
    secs = lat_long_form["lonSec"]

    if neg != 1:
        return degs + (mins / 60) + (secs / 3600)
    elif neg == 1:
        return degs - (mins / 60) - (secs / 3600)
    else:
        return -9999


def calc_sun(rise_set_form, lat_long_form, index, index2):
    if index2 != 0:
        set_lat_long(lat_long_form, index2)

    latitude = get_latitude(lat_long_form)
    longitude = get_longitude(lat_long_form)

    for indexRS, m in enumerate(monthList):
        if m.name == rise_set_form['mos']:
            break
    else:
        raise RuntimeError

    if is_valid_input(rise_set_form, indexRS, lat_long_form):
        if -89.8 > latitude >= -90:
            print("All latitudes between 89.8 and 90 S\n will be set to -89.8.")
            lat_long_form["latDeg"] = -89.8
            latitude = -89.8

        if 89.8 < latitude <= 90:
            print("All latitudes between 89.8 and 90 N\n will be set to 89.8.")
            lat_long_form["latDeg"] = 89.8
            latitude = 89.8

    zone = lat_long_form["hrsToGMT"]
    daySavings = yesno[index].value  # = 0 (no) or 60 (yes)

    ss = rise_set_form["secs"]
    mm = rise_set_form["mins"]
    hh = rise_set_form["hour"] - (daySavings / 60)

    while hh > 23:
        hh -= 24

    rise_set_form["hour"] = hh + (daySavings / 60)
    if mm > 9:
        rise_set_form["mins"] = mm
    else:
        rise_set_form["mins"] = mm

    if ss > 9:
        rise_set_form["secs"] = ss

    else:
        rise_set_form["secs"] = ss

    timenow = hh + mm / 60 + ss / 3600 + zone # in hours since 0Z


    JD = calc_jd(rise_set_form["year"], indexRS + 1, rise_set_form["day"])
    t = calc_time_julian_cent(JD + timenow / 24.0)
    theta = calc_sun_declination(t)
    E_time = calc_equation_of_time(t)

    eq_time = E_time
    solar_dec = theta

    rise_set_form["eqTime"] = math.floor(100 * eq_time) / 100
    rise_set_form["solarDec"] = math.floor(100 * solar_dec) / 100

    solar_time_fix = eq_time - 4.0 * longitude + 60.0 * zone
    true_solar_time = hh * 60.0 + mm + ss / 60.0 + solar_time_fix

    while true_solar_time > 1440:
        true_solar_time -= 1440

    hour_angle = true_solar_time / 4.0 - 180.0

    if hour_angle < -180:
        hour_angle += 360.0

    ha_rad = deg_to_rad(hour_angle)

    csz = (
        math.sin(deg_to_rad(latitude)) *
        math.sin(deg_to_rad(solar_dec)) +
        math.cos(deg_to_rad(latitude)) *
        math.cos(deg_to_rad(solar_dec)) *
        math.cos(ha_rad)
    )
    if csz > 1.0:
        csz = 1.0
    elif csz < -1.0:
        csz = -1.0

    zenith = rad_to_deg(math.acos(csz))

    az_denom = (
        math.cos(deg_to_rad(latitude)) *
        math.sin(deg_to_rad(zenith))
    )
    if abs(az_denom) > 0.001:
        az_rad = (
            (
                (math.sin(deg_to_rad(latitude)) * math.cos(deg_to_rad(zenith))) -
                math.sin(deg_to_rad(solar_dec))
            ) / az_denom
        )

        if abs(az_rad) > 1.0:
            if az_rad < 0:
                az_rad = -1.0
            else:
                az_rad = 1.0

        azimuth = 180.0 - rad_to_deg(math.acos(az_rad))

        if hour_angle > 0.0:
            azimuth = -azimuth
        else:
            if latitude > 0.0:
                azimuth = 180.0
            else:
                azimuth = 0.0

        if azimuth < 0.0:
            azimuth += 360.0

        exoatm_elevation = 90.0 - zenith

        if exoatm_elevation > 85.0:
            refraction_correction = 0.0
        else:
            te = math.tan(deg_to_rad(exoatm_elevation))
            if exoatm_elevation > 5.0:
                refraction_correction = 58.1 / te - 0.07 / (te * te * te) + 0.000086 / (te * te * te * te * te)

            elif exoatm_elevation > -0.575:
                refraction_correction = (
                    1735.0 + exoatm_elevation * -518.2 + exoatm_elevation *
                    (103.4 + exoatm_elevation * (-12.79 + exoatm_elevation * 0.711))
                )
            else:
                refraction_correction = -20.774 / te

            refraction_correction = refraction_correction / 3600.0

        solarZen = zenith - refraction_correction

        if solarZen < 108.0: # astronomical twilight
            rise_set_form["azimuth"] = (math.floor(100 * azimuth)) / 100
            rise_set_form["elevation"] = (math.floor(100 * (90.0 - solarZen))) / 100

            if solarZen < 90.0:
                rise_set_form["coszen"] = (math.floor(10000.0 * (math.cos(deg_to_rad(solarZen))))) / 10000.0
            else:
                rise_set_form["coszen"] = 0.0

        else: # do not report az & el after astro twilight
            rise_set_form["azimuth"] = "dark"
            rise_set_form["elevation"] = "dark"
            rise_set_form["coszen"] = 0.0

        conv_lat_long(lat_long_form)

    else:
        rise_set_form["azimuth"] = "error"
        rise_set_form["elevation"] = "error"
        rise_set_form["eqTime"] = "error"
        rise_set_form["solarDec"] = "error"
        rise_set_form["coszen"] = "error"


def getdms(t):
    d = abs(t)
    n = math.floor(d)
    m = 3600 * (d - n)
    a = math.floor(m / 60)
    m = round(1e4 * (m - 60 * a)) / 1e4
    return n, a, m

form1 = {}

form1['latDeg'], form1['latMin'], form1['latSec'] = getdms(LATITUDE)
form1['lonDeg'], form1['lonMin'], form1['lonSec'] = getdms(LONGITUDE)
form1['hrsToGMT'] = UTC_OFFSET

import time

localtime = time.localtime(time.time())

form2 = {}

form2['hour'] = int(time.strftime('%H', localtime))
form2['mins'] = int(time.strftime('%M', localtime))
form2['secs'] = int(time.strftime('%S', localtime))
form2['mos'] = time.strftime('%B', localtime)
form2['day'] = int(time.strftime('%d', localtime))
form2['year'] = int(time.strftime('%Y', localtime))
form2['ampm'] = 24
dayAns = DAYLIGHT_SAVINGS


calc_sun(form2, form1, int(DAYLIGHT_SAVINGS), 0)

print(time.strftime('%c', localtime))
print()
print('Latitude:', form1['latDeg'], 'degrees', form1['latMin'], 'minutes', form1['latSec'], 'seconds')
print('Longitude:', form1['lonDeg'], 'degrees', form1['lonMin'], 'minutes', form1['lonSec'], 'seconds')
print()
print('Equation of time (minutes):', form2['eqTime'])
print('Solar Declination (degrees):', form2['solarDec'])
print('Solar Azimuth:', form2['azimuth'])
print('Solar Elevation:', form2['elevation'])
print('Cosine of solar zenith angle:', form2['coszen'])

the cloud cover is easier. I will use your lat and lon to scrape a radar map of the cloud cover from a radar service to determine the cloud cover.
If you like the work I have been doing then feel free to Image

User avatar
kgschlosser
Site Admin
Posts: 5214
Joined: Fri Jun 05, 2015 5:43 am
Location: Rocky Mountains, Colorado USA

Re: TCP Control for Panasonic projector

Post by kgschlosser » Thu Oct 17, 2019 7:24 am

OK so scratch that last script.

This one supports cloud cover.. I am going to target it a wee bit better by using the solar calculations to know exactly where in the sky the sun is in order to determine if a cloud is in the way.. But for the time being this uses a cloud density and % of the sky the clouds cover.. there are adjustments you can make.. read the comments at the top of the script. (comments have a # before them)

I didn't tell you how to use this thing either..
create a macro. put a python script action (eventghost\python script) in the macro. paste the code below into the action and click on apply and then test

you ae going to need to go to openweathermap.org and register so you can get an api key. the api key after you generate it may take an hour or so to become active. you need to enter that key into the script.

There is a full set of directions in the comments of the script. Do read them.

Code: Select all

from __future__ import print_function
import requests
from PIL import Image
from io import BytesIO

# these are the settings you need to change.

# in order to get the exact lat/lon for where the projector is located
# I want you to use this website. https://www.latlong.net/

# be sure to click on the map when you zoom in on it's location.
# and write down the lat and lon.

# once you have clicked on the location this is going to set a "pin" that you can see.
# I now ant you to zoom all the way out. You will still be able to see the pin.
# now zoom on and count the number of zoom levels keeping the mouse pointer
# hovered over the pin while you do this. what we are trying to get here is a
# zoom level that will encompass your physical view of the sky from that location.
# any clouds within that physical view are going to effect how bright it is outside.
# write down this zoom level
LATITUDE = 40.271877
LONGITUDE = -76.885515
DAYLIGHT_SAVINGS = False
UTC_OFFSET = 5

# the radar images of the cloud cover are provided by openweathermap.org
# in order to use the cloud cover portion of this script you will
# need to register to get an api key. It is free to do this
# and allows you to query their system once a second.
RADAR_API_KEY = ''

# You are going to need to tinker a bit with this number in order to get the
# ideal point in which you consider it dar enough to turn the second lamp on

# enter the zoom level you wrote down
RADAR_ZOOM = 9

# I am going to retool this portion of the script later on. I wanted to know
# if this is a sound way of going about it first. once I know this works properly
# I will be able to map the suns location relative to the radar image and be able
# to determine if a cloud is blocking the sun. but for now this should work.

# This is the percentage of the radar image that is covered
# by a the cloud level you set with RADAR_CLOUD_DENSITY.
RADAR_CLOUD_COVER_PERCENT = 35.00

# This parameter represents how thick or dark the clouds are.
# The range is 0 (no cloud) to 100 (day becomes night)
RADAR_CLOUD_DENSITY = 41

# do not edit anything below this point

RADAR_URL = 'https://tile.openweathermap.org/map/clouds_new/{z}/{x}/{y}.png'

import math

def lat_lon_to_tile(lat_deg, lon_deg, zoom):
  lat_rad = math.radians(lat_deg)
  n = 2.0 ** zoom
  xtile = int((lon_deg + 180.0) / 360.0 * n)
  ytile = int((1.0 - math.asinh(math.tan(lat_rad)) / math.pi) / 2.0 * n)

  # ytile = n - ytile - 1

  return xtile, ytile, zoom

def get_url():
    x, y, z = lat_lon_to_tile( LATITUDE, LONGITUDE, RADAR_ZOOM)

    url = RADAR_URL.format(x=x, y=y, z=z)
    return url

class Month(object):
    def __init__(self, name, numdays, abbr):
        self.name = name
        self.numdays = numdays
        self.abbr = abbr


class ANS(object):

    def __init__(self, daySave, value):
        self.daySave = daySave
        self.value = value


class City(object):

    def __init__(self, name, lat, lng, zoneHr):
        self.name = name
        self.lat = lat
        self.lng = lng
        self.zoneHr = zoneHr


monthList = [
    Month("January", 31, "Jan"),
    Month("February", 28, "Feb"),
    Month("March", 31, "Mar"),
    Month("April", 30, "Apr"),
    Month("May", 31, "May"),
    Month("June", 30, "Jun"),
    Month("July", 31, "Jul"),
    Month("August", 31, "Aug"),
    Month("September", 30, "Sep"),
    Month("October", 31, "Oct"),
    Month("November", 30, "Nov"),
    Month("December", 31, "Dec")
]


yesno = [
    ANS("No",0),
    ANS("Yes",60)
]


city = [
    City("Enter Lat/Long -->", 0, 0, 0),
    City("", 0, 0, 0),
    City("US CITIES", 0, 0, 0),
    City("Albuquerque, NM", 35.0833, 106.65, 7),
    City("Anchorage, AK", 61.217, 149.90, 9),
    City("Atlanta, GA", 33.733, 84.383, 5),
    City("Austin, TX", 30.283, 97.733, 6),
    City("Birmingham, AL", 33.521, 86.8025, 6),
    City("Bismarck, ND", 46.817, 100.783, 6),
    City("Boston, MA", 42.35, 71.05, 5),
    City("Boulder, CO", 40.125, 105.237, 7),
    City("Chicago, IL", 41.85, 87.65, 6),
    City("Dallas, TX", 32.46, 96.47, 6),
    City("Denver, CO", 39.733, 104.983, 7),
    City("Detroit, MI", 42.333, 83.05, 5),
    City("Honolulu, HI", 21.30, 157.85, 10),
    City("Houston, TX", 29.75, 95.35, 6),
    City("Indianapolis, IN", 39.767, 86.15, 5),
    City("Jackson, MS", 32.283, 90.183, 6),
    City("Kansas City, MO", 39.083, 94.567, 6),
    City("Los Angeles, CA", 34.05, 118.233, 8),
    City("Menomonee Falls, WI", 43.11, 88.10, 6),
    City("Miami, FL", 25.767, 80.183, 5),
    City("Minneapolis, MN", 44.967, 93.25, 6),
    City("New Orleans, LA", 29.95, 90.067, 6),
    City("New York City, NY", 40.7167, 74.0167, 5),
    City("Oklahoma City, OK", 35.483, 97.533, 6),
    City("Philadelphia, PA", 39.95, 75.15, 5),
    City("Phoenix, AZ", 33.433, 112.067, 7),
    City("Pittsburgh, PA",40.433,79.9833,5),
    City("Portland, ME", 43.666, 70.283, 5),
    City("Portland, OR", 45.517, 122.65, 8),
    City("Raleigh, NC", 35.783, 78.65, 5),
    City("Richmond, VA", 37.5667, 77.450, 5),
    City("Saint Louis, MO", 38.6167, 90.1833, 6),
    City("San Antonio, TX", 29.53, 98.47, 6),
    City("San Diego, CA", 32.7667, 117.2167, 8),
    City("San Francisco, CA", 37.7667, 122.4167, 8),
    City("Seattle, WA", 47.60, 122.3167, 8),
    City("Washington DC", 38.8833, 77.0333, 5),
    City("", 0, 0, 0),
    City("WORLD CITIES", 0, 0, 0),
    City("Beijing, China", 39.9167, -116.4167, -8),
    City("Berlin, Germany", 52.33, -13.30, -1),
    City("Bombay, India", 18.9333, -72.8333, -5.5),
    City("Buenos Aires, Argentina", -34.60, 58.45, 3),
    City("Cairo, Egypt", 30.10, -31.3667, -2),
    City("Cape Town, South Africa", -33.9167, -18.3667, -2),
    City("Caracas, Venezuela", 10.50, 66.9333, 4),
    City("Helsinki, Finland", 60.1667, -24.9667,-2),
    City("Hong Kong, China", 22.25,-114.1667, -8),
    City("Jerusalem, Israel", 31.7833, -35.2333, -2),
    City("London, England", 51.50, 0.1667, 0),
    City("Mexico City, Mexico", 19.4, 99.15, 6),
    City("Moscow, Russia", 55.75, -37.5833, -3),
    City("New Delhi, India", 28.6, -77.2, -5.5),
    City("Ottawa, Canada", 45.41667, 75.7,5),
    City("Paris, France", 48.8667, -2.667, -1),
    City("Rio de Janeiro, Brazil", -22.90, 43.2333, 3),
    City("Riyadh, Saudi Arabia", 24.633, -46.71667, -3),
    City("Rome, Italy", 41.90, -12.4833, -1),
    City("Sydney, Australia", -33.8667, -151.2167, -10),
    City("Tokyo, Japan", 35.70, -139.7667, -9),
    City("Zurich, Switzerland", 47.3833, -8.5333, -1),
    City("", 0, 0, 0),
    City("SURFRAD NETWORK", 0, 0, 0),
    City("Goodwin Creek, MS", 34.2544444, 89.8738888, 6),
    City("Fort Peck, MT", 48.310555, 105.1025, 7),
    City("Bondville, IL", 40.055277, 88.371944, 6),
    City("Table Mountain, CO", 40.125, 105.23694, 7),
    City("Desert Rock, NV", 36.626, 116.018, 8),
    City("Penn State, PA", 40.72, 77.93, 5),
    City("Canaan Valley, WV", 39.1, 79.4, 5),
    City("Sioux Falls, SD", 43.733, 96.6233, 6),
    City("", 0, 0, 0),
    City("ARM/CART NETWORK", 0, 0, 0),
    City("Atqasuk, AK", 70.47215, 157.4078, 9),
    City("Barrow, AK", 71.30, 156.683, 9),
    City("Manus Island, PNG", -2.06, -147.425, -10),
    City("Nauru Island", -0.52, -166.92, -12),
    City("Darwin, Australia", -12.425, -130.891, -9.5),
    City("SGP Central Facility", 36.6167, 97.5, 6),
    City("",0,0,0),
    City("SOLRAD NETWORK", 0, 0, 0),
    City("Albuquerque, NM", 35.04, 106.62, 7),
    City("Bismarck, ND", 46.77, 100.77, 6),
    City("Hanford, CA", 36.31, 119.63, 8),
    City("Madison, WI", 43.13, 89.33, 6),
    City("Oak Ridge, TN", 35.96, 84.37, 5),
    City("Salt Lake City, UT", 40.77, 111.97, 7),
    City("Seattle, WA", 47.68, 122.25, 8),
    City("Sterling, VA", 38.98, 77.47, 5),
    City("Tallahassee, FL", 30.38, 84.37, 5)
]

def set_lat_long(f, index):

    f["latDeg"] = city[index].lat
    f["lonDeg"] = city[index].lng

    f["latMin"] = 0
    f["latSec"] = 0
    f["lonMin"] = 0
    f["lonSec"] = 0

    conv_lat_long(f)

    f["hrsToGMT"] =  city[index].zoneHr

def is_leap_year(yr):
    yr = int(yr)
    return (
        (yr % 4 == 0 and yr % 100 != 0) or
        yr % 400 == 0
    )

def is_pos_integer(inputVal):     
    inputStr = str(inputVal)
    for oneChar in list(inputStr):
        if oneChar < "0" or oneChar > "9":
            return False

    return True


def is_valid_input(f, index, lat_long_form): 
    if f["day"] == "": # see if the day field is empty
        print("You must enter a day before attempting the calculation.")
        return False

    elif f["year"] == "": 	# see if year field is empty
        print("You must enter a year before attempting the calculation.")
        return False

    elif is_pos_integer(f["day"]) is False or f["day"] == 0:
        print("The day must be a positive integer.")
        return False

    elif is_pos_integer(f["year"]) is False:
        print("The year must be a positive integer.")
        return False

    elif f["hour"] == "": # see if hour field is empty
        print("You must enter a time before attempting the calculation.")
        return False

    elif (
        is_pos_integer(f["hour"]) is False or
        is_pos_integer(f["mins"]) is False or
        is_pos_integer(f["secs"]) is False
    ):
        print("The time fields must contain positive integers.")
        return False

    elif int(f["hour"]) > 23:
        print("Hour must be between 0 and 23.")
        return False

    elif int(f["mins"]) > 59:
        print("Minutes must be between 0 and 59.")
        return False

    elif int(f["secs"]) > 59:
        print("Seconds must be between 0 and 59.")
        return False

    elif index != 1 and int(f["day"]) > monthList[index].numdays:
        print("There are only " + str(monthList[index].numdays) + " days in " + str(monthList[index].name) + ".")
        return False


    elif index == 1:
        if is_leap_year(f["year"]):
            if int(f["day"]) > monthList[index].numdays + 1:
                print("There are only " + str(monthList[index].numdays + 1) + " days in " + str(monthList[index].name) + ".")
                return False
            else:
                return True

        else: # year entered is not a leap year
            if int(f["day"]) > monthList[index].numdays:
                print("There are only " + str(monthList[index].numdays) + " days in " + str(monthList[index].name) + ".")
                return False

            else:
                return True
    else:
        return True


def conv_lat_long(f):

    if f["latDeg"] == "":
        f["latDeg"] = '0'

    if f["latMin"] == "":
        f["latMin"] = '0'

    if f["latSec"] == "":
        f["latSec"] = '0'

    if f["lonDeg"] == "":
        f["lonDeg"] = '0'

    if f["lonMin"] == "":
        f["lonMin"] = '0'

    if f["lonSec"] == "":
        f["lonSec"] = '0'


    neg = 0
    if f["latDeg"] < 0:
        neg = 1

    if neg != 1:

        latSeconds = (
            (f["latDeg"] * 3600) +
            (f["latMin"] * 60) +
            (f["latSec"] * 1)
        )

        f["latDeg"] = math.floor(latSeconds / 3600)
        f["latMin"] = math.floor((latSeconds - (f["latDeg"] * 3600)) / 60)
        f["latSec"] = math.floor((latSeconds - (f["latDeg"] * 3600) - (f["latMin"] * 60)) + 0.5)


    elif f["latDeg"] > -1:

        latSeconds = (
            (f["latDeg"] * 3600) -
            (f["latMin"] * 60) -
            (f["latSec"] * 1)
        )

        f["latDeg"] = 0
        f["latMin"] = math.floor(-latSeconds / 60)
        f["latSec"] = math.floor( (-latSeconds - (f["latMin"] * 60)) + 0.5)


    else:
        latSeconds = (
            (f["latDeg"] * 3600) -
            (f["latMin"] * 60) -
            (f["latSec"] * 1)
        )

        f["latDeg"] = math.ceil(latSeconds / 3600)
        f["latMin"] = math.floor((-latSeconds + (f["latDeg"] * 3600)) / 60)
        f["latSec"] = math.floor((-latSeconds + (f["latDeg"] * 3600) - (f["latMin"] * 60)) + 0.5)



    neg = 0
    if f["lonDeg"] < 0:
        neg = 1


    if neg != 1:
        lonSeconds = (
            (f["lonDeg"] * 3600) +
            (f["lonMin"] * 60) +
            (f["lonSec"] * 1)
        )
        
        f["lonDeg"] = math.floor(lonSeconds / 3600)
        f["lonMin"] = math.floor((lonSeconds - (f["lonDeg"] * 3600)) / 60)
        f["lonSec"] = math.floor((lonSeconds - (f["lonDeg"] * 3600) - (f["lonMin"]) * 60) + 0.5)

    elif f["lonDeg"] > -1:

        lonSeconds = (
            (f["lonDeg"] * 3600) -
            (f["lonMin"] * 60) -
            (f["lonSec"] * 1)
        )

        f["lonDeg"] = 0
        f["lonMin"] = math.floor(-lonSeconds / 60)
        f["lonSec"] = math.floor((-lonSeconds - (f["lonMin"] * 60)) + 0.5)


    else:
        lonSeconds = (
            (f["lonDeg"] * 3600) -
            (f["lonMin"] * 60) -
            (f["lonSec"] * 1)
        )

        f["lonDeg"] = math.ceil(lonSeconds / 3600)
        f["lonMin"] = math.floor((-lonSeconds + (f["lonDeg"] * 3600)) / 60)
        f["lonSec"] = math.floor((-lonSeconds + (f["lonDeg"] * 3600) - (f["lonMin"] * 60)) + 0.5)

    # Test for invalid lat/long input

    if latSeconds > 323280:
        print("You have entered an invalid latitude.\nSetting lat=89.8.")
        f["latDeg"] = 89.8
        f["latMin"] = 0
        f["latSec"] = 0

    if latSeconds < -323280:
        print ("You have entered an invalid latitude.\n  Setting lat= -89.8.")
        f["latDeg"] = -89.8
        f["latMin"] = 0
        f["latSec"] = 0

    if lonSeconds > 648000:
        print ("You have entered an invalid longitude.\n Setting lon= 180.")
        f["lonDeg"] = 180
        f["lonMin"] = 0
        f["lonSec"] = 0

    if lonSeconds < -648000:
        print("You have entered an invalid latitude.\n Setting lon= -180.")
        f["lonDeg"] = -180
        f["lonMin"] = 0
        f["lonSec"] =0


def rad_to_deg(angle_rad): 
    return 180.0 * angle_rad / math.pi


def deg_to_rad(angle_deg): 
    return math.pi * angle_deg / 180.0


def calc_day_of_year(mn, dy, lpyr): 
    k = 2 if lpyr else 1
    return math.floor((275 * mn) / 9) - k * math.floor((mn + 9) / 12) + dy -30


def calc_day_of_week(juld):
    dow = [
        "Sunday",
        "Monday",
        "Tuesday",
        "Wednesday",
        "Thursday",
        "Friday",
        "Saturday"
    ]
    return dow[int((juld + 1.5) % 7)]


def calc_jd(year, month, day):
    if month <= 2:
        year -= 1
        month += 12

    a = math.floor(year / 100)
    b = 2 - a + math.floor(a / 4)

    return math.floor(365.25 * (year + 4716)) + math.floor(30.6001 * (month + 1)) + day + b - 1524.5


def calc_time_julian_cent(jd):
    return (jd - 2451545.0) / 36525.0


def calc_geom_mean_long_sun(t):
    l0 = 280.46646 + t * (36000.76983 + 0.0003032 * t)
    while l0 > 360.0:
        l0 -= 360.0

    while l0 < 0.0:
        l0 += 360.0

    return l0


def calc_geom_mean_anomaly_sun(t):
    return 357.52911 + t * (35999.05029 - 0.0001537 * t)


def calc_eccentricity_earth_orbit(t):
    return 0.016708634 - t * (0.000042037 + 0.0000001267 * t)


def calc_sun_eq_of_center(t):
    m = calc_geom_mean_anomaly_sun(t)

    mrad = deg_to_rad(m)
    sinm = math.sin(mrad)
    sin2m = math.sin(mrad + mrad)
    sin3m = math.sin(mrad + mrad + mrad)
    return sinm * (1.914602 - t * (0.004817 + 0.000014 * t)) + sin2m * (0.019993 - 0.000101 * t) + sin3m * 0.000289


def calc_sun_true_long(t):
    l0 = calc_geom_mean_long_sun(t)
    c = calc_sun_eq_of_center(t)
    return l0 + c


def calc_sun_true_anomaly(t):
    m = calc_geom_mean_anomaly_sun(t)
    c = calc_sun_eq_of_center(t)
    return m + c


def calc_sun_rad_vector(t):
    v = calc_sun_true_anomaly(t)
    e = calc_eccentricity_earth_orbit(t)
    return (1.000001018 * (1 - e * e)) / (1 + e * math.cos(deg_to_rad(v)))


def calc_sun_apparent_long(t):
    o = calc_sun_true_long(t)

    omega = 125.04 - 1934.136 * t
    lbda = o - 0.00569 - 0.00478 * math.sin(deg_to_rad(omega))
    return lbda


def calc_mean_obliquity_of_ecliptic(t):
    seconds = 21.448 - t * (46.8150 + t * (0.00059 - t * 0.001813))
    e0 = 23.0 + (26.0 + (seconds/60.0)) / 60.0
    return e0


def calc_obliquity_correction(t):
    e0 = calc_mean_obliquity_of_ecliptic(t)

    omega = 125.04 - 1934.136 * t
    e = e0 + 0.00256 * math.cos(deg_to_rad(omega))
    return e


def calc_sun_right_ascension(t):
    e = calc_obliquity_correction(t)
    lbda = calc_sun_apparent_long(t)
 
    tananum = (math.cos(deg_to_rad(e)) * math.sin(deg_to_rad(lbda)))
    tanadenom = (math.cos(deg_to_rad(lbda)))
    alpha = rad_to_deg(math.atan2(tananum, tanadenom))
    return alpha


def calc_sun_declination(t):
    e = calc_obliquity_correction(t)
    lbda = calc_sun_apparent_long(t)

    sint = math.sin(deg_to_rad(e)) * math.sin(deg_to_rad(lbda))
    theta = rad_to_deg(math.asin(sint))
    return theta


def calc_equation_of_time(t):
    epsilon = calc_obliquity_correction(t)
    l0 = calc_geom_mean_long_sun(t)
    e = calc_eccentricity_earth_orbit(t)
    m = calc_geom_mean_anomaly_sun(t)

    y = math.tan(deg_to_rad(epsilon) / 2.0)
    y *= y

    sin2l0 = math.sin(2.0 * deg_to_rad(l0))
    sinm   = math.sin(deg_to_rad(m))
    cos2l0 = math.cos(2.0 * deg_to_rad(l0))
    sin4l0 = math.sin(4.0 * deg_to_rad(l0))
    sin2m  = math.sin(2.0 * deg_to_rad(m))

    e_time = y * sin2l0 - 2.0 * e * sinm + 4.0 * e * y * sinm * cos2l0 - 0.5 * y * y * sin4l0 - 1.25 * e * e * sin2m

    return rad_to_deg(e_time) * 4.0


def calc_hour_angle(tme, longitude, eqtime):
    return 15.0 * (tme - (longitude / 15.0) - (eqtime / 60.0))


def get_latitude(lat_long_form):
    neg = 0
    degs = lat_long_form["latDeg"]

    if lat_long_form["latDeg"] < 0:
        neg = 1

    mins = lat_long_form["latMin"]
    secs = lat_long_form["latSec"]

    if neg != 1:
        return degs + (mins / 60) + (secs / 3600)
    elif neg == 1:
        return degs - (mins / 60) - (secs / 3600)
    else:
        return -9999


def get_longitude(lat_long_form):
    neg = 0
    degs = lat_long_form["lonDeg"]
    if lat_long_form["lonDeg"] < 0:
        neg = 1

    mins = lat_long_form["lonMin"]
    secs = lat_long_form["lonSec"]

    if neg != 1:
        return degs + (mins / 60) + (secs / 3600)
    elif neg == 1:
        return degs - (mins / 60) - (secs / 3600)
    else:
        return -9999


def calc_sun(rise_set_form, lat_long_form, index, index2):
    if index2 != 0:
        set_lat_long(lat_long_form, index2)

    latitude = get_latitude(lat_long_form)
    longitude = get_longitude(lat_long_form)

    for indexRS, m in enumerate(monthList):
        if m.name == rise_set_form['mos']:
            break
    else:
        raise RuntimeError

    if is_valid_input(rise_set_form, indexRS, lat_long_form):
        if -89.8 > latitude >= -90:
            print("All latitudes between 89.8 and 90 S\n will be set to -89.8.")
            lat_long_form["latDeg"] = -89.8
            latitude = -89.8

        if 89.8 < latitude <= 90:
            print("All latitudes between 89.8 and 90 N\n will be set to 89.8.")
            lat_long_form["latDeg"] = 89.8
            latitude = 89.8

    zone = lat_long_form["hrsToGMT"]
    daySavings = yesno[index].value  # = 0 (no) or 60 (yes)

    ss = rise_set_form["secs"]
    mm = rise_set_form["mins"]
    hh = rise_set_form["hour"] - (daySavings / 60)

    while hh > 23:
        hh -= 24

    rise_set_form["hour"] = hh + (daySavings / 60)
    if mm > 9:
        rise_set_form["mins"] = mm
    else:
        rise_set_form["mins"] = mm

    if ss > 9:
        rise_set_form["secs"] = ss

    else:
        rise_set_form["secs"] = ss

    timenow = hh + mm / 60 + ss / 3600 + zone # in hours since 0Z


    JD = calc_jd(rise_set_form["year"], indexRS + 1, rise_set_form["day"])
    t = calc_time_julian_cent(JD + timenow / 24.0)
    theta = calc_sun_declination(t)
    E_time = calc_equation_of_time(t)

    eq_time = E_time
    solar_dec = theta

    rise_set_form["eqTime"] = math.floor(100 * eq_time) / 100
    rise_set_form["solarDec"] = math.floor(100 * solar_dec) / 100

    solar_time_fix = eq_time - 4.0 * longitude + 60.0 * zone
    true_solar_time = hh * 60.0 + mm + ss / 60.0 + solar_time_fix

    while true_solar_time > 1440:
        true_solar_time -= 1440

    hour_angle = true_solar_time / 4.0 - 180.0

    if hour_angle < -180:
        hour_angle += 360.0

    ha_rad = deg_to_rad(hour_angle)

    csz = (
        math.sin(deg_to_rad(latitude)) *
        math.sin(deg_to_rad(solar_dec)) +
        math.cos(deg_to_rad(latitude)) *
        math.cos(deg_to_rad(solar_dec)) *
        math.cos(ha_rad)
    )
    if csz > 1.0:
        csz = 1.0
    elif csz < -1.0:
        csz = -1.0

    zenith = rad_to_deg(math.acos(csz))

    az_denom = (
        math.cos(deg_to_rad(latitude)) *
        math.sin(deg_to_rad(zenith))
    )
    if abs(az_denom) > 0.001:
        az_rad = (
            (
                (math.sin(deg_to_rad(latitude)) * math.cos(deg_to_rad(zenith))) -
                math.sin(deg_to_rad(solar_dec))
            ) / az_denom
        )

        if abs(az_rad) > 1.0:
            if az_rad < 0:
                az_rad = -1.0
            else:
                az_rad = 1.0

        azimuth = 180.0 - rad_to_deg(math.acos(az_rad))

        if hour_angle > 0.0:
            azimuth = -azimuth
        else:
            if latitude > 0.0:
                azimuth = 180.0
            else:
                azimuth = 0.0

        if azimuth < 0.0:
            azimuth += 360.0

        exoatm_elevation = 90.0 - zenith

        if exoatm_elevation > 85.0:
            refraction_correction = 0.0
        else:
            te = math.tan(deg_to_rad(exoatm_elevation))
            if exoatm_elevation > 5.0:
                refraction_correction = 58.1 / te - 0.07 / (te * te * te) + 0.000086 / (te * te * te * te * te)

            elif exoatm_elevation > -0.575:
                refraction_correction = (
                    1735.0 + exoatm_elevation * -518.2 + exoatm_elevation *
                    (103.4 + exoatm_elevation * (-12.79 + exoatm_elevation * 0.711))
                )
            else:
                refraction_correction = -20.774 / te

            refraction_correction = refraction_correction / 3600.0

        solarZen = zenith - refraction_correction

        if solarZen < 108.0: # astronomical twilight
            rise_set_form["azimuth"] = (math.floor(100 * azimuth)) / 100
            rise_set_form["elevation"] = (math.floor(100 * (90.0 - solarZen))) / 100

            if solarZen < 90.0:
                rise_set_form["coszen"] = (math.floor(10000.0 * (math.cos(deg_to_rad(solarZen))))) / 10000.0
            else:
                rise_set_form["coszen"] = 0.0

        else: # do not report az & el after astro twilight
            rise_set_form["azimuth"] = "dark"
            rise_set_form["elevation"] = "dark"
            rise_set_form["coszen"] = 0.0

        conv_lat_long(lat_long_form)

    else:
        rise_set_form["azimuth"] = "error"
        rise_set_form["elevation"] = "error"
        rise_set_form["eqTime"] = "error"
        rise_set_form["solarDec"] = "error"
        rise_set_form["coszen"] = "error"


def getdms(t):
    d = abs(t)
    n = math.floor(d)
    m = 3600 * (d - n)
    a = math.floor(m / 60)
    m = round(1e4 * (m - 60 * a)) / 1e4
    return n, a, m

form1 = {}

form1['latDeg'], form1['latMin'], form1['latSec'] = getdms(LATITUDE)
form1['lonDeg'], form1['lonMin'], form1['lonSec'] = getdms(LONGITUDE)
form1['hrsToGMT'] = UTC_OFFSET

import time

localtime = time.localtime(time.time())

form2 = {}

form2['hour'] = int(time.strftime('%H', localtime))
form2['mins'] = int(time.strftime('%M', localtime))
form2['secs'] = int(time.strftime('%S', localtime))
form2['mos'] = time.strftime('%B', localtime)
form2['day'] = int(time.strftime('%d', localtime))
form2['year'] = int(time.strftime('%Y', localtime))
form2['ampm'] = 24
dayAns = DAYLIGHT_SAVINGS


# Azimuth

calc_sun(form2, form1, int(DAYLIGHT_SAVINGS), 0)


response = requests.get(get_url(), params=dict(appid=RADAR_API_KEY))
stream = BytesIO(response.content)

# with open(r'C:\Users\Administrator\Desktop\New folder (126)\test.png', 'wb') as f:
    # f.write(response.content)

image = Image.open(stream).convert('RGBA')
image_data = image.getdata(3)
stream.close()
image.close()

cloud_density = ((RADAR_CLOUD_DENSITY * 102) / 100) + 153
cloud_cover = list(i for i in image_data if i >= cloud_density)
percent_covered = (float(len(cloud_cover)) / float(len(image_data))) * 100.0


print(time.strftime('%c', localtime))
print()
print('Latitude:', form1['latDeg'], 'degrees', form1['latMin'], 'minutes', form1['latSec'], 'seconds')
print('Longitude:', form1['lonDeg'], 'degrees', form1['lonMin'], 'minutes', form1['lonSec'], 'seconds')
print()
print('Cloud Cover Percentage:', '{0:.2f}%'.format(percent_covered))
print()
print('Equation of time (minutes):', form2['eqTime'])
print('Solar Declination (degrees):', form2['solarDec'])
print('Solar Azimuth:', form2['azimuth'])
print('Solar Elevation:', form2['elevation'])
print('Cosine of solar zenith angle:', form2['coszen'])
this is only for testing purposes at the moment. It is not going to trigger any events, it is only going to print out the data to the log.
I do not know what your requirements are for solar elevation, declination and azimuth to know when to turn on the second lamp. once I have those from you we can add that to the script for the projector.

I can set the scrip up so that it runs as a thread and grabs the state of the projector and if the projector is on it will automatically adjust the bulb as needed.
If you like the work I have been doing then feel free to Image

User avatar
kgschlosser
Site Admin
Posts: 5214
Joined: Fri Jun 05, 2015 5:43 am
Location: Rocky Mountains, Colorado USA

Re: TCP Control for Panasonic projector

Post by kgschlosser » Thu Oct 17, 2019 7:31 am

maybe I will make this into a plugin where a user can enter in a range of the solar metrics and if the sun is within that range then it will trigger an event... and then trigger an event when it is out of that range. add an action to print out the current metrics so they will be able to collect those numbers in order to enter them.
If you like the work I have been doing then feel free to Image

Post Reply